diff options
2135 files changed, 69909 insertions, 60329 deletions
diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 0000000000..17fcaa4360 --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,13 @@ +engines: + rubocop: + enabled: true + +ratings: + paths: + - "**.rb" + +exclude_paths: + - ci/ + - guides/ + - tasks/ + - tools/ diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..f6276855ce --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.rb diff=ruby +*.gemspec diff=ruby diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 0000000000..2d071d4a71 --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,15 @@ +### Steps to reproduce + +(Guidelines for creating a bug report are [available +here](http://guides.rubyonrails.org/contributing_to_ruby_on_rails.html#creating-a-bug-report)) + +### Expected behavior +Tell us what should happen + +### Actual behavior +Tell us what happens instead + +### System configuration +**Rails version**: + +**Ruby version**: diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000..214d63740c --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,21 @@ +### Summary + +Provide a general description of the code changes in your pull +request... were there any bugs you had fixed? If so, mention them. If +these bugs have open GitHub issues, be sure to tag them here as well, +to keep the conversation linked together. + +### Other Information + +If there's anything else that's important and relevant to your pull +request, mention that information here. This could include +benchmarks, or other information. + +If you are updating any of the CHANGELOG files or are asked to update the +CHANGELOG files by reviewers, please add the CHANGELOG entry at the top of the file. + +Finally, if your pull request affects documentation or any non-code +changes, guidelines for those changes are [available +here](http://guides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation) + +Thanks for contributing to Rails! diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000000..985a036556 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,98 @@ +AllCops: + TargetRubyVersion: 2.2 + # RuboCop has a bunch of cops enabled by default. This setting tells RuboCop + # to ignore them, so only the ones explicitly set in this file are enabled. + DisabledByDefault: true + Exclude: + - '**/templates/**/*' + - '**/vendor/**/*' + +# Prefer &&/|| over and/or. +Style/AndOr: + Enabled: true + +# Do not use braces for hash literals when they are the last argument of a +# method call. +Style/BracesAroundHashParameters: + Enabled: true + +# Align `when` with `case`. +Style/CaseIndentation: + Enabled: true + +# Align comments with method definitions. +Style/CommentIndentation: + Enabled: true + +# No extra empty lines. +Style/EmptyLines: + Enabled: true + +# In a regular class definition, no empty lines around the body. +Style/EmptyLinesAroundClassBody: + Enabled: true + +# In a regular module definition, no empty lines around the body. +Style/EmptyLinesAroundModuleBody: + Enabled: true + +# Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }. +Style/HashSyntax: + Enabled: true + +# Method definitions after `private` or `protected` isolated calls need one +# extra level of indentation. +Style/IndentationConsistency: + Enabled: true + EnforcedStyle: rails + +# Two spaces, no tabs (for indentation). +Style/IndentationWidth: + Enabled: true + +# Defining a method with parameters needs parentheses. +Style/MethodDefParentheses: + Enabled: true + +# Use `foo {}` not `foo{}`. +Style/SpaceBeforeBlockBraces: + Enabled: true + +# Use `foo { bar }` not `foo {bar}`. +Style/SpaceInsideBlockBraces: + Enabled: true + +# Use `{ a: 1 }` not `{a:1}`. +Style/SpaceInsideHashLiteralBraces: + Enabled: true + +# Check quotes usage according to lint rule below. +Style/StringLiterals: + Enabled: true + EnforcedStyle: double_quotes + +# Detect hard tabs, no hard tabs. +Style/Tab: + Enabled: true + +# Blank lines should not have any spaces. +Style/TrailingBlankLines: + Enabled: true + +# No trailing whitespace. +Style/TrailingWhitespace: + Enabled: true + +# Use quotes for string literals when they are enough. +Style/UnneededPercentQ: + Enabled: true + +# Align `end` with the matching keyword or starting expression except for +# assignments, where it should be aligned with the LHS. +Lint/EndAlignment: + Enabled: true + AlignWith: variable + +# Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg. +Lint/RequireParentheses: + Enabled: true diff --git a/.travis.yml b/.travis.yml index ae38617b99..d29ef1702a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,36 +1,74 @@ language: ruby sudo: false -script: 'ci/travis.rb' + +cache: + bundler: true + directories: + - /tmp/cache/unicode_conformance + - /tmp/beanstalkd-1.10 + +services: + - memcached + - redis + - rabbitmq + +addons: + postgresql: "9.4" + +bundler_args: --without test --jobs 3 --retry 3 before_install: - - gem install bundler - "rm ${BUNDLE_GEMFILE}.lock" - - curl -L https://github.com/kr/beanstalkd/archive/v1.10.tar.gz | tar xz -C /tmp - - cd /tmp/beanstalkd-1.10/ - - make - - ./beanstalkd & - - cd $TRAVIS_BUILD_DIR + - "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" + before_script: - bundle update -cache: bundler + + # Set Sauce Labs username and access key. Obfuscated, purposefully not encrypted. + # Decodes to e.g. `export VARIABLE=VALUE` + - $(base64 --decode <<< "ZXhwb3J0IFNBVUNFX0FDQ0VTU19LRVk9YTAzNTM0M2YtZTkyMi00MGIzLWFhM2MtMDZiM2VhNjM1YzQ4") + - $(base64 --decode <<< "ZXhwb3J0IFNBVUNFX1VTRVJOQU1FPXJ1YnlvbnJhaWxz") + +script: 'ci/travis.rb' + env: matrix: - "GEM=railties" - "GEM=ap" - "GEM=ac" + - "GEM=ac:integration" - "GEM=am,amo,as,av,aj" + - "GEM=as PRESERVE_TIMEZONES=1" - "GEM=ar:mysql2" - "GEM=ar:sqlite3" - "GEM=ar:postgresql" - "GEM=aj:integration" - "GEM=guides" + rvm: - - 2.2.4 - - 2.3.0 + - 2.2.5 + - 2.3.1 - ruby-head + matrix: + include: + # Latest compiled version in http://rubies.travis-ci.org + - rvm: 2.3.1 + env: + - "GEM=ar:mysql2" + addons: + mariadb: 10.0 + - rvm: jruby-9.0.5.0 + jdk: oraclejdk8 + env: + - "JRUBY_OPTS='--dev -J-Xmx1024M'" + - "GEM='ap'" allow_failures: - rvm: ruby-head + - rvm: jruby-9.0.5.0 fast_finish: true + notifications: email: false irc: @@ -43,10 +81,3 @@ notifications: on_failure: always rooms: - secure: "YA1alef1ESHWGFNVwvmVGCkMe4cUy4j+UcNvMUESraceiAfVyRMAovlQBGs6\n9kBRm7DHYBUXYC2ABQoJbQRLDr/1B5JPf/M8+Qd7BKu8tcDC03U01SMHFLpO\naOs/HLXcDxtnnpL07tGVsm0zhMc5N8tq4/L3SHxK7Vi+TacwQzI=" -bundler_args: --without test --jobs 3 --retry 3 -services: - - memcached - - redis - - rabbitmq -addons: - postgresql: "9.4" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 961b48733c..f6ebef7e89 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,6 +21,10 @@ * Before submitting, please read the [Contributing to Ruby on Rails](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html) guide to know more about coding conventions and benchmarks. +#### **Did you fix whitespace, format code, or make a purely cosmetic patch?** + +Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability of Rails will generally not be accepted (read more about [our rationales behind this decision](https://github.com/rails/rails/pull/13771#issuecomment-32746700)). + #### **Do you intend to add a new feature or change an existing one?** * Suggest your change in the [rubyonrails-core mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core) and start writing code. @@ -1,74 +1,81 @@ -source 'https://rubygems.org' +source "https://rubygems.org" + +git_source(:github) do |repo_name| + repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") + "https://github.com/#{repo_name}.git" +end gemspec # We need a newish Rake since Active Job sets its test tasks' descriptions. -gem 'rake', '>= 10.3' +gem "rake", ">= 11.1" -# This needs to be with require false to ensure correct loading order, as has to +# This needs to be with require false to ensure correct loading order, as it has to # be loaded after loading the test library. -gem 'mocha', '~> 0.14', require: false +gem "mocha", "~> 0.14", require: false -gem 'rack-cache', '~> 1.2' -gem 'jquery-rails' -gem 'coffee-rails', '~> 4.1.0' -gem 'turbolinks', github: 'turbolinks/turbolinks-rails' +gem "rack-cache", "~> 1.2" +gem "jquery-rails" +gem "coffee-rails" +gem "sass-rails" +gem "turbolinks", "~> 5" # require: false so bcrypt is loaded only when has_secure_password is used. # This is to avoid Active Model (and by extension the entire framework) # being dependent on a binary library. -platforms :mingw, :x64_mingw, :mswin, :mswin64 do - gem 'bcrypt-ruby', '~> 3.0.0', require: false -end - -platforms :ruby, :jruby, :rbx do - gem 'bcrypt', '~> 3.1.10', require: false -end +gem "bcrypt", "~> 3.1.11", require: false # This needs to be with require false to avoid it being automatically loaded by # sprockets. -gem 'uglifier', '>= 1.3.0', require: false +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 +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 group :doc do - gem 'sdoc', '~> 0.4.0' - gem 'redcarpet', '~> 3.2.3', platforms: :ruby - gem 'w3c_validators' - gem 'kindlerb', '0.1.1' + gem "sdoc", "1.0.0.beta2" + gem "redcarpet", "~> 3.2.3", platforms: :ruby + gem "w3c_validators" + gem "kindlerb", ">= 1.0.1" end # Active Support. -gem 'dalli', '>= 2.2.1' -gem 'listen', '~> 3.0.5', require: false +gem "dalli", ">= 2.2.1" +gem "listen", ">= 3.0.5", "< 3.2", require: false # Active Job. group :job do - gem 'resque', require: false - gem 'resque-scheduler', require: false - gem 'sidekiq', require: false - gem 'sucker_punch', require: false - 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 - gem 'backburner', require: false - gem 'qu-rails', github: "bkeepers/qu", branch: "master", require: false - gem 'qu-redis', require: false - gem 'delayed_job_active_record', require: false - gem 'sequel', require: false + gem "resque", github: "resque/resque", require: false + gem "resque-scheduler", require: false + gem "sidekiq", require: false + gem "sucker_punch", require: false + gem "delayed_job", require: false, github: "collectiveidea/delayed_job" + gem "queue_classic", github: "QueueClassic/queue_classic", branch: "master", require: false, platforms: :ruby + gem "sneakers", require: false + gem "que", require: false + gem "backburner", require: false + #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 "sequel", require: false end # Action Cable group :cable do - gem 'puma', require: false + gem "puma", require: false + + gem "em-hiredis", require: false + gem "hiredis", require: false + gem "redis", require: false - gem 'em-hiredis', require: false - gem 'hiredis', require: false - gem 'redis', require: false + gem "websocket-client-simple", github: "matthewd/websocket-client-simple", branch: "close-race", require: false - gem 'faye-websocket', require: false + gem "blade", require: false, platforms: [:ruby] + gem "blade-sauce_labs_plugin", require: false, platforms: [:ruby] end # Add your own local bundler stuff. @@ -77,43 +84,43 @@ instance_eval File.read local_gemfile if File.exist? local_gemfile group :test do # FIX: Our test suite isn't ready to run in random order yet. - gem 'minitest', '< 5.3.4' + gem "minitest", "< 5.3.4" platforms :mri do - gem 'stackprof' - gem 'byebug' + gem "stackprof" + gem "byebug" end - gem 'benchmark-ips' + gem "benchmark-ips" end platforms :ruby, :mswin, :mswin64, :mingw, :x64_mingw do - gem 'nokogiri', '>= 1.6.7.1' + gem "nokogiri", ">= 1.6.8" # Needed for compiling the ActionDispatch::Journey parser. - gem 'racc', '>=1.4.6', require: false + gem "racc", ">=1.4.6", require: false # Active Record. - gem 'sqlite3', '~> 1.3.6' + gem "sqlite3", "~> 1.3.6" group :db do - gem 'pg', '>= 0.18.0' - gem 'mysql2', '>= 0.4.0' + gem "pg", ">= 0.18.0" + gem "mysql2", ">= 0.4.4" end end platforms :jruby do - if ENV['AR_JDBC'] - gem 'activerecord-jdbcsqlite3-adapter', github: 'jruby/activerecord-jdbc-adapter', branch: 'master' + if ENV["AR_JDBC"] + gem "activerecord-jdbcsqlite3-adapter", github: "jruby/activerecord-jdbc-adapter", branch: "master" group :db do - gem 'activerecord-jdbcmysql-adapter', github: 'jruby/activerecord-jdbc-adapter', branch: 'master' - gem 'activerecord-jdbcpostgresql-adapter', github: 'jruby/activerecord-jdbc-adapter', branch: 'master' + gem "activerecord-jdbcmysql-adapter", github: "jruby/activerecord-jdbc-adapter", branch: "master" + gem "activerecord-jdbcpostgresql-adapter", github: "jruby/activerecord-jdbc-adapter", branch: "master" end else - gem 'activerecord-jdbcsqlite3-adapter', '>= 1.3.0' + gem "activerecord-jdbcsqlite3-adapter", ">= 1.3.0" group :db do - gem 'activerecord-jdbcmysql-adapter', '>= 1.3.0' - gem 'activerecord-jdbcpostgresql-adapter', '>= 1.3.0' + gem "activerecord-jdbcmysql-adapter", ">= 1.3.0" + gem "activerecord-jdbcpostgresql-adapter", ">= 1.3.0" end end end @@ -121,17 +128,18 @@ end platforms :rbx do # The rubysl-yaml gem doesn't ship with Psych by default as it needs # libyaml that isn't always available. - gem 'psych', '~> 2.0' + gem "psych", "~> 2.0" end # Gems that are necessary for Active Record tests with Oracle. -if ENV['ORACLE_ENHANCED'] +if ENV["ORACLE_ENHANCED"] platforms :ruby do - gem 'ruby-oci8', '~> 2.2' + gem "ruby-oci8", "~> 2.2" end - gem 'activerecord-oracle_enhanced-adapter', github: 'rsim/oracle-enhanced', branch: 'master' + gem "activerecord-oracle_enhanced-adapter", github: "rsim/oracle-enhanced", branch: "master" end # A gem necessary for Active Record tests with IBM DB. -gem 'ibm_db' if ENV['IBM_DB'] -gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] +gem "ibm_db" if ENV["IBM_DB"] +gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby] +gem "wdm", ">= 0.1.0", platforms: [:mingw, :mswin, :x64_mingw, :mswin64] diff --git a/Gemfile.lock b/Gemfile.lock index 0d2ce7b97f..4257685c46 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,93 +1,116 @@ GIT - remote: git://github.com/QueueClassic/queue_classic.git - revision: 2e3b624f3043849751b24a758d8c156337ead862 + remote: https://github.com/QueueClassic/queue_classic.git + revision: 51d56ca6fa2fdf1eeffdffd702ae1cc0940b5156 branch: master specs: queue_classic (3.2.0.RC1) - pg (>= 0.17, < 0.19) + pg (>= 0.17, < 0.20) GIT - remote: git://github.com/bkeepers/qu.git - revision: d098e2657c92e89a6413bebd9c033930759c061f - branch: master + remote: https://github.com/collectiveidea/delayed_job.git + revision: e3772d4f0c8470d0fcba00c86ca3bc4f5e876830 specs: - qu (0.2.0) - qu-rails (0.2.0) - qu (= 0.2.0) - railties (>= 3.2, < 5) - qu-redis (0.2.0) - qu (= 0.2.0) - redis-namespace + delayed_job (4.1.2) + activesupport (>= 3.0, < 5.1) GIT - remote: git://github.com/sass/sass.git - revision: ac8e7aa8e99c425237c708f347f76495a2e18519 - branch: stable + remote: https://github.com/collectiveidea/delayed_job_active_record.git + revision: 36f434c4fd660e8f11ce932be117e9c71dde7212 specs: - sass (3.4.21) + delayed_job_active_record (4.1.1) + activerecord (>= 3.0, < 5.1) + delayed_job (>= 3.0, < 5) GIT - remote: git://github.com/turbolinks/turbolinks-rails.git - revision: 1604dcd7bad911f1471d65e4f47cd19d844354f1 + remote: https://github.com/matthewd/rb-inotify.git + revision: 90553518d1fb79aedc98a3036c59bd2b6731ac40 + branch: close-handling specs: - turbolinks (5.0.0.beta1) - turbolinks-source + rb-inotify (0.9.7) + ffi (>= 0.5.0) + +GIT + remote: https://github.com/matthewd/websocket-client-simple.git + revision: e161305f1a466b9398d86df3b1731b03362da91b + branch: close-race + specs: + websocket-client-simple (0.3.0) + event_emitter + websocket + +GIT + remote: https://github.com/resque/resque.git + revision: 20d885065ac19e7f7d7a982f4ed1296083db0300 + 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) PATH remote: . specs: - actioncable (5.0.0.beta2) - actionpack (= 5.0.0.beta2) + actioncable (5.1.0.alpha) + actionpack (= 5.1.0.alpha) nio4r (~> 1.2) websocket-driver (~> 0.6.1) - actionmailer (5.0.0.beta2) - actionpack (= 5.0.0.beta2) - actionview (= 5.0.0.beta2) - activejob (= 5.0.0.beta2) + actionmailer (5.1.0.alpha) + actionpack (= 5.1.0.alpha) + actionview (= 5.1.0.alpha) + activejob (= 5.1.0.alpha) mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (5.0.0.beta2) - actionview (= 5.0.0.beta2) - activesupport (= 5.0.0.beta2) - rack (~> 2.x) + rails-dom-testing (~> 2.0) + actionpack (5.1.0.alpha) + actionview (= 5.1.0.alpha) + activesupport (= 5.1.0.alpha) + rack (~> 2.0) rack-test (~> 0.6.3) - rails-dom-testing (~> 1.0, >= 1.0.5) + rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.0.0.beta2) - activesupport (= 5.0.0.beta2) + actionview (5.1.0.alpha) + activesupport (= 5.1.0.alpha) builder (~> 3.1) erubis (~> 2.7.0) - rails-dom-testing (~> 1.0, >= 1.0.5) + rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - activejob (5.0.0.beta2) - activesupport (= 5.0.0.beta2) + activejob (5.1.0.alpha) + activesupport (= 5.1.0.alpha) globalid (>= 0.3.6) - activemodel (5.0.0.beta2) - activesupport (= 5.0.0.beta2) - activerecord (5.0.0.beta2) - activemodel (= 5.0.0.beta2) - activesupport (= 5.0.0.beta2) + activemodel (5.1.0.alpha) + activesupport (= 5.1.0.alpha) + activerecord (5.1.0.alpha) + activemodel (= 5.1.0.alpha) + activesupport (= 5.1.0.alpha) arel (~> 7.0) - activesupport (5.0.0.beta2) - concurrent-ruby (~> 1.0) + activesupport (5.1.0.alpha) + concurrent-ruby (~> 1.0, >= 1.0.2) i18n (~> 0.7) minitest (~> 5.1) tzinfo (~> 1.1) - rails (5.0.0.beta2) - actioncable (= 5.0.0.beta2) - actionmailer (= 5.0.0.beta2) - actionpack (= 5.0.0.beta2) - actionview (= 5.0.0.beta2) - activejob (= 5.0.0.beta2) - activemodel (= 5.0.0.beta2) - activerecord (= 5.0.0.beta2) - activesupport (= 5.0.0.beta2) + rails (5.1.0.alpha) + actioncable (= 5.1.0.alpha) + actionmailer (= 5.1.0.alpha) + actionpack (= 5.1.0.alpha) + actionview (= 5.1.0.alpha) + activejob (= 5.1.0.alpha) + activemodel (= 5.1.0.alpha) + activerecord (= 5.1.0.alpha) + activesupport (= 5.1.0.alpha) bundler (>= 1.3.0, < 2.0) - railties (= 5.0.0.beta2) + railties (= 5.1.0.alpha) sprockets-rails (>= 2.0.0) - railties (5.0.0.beta2) - actionpack (= 5.0.0.beta2) - activesupport (= 5.0.0.beta2) + railties (5.1.0.alpha) + actionpack (= 5.1.0.alpha) + activesupport (= 5.1.0.alpha) method_source rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) @@ -95,140 +118,195 @@ PATH GEM remote: https://rubygems.org/ specs: + addressable (2.4.0) amq-protocol (2.0.1) - arel (7.0.0) - backburner (1.2.0) + arel (7.1.2) + backburner (1.3.1) beaneater (~> 1.0) + concurrent-ruby (~> 1.0.1) dante (> 0.1.5) - bcrypt (3.1.10) - bcrypt-ruby (3.0.1) - bcrypt-ruby (3.0.1-x86-mingw32) + bcrypt (3.1.11) + bcrypt (3.1.11-x64-mingw32) + bcrypt (3.1.11-x86-mingw32) beaneater (1.0.0) - benchmark-ips (2.3.0) + benchmark-ips (2.7.2) + blade (0.5.6) + activesupport (>= 3.0.0) + blade-qunit_adapter (~> 1.20.0) + coffee-script + coffee-script-source + curses (~> 1.0.0) + 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 (1.20.0) + blade-sauce_labs_plugin (0.5.3) + childprocess + faraday + selenium-webdriver builder (3.2.2) bunny (2.2.2) amq-protocol (>= 2.0.1) - byebug (8.2.1) - coffee-rails (4.1.1) + byebug (9.0.6) + childprocess (0.5.9) + ffi (~> 1.0, >= 1.0.11) + coffee-rails (4.2.1) coffee-script (>= 2.2.0) - railties (>= 4.0.0, < 5.1.x) + railties (>= 4.0.0, < 5.2.x) coffee-script (2.4.1) coffee-script-source execjs coffee-script-source (1.10.0) - concurrent-ruby (1.0.0) + concurrent-ruby (1.0.2) connection_pool (2.2.0) - dalli (2.7.5) + cookiejar (0.3.3) + curses (1.0.2) + daemons (1.2.4) + dalli (2.7.6) dante (0.2.0) - delayed_job (4.1.1) - activesupport (>= 3.0, < 5.0) - delayed_job_active_record (4.1.0) - activerecord (>= 3.0, < 5) - delayed_job (>= 3.0, < 5) em-hiredis (0.3.1) eventmachine (~> 1.0) hiredis (~> 0.6.0) + em-http-request (1.1.5) + addressable (>= 2.3.4) + cookiejar (!= 0.3.1) + em-socksify (>= 0.3) + eventmachine (>= 1.0.3) + http_parser.rb (>= 0.6.0) + em-socksify (0.3.1) + eventmachine (>= 1.0.0.beta.4) erubis (2.7.0) - eventmachine (1.0.9.1) - execjs (2.6.0) - faye-websocket (0.10.2) + event_emitter (0.2.5) + eventmachine (1.2.0.1) + eventmachine (1.2.0.1-x64-mingw32) + eventmachine (1.2.0.1-x86-mingw32) + execjs (2.7.0) + faraday (0.9.2) + multipart-post (>= 1.2, < 3) + faye (1.2.2) + cookiejar (>= 0.3.0) + em-http-request (>= 0.3.0) eventmachine (>= 0.12.0) + faye-websocket (>= 0.9.1) + multi_json (>= 1.0.0) + rack (>= 1.0.0) websocket-driver (>= 0.5.1) - ffi (1.9.10) - ffi (1.9.10-x64-mingw32) - ffi (1.9.10-x86-mingw32) - globalid (0.3.6) + faye-websocket (0.10.4) + 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) + 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.1.0) - rails-dom-testing (~> 1.0) + jquery-rails (4.2.1) + rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (1.8.3) - kindlerb (0.1.1) + json (2.0.2) + kindlerb (1.0.1) mustache nokogiri - listen (3.0.5) - rb-fsevent (>= 0.9.3) - rb-inotify (>= 0.9) + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) loofah (2.0.3) nokogiri (>= 1.5.9) - mail (2.6.3) - mime-types (>= 1.16, < 3) + mail (2.6.4) + mime-types (>= 1.16, < 4) metaclass (0.0.4) method_source (0.8.2) - mime-types (2.99) - mini_portile2 (2.0.0) + mime-types (3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2016.0521) + mini_portile2 (2.1.0) minitest (5.3.3) mocha (0.14.0) metaclass (~> 0.0.1) mono_logger (1.1.0) - multi_json (1.11.2) - mustache (1.0.2) - mysql2 (0.4.2) - mysql2 (0.4.2-x64-mingw32) - mysql2 (0.4.2-x86-mingw32) + multi_json (1.12.1) + multipart-post (2.0.0) + mustache (1.0.3) + mysql2 (0.4.5) + mysql2 (0.4.5-x64-mingw32) + mysql2 (0.4.5-x86-mingw32) nio4r (1.2.1) - nokogiri (1.6.7.2) - mini_portile2 (~> 2.0.0.rc2) - nokogiri (1.6.7.2-x64-mingw32) - mini_portile2 (~> 2.0.0.rc2) - nokogiri (1.6.7.2-x86-mingw32) - mini_portile2 (~> 2.0.0.rc2) - pg (0.18.4) - pg (0.18.4-x64-mingw32) - pg (0.18.4-x86-mingw32) - psych (2.0.17) - puma (2.16.0) - que (0.11.2) + nokogiri (1.6.8.1) + mini_portile2 (~> 2.1.0) + nokogiri (1.6.8.1-x64-mingw32) + mini_portile2 (~> 2.1.0) + nokogiri (1.6.8.1-x86-mingw32) + mini_portile2 (~> 2.1.0) + pg (0.19.0) + pg (0.19.0-x64-mingw32) + pg (0.19.0-x86-mingw32) + psych (2.1.1) + puma (3.6.0) + qu (0.2.0) + multi_json + qu-redis (0.2.0) + qu (= 0.2.0) + redis-namespace + simple_uuid + que (0.12.0) racc (1.4.14) - rack (2.0.0.alpha) - json - rack-cache (1.5.1) + rack (2.0.1) + rack-cache (1.6.1) rack (>= 0.4) + rack-protection (1.5.3) + rack rack-test (0.6.3) rack (>= 1.0) - rails-deprecated_sanitizer (1.0.3) - activesupport (>= 4.2.0.alpha) - rails-dom-testing (1.0.7) - activesupport (>= 4.2.0.beta, < 5.0) + rails-dom-testing (2.0.1) + activesupport (>= 4.2.0, < 6.0) nokogiri (~> 1.6.0) - rails-deprecated_sanitizer (>= 1.0.1) rails-html-sanitizer (1.0.3) loofah (~> 2.0) - rake (10.5.0) + rake (11.3.0) rb-fsevent (0.9.7) - rb-inotify (0.9.5) - ffi (>= 0.5.0) - rdoc (4.2.1) + rdoc (5.0.0.beta2) redcarpet (3.2.3) - redis (3.2.2) + redis (3.3.1) redis-namespace (1.5.2) redis (~> 3.0, >= 3.0.4) - resque (1.25.2) - mono_logger (~> 1.0) - multi_json (~> 1.0) - redis-namespace (~> 1.3) - sinatra (>= 0.9.2) - vegas (~> 0.1.2) - resque-scheduler (4.0.0) + resque-scheduler (4.3.0) mono_logger (~> 1.0) - redis (~> 3.0) - resque (~> 1.25) - rufus-scheduler (~> 3.0) - rufus-scheduler (3.2.0) - sdoc (0.4.1) - json (~> 1.7, >= 1.7.7) - rdoc (~> 4.0) - sequel (4.30.0) + redis (~> 3.3) + resque (~> 1.26) + rufus-scheduler (~> 3.2) + ruby_dep (1.4.0) + rubyzip (1.2.0) + rufus-scheduler (3.2.2) + 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 (2.53.4) + childprocess (~> 0.5) + rubyzip (~> 1.0) + websocket (~> 1.0) + sequel (4.39.0) serverengine (1.5.11) sigdump (~> 0.2.2) - sidekiq (4.0.2) + sidekiq (4.2.2) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) + rack-protection (~> 1.5) redis (~> 3.2, >= 3.2.1) - sigdump (0.2.3) + sigdump (0.2.4) + simple_uuid (0.4.0) sinatra (1.0) rack (>= 1.0) sneakers (2.3.5) @@ -236,36 +314,46 @@ GEM serverengine (~> 1.5.11) thor thread (~> 0.1.7) - sprockets (3.5.2) + sprockets (3.7.0) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.0.1) + sprockets-export (0.9.1) + sprockets-rails (3.2.0) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) sqlite3 (1.3.11) sqlite3 (1.3.11-x64-mingw32) sqlite3 (1.3.11-x86-mingw32) - stackprof (0.2.8) - sucker_punch (2.0.0) + stackprof (0.2.10) + sucker_punch (2.0.2) concurrent-ruby (~> 1.0.0) + thin (1.7.0) + daemons (~> 1.0, >= 1.0.9) + eventmachine (~> 1.0, >= 1.0.4) + rack (>= 1, < 3) thor (0.19.1) thread (0.1.7) thread_safe (0.3.5) - turbolinks-source (5.0.0.beta1.1) + tilt (2.0.5) + turbolinks (5.0.1) + turbolinks-source (~> 5) + turbolinks-source (5.0.0) tzinfo (1.2.2) thread_safe (~> 0.1) - tzinfo-data (1.2015.7) + tzinfo-data (1.2016.7) tzinfo (>= 1.0.0) - uglifier (2.7.2) - execjs (>= 0.3.0) - json (>= 1.8.0) + uglifier (3.0.2) + execjs (>= 0.3.0, < 3) + useragent (0.16.8) vegas (0.1.11) rack (>= 1.0.0) w3c_validators (1.2) json nokogiri - websocket-driver (0.6.3) + wdm (0.1.1) + websocket (1.2.3) + websocket-driver (0.6.4) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.2) @@ -279,51 +367,54 @@ DEPENDENCIES activerecord-jdbcpostgresql-adapter (>= 1.3.0) activerecord-jdbcsqlite3-adapter (>= 1.3.0) backburner - bcrypt (~> 3.1.10) - bcrypt-ruby (~> 3.0.0) + bcrypt (~> 3.1.11) benchmark-ips + blade + blade-sauce_labs_plugin byebug - coffee-rails (~> 4.1.0) + coffee-rails dalli (>= 2.2.1) - delayed_job - delayed_job_active_record + delayed_job! + delayed_job_active_record! em-hiredis - faye-websocket hiredis jquery-rails - kindlerb (= 0.1.1) - listen (~> 3.0.5) + kindlerb (>= 1.0.1) + listen (>= 3.0.5, < 3.2) minitest (< 5.3.4) mocha (~> 0.14) - mysql2 (>= 0.4.0) - nokogiri (>= 1.6.7.1) + mysql2 (>= 0.4.4) + nokogiri (>= 1.6.8) pg (>= 0.18.0) psych (~> 2.0) puma - qu-rails! qu-redis que queue_classic! racc (>= 1.4.6) rack-cache (~> 1.2) rails! - rake (>= 10.3) + rake (>= 11.1) + rb-inotify! redcarpet (~> 3.2.3) redis - resque + resque! resque-scheduler sass! - sdoc (~> 0.4.0) + sass-rails + sdoc (= 1.0.0.beta2) sequel sidekiq sneakers sqlite3 (~> 1.3.6) stackprof sucker_punch - turbolinks! + turbolinks (~> 5) tzinfo-data uglifier (>= 1.3.0) w3c_validators + wdm (>= 0.1.0) + websocket-client-simple! BUNDLED WITH - 1.11.2 + 1.13.6 diff --git a/MIT-LICENSE b/MIT-LICENSE new file mode 100644 index 0000000000..40235833ba --- /dev/null +++ b/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2005-2016 David Heinemeier Hansson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/RAILS_VERSION b/RAILS_VERSION index 60b8d0bf66..8ea1016081 100644 --- a/RAILS_VERSION +++ b/RAILS_VERSION @@ -1 +1 @@ -5.0.0.beta2 +5.1.0.alpha @@ -1,4 +1,4 @@ -## Welcome to Rails +# Welcome to Rails Rails is a web-application framework that includes everything needed to create database-backed web applications according to the @@ -35,7 +35,7 @@ or to generate the body of an email. In Rails, View generation is handled by Act You can read more about Action View in its [README](actionview/README.rdoc). Active Record, Active Model, Action Pack, and Action View can each be used independently outside Rails. -In addition to them, Rails also comes with Action Mailer ([README](actionmailer/README.rdoc)), a library +In addition to that, Rails also comes with Action Mailer ([README](actionmailer/README.rdoc)), a library to generate and send emails; Active Job ([README](activejob/README.md)), a framework for declaring jobs and making them run on a variety of queueing backends; Action Cable ([README](actioncable/README.md)), a framework to diff --git a/RELEASING_RAILS.md b/RELEASING_RAILS.md index 037aa8c119..4e6b811d3e 100644 --- a/RELEASING_RAILS.md +++ b/RELEASING_RAILS.md @@ -20,8 +20,8 @@ http://travis-ci.org/rails/rails ### Is Sam Ruby happy? If not, make him happy. Sam Ruby keeps a [test suite](https://github.com/rubys/awdwr) that makes -sure the code samples in his book -([Agile Web Development with Rails](https://pragprog.com/titles/rails4/agile-web-development-with-rails-4th-edition)) +sure the code samples in his book +([Agile Web Development with Rails](https://pragprog.com/titles/rails5/agile-web-development-with-rails-5th-edition)) all work. These are valuable system tests for Rails. You can check the status of these tests here: @@ -47,7 +47,7 @@ Ruby implementors have high stakes in making sure Rails works. Be kind and give them a heads up that Rails will be released soonish. This is only required for major and minor releases, bugfix releases aren't a -big enough deal, and are supposed to be backwards compatible. +big enough deal, and are supposed to be backward compatible. Send an email just giving a heads up about the upcoming release to these lists: @@ -103,18 +103,24 @@ branch. Run `rake install` to generate the gems and install them locally. Then try generating a new app and ensure that nothing explodes. +Verify that Action Cable's package.json is updated with the RC version. + This will stop you from looking silly when you push an RC to rubygems.org and then realize it is broken. -### Release the gem. +### Release to RubyGems and NPM. + +IMPORTANT: The Action Cable client is released as an NPM package, so you must +have Node.js installed, have an NPM account (npmjs.com), and be an actioncable +package owner (`npm owner ls actioncable`) to do a full release. Do not release +until you're set up with NPM! -IMPORTANT: Due to YAML parse problems on the rubygems.org server, it is safest -to use Ruby 1.8 when releasing. +Run `rake release`. This will populate the gemspecs and NPM package.json with +the current RAILS_VERSION, commit the changes, tag it, and push the gems to +rubygems.org. -Run `rake release`. This will populate the gemspecs with data from -RAILS_VERSION, commit the changes, tag it, and push the gems to rubygems.org. -Here are the commands that `rake release` should use, so you can understand -what to do in case anything goes wrong: +Here are the commands that `rake release` uses so you can understand what to do +in case anything goes wrong: ``` $ rake all:build @@ -122,7 +128,7 @@ $ git commit -am'updating RAILS_VERSION' $ git tag -m 'v3.0.10.rc1 release' v3.0.10.rc1 $ git push $ git push --tags -$ for i in $(ls pkg); do gem push $i; done +$ for i in $(ls pkg); do gem push $i; npm publish; done ``` ### Send Rails release announcements @@ -144,7 +150,7 @@ break existing applications. ### Post the announcement to the Rails blog. -If you used Markdown format for your email, you can just paste it in to the +If you used Markdown format for your email, you can just paste it into the blog. * http://weblog.rubyonrails.org @@ -1,27 +1,27 @@ -require 'net/http' +require "net/http" -$:.unshift File.expand_path('..', __FILE__) +$:.unshift File.expand_path("..", __FILE__) require "tasks/release" -require 'railties/lib/rails/api/task' +require "railties/lib/rails/api/task" desc "Build gem files for all projects" -task :build => "all:build" +task build: "all:build" desc "Prepare the release" -task :prep_release => "all:prep_release" +task prep_release: "all:prep_release" desc "Release all gems to rubygems and create a tag" -task :release => "all:release" +task release: "all:release" -desc 'Run all tests by default' -task :default => %w(test test:isolated) +desc "Run all tests by default" +task default: %w(test test:isolated) %w(test test:isolated package gem).each do |task_name| desc "Run #{task_name} task for all projects" task task_name do errors = [] FRAMEWORKS.each do |project| - system(%(cd #{project} && #{$0} #{task_name})) || errors << project + system(%(cd #{project} && #{$0} #{task_name} --trace)) || errors << project end fail("Errors in #{errors.join(', ')}") unless errors.empty? end @@ -30,23 +30,23 @@ end desc "Smoke-test all projects" task :smoke do (FRAMEWORKS - %w(activerecord)).each do |project| - system %(cd #{project} && #{$0} test:isolated) + system %(cd #{project} && #{$0} test:isolated --trace) end - system %(cd activerecord && #{$0} sqlite3:isolated_test) + system %(cd activerecord && #{$0} sqlite3:isolated_test --trace) end desc "Install gems for all projects." -task :install => "all:install" +task install: "all:install" desc "Generate documentation for the Rails framework" -if ENV['EDGE'] - Rails::API::EdgeTask.new('rdoc') +if ENV["EDGE"] + Rails::API::EdgeTask.new("rdoc") else - Rails::API::StableTask.new('rdoc') + Rails::API::StableTask.new("rdoc") end -desc 'Bump all versions to match RAILS_VERSION' -task :update_versions => "all:update_versions" +desc "Bump all versions to match RAILS_VERSION" +task update_versions: "all:update_versions" # We have a webhook configured in GitHub that gets invoked after pushes. # This hook triggers the following tasks: @@ -57,10 +57,10 @@ task :update_versions => "all:update_versions" # * if there's a new stable tag, generates and publishes stable docs # # Everything is automated and you do NOT need to run this task normally. -desc 'Publishes docs, run this AFTER a new stable tag has been pushed' +desc "Publishes docs, run this AFTER a new stable tag has been pushed" task :publish_docs do - Net::HTTP.new('api.rubyonrails.org', 8080).start do |http| - request = Net::HTTP::Post.new('/rails-master-hook') + Net::HTTP.new("api.rubyonrails.org", 8080).start do |http| + request = Net::HTTP::Post.new("/rails-master-hook") response = http.request(request) puts response.body end diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md index bfc229d795..d70d32ce07 100644 --- a/actioncable/CHANGELOG.md +++ b/actioncable/CHANGELOG.md @@ -1,35 +1,37 @@ -* Added ActionCable::SubscriptionAdapter::EventedRedis.em_redis_connector/redis_connector and - ActionCable::SubscriptionAdapter::Redis.redis_connector factory methods for redis connections, - so you can overwrite with your own initializers. This is used when you want to use different-than-standard Redis adapters, - like for Makara distributed Redis. +* Permit same-origin connections by default. - *DHH* + New option `config.action_cable.allow_same_origin_as_host = false` + to disable. -## Rails 5.0.0.beta2 (February 01, 2016) ## + *Dávid Halász*, *Matthew Draper* -* Support PostgreSQL pubsub adapter. +* Prevent race where the client could receive and act upon a + subscription confirmation before the channel's `subscribed` method + completed. - *Jon Moss* + Fixes #25381. -* Remove EventMachine dependency. + *Vladimir Dementyev* - *Matthew Draper* +* Buffer writes to websocket connections, to avoid blocking threads + that could be doing more useful things. -* Remove Celluloid dependency. + *Matthew Draper*, *Tinco Andringa* - *Mike Perham* +* Protect against concurrent writes to a websocket connection from + multiple threads; the underlying OS write is not always threadsafe. -* Create notion of an `ActionCable::SubscriptionAdapter`. - Separate out Redis functionality into - `ActionCable::SubscriptionAdapter::Redis`, and add a - PostgreSQL adapter as well. Configuration file for - ActionCable was changed from`config/redis/cable.yml` to - `config/cable.yml`. + *Tinco Andringa* - *Jon Moss* +* Add ActiveSupport::Notifications hook to Broadcaster#broadcast. -## Rails 5.0.0.beta1 (December 18, 2015) ## + *Matthew Wear* -* Added to Rails! +* Close hijacked socket when connection is shut down. - *DHH* + Fixes #25613. + + *Tinco Andringa* + + +Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/actioncable/CHANGELOG.md) for previous changes. diff --git a/actioncable/README.md b/actioncable/README.md index c85d59a1c8..8ad9aeb1f1 100644 --- a/actioncable/README.md +++ b/actioncable/README.md @@ -7,7 +7,6 @@ and scalable. It's a full-stack offering that provides both a client-side JavaScript framework and a server-side Ruby framework. You have access to your full domain model written with Active Record or your ORM of choice. - ## Terminology A single Action Cable server can handle multiple connection instances. It has one @@ -39,7 +38,7 @@ reflections of each unit. ### A full-stack example The first thing you must do is define your `ApplicationCable::Connection` class in Ruby. This -is the place where you authorize the incoming connection, and proceed to establish it +is the place where you authorize the incoming connection, and proceed to establish it, if all is well. Here's the simplest example starting with the server-side connection class: ```ruby @@ -73,7 +72,7 @@ use that to set the `current_user`. By identifying the connection by this same c you're also ensuring that you can later retrieve all open connections by a given user (and potentially disconnect them all if the user is deleted or deauthorized). -Then you should define your `ApplicationCable::Channel` class in Ruby. This is the place where you put +Next, you should define your `ApplicationCable::Channel` class in Ruby. This is the place where you put shared logic between your channels. ```ruby @@ -86,15 +85,20 @@ end The client-side needs to setup a consumer instance of this connection. That's done like so: -```coffeescript -# app/assets/javascripts/cable.coffee -#= require action_cable +```js +// app/assets/javascripts/cable.js +//= require action_cable +//= require_self +//= require_tree ./channels -@App = {} -App.cable = ActionCable.createConsumer("ws://cable.example.com") +(function() { + this.App || (this.App = {}); + + App.cable = ActionCable.createConsumer("ws://cable.example.com"); +}).call(this); ``` -The ws://cable.example.com address must point to your set of Action Cable servers, and it +The `ws://cable.example.com` address must point to your Action Cable server(s), and it must share a cookie namespace with the rest of the application (which may live under http://example.com). This ensures that the signed cookie will be correctly sent. @@ -105,8 +109,8 @@ is defined by declaring channels on the server and allowing the consumer to subs ### Channel example 1: User appearances -Here's a simple example of a channel that tracks whether a user is online or not and what page they're on. -(This is useful for creating presence features like showing a green dot next to a user name if they're online). +Here's a simple example of a channel that tracks whether a user is online or not, and also what page they are currently on. +(This is useful for creating presence features like showing a green dot next to a user's name if they're online). First you declare the server-side channel: @@ -163,7 +167,7 @@ App.cable.subscriptions.create "AppearanceChannel", buttonSelector = "[data-behavior~=appear_away]" install: -> - $(document).on "page:change.appearance", => + $(document).on "turbolinks:load.appearance", => @appear() $(document).on "click.appearance", buttonSelector, => @@ -178,9 +182,9 @@ App.cable.subscriptions.create "AppearanceChannel", ``` Simply calling `App.cable.subscriptions.create` will setup the subscription, which will call `AppearanceChannel#subscribed`, -which in turn is linked to original `App.cable` -> `ApplicationCable::Connection` instances. +which in turn is linked to the original `App.cable` -> `ApplicationCable::Connection` instances. -We then link the client-side `appear` method to `AppearanceChannel#appear(data)`. This is possible because the server-side +Next, we link the client-side `appear` method to `AppearanceChannel#appear(data)`. This is possible because the server-side channel instance will automatically expose the public methods declared on the class (minus the callbacks), so that these can be reached as remote procedure calls via a subscription's `perform` method. @@ -215,7 +219,7 @@ ActionCable.server.broadcast \ "web_notifications_#{current_user.id}", { title: 'New things!', body: 'All the news that is fit to print' } ``` -The `ActionCable.server.broadcast` call places a message in the Redis' 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 `ActionCable.server.broadcast` call places a message in the Action Cable 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 `web_notifications_1` directly to the client by invoking the `#received(data)` callback. The data is the hash sent as the second parameter to the server-side broadcast call, JSON encoded for the trip across the wire, and unpacked for the data argument arriving to `#received`. @@ -234,7 +238,7 @@ class ChatChannel < ApplicationCable::Channel end ``` -Pass an object as the first argument to `subscriptions.create`, and that object will become your params hash in your cable channel. The keyword `channel` is required. +If you pass an object as the first argument to `subscriptions.create`, that object will become the params hash in your cable channel. The keyword `channel` is required. ```coffeescript # Client-side, which assumes you've already requested the right to send web notifications @@ -293,8 +297,7 @@ The rebroadcast will be received by all connected clients, _including_ the clien ### More complete examples -See the [rails/actioncable-examples](http://github.com/rails/actioncable-examples) repository for a full example of how to setup Action Cable in a Rails app and adding channels. - +See the [rails/actioncable-examples](https://github.com/rails/actioncable-examples) repository for a full example of how to setup Action Cable in a Rails app, and how to add channels. ## Configuration @@ -323,7 +326,10 @@ Rails.application.paths.add "config/cable", with: "somewhere/else/cable.yml" ### Allowed Request Origins -Action Cable will only accept requests from specified origins, which are passed to the server config as an array. The origins can be instances of strings or regular expressions, against which a check for match will be performed. +Action Cable will only accept requests from specific origins. + +By default, only an origin matching the cable server itself will be permitted. +Additional origins can be specified using strings or regular expressions, provided in an array. ```ruby Rails.application.config.action_cable.allowed_request_origins = ['http://rubyonrails.com', /http:\/\/ruby.*/] @@ -331,29 +337,36 @@ Rails.application.config.action_cable.allowed_request_origins = ['http://rubyonr When running in the development environment, this defaults to "http://localhost:3000". -To disable and allow requests from any origin: +To disable protection and allow requests from any origin: ```ruby Rails.application.config.action_cable.disable_request_forgery_protection = true ``` +To disable automatic access for same-origin requests, and strictly allow +only the configured origins: + +```ruby +Rails.application.config.action_cable.allow_same_origin_as_host = false +``` + ### Consumer Configuration -Once you have decided how to run your cable server (see below), you must provide the server url (or path) to your client-side setup. +Once you have decided how to run your cable server (see below), you must provide the server URL (or path) to your client-side setup. There are two ways you can do this. The first is to simply pass it in when creating your consumer. For a standalone server, this would be something like: `App.cable = ActionCable.createConsumer("ws://example.com:28080")`, and for an in-app server, something like: `App.cable = ActionCable.createConsumer("/cable")`. -The second option is to pass the server url through the `action_cable_meta_tag` in your layout. -This uses a url or path typically set via `config.action_cable.url` in the environment configuration files, or defaults to "/cable". +The second option is to pass the server URL through the `action_cable_meta_tag` in your layout. +This uses a URL or path typically set via `config.action_cable.url` in the environment configuration files, or defaults to "/cable". -This method is especially useful if your websocket url might change between environments. If you host your production server via https, you will need to use the wss scheme -for your ActionCable server, but development might remain http and use the ws scheme. You might use localhost in development and your +This method is especially useful if your WebSocket URL might change between environments. If you host your production server via https, you will need to use the wss scheme +for your Action Cable server, but development might remain http and use the ws scheme. You might use localhost in development and your domain in production. -In any case, to vary the websocket url between environments, add the following configuration to each environment: +In any case, to vary the WebSocket URL between environments, add the following configuration to each environment: ```ruby config.action_cable.url = "ws://example.com:28080" @@ -373,11 +386,11 @@ App.cable = ActionCable.createConsumer() ### Other Configurations -The other common option to configure is the log tags applied to the per-connection logger. Here's close to what we're using in Basecamp: +The other common option to configure is the log tags applied to the per-connection logger. Here's an example that uses the user account id if available, else "no-account" while tagging: ```ruby -Rails.application.config.action_cable.log_tags = [ - -> request { request.env['bc.account_id'] || "no-account" }, +config.action_cable.log_tags = [ + -> request { request.env['user_account_id'] || "no-account" }, :action_cable, -> request { request.uuid } ] @@ -385,7 +398,7 @@ Rails.application.config.action_cable.log_tags = [ For a full list of all configuration options, see the `ActionCable::Server::Configuration` class. -Also note that your server must provide at least the same number of database connections as you have workers. The default worker pool is set to 100, so that means you have to make at least that available. You can change that in `config/database.yml` through the `pool` attribute. +Also note that your server must provide at least the same number of database connections as you have workers. The default worker pool is set to 4, so that means you have to make at least that available. You can change that in `config/database.yml` through the `pool` attribute. ## Running the cable server @@ -403,7 +416,7 @@ run ActionCable.server ``` Then you start the server using a binstub in bin/cable ala: -``` +```sh #!/bin/bash bundle exec puma -p 28080 cable/config.ru ``` @@ -412,20 +425,20 @@ The above will start a cable server on port 28080. ### In app -If you are using a threaded server like Puma or Thin, the current implementation of ActionCable can run side-along with your Rails application. For example, to listen for WebSocket requests on `/cable`, mount the server at that path: +If you are using a server that supports the [Rack socket hijacking API](http://www.rubydoc.info/github/rack/rack/file/SPEC#Hijacking), Action Cable can run alongside your Rails application. For example, to listen for WebSocket requests on `/websocket`, specify that path to `config.action_cable.mount_path`: ```ruby -# config/routes.rb -Example::Application.routes.draw do - mount ActionCable.server => '/cable' +# config/application.rb +class Application < Rails::Application + config.action_cable.mount_path = '/websocket' end ``` -For every instance of your server you create and for every worker your server spawns, you will also have a new instance of ActionCable, but the use of Redis keeps messages synced across connections. +For every instance of your server you create and for every worker your server spawns, you will also have a new instance of Action Cable, but the use of Redis keeps messages synced across connections. ### Notes -Beware that currently the cable server will _not_ auto-reload any changes in the framework. As we've discussed, long-running cable connections mean long-running objects. We don't yet have a way of reloading the classes of those objects in a safe manner. So when you change your channels, or the model your channels use, you must restart the cable server. +Beware that currently, the cable server will _not_ auto-reload any changes in the framework. As we've discussed, long-running cable connections mean long-running objects. We don't yet have a way of reloading the classes of those objects in a safe manner. So when you change your channels, or the model your channels use, you must restart the cable server. We'll get all this abstracted properly when the framework is integrated into Rails. @@ -433,24 +446,20 @@ The WebSocket server doesn't have access to the session, but it has access to th ## Dependencies -Action Cable provides a subscription adapter interface to process its pubsub internals. By default, asynchronous, inline, PostgreSQL, evented Redis, and non-evented Redis adapters are included. The default adapter in new Rails applications is the asynchronous (`async`) adapter. To create your own adapter, you can look at `ActionCable::SubscriptionAdapter::Base` for all methods that must be implemented, and any of the adapters included within ActionCable as example implementations. +Action Cable provides a subscription adapter interface to process its pubsub internals. By default, asynchronous, inline, PostgreSQL, evented Redis, and non-evented Redis adapters are included. The default adapter in new Rails applications is the asynchronous (`async`) adapter. To create your own adapter, you can look at `ActionCable::SubscriptionAdapter::Base` for all methods that must be implemented, and any of the adapters included within Action Cable as example implementations. The Ruby side of things is built on top of [websocket-driver](https://github.com/faye/websocket-driver-ruby), [nio4r](https://github.com/celluloid/nio4r), and [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby). ## Deployment -Action Cable is powered by a combination of websockets and threads. All of the +Action Cable is powered by a combination of WebSockets and threads. All of the connection management is handled internally by utilizing Ruby’s native thread support, which means you can use all your regular Rails models with no problems as long as you haven’t committed any thread-safety sins. -But this also means that Action Cable needs to run in its own server process. -So you'll have one set of server processes for your normal web work, and another -set of server processes for the Action Cable. - The Action Cable server does _not_ need to be a multi-threaded application server. -This is because Action Cable uses the [Rack socket hijacking API](http://old.blog.phusion.nl/2013/01/23/the-new-rack-socket-hijacking-api/) +This is because Action Cable uses the [Rack socket hijacking API](http://www.rubydoc.info/github/rack/rack/file/SPEC#Hijacking) to take over control of connections from the application server. Action Cable then manages connections internally, in a multithreaded manner, regardless of whether the application server is multi-threaded or not. So Action Cable works @@ -459,6 +468,74 @@ with all the popular application servers -- Unicorn, Puma and Passenger. Action Cable does not work with WEBrick, because WEBrick does not support the Rack socket hijacking API. +## Frontend assets + +Action Cable's frontend assets are distributed through two channels: the +official gem and npm package, both titled `actioncable`. + +### Gem usage + +Through the `actioncable` gem, Action Cable's frontend assets are +available through the Rails Asset Pipeline. Create a `cable.js` or +`cable.coffee` file (this is automatically done for you with Rails +generators), and then simply require the assets: + +In JavaScript... + +```javascript +//= require action_cable +``` + +... and in CoffeeScript: + +```coffeescript +#= require action_cable +``` + +### npm usage + +In addition to being available through the `actioncable` gem, Action Cable's +frontend JS assets are also bundled in an officially supported npm module, +intended for usage in standalone frontend applications that communicate with a +Rails application. A common use case for this could be if you have a decoupled +frontend application written in React, Ember.js, etc. and want to add real-time +WebSocket functionality. + +### Installation + +``` +npm install actioncable --save +``` + +### Usage + +The `ActionCable` constant is available as a `require`-able module, so +you only have to require the package to gain access to the API that is +provided. + +In JavaScript... + +```javascript +ActionCable = require('actioncable') + +var cable = ActionCable.createConsumer('wss://RAILS-API-PATH.com/cable') + +cable.subscriptions.create('AppearanceChannel', { + // normal channel code goes here... +}); +``` + +and in CoffeeScript... + +```coffeescript +ActionCable = require('actioncable') + +cable = ActionCable.createConsumer('wss://RAILS-API-PATH.com/cable') + +cable.subscriptions.create 'AppearanceChannel', + # normal channel code goes here... +``` + ## License Action Cable is released under the MIT license: diff --git a/actioncable/Rakefile b/actioncable/Rakefile index 1d77fc7067..87d443919c 100644 --- a/actioncable/Rakefile +++ b/actioncable/Rakefile @@ -1,15 +1,12 @@ -require 'rake/testtask' -require 'pathname' -require 'sprockets' -require 'coffee-script' -require 'action_cable' +require "rake/testtask" +require "pathname" +require "action_cable" dir = File.dirname(__FILE__) -task :default => :test +task default: :test -task :package => "assets:compile" -task "package:clean" => "assets:clean" +task package: "assets:compile" Rake::TestTask.new do |t| t.libs << "test" @@ -19,39 +16,27 @@ Rake::TestTask.new do |t| t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end -namespace :assets do - root_path = Pathname.new(dir) - destination_path = root_path.join("lib/assets/compiled") - - desc "Compile dist/action_cable.js" - task :compile do - puts 'Compiling Action Cable assets...' - - precompile_list = %w(action_cable.js) - - environment = Sprockets::Environment.new - environment.gzip = false - Pathname.glob(root_path.join("app/assets/*/")) do |subdir| - environment.append_path subdir - end - - compile_path = root_path.join("tmp/sprockets") - compile_path.rmtree if compile_path.exist? - compile_path.mkpath - - manifest = Sprockets::Manifest.new(environment.index, compile_path) - manifest.compile(precompile_list) +namespace :test do + task :isolated do + Dir.glob("test/**/*_test.rb").all? do |file| + sh(Gem.ruby, "-w", "-Ilib:test", file) + end || raise("Failures") + end - destination_path.rmtree if destination_path.exist? - manifest.assets.each do |path, fingerprint_path| - destination_path.join(path).dirname.mkpath - FileUtils.cp(compile_path.join(fingerprint_path), destination_path.join(path)) + task :integration do + require "blade" + if ENV["CI"] + Blade.start(interface: :ci) + else + Blade.start(interface: :runner) end - - puts 'Done' end +end - task :clean do - destination_path.rmtree if destination_path.exist? +namespace :assets do + desc "Compile Action Cable assets" + task :compile do + require "blade" + Blade.build end end diff --git a/actioncable/actioncable.gemspec b/actioncable/actioncable.gemspec index c65ff7871f..e7f91d0e34 100644 --- a/actioncable/actioncable.gemspec +++ b/actioncable/actioncable.gemspec @@ -1,25 +1,25 @@ -version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip +version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY - s.name = 'actioncable' + s.name = "actioncable" s.version = version - s.summary = 'WebSocket framework for Rails.' - s.description = 'Structure many real-time application concerns into channels over a single WebSocket connection.' + s.summary = "WebSocket framework for Rails." + s.description = "Structure many real-time application concerns into channels over a single WebSocket connection." - s.required_ruby_version = '>= 2.2.2' + s.required_ruby_version = ">= 2.2.2" - s.license = 'MIT' + s.license = "MIT" - s.author = ['Pratik Naik', 'David Heinemeier Hansson'] - s.email = ['pratiknaik@gmail.com', 'david@loudthinking.com'] - s.homepage = 'http://rubyonrails.org' + s.author = ["Pratik Naik", "David Heinemeier Hansson"] + s.email = ["pratiknaik@gmail.com", "david@loudthinking.com"] + s.homepage = "http://rubyonrails.org" - s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.md', 'lib/**/*'] - s.require_path = 'lib' + s.files = Dir["CHANGELOG.md", "MIT-LICENSE", "README.md", "lib/**/*"] + s.require_path = "lib" - s.add_dependency 'actionpack', version + s.add_dependency "actionpack", version - s.add_dependency 'nio4r', '~> 1.2' - s.add_dependency 'websocket-driver', '~> 0.6.1' + s.add_dependency "nio4r", "~> 1.2" + s.add_dependency "websocket-driver", "~> 0.6.1" end diff --git a/actioncable/app/assets/javascripts/action_cable.coffee.erb b/actioncable/app/assets/javascripts/action_cable.coffee.erb index 18a48c0610..e0758dae72 100644 --- a/actioncable/app/assets/javascripts/action_cable.coffee.erb +++ b/actioncable/app/assets/javascripts/action_cable.coffee.erb @@ -1,10 +1,14 @@ +#= export ActionCable #= require_self #= require ./action_cable/consumer @ActionCable = INTERNAL: <%= ActionCable::INTERNAL.to_json %> + WebSocket: window.WebSocket + logger: window.console - createConsumer: (url = @getConfig("url")) -> + createConsumer: (url) -> + url ?= @getConfig("url") ? @INTERNAL.default_mount_path new ActionCable.Consumer @createWebSocketURL(url) getConfig: (name) -> @@ -21,3 +25,14 @@ a.href else url + + startDebugging: -> + @debugging = true + + stopDebugging: -> + @debugging = null + + log: (messages...) -> + if @debugging + messages.push(Date.now()) + @logger.log("[ActionCable]", messages...) diff --git a/actioncable/app/assets/javascripts/action_cable/connection.coffee b/actioncable/app/assets/javascripts/action_cable/connection.coffee index fbd7dbd35b..29ad676290 100644 --- a/actioncable/app/assets/javascripts/action_cable/connection.coffee +++ b/actioncable/app/assets/javascripts/action_cable/connection.coffee @@ -1,12 +1,17 @@ +#= require ./connection_monitor + # Encapsulate the cable connection held by the consumer. This is an internal class not intended for direct user manipulation. -{message_types} = ActionCable.INTERNAL +{message_types, protocols} = ActionCable.INTERNAL +[supportedProtocols..., unsupportedProtocol] = protocols class ActionCable.Connection @reopenDelay: 500 constructor: (@consumer) -> - @open() + {@subscriptions} = @consumer + @monitor = new ActionCable.ConnectionMonitor this + @disconnected = true send: (data) -> if @isOpen() @@ -16,30 +21,48 @@ class ActionCable.Connection false open: => - if @webSocket and not @isState("closed") + if @isActive() + ActionCable.log("Attempted to open WebSocket, but existing socket is #{@getState()}") throw new Error("Existing connection must be closed before opening") else - @webSocket = new WebSocket(@consumer.url) + ActionCable.log("Opening WebSocket, current state is #{@getState()}, subprotocols: #{protocols}") + @uninstallEventHandlers() if @webSocket? + @webSocket = new ActionCable.WebSocket(@consumer.url, protocols) @installEventHandlers() + @monitor.start() true - close: -> - @webSocket?.close() + close: ({allowReconnect} = {allowReconnect: true}) -> + @monitor.stop() unless allowReconnect + @webSocket?.close() if @isActive() reopen: -> - if @isState("closed") - @open() - else + ActionCable.log("Reopening WebSocket, current state is #{@getState()}") + if @isActive() try @close() + catch error + ActionCable.log("Failed to reopen WebSocket", error) finally + ActionCable.log("Reopening WebSocket in #{@constructor.reopenDelay}ms") setTimeout(@open, @constructor.reopenDelay) + else + @open() + + getProtocol: -> + @webSocket?.protocol isOpen: -> @isState("open") + isActive: -> + @isState("open", "connecting") + # Private + isProtocolSupported: -> + @getProtocol() in supportedProtocols + isState: (states...) -> @getState() in states @@ -53,29 +76,41 @@ class ActionCable.Connection @webSocket["on#{eventName}"] = handler return + uninstallEventHandlers: -> + for eventName of @events + @webSocket["on#{eventName}"] = -> + return + events: message: (event) -> + return unless @isProtocolSupported() {identifier, message, type} = JSON.parse(event.data) - switch type + when message_types.welcome + @monitor.recordConnect() + @subscriptions.reload() + when message_types.ping + @monitor.recordPing() when message_types.confirmation - @consumer.subscriptions.notify(identifier, "connected") + @subscriptions.notify(identifier, "connected") when message_types.rejection - @consumer.subscriptions.reject(identifier) + @subscriptions.reject(identifier) else - @consumer.subscriptions.notify(identifier, "received", message) + @subscriptions.notify(identifier, "received", message) open: -> + ActionCable.log("WebSocket onopen event, using '#{@getProtocol()}' subprotocol") @disconnected = false - @consumer.subscriptions.reload() + if not @isProtocolSupported() + ActionCable.log("Protocol is unsupported. Stopping monitor and disconnecting.") + @close(allowReconnect: false) - close: -> - @disconnect() + close: (event) -> + ActionCable.log("WebSocket onclose event") + return if @disconnected + @disconnected = true + @monitor.recordDisconnect() + @subscriptions.notifyAll("disconnected", {willAttemptReconnect: @monitor.isRunning()}) error: -> - @disconnect() - - disconnect: -> - return if @disconnected - @disconnected = true - @consumer.subscriptions.notifyAll("disconnected") + ActionCable.log("WebSocket onerror event") diff --git a/actioncable/app/assets/javascripts/action_cable/connection_monitor.coffee b/actioncable/app/assets/javascripts/action_cable/connection_monitor.coffee index 99b9a1c6d5..0cc675fa94 100644 --- a/actioncable/app/assets/javascripts/action_cable/connection_monitor.coffee +++ b/actioncable/app/assets/javascripts/action_cable/connection_monitor.coffee @@ -7,54 +7,69 @@ class ActionCable.ConnectionMonitor @staleThreshold: 6 # Server::Connections::BEAT_INTERVAL * 2 (missed two pings) - identifier: ActionCable.INTERNAL.identifiers.ping + constructor: (@connection) -> + @reconnectAttempts = 0 - constructor: (@consumer) -> - @consumer.subscriptions.add(this) - @start() + start: -> + unless @isRunning() + @startedAt = now() + delete @stoppedAt + @startPolling() + document.addEventListener("visibilitychange", @visibilityDidChange) + ActionCable.log("ConnectionMonitor started. pollInterval = #{@getPollInterval()} ms") - connected: -> - @reset() - @pingedAt = now() - delete @disconnectedAt + stop: -> + if @isRunning() + @stoppedAt = now() + @stopPolling() + document.removeEventListener("visibilitychange", @visibilityDidChange) + ActionCable.log("ConnectionMonitor stopped") - disconnected: -> - @disconnectedAt = now() + isRunning: -> + @startedAt? and not @stoppedAt? - received: -> + recordPing: -> @pingedAt = now() - reset: -> + recordConnect: -> @reconnectAttempts = 0 + @recordPing() + delete @disconnectedAt + ActionCable.log("ConnectionMonitor recorded connect") - start: -> - @reset() - delete @stoppedAt - @startedAt = now() + recordDisconnect: -> + @disconnectedAt = now() + ActionCable.log("ConnectionMonitor recorded disconnect") + + # Private + + startPolling: -> + @stopPolling() @poll() - document.addEventListener("visibilitychange", @visibilityDidChange) - stop: -> - @stoppedAt = now() - document.removeEventListener("visibilitychange", @visibilityDidChange) + stopPolling: -> + clearTimeout(@pollTimeout) poll: -> - setTimeout => - unless @stoppedAt - @reconnectIfStale() - @poll() - , @getInterval() + @pollTimeout = setTimeout => + @reconnectIfStale() + @poll() + , @getPollInterval() - getInterval: -> + getPollInterval: -> {min, max} = @constructor.pollInterval interval = 5 * Math.log(@reconnectAttempts + 1) - clamp(interval, min, max) * 1000 + Math.round(clamp(interval, min, max) * 1000) reconnectIfStale: -> if @connectionIsStale() + ActionCable.log("ConnectionMonitor detected stale connection. reconnectAttempts = #{@reconnectAttempts}, pollInterval = #{@getPollInterval()} ms, time disconnected = #{secondsSince(@disconnectedAt)} s, stale threshold = #{@constructor.staleThreshold} s") @reconnectAttempts++ - unless @disconnectedRecently() - @consumer.connection.reopen() + if @disconnectedRecently() + ActionCable.log("ConnectionMonitor skipping reopening recent disconnect") + else + ActionCable.log("ConnectionMonitor reopening") + @connection.reopen() connectionIsStale: -> secondsSince(@pingedAt ? @startedAt) > @constructor.staleThreshold @@ -65,8 +80,9 @@ class ActionCable.ConnectionMonitor visibilityDidChange: => if document.visibilityState is "visible" setTimeout => - if @connectionIsStale() or not @consumer.connection.isOpen() - @consumer.connection.reopen() + if @connectionIsStale() or not @connection.isOpen() + ActionCable.log("ConnectionMonitor reopening stale connection on visibilitychange. visbilityState = #{document.visibilityState}") + @connection.reopen() , 200 now = -> diff --git a/actioncable/app/assets/javascripts/action_cable/consumer.coffee b/actioncable/app/assets/javascripts/action_cable/consumer.coffee index 717c0641a9..3298be717f 100644 --- a/actioncable/app/assets/javascripts/action_cable/consumer.coffee +++ b/actioncable/app/assets/javascripts/action_cable/consumer.coffee @@ -1,5 +1,4 @@ #= require ./connection -#= require ./connection_monitor #= require ./subscriptions #= require ./subscription @@ -15,11 +14,33 @@ # App.appearance = App.cable.subscriptions.create "AppearanceChannel" # # For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription. +# +# When a consumer is created, it automatically connects with the server. +# +# To disconnect from the server, call +# +# App.cable.disconnect() +# +# and to restart the connection: +# +# App.cable.connect() +# +# Any channel subscriptions which existed prior to disconnecting will +# automatically resubscribe. class ActionCable.Consumer constructor: (@url) -> @subscriptions = new ActionCable.Subscriptions this @connection = new ActionCable.Connection this - @connectionMonitor = new ActionCable.ConnectionMonitor this send: (data) -> @connection.send(data) + + connect: -> + @connection.open() + + disconnect: -> + @connection.close(allowReconnect: false) + + ensureActiveConnection: -> + unless @connection.isActive() + @connection.open() diff --git a/actioncable/app/assets/javascripts/action_cable/subscription.coffee b/actioncable/app/assets/javascripts/action_cable/subscription.coffee index 339d676933..8e0805a174 100644 --- a/actioncable/app/assets/javascripts/action_cable/subscription.coffee +++ b/actioncable/app/assets/javascripts/action_cable/subscription.coffee @@ -1,5 +1,5 @@ -# A new subscription is created through the ActionCable.Subscriptions instance available on the consumer. -# It provides a number of callbacks and a method for calling remote procedure calls on the corresponding +# A new subscription is created through the ActionCable.Subscriptions instance available on the consumer. +# It provides a number of callbacks and a method for calling remote procedure calls on the corresponding # Channel instance on the server side. # # An example demonstrates the basic functionality: @@ -7,13 +7,19 @@ # App.appearance = App.cable.subscriptions.create "AppearanceChannel", # connected: -> # # Called once the subscription has been successfully completed -# +# +# disconnected: ({ willAttemptReconnect: boolean }) -> +# # Called when the client has disconnected with the server. +# # The object will have an `willAttemptReconnect` property which +# # says whether the client has the intention of attempting +# # to reconnect. +# # appear: -> # @perform 'appear', appearing_on: @appearingOn() -# +# # away: -> # @perform 'away' -# +# # appearingOn: -> # $('main').data 'appearing-on' # @@ -27,15 +33,15 @@ # def subscribed # current_user.appear # end -# +# # def unsubscribed # current_user.disappear # end -# +# # def appear(data) # current_user.appear on: data['appearing_on'] # end -# +# # def away # current_user.away # end @@ -44,11 +50,9 @@ # The "AppearanceChannel" name is automatically mapped between the client-side subscription creation and the server-side Ruby class name. # The AppearanceChannel#appear/away public methods are exposed automatically to client-side invocation through the @perform method. class ActionCable.Subscription - constructor: (@subscriptions, params = {}, mixin) -> + constructor: (@consumer, params = {}, mixin) -> @identifier = JSON.stringify(params) extend(this, mixin) - @subscriptions.add(this) - @consumer = @subscriptions.consumer # Perform a channel action with the optional data passed as an attribute perform: (action, data = {}) -> @@ -59,7 +63,7 @@ class ActionCable.Subscription @consumer.send(command: "message", identifier: @identifier, data: JSON.stringify(data)) unsubscribe: -> - @subscriptions.remove(this) + @consumer.subscriptions.remove(this) extend = (object, properties) -> if properties? diff --git a/actioncable/app/assets/javascripts/action_cable/subscriptions.coffee b/actioncable/app/assets/javascripts/action_cable/subscriptions.coffee index ae041ffa2b..aa052bf5d8 100644 --- a/actioncable/app/assets/javascripts/action_cable/subscriptions.coffee +++ b/actioncable/app/assets/javascripts/action_cable/subscriptions.coffee @@ -13,28 +13,33 @@ class ActionCable.Subscriptions create: (channelName, mixin) -> channel = channelName params = if typeof channel is "object" then channel else {channel} - new ActionCable.Subscription this, params, mixin + subscription = new ActionCable.Subscription @consumer, params, mixin + @add(subscription) # Private add: (subscription) -> @subscriptions.push(subscription) + @consumer.ensureActiveConnection() @notify(subscription, "initialized") @sendCommand(subscription, "subscribe") + subscription remove: (subscription) -> @forget(subscription) - unless @findAll(subscription.identifier).length @sendCommand(subscription, "unsubscribe") + subscription reject: (identifier) -> for subscription in @findAll(identifier) @forget(subscription) @notify(subscription, "rejected") + subscription forget: (subscription) -> @subscriptions = (s for s in @subscriptions when s isnt subscription) + subscription findAll: (identifier) -> s for s in @subscriptions when s.identifier is identifier @@ -58,7 +63,4 @@ class ActionCable.Subscriptions sendCommand: (subscription, command) -> {identifier} = subscription - if identifier is ActionCable.INTERNAL.identifiers.ping - @consumer.connection.isOpen() - else - @consumer.send({command, identifier}) + @consumer.send({command, identifier}) diff --git a/actioncable/blade.yml b/actioncable/blade.yml new file mode 100644 index 0000000000..e38e9b2f1d --- /dev/null +++ b/actioncable/blade.yml @@ -0,0 +1,34 @@ +load_paths: + - app/assets/javascripts + - test/javascript/src + - test/javascript/vendor + +logical_paths: + - test.js + +build: + logical_paths: + - action_cable.js + path: lib/assets/compiled + clean: true + +plugins: + sauce_labs: + browsers: + Google Chrome: + os: Mac, Windows + version: -1 + Firefox: + os: Mac, Windows + version: -1 + Safari: + platform: Mac + version: -1 + Microsoft Edge: + version: -1 + Internet Explorer: + version: 11 + iPhone: + version: -1 + Motorola Droid 4 Emulator: + version: -1 diff --git a/actioncable/lib/action_cable.rb b/actioncable/lib/action_cable.rb index 1dc66ef3ad..d353716636 100644 --- a/actioncable/lib/action_cable.rb +++ b/actioncable/lib/action_cable.rb @@ -21,21 +21,22 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ -require 'active_support' -require 'active_support/rails' -require 'action_cable/version' +require "active_support" +require "active_support/rails" +require "action_cable/version" module ActionCable extend ActiveSupport::Autoload INTERNAL = { - identifiers: { - ping: '_ping'.freeze - }, message_types: { - confirmation: 'confirm_subscription'.freeze, - rejection: 'reject_subscription'.freeze - } + welcome: "welcome".freeze, + ping: "ping".freeze, + confirmation: "confirm_subscription".freeze, + rejection: "reject_subscription".freeze + }, + default_mount_path: "/cable".freeze, + protocols: ["actioncable-v1-json".freeze, "actioncable-unsupported".freeze].freeze } # Singleton instance of the server diff --git a/actioncable/lib/action_cable/channel/base.rb b/actioncable/lib/action_cable/channel/base.rb index 874ebe2e71..a866044f95 100644 --- a/actioncable/lib/action_cable/channel/base.rb +++ b/actioncable/lib/action_cable/channel/base.rb @@ -1,4 +1,4 @@ -require 'set' +require "set" module ActionCable module Channel @@ -32,8 +32,8 @@ module ActionCable # # == Action processing # - # Unlike subclasses of ActionController::Base, channels do not follow a REST - # constraint form for their actions. Instead, ActionCable operates through a + # Unlike subclasses of ActionController::Base, channels do not follow a RESTful + # constraint form for their actions. Instead, Action Cable operates through a # remote-procedure call model. You can declare any public method on the # channel (optionally taking a <tt>data</tt> argument), and this method is # automatically exposed as callable to the client. @@ -63,10 +63,10 @@ module ActionCable # end # end # - # In this example, subscribed/unsubscribed are not callable methods, as they + # In this example, the subscribed and unsubscribed methods are not callable methods, as they # were already declared in ActionCable::Channel::Base, but <tt>#appear</tt> # and <tt>#away</tt> are. <tt>#generate_connection_token</tt> is also not - # callable as it's a private method. You'll see that appear accepts a data + # callable, since it's a private method. You'll see that appear accepts a data # parameter, which it then uses as part of its model call. <tt>#away</tt> # does not, since it's simply a trigger action. # @@ -125,7 +125,7 @@ module ActionCable protected # 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 + # you run action_methods, they will be recalculated. def clear_action_methods! @action_methods = nil end @@ -144,13 +144,14 @@ module ActionCable # When a channel is streaming via pubsub, we want to delay the confirmation # transmission until pubsub subscription is confirmed. - @defer_subscription_confirmation = false + # + # The counter starts at 1 because it's awaiting a call to #subscribe_to_channel + @defer_subscription_confirmation_counter = Concurrent::AtomicFixnum.new(1) @reject_subscription = nil @subscription_confirmation_sent = nil delegate_connection_identifiers - subscribe_to_channel end # Extract the action name from the passed data and process it via the channel. The process will ensure @@ -160,21 +161,34 @@ module ActionCable action = extract_action(data) if processable_action?(action) - dispatch_action(action, data) + payload = { channel_class: self.class.name, action: action, data: data } + ActiveSupport::Notifications.instrument("perform_action.action_cable", payload) do + dispatch_action(action, data) + end else logger.error "Unable to process #{action_signature(action, data)}" end end - # Called by the cable connection when its cut so the channel has a chance to cleanup with callbacks. + # This method is called after subscription has been added to the connection + # and confirms or rejects the subscription. + def subscribe_to_channel + run_callbacks :subscribe do + subscribed + end + + reject_subscription if subscription_rejected? + ensure_confirmation_sent + end + + # Called by the cable connection when it's cut, so the channel has a chance to cleanup with callbacks. # This method is not intended to be called directly by the user. Instead, overwrite the #unsubscribed callback. - def unsubscribe_from_channel + def unsubscribe_from_channel # :nodoc: run_callbacks :unsubscribe do unsubscribed end end - protected # 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. @@ -183,7 +197,7 @@ module ActionCable end # Called once a consumer has cut its cable connection. Can be used for cleaning up connections or marking - # people as offline or the like. + # users as offline or the like. def unsubscribed # Override in subclasses end @@ -191,16 +205,26 @@ module ActionCable # 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}".tap { |m| m << " (via #{via})" if via } - connection.transmit ActiveSupport::JSON.encode(identifier: @identifier, message: data) + logger.info "#{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 + connection.transmit identifier: @identifier, message: data + end + end + + def ensure_confirmation_sent + return if subscription_rejected? + @defer_subscription_confirmation_counter.decrement + transmit_subscription_confirmation unless defer_subscription_confirmation? end def defer_subscription_confirmation! - @defer_subscription_confirmation = true + @defer_subscription_confirmation_counter.increment end def defer_subscription_confirmation? - @defer_subscription_confirmation + @defer_subscription_confirmation_counter.value > 0 end def subscription_confirmation_sent? @@ -224,26 +248,12 @@ module ActionCable end end - - def subscribe_to_channel - run_callbacks :subscribe do - subscribed - end - - if subscription_rejected? - reject_subscription - else - transmit_subscription_confirmation unless defer_subscription_confirmation? - end - end - - def extract_action(data) - (data['action'].presence || :receive).to_sym + (data["action"].presence || :receive).to_sym end def processable_action?(action) - self.class.action_methods.include?(action.to_s) + self.class.action_methods.include?(action.to_s) unless subscription_rejected? end def dispatch_action(action, data) @@ -258,7 +268,7 @@ module ActionCable def action_signature(action, data) "#{self.class.name}##{action}".tap do |signature| - if (arguments = data.except('action')).any? + if (arguments = data.except("action")).any? signature << "(#{arguments.inspect})" end end @@ -267,8 +277,11 @@ module ActionCable def transmit_subscription_confirmation unless subscription_confirmation_sent? logger.info "#{self.class.name} is transmitting the subscription confirmation" - connection.transmit ActiveSupport::JSON.encode(identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:confirmation]) - @subscription_confirmation_sent = true + + ActiveSupport::Notifications.instrument("transmit_subscription_confirmation.action_cable", channel_class: self.class.name) do + connection.transmit identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:confirmation] + @subscription_confirmation_sent = true + end end end @@ -279,7 +292,10 @@ module ActionCable def transmit_subscription_rejection logger.info "#{self.class.name} is transmitting the subscription rejection" - connection.transmit ActiveSupport::JSON.encode(identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:rejection]) + + ActiveSupport::Notifications.instrument("transmit_subscription_rejection.action_cable", channel_class: self.class.name) do + connection.transmit identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:rejection] + end end end end diff --git a/actioncable/lib/action_cable/channel/broadcasting.rb b/actioncable/lib/action_cable/channel/broadcasting.rb index afc23d7d1a..23ed4ec943 100644 --- a/actioncable/lib/action_cable/channel/broadcasting.rb +++ b/actioncable/lib/action_cable/channel/broadcasting.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/object/to_param' +require "active_support/core_ext/object/to_param" module ActionCable module Channel @@ -16,7 +16,7 @@ module ActionCable def broadcasting_for(model) #:nodoc: case when model.is_a?(Array) - model.map { |m| broadcasting_for(m) }.join(':') + model.map { |m| broadcasting_for(m) }.join(":") when model.respond_to?(:to_gid_param) model.to_gid_param else diff --git a/actioncable/lib/action_cable/channel/callbacks.rb b/actioncable/lib/action_cable/channel/callbacks.rb index 295d750e86..c740132c94 100644 --- a/actioncable/lib/action_cable/channel/callbacks.rb +++ b/actioncable/lib/action_cable/channel/callbacks.rb @@ -1,4 +1,4 @@ -require 'active_support/callbacks' +require "active_support/callbacks" module ActionCable module Channel diff --git a/actioncable/lib/action_cable/channel/naming.rb b/actioncable/lib/action_cable/channel/naming.rb index 4c9d53b15a..b7e88bf73d 100644 --- a/actioncable/lib/action_cable/channel/naming.rb +++ b/actioncable/lib/action_cable/channel/naming.rb @@ -10,8 +10,9 @@ module ActionCable # # ChatChannel.channel_name # => 'chat' # Chats::AppearancesChannel.channel_name # => 'chats:appearances' + # FooChats::BarAppearancesChannel.channel_name # => 'foo_chats:bar_appearances' def channel_name - @channel_name ||= name.sub(/Channel$/, '').gsub('::',':').underscore + @channel_name ||= name.sub(/Channel$/, "").gsub("::",":").underscore end end diff --git a/actioncable/lib/action_cable/channel/periodic_timers.rb b/actioncable/lib/action_cable/channel/periodic_timers.rb index 56597d02d7..c9daa0bcd3 100644 --- a/actioncable/lib/action_cable/channel/periodic_timers.rb +++ b/actioncable/lib/action_cable/channel/periodic_timers.rb @@ -12,11 +12,42 @@ module ActionCable end module ClassMethods - # Allow you to call a private method <tt>every</tt> so often seconds. This periodic timer can be useful - # for sending a steady flow of updates to a client based off an object that was configured on subscription. - # It's an alternative to using streams if the channel is able to do the work internally. - def periodically(callback, every:) - self.periodic_timers += [ [ callback, every: every ] ] + # Periodically performs a task on the channel, like updating an online + # user counter, polling a backend for new status messages, sending + # regular "heartbeat" messages, or doing some internal work and giving + # progress updates. + # + # Pass a method name or lambda argument or provide a block to call. + # Specify the calling period in seconds using the <tt>every:</tt> + # keyword argument. + # + # periodically :transmit_progress, every: 5.seconds + # + # periodically every: 3.minutes do + # transmit action: :update_count, count: current_count + # end + # + def periodically(callback_or_method_name = nil, every:, &block) + callback = + if block_given? + raise ArgumentError, "Pass a block or provide a callback arg, not both" if callback_or_method_name + block + else + case callback_or_method_name + when Proc + callback_or_method_name + when Symbol + -> { __send__ callback_or_method_name } + else + raise ArgumentError, "Expected a Symbol method name or a Proc, got #{callback_or_method_name.inspect}" + end + end + + unless every.kind_of?(Numeric) && every > 0 + raise ArgumentError, "Expected every: to be a positive number of seconds, got #{every.inspect}" + end + + self.periodic_timers += [[ callback, every: every ]] end end @@ -27,14 +58,19 @@ module ActionCable def start_periodic_timers self.class.periodic_timers.each do |callback, options| - active_periodic_timers << Concurrent::TimerTask.new(execution_interval: options[:every]) do - connection.worker_pool.async_run_periodic_timer(self, callback) - end + active_periodic_timers << start_periodic_timer(callback, every: options.fetch(:every)) + end + end + + def start_periodic_timer(callback, every:) + connection.server.event_loop.timer every do + connection.worker_pool.async_exec self, connection: connection, &callback end end def stop_periodic_timers active_periodic_timers.each { |timer| timer.shutdown } + active_periodic_timers.clear end end end diff --git a/actioncable/lib/action_cable/channel/streams.rb b/actioncable/lib/action_cable/channel/streams.rb index 3158f30814..dbba333353 100644 --- a/actioncable/lib/action_cable/channel/streams.rb +++ b/actioncable/lib/action_cable/channel/streams.rb @@ -1,8 +1,8 @@ module ActionCable module Channel - # Streams allow channels to route broadcastings to the subscriber. A broadcasting is, as discussed elsewhere, a pub/sub queue where any data - # put into it is automatically sent to the clients that are connected at that time. It's purely an online queue, though. If you're not - # streaming a broadcasting at the very moment it sends out an update, you'll not get that update when connecting later. + # Streams allow channels to route broadcastings to the subscriber. A broadcasting is, as discussed elsewhere, a pubsub queue where any data + # placed into it is automatically sent to the clients that are connected at that time. It's purely an online queue, though. If you're not + # streaming a broadcasting at the very moment it sends out an update, you will not get that update, even if you connect after it has been sent. # # Most commonly, the streamed broadcast is sent straight to the subscriber on the client-side. The channel just acts as a connector between # the two parties (the broadcaster and the channel subscriber). Here's an example of a channel that allows subscribers to get all new @@ -18,13 +18,15 @@ module ActionCable # end # end # - # So the subscribers of this channel will get whatever data is put into the, let's say, `comments_for_45` broadcasting as soon as it's put there. - # That looks like so from that side of things: + # Based on the above example, the subscribers of this channel will get whatever data is put into the, + # let's say, <tt>comments_for_45</tt> broadcasting as soon as it's put there. + # + # An example broadcasting for this channel looks like so: # # ActionCable.server.broadcast "comments_for_45", author: 'DHH', content: 'Rails is just swell' # # If you have a stream that is related to a model, then the broadcasting used can be generated from the model and channel. - # The following example would subscribe to a broadcasting like `comments:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE` + # The following example would subscribe to a broadcasting like <tt>comments:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE</tt>. # # class CommentsChannel < ApplicationCable::Channel # def subscribed @@ -37,16 +39,14 @@ module ActionCable # # CommentsChannel.broadcast_to(@post, @comment) # - # If you don't just want to parlay the broadcast unfiltered to the subscriber, you can supply a callback that lets you alter what goes out. - # Example below shows how you can use this to provide performance introspection in the process: + # If you don't just want to parlay the broadcast unfiltered to the subscriber, you can also supply a callback that lets you alter what is sent out. + # The below example shows how you can use this to provide performance introspection in the process: # # class ChatChannel < ApplicationCable::Channel # def subscribed # @room = Chat::Room[params[:room_number]] # - # stream_for @room, -> (encoded_message) do - # message = ActiveSupport::JSON.decode(encoded_message) - # + # stream_for @room, coder: ActiveSupport::JSON do |message| # if message['originated_at'].present? # elapsed_time = (Time.now.to_f - message['originated_at']).round(2) # @@ -69,16 +69,22 @@ module ActionCable # Start streaming from the named <tt>broadcasting</tt> pubsub queue. Optionally, you can pass a <tt>callback</tt> that'll be used # instead of the default of just transmitting the updates straight to the subscriber. - def stream_from(broadcasting, callback = nil) - # Hold off the confirmation until pubsub#subscribe is successful + # Pass <tt>coder: ActiveSupport::JSON</tt> to decode messages as JSON before passing to the callback. + # Defaults to <tt>coder: nil</tt> which does no decoding, passes raw messages. + def stream_from(broadcasting, callback = nil, coder: nil, &block) + broadcasting = String(broadcasting) + + # Don't send the confirmation until pubsub#subscribe is successful defer_subscription_confirmation! - callback ||= default_stream_callback(broadcasting) - streams << [ broadcasting, callback ] + # Build a stream handler by wrapping the user-provided callback with + # a decoder or defaulting to a JSON-decoding retransmitter. + handler = worker_pool_stream_handler(broadcasting, callback || block, coder: coder) + streams << [ broadcasting, handler ] - Concurrent.global_io_executor.post do - pubsub.subscribe(broadcasting, callback, lambda do - transmit_subscription_confirmation + connection.server.event_loop.post do + pubsub.subscribe(broadcasting, handler, lambda do + ensure_confirmation_sent logger.info "#{self.class.name} is streaming from #{broadcasting}" end) end @@ -87,8 +93,11 @@ module ActionCable # Start streaming the pubsub queue for the <tt>model</tt> in this channel. Optionally, you can pass a # <tt>callback</tt> that'll be used instead of the default of just transmitting the updates straight # to the subscriber. - def stream_for(model, callback = nil) - stream_from(broadcasting_for([ channel_name, model ]), callback) + # + # Pass <tt>coder: ActiveSupport::JSON</tt> to decode messages as JSON before passing to the callback. + # Defaults to <tt>coder: nil</tt> which does no decoding, passes raw messages. + def stream_for(model, callback = nil, coder: nil, &block) + stream_from(broadcasting_for([ channel_name, model ]), callback || block, coder: coder) end # Unsubscribes all streams associated with this channel from the pubsub queue. @@ -106,11 +115,60 @@ module ActionCable @_streams ||= [] end - def default_stream_callback(broadcasting) + # Always wrap the outermost handler to invoke the user handler on the + # worker pool rather than blocking the event loop. + def worker_pool_stream_handler(broadcasting, user_handler, coder: nil) + handler = stream_handler(broadcasting, user_handler, coder: coder) + + -> message do + connection.worker_pool.async_invoke handler, :call, message, connection: connection + end + end + + # May be overridden to add instrumentation, logging, specialized error + # handling, or other forms of handler decoration. + # + # TODO: Tests demonstrating this. + def stream_handler(broadcasting, user_handler, coder: nil) + if user_handler + stream_decoder user_handler, coder: coder + else + default_stream_handler broadcasting, coder: coder + end + end + + # May be overridden to change the default stream handling behavior + # which decodes JSON and transmits to the client. + # + # TODO: Tests demonstrating this. + # + # TODO: Room for optimization. Update transmit API to be coder-aware + # so we can no-op when pubsub and connection are both JSON-encoded. + # Then we can skip decode+encode if we're just proxying messages. + def default_stream_handler(broadcasting, coder:) + coder ||= ActiveSupport::JSON + stream_transmitter stream_decoder(coder: coder), broadcasting: broadcasting + end + + def stream_decoder(handler = identity_handler, coder:) + if coder + -> message { handler.(coder.decode(message)) } + else + handler + end + end + + def stream_transmitter(handler = identity_handler, broadcasting:) + via = "streamed from #{broadcasting}" + -> (message) do - transmit ActiveSupport::JSON.decode(message), via: "streamed from #{broadcasting}" + transmit handler.(message), via: via end end + + def identity_handler + -> message { message } + end end end end diff --git a/actioncable/lib/action_cable/connection/authorization.rb b/actioncable/lib/action_cable/connection/authorization.rb index 070a70e4e2..85df206445 100644 --- a/actioncable/lib/action_cable/connection/authorization.rb +++ b/actioncable/lib/action_cable/connection/authorization.rb @@ -10,4 +10,4 @@ module ActionCable end end end -end
\ No newline at end of file +end diff --git a/actioncable/lib/action_cable/connection/base.rb b/actioncable/lib/action_cable/connection/base.rb index 1acef93025..dfee123ea2 100644 --- a/actioncable/lib/action_cable/connection/base.rb +++ b/actioncable/lib/action_cable/connection/base.rb @@ -1,10 +1,10 @@ -require 'action_dispatch' +require "action_dispatch" module ActionCable module Connection - # For every WebSocket the cable server is accepting, a Connection object will be instantiated. This instance becomes the parent - # of all the channel subscriptions that are created from there on. Incoming messages are then routed to these channel subscriptions - # based on an identifier sent by the cable consumer. The Connection itself does not deal with any specific application logic beyond + # For every WebSocket connection the Action Cable server accepts, a Connection object will be instantiated. This instance becomes the parent + # of all of the channel subscriptions that are created from there on. Incoming messages are then routed to these channel subscriptions + # based on an identifier sent by the Action Cable consumer. The Connection itself does not deal with any specific application logic beyond # authentication and authorization. # # Here's a basic example: @@ -33,14 +33,14 @@ module ActionCable # end # end # - # First, we declare that this connection can be identified by its current_user. This allows us later to be able to find all connections - # established for that current_user (and potentially disconnect them if the user was removed from an account). You can declare as many + # First, we declare that this connection can be identified by its current_user. This allows us to later be able to find all connections + # established for that current_user (and potentially disconnect them). You can declare as many # identification indexes as you like. Declaring an identification means that an attr_accessor is automatically set for that key. # # Second, we rely on the fact that the WebSocket connection is established with the cookies from the domain being sent along. This makes # it easy to use signed cookies that were set when logging in via a web interface to authorize the WebSocket connection. # - # Finally, we add a tag to the connection-specific logger with name of the current user to easily distinguish their messages in the log. + # Finally, we add a tag to the connection-specific logger with the name of the current user to easily distinguish their messages in the log. # # Pretty simple, eh? class Base @@ -48,15 +48,16 @@ module ActionCable include InternalChannel include Authorization - attr_reader :server, :env, :subscriptions, :logger - delegate :stream_event_loop, :worker_pool, :pubsub, to: :server + attr_reader :server, :env, :subscriptions, :logger, :worker_pool, :protocol + delegate :event_loop, :pubsub, to: :server - def initialize(server, env) - @server, @env = server, env + def initialize(server, env, coder: ActiveSupport::JSON) + @server, @env, @coder = server, env, coder + @worker_pool = server.worker_pool @logger = new_tagged_logger - @websocket = ActionCable::Connection::WebSocket.new(env, self, stream_event_loop) + @websocket = ActionCable::Connection::WebSocket.new(env, self, event_loop) @subscriptions = ActionCable::Connection::Subscriptions.new(self) @message_buffer = ActionCable::Connection::MessageBuffer.new(self) @@ -65,8 +66,8 @@ module ActionCable end # Called by the server when a new WebSocket connection is established. This configures the callbacks intended for overwriting by the user. - # This method should not be called directly. Rely on the #connect (and #disconnect) callback instead. - def process + # This method should not be called directly -- instead rely upon on the #connect (and #disconnect) callbacks. + def process #:nodoc: logger.info started_request_message if websocket.possible? && allow_request_origin? @@ -76,20 +77,22 @@ module ActionCable end end - # Data received over the cable is handled by this method. It's expected that everything inbound is JSON encoded. - # The data is routed to the proper channel that the connection has subscribed to. - def receive(data_in_json) + # Decodes WebSocket messages and dispatches them to subscribed channels. + # WebSocket message transfer encoding is always JSON. + def receive(websocket_message) #:nodoc: + send_async :dispatch_websocket_message, websocket_message + end + + def dispatch_websocket_message(websocket_message) #:nodoc: if websocket.alive? - subscriptions.execute_command ActiveSupport::JSON.decode(data_in_json) + subscriptions.execute_command decode(websocket_message) else - logger.error "Received data without a live WebSocket (#{data_in_json.inspect})" + logger.error "Ignoring message processed after the WebSocket was closed: #{websocket_message.inspect})" end end - # Send raw data straight back down the WebSocket. This is not intended to be called directly. Use the #transmit available on the - # Channel instead, as that'll automatically address the correct subscriber and wrap the message in JSON. - def transmit(data) - websocket.transmit data + def transmit(cable_message) # :nodoc: + websocket.transmit encode(cable_message) end # Close the WebSocket connection. @@ -102,19 +105,19 @@ module ActionCable worker_pool.async_invoke(self, method, *arguments) end - # Return a basic hash of statistics for the connection keyed with `identifier`, `started_at`, and `subscriptions`. + # Return a basic hash of statistics for the connection keyed with <tt>identifier</tt>, <tt>started_at</tt>, <tt>subscriptions</tt>, and <tt>request_id</tt>. # This can be returned by a health check against the connection. def statistics { identifier: connection_identifier, started_at: @started_at, subscriptions: subscriptions.identifiers, - request_id: @env['action_dispatch.request_id'] + request_id: @env["action_dispatch.request_id"] } end def beat - transmit ActiveSupport::JSON.encode(identifier: ActionCable::INTERNAL[:identifiers][:ping], message: Time.now.to_i) + transmit type: ActionCable::INTERNAL[:message_types][:ping], message: Time.now.to_i end def on_open # :nodoc: @@ -151,10 +154,19 @@ module ActionCable attr_reader :message_buffer private + def encode(cable_message) + @coder.encode cable_message + end + + def decode(websocket_message) + @coder.decode websocket_message + end + def handle_open + @protocol = websocket.protocol connect if respond_to?(:connect) subscribe_to_internal_channel - beat + send_welcome_message message_buffer.process! server.add_connection(self) @@ -173,10 +185,20 @@ module ActionCable disconnect if respond_to?(:disconnect) end + def send_welcome_message + # Send welcome message to the internal connection monitor channel. + # This ensures the connection monitor state is reset after a successful + # websocket connection. + transmit type: ActionCable::INTERNAL[:message_types][:welcome] + end + def allow_request_origin? return true if server.config.disable_request_forgery_protection - if Array(server.config.allowed_request_origins).any? { |allowed_origin| allowed_origin === env['HTTP_ORIGIN'] } + proto = Rack::Request.new(env).ssl? ? "https" : "http" + if server.config.allow_same_origin_as_host && env["HTTP_ORIGIN"] == "#{proto}://#{env['HTTP_HOST']}" + true + elsif Array(server.config.allowed_request_origins).any? { |allowed_origin| allowed_origin === env["HTTP_ORIGIN"] } true else logger.error("Request origin not allowed: #{env['HTTP_ORIGIN']}") @@ -194,7 +216,7 @@ module ActionCable logger.error invalid_request_message logger.info finished_request_message - [ 404, { 'Content-Type' => 'text/plain' }, [ 'Page not found' ] ] + [ 404, { "Content-Type" => "text/plain" }, [ "Page not found" ] ] end # Tags are declared in the server but computed in the connection. This allows us per-connection tailored tags. @@ -207,7 +229,7 @@ module ActionCable 'Started %s "%s"%s for %s at %s' % [ request.request_method, request.filtered_path, - websocket.possible? ? ' [WebSocket]' : '[non-WebSocket]', + websocket.possible? ? " [WebSocket]" : "[non-WebSocket]", request.ip, Time.now.to_s ] end @@ -215,19 +237,19 @@ module ActionCable def finished_request_message 'Finished "%s"%s for %s at %s' % [ request.filtered_path, - websocket.possible? ? ' [WebSocket]' : '[non-WebSocket]', + websocket.possible? ? " [WebSocket]" : "[non-WebSocket]", request.ip, Time.now.to_s ] end def invalid_request_message - 'Failed to upgrade to WebSocket (REQUEST_METHOD: %s, HTTP_CONNECTION: %s, HTTP_UPGRADE: %s)' % [ + "Failed to upgrade to WebSocket (REQUEST_METHOD: %s, HTTP_CONNECTION: %s, HTTP_UPGRADE: %s)" % [ env["REQUEST_METHOD"], env["HTTP_CONNECTION"], env["HTTP_UPGRADE"] ] end def successful_request_message - 'Successfully upgraded to WebSocket (REQUEST_METHOD: %s, HTTP_CONNECTION: %s, HTTP_UPGRADE: %s)' % [ + "Successfully upgraded to WebSocket (REQUEST_METHOD: %s, HTTP_CONNECTION: %s, HTTP_UPGRADE: %s)" % [ env["REQUEST_METHOD"], env["HTTP_CONNECTION"], env["HTTP_UPGRADE"] ] end diff --git a/actioncable/lib/action_cable/connection/client_socket.rb b/actioncable/lib/action_cable/connection/client_socket.rb index ef937d7c16..70a2bbecb1 100644 --- a/actioncable/lib/action_cable/connection/client_socket.rb +++ b/actioncable/lib/action_cable/connection/client_socket.rb @@ -1,4 +1,4 @@ -require 'websocket/driver' +require "websocket/driver" module ActionCable module Connection @@ -8,16 +8,16 @@ module ActionCable # Copyright (c) 2010-2015 James Coglan class ClientSocket # :nodoc: def self.determine_url(env) - scheme = secure_request?(env) ? 'wss:' : 'ws:' + scheme = secure_request?(env) ? "wss:" : "ws:" "#{ scheme }//#{ env['HTTP_HOST'] }#{ env['REQUEST_URI'] }" end def self.secure_request?(env) - return true if env['HTTPS'] == 'on' - return true if env['HTTP_X_FORWARDED_SSL'] == 'on' - return true if env['HTTP_X_FORWARDED_SCHEME'] == 'https' - return true if env['HTTP_X_FORWARDED_PROTO'] == 'https' - return true if env['rack.url_scheme'] == 'https' + return true if env["HTTPS"] == "on" + return true if env["HTTP_X_FORWARDED_SSL"] == "on" + return true if env["HTTP_X_FORWARDED_SCHEME"] == "https" + return true if env["HTTP_X_FORWARDED_PROTO"] == "https" + return true if env["rack.url_scheme"] == "https" return false end @@ -29,35 +29,37 @@ module ActionCable attr_reader :env, :url - def initialize(env, event_target, stream_event_loop) - @env = env - @event_target = event_target - @stream_event_loop = stream_event_loop + def initialize(env, event_target, event_loop, protocols) + @env = env + @event_target = event_target + @event_loop = event_loop @url = ClientSocket.determine_url(@env) @driver = @driver_started = nil - @close_params = ['', 1006] + @close_params = ["", 1006] @ready_state = CONNECTING # The driver calls +env+, +url+, and +write+ - @driver = ::WebSocket::Driver.rack(self) + @driver = ::WebSocket::Driver.rack(self, protocols: protocols) @driver.on(:open) { |e| open } @driver.on(:message) { |e| receive_message(e.data) } @driver.on(:close) { |e| begin_close(e.reason, e.code) } @driver.on(:error) { |e| emit_error(e.message) } - @stream = ActionCable::Connection::Stream.new(@stream_event_loop, self) - - if callback = @env['async.callback'] - callback.call([101, {}, @stream]) - end + @stream = ActionCable::Connection::Stream.new(@event_loop, self) end def start_driver return if @driver.nil? || @driver_started + @stream.hijack_rack_socket + + if callback = @env["async.callback"] + callback.call([101, {}, @stream]) + end + @driver_started = true @driver.start end @@ -69,23 +71,25 @@ module ActionCable def write(data) @stream.write(data) + rescue => e + emit_error e.message end def transmit(message) return false if @ready_state > OPEN case message - when Numeric then @driver.text(message.to_s) - when String then @driver.text(message) - when Array then @driver.binary(message) + when Numeric then @driver.text(message.to_s) + when String then @driver.text(message) + when Array then @driver.binary(message) else false end end def close(code = nil, reason = nil) code ||= 1000 - reason ||= '' + reason ||= "" - unless code == 1000 or (code >= 3000 and code <= 4999) + 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. " + "#{code} is neither." @@ -107,6 +111,10 @@ module ActionCable @ready_state == OPEN end + def protocol + @driver.protocol + end + private def open return unless @ready_state == CONNECTING @@ -132,11 +140,8 @@ module ActionCable @ready_state = CLOSING @close_params = [reason, code] - if @stream - @stream.shutdown - else - finalize_close - end + @stream.shutdown if @stream + finalize_close end def finalize_close diff --git a/actioncable/lib/action_cable/connection/identification.rb b/actioncable/lib/action_cable/connection/identification.rb index 885ff3f102..c91a1d1fd7 100644 --- a/actioncable/lib/action_cable/connection/identification.rb +++ b/actioncable/lib/action_cable/connection/identification.rb @@ -1,4 +1,4 @@ -require 'set' +require "set" module ActionCable module Connection @@ -12,7 +12,7 @@ module ActionCable class_methods do # Mark a key as being a connection identifier index that can then be used to find the specific connection again later. - # Common identifiers are current_user and current_account, but could be anything really. + # Common identifiers are current_user and current_account, but could be anything, really. # # Note that anything marked as an identifier will automatically create a delegate by the same name on any # channel instances created off the connection. diff --git a/actioncable/lib/action_cable/connection/internal_channel.rb b/actioncable/lib/action_cable/connection/internal_channel.rb index 27826792b3..8f0ec766c3 100644 --- a/actioncable/lib/action_cable/connection/internal_channel.rb +++ b/actioncable/lib/action_cable/connection/internal_channel.rb @@ -11,26 +11,24 @@ module ActionCable def subscribe_to_internal_channel if connection_identifier.present? - callback = -> (message) { process_internal_message(message) } + callback = -> (message) { process_internal_message decode(message) } @_internal_subscriptions ||= [] @_internal_subscriptions << [ internal_channel, callback ] - Concurrent.global_io_executor.post { pubsub.subscribe(internal_channel, callback) } + server.event_loop.post { pubsub.subscribe(internal_channel, callback) } logger.info "Registered connection (#{connection_identifier})" end end def unsubscribe_from_internal_channel if @_internal_subscriptions.present? - @_internal_subscriptions.each { |channel, callback| Concurrent.global_io_executor.post { pubsub.unsubscribe(channel, callback) } } + @_internal_subscriptions.each { |channel, callback| server.event_loop.post { pubsub.unsubscribe(channel, callback) } } end end def process_internal_message(message) - message = ActiveSupport::JSON.decode(message) - - case message['type'] - when 'disconnect' + case message["type"] + when "disconnect" logger.info "Removing connection (#{connection_identifier})" websocket.close end diff --git a/actioncable/lib/action_cable/connection/message_buffer.rb b/actioncable/lib/action_cable/connection/message_buffer.rb index 2f65a1e84a..6a80770cae 100644 --- a/actioncable/lib/action_cable/connection/message_buffer.rb +++ b/actioncable/lib/action_cable/connection/message_buffer.rb @@ -1,8 +1,7 @@ module ActionCable module Connection - # Allows us to buffer messages received from the WebSocket before the Connection has been fully initialized and is ready to receive them. - # Entirely internal operation and should not be used directly by the user. - class MessageBuffer + # Allows us to buffer messages received from the WebSocket before the Connection has been fully initialized, and is ready to receive them. + class MessageBuffer # :nodoc: def initialize(connection) @connection = connection @buffered_messages = [] @@ -31,7 +30,7 @@ module ActionCable protected attr_reader :connection - attr_accessor :buffered_messages + attr_reader :buffered_messages private def valid?(message) @@ -39,7 +38,7 @@ module ActionCable end def receive(message) - connection.send_async :receive, message + connection.receive message end def buffer(message) diff --git a/actioncable/lib/action_cable/connection/stream.rb b/actioncable/lib/action_cable/connection/stream.rb index ace250cd16..e620b93845 100644 --- a/actioncable/lib/action_cable/connection/stream.rb +++ b/actioncable/lib/action_cable/connection/stream.rb @@ -1,18 +1,22 @@ +require "thread" + module ActionCable module Connection #-- # This class is heavily based on faye-websocket-ruby # # Copyright (c) 2010-2015 James Coglan - class Stream + class Stream # :nodoc: def initialize(event_loop, socket) @event_loop = event_loop @socket_object = socket - @stream_send = socket.env['stream.send'] + @stream_send = socket.env["stream.send"] @rack_hijack_io = nil + @write_lock = Mutex.new - hijack_rack_socket + @write_head = nil + @write_buffer = Queue.new end def each(&callback) @@ -29,26 +33,76 @@ module ActionCable end def write(data) - return @rack_hijack_io.write(data) if @rack_hijack_io - return @stream_send.call(data) if @stream_send - rescue EOFError + if @stream_send + return @stream_send.call(data) + end + + if @write_lock.try_lock + begin + if @write_head.nil? && @write_buffer.empty? + written = @rack_hijack_io.write_nonblock(data, exception: false) + + case written + when :wait_writable + # proceed below + when data.bytesize + return data.bytesize + else + @write_head = data.byteslice(written, data.bytesize) + @event_loop.writes_pending @rack_hijack_io + + return data.bytesize + end + end + ensure + @write_lock.unlock + end + end + + @write_buffer << data + @event_loop.writes_pending @rack_hijack_io + + data.bytesize + rescue EOFError, Errno::ECONNRESET @socket_object.client_gone end + def flush_write_buffer + @write_lock.synchronize do + loop do + if @write_head.nil? + return true if @write_buffer.empty? + @write_head = @write_buffer.pop + end + + written = @rack_hijack_io.write_nonblock(@write_head, exception: false) + case written + when :wait_writable + return false + when @write_head.bytesize + @write_head = nil + else + @write_head = @write_head.byteslice(written, @write_head.bytesize) + return false + end + end + end + end + def receive(data) @socket_object.parse(data) end - private - def hijack_rack_socket - return unless @socket_object.env['rack.hijack'] + def hijack_rack_socket + return unless @socket_object.env["rack.hijack"] - @socket_object.env['rack.hijack'].call - @rack_hijack_io = @socket_object.env['rack.hijack_io'] + @socket_object.env["rack.hijack"].call + @rack_hijack_io = @socket_object.env["rack.hijack_io"] - @event_loop.attach(@rack_hijack_io, self) - end + @event_loop.attach(@rack_hijack_io, self) + end + private def clean_rack_hijack return unless @rack_hijack_io @event_loop.detach(@rack_hijack_io, self) diff --git a/actioncable/lib/action_cable/connection/stream_event_loop.rb b/actioncable/lib/action_cable/connection/stream_event_loop.rb index e6335082d2..2d1af0ff9f 100644 --- a/actioncable/lib/action_cable/connection/stream_event_loop.rb +++ b/actioncable/lib/action_cable/connection/stream_event_loop.rb @@ -1,23 +1,33 @@ -require 'nio' -require 'thread' +require "nio" +require "thread" module ActionCable module Connection class StreamEventLoop def initialize - @nio = @thread = nil + @nio = @executor = @thread = nil @map = {} @stopping = false @todo = Queue.new @spawn_mutex = Mutex.new + end + + def timer(interval, &block) + Concurrent::TimerTask.new(execution_interval: interval, &block).tap(&:execute) + end + + def post(task = nil, &block) + task ||= block + spawn + @executor << task end def attach(io, stream) @todo << lambda do - @map[io] = stream - @nio.register(io, :r) + @map[io] = @nio.register(io, :r) + @map[io].value = stream end wakeup end @@ -26,6 +36,16 @@ module ActionCable @todo << lambda do @nio.deregister io @map.delete io + io.close + end + wakeup + end + + def writes_pending(io) + @todo << lambda do + if monitor = @map[io] + monitor.interests = :rw + end end wakeup end @@ -43,6 +63,13 @@ module ActionCable return if @thread && @thread.status @nio ||= NIO::Selector.new + + @executor ||= Concurrent::ThreadPoolExecutor.new( + min_threads: 1, + max_threads: 10, + max_queue: 0, + ) + @thread = Thread.new { run } return true @@ -68,12 +95,25 @@ module ActionCable monitors.each do |monitor| io = monitor.io - stream = @map[io] + stream = monitor.value begin - stream.receive io.read_nonblock(4096) - rescue IO::WaitReadable - next + if monitor.writable? + if stream.flush_write_buffer + monitor.interests = :r + end + next unless monitor.readable? + end + + incoming = io.read_nonblock(4096, exception: false) + case incoming + when :wait_readable + next + when nil + stream.close + else + stream.receive incoming + end rescue # We expect one of EOFError or Errno::ECONNRESET in # normal operation (when the client goes away). But if diff --git a/actioncable/lib/action_cable/connection/subscriptions.rb b/actioncable/lib/action_cable/connection/subscriptions.rb index d7f95e6a62..00511aead5 100644 --- a/actioncable/lib/action_cable/connection/subscriptions.rb +++ b/actioncable/lib/action_cable/connection/subscriptions.rb @@ -1,20 +1,20 @@ -require 'active_support/core_ext/hash/indifferent_access' +require "active_support/core_ext/hash/indifferent_access" module ActionCable module Connection # Collection class for all the channel subscriptions established on a given connection. Responsible for routing incoming commands that arrive on - # the connection to the proper channel. Should not be used directly by the user. - class Subscriptions + # the connection to the proper channel. + class Subscriptions # :nodoc: def initialize(connection) @connection = connection @subscriptions = {} end def execute_command(data) - case data['command'] - when 'subscribe' then add data - when 'unsubscribe' then remove data - when 'message' then perform_action data + case data["command"] + when "subscribe" then add data + when "unsubscribe" then remove data + when "message" then perform_action data else logger.error "Received unrecognized command in #{data.inspect}" end @@ -23,21 +23,25 @@ module ActionCable end def add(data) - id_key = data['identifier'] + id_key = data["identifier"] id_options = ActiveSupport::JSON.decode(id_key).with_indifferent_access - subscription_klass = connection.server.channel_classes[id_options[:channel]] + return if subscriptions.key?(id_key) - if subscription_klass - subscriptions[id_key] ||= subscription_klass.new(connection, id_key, id_options) + subscription_klass = id_options[:channel].safe_constantize + + if subscription_klass && ActionCable::Channel::Base >= subscription_klass + subscription = subscription_klass.new(connection, id_key, id_options) + subscriptions[id_key] = subscription + subscription.subscribe_to_channel else - logger.error "Subscription class not found (#{data.inspect})" + logger.error "Subscription class not found: #{id_options[:channel].inspect}" end end def remove(data) logger.info "Unsubscribing from channel: #{data['identifier']}" - remove_subscription subscriptions[data['identifier']] + remove_subscription subscriptions[data["identifier"]] end def remove_subscription(subscription) @@ -46,7 +50,7 @@ module ActionCable end def perform_action(data) - find(data).perform_action ActiveSupport::JSON.decode(data['data']) + find(data).perform_action ActiveSupport::JSON.decode(data["data"]) end def identifiers @@ -54,7 +58,7 @@ module ActionCable end def unsubscribe_from_all - subscriptions.each { |id, channel| channel.unsubscribe_from_channel } + subscriptions.each { |id, channel| remove_subscription(channel) } end protected @@ -64,7 +68,7 @@ module ActionCable delegate :logger, to: :connection def find(data) - if subscription = subscriptions[data['identifier']] + if subscription = subscriptions[data["identifier"]] subscription else raise "Unable to find subscription with identifier: #{data['identifier']}" diff --git a/actioncable/lib/action_cable/connection/web_socket.rb b/actioncable/lib/action_cable/connection/web_socket.rb index 5e89fb9b72..382141b89f 100644 --- a/actioncable/lib/action_cable/connection/web_socket.rb +++ b/actioncable/lib/action_cable/connection/web_socket.rb @@ -1,11 +1,11 @@ -require 'websocket/driver' +require "websocket/driver" module ActionCable module Connection # Wrap the real socket to minimize the externally-presented API class WebSocket - def initialize(env, event_target, stream_event_loop) - @websocket = ::WebSocket::Driver.websocket?(env) ? ClientSocket.new(env, event_target, stream_event_loop) : nil + def initialize(env, event_target, event_loop, protocols: ActionCable::INTERNAL[:protocols]) + @websocket = ::WebSocket::Driver.websocket?(env) ? ClientSocket.new(env, event_target, event_loop, protocols) : nil end def possible? @@ -24,6 +24,10 @@ module ActionCable websocket.close end + def protocol + websocket.protocol + end + def rack_response websocket.rack_response end diff --git a/actioncable/lib/action_cable/engine.rb b/actioncable/lib/action_cable/engine.rb index f5e233e091..4c5c975cd8 100644 --- a/actioncable/lib/action_cable/engine.rb +++ b/actioncable/lib/action_cable/engine.rb @@ -4,9 +4,9 @@ require "action_cable/helpers/action_cable_helper" require "active_support/core_ext/hash/indifferent_access" module ActionCable - class Railtie < Rails::Engine # :nodoc: + class Engine < Rails::Engine # :nodoc: config.action_cable = ActiveSupport::OrderedOptions.new - config.action_cable.url = '/cable' + config.action_cable.mount_path = ActionCable::INTERNAL[:default_mount_path] config.eager_load_namespaces << ActionCable @@ -22,7 +22,7 @@ module ActionCable initializer "action_cable.set_configs" do |app| options = app.config.action_cable - options.allowed_request_origins ||= "http://localhost:3000" if ::Rails.env.development? + options.allowed_request_origins ||= /https?:\/\/localhost:\d+/ if ::Rails.env.development? app.paths.add "config/cable", with: "config/cable.yml" @@ -31,8 +31,47 @@ module ActionCable self.cable = Rails.application.config_for(config_path).with_indifferent_access end + previous_connection_class = self.connection_class + self.connection_class = -> { "ApplicationCable::Connection".safe_constantize || previous_connection_class.call } + options.each { |k,v| send("#{k}=", v) } end end + + initializer "action_cable.routes" do + config.after_initialize do |app| + config = app.config + unless config.action_cable.mount_path.nil? + app.routes.prepend do + mount ActionCable.server => config.action_cable.mount_path, internal: true + end + end + end + end + + initializer "action_cable.set_work_hooks" do |app| + ActiveSupport.on_load(:action_cable) do + ActionCable::Server::Worker.set_callback :work, :around, prepend: true do |_, inner| + app.executor.wrap do + # If we took a while to get the lock, we may have been halted + # in the meantime. As we haven't started doing any real work + # yet, we should pretend that we never made it off the queue. + unless stopping? + inner.call + end + end + end + + wrap = lambda do |_, inner| + app.executor.wrap(&inner) + end + ActionCable::Channel::Base.set_callback :subscribe, :around, prepend: true, &wrap + ActionCable::Channel::Base.set_callback :unsubscribe, :around, prepend: true, &wrap + + app.reloader.before_class_unload do + ActionCable.server.restart + end + end + end end end diff --git a/actioncable/lib/action_cable/gem_version.rb b/actioncable/lib/action_cable/gem_version.rb index a71603e61a..8ba0230d47 100644 --- a/actioncable/lib/action_cable/gem_version.rb +++ b/actioncable/lib/action_cable/gem_version.rb @@ -6,9 +6,9 @@ module ActionCable module VERSION MAJOR = 5 - MINOR = 0 + MINOR = 1 TINY = 0 - PRE = "beta2" + PRE = "alpha" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actioncable/lib/action_cable/helpers/action_cable_helper.rb b/actioncable/lib/action_cable/helpers/action_cable_helper.rb index b82751468a..2081a37db6 100644 --- a/actioncable/lib/action_cable/helpers/action_cable_helper.rb +++ b/actioncable/lib/action_cable/helpers/action_cable_helper.rb @@ -1,28 +1,39 @@ module ActionCable module Helpers module ActionCableHelper - # Returns an "action-cable-url" meta tag with the value of the url specified in your - # configuration. Ensure this is above your javascript tag: + # Returns an "action-cable-url" meta tag with the value of the URL specified in your + # configuration. Ensure this is above your JavaScript tag: # # <head> # <%= action_cable_meta_tag %> # <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> # </head> # - # This is then used by ActionCable to determine the url of your websocket server. + # This is then used by Action Cable to determine the URL of your WebSocket server. # Your CoffeeScript can then connect to the server without needing to specify the - # url directly: + # URL directly: # # #= require cable # @App = {} # App.cable = Cable.createConsumer() # - # Make sure to specify the correct server location in each of your environments - # config file: + # Make sure to specify the correct server location in each of your environment + # config files: + # + # config.action_cable.mount_path = "/cable123" + # <%= action_cable_meta_tag %> would render: + # => <meta name="action-cable-url" content="/cable123" /> + # + # config.action_cable.url = "ws://actioncable.com" + # <%= action_cable_meta_tag %> would render: + # => <meta name="action-cable-url" content="ws://actioncable.com" /> # - # config.action_cable.url = "ws://example.com:28080" def action_cable_meta_tag - tag "meta", name: "action-cable-url", content: Rails.application.config.action_cable.url + tag "meta", name: "action-cable-url", content: ( + ActionCable.server.config.url || + ActionCable.server.config.mount_path || + raise("No Action Cable URL configured -- please configure this at config.action_cable.url") + ) end end end diff --git a/actioncable/lib/action_cable/remote_connections.rb b/actioncable/lib/action_cable/remote_connections.rb index 7ec121308a..720ba52d19 100644 --- a/actioncable/lib/action_cable/remote_connections.rb +++ b/actioncable/lib/action_cable/remote_connections.rb @@ -13,8 +13,8 @@ module ActionCable # ActionCable.server.remote_connections.where(current_user: User.find(1)).disconnect # # This will disconnect all the connections established for - # <tt>User.find(1)</tt> across all servers running on all machines, because - # it uses the internal channel that all these servers are subscribed to. + # <tt>User.find(1)</tt>, across all servers running on all machines, because + # it uses the internal channel that all of these servers are subscribed to. class RemoteConnections attr_reader :server @@ -28,7 +28,7 @@ module ActionCable private # Represents a single remote connection found via <tt>ActionCable.server.remote_connections.where(*)</tt>. - # Exists for the solely for the purpose of calling #disconnect on that connection. + # Exists solely for the purpose of calling #disconnect on that connection. class RemoteConnection class InvalidIdentifiersError < StandardError; end @@ -41,7 +41,7 @@ module ActionCable # Uses the internal channel to disconnect the connection. def disconnect - server.broadcast internal_channel, type: 'disconnect' + server.broadcast internal_channel, type: "disconnect" end # Returns all the identifiers that were applied to this connection. diff --git a/actioncable/lib/action_cable/server.rb b/actioncable/lib/action_cable/server.rb index bd6a3826a3..22f9353825 100644 --- a/actioncable/lib/action_cable/server.rb +++ b/actioncable/lib/action_cable/server.rb @@ -9,7 +9,7 @@ module ActionCable autoload :Configuration autoload :Worker - autoload :ActiveRecordConnectionManagement, 'action_cable/server/worker/active_record_connection_management' + autoload :ActiveRecordConnectionManagement, "action_cable/server/worker/active_record_connection_management" end end end diff --git a/actioncable/lib/action_cable/server/base.rb b/actioncable/lib/action_cable/server/base.rb index fe48c112df..419eccd73c 100644 --- a/actioncable/lib/action_cable/server/base.rb +++ b/actioncable/lib/action_cable/server/base.rb @@ -1,11 +1,11 @@ -require 'thread' +require "monitor" module ActionCable module Server - # A singleton ActionCable::Server instance is available via ActionCable.server. It's used by the rack process that starts the cable server, but - # also by the user to reach the RemoteConnections instead for finding and disconnecting connections across all servers. + # A singleton ActionCable::Server instance is available via ActionCable.server. It's used by the Rack process that starts the Action Cable server, but + # is also used by the user to reach the RemoteConnections object, which is used for finding and disconnecting connections across all servers. # - # Also, this is the server instance used for broadcasting. See Broadcasting for details. + # Also, this is the server instance used for broadcasting. See Broadcasting for more information. class Base include ActionCable::Server::Broadcasting include ActionCable::Server::Connections @@ -18,15 +18,14 @@ module ActionCable attr_reader :mutex def initialize - @mutex = Mutex.new - - @remote_connections = @stream_event_loop = @worker_pool = @channel_classes = @pubsub = nil + @mutex = Monitor.new + @remote_connections = @event_loop = @worker_pool = @pubsub = nil end - # Called by rack to setup the server. + # Called by Rack to setup the server. def call(env) setup_heartbeat_timer - config.connection_class.new(self, env).process + config.connection_class.call.new(self, env).process end # Disconnect all the connections identified by `identifiers` on this server or any others via RemoteConnections. @@ -34,38 +33,52 @@ module ActionCable remote_connections.where(identifiers).disconnect end + def restart + connections.each(&:close) + + @mutex.synchronize do + # Shutdown the worker pool + @worker_pool.halt if @worker_pool + @worker_pool = nil + + # Shutdown the pub/sub adapter + @pubsub.shutdown if @pubsub + @pubsub = nil + end + end + # Gateway to RemoteConnections. See that class for details. def remote_connections @remote_connections || @mutex.synchronize { @remote_connections ||= RemoteConnections.new(self) } end - def stream_event_loop - @stream_event_loop || @mutex.synchronize { @stream_event_loop ||= ActionCable::Connection::StreamEventLoop.new } + def event_loop + @event_loop || @mutex.synchronize { @event_loop ||= ActionCable::Connection::StreamEventLoop.new } end - # The thread worker pool for handling all the connection work on this server. Default size is set by config.worker_pool_size. + # The worker pool is where we run connection callbacks and channel actions. We do as little as possible on the server's main thread. + # The worker pool is an executor service that's backed by a pool of threads working from a task queue. The thread pool size maxes out + # at 4 worker threads by default. Tune the size yourself with <tt>config.action_cable.worker_pool_size</tt>. + # + # Using Active Record, Redis, etc within your channel actions means you'll get a separate connection from each thread in the worker pool. + # Plan your deployment accordingly: 5 servers each running 5 Puma workers each running an 8-thread worker pool means at least 200 database + # connections. + # + # Also, ensure that your database connection pool size is as least as large as your worker pool size. Otherwise, workers may oversubscribe + # the database connection pool and block while they wait for other workers to release their connections. Use a smaller worker pool or a larger + # database connection pool instead. def worker_pool @worker_pool || @mutex.synchronize { @worker_pool ||= ActionCable::Server::Worker.new(max_size: config.worker_pool_size) } end - # Requires and returns a hash of all the channel class constants keyed by name. - def channel_classes - @channel_classes || @mutex.synchronize do - @channel_classes ||= begin - config.channel_paths.each { |channel_path| require channel_path } - config.channel_class_names.each_with_object({}) { |name, hash| hash[name] = name.constantize } - end - end - end - # Adapter used for all streams/broadcasting. def pubsub @pubsub || @mutex.synchronize { @pubsub ||= config.pubsub_adapter.new(self) } end - # All the identifiers applied to the connection class associated with this server. + # All of the identifiers applied to the connection class associated with this server. def connection_identifiers - config.connection_class.identifiers + config.connection_class.call.identifiers end end diff --git a/actioncable/lib/action_cable/server/broadcasting.rb b/actioncable/lib/action_cable/server/broadcasting.rb index b87232671b..1fc58baa3e 100644 --- a/actioncable/lib/action_cable/server/broadcasting.rb +++ b/actioncable/lib/action_cable/server/broadcasting.rb @@ -1,6 +1,6 @@ module ActionCable module Server - # Broadcasting is how other parts of your application can send messages to the channel subscribers. As explained in Channel, most of the time, these + # Broadcasting is how other parts of your application can send messages to a channel's subscribers. As explained in Channel, most of the time, these # broadcastings are streamed directly to the clients subscribed to the named broadcasting. Let's explain with a full-stack example: # # class WebNotificationsChannel < ApplicationCable::Channel @@ -9,37 +9,42 @@ module ActionCable # end # end # - # # Somewhere in your app this is called, perhaps from a NewCommentJob + # # Somewhere in your app this is called, perhaps from a NewCommentJob: # ActionCable.server.broadcast \ # "web_notifications_1", { title: "New things!", body: "All that's fit for print" } # - # # Client-side CoffeeScript, which assumes you've already requested the right to send web notifications + # # Client-side CoffeeScript, which assumes you've already requested the right to send web notifications: # App.cable.subscriptions.create "WebNotificationsChannel", # received: (data) -> # new Notification data['title'], body: data['body'] module Broadcasting - # Broadcast a hash directly to a named <tt>broadcasting</tt>. It'll automatically be JSON encoded. - def broadcast(broadcasting, message) - broadcaster_for(broadcasting).broadcast(message) + # Broadcast a hash directly to a named <tt>broadcasting</tt>. This will later be JSON encoded. + def broadcast(broadcasting, message, coder: ActiveSupport::JSON) + broadcaster_for(broadcasting, coder: coder).broadcast(message) end # Returns a broadcaster for a named <tt>broadcasting</tt> that can be reused. Useful when you have an object that # may need multiple spots to transmit to a specific broadcasting over and over. - def broadcaster_for(broadcasting) - Broadcaster.new(self, broadcasting) + def broadcaster_for(broadcasting, coder: ActiveSupport::JSON) + Broadcaster.new(self, String(broadcasting), coder: coder) end private class Broadcaster - attr_reader :server, :broadcasting + attr_reader :server, :broadcasting, :coder - def initialize(server, broadcasting) - @server, @broadcasting = server, broadcasting + def initialize(server, broadcasting, coder:) + @server, @broadcasting, @coder = server, broadcasting, coder end def broadcast(message) - server.logger.info "[ActionCable] Broadcasting to #{broadcasting}: #{message}" - server.pubsub.broadcast broadcasting, ActiveSupport::JSON.encode(message) + server.logger.info "[ActionCable] Broadcasting to #{broadcasting}: #{message.inspect}" + + payload = { broadcasting: broadcasting, message: message, coder: coder } + ActiveSupport::Notifications.instrument("broadcast.action_cable", payload) do + encoded = coder ? coder.encode(message) : message + server.pubsub.broadcast broadcasting, encoded + end end end end diff --git a/actioncable/lib/action_cable/server/configuration.rb b/actioncable/lib/action_cable/server/configuration.rb index 9a248933c4..17e0dee064 100644 --- a/actioncable/lib/action_cable/server/configuration.rb +++ b/actioncable/lib/action_cable/server/configuration.rb @@ -1,42 +1,28 @@ module ActionCable module Server - # An instance of this configuration object is available via ActionCable.server.config, which allows you to tweak the configuration points + # An instance of this configuration object is available via ActionCable.server.config, which allows you to tweak Action Cable configuration # in a Rails config initializer. class Configuration attr_accessor :logger, :log_tags attr_accessor :connection_class, :worker_pool_size - attr_accessor :channel_load_paths - attr_accessor :disable_request_forgery_protection, :allowed_request_origins - attr_accessor :cable, :url + attr_accessor :disable_request_forgery_protection, :allowed_request_origins, :allow_same_origin_as_host + attr_accessor :cable, :url, :mount_path def initialize @log_tags = [] - @connection_class = ApplicationCable::Connection - @worker_pool_size = 100 - - @channel_load_paths = [Rails.root.join('app/channels')] + @connection_class = -> { ActionCable::Connection::Base } + @worker_pool_size = 4 @disable_request_forgery_protection = false - end - - def channel_paths - @channel_paths ||= channel_load_paths.flat_map do |path| - Dir["#{path}/**/*_channel.rb"] - end - end - - def channel_class_names - @channel_class_names ||= channel_paths.collect do |channel_path| - Pathname.new(channel_path).basename.to_s.split('.').first.camelize - end + @allow_same_origin_as_host = true end # Returns constant of subscription adapter specified in config/cable.yml. # If the adapter cannot be found, this will default to the Redis adapter. # Also makes sure proper dependencies are required. def pubsub_adapter - adapter = (cable.fetch('adapter') { 'redis' }) + adapter = (cable.fetch("adapter") { "redis" }) path_to_adapter = "action_cable/subscription_adapter/#{adapter}" begin require path_to_adapter @@ -47,7 +33,7 @@ module ActionCable end adapter = adapter.camelize - adapter = 'PostgreSQL' if adapter == 'Postgresql' + adapter = "PostgreSQL" if adapter == "Postgresql" "ActionCable::SubscriptionAdapter::#{adapter}".constantize end end diff --git a/actioncable/lib/action_cable/server/connections.rb b/actioncable/lib/action_cable/server/connections.rb index 8671dd5ebd..5e61b4e335 100644 --- a/actioncable/lib/action_cable/server/connections.rb +++ b/actioncable/lib/action_cable/server/connections.rb @@ -1,9 +1,8 @@ module ActionCable module Server - # Collection class for all the connections that's been established on this specific server. Remember, usually you'll run many cable servers, so - # you can't use this collection as an full list of all the connections established against your application. Use RemoteConnections for that. - # As such, this is primarily for internal use. - module Connections + # Collection class for all the connections that have been established on this specific server. Remember, usually you'll run many Action Cable servers, so + # you can't use this collection as a full list of all of the connections established against your application. Instead, use RemoteConnections for that. + module Connections # :nodoc: BEAT_INTERVAL = 3 def connections @@ -19,12 +18,12 @@ module ActionCable end # WebSocket connection implementations differ on when they'll mark a connection as stale. We basically never want a connection to go stale, as you - # then can't rely on being able to receive and send to it. So there's a 3 second heartbeat running on all connections. If the beat fails, we automatically + # then can't rely on being able to communicate with the connection. To solve this, a 3 second heartbeat runs on all connections. If the beat fails, we automatically # disconnect. def setup_heartbeat_timer - @heartbeat_timer ||= Concurrent::TimerTask.new(execution_interval: BEAT_INTERVAL) do - Concurrent.global_io_executor.post { connections.map(&:beat) } - end.tap(&:execute) + @heartbeat_timer ||= event_loop.timer(BEAT_INTERVAL) do + event_loop.post { connections.map(&:beat) } + end end def open_connections_statistics diff --git a/actioncable/lib/action_cable/server/worker.rb b/actioncable/lib/action_cable/server/worker.rb index 3b6c6d44a1..43639c27af 100644 --- a/actioncable/lib/action_cable/server/worker.rb +++ b/actioncable/lib/action_cable/server/worker.rb @@ -1,63 +1,67 @@ -require 'active_support/callbacks' -require 'active_support/core_ext/module/attribute_accessors_per_thread' -require 'concurrent' +require "active_support/callbacks" +require "active_support/core_ext/module/attribute_accessors_per_thread" +require "concurrent" module ActionCable module Server - # Worker used by Server.send_async to do connection work in threads. Only for internal use. - class Worker + # Worker used by Server.send_async to do connection work in threads. + class Worker # :nodoc: include ActiveSupport::Callbacks thread_mattr_accessor :connection define_callbacks :work include ActiveRecordConnectionManagement + attr_reader :executor + def initialize(max_size: 5) - @pool = Concurrent::ThreadPoolExecutor.new( + @executor = Concurrent::ThreadPoolExecutor.new( min_threads: 1, max_threads: max_size, max_queue: 0, ) end - def async_invoke(receiver, method, *args) - @pool.post do - invoke(receiver, method, *args) - end + # Stop processing work: any work that has not already started + # running will be discarded from the queue + def halt + @executor.shutdown end - def invoke(receiver, method, *args) - begin - self.connection = receiver + def stopping? + @executor.shuttingdown? + end - run_callbacks :work do - receiver.send method, *args - end - rescue Exception => e - logger.error "There was an exception - #{e.class}(#{e.message})" - logger.error e.backtrace.join("\n") + def work(connection) + self.connection = connection - receiver.handle_exception if receiver.respond_to?(:handle_exception) - ensure - self.connection = nil + run_callbacks :work do + yield end + ensure + self.connection = nil + end + + def async_exec(receiver, *args, connection:, &block) + async_invoke receiver, :instance_exec, *args, connection: connection, &block end - def async_run_periodic_timer(channel, callback) - @pool.post do - run_periodic_timer(channel, callback) + def async_invoke(receiver, method, *args, connection: receiver, &block) + @executor.post do + invoke(receiver, method, *args, connection: connection, &block) end end - def run_periodic_timer(channel, callback) - begin - self.connection = channel.connection + def invoke(receiver, method, *args, connection:, &block) + work(connection) do + begin + receiver.send method, *args, &block + rescue Exception => e + logger.error "There was an exception - #{e.class}(#{e.message})" + logger.error e.backtrace.join("\n") - run_callbacks :work do - callback.respond_to?(:call) ? channel.instance_exec(&callback) : channel.send(callback) + receiver.handle_exception if receiver.respond_to?(:handle_exception) end - ensure - self.connection = nil end end diff --git a/actioncable/lib/action_cable/server/worker/active_record_connection_management.rb b/actioncable/lib/action_cable/server/worker/active_record_connection_management.rb index ecece4e270..c1e4aa8103 100644 --- a/actioncable/lib/action_cable/server/worker/active_record_connection_management.rb +++ b/actioncable/lib/action_cable/server/worker/active_record_connection_management.rb @@ -1,7 +1,6 @@ module ActionCable module Server class Worker - # Clear active connections between units of work so the long-running channel or connection processes do not hoard connections. module ActiveRecordConnectionManagement extend ActiveSupport::Concern @@ -13,10 +12,8 @@ module ActionCable def with_database_connections connection.logger.tag(ActiveRecord::Base.logger) { yield } - ensure - ActiveRecord::Base.clear_active_connections! end end end end -end
\ No newline at end of file +end diff --git a/actioncable/lib/action_cable/subscription_adapter/async.rb b/actioncable/lib/action_cable/subscription_adapter/async.rb index cca6894289..46819dbfec 100644 --- a/actioncable/lib/action_cable/subscription_adapter/async.rb +++ b/actioncable/lib/action_cable/subscription_adapter/async.rb @@ -1,20 +1,25 @@ -require 'action_cable/subscription_adapter/inline' +require "action_cable/subscription_adapter/inline" module ActionCable module SubscriptionAdapter class Async < Inline # :nodoc: private def new_subscriber_map - AsyncSubscriberMap.new + AsyncSubscriberMap.new(server.event_loop) end class AsyncSubscriberMap < SubscriberMap + def initialize(event_loop) + @event_loop = event_loop + super() + end + def add_subscriber(*) - Concurrent.global_io_executor.post { super } + @event_loop.post { super } end def invoke_callback(*) - Concurrent.global_io_executor.post { super } + @event_loop.post { super } 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 af04a58c70..bcd46d2a0e 100644 --- a/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb +++ b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb @@ -1,9 +1,9 @@ -require 'thread' +require "thread" -gem 'em-hiredis', '~> 0.3.0' -gem 'redis', '~> 3.0' -require 'em-hiredis' -require 'redis' +gem "em-hiredis", "~> 0.3.0" +gem "redis", "~> 3.0" +require "em-hiredis" +require "redis" EventMachine.epoll if EventMachine.epoll? EventMachine.kqueue if EventMachine.kqueue? @@ -13,11 +13,11 @@ module ActionCable class EventedRedis < Base # :nodoc: @@mutex = Mutex.new - # Overwrite this factory method for EventMachine redis connections if you want to use a different Redis library than EM::Hiredis. + # Overwrite this factory method for EventMachine Redis connections if you want to use a different Redis connection library than EM::Hiredis. # This is needed, for example, when using Makara proxies for distributed Redis. cattr_accessor(:em_redis_connector) { ->(config) { EM::Hiredis.connect(config[:url]) } } - # Overwrite this factory method for redis connections if you want to use a different Redis library than Redis. + # Overwrite this factory method for Redis connections if you want to use a different Redis connection 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]) } } @@ -51,7 +51,11 @@ module ActionCable @redis_connection_for_subscriptions || @server.mutex.synchronize do @redis_connection_for_subscriptions ||= self.class.em_redis_connector.call(@server.config.cable).tap do |redis| redis.on(:reconnect_failed) do - @logger.info "[ActionCable] Redis reconnect failed." + @logger.error "[ActionCable] Redis reconnect failed." + end + + redis.on(:failed) do + @logger.error "[ActionCable] Redis connection has failed." end end end diff --git a/actioncable/lib/action_cable/subscription_adapter/postgresql.rb b/actioncable/lib/action_cable/subscription_adapter/postgresql.rb index abaeb92e54..bdab5205ec 100644 --- a/actioncable/lib/action_cable/subscription_adapter/postgresql.rb +++ b/actioncable/lib/action_cable/subscription_adapter/postgresql.rb @@ -1,6 +1,6 @@ -gem 'pg', '~> 0.18' -require 'pg' -require 'thread' +gem "pg", "~> 0.18" +require "pg" +require "thread" module ActionCable module SubscriptionAdapter @@ -33,7 +33,7 @@ module ActionCable pg_conn = ar_conn.raw_connection unless pg_conn.is_a?(PG::Connection) - raise 'ActiveRecord database must be Postgres in order to use the Postgres ActionCable storage adapter' + raise "The Active Record database must be PostgreSQL in order to use the PostgreSQL Action Cable storage adapter" end yield pg_conn @@ -42,14 +42,15 @@ module ActionCable private def listener - @listener || @server.mutex.synchronize { @listener ||= Listener.new(self) } + @listener || @server.mutex.synchronize { @listener ||= Listener.new(self, @server.event_loop) } end class Listener < SubscriberMap - def initialize(adapter) + def initialize(adapter, event_loop) super() @adapter = adapter + @event_loop = event_loop @queue = Queue.new @thread = Thread.new do @@ -68,7 +69,7 @@ module ActionCable case action when :listen pg_conn.exec("LISTEN #{pg_conn.escape_identifier channel}") - Concurrent.global_io_executor << callback if callback + @event_loop.post(&callback) if callback when :unlisten pg_conn.exec("UNLISTEN #{pg_conn.escape_identifier channel}") when :shutdown @@ -98,7 +99,7 @@ module ActionCable end def invoke_callback(*) - Concurrent.global_io_executor.post { super } + @event_loop.post { super } end end end diff --git a/actioncable/lib/action_cable/subscription_adapter/redis.rb b/actioncable/lib/action_cable/subscription_adapter/redis.rb index ba4934a264..62bd284a6b 100644 --- a/actioncable/lib/action_cable/subscription_adapter/redis.rb +++ b/actioncable/lib/action_cable/subscription_adapter/redis.rb @@ -1,7 +1,7 @@ -require 'thread' +require "thread" -gem 'redis', '~> 3.0' -require 'redis' +gem "redis", "~> 3.0" +require "redis" module ActionCable module SubscriptionAdapter @@ -33,25 +33,30 @@ module ActionCable end def redis_connection_for_subscriptions - ::Redis.new(@server.config.cable) + redis_connection end private def listener - @listener || @server.mutex.synchronize { @listener ||= Listener.new(self) } + @listener || @server.mutex.synchronize { @listener ||= Listener.new(self, @server.event_loop) } end def redis_connection_for_broadcasts @redis_connection_for_broadcasts || @server.mutex.synchronize do - @redis_connection_for_broadcasts ||= self.class.redis_connector.call(@server.config.cable) + @redis_connection_for_broadcasts ||= redis_connection end end + def redis_connection + self.class.redis_connector.call(@server.config.cable) + end + class Listener < SubscriberMap - def initialize(adapter) + def initialize(adapter, event_loop) super() @adapter = adapter + @event_loop = event_loop @subscribe_callbacks = Hash.new { |h, k| h[k] = [] } @subscription_lock = Mutex.new @@ -67,7 +72,7 @@ module ActionCable conn.without_reconnect do original_client = conn.client - conn.subscribe('_action_cable_internal') do |on| + conn.subscribe("_action_cable_internal") do |on| on.subscribe do |chan, count| @subscription_lock.synchronize do if count == 1 @@ -80,7 +85,7 @@ module ActionCable if callbacks = @subscribe_callbacks[chan] next_callback = callbacks.shift - Concurrent.global_io_executor << next_callback if next_callback + @event_loop.post(&next_callback) if next_callback @subscribe_callbacks.delete(chan) if callbacks.empty? end end @@ -106,7 +111,7 @@ module ActionCable return if @thread.nil? when_connected do - send_command('unsubscribe') + send_command("unsubscribe") @raw_client = nil end end @@ -118,18 +123,18 @@ module ActionCable @subscription_lock.synchronize do ensure_listener_running @subscribe_callbacks[channel] << on_success - when_connected { send_command('subscribe', channel) } + when_connected { send_command("subscribe", channel) } end end def remove_channel(channel) @subscription_lock.synchronize do - when_connected { send_command('unsubscribe', channel) } + when_connected { send_command("unsubscribe", channel) } end end def invoke_callback(*) - Concurrent.global_io_executor.post { super } + @event_loop.post { super } end private diff --git a/actioncable/lib/action_cable/subscription_adapter/subscriber_map.rb b/actioncable/lib/action_cable/subscription_adapter/subscriber_map.rb index 37eed09793..4ec513e3ba 100644 --- a/actioncable/lib/action_cable/subscription_adapter/subscriber_map.rb +++ b/actioncable/lib/action_cable/subscription_adapter/subscriber_map.rb @@ -32,7 +32,11 @@ module ActionCable end def broadcast(channel, message) - list = @sync.synchronize { @subscribers[channel].dup } + list = @sync.synchronize do + return if !@subscribers.key?(channel) + @subscribers[channel].dup + end + list.each do |subscriber| invoke_callback(subscriber, message) end diff --git a/actioncable/lib/action_cable/version.rb b/actioncable/lib/action_cable/version.rb index e17877202b..d6081409f0 100644 --- a/actioncable/lib/action_cable/version.rb +++ b/actioncable/lib/action_cable/version.rb @@ -1,4 +1,4 @@ -require_relative 'gem_version' +require_relative "gem_version" module ActionCable # Returns the version of the currently loaded Action Cable as a <tt>Gem::Version</tt> diff --git a/actioncable/lib/rails/generators/channel/USAGE b/actioncable/lib/rails/generators/channel/USAGE index 27a934c689..6249553c22 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.coffee after generating any channels. + Note: Turn on the cable connection in app/assets/javascript/cable.js after generating any channels. Example: ======== diff --git a/actioncable/lib/rails/generators/channel/channel_generator.rb b/actioncable/lib/rails/generators/channel/channel_generator.rb index c5d398810a..20d807c033 100644 --- a/actioncable/lib/rails/generators/channel/channel_generator.rb +++ b/actioncable/lib/rails/generators/channel/channel_generator.rb @@ -10,16 +10,37 @@ module Rails check_class_collision suffix: "Channel" def create_channel_file - template "channel.rb", File.join('app/channels', class_path, "#{file_name}_channel.rb") + template "channel.rb", File.join("app/channels", class_path, "#{file_name}_channel.rb") if options[:assets] - template "assets/channel.coffee", File.join('app/assets/javascripts/channels', class_path, "#{file_name}.coffee") + if self.behavior == :invoke + template "assets/cable.js", "app/assets/javascripts/cable.js" + end + + js_template "assets/channel", File.join("app/assets/javascripts/channels", class_path, "#{file_name}") end + + generate_application_cable_files end protected def file_name - @_file_name ||= super.gsub(/\_channel/i, '') + @_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 + + files = [ + "application_cable/channel.rb", + "application_cable/connection.rb" + ] + + files.each do |name| + path = File.join("app/channels/", name) + template(name, path) if !File.exist?(path) + end end end end diff --git a/actioncable/lib/rails/generators/channel/templates/application_cable/channel.rb b/actioncable/lib/rails/generators/channel/templates/application_cable/channel.rb new file mode 100644 index 0000000000..d672697283 --- /dev/null +++ b/actioncable/lib/rails/generators/channel/templates/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/actioncable/lib/rails/generators/channel/templates/application_cable/connection.rb b/actioncable/lib/rails/generators/channel/templates/application_cable/connection.rb new file mode 100644 index 0000000000..0ff5442f47 --- /dev/null +++ b/actioncable/lib/rails/generators/channel/templates/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/actioncable/lib/rails/generators/channel/templates/assets/cable.js b/actioncable/lib/rails/generators/channel/templates/assets/cable.js new file mode 100644 index 0000000000..739aa5f022 --- /dev/null +++ b/actioncable/lib/rails/generators/channel/templates/assets/cable.js @@ -0,0 +1,13 @@ +// Action Cable provides the framework to deal with WebSockets in Rails. +// You can generate new channels where WebSocket features live using the `rails generate channel` command. +// +//= require action_cable +//= require_self +//= require_tree ./channels + +(function() { + this.App || (this.App = {}); + + App.cable = ActionCable.createConsumer(); + +}).call(this); diff --git a/actioncable/lib/rails/generators/channel/templates/assets/channel.js b/actioncable/lib/rails/generators/channel/templates/assets/channel.js new file mode 100644 index 0000000000..ab0e68b11a --- /dev/null +++ b/actioncable/lib/rails/generators/channel/templates/assets/channel.js @@ -0,0 +1,18 @@ +App.<%= class_name.underscore %> = App.cable.subscriptions.create("<%= class_name %>Channel", { + connected: function() { + // Called when the subscription is ready for use on the server + }, + + disconnected: function() { + // Called when the subscription has been terminated by the server + }, + + received: function(data) { + // Called when there's incoming data on the websocket for this channel + }<%= actions.any? ? ",\n" : '' %> +<% actions.each do |action| -%> + <%=action %>: function() { + return this.perform('<%= action %>'); + }<%= action == actions[-1] ? '' : ",\n" %> +<% end -%> +}); diff --git a/actioncable/lib/rails/generators/channel/templates/channel.rb b/actioncable/lib/rails/generators/channel/templates/channel.rb index 7bff3341c1..4bcfb2be4d 100644 --- a/actioncable/lib/rails/generators/channel/templates/channel.rb +++ b/actioncable/lib/rails/generators/channel/templates/channel.rb @@ -1,4 +1,3 @@ -# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading. <% module_namespacing do -%> class <%= class_name %>Channel < ApplicationCable::Channel def subscribed diff --git a/actioncable/package.json b/actioncable/package.json new file mode 100644 index 0000000000..37f82fa1ea --- /dev/null +++ b/actioncable/package.json @@ -0,0 +1,24 @@ +{ + "name": "actioncable", + "version": "5.0.0-rc1", + "description": "WebSocket framework for Ruby on Rails.", + "main": "lib/assets/compiled/action_cable.js", + "files": [ + "lib/assets/compiled/*.js" + ], + "repository": { + "type": "git", + "url": "rails/rails" + }, + "keywords": [ + "websockets", + "actioncable", + "rails" + ], + "author": "David Heinemeier Hansson <david@loudthinking.com>", + "license": "MIT", + "bugs": { + "url": "https://github.com/rails/rails/issues" + }, + "homepage": "http://rubyonrails.org/" +} diff --git a/actioncable/test/channel/base_test.rb b/actioncable/test/channel/base_test.rb index d41bf3064b..9a3a3581e6 100644 --- a/actioncable/test/channel/base_test.rb +++ b/actioncable/test/channel/base_test.rb @@ -1,6 +1,6 @@ -require 'test_helper' -require 'stubs/test_connection' -require 'stubs/room' +require "test_helper" +require "stubs/test_connection" +require "stubs/room" class ActionCable::Channel::BaseTest < ActiveSupport::TestCase class ActionCable::Channel::Base @@ -58,7 +58,7 @@ class ActionCable::Channel::BaseTest < ActiveSupport::TestCase end def get_latest - transmit data: 'latest' + transmit data: "latest" end def receive @@ -74,14 +74,16 @@ class ActionCable::Channel::BaseTest < ActiveSupport::TestCase setup do @user = User.new "lifo" @connection = TestConnection.new(@user) - @channel = ChatChannel.new @connection, "{id: 1}", { id: 1 } + @channel = ChatChannel.new @connection, "{id: 1}", id: 1 end - test "should subscribe to a channel on initialize" do + test "should subscribe to a channel" do + @channel.subscribe_to_channel assert_equal 1, @channel.room.id end test "on subscribe callbacks" do + @channel.subscribe_to_channel assert @channel.subscribed end @@ -90,6 +92,8 @@ class ActionCable::Channel::BaseTest < ActiveSupport::TestCase end test "unsubscribing from a channel" do + @channel.subscribe_to_channel + assert @channel.room assert @channel.subscribed? @@ -104,54 +108,59 @@ class ActionCable::Channel::BaseTest < ActiveSupport::TestCase end test "callable action without any argument" do - @channel.perform_action 'action' => :leave + @channel.perform_action "action" => :leave assert_equal [ :leave ], @channel.last_action end test "callable action with arguments" do - data = { 'action' => :speak, 'content' => "Hello World" } + data = { "action" => :speak, "content" => "Hello World" } @channel.perform_action data assert_equal [ :speak, data ], @channel.last_action end test "should not dispatch a private method" do - @channel.perform_action 'action' => :rm_rf + @channel.perform_action "action" => :rm_rf assert_nil @channel.last_action end test "should not dispatch a public method defined on Base" do - @channel.perform_action 'action' => :kick + @channel.perform_action "action" => :kick assert_nil @channel.last_action end test "should dispatch a public method defined on Base and redefined on channel" do - data = { 'action' => :topic, 'content' => "This is Sparta!" } + data = { "action" => :topic, "content" => "This is Sparta!" } @channel.perform_action data assert_equal [ :topic, data ], @channel.last_action end test "should dispatch calling a public method defined in an ancestor" do - @channel.perform_action 'action' => :chatters + @channel.perform_action "action" => :chatters assert_equal [ :chatters ], @channel.last_action end test "should dispatch receive action when perform_action is called with empty action" do - data = { 'content' => 'hello' } + data = { "content" => "hello" } @channel.perform_action data assert_equal [ :receive ], @channel.last_action end test "transmitting data" do - @channel.perform_action 'action' => :get_latest + @channel.perform_action "action" => :get_latest - expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "message" => { "data" => "latest" } + expected = { "identifier" => "{id: 1}", "message" => { "data" => "latest" } } assert_equal expected, @connection.last_transmission end - test "subscription confirmation" do - expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "type" => "confirm_subscription" + test "do not send subscription confirmation on initialize" do + assert_nil @connection.last_transmission + end + + test "subscription confirmation on subscribe_to_channel" do + expected = { "identifier" => "{id: 1}", "type" => "confirm_subscription" } + @channel.subscribe_to_channel assert_equal expected, @connection.last_transmission end @@ -162,7 +171,84 @@ class ActionCable::Channel::BaseTest < ActiveSupport::TestCase test "invalid action on Channel" do assert_logged("Unable to process ActionCable::Channel::BaseTest::ChatChannel#invalid_action") do - @channel.perform_action 'action' => :invalid_action + @channel.perform_action "action" => :invalid_action + end + end + + test "notification for perform_action" do + begin + events = [] + ActiveSupport::Notifications.subscribe "perform_action.action_cable" do |*args| + events << ActiveSupport::Notifications::Event.new(*args) + end + + data = { "action" => :speak, "content" => "hello" } + @channel.perform_action data + + assert_equal 1, events.length + assert_equal "perform_action.action_cable", events[0].name + assert_equal "ActionCable::Channel::BaseTest::ChatChannel", events[0].payload[:channel_class] + assert_equal :speak, events[0].payload[:action] + assert_equal data, events[0].payload[:data] + ensure + ActiveSupport::Notifications.unsubscribe "perform_action.action_cable" + end + end + + test "notification for transmit" do + begin + events = [] + ActiveSupport::Notifications.subscribe "transmit.action_cable" do |*args| + events << ActiveSupport::Notifications::Event.new(*args) + end + + @channel.perform_action "action" => :get_latest + expected_data = { data: "latest" } + + assert_equal 1, events.length + assert_equal "transmit.action_cable", events[0].name + assert_equal "ActionCable::Channel::BaseTest::ChatChannel", events[0].payload[:channel_class] + assert_equal expected_data, events[0].payload[:data] + assert_nil events[0].payload[:via] + ensure + ActiveSupport::Notifications.unsubscribe "transmit.action_cable" + end + end + + test "notification for transmit_subscription_confirmation" do + begin + @channel.subscribe_to_channel + + events = [] + ActiveSupport::Notifications.subscribe "transmit_subscription_confirmation.action_cable" do |*args| + events << ActiveSupport::Notifications::Event.new(*args) + end + + @channel.stubs(:subscription_confirmation_sent?).returns(false) + @channel.send(:transmit_subscription_confirmation) + + assert_equal 1, events.length + assert_equal "transmit_subscription_confirmation.action_cable", events[0].name + assert_equal "ActionCable::Channel::BaseTest::ChatChannel", events[0].payload[:channel_class] + ensure + ActiveSupport::Notifications.unsubscribe "transmit_subscription_confirmation.action_cable" + end + end + + test "notification for transmit_subscription_rejection" do + begin + events = [] + ActiveSupport::Notifications.subscribe "transmit_subscription_rejection.action_cable" do |*args| + events << ActiveSupport::Notifications::Event.new(*args) + end + + @channel.send(:transmit_subscription_rejection) + + assert_equal 1, events.length + assert_equal "transmit_subscription_rejection.action_cable", events[0].name + assert_equal "ActionCable::Channel::BaseTest::ChatChannel", events[0].payload[:channel_class] + ensure + ActiveSupport::Notifications.unsubscribe "transmit_subscription_rejection.action_cable" end end diff --git a/actioncable/test/channel/broadcasting_test.rb b/actioncable/test/channel/broadcasting_test.rb index 1de04243e5..3476c1db31 100644 --- a/actioncable/test/channel/broadcasting_test.rb +++ b/actioncable/test/channel/broadcasting_test.rb @@ -1,6 +1,6 @@ -require 'test_helper' -require 'stubs/test_connection' -require 'stubs/room' +require "test_helper" +require "stubs/test_connection" +require "stubs/room" class ActionCable::Channel::BroadcastingTest < ActiveSupport::TestCase class ChatChannel < ActionCable::Channel::Base @@ -11,7 +11,7 @@ class ActionCable::Channel::BroadcastingTest < ActiveSupport::TestCase end test "broadcasts_to" do - ActionCable.stubs(:server).returns mock().tap { |m| m.expects(:broadcast).with('action_cable:channel:broadcasting_test:chat:Room#1-Campfire', "Hello World") } + ActionCable.stubs(:server).returns mock().tap { |m| m.expects(:broadcast).with("action_cable:channel:broadcasting_test:chat:Room#1-Campfire", "Hello World") } ChatChannel.broadcast_to(Room.new(1), "Hello World") end diff --git a/actioncable/test/channel/naming_test.rb b/actioncable/test/channel/naming_test.rb index 89ef6ad8b0..08f0e7be48 100644 --- a/actioncable/test/channel/naming_test.rb +++ b/actioncable/test/channel/naming_test.rb @@ -1,4 +1,4 @@ -require 'test_helper' +require "test_helper" class ActionCable::Channel::NamingTest < ActiveSupport::TestCase class ChatChannel < ActionCable::Channel::Base diff --git a/actioncable/test/channel/periodic_timers_test.rb b/actioncable/test/channel/periodic_timers_test.rb index 64f0247cd6..2ee711fd29 100644 --- a/actioncable/test/channel/periodic_timers_test.rb +++ b/actioncable/test/channel/periodic_timers_test.rb @@ -1,12 +1,21 @@ -require 'test_helper' -require 'stubs/test_connection' -require 'stubs/room' +require "test_helper" +require "stubs/test_connection" +require "stubs/room" +require "active_support/time" class ActionCable::Channel::PeriodicTimersTest < ActiveSupport::TestCase class ChatChannel < ActionCable::Channel::Base - periodically -> { ping }, every: 5 + # Method name arg periodically :send_updates, every: 1 + # Proc arg + periodically -> { ping }, every: 2 + + # Block arg + periodically every: 3 do + ping + end + private def ping end @@ -19,22 +28,42 @@ class ActionCable::Channel::PeriodicTimersTest < ActiveSupport::TestCase test "periodic timers definition" do timers = ChatChannel.periodic_timers - assert_equal 2, timers.size + assert_equal 3, timers.size - first_timer = timers[0] - assert_kind_of Proc, first_timer[0] - assert_equal 5, first_timer[1][:every] + timers.each_with_index do |timer, i| + assert_kind_of Proc, timer[0] + assert_equal i+1, timer[1][:every] + end + end - second_timer = timers[1] - assert_equal :send_updates, second_timer[0] - assert_equal 1, second_timer[1][:every] + 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 + ChatChannel.periodically :send_updates, every: invalid + end + end + end + + test "disallow block and arg together" do + assert_raise ArgumentError, /not both/ do + ChatChannel.periodically(:send_updates, every: 1) { ping } + end + end + + test "disallow unknown args" do + [ "send_updates", Object.new, nil ].each do |invalid| + assert_raise ArgumentError, /Expected a Symbol/ do + ChatChannel.periodically invalid, every: 1 + end + end end test "timer start and stop" do - Concurrent::TimerTask.expects(:new).times(2).returns(true) - channel = ChatChannel.new @connection, "{id: 1}", { id: 1 } + @connection.server.event_loop.expects(:timer).times(3).returns(stub(shutdown: nil)) + channel = ChatChannel.new @connection, "{id: 1}", id: 1 - channel.expects(:stop_periodic_timers).once + channel.subscribe_to_channel channel.unsubscribe_from_channel + assert_equal [], channel.send(:active_periodic_timers) end end diff --git a/actioncable/test/channel/rejection_test.rb b/actioncable/test/channel/rejection_test.rb index aa93396d44..99c4a7603a 100644 --- a/actioncable/test/channel/rejection_test.rb +++ b/actioncable/test/channel/rejection_test.rb @@ -1,12 +1,15 @@ -require 'test_helper' -require 'stubs/test_connection' -require 'stubs/room' +require "test_helper" +require "stubs/test_connection" +require "stubs/room" class ActionCable::Channel::RejectionTest < ActiveSupport::TestCase class SecretChannel < ActionCable::Channel::Base def subscribed reject if params[:id] > 0 end + + def secret_action + end end setup do @@ -16,10 +19,23 @@ class ActionCable::Channel::RejectionTest < ActiveSupport::TestCase test "subscription rejection" do @connection.expects(:subscriptions).returns mock().tap { |m| m.expects(:remove_subscription).with instance_of(SecretChannel) } - @channel = SecretChannel.new @connection, "{id: 1}", { id: 1 } + @channel = SecretChannel.new @connection, "{id: 1}", id: 1 + @channel.subscribe_to_channel - expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "type" => "reject_subscription" + expected = { "identifier" => "{id: 1}", "type" => "reject_subscription" } assert_equal expected, @connection.last_transmission end + test "does not execute action if subscription is rejected" do + @connection.expects(:subscriptions).returns mock().tap { |m| m.expects(:remove_subscription).with instance_of(SecretChannel) } + @channel = SecretChannel.new @connection, "{id: 1}", id: 1 + @channel.subscribe_to_channel + + expected = { "identifier" => "{id: 1}", "type" => "reject_subscription" } + assert_equal expected, @connection.last_transmission + assert_equal 1, @connection.transmissions.size + + @channel.perform_action("action" => :secret_action) + assert_equal 1, @connection.transmissions.size + end end diff --git a/actioncable/test/channel/stream_test.rb b/actioncable/test/channel/stream_test.rb index 947efd96d4..31dcde2e29 100644 --- a/actioncable/test/channel/stream_test.rb +++ b/actioncable/test/channel/stream_test.rb @@ -1,13 +1,21 @@ -require 'test_helper' -require 'stubs/test_connection' -require 'stubs/room' +require "test_helper" +require "stubs/test_connection" +require "stubs/room" + +module ActionCable::StreamTests + class Connection < ActionCable::Connection::Base + attr_reader :websocket + + def send_async(method, *args) + send method, *args + end + end -class ActionCable::Channel::StreamTest < ActionCable::TestCase class ChatChannel < ActionCable::Channel::Base def subscribed if params[:id] @room = Room.new params[:id] - stream_from "test_room_#{@room.id}" + stream_from "test_room_#{@room.id}", coder: pick_coder(params[:coder]) end end @@ -15,60 +23,180 @@ class ActionCable::Channel::StreamTest < ActionCable::TestCase transmit_subscription_confirmation end + private def pick_coder(coder) + case coder + when nil, "json" + ActiveSupport::JSON + when "custom" + DummyEncoder + when "none" + nil + end + end end - test "streaming start and stop" do - run_in_eventmachine do - connection = TestConnection.new - connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("test_room_1", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) } - channel = ChatChannel.new connection, "{id: 1}", { id: 1 } + module DummyEncoder + extend self + def encode(*) '{ "foo": "encoded" }' end + def decode(*) { foo: "decoded" } end + end - connection.expects(:pubsub).returns mock().tap { |m| m.expects(:unsubscribe) } - channel.unsubscribe_from_channel + class SymbolChannel < ActionCable::Channel::Base + def subscribed + stream_from :channel end end - test "stream_for" do - run_in_eventmachine do - connection = TestConnection.new - connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("action_cable:channel:stream_test:chat:Room#1-Campfire", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) } + class StreamTest < ActionCable::TestCase + test "streaming start and stop" do + run_in_eventmachine do + connection = TestConnection.new + connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("test_room_1", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) } + channel = ChatChannel.new connection, "{id: 1}", id: 1 + channel.subscribe_to_channel - channel = ChatChannel.new connection, "" - channel.stream_for Room.new(1) + connection.expects(:pubsub).returns mock().tap { |m| m.expects(:unsubscribe) } + channel.unsubscribe_from_channel + end end - end - test "stream_from subscription confirmation" do - run_in_eventmachine do - connection = TestConnection.new + test "stream from non-string channel" do + run_in_eventmachine do + connection = TestConnection.new + connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("channel", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) } + channel = SymbolChannel.new connection, "" + channel.subscribe_to_channel - ChatChannel.new connection, "{id: 1}", { id: 1 } - assert_nil connection.last_transmission + connection.expects(:pubsub).returns mock().tap { |m| m.expects(:unsubscribe) } + channel.unsubscribe_from_channel + end + end - wait_for_async + test "stream_for" do + run_in_eventmachine do + connection = TestConnection.new + connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("action_cable:stream_tests:chat:Room#1-Campfire", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) } - expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "type" => "confirm_subscription" - connection.transmit(expected) + channel = ChatChannel.new connection, "" + channel.subscribe_to_channel + channel.stream_for Room.new(1) + end + end - assert_equal expected, connection.last_transmission, "Did not receive subscription confirmation within 0.1s" + test "stream_from subscription confirmation" do + run_in_eventmachine do + connection = TestConnection.new + + channel = ChatChannel.new connection, "{id: 1}", id: 1 + channel.subscribe_to_channel + + assert_nil connection.last_transmission + + wait_for_async + + confirmation = { "identifier" => "{id: 1}", "type" => "confirm_subscription" } + connection.transmit(confirmation) + + assert_equal confirmation, connection.last_transmission, "Did not receive subscription confirmation within 0.1s" + end end - end - test "subscription confirmation should only be sent out once" do - run_in_eventmachine do - connection = TestConnection.new + test "subscription confirmation should only be sent out once" do + run_in_eventmachine do + connection = TestConnection.new - channel = ChatChannel.new connection, "test_channel" - channel.send_confirmation - channel.send_confirmation + channel = ChatChannel.new connection, "test_channel" + channel.send_confirmation + channel.send_confirmation + + wait_for_async + + expected = { "identifier" => "test_channel", "type" => "confirm_subscription" } + assert_equal expected, connection.last_transmission, "Did not receive subscription confirmation" + + assert_equal 1, connection.transmissions.size + end + end + end - wait_for_async + require "action_cable/subscription_adapter/async" - expected = ActiveSupport::JSON.encode "identifier" => "test_channel", "type" => "confirm_subscription" - assert_equal expected, connection.last_transmission, "Did not receive subscription confirmation" + class UserCallbackChannel < ActionCable::Channel::Base + def subscribed + stream_from :channel do + Thread.current[:ran_callback] = true + end + end + end - assert_equal 1, connection.transmissions.size + class MultiChatChannel < ActionCable::Channel::Base + def subscribed + stream_from "main_room" + stream_from "test_all_rooms" end end + class StreamFromTest < ActionCable::TestCase + setup do + @server = TestServer.new(subscription_adapter: ActionCable::SubscriptionAdapter::Async) + @server.config.allowed_request_origins = %w( http://rubyonrails.com ) + end + + test "custom encoder" do + run_in_eventmachine do + connection = open_connection + subscribe_to connection, identifiers: { id: 1 } + + connection.websocket.expects(:transmit) + @server.broadcast "test_room_1", { foo: "bar" }, coder: DummyEncoder + wait_for_async + wait_for_executor connection.server.worker_pool.executor + end + end + + test "user supplied callbacks are run through the worker pool" do + run_in_eventmachine do + connection = open_connection + receive(connection, command: "subscribe", channel: UserCallbackChannel.name, identifiers: { id: 1 }) + + @server.broadcast "channel", {} + wait_for_async + refute Thread.current[:ran_callback], "User callback was not run through the worker pool" + end + end + + test "subscription confirmation should only be sent out once with muptiple stream_from" do + run_in_eventmachine do + connection = open_connection + expected = { "identifier" => { "channel" => MultiChatChannel.name }.to_json, "type" => "confirm_subscription" } + connection.websocket.expects(:transmit).with(expected.to_json) + receive(connection, command: "subscribe", channel: MultiChatChannel.name, identifiers: {}) + + wait_for_async + end + end + + private + def subscribe_to(connection, identifiers:) + receive connection, command: "subscribe", identifiers: identifiers + end + + def open_connection + env = Rack::MockRequest.env_for "/test", "HTTP_HOST" => "localhost", "HTTP_CONNECTION" => "upgrade", "HTTP_UPGRADE" => "websocket", "HTTP_ORIGIN" => "http://rubyonrails.com" + + Connection.new(@server, env).tap do |connection| + connection.process + assert connection.websocket.possible? + + wait_for_async + assert connection.websocket.alive? + end + end + + def receive(connection, command:, identifiers:, channel: "ActionCable::StreamTests::ChatChannel") + identifier = JSON.generate(channel: channel, **identifiers) + connection.dispatch_websocket_message JSON.generate(command: command, identifier: identifier) + wait_for_async + end + end end diff --git a/actioncable/test/client/echo_channel.rb b/actioncable/test/client/echo_channel.rb deleted file mode 100644 index 63e35f194a..0000000000 --- a/actioncable/test/client/echo_channel.rb +++ /dev/null @@ -1,18 +0,0 @@ -class EchoChannel < ActionCable::Channel::Base - def subscribed - stream_from "global" - end - - def ding(data) - transmit(dong: data['message']) - end - - def delay(data) - sleep 1 - transmit(dong: data['message']) - end - - def bulk(data) - ActionCable.server.broadcast "global", wide: data['message'] - end -end diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb index 4ade9832e0..db10a7ad16 100644 --- a/actioncable/test/client_test.rb +++ b/actioncable/test/client_test.rb @@ -1,59 +1,70 @@ -require 'test_helper' -require 'concurrent' +require "test_helper" +require "concurrent" -require 'active_support/core_ext/hash/indifferent_access' -require 'pathname' +require "websocket-client-simple" +require "json" -require 'faye/websocket' -require 'json' +require "active_support/hash_with_indifferent_access" -class ClientTest < ActionCable::TestCase - WAIT_WHEN_EXPECTING_EVENT = 3 - WAIT_WHEN_NOT_EXPECTING_EVENT = 0.2 +#### +# 😷 Warning suppression 😷 +WebSocket::Frame::Handler::Handler03.prepend Module.new { + def initialize(*) + @application_data_buffer = nil + super + end +} - def setup - # TODO: ActionCable requires a *lot* of setup at the moment... - ::Object.const_set(:ApplicationCable, Module.new) - ::ApplicationCable.const_set(:Connection, Class.new(ActionCable::Connection::Base)) +WebSocket::Frame::Data.prepend Module.new { + def initialize(*) + @masking_key = nil + super + end +} +# +#### - ::Object.const_set(:Rails, Module.new) - ::Rails.singleton_class.send(:define_method, :root) { Pathname.new(__dir__) } +class ClientTest < ActionCable::TestCase + WAIT_WHEN_EXPECTING_EVENT = 2 + WAIT_WHEN_NOT_EXPECTING_EVENT = 0.5 - ActionCable.instance_variable_set(:@server, nil) - server = ActionCable.server - server.config = ActionCable::Server::Configuration.new - inner_logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN } - server.config.logger = ActionCable::Connection::TaggedLoggerProxy.new(inner_logger, tags: []) + class EchoChannel < ActionCable::Channel::Base + def subscribed + stream_from "global" + end - server.config.cable = { adapter: 'async' }.with_indifferent_access + def unsubscribed + "Goodbye from EchoChannel!" + end - # and now the "real" setup for our test: - server.config.disable_request_forgery_protection = true - server.config.channel_load_paths = [File.expand_path('client', __dir__)] + def ding(data) + transmit(dong: data["message"]) + end - Thread.new { EventMachine.run } unless EventMachine.reactor_running? - Thread.pass until EventMachine.reactor_running? + def delay(data) + sleep 1 + transmit(dong: data["message"]) + end - # faye-websocket is warning-rich - @previous_verbose, $VERBOSE = $VERBOSE, nil + def bulk(data) + ActionCable.server.broadcast "global", wide: data["message"] + end end - def teardown - $VERBOSE = @previous_verbose + def setup + ActionCable.instance_variable_set(:@server, nil) + server = ActionCable.server + server.config.logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN } - begin - ::Object.send(:remove_const, :ApplicationCable) - rescue NameError - end - begin - ::Object.send(:remove_const, :Rails) - rescue NameError - end + server.config.cable = ActiveSupport::HashWithIndifferentAccess.new(adapter: "async") + + # and now the "real" setup for our test: + server.config.disable_request_forgery_protection = true end def with_puma_server(rack_app = ActionCable.server, port = 3099) server = ::Puma::Server.new(rack_app, ::Puma::Events.strings) - server.add_tcp_listener '127.0.0.1', port + server.add_tcp_listener "127.0.0.1", port server.min_threads = 1 server.max_threads = 4 @@ -69,49 +80,53 @@ class ClientTest < ActionCable::TestCase attr_reader :pings def initialize(port) - @ws = Faye::WebSocket::Client.new("ws://127.0.0.1:#{port}/") - @messages = Queue.new - @closed = Concurrent::Event.new - @has_messages = Concurrent::Event.new - @pings = 0 - - open = Concurrent::Event.new - error = nil - - @ws.on(:error) do |event| - if open.set? - @messages << RuntimeError.new(event.message) - else - error = event.message - open.set + messages = @messages = Queue.new + closed = @closed = Concurrent::Event.new + has_messages = @has_messages = Concurrent::Semaphore.new(0) + pings = @pings = Concurrent::AtomicFixnum.new(0) + + open = Concurrent::Promise.new + + @ws = WebSocket::Client::Simple.connect("ws://127.0.0.1:#{port}/") do |ws| + ws.on(:error) do |event| + event = RuntimeError.new(event.message) unless event.is_a?(Exception) + + if open.pending? + open.fail(event) + else + messages << event + has_messages.release + end end - end - @ws.on(:open) do |event| - open.set - end + ws.on(:open) do |event| + open.set(true) + end - @ws.on(:message) do |event| - hash = JSON.parse(event.data) - if hash['identifier'] == '_ping' - @pings += 1 - else - @messages << hash - @has_messages.set + ws.on(:message) do |event| + if event.type == :close + closed.set + else + message = JSON.parse(event.data) + if message["type"] == "ping" + pings.increment + else + messages << message + has_messages.release + end + end end - end - @ws.on(:close) do |event| - @closed.set + ws.on(:close) do |event| + closed.set + end end - open.wait(WAIT_WHEN_EXPECTING_EVENT) - raise error if error + open.wait!(WAIT_WHEN_EXPECTING_EVENT) end def read_message - @has_messages.wait(WAIT_WHEN_EXPECTING_EVENT) if @messages.empty? - @has_messages.reset if @messages.size < 2 + @has_messages.try_acquire(1, WAIT_WHEN_EXPECTING_EVENT) msg = @messages.pop(true) raise msg if msg.is_a?(Exception) @@ -122,9 +137,11 @@ class ClientTest < ActionCable::TestCase def read_messages(expected_size = 0) list = [] loop do - @has_messages.wait(list.size < expected_size ? WAIT_WHEN_EXPECTING_EVENT : WAIT_WHEN_NOT_EXPECTING_EVENT) - if @has_messages.set? - list << read_message + if @has_messages.try_acquire(1, list.size < expected_size ? WAIT_WHEN_EXPECTING_EVENT : WAIT_WHEN_NOT_EXPECTING_EVENT) + msg = @messages.pop(true) + raise msg if msg.is_a?(Exception) + + list << msg else break end @@ -132,8 +149,8 @@ class ClientTest < ActionCable::TestCase list end - def send_message(hash) - @ws.send(JSON.dump(hash)) + def send_message(message) + @ws.send(JSON.generate(message)) end def close @@ -144,76 +161,130 @@ class ClientTest < ActionCable::TestCase end @ws.close + wait_for_close + end + + def wait_for_close @closed.wait(WAIT_WHEN_EXPECTING_EVENT) end + + def closed? + @closed.set? + end end - def faye_client(port) + def websocket_client(port) SyncClient.new(port) end + def concurrently(enum) + enum.map { |*x| Concurrent::Future.execute { yield(*x) } }.map(&:value!) + end + def test_single_client with_puma_server do |port| - c = faye_client(port) - c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel') - assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message) - c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'ding', message: 'hello') - assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "message"=>{"dong"=>"hello"}}, c.read_message) + c = websocket_client(port) + assert_equal({ "type" => "welcome" }, c.read_message) # pop the first welcome message off the stack + c.send_message command: "subscribe", identifier: JSON.generate(channel: "ClientTest::EchoChannel") + assert_equal({ "identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription" }, c.read_message) + c.send_message command: "message", identifier: JSON.generate(channel: "ClientTest::EchoChannel"), data: JSON.generate(action: "ding", message: "hello") + assert_equal({ "identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "message"=>{ "dong"=>"hello" } }, c.read_message) c.close end end def test_interacting_clients with_puma_server do |port| - clients = 10.times.map { faye_client(port) } + clients = concurrently(10.times) { websocket_client(port) } barrier_1 = Concurrent::CyclicBarrier.new(clients.size) barrier_2 = Concurrent::CyclicBarrier.new(clients.size) - clients.map {|c| Concurrent::Future.execute { - c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel') - assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "type"=>"confirm_subscription"}, c.read_message) - c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'ding', message: 'hello') - assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message) + concurrently(clients) do |c| + assert_equal({ "type" => "welcome" }, c.read_message) # pop the first welcome message off the stack + c.send_message command: "subscribe", identifier: JSON.generate(channel: "ClientTest::EchoChannel") + assert_equal({ "identifier"=>'{"channel":"ClientTest::EchoChannel"}', "type"=>"confirm_subscription" }, c.read_message) + c.send_message command: "message", identifier: JSON.generate(channel: "ClientTest::EchoChannel"), data: JSON.generate(action: "ding", message: "hello") + assert_equal({ "identifier"=>'{"channel":"ClientTest::EchoChannel"}', "message"=>{ "dong"=>"hello" } }, c.read_message) barrier_1.wait WAIT_WHEN_EXPECTING_EVENT - c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'bulk', message: 'hello') + c.send_message command: "message", identifier: JSON.generate(channel: "ClientTest::EchoChannel"), data: JSON.generate(action: "bulk", message: "hello") barrier_2.wait WAIT_WHEN_EXPECTING_EVENT assert_equal clients.size, c.read_messages(clients.size).size - } }.each(&:wait!) + end - clients.map {|c| Concurrent::Future.execute { c.close } }.each(&:wait!) + concurrently(clients, &:close) end end def test_many_clients with_puma_server do |port| - clients = 100.times.map { faye_client(port) } - - clients.map {|c| Concurrent::Future.execute { - c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel') - assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "type"=>"confirm_subscription"}, c.read_message) - c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'ding', message: 'hello') - assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message) - } }.each(&:wait!) + clients = concurrently(100.times) { websocket_client(port) } + + concurrently(clients) do |c| + assert_equal({ "type" => "welcome" }, c.read_message) # pop the first welcome message off the stack + c.send_message command: "subscribe", identifier: JSON.generate(channel: "ClientTest::EchoChannel") + assert_equal({ "identifier"=>'{"channel":"ClientTest::EchoChannel"}', "type"=>"confirm_subscription" }, c.read_message) + c.send_message command: "message", identifier: JSON.generate(channel: "ClientTest::EchoChannel"), data: JSON.generate(action: "ding", message: "hello") + assert_equal({ "identifier"=>'{"channel":"ClientTest::EchoChannel"}', "message"=>{ "dong"=>"hello" } }, c.read_message) + end - clients.map {|c| Concurrent::Future.execute { c.close } }.each(&:wait!) + concurrently(clients, &:close) end end def test_disappearing_client with_puma_server do |port| - c = faye_client(port) - c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel') - assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message) - c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'delay', message: 'hello') + c = websocket_client(port) + assert_equal({ "type" => "welcome" }, c.read_message) # pop the first welcome message off the stack + c.send_message command: "subscribe", identifier: JSON.generate(channel: "ClientTest::EchoChannel") + assert_equal({ "identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription" }, c.read_message) + c.send_message command: "message", identifier: JSON.generate(channel: "ClientTest::EchoChannel"), data: JSON.generate(action: "delay", message: "hello") c.close # disappear before write - c = faye_client(port) - c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel') - assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message) - c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'ding', message: 'hello') - assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message) + c = websocket_client(port) + assert_equal({ "type" => "welcome" }, c.read_message) # pop the first welcome message off the stack + c.send_message command: "subscribe", identifier: JSON.generate(channel: "ClientTest::EchoChannel") + assert_equal({ "identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription" }, c.read_message) + c.send_message command: "message", identifier: JSON.generate(channel: "ClientTest::EchoChannel"), data: JSON.generate(action: "ding", message: "hello") + assert_equal({ "identifier"=>'{"channel":"ClientTest::EchoChannel"}', "message"=>{ "dong"=>"hello" } }, c.read_message) c.close # disappear before read end end + + def test_unsubscribe_client + with_puma_server do |port| + app = ActionCable.server + identifier = JSON.generate(channel: "ClientTest::EchoChannel") + + c = websocket_client(port) + assert_equal({ "type" => "welcome" }, c.read_message) + c.send_message command: "subscribe", identifier: identifier + assert_equal({ "identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription" }, c.read_message) + assert_equal(1, app.connections.count) + assert(app.remote_connections.where(identifier: identifier)) + + subscriptions = app.connections.first.subscriptions.send(:subscriptions) + assert_not_equal 0, subscriptions.size, "Missing EchoChannel subscription" + channel = subscriptions.first[1] + channel.expects(:unsubscribed) + c.close + sleep 0.1 # Data takes a moment to process + + # All data is removed: No more connection or subscription information! + assert_equal(0, app.connections.count) + end + end + + def test_server_restart + with_puma_server do |port| + c = websocket_client(port) + assert_equal({ "type" => "welcome" }, c.read_message) + c.send_message command: "subscribe", identifier: JSON.generate(channel: "ClientTest::EchoChannel") + assert_equal({ "identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription" }, c.read_message) + + ActionCable.server.restart + c.wait_for_close + assert c.closed? + end + end end diff --git a/actioncable/test/connection/authorization_test.rb b/actioncable/test/connection/authorization_test.rb index 87d0e79ef3..dcdbe9c1d1 100644 --- a/actioncable/test/connection/authorization_test.rb +++ b/actioncable/test/connection/authorization_test.rb @@ -1,5 +1,5 @@ -require 'test_helper' -require 'stubs/test_server' +require "test_helper" +require "stubs/test_server" class ActionCable::Connection::AuthorizationTest < ActionCable::TestCase class Connection < ActionCable::Connection::Base @@ -19,8 +19,8 @@ class ActionCable::Connection::AuthorizationTest < ActionCable::TestCase server = TestServer.new server.config.allowed_request_origins = %w( http://rubyonrails.com ) - env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket', - 'HTTP_ORIGIN' => 'http://rubyonrails.com' + env = Rack::MockRequest.env_for "/test", "HTTP_CONNECTION" => "upgrade", "HTTP_UPGRADE" => "websocket", + "HTTP_HOST" => "localhost", "HTTP_ORIGIN" => "http://rubyonrails.com" connection = Connection.new(server, env) connection.websocket.expects(:close) diff --git a/actioncable/test/connection/base_test.rb b/actioncable/test/connection/base_test.rb index e2b017a9a1..9bcd0700cf 100644 --- a/actioncable/test/connection/base_test.rb +++ b/actioncable/test/connection/base_test.rb @@ -1,5 +1,6 @@ -require 'test_helper' -require 'stubs/test_server' +require "test_helper" +require "stubs/test_server" +require "active_support/core_ext/object/json" class ActionCable::Connection::BaseTest < ActionCable::TestCase class Connection < ActionCable::Connection::Base @@ -56,7 +57,7 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase run_in_eventmachine do connection = open_connection - connection.websocket.expects(:transmit).with(regexp_matches(/\_ping/)) + connection.websocket.expects(:transmit).with({ type: "welcome" }.to_json) connection.message_buffer.expects(:process!) connection.process @@ -73,7 +74,7 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase connection.process # Setup the connection - Concurrent::TimerTask.stubs(:new).returns(true) + connection.server.stubs(:timer).returns(true) connection.send :handle_open assert connection.connected @@ -108,10 +109,30 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase end end + test "rejecting a connection causes a 404" do + run_in_eventmachine do + class CallMeMaybe + def call(*) + raise "Do not call me!" + end + end + + env = Rack::MockRequest.env_for( + "/test", + "HTTP_CONNECTION" => "upgrade", "HTTP_UPGRADE" => "websocket", + "HTTP_HOST" => "localhost", "HTTP_ORIGIN" => "http://rubyonrails.org", "rack.hijack" => CallMeMaybe.new + ) + + connection = ActionCable::Connection::Base.new(@server, env) + response = connection.process + assert_equal 404, response[0] + end + end + private def open_connection - env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket', - 'HTTP_ORIGIN' => 'http://rubyonrails.com' + env = Rack::MockRequest.env_for "/test", "HTTP_CONNECTION" => "upgrade", "HTTP_UPGRADE" => "websocket", + "HTTP_HOST" => "localhost", "HTTP_ORIGIN" => "http://rubyonrails.com" Connection.new(@server, env) end diff --git a/actioncable/test/connection/client_socket_test.rb b/actioncable/test/connection/client_socket_test.rb new file mode 100644 index 0000000000..bc3ff6a3d7 --- /dev/null +++ b/actioncable/test/connection/client_socket_test.rb @@ -0,0 +1,82 @@ +require "test_helper" +require "stubs/test_server" + +class ActionCable::Connection::ClientSocketTest < ActionCable::TestCase + class Connection < ActionCable::Connection::Base + attr_reader :connected, :websocket, :errors + + def initialize(*) + super + @errors = [] + end + + def connect + @connected = true + end + + def disconnect + @connected = false + end + + def send_async(method, *args) + send method, *args + end + + def on_error(message) + @errors << message + end + end + + setup do + @server = TestServer.new + @server.config.allowed_request_origins = %w( http://rubyonrails.com ) + end + + test "delegate socket errors to on_error handler" do + run_in_eventmachine do + connection = open_connection + + # Internal hax = :( + client = connection.websocket.send(:websocket) + client.instance_variable_get("@stream").expects(:write).raises("foo") + client.expects(:client_gone).never + + client.write("boo") + assert_equal %w[ foo ], connection.errors + end + end + + test "closes hijacked i/o socket at shutdown" do + run_in_eventmachine do + connection = open_connection + + client = connection.websocket.send(:websocket) + event = Concurrent::Event.new + client.instance_variable_get("@stream") + .instance_variable_get("@rack_hijack_io") + .define_singleton_method(:close) { event.set } + connection.close + event.wait + end + end + + private + def open_connection + env = Rack::MockRequest.env_for "/test", + "HTTP_CONNECTION" => "upgrade", "HTTP_UPGRADE" => "websocket", + "HTTP_HOST" => "localhost", "HTTP_ORIGIN" => "http://rubyonrails.com" + io = \ + begin + Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM, 0).first + rescue + StringIO.new + end + env["rack.hijack"] = -> { env["rack.hijack_io"] = io } + + Connection.new(@server, env).tap do |connection| + connection.process + connection.send :handle_open + assert connection.connected + end + end +end diff --git a/actioncable/test/connection/cross_site_forgery_test.rb b/actioncable/test/connection/cross_site_forgery_test.rb index a29f65fb97..37bedfd734 100644 --- a/actioncable/test/connection/cross_site_forgery_test.rb +++ b/actioncable/test/connection/cross_site_forgery_test.rb @@ -1,8 +1,8 @@ -require 'test_helper' -require 'stubs/test_server' +require "test_helper" +require "stubs/test_server" class ActionCable::Connection::CrossSiteForgeryTest < ActionCable::TestCase - HOST = 'rubyonrails.com' + HOST = "rubyonrails.com" class Connection < ActionCable::Connection::Base def send_async(method, *args) @@ -13,44 +13,53 @@ class ActionCable::Connection::CrossSiteForgeryTest < ActionCable::TestCase setup do @server = TestServer.new @server.config.allowed_request_origins = %w( http://rubyonrails.com ) + @server.config.allow_same_origin_as_host = false end teardown do @server.config.disable_request_forgery_protection = false @server.config.allowed_request_origins = [] + @server.config.allow_same_origin_as_host = true end test "disable forgery protection" do @server.config.disable_request_forgery_protection = true - assert_origin_allowed 'http://rubyonrails.com' - assert_origin_allowed 'http://hax.com' + assert_origin_allowed "http://rubyonrails.com" + assert_origin_allowed "http://hax.com" end test "explicitly specified a single allowed origin" do - @server.config.allowed_request_origins = 'http://hax.com' - assert_origin_not_allowed 'http://rubyonrails.com' - assert_origin_allowed 'http://hax.com' + @server.config.allowed_request_origins = "http://hax.com" + assert_origin_not_allowed "http://rubyonrails.com" + assert_origin_allowed "http://hax.com" end test "explicitly specified multiple allowed origins" do @server.config.allowed_request_origins = %w( http://rubyonrails.com http://www.rubyonrails.com ) - assert_origin_allowed 'http://rubyonrails.com' - assert_origin_allowed 'http://www.rubyonrails.com' - assert_origin_not_allowed 'http://hax.com' + assert_origin_allowed "http://rubyonrails.com" + assert_origin_allowed "http://www.rubyonrails.com" + assert_origin_not_allowed "http://hax.com" end test "explicitly specified a single regexp allowed origin" do @server.config.allowed_request_origins = /.*ha.*/ - assert_origin_not_allowed 'http://rubyonrails.com' - assert_origin_allowed 'http://hax.com' + assert_origin_not_allowed "http://rubyonrails.com" + assert_origin_allowed "http://hax.com" end test "explicitly specified multiple regexp allowed origins" do - @server.config.allowed_request_origins = [/http:\/\/ruby.*/, /.*rai.s.*com/, 'string' ] - assert_origin_allowed 'http://rubyonrails.com' - assert_origin_allowed 'http://www.rubyonrails.com' - assert_origin_not_allowed 'http://hax.com' - assert_origin_not_allowed 'http://rails.co.uk' + @server.config.allowed_request_origins = [/http:\/\/ruby.*/, /.*rai.s.*com/, "string" ] + assert_origin_allowed "http://rubyonrails.com" + assert_origin_allowed "http://www.rubyonrails.com" + assert_origin_not_allowed "http://hax.com" + assert_origin_not_allowed "http://rails.co.uk" + end + + test "allow same origin as host" do + @server.config.allow_same_origin_as_host = true + assert_origin_allowed "http://#{HOST}" + assert_origin_not_allowed "http://hax.com" + assert_origin_not_allowed "http://rails.co.uk" end private @@ -75,7 +84,7 @@ class ActionCable::Connection::CrossSiteForgeryTest < ActionCable::TestCase end def env_for_origin(origin) - Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket', 'SERVER_NAME' => HOST, - 'HTTP_ORIGIN' => origin + Rack::MockRequest.env_for "/test", "HTTP_CONNECTION" => "upgrade", "HTTP_UPGRADE" => "websocket", "SERVER_NAME" => HOST, + "HTTP_HOST" => HOST, "HTTP_ORIGIN" => origin end end diff --git a/actioncable/test/connection/identifier_test.rb b/actioncable/test/connection/identifier_test.rb index 1019ad541e..a4dfcc06f0 100644 --- a/actioncable/test/connection/identifier_test.rb +++ b/actioncable/test/connection/identifier_test.rb @@ -1,6 +1,6 @@ -require 'test_helper' -require 'stubs/test_server' -require 'stubs/user' +require "test_helper" +require "stubs/test_server" +require "stubs/user" class ActionCable::Connection::IdentifierTest < ActionCable::TestCase class Connection < ActionCable::Connection::Base @@ -23,9 +23,9 @@ class ActionCable::Connection::IdentifierTest < ActionCable::TestCase test "should subscribe to internal channel on open and unsubscribe on close" do run_in_eventmachine do - pubsub = mock('pubsub_adapter') - pubsub.expects(:subscribe).with('action_cable/User#lifo', kind_of(Proc)) - pubsub.expects(:unsubscribe).with('action_cable/User#lifo', kind_of(Proc)) + pubsub = mock("pubsub_adapter") + pubsub.expects(:subscribe).with("action_cable/User#lifo", kind_of(Proc)) + pubsub.expects(:unsubscribe).with("action_cable/User#lifo", kind_of(Proc)) server = TestServer.new server.stubs(:pubsub).returns(pubsub) @@ -40,8 +40,7 @@ class ActionCable::Connection::IdentifierTest < ActionCable::TestCase open_connection_with_stubbed_pubsub @connection.websocket.expects(:close) - message = ActiveSupport::JSON.encode('type' => 'disconnect') - @connection.process_internal_message message + @connection.process_internal_message "type" => "disconnect" end end @@ -50,21 +49,20 @@ class ActionCable::Connection::IdentifierTest < ActionCable::TestCase open_connection_with_stubbed_pubsub @connection.websocket.expects(:close).never - message = ActiveSupport::JSON.encode('type' => 'unknown') - @connection.process_internal_message message + @connection.process_internal_message "type" => "unknown" end end protected def open_connection_with_stubbed_pubsub server = TestServer.new - server.stubs(:adapter).returns(stub_everything('adapter')) + server.stubs(:adapter).returns(stub_everything("adapter")) open_connection server: server end def open_connection(server:) - env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket' + env = Rack::MockRequest.env_for "/test", "HTTP_HOST" => "localhost", "HTTP_CONNECTION" => "upgrade", "HTTP_UPGRADE" => "websocket" @connection = Connection.new(server, env) @connection.process diff --git a/actioncable/test/connection/multiple_identifiers_test.rb b/actioncable/test/connection/multiple_identifiers_test.rb index e9bb4e6d7f..67e68355c5 100644 --- a/actioncable/test/connection/multiple_identifiers_test.rb +++ b/actioncable/test/connection/multiple_identifiers_test.rb @@ -1,6 +1,6 @@ -require 'test_helper' -require 'stubs/test_server' -require 'stubs/user' +require "test_helper" +require "stubs/test_server" +require "stubs/user" class ActionCable::Connection::MultipleIdentifiersTest < ActionCable::TestCase class Connection < ActionCable::Connection::Base @@ -22,13 +22,13 @@ class ActionCable::Connection::MultipleIdentifiersTest < ActionCable::TestCase protected def open_connection_with_stubbed_pubsub server = TestServer.new - server.stubs(:pubsub).returns(stub_everything('pubsub')) + server.stubs(:pubsub).returns(stub_everything("pubsub")) open_connection server: server end def open_connection(server:) - env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket' + env = Rack::MockRequest.env_for "/test", "HTTP_HOST" => "localhost", "HTTP_CONNECTION" => "upgrade", "HTTP_UPGRADE" => "websocket" @connection = Connection.new(server, env) @connection.process diff --git a/actioncable/test/connection/stream_test.rb b/actioncable/test/connection/stream_test.rb new file mode 100644 index 0000000000..36e1d3c095 --- /dev/null +++ b/actioncable/test/connection/stream_test.rb @@ -0,0 +1,64 @@ +require "test_helper" +require "stubs/test_server" + +class ActionCable::Connection::StreamTest < ActionCable::TestCase + class Connection < ActionCable::Connection::Base + attr_reader :connected, :websocket, :errors + + def initialize(*) + super + @errors = [] + end + + def connect + @connected = true + end + + def disconnect + @connected = false + end + + def send_async(method, *args) + send method, *args + end + + def on_error(message) + @errors << message + end + end + + setup do + @server = TestServer.new + @server.config.allowed_request_origins = %w( http://rubyonrails.com ) + end + + [ EOFError, Errno::ECONNRESET ].each do |closed_exception| + test "closes socket on #{closed_exception}" do + run_in_eventmachine do + connection = open_connection + + # Internal hax = :( + client = connection.websocket.send(:websocket) + client.instance_variable_get("@stream").instance_variable_get("@rack_hijack_io").expects(:write).raises(closed_exception, "foo") + client.expects(:client_gone) + + client.write("boo") + assert_equal [], connection.errors + end + end + end + + private + def open_connection + env = Rack::MockRequest.env_for "/test", + "HTTP_CONNECTION" => "upgrade", "HTTP_UPGRADE" => "websocket", + "HTTP_HOST" => "localhost", "HTTP_ORIGIN" => "http://rubyonrails.com" + env["rack.hijack"] = -> { env["rack.hijack_io"] = StringIO.new } + + Connection.new(@server, env).tap do |connection| + connection.process + connection.send :handle_open + assert connection.connected + end + end +end diff --git a/actioncable/test/connection/string_identifier_test.rb b/actioncable/test/connection/string_identifier_test.rb index 9d0bda83ef..87484765e5 100644 --- a/actioncable/test/connection/string_identifier_test.rb +++ b/actioncable/test/connection/string_identifier_test.rb @@ -1,5 +1,5 @@ -require 'test_helper' -require 'stubs/test_server' +require "test_helper" +require "stubs/test_server" class ActionCable::Connection::StringIdentifierTest < ActionCable::TestCase class Connection < ActionCable::Connection::Base @@ -24,13 +24,13 @@ class ActionCable::Connection::StringIdentifierTest < ActionCable::TestCase protected def open_connection_with_stubbed_pubsub @server = TestServer.new - @server.stubs(:pubsub).returns(stub_everything('pubsub')) + @server.stubs(:pubsub).returns(stub_everything("pubsub")) open_connection end def open_connection - env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket' + env = Rack::MockRequest.env_for "/test", "HTTP_HOST" => "localhost", "HTTP_CONNECTION" => "upgrade", "HTTP_UPGRADE" => "websocket" @connection = Connection.new(@server, env) @connection.process diff --git a/actioncable/test/connection/subscriptions_test.rb b/actioncable/test/connection/subscriptions_test.rb index 62e41484fe..a1c8a4613c 100644 --- a/actioncable/test/connection/subscriptions_test.rb +++ b/actioncable/test/connection/subscriptions_test.rb @@ -1,4 +1,4 @@ -require 'test_helper' +require "test_helper" class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase class Connection < ActionCable::Connection::Base @@ -24,9 +24,8 @@ class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase setup do @server = TestServer.new - @server.stubs(:channel_classes).returns(ChatChannel.name => ChatChannel) - @chat_identifier = ActiveSupport::JSON.encode(id: 1, channel: 'ActionCable::Connection::SubscriptionsTest::ChatChannel') + @chat_identifier = ActiveSupport::JSON.encode(id: 1, channel: "ActionCable::Connection::SubscriptionsTest::ChatChannel") end test "subscribe command" do @@ -43,7 +42,7 @@ class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase run_in_eventmachine do setup_connection - @subscriptions.execute_command 'command' => 'subscribe' + @subscriptions.execute_command "command" => "subscribe" assert @subscriptions.identifiers.empty? end end @@ -56,7 +55,7 @@ class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase channel = subscribe_to_chat_channel channel.expects(:unsubscribe_from_channel) - @subscriptions.execute_command 'command' => 'unsubscribe', 'identifier' => @chat_identifier + @subscriptions.execute_command "command" => "unsubscribe", "identifier" => @chat_identifier assert @subscriptions.identifiers.empty? end end @@ -65,7 +64,7 @@ class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase run_in_eventmachine do setup_connection - @subscriptions.execute_command 'command' => 'unsubscribe' + @subscriptions.execute_command "command" => "unsubscribe" assert @subscriptions.identifiers.empty? end end @@ -75,20 +74,20 @@ class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase setup_connection channel = subscribe_to_chat_channel - data = { 'content' => 'Hello World!', 'action' => 'speak' } - @subscriptions.execute_command 'command' => 'message', 'identifier' => @chat_identifier, 'data' => ActiveSupport::JSON.encode(data) + data = { "content" => "Hello World!", "action" => "speak" } + @subscriptions.execute_command "command" => "message", "identifier" => @chat_identifier, "data" => ActiveSupport::JSON.encode(data) assert_equal [ data ], channel.lines end end - test "unsubscrib from all" do + test "unsubscribe from all" do run_in_eventmachine do setup_connection channel1 = subscribe_to_chat_channel - channel2_id = ActiveSupport::JSON.encode(id: 2, channel: 'ActionCable::Connection::SubscriptionsTest::ChatChannel') + channel2_id = ActiveSupport::JSON.encode(id: 2, channel: "ActionCable::Connection::SubscriptionsTest::ChatChannel") channel2 = subscribe_to_chat_channel(channel2_id) channel1.expects(:unsubscribe_from_channel) @@ -100,14 +99,14 @@ class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase private def subscribe_to_chat_channel(identifier = @chat_identifier) - @subscriptions.execute_command 'command' => 'subscribe', 'identifier' => identifier + @subscriptions.execute_command "command" => "subscribe", "identifier" => identifier assert_equal identifier, @subscriptions.identifiers.last - @subscriptions.send :find, 'identifier' => identifier + @subscriptions.send :find, "identifier" => identifier end def setup_connection - env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket' + env = Rack::MockRequest.env_for "/test", "HTTP_HOST" => "localhost", "HTTP_CONNECTION" => "upgrade", "HTTP_UPGRADE" => "websocket" @connection = Connection.new(@server, env) @subscriptions = ActionCable::Connection::Subscriptions.new(@connection) diff --git a/actioncable/test/javascript/src/test.coffee b/actioncable/test/javascript/src/test.coffee new file mode 100644 index 0000000000..eb95fb2604 --- /dev/null +++ b/actioncable/test/javascript/src/test.coffee @@ -0,0 +1,3 @@ +#= require action_cable +#= require ./test_helpers +#= require_tree ./unit diff --git a/actioncable/test/javascript/src/test_helpers/consumer_test_helper.coffee b/actioncable/test/javascript/src/test_helpers/consumer_test_helper.coffee new file mode 100644 index 0000000000..6b145dede8 --- /dev/null +++ b/actioncable/test/javascript/src/test_helpers/consumer_test_helper.coffee @@ -0,0 +1,37 @@ +#= require mock-socket + +{TestHelpers} = ActionCable + +TestHelpers.consumerTest = (name, options = {}, callback) -> + unless callback? + callback = options + options = {} + + options.url ?= TestHelpers.testURL + + QUnit.test name, (assert) -> + doneAsync = assert.async() + + ActionCable.WebSocket = MockWebSocket + server = new MockServer options.url + consumer = ActionCable.createConsumer(options.url) + + server.on "connection", -> + clients = server.clients() + assert.equal clients.length, 1 + assert.equal clients[0].readyState, WebSocket.OPEN + + done = -> + consumer.disconnect() + server.close() + doneAsync() + + testData = {assert, consumer, server, done} + + if options.connect is false + callback(testData) + else + server.on "connection", -> + testData.client = server.clients()[0] + callback(testData) + consumer.connect() diff --git a/actioncable/test/javascript/src/test_helpers/index.coffee b/actioncable/test/javascript/src/test_helpers/index.coffee new file mode 100644 index 0000000000..d36524d9cc --- /dev/null +++ b/actioncable/test/javascript/src/test_helpers/index.coffee @@ -0,0 +1,8 @@ +#= require_self +#= require_tree . + +ActionCable.TestHelpers = + testURL: "ws://cable.example.com/" + +originalWebSocket = ActionCable.WebSocket +QUnit.testDone -> ActionCable.WebSocket = originalWebSocket diff --git a/actioncable/test/javascript/src/unit/action_cable_test.coffee b/actioncable/test/javascript/src/unit/action_cable_test.coffee new file mode 100644 index 0000000000..3944f3a7f6 --- /dev/null +++ b/actioncable/test/javascript/src/unit/action_cable_test.coffee @@ -0,0 +1,41 @@ +{module, test} = QUnit +{testURL} = ActionCable.TestHelpers + +module "ActionCable", -> + module "Adapters", -> + module "WebSocket", -> + test "default is window.WebSocket", (assert) -> + assert.equal ActionCable.WebSocket, window.WebSocket + + test "configurable", (assert) -> + ActionCable.WebSocket = "" + assert.equal ActionCable.WebSocket, "" + + module "logger", -> + test "default is window.console", (assert) -> + assert.equal ActionCable.logger, window.console + + test "configurable", (assert) -> + ActionCable.logger = "" + assert.equal ActionCable.logger, "" + + module "#createConsumer", -> + test "uses specified URL", (assert) -> + consumer = ActionCable.createConsumer(testURL) + assert.equal consumer.url, testURL + + test "uses default URL", (assert) -> + pattern = ///#{ActionCable.INTERNAL.default_mount_path}$/// + consumer = ActionCable.createConsumer() + assert.ok pattern.test(consumer.url), "Expected #{consumer.url} to match #{pattern}" + + test "uses URL from meta tag", (assert) -> + element = document.createElement("meta") + element.setAttribute("name", "action-cable-url") + element.setAttribute("content", testURL) + + document.head.appendChild(element) + consumer = ActionCable.createConsumer() + document.head.removeChild(element) + + assert.equal consumer.url, testURL diff --git a/actioncable/test/javascript/src/unit/consumer_test.coffee b/actioncable/test/javascript/src/unit/consumer_test.coffee new file mode 100644 index 0000000000..cf8a592255 --- /dev/null +++ b/actioncable/test/javascript/src/unit/consumer_test.coffee @@ -0,0 +1,11 @@ +{module, test} = QUnit +{consumerTest} = ActionCable.TestHelpers + +module "ActionCable.Consumer", -> + consumerTest "#connect", connect: false, ({consumer, server, done}) -> + server.on("connection", done) + consumer.connect() + + consumerTest "#disconnect", ({consumer, client, done}) -> + client.addEventListener("close", done) + consumer.disconnect() diff --git a/actioncable/test/javascript/vendor/mock-socket.js b/actioncable/test/javascript/vendor/mock-socket.js new file mode 100644 index 0000000000..b465c8b53f --- /dev/null +++ b/actioncable/test/javascript/vendor/mock-socket.js @@ -0,0 +1,4533 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ +/*! + * URI.js - Mutating URLs + * IPv6 Support + * + * Version: 1.17.0 + * + * Author: Rodney Rehm + * Web: http://medialize.github.io/URI.js/ + * + * Licensed under + * MIT License http://www.opensource.org/licenses/mit-license + * GPL v3 http://opensource.org/licenses/GPL-3.0 + * + */ + +(function (root, factory) { + 'use strict'; + // https://github.com/umdjs/umd/blob/master/returnExports.js + if (typeof exports === 'object') { + // Node + module.exports = factory(); + } else if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(factory); + } else { + // Browser globals (root is window) + root.IPv6 = factory(root); + } +}(this, function (root) { + 'use strict'; + + /* + var _in = "fe80:0000:0000:0000:0204:61ff:fe9d:f156"; + var _out = IPv6.best(_in); + var _expected = "fe80::204:61ff:fe9d:f156"; + + console.log(_in, _out, _expected, _out === _expected); + */ + + // save current IPv6 variable, if any + var _IPv6 = root && root.IPv6; + + function bestPresentation(address) { + // based on: + // Javascript to test an IPv6 address for proper format, and to + // present the "best text representation" according to IETF Draft RFC at + // http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-04 + // 8 Feb 2010 Rich Brown, Dartware, LLC + // Please feel free to use this code as long as you provide a link to + // http://www.intermapper.com + // http://intermapper.com/support/tools/IPV6-Validator.aspx + // http://download.dartware.com/thirdparty/ipv6validator.js + + var _address = address.toLowerCase(); + var segments = _address.split(':'); + var length = segments.length; + var total = 8; + + // trim colons (:: or ::a:b:c… or …a:b:c::) + if (segments[0] === '' && segments[1] === '' && segments[2] === '') { + // must have been :: + // remove first two items + segments.shift(); + segments.shift(); + } else if (segments[0] === '' && segments[1] === '') { + // must have been ::xxxx + // remove the first item + segments.shift(); + } else if (segments[length - 1] === '' && segments[length - 2] === '') { + // must have been xxxx:: + segments.pop(); + } + + length = segments.length; + + // adjust total segments for IPv4 trailer + if (segments[length - 1].indexOf('.') !== -1) { + // found a "." which means IPv4 + total = 7; + } + + // fill empty segments them with "0000" + var pos; + for (pos = 0; pos < length; pos++) { + if (segments[pos] === '') { + break; + } + } + + if (pos < total) { + segments.splice(pos, 1, '0000'); + while (segments.length < total) { + segments.splice(pos, 0, '0000'); + } + + length = segments.length; + } + + // strip leading zeros + var _segments; + for (var i = 0; i < total; i++) { + _segments = segments[i].split(''); + for (var j = 0; j < 3 ; j++) { + if (_segments[0] === '0' && _segments.length > 1) { + _segments.splice(0,1); + } else { + break; + } + } + + segments[i] = _segments.join(''); + } + + // find longest sequence of zeroes and coalesce them into one segment + var best = -1; + var _best = 0; + var _current = 0; + var current = -1; + var inzeroes = false; + // i; already declared + + for (i = 0; i < total; i++) { + if (inzeroes) { + if (segments[i] === '0') { + _current += 1; + } else { + inzeroes = false; + if (_current > _best) { + best = current; + _best = _current; + } + } + } else { + if (segments[i] === '0') { + inzeroes = true; + current = i; + _current = 1; + } + } + } + + if (_current > _best) { + best = current; + _best = _current; + } + + if (_best > 1) { + segments.splice(best, _best, ''); + } + + length = segments.length; + + // assemble remaining segments + var result = ''; + if (segments[0] === '') { + result = ':'; + } + + for (i = 0; i < length; i++) { + result += segments[i]; + if (i === length - 1) { + break; + } + + result += ':'; + } + + if (segments[length - 1] === '') { + result += ':'; + } + + return result; + } + + function noConflict() { + /*jshint validthis: true */ + if (root.IPv6 === this) { + root.IPv6 = _IPv6; + } + + return this; + } + + return { + best: bestPresentation, + noConflict: noConflict + }; +})); + +},{}],2:[function(require,module,exports){ +/*! + * URI.js - Mutating URLs + * Second Level Domain (SLD) Support + * + * Version: 1.17.0 + * + * Author: Rodney Rehm + * Web: http://medialize.github.io/URI.js/ + * + * Licensed under + * MIT License http://www.opensource.org/licenses/mit-license + * GPL v3 http://opensource.org/licenses/GPL-3.0 + * + */ + +(function (root, factory) { + 'use strict'; + // https://github.com/umdjs/umd/blob/master/returnExports.js + if (typeof exports === 'object') { + // Node + module.exports = factory(); + } else if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(factory); + } else { + // Browser globals (root is window) + root.SecondLevelDomains = factory(root); + } +}(this, function (root) { + 'use strict'; + + // save current SecondLevelDomains variable, if any + var _SecondLevelDomains = root && root.SecondLevelDomains; + + var SLD = { + // list of known Second Level Domains + // converted list of SLDs from https://github.com/gavingmiller/second-level-domains + // ---- + // publicsuffix.org is more current and actually used by a couple of browsers internally. + // downside is it also contains domains like "dyndns.org" - which is fine for the security + // issues browser have to deal with (SOP for cookies, etc) - but is way overboard for URI.js + // ---- + list: { + 'ac':' com gov mil net org ', + 'ae':' ac co gov mil name net org pro sch ', + 'af':' com edu gov net org ', + 'al':' com edu gov mil net org ', + 'ao':' co ed gv it og pb ', + 'ar':' com edu gob gov int mil net org tur ', + 'at':' ac co gv or ', + 'au':' asn com csiro edu gov id net org ', + 'ba':' co com edu gov mil net org rs unbi unmo unsa untz unze ', + 'bb':' biz co com edu gov info net org store tv ', + 'bh':' biz cc com edu gov info net org ', + 'bn':' com edu gov net org ', + 'bo':' com edu gob gov int mil net org tv ', + 'br':' adm adv agr am arq art ato b bio blog bmd cim cng cnt com coop ecn edu eng esp etc eti far flog fm fnd fot fst g12 ggf gov imb ind inf jor jus lel mat med mil mus net nom not ntr odo org ppg pro psc psi qsl rec slg srv tmp trd tur tv vet vlog wiki zlg ', + 'bs':' com edu gov net org ', + 'bz':' du et om ov rg ', + 'ca':' ab bc mb nb nf nl ns nt nu on pe qc sk yk ', + 'ck':' biz co edu gen gov info net org ', + 'cn':' ac ah bj com cq edu fj gd gov gs gx gz ha hb he hi hl hn jl js jx ln mil net nm nx org qh sc sd sh sn sx tj tw xj xz yn zj ', + 'co':' com edu gov mil net nom org ', + 'cr':' ac c co ed fi go or sa ', + 'cy':' ac biz com ekloges gov ltd name net org parliament press pro tm ', + 'do':' art com edu gob gov mil net org sld web ', + 'dz':' art asso com edu gov net org pol ', + 'ec':' com edu fin gov info med mil net org pro ', + 'eg':' com edu eun gov mil name net org sci ', + 'er':' com edu gov ind mil net org rochest w ', + 'es':' com edu gob nom org ', + 'et':' biz com edu gov info name net org ', + 'fj':' ac biz com info mil name net org pro ', + 'fk':' ac co gov net nom org ', + 'fr':' asso com f gouv nom prd presse tm ', + 'gg':' co net org ', + 'gh':' com edu gov mil org ', + 'gn':' ac com gov net org ', + 'gr':' com edu gov mil net org ', + 'gt':' com edu gob ind mil net org ', + 'gu':' com edu gov net org ', + 'hk':' com edu gov idv net org ', + 'hu':' 2000 agrar bolt casino city co erotica erotika film forum games hotel info ingatlan jogasz konyvelo lakas media news org priv reklam sex shop sport suli szex tm tozsde utazas video ', + 'id':' ac co go mil net or sch web ', + 'il':' ac co gov idf k12 muni net org ', + 'in':' ac co edu ernet firm gen gov i ind mil net nic org res ', + 'iq':' com edu gov i mil net org ', + 'ir':' ac co dnssec gov i id net org sch ', + 'it':' edu gov ', + 'je':' co net org ', + 'jo':' com edu gov mil name net org sch ', + 'jp':' ac ad co ed go gr lg ne or ', + 'ke':' ac co go info me mobi ne or sc ', + 'kh':' com edu gov mil net org per ', + 'ki':' biz com de edu gov info mob net org tel ', + 'km':' asso com coop edu gouv k medecin mil nom notaires pharmaciens presse tm veterinaire ', + 'kn':' edu gov net org ', + 'kr':' ac busan chungbuk chungnam co daegu daejeon es gangwon go gwangju gyeongbuk gyeonggi gyeongnam hs incheon jeju jeonbuk jeonnam k kg mil ms ne or pe re sc seoul ulsan ', + 'kw':' com edu gov net org ', + 'ky':' com edu gov net org ', + 'kz':' com edu gov mil net org ', + 'lb':' com edu gov net org ', + 'lk':' assn com edu gov grp hotel int ltd net ngo org sch soc web ', + 'lr':' com edu gov net org ', + 'lv':' asn com conf edu gov id mil net org ', + 'ly':' com edu gov id med net org plc sch ', + 'ma':' ac co gov m net org press ', + 'mc':' asso tm ', + 'me':' ac co edu gov its net org priv ', + 'mg':' com edu gov mil nom org prd tm ', + 'mk':' com edu gov inf name net org pro ', + 'ml':' com edu gov net org presse ', + 'mn':' edu gov org ', + 'mo':' com edu gov net org ', + 'mt':' com edu gov net org ', + 'mv':' aero biz com coop edu gov info int mil museum name net org pro ', + 'mw':' ac co com coop edu gov int museum net org ', + 'mx':' com edu gob net org ', + 'my':' com edu gov mil name net org sch ', + 'nf':' arts com firm info net other per rec store web ', + 'ng':' biz com edu gov mil mobi name net org sch ', + 'ni':' ac co com edu gob mil net nom org ', + 'np':' com edu gov mil net org ', + 'nr':' biz com edu gov info net org ', + 'om':' ac biz co com edu gov med mil museum net org pro sch ', + 'pe':' com edu gob mil net nom org sld ', + 'ph':' com edu gov i mil net ngo org ', + 'pk':' biz com edu fam gob gok gon gop gos gov net org web ', + 'pl':' art bialystok biz com edu gda gdansk gorzow gov info katowice krakow lodz lublin mil net ngo olsztyn org poznan pwr radom slupsk szczecin torun warszawa waw wroc wroclaw zgora ', + 'pr':' ac biz com edu est gov info isla name net org pro prof ', + 'ps':' com edu gov net org plo sec ', + 'pw':' belau co ed go ne or ', + 'ro':' arts com firm info nom nt org rec store tm www ', + 'rs':' ac co edu gov in org ', + 'sb':' com edu gov net org ', + 'sc':' com edu gov net org ', + 'sh':' co com edu gov net nom org ', + 'sl':' com edu gov net org ', + 'st':' co com consulado edu embaixada gov mil net org principe saotome store ', + 'sv':' com edu gob org red ', + 'sz':' ac co org ', + 'tr':' av bbs bel biz com dr edu gen gov info k12 name net org pol tel tsk tv web ', + 'tt':' aero biz cat co com coop edu gov info int jobs mil mobi museum name net org pro tel travel ', + 'tw':' club com ebiz edu game gov idv mil net org ', + 'mu':' ac co com gov net or org ', + 'mz':' ac co edu gov org ', + 'na':' co com ', + 'nz':' ac co cri geek gen govt health iwi maori mil net org parliament school ', + 'pa':' abo ac com edu gob ing med net nom org sld ', + 'pt':' com edu gov int net nome org publ ', + 'py':' com edu gov mil net org ', + 'qa':' com edu gov mil net org ', + 're':' asso com nom ', + 'ru':' ac adygeya altai amur arkhangelsk astrakhan bashkiria belgorod bir bryansk buryatia cbg chel chelyabinsk chita chukotka chuvashia com dagestan e-burg edu gov grozny int irkutsk ivanovo izhevsk jar joshkar-ola kalmykia kaluga kamchatka karelia kazan kchr kemerovo khabarovsk khakassia khv kirov koenig komi kostroma kranoyarsk kuban kurgan kursk lipetsk magadan mari mari-el marine mil mordovia mosreg msk murmansk nalchik net nnov nov novosibirsk nsk omsk orenburg org oryol penza perm pp pskov ptz rnd ryazan sakhalin samara saratov simbirsk smolensk spb stavropol stv surgut tambov tatarstan tom tomsk tsaritsyn tsk tula tuva tver tyumen udm udmurtia ulan-ude vladikavkaz vladimir vladivostok volgograd vologda voronezh vrn vyatka yakutia yamal yekaterinburg yuzhno-sakhalinsk ', + 'rw':' ac co com edu gouv gov int mil net ', + 'sa':' com edu gov med net org pub sch ', + 'sd':' com edu gov info med net org tv ', + 'se':' a ac b bd c d e f g h i k l m n o org p parti pp press r s t tm u w x y z ', + 'sg':' com edu gov idn net org per ', + 'sn':' art com edu gouv org perso univ ', + 'sy':' com edu gov mil net news org ', + 'th':' ac co go in mi net or ', + 'tj':' ac biz co com edu go gov info int mil name net nic org test web ', + 'tn':' agrinet com defense edunet ens fin gov ind info intl mincom nat net org perso rnrt rns rnu tourism ', + 'tz':' ac co go ne or ', + 'ua':' biz cherkassy chernigov chernovtsy ck cn co com crimea cv dn dnepropetrovsk donetsk dp edu gov if in ivano-frankivsk kh kharkov kherson khmelnitskiy kiev kirovograd km kr ks kv lg lugansk lutsk lviv me mk net nikolaev od odessa org pl poltava pp rovno rv sebastopol sumy te ternopil uzhgorod vinnica vn zaporizhzhe zhitomir zp zt ', + 'ug':' ac co go ne or org sc ', + 'uk':' ac bl british-library co cym gov govt icnet jet lea ltd me mil mod national-library-scotland nel net nhs nic nls org orgn parliament plc police sch scot soc ', + 'us':' dni fed isa kids nsn ', + 'uy':' com edu gub mil net org ', + 've':' co com edu gob info mil net org web ', + 'vi':' co com k12 net org ', + 'vn':' ac biz com edu gov health info int name net org pro ', + 'ye':' co com gov ltd me net org plc ', + 'yu':' ac co edu gov org ', + 'za':' ac agric alt bourse city co cybernet db edu gov grondar iaccess imt inca landesign law mil net ngo nis nom olivetti org pix school tm web ', + 'zm':' ac co com edu gov net org sch ' + }, + // gorhill 2013-10-25: Using indexOf() instead Regexp(). Significant boost + // in both performance and memory footprint. No initialization required. + // http://jsperf.com/uri-js-sld-regex-vs-binary-search/4 + // Following methods use lastIndexOf() rather than array.split() in order + // to avoid any memory allocations. + has: function(domain) { + var tldOffset = domain.lastIndexOf('.'); + if (tldOffset <= 0 || tldOffset >= (domain.length-1)) { + return false; + } + var sldOffset = domain.lastIndexOf('.', tldOffset-1); + if (sldOffset <= 0 || sldOffset >= (tldOffset-1)) { + return false; + } + var sldList = SLD.list[domain.slice(tldOffset+1)]; + if (!sldList) { + return false; + } + return sldList.indexOf(' ' + domain.slice(sldOffset+1, tldOffset) + ' ') >= 0; + }, + is: function(domain) { + var tldOffset = domain.lastIndexOf('.'); + if (tldOffset <= 0 || tldOffset >= (domain.length-1)) { + return false; + } + var sldOffset = domain.lastIndexOf('.', tldOffset-1); + if (sldOffset >= 0) { + return false; + } + var sldList = SLD.list[domain.slice(tldOffset+1)]; + if (!sldList) { + return false; + } + return sldList.indexOf(' ' + domain.slice(0, tldOffset) + ' ') >= 0; + }, + get: function(domain) { + var tldOffset = domain.lastIndexOf('.'); + if (tldOffset <= 0 || tldOffset >= (domain.length-1)) { + return null; + } + var sldOffset = domain.lastIndexOf('.', tldOffset-1); + if (sldOffset <= 0 || sldOffset >= (tldOffset-1)) { + return null; + } + var sldList = SLD.list[domain.slice(tldOffset+1)]; + if (!sldList) { + return null; + } + if (sldList.indexOf(' ' + domain.slice(sldOffset+1, tldOffset) + ' ') < 0) { + return null; + } + return domain.slice(sldOffset+1); + }, + noConflict: function(){ + if (root.SecondLevelDomains === this) { + root.SecondLevelDomains = _SecondLevelDomains; + } + return this; + } + }; + + return SLD; +})); + +},{}],3:[function(require,module,exports){ +/*! + * URI.js - Mutating URLs + * + * Version: 1.17.0 + * + * Author: Rodney Rehm + * Web: http://medialize.github.io/URI.js/ + * + * Licensed under + * MIT License http://www.opensource.org/licenses/mit-license + * GPL v3 http://opensource.org/licenses/GPL-3.0 + * + */ +(function (root, factory) { + 'use strict'; + // https://github.com/umdjs/umd/blob/master/returnExports.js + if (typeof exports === 'object') { + // Node + module.exports = factory(require('./punycode'), require('./IPv6'), require('./SecondLevelDomains')); + } else if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['./punycode', './IPv6', './SecondLevelDomains'], factory); + } else { + // Browser globals (root is window) + root.URI = factory(root.punycode, root.IPv6, root.SecondLevelDomains, root); + } +}(this, function (punycode, IPv6, SLD, root) { + 'use strict'; + /*global location, escape, unescape */ + // FIXME: v2.0.0 renamce non-camelCase properties to uppercase + /*jshint camelcase: false */ + + // save current URI variable, if any + var _URI = root && root.URI; + + function URI(url, base) { + var _urlSupplied = arguments.length >= 1; + var _baseSupplied = arguments.length >= 2; + + // Allow instantiation without the 'new' keyword + if (!(this instanceof URI)) { + if (_urlSupplied) { + if (_baseSupplied) { + return new URI(url, base); + } + + return new URI(url); + } + + return new URI(); + } + + if (url === undefined) { + if (_urlSupplied) { + throw new TypeError('undefined is not a valid argument for URI'); + } + + if (typeof location !== 'undefined') { + url = location.href + ''; + } else { + url = ''; + } + } + + this.href(url); + + // resolve to base according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#constructor + if (base !== undefined) { + return this.absoluteTo(base); + } + + return this; + } + + URI.version = '1.17.0'; + + var p = URI.prototype; + var hasOwn = Object.prototype.hasOwnProperty; + + function escapeRegEx(string) { + // https://github.com/medialize/URI.js/commit/85ac21783c11f8ccab06106dba9735a31a86924d#commitcomment-821963 + return string.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); + } + + function getType(value) { + // IE8 doesn't return [Object Undefined] but [Object Object] for undefined value + if (value === undefined) { + return 'Undefined'; + } + + return String(Object.prototype.toString.call(value)).slice(8, -1); + } + + function isArray(obj) { + return getType(obj) === 'Array'; + } + + function filterArrayValues(data, value) { + var lookup = {}; + var i, length; + + if (getType(value) === 'RegExp') { + lookup = null; + } else if (isArray(value)) { + for (i = 0, length = value.length; i < length; i++) { + lookup[value[i]] = true; + } + } else { + lookup[value] = true; + } + + for (i = 0, length = data.length; i < length; i++) { + /*jshint laxbreak: true */ + var _match = lookup && lookup[data[i]] !== undefined + || !lookup && value.test(data[i]); + /*jshint laxbreak: false */ + if (_match) { + data.splice(i, 1); + length--; + i--; + } + } + + return data; + } + + function arrayContains(list, value) { + var i, length; + + // value may be string, number, array, regexp + if (isArray(value)) { + // Note: this can be optimized to O(n) (instead of current O(m * n)) + for (i = 0, length = value.length; i < length; i++) { + if (!arrayContains(list, value[i])) { + return false; + } + } + + return true; + } + + var _type = getType(value); + for (i = 0, length = list.length; i < length; i++) { + if (_type === 'RegExp') { + if (typeof list[i] === 'string' && list[i].match(value)) { + return true; + } + } else if (list[i] === value) { + return true; + } + } + + return false; + } + + function arraysEqual(one, two) { + if (!isArray(one) || !isArray(two)) { + return false; + } + + // arrays can't be equal if they have different amount of content + if (one.length !== two.length) { + return false; + } + + one.sort(); + two.sort(); + + for (var i = 0, l = one.length; i < l; i++) { + if (one[i] !== two[i]) { + return false; + } + } + + return true; + } + + function trimSlashes(text) { + var trim_expression = /^\/+|\/+$/g; + return text.replace(trim_expression, ''); + } + + URI._parts = function() { + return { + protocol: null, + username: null, + password: null, + hostname: null, + urn: null, + port: null, + path: null, + query: null, + fragment: null, + // state + duplicateQueryParameters: URI.duplicateQueryParameters, + escapeQuerySpace: URI.escapeQuerySpace + }; + }; + // state: allow duplicate query parameters (a=1&a=1) + URI.duplicateQueryParameters = false; + // state: replaces + with %20 (space in query strings) + URI.escapeQuerySpace = true; + // static properties + URI.protocol_expression = /^[a-z][a-z0-9.+-]*$/i; + URI.idn_expression = /[^a-z0-9\.-]/i; + URI.punycode_expression = /(xn--)/i; + // well, 333.444.555.666 matches, but it sure ain't no IPv4 - do we care? + URI.ip4_expression = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/; + // credits to Rich Brown + // source: http://forums.intermapper.com/viewtopic.php?p=1096#1096 + // specification: http://www.ietf.org/rfc/rfc4291.txt + URI.ip6_expression = /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/; + // expression used is "gruber revised" (@gruber v2) determined to be the + // best solution in a regex-golf we did a couple of ages ago at + // * http://mathiasbynens.be/demo/url-regex + // * http://rodneyrehm.de/t/url-regex.html + URI.find_uri_expression = /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“â€â€˜â€™]))/ig; + URI.findUri = { + // valid "scheme://" or "www." + start: /\b(?:([a-z][a-z0-9.+-]*:\/\/)|www\.)/gi, + // everything up to the next whitespace + end: /[\s\r\n]|$/, + // trim trailing punctuation captured by end RegExp + trim: /[`!()\[\]{};:'".,<>?«»“â€â€žâ€˜â€™]+$/ + }; + // http://www.iana.org/assignments/uri-schemes.html + // http://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers#Well-known_ports + URI.defaultPorts = { + http: '80', + https: '443', + ftp: '21', + gopher: '70', + ws: '80', + wss: '443' + }; + // allowed hostname characters according to RFC 3986 + // ALPHA DIGIT "-" "." "_" "~" "!" "$" "&" "'" "(" ")" "*" "+" "," ";" "=" %encoded + // I've never seen a (non-IDN) hostname other than: ALPHA DIGIT . - + URI.invalid_hostname_characters = /[^a-zA-Z0-9\.-]/; + // map DOM Elements to their URI attribute + URI.domAttributes = { + 'a': 'href', + 'blockquote': 'cite', + 'link': 'href', + 'base': 'href', + 'script': 'src', + 'form': 'action', + 'img': 'src', + 'area': 'href', + 'iframe': 'src', + 'embed': 'src', + 'source': 'src', + 'track': 'src', + 'input': 'src', // but only if type="image" + 'audio': 'src', + 'video': 'src' + }; + URI.getDomAttribute = function(node) { + if (!node || !node.nodeName) { + return undefined; + } + + var nodeName = node.nodeName.toLowerCase(); + // <input> should only expose src for type="image" + if (nodeName === 'input' && node.type !== 'image') { + return undefined; + } + + return URI.domAttributes[nodeName]; + }; + + function escapeForDumbFirefox36(value) { + // https://github.com/medialize/URI.js/issues/91 + return escape(value); + } + + // encoding / decoding according to RFC3986 + function strictEncodeURIComponent(string) { + // see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURIComponent + return encodeURIComponent(string) + .replace(/[!'()*]/g, escapeForDumbFirefox36) + .replace(/\*/g, '%2A'); + } + URI.encode = strictEncodeURIComponent; + URI.decode = decodeURIComponent; + URI.iso8859 = function() { + URI.encode = escape; + URI.decode = unescape; + }; + URI.unicode = function() { + URI.encode = strictEncodeURIComponent; + URI.decode = decodeURIComponent; + }; + URI.characters = { + pathname: { + encode: { + // RFC3986 2.1: For consistency, URI producers and normalizers should + // use uppercase hexadecimal digits for all percent-encodings. + expression: /%(24|26|2B|2C|3B|3D|3A|40)/ig, + map: { + // -._~!'()* + '%24': '$', + '%26': '&', + '%2B': '+', + '%2C': ',', + '%3B': ';', + '%3D': '=', + '%3A': ':', + '%40': '@' + } + }, + decode: { + expression: /[\/\?#]/g, + map: { + '/': '%2F', + '?': '%3F', + '#': '%23' + } + } + }, + reserved: { + encode: { + // RFC3986 2.1: For consistency, URI producers and normalizers should + // use uppercase hexadecimal digits for all percent-encodings. + expression: /%(21|23|24|26|27|28|29|2A|2B|2C|2F|3A|3B|3D|3F|40|5B|5D)/ig, + map: { + // gen-delims + '%3A': ':', + '%2F': '/', + '%3F': '?', + '%23': '#', + '%5B': '[', + '%5D': ']', + '%40': '@', + // sub-delims + '%21': '!', + '%24': '$', + '%26': '&', + '%27': '\'', + '%28': '(', + '%29': ')', + '%2A': '*', + '%2B': '+', + '%2C': ',', + '%3B': ';', + '%3D': '=' + } + } + }, + urnpath: { + // The characters under `encode` are the characters called out by RFC 2141 as being acceptable + // for usage in a URN. RFC2141 also calls out "-", ".", and "_" as acceptable characters, but + // these aren't encoded by encodeURIComponent, so we don't have to call them out here. Also + // note that the colon character is not featured in the encoding map; this is because URI.js + // gives the colons in URNs semantic meaning as the delimiters of path segements, and so it + // should not appear unencoded in a segment itself. + // See also the note above about RFC3986 and capitalalized hex digits. + encode: { + expression: /%(21|24|27|28|29|2A|2B|2C|3B|3D|40)/ig, + map: { + '%21': '!', + '%24': '$', + '%27': '\'', + '%28': '(', + '%29': ')', + '%2A': '*', + '%2B': '+', + '%2C': ',', + '%3B': ';', + '%3D': '=', + '%40': '@' + } + }, + // These characters are the characters called out by RFC2141 as "reserved" characters that + // should never appear in a URN, plus the colon character (see note above). + decode: { + expression: /[\/\?#:]/g, + map: { + '/': '%2F', + '?': '%3F', + '#': '%23', + ':': '%3A' + } + } + } + }; + URI.encodeQuery = function(string, escapeQuerySpace) { + var escaped = URI.encode(string + ''); + if (escapeQuerySpace === undefined) { + escapeQuerySpace = URI.escapeQuerySpace; + } + + return escapeQuerySpace ? escaped.replace(/%20/g, '+') : escaped; + }; + URI.decodeQuery = function(string, escapeQuerySpace) { + string += ''; + if (escapeQuerySpace === undefined) { + escapeQuerySpace = URI.escapeQuerySpace; + } + + try { + return URI.decode(escapeQuerySpace ? string.replace(/\+/g, '%20') : string); + } catch(e) { + // we're not going to mess with weird encodings, + // give up and return the undecoded original string + // see https://github.com/medialize/URI.js/issues/87 + // see https://github.com/medialize/URI.js/issues/92 + return string; + } + }; + // generate encode/decode path functions + var _parts = {'encode':'encode', 'decode':'decode'}; + var _part; + var generateAccessor = function(_group, _part) { + return function(string) { + try { + return URI[_part](string + '').replace(URI.characters[_group][_part].expression, function(c) { + return URI.characters[_group][_part].map[c]; + }); + } catch (e) { + // we're not going to mess with weird encodings, + // give up and return the undecoded original string + // see https://github.com/medialize/URI.js/issues/87 + // see https://github.com/medialize/URI.js/issues/92 + return string; + } + }; + }; + + for (_part in _parts) { + URI[_part + 'PathSegment'] = generateAccessor('pathname', _parts[_part]); + URI[_part + 'UrnPathSegment'] = generateAccessor('urnpath', _parts[_part]); + } + + var generateSegmentedPathFunction = function(_sep, _codingFuncName, _innerCodingFuncName) { + return function(string) { + // Why pass in names of functions, rather than the function objects themselves? The + // definitions of some functions (but in particular, URI.decode) will occasionally change due + // to URI.js having ISO8859 and Unicode modes. Passing in the name and getting it will ensure + // that the functions we use here are "fresh". + var actualCodingFunc; + if (!_innerCodingFuncName) { + actualCodingFunc = URI[_codingFuncName]; + } else { + actualCodingFunc = function(string) { + return URI[_codingFuncName](URI[_innerCodingFuncName](string)); + }; + } + + var segments = (string + '').split(_sep); + + for (var i = 0, length = segments.length; i < length; i++) { + segments[i] = actualCodingFunc(segments[i]); + } + + return segments.join(_sep); + }; + }; + + // This takes place outside the above loop because we don't want, e.g., encodeUrnPath functions. + URI.decodePath = generateSegmentedPathFunction('/', 'decodePathSegment'); + URI.decodeUrnPath = generateSegmentedPathFunction(':', 'decodeUrnPathSegment'); + URI.recodePath = generateSegmentedPathFunction('/', 'encodePathSegment', 'decode'); + URI.recodeUrnPath = generateSegmentedPathFunction(':', 'encodeUrnPathSegment', 'decode'); + + URI.encodeReserved = generateAccessor('reserved', 'encode'); + + URI.parse = function(string, parts) { + var pos; + if (!parts) { + parts = {}; + } + // [protocol"://"[username[":"password]"@"]hostname[":"port]"/"?][path]["?"querystring]["#"fragment] + + // extract fragment + pos = string.indexOf('#'); + if (pos > -1) { + // escaping? + parts.fragment = string.substring(pos + 1) || null; + string = string.substring(0, pos); + } + + // extract query + pos = string.indexOf('?'); + if (pos > -1) { + // escaping? + parts.query = string.substring(pos + 1) || null; + string = string.substring(0, pos); + } + + // extract protocol + if (string.substring(0, 2) === '//') { + // relative-scheme + parts.protocol = null; + string = string.substring(2); + // extract "user:pass@host:port" + string = URI.parseAuthority(string, parts); + } else { + pos = string.indexOf(':'); + if (pos > -1) { + parts.protocol = string.substring(0, pos) || null; + if (parts.protocol && !parts.protocol.match(URI.protocol_expression)) { + // : may be within the path + parts.protocol = undefined; + } else if (string.substring(pos + 1, pos + 3) === '//') { + string = string.substring(pos + 3); + + // extract "user:pass@host:port" + string = URI.parseAuthority(string, parts); + } else { + string = string.substring(pos + 1); + parts.urn = true; + } + } + } + + // what's left must be the path + parts.path = string; + + // and we're done + return parts; + }; + URI.parseHost = function(string, parts) { + // Copy chrome, IE, opera backslash-handling behavior. + // Back slashes before the query string get converted to forward slashes + // See: https://github.com/joyent/node/blob/386fd24f49b0e9d1a8a076592a404168faeecc34/lib/url.js#L115-L124 + // See: https://code.google.com/p/chromium/issues/detail?id=25916 + // https://github.com/medialize/URI.js/pull/233 + string = string.replace(/\\/g, '/'); + + // extract host:port + var pos = string.indexOf('/'); + var bracketPos; + var t; + + if (pos === -1) { + pos = string.length; + } + + if (string.charAt(0) === '[') { + // IPv6 host - http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-04#section-6 + // I claim most client software breaks on IPv6 anyways. To simplify things, URI only accepts + // IPv6+port in the format [2001:db8::1]:80 (for the time being) + bracketPos = string.indexOf(']'); + parts.hostname = string.substring(1, bracketPos) || null; + parts.port = string.substring(bracketPos + 2, pos) || null; + if (parts.port === '/') { + parts.port = null; + } + } else { + var firstColon = string.indexOf(':'); + var firstSlash = string.indexOf('/'); + var nextColon = string.indexOf(':', firstColon + 1); + if (nextColon !== -1 && (firstSlash === -1 || nextColon < firstSlash)) { + // IPv6 host contains multiple colons - but no port + // this notation is actually not allowed by RFC 3986, but we're a liberal parser + parts.hostname = string.substring(0, pos) || null; + parts.port = null; + } else { + t = string.substring(0, pos).split(':'); + parts.hostname = t[0] || null; + parts.port = t[1] || null; + } + } + + if (parts.hostname && string.substring(pos).charAt(0) !== '/') { + pos++; + string = '/' + string; + } + + return string.substring(pos) || '/'; + }; + URI.parseAuthority = function(string, parts) { + string = URI.parseUserinfo(string, parts); + return URI.parseHost(string, parts); + }; + URI.parseUserinfo = function(string, parts) { + // extract username:password + var firstSlash = string.indexOf('/'); + var pos = string.lastIndexOf('@', firstSlash > -1 ? firstSlash : string.length - 1); + var t; + + // authority@ must come before /path + if (pos > -1 && (firstSlash === -1 || pos < firstSlash)) { + t = string.substring(0, pos).split(':'); + parts.username = t[0] ? URI.decode(t[0]) : null; + t.shift(); + parts.password = t[0] ? URI.decode(t.join(':')) : null; + string = string.substring(pos + 1); + } else { + parts.username = null; + parts.password = null; + } + + return string; + }; + URI.parseQuery = function(string, escapeQuerySpace) { + if (!string) { + return {}; + } + + // throw out the funky business - "?"[name"="value"&"]+ + string = string.replace(/&+/g, '&').replace(/^\?*&*|&+$/g, ''); + + if (!string) { + return {}; + } + + var items = {}; + var splits = string.split('&'); + var length = splits.length; + var v, name, value; + + for (var i = 0; i < length; i++) { + v = splits[i].split('='); + name = URI.decodeQuery(v.shift(), escapeQuerySpace); + // no "=" is null according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#collect-url-parameters + value = v.length ? URI.decodeQuery(v.join('='), escapeQuerySpace) : null; + + if (hasOwn.call(items, name)) { + if (typeof items[name] === 'string' || items[name] === null) { + items[name] = [items[name]]; + } + + items[name].push(value); + } else { + items[name] = value; + } + } + + return items; + }; + + URI.build = function(parts) { + var t = ''; + + if (parts.protocol) { + t += parts.protocol + ':'; + } + + if (!parts.urn && (t || parts.hostname)) { + t += '//'; + } + + t += (URI.buildAuthority(parts) || ''); + + if (typeof parts.path === 'string') { + if (parts.path.charAt(0) !== '/' && typeof parts.hostname === 'string') { + t += '/'; + } + + t += parts.path; + } + + if (typeof parts.query === 'string' && parts.query) { + t += '?' + parts.query; + } + + if (typeof parts.fragment === 'string' && parts.fragment) { + t += '#' + parts.fragment; + } + return t; + }; + URI.buildHost = function(parts) { + var t = ''; + + if (!parts.hostname) { + return ''; + } else if (URI.ip6_expression.test(parts.hostname)) { + t += '[' + parts.hostname + ']'; + } else { + t += parts.hostname; + } + + if (parts.port) { + t += ':' + parts.port; + } + + return t; + }; + URI.buildAuthority = function(parts) { + return URI.buildUserinfo(parts) + URI.buildHost(parts); + }; + URI.buildUserinfo = function(parts) { + var t = ''; + + if (parts.username) { + t += URI.encode(parts.username); + + if (parts.password) { + t += ':' + URI.encode(parts.password); + } + + t += '@'; + } + + return t; + }; + URI.buildQuery = function(data, duplicateQueryParameters, escapeQuerySpace) { + // according to http://tools.ietf.org/html/rfc3986 or http://labs.apache.org/webarch/uri/rfc/rfc3986.html + // being »-._~!$&'()*+,;=:@/?« %HEX and alnum are allowed + // the RFC explicitly states ?/foo being a valid use case, no mention of parameter syntax! + // URI.js treats the query string as being application/x-www-form-urlencoded + // see http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type + + var t = ''; + var unique, key, i, length; + for (key in data) { + if (hasOwn.call(data, key) && key) { + if (isArray(data[key])) { + unique = {}; + for (i = 0, length = data[key].length; i < length; i++) { + if (data[key][i] !== undefined && unique[data[key][i] + ''] === undefined) { + t += '&' + URI.buildQueryParameter(key, data[key][i], escapeQuerySpace); + if (duplicateQueryParameters !== true) { + unique[data[key][i] + ''] = true; + } + } + } + } else if (data[key] !== undefined) { + t += '&' + URI.buildQueryParameter(key, data[key], escapeQuerySpace); + } + } + } + + return t.substring(1); + }; + URI.buildQueryParameter = function(name, value, escapeQuerySpace) { + // http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type -- application/x-www-form-urlencoded + // don't append "=" for null values, according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#url-parameter-serialization + return URI.encodeQuery(name, escapeQuerySpace) + (value !== null ? '=' + URI.encodeQuery(value, escapeQuerySpace) : ''); + }; + + URI.addQuery = function(data, name, value) { + if (typeof name === 'object') { + for (var key in name) { + if (hasOwn.call(name, key)) { + URI.addQuery(data, key, name[key]); + } + } + } else if (typeof name === 'string') { + if (data[name] === undefined) { + data[name] = value; + return; + } else if (typeof data[name] === 'string') { + data[name] = [data[name]]; + } + + if (!isArray(value)) { + value = [value]; + } + + data[name] = (data[name] || []).concat(value); + } else { + throw new TypeError('URI.addQuery() accepts an object, string as the name parameter'); + } + }; + URI.removeQuery = function(data, name, value) { + var i, length, key; + + if (isArray(name)) { + for (i = 0, length = name.length; i < length; i++) { + data[name[i]] = undefined; + } + } else if (getType(name) === 'RegExp') { + for (key in data) { + if (name.test(key)) { + data[key] = undefined; + } + } + } else if (typeof name === 'object') { + for (key in name) { + if (hasOwn.call(name, key)) { + URI.removeQuery(data, key, name[key]); + } + } + } else if (typeof name === 'string') { + if (value !== undefined) { + if (getType(value) === 'RegExp') { + if (!isArray(data[name]) && value.test(data[name])) { + data[name] = undefined; + } else { + data[name] = filterArrayValues(data[name], value); + } + } else if (data[name] === String(value) && (!isArray(value) || value.length === 1)) { + data[name] = undefined; + } else if (isArray(data[name])) { + data[name] = filterArrayValues(data[name], value); + } + } else { + data[name] = undefined; + } + } else { + throw new TypeError('URI.removeQuery() accepts an object, string, RegExp as the first parameter'); + } + }; + URI.hasQuery = function(data, name, value, withinArray) { + if (typeof name === 'object') { + for (var key in name) { + if (hasOwn.call(name, key)) { + if (!URI.hasQuery(data, key, name[key])) { + return false; + } + } + } + + return true; + } else if (typeof name !== 'string') { + throw new TypeError('URI.hasQuery() accepts an object, string as the name parameter'); + } + + switch (getType(value)) { + case 'Undefined': + // true if exists (but may be empty) + return name in data; // data[name] !== undefined; + + case 'Boolean': + // true if exists and non-empty + var _booly = Boolean(isArray(data[name]) ? data[name].length : data[name]); + return value === _booly; + + case 'Function': + // allow complex comparison + return !!value(data[name], name, data); + + case 'Array': + if (!isArray(data[name])) { + return false; + } + + var op = withinArray ? arrayContains : arraysEqual; + return op(data[name], value); + + case 'RegExp': + if (!isArray(data[name])) { + return Boolean(data[name] && data[name].match(value)); + } + + if (!withinArray) { + return false; + } + + return arrayContains(data[name], value); + + case 'Number': + value = String(value); + /* falls through */ + case 'String': + if (!isArray(data[name])) { + return data[name] === value; + } + + if (!withinArray) { + return false; + } + + return arrayContains(data[name], value); + + default: + throw new TypeError('URI.hasQuery() accepts undefined, boolean, string, number, RegExp, Function as the value parameter'); + } + }; + + + URI.commonPath = function(one, two) { + var length = Math.min(one.length, two.length); + var pos; + + // find first non-matching character + for (pos = 0; pos < length; pos++) { + if (one.charAt(pos) !== two.charAt(pos)) { + pos--; + break; + } + } + + if (pos < 1) { + return one.charAt(0) === two.charAt(0) && one.charAt(0) === '/' ? '/' : ''; + } + + // revert to last / + if (one.charAt(pos) !== '/' || two.charAt(pos) !== '/') { + pos = one.substring(0, pos).lastIndexOf('/'); + } + + return one.substring(0, pos + 1); + }; + + URI.withinString = function(string, callback, options) { + options || (options = {}); + var _start = options.start || URI.findUri.start; + var _end = options.end || URI.findUri.end; + var _trim = options.trim || URI.findUri.trim; + var _attributeOpen = /[a-z0-9-]=["']?$/i; + + _start.lastIndex = 0; + while (true) { + var match = _start.exec(string); + if (!match) { + break; + } + + var start = match.index; + if (options.ignoreHtml) { + // attribut(e=["']?$) + var attributeOpen = string.slice(Math.max(start - 3, 0), start); + if (attributeOpen && _attributeOpen.test(attributeOpen)) { + continue; + } + } + + var end = start + string.slice(start).search(_end); + var slice = string.slice(start, end).replace(_trim, ''); + if (options.ignore && options.ignore.test(slice)) { + continue; + } + + end = start + slice.length; + var result = callback(slice, start, end, string); + string = string.slice(0, start) + result + string.slice(end); + _start.lastIndex = start + result.length; + } + + _start.lastIndex = 0; + return string; + }; + + URI.ensureValidHostname = function(v) { + // Theoretically URIs allow percent-encoding in Hostnames (according to RFC 3986) + // they are not part of DNS and therefore ignored by URI.js + + if (v.match(URI.invalid_hostname_characters)) { + // test punycode + if (!punycode) { + throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-] and Punycode.js is not available'); + } + + if (punycode.toASCII(v).match(URI.invalid_hostname_characters)) { + throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]'); + } + } + }; + + // noConflict + URI.noConflict = function(removeAll) { + if (removeAll) { + var unconflicted = { + URI: this.noConflict() + }; + + if (root.URITemplate && typeof root.URITemplate.noConflict === 'function') { + unconflicted.URITemplate = root.URITemplate.noConflict(); + } + + if (root.IPv6 && typeof root.IPv6.noConflict === 'function') { + unconflicted.IPv6 = root.IPv6.noConflict(); + } + + if (root.SecondLevelDomains && typeof root.SecondLevelDomains.noConflict === 'function') { + unconflicted.SecondLevelDomains = root.SecondLevelDomains.noConflict(); + } + + return unconflicted; + } else if (root.URI === this) { + root.URI = _URI; + } + + return this; + }; + + p.build = function(deferBuild) { + if (deferBuild === true) { + this._deferred_build = true; + } else if (deferBuild === undefined || this._deferred_build) { + this._string = URI.build(this._parts); + this._deferred_build = false; + } + + return this; + }; + + p.clone = function() { + return new URI(this); + }; + + p.valueOf = p.toString = function() { + return this.build(false)._string; + }; + + + function generateSimpleAccessor(_part){ + return function(v, build) { + if (v === undefined) { + return this._parts[_part] || ''; + } else { + this._parts[_part] = v || null; + this.build(!build); + return this; + } + }; + } + + function generatePrefixAccessor(_part, _key){ + return function(v, build) { + if (v === undefined) { + return this._parts[_part] || ''; + } else { + if (v !== null) { + v = v + ''; + if (v.charAt(0) === _key) { + v = v.substring(1); + } + } + + this._parts[_part] = v; + this.build(!build); + return this; + } + }; + } + + p.protocol = generateSimpleAccessor('protocol'); + p.username = generateSimpleAccessor('username'); + p.password = generateSimpleAccessor('password'); + p.hostname = generateSimpleAccessor('hostname'); + p.port = generateSimpleAccessor('port'); + p.query = generatePrefixAccessor('query', '?'); + p.fragment = generatePrefixAccessor('fragment', '#'); + + p.search = function(v, build) { + var t = this.query(v, build); + return typeof t === 'string' && t.length ? ('?' + t) : t; + }; + p.hash = function(v, build) { + var t = this.fragment(v, build); + return typeof t === 'string' && t.length ? ('#' + t) : t; + }; + + p.pathname = function(v, build) { + if (v === undefined || v === true) { + var res = this._parts.path || (this._parts.hostname ? '/' : ''); + return v ? (this._parts.urn ? URI.decodeUrnPath : URI.decodePath)(res) : res; + } else { + if (this._parts.urn) { + this._parts.path = v ? URI.recodeUrnPath(v) : ''; + } else { + this._parts.path = v ? URI.recodePath(v) : '/'; + } + this.build(!build); + return this; + } + }; + p.path = p.pathname; + p.href = function(href, build) { + var key; + + if (href === undefined) { + return this.toString(); + } + + this._string = ''; + this._parts = URI._parts(); + + var _URI = href instanceof URI; + var _object = typeof href === 'object' && (href.hostname || href.path || href.pathname); + if (href.nodeName) { + var attribute = URI.getDomAttribute(href); + href = href[attribute] || ''; + _object = false; + } + + // window.location is reported to be an object, but it's not the sort + // of object we're looking for: + // * location.protocol ends with a colon + // * location.query != object.search + // * location.hash != object.fragment + // simply serializing the unknown object should do the trick + // (for location, not for everything...) + if (!_URI && _object && href.pathname !== undefined) { + href = href.toString(); + } + + if (typeof href === 'string' || href instanceof String) { + this._parts = URI.parse(String(href), this._parts); + } else if (_URI || _object) { + var src = _URI ? href._parts : href; + for (key in src) { + if (hasOwn.call(this._parts, key)) { + this._parts[key] = src[key]; + } + } + } else { + throw new TypeError('invalid input'); + } + + this.build(!build); + return this; + }; + + // identification accessors + p.is = function(what) { + var ip = false; + var ip4 = false; + var ip6 = false; + var name = false; + var sld = false; + var idn = false; + var punycode = false; + var relative = !this._parts.urn; + + if (this._parts.hostname) { + relative = false; + ip4 = URI.ip4_expression.test(this._parts.hostname); + ip6 = URI.ip6_expression.test(this._parts.hostname); + ip = ip4 || ip6; + name = !ip; + sld = name && SLD && SLD.has(this._parts.hostname); + idn = name && URI.idn_expression.test(this._parts.hostname); + punycode = name && URI.punycode_expression.test(this._parts.hostname); + } + + switch (what.toLowerCase()) { + case 'relative': + return relative; + + case 'absolute': + return !relative; + + // hostname identification + case 'domain': + case 'name': + return name; + + case 'sld': + return sld; + + case 'ip': + return ip; + + case 'ip4': + case 'ipv4': + case 'inet4': + return ip4; + + case 'ip6': + case 'ipv6': + case 'inet6': + return ip6; + + case 'idn': + return idn; + + case 'url': + return !this._parts.urn; + + case 'urn': + return !!this._parts.urn; + + case 'punycode': + return punycode; + } + + return null; + }; + + // component specific input validation + var _protocol = p.protocol; + var _port = p.port; + var _hostname = p.hostname; + + p.protocol = function(v, build) { + if (v !== undefined) { + if (v) { + // accept trailing :// + v = v.replace(/:(\/\/)?$/, ''); + + if (!v.match(URI.protocol_expression)) { + throw new TypeError('Protocol "' + v + '" contains characters other than [A-Z0-9.+-] or doesn\'t start with [A-Z]'); + } + } + } + return _protocol.call(this, v, build); + }; + p.scheme = p.protocol; + p.port = function(v, build) { + if (this._parts.urn) { + return v === undefined ? '' : this; + } + + if (v !== undefined) { + if (v === 0) { + v = null; + } + + if (v) { + v += ''; + if (v.charAt(0) === ':') { + v = v.substring(1); + } + + if (v.match(/[^0-9]/)) { + throw new TypeError('Port "' + v + '" contains characters other than [0-9]'); + } + } + } + return _port.call(this, v, build); + }; + p.hostname = function(v, build) { + if (this._parts.urn) { + return v === undefined ? '' : this; + } + + if (v !== undefined) { + var x = {}; + var res = URI.parseHost(v, x); + if (res !== '/') { + throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]'); + } + + v = x.hostname; + } + return _hostname.call(this, v, build); + }; + + // compound accessors + p.origin = function(v, build) { + var parts; + + if (this._parts.urn) { + return v === undefined ? '' : this; + } + + if (v === undefined) { + var protocol = this.protocol(); + var authority = this.authority(); + if (!authority) return ''; + return (protocol ? protocol + '://' : '') + this.authority(); + } else { + var origin = URI(v); + this + .protocol(origin.protocol()) + .authority(origin.authority()) + .build(!build); + return this; + } + }; + p.host = function(v, build) { + if (this._parts.urn) { + return v === undefined ? '' : this; + } + + if (v === undefined) { + return this._parts.hostname ? URI.buildHost(this._parts) : ''; + } else { + var res = URI.parseHost(v, this._parts); + if (res !== '/') { + throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]'); + } + + this.build(!build); + return this; + } + }; + p.authority = function(v, build) { + if (this._parts.urn) { + return v === undefined ? '' : this; + } + + if (v === undefined) { + return this._parts.hostname ? URI.buildAuthority(this._parts) : ''; + } else { + var res = URI.parseAuthority(v, this._parts); + if (res !== '/') { + throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]'); + } + + this.build(!build); + return this; + } + }; + p.userinfo = function(v, build) { + if (this._parts.urn) { + return v === undefined ? '' : this; + } + + if (v === undefined) { + if (!this._parts.username) { + return ''; + } + + var t = URI.buildUserinfo(this._parts); + return t.substring(0, t.length -1); + } else { + if (v[v.length-1] !== '@') { + v += '@'; + } + + URI.parseUserinfo(v, this._parts); + this.build(!build); + return this; + } + }; + p.resource = function(v, build) { + var parts; + + if (v === undefined) { + return this.path() + this.search() + this.hash(); + } + + parts = URI.parse(v); + this._parts.path = parts.path; + this._parts.query = parts.query; + this._parts.fragment = parts.fragment; + this.build(!build); + return this; + }; + + // fraction accessors + p.subdomain = function(v, build) { + if (this._parts.urn) { + return v === undefined ? '' : this; + } + + // convenience, return "www" from "www.example.org" + if (v === undefined) { + if (!this._parts.hostname || this.is('IP')) { + return ''; + } + + // grab domain and add another segment + var end = this._parts.hostname.length - this.domain().length - 1; + return this._parts.hostname.substring(0, end) || ''; + } else { + var e = this._parts.hostname.length - this.domain().length; + var sub = this._parts.hostname.substring(0, e); + var replace = new RegExp('^' + escapeRegEx(sub)); + + if (v && v.charAt(v.length - 1) !== '.') { + v += '.'; + } + + if (v) { + URI.ensureValidHostname(v); + } + + this._parts.hostname = this._parts.hostname.replace(replace, v); + this.build(!build); + return this; + } + }; + p.domain = function(v, build) { + if (this._parts.urn) { + return v === undefined ? '' : this; + } + + if (typeof v === 'boolean') { + build = v; + v = undefined; + } + + // convenience, return "example.org" from "www.example.org" + if (v === undefined) { + if (!this._parts.hostname || this.is('IP')) { + return ''; + } + + // if hostname consists of 1 or 2 segments, it must be the domain + var t = this._parts.hostname.match(/\./g); + if (t && t.length < 2) { + return this._parts.hostname; + } + + // grab tld and add another segment + var end = this._parts.hostname.length - this.tld(build).length - 1; + end = this._parts.hostname.lastIndexOf('.', end -1) + 1; + return this._parts.hostname.substring(end) || ''; + } else { + if (!v) { + throw new TypeError('cannot set domain empty'); + } + + URI.ensureValidHostname(v); + + if (!this._parts.hostname || this.is('IP')) { + this._parts.hostname = v; + } else { + var replace = new RegExp(escapeRegEx(this.domain()) + '$'); + this._parts.hostname = this._parts.hostname.replace(replace, v); + } + + this.build(!build); + return this; + } + }; + p.tld = function(v, build) { + if (this._parts.urn) { + return v === undefined ? '' : this; + } + + if (typeof v === 'boolean') { + build = v; + v = undefined; + } + + // return "org" from "www.example.org" + if (v === undefined) { + if (!this._parts.hostname || this.is('IP')) { + return ''; + } + + var pos = this._parts.hostname.lastIndexOf('.'); + var tld = this._parts.hostname.substring(pos + 1); + + if (build !== true && SLD && SLD.list[tld.toLowerCase()]) { + return SLD.get(this._parts.hostname) || tld; + } + + return tld; + } else { + var replace; + + if (!v) { + throw new TypeError('cannot set TLD empty'); + } else if (v.match(/[^a-zA-Z0-9-]/)) { + if (SLD && SLD.is(v)) { + replace = new RegExp(escapeRegEx(this.tld()) + '$'); + this._parts.hostname = this._parts.hostname.replace(replace, v); + } else { + throw new TypeError('TLD "' + v + '" contains characters other than [A-Z0-9]'); + } + } else if (!this._parts.hostname || this.is('IP')) { + throw new ReferenceError('cannot set TLD on non-domain host'); + } else { + replace = new RegExp(escapeRegEx(this.tld()) + '$'); + this._parts.hostname = this._parts.hostname.replace(replace, v); + } + + this.build(!build); + return this; + } + }; + p.directory = function(v, build) { + if (this._parts.urn) { + return v === undefined ? '' : this; + } + + if (v === undefined || v === true) { + if (!this._parts.path && !this._parts.hostname) { + return ''; + } + + if (this._parts.path === '/') { + return '/'; + } + + var end = this._parts.path.length - this.filename().length - 1; + var res = this._parts.path.substring(0, end) || (this._parts.hostname ? '/' : ''); + + return v ? URI.decodePath(res) : res; + + } else { + var e = this._parts.path.length - this.filename().length; + var directory = this._parts.path.substring(0, e); + var replace = new RegExp('^' + escapeRegEx(directory)); + + // fully qualifier directories begin with a slash + if (!this.is('relative')) { + if (!v) { + v = '/'; + } + + if (v.charAt(0) !== '/') { + v = '/' + v; + } + } + + // directories always end with a slash + if (v && v.charAt(v.length - 1) !== '/') { + v += '/'; + } + + v = URI.recodePath(v); + this._parts.path = this._parts.path.replace(replace, v); + this.build(!build); + return this; + } + }; + p.filename = function(v, build) { + if (this._parts.urn) { + return v === undefined ? '' : this; + } + + if (v === undefined || v === true) { + if (!this._parts.path || this._parts.path === '/') { + return ''; + } + + var pos = this._parts.path.lastIndexOf('/'); + var res = this._parts.path.substring(pos+1); + + return v ? URI.decodePathSegment(res) : res; + } else { + var mutatedDirectory = false; + + if (v.charAt(0) === '/') { + v = v.substring(1); + } + + if (v.match(/\.?\//)) { + mutatedDirectory = true; + } + + var replace = new RegExp(escapeRegEx(this.filename()) + '$'); + v = URI.recodePath(v); + this._parts.path = this._parts.path.replace(replace, v); + + if (mutatedDirectory) { + this.normalizePath(build); + } else { + this.build(!build); + } + + return this; + } + }; + p.suffix = function(v, build) { + if (this._parts.urn) { + return v === undefined ? '' : this; + } + + if (v === undefined || v === true) { + if (!this._parts.path || this._parts.path === '/') { + return ''; + } + + var filename = this.filename(); + var pos = filename.lastIndexOf('.'); + var s, res; + + if (pos === -1) { + return ''; + } + + // suffix may only contain alnum characters (yup, I made this up.) + s = filename.substring(pos+1); + res = (/^[a-z0-9%]+$/i).test(s) ? s : ''; + return v ? URI.decodePathSegment(res) : res; + } else { + if (v.charAt(0) === '.') { + v = v.substring(1); + } + + var suffix = this.suffix(); + var replace; + + if (!suffix) { + if (!v) { + return this; + } + + this._parts.path += '.' + URI.recodePath(v); + } else if (!v) { + replace = new RegExp(escapeRegEx('.' + suffix) + '$'); + } else { + replace = new RegExp(escapeRegEx(suffix) + '$'); + } + + if (replace) { + v = URI.recodePath(v); + this._parts.path = this._parts.path.replace(replace, v); + } + + this.build(!build); + return this; + } + }; + p.segment = function(segment, v, build) { + var separator = this._parts.urn ? ':' : '/'; + var path = this.path(); + var absolute = path.substring(0, 1) === '/'; + var segments = path.split(separator); + + if (segment !== undefined && typeof segment !== 'number') { + build = v; + v = segment; + segment = undefined; + } + + if (segment !== undefined && typeof segment !== 'number') { + throw new Error('Bad segment "' + segment + '", must be 0-based integer'); + } + + if (absolute) { + segments.shift(); + } + + if (segment < 0) { + // allow negative indexes to address from the end + segment = Math.max(segments.length + segment, 0); + } + + if (v === undefined) { + /*jshint laxbreak: true */ + return segment === undefined + ? segments + : segments[segment]; + /*jshint laxbreak: false */ + } else if (segment === null || segments[segment] === undefined) { + if (isArray(v)) { + segments = []; + // collapse empty elements within array + for (var i=0, l=v.length; i < l; i++) { + if (!v[i].length && (!segments.length || !segments[segments.length -1].length)) { + continue; + } + + if (segments.length && !segments[segments.length -1].length) { + segments.pop(); + } + + segments.push(trimSlashes(v[i])); + } + } else if (v || typeof v === 'string') { + v = trimSlashes(v); + if (segments[segments.length -1] === '') { + // empty trailing elements have to be overwritten + // to prevent results such as /foo//bar + segments[segments.length -1] = v; + } else { + segments.push(v); + } + } + } else { + if (v) { + segments[segment] = trimSlashes(v); + } else { + segments.splice(segment, 1); + } + } + + if (absolute) { + segments.unshift(''); + } + + return this.path(segments.join(separator), build); + }; + p.segmentCoded = function(segment, v, build) { + var segments, i, l; + + if (typeof segment !== 'number') { + build = v; + v = segment; + segment = undefined; + } + + if (v === undefined) { + segments = this.segment(segment, v, build); + if (!isArray(segments)) { + segments = segments !== undefined ? URI.decode(segments) : undefined; + } else { + for (i = 0, l = segments.length; i < l; i++) { + segments[i] = URI.decode(segments[i]); + } + } + + return segments; + } + + if (!isArray(v)) { + v = (typeof v === 'string' || v instanceof String) ? URI.encode(v) : v; + } else { + for (i = 0, l = v.length; i < l; i++) { + v[i] = URI.encode(v[i]); + } + } + + return this.segment(segment, v, build); + }; + + // mutating query string + var q = p.query; + p.query = function(v, build) { + if (v === true) { + return URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); + } else if (typeof v === 'function') { + var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); + var result = v.call(this, data); + this._parts.query = URI.buildQuery(result || data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); + this.build(!build); + return this; + } else if (v !== undefined && typeof v !== 'string') { + this._parts.query = URI.buildQuery(v, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); + this.build(!build); + return this; + } else { + return q.call(this, v, build); + } + }; + p.setQuery = function(name, value, build) { + var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); + + if (typeof name === 'string' || name instanceof String) { + data[name] = value !== undefined ? value : null; + } else if (typeof name === 'object') { + for (var key in name) { + if (hasOwn.call(name, key)) { + data[key] = name[key]; + } + } + } else { + throw new TypeError('URI.addQuery() accepts an object, string as the name parameter'); + } + + this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); + if (typeof name !== 'string') { + build = value; + } + + this.build(!build); + return this; + }; + p.addQuery = function(name, value, build) { + var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); + URI.addQuery(data, name, value === undefined ? null : value); + this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); + if (typeof name !== 'string') { + build = value; + } + + this.build(!build); + return this; + }; + p.removeQuery = function(name, value, build) { + var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); + URI.removeQuery(data, name, value); + this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); + if (typeof name !== 'string') { + build = value; + } + + this.build(!build); + return this; + }; + p.hasQuery = function(name, value, withinArray) { + var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); + return URI.hasQuery(data, name, value, withinArray); + }; + p.setSearch = p.setQuery; + p.addSearch = p.addQuery; + p.removeSearch = p.removeQuery; + p.hasSearch = p.hasQuery; + + // sanitizing URLs + p.normalize = function() { + if (this._parts.urn) { + return this + .normalizeProtocol(false) + .normalizePath(false) + .normalizeQuery(false) + .normalizeFragment(false) + .build(); + } + + return this + .normalizeProtocol(false) + .normalizeHostname(false) + .normalizePort(false) + .normalizePath(false) + .normalizeQuery(false) + .normalizeFragment(false) + .build(); + }; + p.normalizeProtocol = function(build) { + if (typeof this._parts.protocol === 'string') { + this._parts.protocol = this._parts.protocol.toLowerCase(); + this.build(!build); + } + + return this; + }; + p.normalizeHostname = function(build) { + if (this._parts.hostname) { + if (this.is('IDN') && punycode) { + this._parts.hostname = punycode.toASCII(this._parts.hostname); + } else if (this.is('IPv6') && IPv6) { + this._parts.hostname = IPv6.best(this._parts.hostname); + } + + this._parts.hostname = this._parts.hostname.toLowerCase(); + this.build(!build); + } + + return this; + }; + p.normalizePort = function(build) { + // remove port of it's the protocol's default + if (typeof this._parts.protocol === 'string' && this._parts.port === URI.defaultPorts[this._parts.protocol]) { + this._parts.port = null; + this.build(!build); + } + + return this; + }; + p.normalizePath = function(build) { + var _path = this._parts.path; + if (!_path) { + return this; + } + + if (this._parts.urn) { + this._parts.path = URI.recodeUrnPath(this._parts.path); + this.build(!build); + return this; + } + + if (this._parts.path === '/') { + return this; + } + + var _was_relative; + var _leadingParents = ''; + var _parent, _pos; + + // handle relative paths + if (_path.charAt(0) !== '/') { + _was_relative = true; + _path = '/' + _path; + } + + // handle relative files (as opposed to directories) + if (_path.slice(-3) === '/..' || _path.slice(-2) === '/.') { + _path += '/'; + } + + // resolve simples + _path = _path + .replace(/(\/(\.\/)+)|(\/\.$)/g, '/') + .replace(/\/{2,}/g, '/'); + + // remember leading parents + if (_was_relative) { + _leadingParents = _path.substring(1).match(/^(\.\.\/)+/) || ''; + if (_leadingParents) { + _leadingParents = _leadingParents[0]; + } + } + + // resolve parents + while (true) { + _parent = _path.indexOf('/..'); + if (_parent === -1) { + // no more ../ to resolve + break; + } else if (_parent === 0) { + // top level cannot be relative, skip it + _path = _path.substring(3); + continue; + } + + _pos = _path.substring(0, _parent).lastIndexOf('/'); + if (_pos === -1) { + _pos = _parent; + } + _path = _path.substring(0, _pos) + _path.substring(_parent + 3); + } + + // revert to relative + if (_was_relative && this.is('relative')) { + _path = _leadingParents + _path.substring(1); + } + + _path = URI.recodePath(_path); + this._parts.path = _path; + this.build(!build); + return this; + }; + p.normalizePathname = p.normalizePath; + p.normalizeQuery = function(build) { + if (typeof this._parts.query === 'string') { + if (!this._parts.query.length) { + this._parts.query = null; + } else { + this.query(URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace)); + } + + this.build(!build); + } + + return this; + }; + p.normalizeFragment = function(build) { + if (!this._parts.fragment) { + this._parts.fragment = null; + this.build(!build); + } + + return this; + }; + p.normalizeSearch = p.normalizeQuery; + p.normalizeHash = p.normalizeFragment; + + p.iso8859 = function() { + // expect unicode input, iso8859 output + var e = URI.encode; + var d = URI.decode; + + URI.encode = escape; + URI.decode = decodeURIComponent; + try { + this.normalize(); + } finally { + URI.encode = e; + URI.decode = d; + } + return this; + }; + + p.unicode = function() { + // expect iso8859 input, unicode output + var e = URI.encode; + var d = URI.decode; + + URI.encode = strictEncodeURIComponent; + URI.decode = unescape; + try { + this.normalize(); + } finally { + URI.encode = e; + URI.decode = d; + } + return this; + }; + + p.readable = function() { + var uri = this.clone(); + // removing username, password, because they shouldn't be displayed according to RFC 3986 + uri.username('').password('').normalize(); + var t = ''; + if (uri._parts.protocol) { + t += uri._parts.protocol + '://'; + } + + if (uri._parts.hostname) { + if (uri.is('punycode') && punycode) { + t += punycode.toUnicode(uri._parts.hostname); + if (uri._parts.port) { + t += ':' + uri._parts.port; + } + } else { + t += uri.host(); + } + } + + if (uri._parts.hostname && uri._parts.path && uri._parts.path.charAt(0) !== '/') { + t += '/'; + } + + t += uri.path(true); + if (uri._parts.query) { + var q = ''; + for (var i = 0, qp = uri._parts.query.split('&'), l = qp.length; i < l; i++) { + var kv = (qp[i] || '').split('='); + q += '&' + URI.decodeQuery(kv[0], this._parts.escapeQuerySpace) + .replace(/&/g, '%26'); + + if (kv[1] !== undefined) { + q += '=' + URI.decodeQuery(kv[1], this._parts.escapeQuerySpace) + .replace(/&/g, '%26'); + } + } + t += '?' + q.substring(1); + } + + t += URI.decodeQuery(uri.hash(), true); + return t; + }; + + // resolving relative and absolute URLs + p.absoluteTo = function(base) { + var resolved = this.clone(); + var properties = ['protocol', 'username', 'password', 'hostname', 'port']; + var basedir, i, p; + + if (this._parts.urn) { + throw new Error('URNs do not have any generally defined hierarchical components'); + } + + if (!(base instanceof URI)) { + base = new URI(base); + } + + if (!resolved._parts.protocol) { + resolved._parts.protocol = base._parts.protocol; + } + + if (this._parts.hostname) { + return resolved; + } + + for (i = 0; (p = properties[i]); i++) { + resolved._parts[p] = base._parts[p]; + } + + if (!resolved._parts.path) { + resolved._parts.path = base._parts.path; + if (!resolved._parts.query) { + resolved._parts.query = base._parts.query; + } + } else if (resolved._parts.path.substring(-2) === '..') { + resolved._parts.path += '/'; + } + + if (resolved.path().charAt(0) !== '/') { + basedir = base.directory(); + basedir = basedir ? basedir : base.path().indexOf('/') === 0 ? '/' : ''; + resolved._parts.path = (basedir ? (basedir + '/') : '') + resolved._parts.path; + resolved.normalizePath(); + } + + resolved.build(); + return resolved; + }; + p.relativeTo = function(base) { + var relative = this.clone().normalize(); + var relativeParts, baseParts, common, relativePath, basePath; + + if (relative._parts.urn) { + throw new Error('URNs do not have any generally defined hierarchical components'); + } + + base = new URI(base).normalize(); + relativeParts = relative._parts; + baseParts = base._parts; + relativePath = relative.path(); + basePath = base.path(); + + if (relativePath.charAt(0) !== '/') { + throw new Error('URI is already relative'); + } + + if (basePath.charAt(0) !== '/') { + throw new Error('Cannot calculate a URI relative to another relative URI'); + } + + if (relativeParts.protocol === baseParts.protocol) { + relativeParts.protocol = null; + } + + if (relativeParts.username !== baseParts.username || relativeParts.password !== baseParts.password) { + return relative.build(); + } + + if (relativeParts.protocol !== null || relativeParts.username !== null || relativeParts.password !== null) { + return relative.build(); + } + + if (relativeParts.hostname === baseParts.hostname && relativeParts.port === baseParts.port) { + relativeParts.hostname = null; + relativeParts.port = null; + } else { + return relative.build(); + } + + if (relativePath === basePath) { + relativeParts.path = ''; + return relative.build(); + } + + // determine common sub path + common = URI.commonPath(relativePath, basePath); + + // If the paths have nothing in common, return a relative URL with the absolute path. + if (!common) { + return relative.build(); + } + + var parents = baseParts.path + .substring(common.length) + .replace(/[^\/]*$/, '') + .replace(/.*?\//g, '../'); + + relativeParts.path = (parents + relativeParts.path.substring(common.length)) || './'; + + return relative.build(); + }; + + // comparing URIs + p.equals = function(uri) { + var one = this.clone(); + var two = new URI(uri); + var one_map = {}; + var two_map = {}; + var checked = {}; + var one_query, two_query, key; + + one.normalize(); + two.normalize(); + + // exact match + if (one.toString() === two.toString()) { + return true; + } + + // extract query string + one_query = one.query(); + two_query = two.query(); + one.query(''); + two.query(''); + + // definitely not equal if not even non-query parts match + if (one.toString() !== two.toString()) { + return false; + } + + // query parameters have the same length, even if they're permuted + if (one_query.length !== two_query.length) { + return false; + } + + one_map = URI.parseQuery(one_query, this._parts.escapeQuerySpace); + two_map = URI.parseQuery(two_query, this._parts.escapeQuerySpace); + + for (key in one_map) { + if (hasOwn.call(one_map, key)) { + if (!isArray(one_map[key])) { + if (one_map[key] !== two_map[key]) { + return false; + } + } else if (!arraysEqual(one_map[key], two_map[key])) { + return false; + } + + checked[key] = true; + } + } + + for (key in two_map) { + if (hasOwn.call(two_map, key)) { + if (!checked[key]) { + // two contains a parameter not present in one + return false; + } + } + } + + return true; + }; + + // state + p.duplicateQueryParameters = function(v) { + this._parts.duplicateQueryParameters = !!v; + return this; + }; + + p.escapeQuerySpace = function(v) { + this._parts.escapeQuerySpace = !!v; + return this; + }; + + return URI; +})); + +},{"./IPv6":1,"./SecondLevelDomains":2,"./punycode":4}],4:[function(require,module,exports){ +(function (global){ +/*! http://mths.be/punycode v1.2.3 by @mathias */ +;(function(root) { + + /** Detect free variables */ + var freeExports = typeof exports == 'object' && exports; + var freeModule = typeof module == 'object' && module && + module.exports == freeExports && module; + var freeGlobal = typeof global == 'object' && global; + if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) { + root = freeGlobal; + } + + /** + * The `punycode` object. + * @name punycode + * @type Object + */ + var punycode, + + /** Highest positive signed 32-bit float value */ + maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1 + + /** Bootstring parameters */ + base = 36, + tMin = 1, + tMax = 26, + skew = 38, + damp = 700, + initialBias = 72, + initialN = 128, // 0x80 + delimiter = '-', // '\x2D' + + /** Regular expressions */ + regexPunycode = /^xn--/, + regexNonASCII = /[^ -~]/, // unprintable ASCII chars + non-ASCII chars + regexSeparators = /\x2E|\u3002|\uFF0E|\uFF61/g, // RFC 3490 separators + + /** Error messages */ + errors = { + 'overflow': 'Overflow: input needs wider integers to process', + 'not-basic': 'Illegal input >= 0x80 (not a basic code point)', + 'invalid-input': 'Invalid input' + }, + + /** Convenience shortcuts */ + baseMinusTMin = base - tMin, + floor = Math.floor, + stringFromCharCode = String.fromCharCode, + + /** Temporary variable */ + key; + + /*--------------------------------------------------------------------------*/ + + /** + * A generic error utility function. + * @private + * @param {String} type The error type. + * @returns {Error} Throws a `RangeError` with the applicable error message. + */ + function error(type) { + throw RangeError(errors[type]); + } + + /** + * A generic `Array#map` utility function. + * @private + * @param {Array} array The array to iterate over. + * @param {Function} callback The function that gets called for every array + * item. + * @returns {Array} A new array of values returned by the callback function. + */ + function map(array, fn) { + var length = array.length; + while (length--) { + array[length] = fn(array[length]); + } + return array; + } + + /** + * A simple `Array#map`-like wrapper to work with domain name strings. + * @private + * @param {String} domain The domain name. + * @param {Function} callback The function that gets called for every + * character. + * @returns {Array} A new string of characters returned by the callback + * function. + */ + function mapDomain(string, fn) { + return map(string.split(regexSeparators), fn).join('.'); + } + + /** + * Creates an array containing the numeric code points of each Unicode + * character in the string. While JavaScript uses UCS-2 internally, + * this function will convert a pair of surrogate halves (each of which + * UCS-2 exposes as separate characters) into a single code point, + * matching UTF-16. + * @see `punycode.ucs2.encode` + * @see <http://mathiasbynens.be/notes/javascript-encoding> + * @memberOf punycode.ucs2 + * @name decode + * @param {String} string The Unicode input string (UCS-2). + * @returns {Array} The new array of code points. + */ + function ucs2decode(string) { + var output = [], + counter = 0, + length = string.length, + value, + extra; + while (counter < length) { + value = string.charCodeAt(counter++); + if (value >= 0xD800 && value <= 0xDBFF && counter < length) { + // high surrogate, and there is a next character + extra = string.charCodeAt(counter++); + if ((extra & 0xFC00) == 0xDC00) { // low surrogate + output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); + } else { + // unmatched surrogate; only append this code unit, in case the next + // code unit is the high surrogate of a surrogate pair + output.push(value); + counter--; + } + } else { + output.push(value); + } + } + return output; + } + + /** + * Creates a string based on an array of numeric code points. + * @see `punycode.ucs2.decode` + * @memberOf punycode.ucs2 + * @name encode + * @param {Array} codePoints The array of numeric code points. + * @returns {String} The new Unicode string (UCS-2). + */ + function ucs2encode(array) { + return map(array, function(value) { + var output = ''; + if (value > 0xFFFF) { + value -= 0x10000; + output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); + value = 0xDC00 | value & 0x3FF; + } + output += stringFromCharCode(value); + return output; + }).join(''); + } + + /** + * Converts a basic code point into a digit/integer. + * @see `digitToBasic()` + * @private + * @param {Number} codePoint The basic numeric code point value. + * @returns {Number} The numeric value of a basic code point (for use in + * representing integers) in the range `0` to `base - 1`, or `base` if + * the code point does not represent a value. + */ + function basicToDigit(codePoint) { + if (codePoint - 48 < 10) { + return codePoint - 22; + } + if (codePoint - 65 < 26) { + return codePoint - 65; + } + if (codePoint - 97 < 26) { + return codePoint - 97; + } + return base; + } + + /** + * Converts a digit/integer into a basic code point. + * @see `basicToDigit()` + * @private + * @param {Number} digit The numeric value of a basic code point. + * @returns {Number} The basic code point whose value (when used for + * representing integers) is `digit`, which needs to be in the range + * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is + * used; else, the lowercase form is used. The behavior is undefined + * if `flag` is non-zero and `digit` has no uppercase form. + */ + function digitToBasic(digit, flag) { + // 0..25 map to ASCII a..z or A..Z + // 26..35 map to ASCII 0..9 + return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); + } + + /** + * Bias adaptation function as per section 3.4 of RFC 3492. + * http://tools.ietf.org/html/rfc3492#section-3.4 + * @private + */ + function adapt(delta, numPoints, firstTime) { + var k = 0; + delta = firstTime ? floor(delta / damp) : delta >> 1; + delta += floor(delta / numPoints); + for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) { + delta = floor(delta / baseMinusTMin); + } + return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); + } + + /** + * Converts a Punycode string of ASCII-only symbols to a string of Unicode + * symbols. + * @memberOf punycode + * @param {String} input The Punycode string of ASCII-only symbols. + * @returns {String} The resulting string of Unicode symbols. + */ + function decode(input) { + // Don't use UCS-2 + var output = [], + inputLength = input.length, + out, + i = 0, + n = initialN, + bias = initialBias, + basic, + j, + index, + oldi, + w, + k, + digit, + t, + length, + /** Cached calculation results */ + baseMinusT; + + // Handle the basic code points: let `basic` be the number of input code + // points before the last delimiter, or `0` if there is none, then copy + // the first basic code points to the output. + + basic = input.lastIndexOf(delimiter); + if (basic < 0) { + basic = 0; + } + + for (j = 0; j < basic; ++j) { + // if it's not a basic code point + if (input.charCodeAt(j) >= 0x80) { + error('not-basic'); + } + output.push(input.charCodeAt(j)); + } + + // Main decoding loop: start just after the last delimiter if any basic code + // points were copied; start at the beginning otherwise. + + for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { + + // `index` is the index of the next character to be consumed. + // Decode a generalized variable-length integer into `delta`, + // which gets added to `i`. The overflow checking is easier + // if we increase `i` as we go, then subtract off its starting + // value at the end to obtain `delta`. + for (oldi = i, w = 1, k = base; /* no condition */; k += base) { + + if (index >= inputLength) { + error('invalid-input'); + } + + digit = basicToDigit(input.charCodeAt(index++)); + + if (digit >= base || digit > floor((maxInt - i) / w)) { + error('overflow'); + } + + i += digit * w; + t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + + if (digit < t) { + break; + } + + baseMinusT = base - t; + if (w > floor(maxInt / baseMinusT)) { + error('overflow'); + } + + w *= baseMinusT; + + } + + out = output.length + 1; + bias = adapt(i - oldi, out, oldi == 0); + + // `i` was supposed to wrap around from `out` to `0`, + // incrementing `n` each time, so we'll fix that now: + if (floor(i / out) > maxInt - n) { + error('overflow'); + } + + n += floor(i / out); + i %= out; + + // Insert `n` at position `i` of the output + output.splice(i++, 0, n); + + } + + return ucs2encode(output); + } + + /** + * Converts a string of Unicode symbols to a Punycode string of ASCII-only + * symbols. + * @memberOf punycode + * @param {String} input The string of Unicode symbols. + * @returns {String} The resulting Punycode string of ASCII-only symbols. + */ + function encode(input) { + var n, + delta, + handledCPCount, + basicLength, + bias, + j, + m, + q, + k, + t, + currentValue, + output = [], + /** `inputLength` will hold the number of code points in `input`. */ + inputLength, + /** Cached calculation results */ + handledCPCountPlusOne, + baseMinusT, + qMinusT; + + // Convert the input in UCS-2 to Unicode + input = ucs2decode(input); + + // Cache the length + inputLength = input.length; + + // Initialize the state + n = initialN; + delta = 0; + bias = initialBias; + + // Handle the basic code points + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue < 0x80) { + output.push(stringFromCharCode(currentValue)); + } + } + + handledCPCount = basicLength = output.length; + + // `handledCPCount` is the number of code points that have been handled; + // `basicLength` is the number of basic code points. + + // Finish the basic string - if it is not empty - with a delimiter + if (basicLength) { + output.push(delimiter); + } + + // Main encoding loop: + while (handledCPCount < inputLength) { + + // All non-basic code points < n have been handled already. Find the next + // larger one: + for (m = maxInt, j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue >= n && currentValue < m) { + m = currentValue; + } + } + + // Increase `delta` enough to advance the decoder's <n,i> state to <m,0>, + // but guard against overflow + handledCPCountPlusOne = handledCPCount + 1; + if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { + error('overflow'); + } + + delta += (m - n) * handledCPCountPlusOne; + n = m; + + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + + if (currentValue < n && ++delta > maxInt) { + error('overflow'); + } + + if (currentValue == n) { + // Represent delta as a generalized variable-length integer + for (q = delta, k = base; /* no condition */; k += base) { + t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + if (q < t) { + break; + } + qMinusT = q - t; + baseMinusT = base - t; + output.push( + stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) + ); + q = floor(qMinusT / baseMinusT); + } + + output.push(stringFromCharCode(digitToBasic(q, 0))); + bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); + delta = 0; + ++handledCPCount; + } + } + + ++delta; + ++n; + + } + return output.join(''); + } + + /** + * Converts a Punycode string representing a domain name to Unicode. Only the + * Punycoded parts of the domain name will be converted, i.e. it doesn't + * matter if you call it on a string that has already been converted to + * Unicode. + * @memberOf punycode + * @param {String} domain The Punycode domain name to convert to Unicode. + * @returns {String} The Unicode representation of the given Punycode + * string. + */ + function toUnicode(domain) { + return mapDomain(domain, function(string) { + return regexPunycode.test(string) + ? decode(string.slice(4).toLowerCase()) + : string; + }); + } + + /** + * Converts a Unicode string representing a domain name to Punycode. Only the + * non-ASCII parts of the domain name will be converted, i.e. it doesn't + * matter if you call it with a domain that's already in ASCII. + * @memberOf punycode + * @param {String} domain The domain name to convert, as a Unicode string. + * @returns {String} The Punycode representation of the given domain name. + */ + function toASCII(domain) { + return mapDomain(domain, function(string) { + return regexNonASCII.test(string) + ? 'xn--' + encode(string) + : string; + }); + } + + /*--------------------------------------------------------------------------*/ + + /** Define the public API */ + punycode = { + /** + * A string representing the current Punycode.js version number. + * @memberOf punycode + * @type String + */ + 'version': '1.2.3', + /** + * An object of methods to convert from JavaScript's internal character + * representation (UCS-2) to Unicode code points, and back. + * @see <http://mathiasbynens.be/notes/javascript-encoding> + * @memberOf punycode + * @type Object + */ + 'ucs2': { + 'decode': ucs2decode, + 'encode': ucs2encode + }, + 'decode': decode, + 'encode': encode, + 'toASCII': toASCII, + 'toUnicode': toUnicode + }; + + /** Expose `punycode` */ + // Some AMD build optimizers, like r.js, check for specific condition patterns + // like the following: + if ( + typeof define == 'function' && + typeof define.amd == 'object' && + define.amd + ) { + define(function() { + return punycode; + }); + } else if (freeExports && !freeExports.nodeType) { + if (freeModule) { // in Node.js or RingoJS v0.8.0+ + freeModule.exports = punycode; + } else { // in Narwhal or RingoJS v0.7.0- + for (key in punycode) { + punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]); + } + } + } else { // in Rhino or a web browser + root.punycode = punycode; + } + +}(this)); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{}],5:[function(require,module,exports){ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _helpersEvent = require('./helpers/event'); + +var _helpersEvent2 = _interopRequireDefault(_helpersEvent); + +var _helpersMessageEvent = require('./helpers/message-event'); + +var _helpersMessageEvent2 = _interopRequireDefault(_helpersMessageEvent); + +var _helpersCloseEvent = require('./helpers/close-event'); + +var _helpersCloseEvent2 = _interopRequireDefault(_helpersCloseEvent); + +/* +* Creates an Event object and extends it to allow full modification of +* its properties. +* +* @param {object} config - within config you will need to pass type and optionally target +*/ +function createEvent(config) { + var type = config.type; + var target = config.target; + + var eventObject = new _helpersEvent2['default'](type); + + if (target) { + eventObject.target = target; + eventObject.srcElement = target; + eventObject.currentTarget = target; + } + + return eventObject; +} + +/* +* Creates a MessageEvent object and extends it to allow full modification of +* its properties. +* +* @param {object} config - within config you will need to pass type, origin, data and optionally target +*/ +function createMessageEvent(config) { + var type = config.type; + var origin = config.origin; + var data = config.data; + var target = config.target; + + var messageEvent = new _helpersMessageEvent2['default'](type, { + data: data, + origin: origin + }); + + if (target) { + messageEvent.target = target; + messageEvent.srcElement = target; + messageEvent.currentTarget = target; + } + + return messageEvent; +} + +/* +* Creates a CloseEvent object and extends it to allow full modification of +* its properties. +* +* @param {object} config - within config you will need to pass type and optionally target, code, and reason +*/ +function createCloseEvent(config) { + var code = config.code; + var reason = config.reason; + var type = config.type; + var target = config.target; + var wasClean = config.wasClean; + + if (!wasClean) { + wasClean = code === 1000; + } + + var closeEvent = new _helpersCloseEvent2['default'](type, { + code: code, + reason: reason, + wasClean: wasClean + }); + + if (target) { + closeEvent.target = target; + closeEvent.srcElement = target; + closeEvent.currentTarget = target; + } + + return closeEvent; +} + +exports.createEvent = createEvent; +exports.createMessageEvent = createMessageEvent; +exports.createCloseEvent = createCloseEvent; +},{"./helpers/close-event":9,"./helpers/event":12,"./helpers/message-event":13}],6:[function(require,module,exports){ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +var _helpersArrayHelpers = require('./helpers/array-helpers'); + +/* +* EventTarget is an interface implemented by objects that can +* receive events and may have listeners for them. +* +* https://developer.mozilla.org/en-US/docs/Web/API/EventTarget +*/ + +var EventTarget = (function () { + function EventTarget() { + _classCallCheck(this, EventTarget); + + this.listeners = {}; + } + + /* + * Ties a listener function to a event type which can later be invoked via the + * dispatchEvent method. + * + * @param {string} type - the type of event (ie: 'open', 'message', etc.) + * @param {function} listener - the callback function to invoke whenever a event is dispatched matching the given type + * @param {boolean} useCapture - N/A TODO: implement useCapture functionality + */ + + _createClass(EventTarget, [{ + key: 'addEventListener', + value: function addEventListener(type, listener /* , useCapture */) { + if (typeof listener === 'function') { + if (!Array.isArray(this.listeners[type])) { + this.listeners[type] = []; + } + + // Only add the same function once + if ((0, _helpersArrayHelpers.filter)(this.listeners[type], function (item) { + return item === listener; + }).length === 0) { + this.listeners[type].push(listener); + } + } + } + + /* + * Removes the listener so it will no longer be invoked via the dispatchEvent method. + * + * @param {string} type - the type of event (ie: 'open', 'message', etc.) + * @param {function} listener - the callback function to invoke whenever a event is dispatched matching the given type + * @param {boolean} useCapture - N/A TODO: implement useCapture functionality + */ + }, { + key: 'removeEventListener', + value: function removeEventListener(type, removingListener /* , useCapture */) { + var arrayOfListeners = this.listeners[type]; + this.listeners[type] = (0, _helpersArrayHelpers.reject)(arrayOfListeners, function (listener) { + return listener === removingListener; + }); + } + + /* + * Invokes all listener functions that are listening to the given event.type property. Each + * listener will be passed the event as the first argument. + * + * @param {object} event - event object which will be passed to all listeners of the event.type property + */ + }, { + key: 'dispatchEvent', + value: function dispatchEvent(event) { + var _this = this; + + for (var _len = arguments.length, customArguments = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + customArguments[_key - 1] = arguments[_key]; + } + + var eventName = event.type; + var listeners = this.listeners[eventName]; + + if (!Array.isArray(listeners)) { + return false; + } + + listeners.forEach(function (listener) { + if (customArguments.length > 0) { + listener.apply(_this, customArguments); + } else { + listener.call(_this, event); + } + }); + } + }]); + + return EventTarget; +})(); + +exports['default'] = EventTarget; +module.exports = exports['default']; +},{"./helpers/array-helpers":7}],7:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.reject = reject; +exports.filter = filter; + +function reject(array, callback) { + var results = []; + array.forEach(function (itemInArray) { + if (!callback(itemInArray)) { + results.push(itemInArray); + } + }); + + return results; +} + +function filter(array, callback) { + var results = []; + array.forEach(function (itemInArray) { + if (callback(itemInArray)) { + results.push(itemInArray); + } + }); + + return results; +} +},{}],8:[function(require,module,exports){ +/* +* https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent +*/ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +var codes = { + CLOSE_NORMAL: 1000, + CLOSE_GOING_AWAY: 1001, + CLOSE_PROTOCOL_ERROR: 1002, + CLOSE_UNSUPPORTED: 1003, + CLOSE_NO_STATUS: 1005, + CLOSE_ABNORMAL: 1006, + CLOSE_TOO_LARGE: 1009 +}; + +exports["default"] = codes; +module.exports = exports["default"]; +},{}],9:[function(require,module,exports){ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _get = function get(_x2, _x3, _x4) { var _again = true; _function: while (_again) { var object = _x2, property = _x3, receiver = _x4; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x2 = parent; _x3 = property; _x4 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _eventPrototype = require('./event-prototype'); + +var _eventPrototype2 = _interopRequireDefault(_eventPrototype); + +var CloseEvent = (function (_EventPrototype) { + _inherits(CloseEvent, _EventPrototype); + + function CloseEvent(type) { + var eventInitConfig = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + _classCallCheck(this, CloseEvent); + + _get(Object.getPrototypeOf(CloseEvent.prototype), 'constructor', this).call(this); + + if (!type) { + throw new TypeError('Failed to construct \'CloseEvent\': 1 argument required, but only 0 present.'); + } + + if (typeof eventInitConfig !== 'object') { + throw new TypeError('Failed to construct \'CloseEvent\': parameter 2 (\'eventInitDict\') is not an object'); + } + + var bubbles = eventInitConfig.bubbles; + var cancelable = eventInitConfig.cancelable; + var code = eventInitConfig.code; + var reason = eventInitConfig.reason; + var wasClean = eventInitConfig.wasClean; + + this.type = String(type); + this.timeStamp = Date.now(); + this.target = null; + this.srcElement = null; + this.returnValue = true; + this.isTrusted = false; + this.eventPhase = 0; + this.defaultPrevented = false; + this.currentTarget = null; + this.cancelable = cancelable ? Boolean(cancelable) : false; + this.canncelBubble = false; + this.bubbles = bubbles ? Boolean(bubbles) : false; + this.code = typeof code === 'number' ? Number(code) : 0; + this.reason = reason ? String(reason) : ''; + this.wasClean = wasClean ? Boolean(wasClean) : false; + } + + return CloseEvent; +})(_eventPrototype2['default']); + +exports['default'] = CloseEvent; +module.exports = exports['default']; +},{"./event-prototype":11}],10:[function(require,module,exports){ +/* +* This delay allows the thread to finish assigning its on* methods +* before invoking the delay callback. This is purely a timing hack. +* http://geekabyte.blogspot.com/2014/01/javascript-effect-of-setting-settimeout.html +* +* @param {callback: function} the callback which will be invoked after the timeout +* @parma {context: object} the context in which to invoke the function +*/ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +function delay(callback, context) { + setTimeout(function timeout(timeoutContext) { + callback.call(timeoutContext); + }, 4, context); +} + +exports["default"] = delay; +module.exports = exports["default"]; +},{}],11:[function(require,module,exports){ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +var EventPrototype = (function () { + function EventPrototype() { + _classCallCheck(this, EventPrototype); + } + + _createClass(EventPrototype, [{ + key: 'stopPropagation', + + // Noops + value: function stopPropagation() {} + }, { + key: 'stopImmediatePropagation', + value: function stopImmediatePropagation() {} + + // if no arguments are passed then the type is set to "undefined" on + // chrome and safari. + }, { + key: 'initEvent', + value: function initEvent() { + var type = arguments.length <= 0 || arguments[0] === undefined ? 'undefined' : arguments[0]; + var bubbles = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; + var cancelable = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; + + Object.assign(this, { + type: String(type), + bubbles: Boolean(bubbles), + cancelable: Boolean(cancelable) + }); + } + }]); + + return EventPrototype; +})(); + +exports['default'] = EventPrototype; +module.exports = exports['default']; +},{}],12:[function(require,module,exports){ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _get = function get(_x2, _x3, _x4) { var _again = true; _function: while (_again) { var object = _x2, property = _x3, receiver = _x4; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x2 = parent; _x3 = property; _x4 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _eventPrototype = require('./event-prototype'); + +var _eventPrototype2 = _interopRequireDefault(_eventPrototype); + +var Event = (function (_EventPrototype) { + _inherits(Event, _EventPrototype); + + function Event(type) { + var eventInitConfig = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + _classCallCheck(this, Event); + + _get(Object.getPrototypeOf(Event.prototype), 'constructor', this).call(this); + + if (!type) { + throw new TypeError('Failed to construct \'Event\': 1 argument required, but only 0 present.'); + } + + if (typeof eventInitConfig !== 'object') { + throw new TypeError('Failed to construct \'Event\': parameter 2 (\'eventInitDict\') is not an object'); + } + + var bubbles = eventInitConfig.bubbles; + var cancelable = eventInitConfig.cancelable; + + this.type = String(type); + this.timeStamp = Date.now(); + this.target = null; + this.srcElement = null; + this.returnValue = true; + this.isTrusted = false; + this.eventPhase = 0; + this.defaultPrevented = false; + this.currentTarget = null; + this.cancelable = cancelable ? Boolean(cancelable) : false; + this.canncelBubble = false; + this.bubbles = bubbles ? Boolean(bubbles) : false; + } + + return Event; +})(_eventPrototype2['default']); + +exports['default'] = Event; +module.exports = exports['default']; +},{"./event-prototype":11}],13:[function(require,module,exports){ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _get = function get(_x2, _x3, _x4) { var _again = true; _function: while (_again) { var object = _x2, property = _x3, receiver = _x4; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x2 = parent; _x3 = property; _x4 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _eventPrototype = require('./event-prototype'); + +var _eventPrototype2 = _interopRequireDefault(_eventPrototype); + +var MessageEvent = (function (_EventPrototype) { + _inherits(MessageEvent, _EventPrototype); + + function MessageEvent(type) { + var eventInitConfig = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + _classCallCheck(this, MessageEvent); + + _get(Object.getPrototypeOf(MessageEvent.prototype), 'constructor', this).call(this); + + if (!type) { + throw new TypeError('Failed to construct \'MessageEvent\': 1 argument required, but only 0 present.'); + } + + if (typeof eventInitConfig !== 'object') { + throw new TypeError('Failed to construct \'MessageEvent\': parameter 2 (\'eventInitDict\') is not an object'); + } + + var bubbles = eventInitConfig.bubbles; + var cancelable = eventInitConfig.cancelable; + var data = eventInitConfig.data; + var origin = eventInitConfig.origin; + var lastEventId = eventInitConfig.lastEventId; + var ports = eventInitConfig.ports; + + this.type = String(type); + this.timeStamp = Date.now(); + this.target = null; + this.srcElement = null; + this.returnValue = true; + this.isTrusted = false; + this.eventPhase = 0; + this.defaultPrevented = false; + this.currentTarget = null; + this.cancelable = cancelable ? Boolean(cancelable) : false; + this.canncelBubble = false; + this.bubbles = bubbles ? Boolean(bubbles) : false; + this.origin = origin ? String(origin) : ''; + this.ports = typeof ports === 'undefined' ? null : ports; + this.data = typeof data === 'undefined' ? null : data; + this.lastEventId = lastEventId ? String(lastEventId) : ''; + } + + return MessageEvent; +})(_eventPrototype2['default']); + +exports['default'] = MessageEvent; +module.exports = exports['default']; +},{"./event-prototype":11}],14:[function(require,module,exports){ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _server = require('./server'); + +var _server2 = _interopRequireDefault(_server); + +var _socketIo = require('./socket-io'); + +var _socketIo2 = _interopRequireDefault(_socketIo); + +var _websocket = require('./websocket'); + +var _websocket2 = _interopRequireDefault(_websocket); + +if (typeof window !== 'undefined') { + window.MockServer = _server2['default']; + window.MockWebSocket = _websocket2['default']; + window.MockSocketIO = _socketIo2['default']; +} + +var Server = _server2['default']; +exports.Server = Server; +var WebSocket = _websocket2['default']; +exports.WebSocket = WebSocket; +var SocketIO = _socketIo2['default']; +exports.SocketIO = SocketIO; +},{"./server":16,"./socket-io":17,"./websocket":18}],15:[function(require,module,exports){ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +var _helpersArrayHelpers = require('./helpers/array-helpers'); + +/* +* The network bridge is a way for the mock websocket object to 'communicate' with +* all avalible servers. This is a singleton object so it is important that you +* clean up urlMap whenever you are finished. +*/ + +var NetworkBridge = (function () { + function NetworkBridge() { + _classCallCheck(this, NetworkBridge); + + this.urlMap = {}; + } + + /* + * Attaches a websocket object to the urlMap hash so that it can find the server + * it is connected to and the server in turn can find it. + * + * @param {object} websocket - websocket object to add to the urlMap hash + * @param {string} url + */ + + _createClass(NetworkBridge, [{ + key: 'attachWebSocket', + value: function attachWebSocket(websocket, url) { + var connectionLookup = this.urlMap[url]; + + if (connectionLookup && connectionLookup.server && connectionLookup.websockets.indexOf(websocket) === -1) { + connectionLookup.websockets.push(websocket); + return connectionLookup.server; + } + } + + /* + * Attaches a websocket to a room + */ + }, { + key: 'addMembershipToRoom', + value: function addMembershipToRoom(websocket, room) { + var connectionLookup = this.urlMap[websocket.url]; + + if (connectionLookup && connectionLookup.server && connectionLookup.websockets.indexOf(websocket) !== -1) { + if (!connectionLookup.roomMemberships[room]) { + connectionLookup.roomMemberships[room] = []; + } + + connectionLookup.roomMemberships[room].push(websocket); + } + } + + /* + * Attaches a server object to the urlMap hash so that it can find a websockets + * which are connected to it and so that websockets can in turn can find it. + * + * @param {object} server - server object to add to the urlMap hash + * @param {string} url + */ + }, { + key: 'attachServer', + value: function attachServer(server, url) { + var connectionLookup = this.urlMap[url]; + + if (!connectionLookup) { + this.urlMap[url] = { + server: server, + websockets: [], + roomMemberships: {} + }; + + return server; + } + } + + /* + * Finds the server which is 'running' on the given url. + * + * @param {string} url - the url to use to find which server is running on it + */ + }, { + key: 'serverLookup', + value: function serverLookup(url) { + var connectionLookup = this.urlMap[url]; + + if (connectionLookup) { + return connectionLookup.server; + } + } + + /* + * Finds all websockets which is 'listening' on the given url. + * + * @param {string} url - the url to use to find all websockets which are associated with it + * @param {string} room - if a room is provided, will only return sockets in this room + */ + }, { + key: 'websocketsLookup', + value: function websocketsLookup(url, room) { + var connectionLookup = this.urlMap[url]; + + if (!connectionLookup) { + return []; + } + + if (room) { + var members = connectionLookup.roomMemberships[room]; + return members ? members : []; + } + + return connectionLookup.websockets; + } + + /* + * Removes the entry associated with the url. + * + * @param {string} url + */ + }, { + key: 'removeServer', + value: function removeServer(url) { + delete this.urlMap[url]; + } + + /* + * Removes the individual websocket from the map of associated websockets. + * + * @param {object} websocket - websocket object to remove from the url map + * @param {string} url + */ + }, { + key: 'removeWebSocket', + value: function removeWebSocket(websocket, url) { + var connectionLookup = this.urlMap[url]; + + if (connectionLookup) { + connectionLookup.websockets = (0, _helpersArrayHelpers.reject)(connectionLookup.websockets, function (socket) { + return socket === websocket; + }); + } + } + + /* + * Removes a websocket from a room + */ + }, { + key: 'removeMembershipFromRoom', + value: function removeMembershipFromRoom(websocket, room) { + var connectionLookup = this.urlMap[websocket.url]; + var memberships = connectionLookup.roomMemberships[room]; + + if (connectionLookup && memberships !== null) { + connectionLookup.roomMemberships[room] = (0, _helpersArrayHelpers.reject)(memberships, function (socket) { + return socket === websocket; + }); + } + } + }]); + + return NetworkBridge; +})(); + +exports['default'] = new NetworkBridge(); +// Note: this is a singleton +module.exports = exports['default']; +},{"./helpers/array-helpers":7}],16:[function(require,module,exports){ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +var _get = function get(_x4, _x5, _x6) { var _again = true; _function: while (_again) { var object = _x4, property = _x5, receiver = _x6; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x4 = parent; _x5 = property; _x6 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _urijs = require('urijs'); + +var _urijs2 = _interopRequireDefault(_urijs); + +var _websocket = require('./websocket'); + +var _websocket2 = _interopRequireDefault(_websocket); + +var _eventTarget = require('./event-target'); + +var _eventTarget2 = _interopRequireDefault(_eventTarget); + +var _networkBridge = require('./network-bridge'); + +var _networkBridge2 = _interopRequireDefault(_networkBridge); + +var _helpersCloseCodes = require('./helpers/close-codes'); + +var _helpersCloseCodes2 = _interopRequireDefault(_helpersCloseCodes); + +var _eventFactory = require('./event-factory'); + +/* +* https://github.com/websockets/ws#server-example +*/ + +var Server = (function (_EventTarget) { + _inherits(Server, _EventTarget); + + /* + * @param {string} url + */ + + function Server(url) { + _classCallCheck(this, Server); + + _get(Object.getPrototypeOf(Server.prototype), 'constructor', this).call(this); + this.url = (0, _urijs2['default'])(url).toString(); + var server = _networkBridge2['default'].attachServer(this, this.url); + + if (!server) { + this.dispatchEvent((0, _eventFactory.createEvent)({ type: 'error' })); + throw new Error('A mock server is already listening on this url'); + } + } + + /* + * Alternative constructor to support namespaces in socket.io + * + * http://socket.io/docs/rooms-and-namespaces/#custom-namespaces + */ + + /* + * This is the main function for the mock server to subscribe to the on events. + * + * ie: mockServer.on('connection', function() { console.log('a mock client connected'); }); + * + * @param {string} type - The event key to subscribe to. Valid keys are: connection, message, and close. + * @param {function} callback - The callback which should be called when a certain event is fired. + */ + + _createClass(Server, [{ + key: 'on', + value: function on(type, callback) { + this.addEventListener(type, callback); + } + + /* + * This send function will notify all mock clients via their onmessage callbacks that the server + * has a message for them. + * + * @param {*} data - Any javascript object which will be crafted into a MessageObject. + */ + }, { + key: 'send', + value: function send(data) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + this.emit('message', data, options); + } + + /* + * Sends a generic message event to all mock clients. + */ + }, { + key: 'emit', + value: function emit(event, data) { + var _this2 = this; + + var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; + var websockets = options.websockets; + + if (!websockets) { + websockets = _networkBridge2['default'].websocketsLookup(this.url); + } + + websockets.forEach(function (socket) { + socket.dispatchEvent((0, _eventFactory.createMessageEvent)({ + type: event, + data: data, + origin: _this2.url, + target: socket + })); + }); + } + + /* + * Closes the connection and triggers the onclose method of all listening + * websockets. After that it removes itself from the urlMap so another server + * could add itself to the url. + * + * @param {object} options + */ + }, { + key: 'close', + value: function close() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + var code = options.code; + var reason = options.reason; + var wasClean = options.wasClean; + + var listeners = _networkBridge2['default'].websocketsLookup(this.url); + + listeners.forEach(function (socket) { + socket.readyState = _websocket2['default'].CLOSE; + socket.dispatchEvent((0, _eventFactory.createCloseEvent)({ + type: 'close', + target: socket, + code: code || _helpersCloseCodes2['default'].CLOSE_NORMAL, + reason: reason || '', + wasClean: wasClean + })); + }); + + this.dispatchEvent((0, _eventFactory.createCloseEvent)({ type: 'close' }), this); + _networkBridge2['default'].removeServer(this.url); + } + + /* + * Returns an array of websockets which are listening to this server + */ + }, { + key: 'clients', + value: function clients() { + return _networkBridge2['default'].websocketsLookup(this.url); + } + + /* + * Prepares a method to submit an event to members of the room + * + * e.g. server.to('my-room').emit('hi!'); + */ + }, { + key: 'to', + value: function to(room) { + var _this = this; + var websockets = _networkBridge2['default'].websocketsLookup(this.url, room); + return { + emit: function emit(event, data) { + _this.emit(event, data, { websockets: websockets }); + } + }; + } + }]); + + return Server; +})(_eventTarget2['default']); + +Server.of = function of(url) { + return new Server(url); +}; + +exports['default'] = Server; +module.exports = exports['default']; +},{"./event-factory":5,"./event-target":6,"./helpers/close-codes":8,"./network-bridge":15,"./websocket":18,"urijs":3}],17:[function(require,module,exports){ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +var _get = function get(_x3, _x4, _x5) { var _again = true; _function: while (_again) { var object = _x3, property = _x4, receiver = _x5; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x3 = parent; _x4 = property; _x5 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _urijs = require('urijs'); + +var _urijs2 = _interopRequireDefault(_urijs); + +var _helpersDelay = require('./helpers/delay'); + +var _helpersDelay2 = _interopRequireDefault(_helpersDelay); + +var _eventTarget = require('./event-target'); + +var _eventTarget2 = _interopRequireDefault(_eventTarget); + +var _networkBridge = require('./network-bridge'); + +var _networkBridge2 = _interopRequireDefault(_networkBridge); + +var _helpersCloseCodes = require('./helpers/close-codes'); + +var _helpersCloseCodes2 = _interopRequireDefault(_helpersCloseCodes); + +var _eventFactory = require('./event-factory'); + +/* +* The socket-io class is designed to mimick the real API as closely as possible. +* +* http://socket.io/docs/ +*/ + +var SocketIO = (function (_EventTarget) { + _inherits(SocketIO, _EventTarget); + + /* + * @param {string} url + */ + + function SocketIO() { + var _this = this; + + var url = arguments.length <= 0 || arguments[0] === undefined ? 'socket.io' : arguments[0]; + var protocol = arguments.length <= 1 || arguments[1] === undefined ? '' : arguments[1]; + + _classCallCheck(this, SocketIO); + + _get(Object.getPrototypeOf(SocketIO.prototype), 'constructor', this).call(this); + + this.binaryType = 'blob'; + this.url = (0, _urijs2['default'])(url).toString(); + this.readyState = SocketIO.CONNECTING; + this.protocol = ''; + + if (typeof protocol === 'string') { + this.protocol = protocol; + } else if (Array.isArray(protocol) && protocol.length > 0) { + this.protocol = protocol[0]; + } + + var server = _networkBridge2['default'].attachWebSocket(this, this.url); + + /* + * Delay triggering the connection events so they can be defined in time. + */ + (0, _helpersDelay2['default'])(function delayCallback() { + if (server) { + this.readyState = SocketIO.OPEN; + server.dispatchEvent((0, _eventFactory.createEvent)({ type: 'connection' }), server, this); + server.dispatchEvent((0, _eventFactory.createEvent)({ type: 'connect' }), server, this); // alias + this.dispatchEvent((0, _eventFactory.createEvent)({ type: 'connect', target: this })); + } else { + this.readyState = SocketIO.CLOSED; + this.dispatchEvent((0, _eventFactory.createEvent)({ type: 'error', target: this })); + this.dispatchEvent((0, _eventFactory.createCloseEvent)({ + type: 'close', + target: this, + code: _helpersCloseCodes2['default'].CLOSE_NORMAL + })); + + console.error('Socket.io connection to \'' + this.url + '\' failed'); + } + }, this); + + /** + Add an aliased event listener for close / disconnect + */ + this.addEventListener('close', function (event) { + _this.dispatchEvent((0, _eventFactory.createCloseEvent)({ + type: 'disconnect', + target: event.target, + code: event.code + })); + }); + } + + /* + * Closes the SocketIO connection or connection attempt, if any. + * If the connection is already CLOSED, this method does nothing. + */ + + _createClass(SocketIO, [{ + key: 'close', + value: function close() { + if (this.readyState !== SocketIO.OPEN) { + return undefined; + } + + var server = _networkBridge2['default'].serverLookup(this.url); + _networkBridge2['default'].removeWebSocket(this, this.url); + + this.readyState = SocketIO.CLOSED; + this.dispatchEvent((0, _eventFactory.createCloseEvent)({ + type: 'close', + target: this, + code: _helpersCloseCodes2['default'].CLOSE_NORMAL + })); + + if (server) { + server.dispatchEvent((0, _eventFactory.createCloseEvent)({ + type: 'disconnect', + target: this, + code: _helpersCloseCodes2['default'].CLOSE_NORMAL + }), server); + } + } + + /* + * Alias for Socket#close + * + * https://github.com/socketio/socket.io-client/blob/master/lib/socket.js#L383 + */ + }, { + key: 'disconnect', + value: function disconnect() { + this.close(); + } + + /* + * Submits an event to the server with a payload + */ + }, { + key: 'emit', + value: function emit(event, data) { + if (this.readyState !== SocketIO.OPEN) { + throw new Error('SocketIO is already in CLOSING or CLOSED state'); + } + + var messageEvent = (0, _eventFactory.createMessageEvent)({ + type: event, + origin: this.url, + data: data + }); + + var server = _networkBridge2['default'].serverLookup(this.url); + + if (server) { + server.dispatchEvent(messageEvent, data); + } + } + + /* + * Submits a 'message' event to the server. + * + * Should behave exactly like WebSocket#send + * + * https://github.com/socketio/socket.io-client/blob/master/lib/socket.js#L113 + */ + }, { + key: 'send', + value: function send(data) { + this.emit('message', data); + } + + /* + * For registering events to be received from the server + */ + }, { + key: 'on', + value: function on(type, callback) { + this.addEventListener(type, callback); + } + + /* + * Join a room on a server + * + * http://socket.io/docs/rooms-and-namespaces/#joining-and-leaving + */ + }, { + key: 'join', + value: function join(room) { + _networkBridge2['default'].addMembershipToRoom(this, room); + } + + /* + * Get the websocket to leave the room + * + * http://socket.io/docs/rooms-and-namespaces/#joining-and-leaving + */ + }, { + key: 'leave', + value: function leave(room) { + _networkBridge2['default'].removeMembershipFromRoom(this, room); + } + + /* + * Invokes all listener functions that are listening to the given event.type property. Each + * listener will be passed the event as the first argument. + * + * @param {object} event - event object which will be passed to all listeners of the event.type property + */ + }, { + key: 'dispatchEvent', + value: function dispatchEvent(event) { + var _this2 = this; + + for (var _len = arguments.length, customArguments = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + customArguments[_key - 1] = arguments[_key]; + } + + var eventName = event.type; + var listeners = this.listeners[eventName]; + + if (!Array.isArray(listeners)) { + return false; + } + + listeners.forEach(function (listener) { + if (customArguments.length > 0) { + listener.apply(_this2, customArguments); + } else { + // Regular WebSockets expect a MessageEvent but Socketio.io just wants raw data + // payload instanceof MessageEvent works, but you can't isntance of NodeEvent + // for now we detect if the output has data defined on it + listener.call(_this2, event.data ? event.data : event); + } + }); + } + }]); + + return SocketIO; +})(_eventTarget2['default']); + +SocketIO.CONNECTING = 0; +SocketIO.OPEN = 1; +SocketIO.CLOSING = 2; +SocketIO.CLOSED = 3; + +/* +* Static constructor methods for the IO Socket +*/ +var IO = function ioConstructor(url) { + return new SocketIO(url); +}; + +/* +* Alias the raw IO() constructor +*/ +IO.connect = function ioConnect(url) { + /* eslint-disable new-cap */ + return IO(url); + /* eslint-enable new-cap */ +}; + +exports['default'] = IO; +module.exports = exports['default']; +},{"./event-factory":5,"./event-target":6,"./helpers/close-codes":8,"./helpers/delay":10,"./network-bridge":15,"urijs":3}],18:[function(require,module,exports){ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +var _get = function get(_x2, _x3, _x4) { var _again = true; _function: while (_again) { var object = _x2, property = _x3, receiver = _x4; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x2 = parent; _x3 = property; _x4 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _urijs = require('urijs'); + +var _urijs2 = _interopRequireDefault(_urijs); + +var _helpersDelay = require('./helpers/delay'); + +var _helpersDelay2 = _interopRequireDefault(_helpersDelay); + +var _eventTarget = require('./event-target'); + +var _eventTarget2 = _interopRequireDefault(_eventTarget); + +var _networkBridge = require('./network-bridge'); + +var _networkBridge2 = _interopRequireDefault(_networkBridge); + +var _helpersCloseCodes = require('./helpers/close-codes'); + +var _helpersCloseCodes2 = _interopRequireDefault(_helpersCloseCodes); + +var _eventFactory = require('./event-factory'); + +/* +* The main websocket class which is designed to mimick the native WebSocket class as close +* as possible. +* +* https://developer.mozilla.org/en-US/docs/Web/API/WebSocket +*/ + +var WebSocket = (function (_EventTarget) { + _inherits(WebSocket, _EventTarget); + + /* + * @param {string} url + */ + + function WebSocket(url) { + var protocol = arguments.length <= 1 || arguments[1] === undefined ? '' : arguments[1]; + + _classCallCheck(this, WebSocket); + + _get(Object.getPrototypeOf(WebSocket.prototype), 'constructor', this).call(this); + + if (!url) { + throw new TypeError('Failed to construct \'WebSocket\': 1 argument required, but only 0 present.'); + } + + this.binaryType = 'blob'; + this.url = (0, _urijs2['default'])(url).toString(); + this.readyState = WebSocket.CONNECTING; + this.protocol = ''; + + if (typeof protocol === 'string') { + this.protocol = protocol; + } else if (Array.isArray(protocol) && protocol.length > 0) { + this.protocol = protocol[0]; + } + + /* + * In order to capture the callback function we need to define custom setters. + * To illustrate: + * mySocket.onopen = function() { alert(true) }; + * + * The only way to capture that function and hold onto it for later is with the + * below code: + */ + Object.defineProperties(this, { + onopen: { + configurable: true, + enumerable: true, + get: function get() { + return this.listeners.open; + }, + set: function set(listener) { + this.addEventListener('open', listener); + } + }, + onmessage: { + configurable: true, + enumerable: true, + get: function get() { + return this.listeners.message; + }, + set: function set(listener) { + this.addEventListener('message', listener); + } + }, + onclose: { + configurable: true, + enumerable: true, + get: function get() { + return this.listeners.close; + }, + set: function set(listener) { + this.addEventListener('close', listener); + } + }, + onerror: { + configurable: true, + enumerable: true, + get: function get() { + return this.listeners.error; + }, + set: function set(listener) { + this.addEventListener('error', listener); + } + } + }); + + var server = _networkBridge2['default'].attachWebSocket(this, this.url); + + /* + * This delay is needed so that we dont trigger an event before the callbacks have been + * setup. For example: + * + * var socket = new WebSocket('ws://localhost'); + * + * // If we dont have the delay then the event would be triggered right here and this is + * // before the onopen had a chance to register itself. + * + * socket.onopen = () => { // this would never be called }; + * + * // and with the delay the event gets triggered here after all of the callbacks have been + * // registered :-) + */ + (0, _helpersDelay2['default'])(function delayCallback() { + if (server) { + this.readyState = WebSocket.OPEN; + server.dispatchEvent((0, _eventFactory.createEvent)({ type: 'connection' }), server, this); + this.dispatchEvent((0, _eventFactory.createEvent)({ type: 'open', target: this })); + } else { + this.readyState = WebSocket.CLOSED; + this.dispatchEvent((0, _eventFactory.createEvent)({ type: 'error', target: this })); + this.dispatchEvent((0, _eventFactory.createCloseEvent)({ type: 'close', target: this, code: _helpersCloseCodes2['default'].CLOSE_NORMAL })); + + console.error('WebSocket connection to \'' + this.url + '\' failed'); + } + }, this); + } + + /* + * Transmits data to the server over the WebSocket connection. + * + * https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#send() + */ + + _createClass(WebSocket, [{ + key: 'send', + value: function send(data) { + if (this.readyState === WebSocket.CLOSING || this.readyState === WebSocket.CLOSED) { + throw new Error('WebSocket is already in CLOSING or CLOSED state'); + } + + var messageEvent = (0, _eventFactory.createMessageEvent)({ + type: 'message', + origin: this.url, + data: data + }); + + var server = _networkBridge2['default'].serverLookup(this.url); + + if (server) { + server.dispatchEvent(messageEvent, data); + } + } + + /* + * Closes the WebSocket connection or connection attempt, if any. + * If the connection is already CLOSED, this method does nothing. + * + * https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#close() + */ + }, { + key: 'close', + value: function close() { + if (this.readyState !== WebSocket.OPEN) { + return undefined; + } + + var server = _networkBridge2['default'].serverLookup(this.url); + var closeEvent = (0, _eventFactory.createCloseEvent)({ + type: 'close', + target: this, + code: _helpersCloseCodes2['default'].CLOSE_NORMAL + }); + + _networkBridge2['default'].removeWebSocket(this, this.url); + + this.readyState = WebSocket.CLOSED; + this.dispatchEvent(closeEvent); + + if (server) { + server.dispatchEvent(closeEvent, server); + } + } + }]); + + return WebSocket; +})(_eventTarget2['default']); + +WebSocket.CONNECTING = 0; +WebSocket.OPEN = 1; +WebSocket.CLOSING = 2; +WebSocket.CLOSED = 3; + +exports['default'] = WebSocket; +module.exports = exports['default']; +},{"./event-factory":5,"./event-target":6,"./helpers/close-codes":8,"./helpers/delay":10,"./network-bridge":15,"urijs":3}]},{},[14]) +//# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL25vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCIuLi8uLi9ub2RlX21vZHVsZXMvdXJpanMvc3JjL0lQdjYuanMiLCIuLi8uLi9ub2RlX21vZHVsZXMvdXJpanMvc3JjL1NlY29uZExldmVsRG9tYWlucy5qcyIsIi4uLy4uL25vZGVfbW9kdWxlcy91cmlqcy9zcmMvVVJJLmpzIiwiLi4vLi4vbm9kZV9tb2R1bGVzL3VyaWpzL3NyYy9wdW55Y29kZS5qcyIsImV2ZW50LWZhY3RvcnkuanMiLCJldmVudC10YXJnZXQuanMiLCJoZWxwZXJzL2FycmF5LWhlbHBlcnMuanMiLCJoZWxwZXJzL2Nsb3NlLWNvZGVzLmpzIiwiaGVscGVycy9jbG9zZS1ldmVudC5qcyIsImhlbHBlcnMvZGVsYXkuanMiLCJoZWxwZXJzL2V2ZW50LXByb3RvdHlwZS5qcyIsImhlbHBlcnMvZXZlbnQuanMiLCJoZWxwZXJzL21lc3NhZ2UtZXZlbnQuanMiLCJtYWluLmpzIiwibmV0d29yay1icmlkZ2UuanMiLCJzZXJ2ZXIuanMiLCJzb2NrZXQtaW8uanMiLCJ3ZWJzb2NrZXQuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUNBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDNUxBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDalBBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7OztBQ2puRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7OztBQzVmQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDckdBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN4R0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM1QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNuQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDL0RBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNwQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDN0NBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3pEQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDakVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDL0JBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM3S0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDN0xBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDclJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24gZSh0LG4scil7ZnVuY3Rpb24gcyhvLHUpe2lmKCFuW29dKXtpZighdFtvXSl7dmFyIGE9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtpZighdSYmYSlyZXR1cm4gYShvLCEwKTtpZihpKXJldHVybiBpKG8sITApO3ZhciBmPW5ldyBFcnJvcihcIkNhbm5vdCBmaW5kIG1vZHVsZSAnXCIrbytcIidcIik7dGhyb3cgZi5jb2RlPVwiTU9EVUxFX05PVF9GT1VORFwiLGZ9dmFyIGw9bltvXT17ZXhwb3J0czp7fX07dFtvXVswXS5jYWxsKGwuZXhwb3J0cyxmdW5jdGlvbihlKXt2YXIgbj10W29dWzFdW2VdO3JldHVybiBzKG4/bjplKX0sbCxsLmV4cG9ydHMsZSx0LG4scil9cmV0dXJuIG5bb10uZXhwb3J0c312YXIgaT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2Zvcih2YXIgbz0wO288ci5sZW5ndGg7bysrKXMocltvXSk7cmV0dXJuIHN9KSIsIi8qIVxuICogVVJJLmpzIC0gTXV0YXRpbmcgVVJMc1xuICogSVB2NiBTdXBwb3J0XG4gKlxuICogVmVyc2lvbjogMS4xNy4wXG4gKlxuICogQXV0aG9yOiBSb2RuZXkgUmVobVxuICogV2ViOiBodHRwOi8vbWVkaWFsaXplLmdpdGh1Yi5pby9VUkkuanMvXG4gKlxuICogTGljZW5zZWQgdW5kZXJcbiAqICAgTUlUIExpY2Vuc2UgaHR0cDovL3d3dy5vcGVuc291cmNlLm9yZy9saWNlbnNlcy9taXQtbGljZW5zZVxuICogICBHUEwgdjMgaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0dQTC0zLjBcbiAqXG4gKi9cblxuKGZ1bmN0aW9uIChyb290LCBmYWN0b3J5KSB7XG4gICd1c2Ugc3RyaWN0JztcbiAgLy8gaHR0cHM6Ly9naXRodWIuY29tL3VtZGpzL3VtZC9ibG9iL21hc3Rlci9yZXR1cm5FeHBvcnRzLmpzXG4gIGlmICh0eXBlb2YgZXhwb3J0cyA9PT0gJ29iamVjdCcpIHtcbiAgICAvLyBOb2RlXG4gICAgbW9kdWxlLmV4cG9ydHMgPSBmYWN0b3J5KCk7XG4gIH0gZWxzZSBpZiAodHlwZW9mIGRlZmluZSA9PT0gJ2Z1bmN0aW9uJyAmJiBkZWZpbmUuYW1kKSB7XG4gICAgLy8gQU1ELiBSZWdpc3RlciBhcyBhbiBhbm9ueW1vdXMgbW9kdWxlLlxuICAgIGRlZmluZShmYWN0b3J5KTtcbiAgfSBlbHNlIHtcbiAgICAvLyBCcm93c2VyIGdsb2JhbHMgKHJvb3QgaXMgd2luZG93KVxuICAgIHJvb3QuSVB2NiA9IGZhY3Rvcnkocm9vdCk7XG4gIH1cbn0odGhpcywgZnVuY3Rpb24gKHJvb3QpIHtcbiAgJ3VzZSBzdHJpY3QnO1xuXG4gIC8qXG4gIHZhciBfaW4gPSBcImZlODA6MDAwMDowMDAwOjAwMDA6MDIwNDo2MWZmOmZlOWQ6ZjE1NlwiO1xuICB2YXIgX291dCA9IElQdjYuYmVzdChfaW4pO1xuICB2YXIgX2V4cGVjdGVkID0gXCJmZTgwOjoyMDQ6NjFmZjpmZTlkOmYxNTZcIjtcblxuICBjb25zb2xlLmxvZyhfaW4sIF9vdXQsIF9leHBlY3RlZCwgX291dCA9PT0gX2V4cGVjdGVkKTtcbiAgKi9cblxuICAvLyBzYXZlIGN1cnJlbnQgSVB2NiB2YXJpYWJsZSwgaWYgYW55XG4gIHZhciBfSVB2NiA9IHJvb3QgJiYgcm9vdC5JUHY2O1xuXG4gIGZ1bmN0aW9uIGJlc3RQcmVzZW50YXRpb24oYWRkcmVzcykge1xuICAgIC8vIGJhc2VkIG9uOlxuICAgIC8vIEphdmFzY3JpcHQgdG8gdGVzdCBhbiBJUHY2IGFkZHJlc3MgZm9yIHByb3BlciBmb3JtYXQsIGFuZCB0b1xuICAgIC8vIHByZXNlbnQgdGhlIFwiYmVzdCB0ZXh0IHJlcHJlc2VudGF0aW9uXCIgYWNjb3JkaW5nIHRvIElFVEYgRHJhZnQgUkZDIGF0XG4gICAgLy8gaHR0cDovL3Rvb2xzLmlldGYub3JnL2h0bWwvZHJhZnQtaWV0Zi02bWFuLXRleHQtYWRkci1yZXByZXNlbnRhdGlvbi0wNFxuICAgIC8vIDggRmViIDIwMTAgUmljaCBCcm93biwgRGFydHdhcmUsIExMQ1xuICAgIC8vIFBsZWFzZSBmZWVsIGZyZWUgdG8gdXNlIHRoaXMgY29kZSBhcyBsb25nIGFzIHlvdSBwcm92aWRlIGEgbGluayB0b1xuICAgIC8vIGh0dHA6Ly93d3cuaW50ZXJtYXBwZXIuY29tXG4gICAgLy8gaHR0cDovL2ludGVybWFwcGVyLmNvbS9zdXBwb3J0L3Rvb2xzL0lQVjYtVmFsaWRhdG9yLmFzcHhcbiAgICAvLyBodHRwOi8vZG93bmxvYWQuZGFydHdhcmUuY29tL3RoaXJkcGFydHkvaXB2NnZhbGlkYXRvci5qc1xuXG4gICAgdmFyIF9hZGRyZXNzID0gYWRkcmVzcy50b0xvd2VyQ2FzZSgpO1xuICAgIHZhciBzZWdtZW50cyA9IF9hZGRyZXNzLnNwbGl0KCc6Jyk7XG4gICAgdmFyIGxlbmd0aCA9IHNlZ21lbnRzLmxlbmd0aDtcbiAgICB2YXIgdG90YWwgPSA4O1xuXG4gICAgLy8gdHJpbSBjb2xvbnMgKDo6IG9yIDo6YTpiOmPigKYgb3Ig4oCmYTpiOmM6OilcbiAgICBpZiAoc2VnbWVudHNbMF0gPT09ICcnICYmIHNlZ21lbnRzWzFdID09PSAnJyAmJiBzZWdtZW50c1syXSA9PT0gJycpIHtcbiAgICAgIC8vIG11c3QgaGF2ZSBiZWVuIDo6XG4gICAgICAvLyByZW1vdmUgZmlyc3QgdHdvIGl0ZW1zXG4gICAgICBzZWdtZW50cy5zaGlmdCgpO1xuICAgICAgc2VnbWVudHMuc2hpZnQoKTtcbiAgICB9IGVsc2UgaWYgKHNlZ21lbnRzWzBdID09PSAnJyAmJiBzZWdtZW50c1sxXSA9PT0gJycpIHtcbiAgICAgIC8vIG11c3QgaGF2ZSBiZWVuIDo6eHh4eFxuICAgICAgLy8gcmVtb3ZlIHRoZSBmaXJzdCBpdGVtXG4gICAgICBzZWdtZW50cy5zaGlmdCgpO1xuICAgIH0gZWxzZSBpZiAoc2VnbWVudHNbbGVuZ3RoIC0gMV0gPT09ICcnICYmIHNlZ21lbnRzW2xlbmd0aCAtIDJdID09PSAnJykge1xuICAgICAgLy8gbXVzdCBoYXZlIGJlZW4geHh4eDo6XG4gICAgICBzZWdtZW50cy5wb3AoKTtcbiAgICB9XG5cbiAgICBsZW5ndGggPSBzZWdtZW50cy5sZW5ndGg7XG5cbiAgICAvLyBhZGp1c3QgdG90YWwgc2VnbWVudHMgZm9yIElQdjQgdHJhaWxlclxuICAgIGlmIChzZWdtZW50c1tsZW5ndGggLSAxXS5pbmRleE9mKCcuJykgIT09IC0xKSB7XG4gICAgICAvLyBmb3VuZCBhIFwiLlwiIHdoaWNoIG1lYW5zIElQdjRcbiAgICAgIHRvdGFsID0gNztcbiAgICB9XG5cbiAgICAvLyBmaWxsIGVtcHR5IHNlZ21lbnRzIHRoZW0gd2l0aCBcIjAwMDBcIlxuICAgIHZhciBwb3M7XG4gICAgZm9yIChwb3MgPSAwOyBwb3MgPCBsZW5ndGg7IHBvcysrKSB7XG4gICAgICBpZiAoc2VnbWVudHNbcG9zXSA9PT0gJycpIHtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKHBvcyA8IHRvdGFsKSB7XG4gICAgICBzZWdtZW50cy5zcGxpY2UocG9zLCAxLCAnMDAwMCcpO1xuICAgICAgd2hpbGUgKHNlZ21lbnRzLmxlbmd0aCA8IHRvdGFsKSB7XG4gICAgICAgIHNlZ21lbnRzLnNwbGljZShwb3MsIDAsICcwMDAwJyk7XG4gICAgICB9XG5cbiAgICAgIGxlbmd0aCA9IHNlZ21lbnRzLmxlbmd0aDtcbiAgICB9XG5cbiAgICAvLyBzdHJpcCBsZWFkaW5nIHplcm9zXG4gICAgdmFyIF9zZWdtZW50cztcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IHRvdGFsOyBpKyspIHtcbiAgICAgIF9zZWdtZW50cyA9IHNlZ21lbnRzW2ldLnNwbGl0KCcnKTtcbiAgICAgIGZvciAodmFyIGogPSAwOyBqIDwgMyA7IGorKykge1xuICAgICAgICBpZiAoX3NlZ21lbnRzWzBdID09PSAnMCcgJiYgX3NlZ21lbnRzLmxlbmd0aCA+IDEpIHtcbiAgICAgICAgICBfc2VnbWVudHMuc3BsaWNlKDAsMSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgc2VnbWVudHNbaV0gPSBfc2VnbWVudHMuam9pbignJyk7XG4gICAgfVxuXG4gICAgLy8gZmluZCBsb25nZXN0IHNlcXVlbmNlIG9mIHplcm9lcyBhbmQgY29hbGVzY2UgdGhlbSBpbnRvIG9uZSBzZWdtZW50XG4gICAgdmFyIGJlc3QgPSAtMTtcbiAgICB2YXIgX2Jlc3QgPSAwO1xuICAgIHZhciBfY3VycmVudCA9IDA7XG4gICAgdmFyIGN1cnJlbnQgPSAtMTtcbiAgICB2YXIgaW56ZXJvZXMgPSBmYWxzZTtcbiAgICAvLyBpOyBhbHJlYWR5IGRlY2xhcmVkXG5cbiAgICBmb3IgKGkgPSAwOyBpIDwgdG90YWw7IGkrKykge1xuICAgICAgaWYgKGluemVyb2VzKSB7XG4gICAgICAgIGlmIChzZWdtZW50c1tpXSA9PT0gJzAnKSB7XG4gICAgICAgICAgX2N1cnJlbnQgKz0gMTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBpbnplcm9lcyA9IGZhbHNlO1xuICAgICAgICAgIGlmIChfY3VycmVudCA+IF9iZXN0KSB7XG4gICAgICAgICAgICBiZXN0ID0gY3VycmVudDtcbiAgICAgICAgICAgIF9iZXN0ID0gX2N1cnJlbnQ7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBpZiAoc2VnbWVudHNbaV0gPT09ICcwJykge1xuICAgICAgICAgIGluemVyb2VzID0gdHJ1ZTtcbiAgICAgICAgICBjdXJyZW50ID0gaTtcbiAgICAgICAgICBfY3VycmVudCA9IDE7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAoX2N1cnJlbnQgPiBfYmVzdCkge1xuICAgICAgYmVzdCA9IGN1cnJlbnQ7XG4gICAgICBfYmVzdCA9IF9jdXJyZW50O1xuICAgIH1cblxuICAgIGlmIChfYmVzdCA+IDEpIHtcbiAgICAgIHNlZ21lbnRzLnNwbGljZShiZXN0LCBfYmVzdCwgJycpO1xuICAgIH1cblxuICAgIGxlbmd0aCA9IHNlZ21lbnRzLmxlbmd0aDtcblxuICAgIC8vIGFzc2VtYmxlIHJlbWFpbmluZyBzZWdtZW50c1xuICAgIHZhciByZXN1bHQgPSAnJztcbiAgICBpZiAoc2VnbWVudHNbMF0gPT09ICcnKSAge1xuICAgICAgcmVzdWx0ID0gJzonO1xuICAgIH1cblxuICAgIGZvciAoaSA9IDA7IGkgPCBsZW5ndGg7IGkrKykge1xuICAgICAgcmVzdWx0ICs9IHNlZ21lbnRzW2ldO1xuICAgICAgaWYgKGkgPT09IGxlbmd0aCAtIDEpIHtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG5cbiAgICAgIHJlc3VsdCArPSAnOic7XG4gICAgfVxuXG4gICAgaWYgKHNlZ21lbnRzW2xlbmd0aCAtIDFdID09PSAnJykge1xuICAgICAgcmVzdWx0ICs9ICc6JztcbiAgICB9XG5cbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG5cbiAgZnVuY3Rpb24gbm9Db25mbGljdCgpIHtcbiAgICAvKmpzaGludCB2YWxpZHRoaXM6IHRydWUgKi9cbiAgICBpZiAocm9vdC5JUHY2ID09PSB0aGlzKSB7XG4gICAgICByb290LklQdjYgPSBfSVB2NjtcbiAgICB9XG4gIFxuICAgIHJldHVybiB0aGlzO1xuICB9XG5cbiAgcmV0dXJuIHtcbiAgICBiZXN0OiBiZXN0UHJlc2VudGF0aW9uLFxuICAgIG5vQ29uZmxpY3Q6IG5vQ29uZmxpY3RcbiAgfTtcbn0pKTtcbiIsIi8qIVxuICogVVJJLmpzIC0gTXV0YXRpbmcgVVJMc1xuICogU2Vjb25kIExldmVsIERvbWFpbiAoU0xEKSBTdXBwb3J0XG4gKlxuICogVmVyc2lvbjogMS4xNy4wXG4gKlxuICogQXV0aG9yOiBSb2RuZXkgUmVobVxuICogV2ViOiBodHRwOi8vbWVkaWFsaXplLmdpdGh1Yi5pby9VUkkuanMvXG4gKlxuICogTGljZW5zZWQgdW5kZXJcbiAqICAgTUlUIExpY2Vuc2UgaHR0cDovL3d3dy5vcGVuc291cmNlLm9yZy9saWNlbnNlcy9taXQtbGljZW5zZVxuICogICBHUEwgdjMgaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0dQTC0zLjBcbiAqXG4gKi9cblxuKGZ1bmN0aW9uIChyb290LCBmYWN0b3J5KSB7XG4gICd1c2Ugc3RyaWN0JztcbiAgLy8gaHR0cHM6Ly9naXRodWIuY29tL3VtZGpzL3VtZC9ibG9iL21hc3Rlci9yZXR1cm5FeHBvcnRzLmpzXG4gIGlmICh0eXBlb2YgZXhwb3J0cyA9PT0gJ29iamVjdCcpIHtcbiAgICAvLyBOb2RlXG4gICAgbW9kdWxlLmV4cG9ydHMgPSBmYWN0b3J5KCk7XG4gIH0gZWxzZSBpZiAodHlwZW9mIGRlZmluZSA9PT0gJ2Z1bmN0aW9uJyAmJiBkZWZpbmUuYW1kKSB7XG4gICAgLy8gQU1ELiBSZWdpc3RlciBhcyBhbiBhbm9ueW1vdXMgbW9kdWxlLlxuICAgIGRlZmluZShmYWN0b3J5KTtcbiAgfSBlbHNlIHtcbiAgICAvLyBCcm93c2VyIGdsb2JhbHMgKHJvb3QgaXMgd2luZG93KVxuICAgIHJvb3QuU2Vjb25kTGV2ZWxEb21haW5zID0gZmFjdG9yeShyb290KTtcbiAgfVxufSh0aGlzLCBmdW5jdGlvbiAocm9vdCkge1xuICAndXNlIHN0cmljdCc7XG5cbiAgLy8gc2F2ZSBjdXJyZW50IFNlY29uZExldmVsRG9tYWlucyB2YXJpYWJsZSwgaWYgYW55XG4gIHZhciBfU2Vjb25kTGV2ZWxEb21haW5zID0gcm9vdCAmJiByb290LlNlY29uZExldmVsRG9tYWlucztcblxuICB2YXIgU0xEID0ge1xuICAgIC8vIGxpc3Qgb2Yga25vd24gU2Vjb25kIExldmVsIERvbWFpbnNcbiAgICAvLyBjb252ZXJ0ZWQgbGlzdCBvZiBTTERzIGZyb20gaHR0cHM6Ly9naXRodWIuY29tL2dhdmluZ21pbGxlci9zZWNvbmQtbGV2ZWwtZG9tYWluc1xuICAgIC8vIC0tLS1cbiAgICAvLyBwdWJsaWNzdWZmaXgub3JnIGlzIG1vcmUgY3VycmVudCBhbmQgYWN0dWFsbHkgdXNlZCBieSBhIGNvdXBsZSBvZiBicm93c2VycyBpbnRlcm5hbGx5LlxuICAgIC8vIGRvd25zaWRlIGlzIGl0IGFsc28gY29udGFpbnMgZG9tYWlucyBsaWtlIFwiZHluZG5zLm9yZ1wiIC0gd2hpY2ggaXMgZmluZSBmb3IgdGhlIHNlY3VyaXR5XG4gICAgLy8gaXNzdWVzIGJyb3dzZXIgaGF2ZSB0byBkZWFsIHdpdGggKFNPUCBmb3IgY29va2llcywgZXRjKSAtIGJ1dCBpcyB3YXkgb3ZlcmJvYXJkIGZvciBVUkkuanNcbiAgICAvLyAtLS0tXG4gICAgbGlzdDoge1xuICAgICAgJ2FjJzonIGNvbSBnb3YgbWlsIG5ldCBvcmcgJyxcbiAgICAgICdhZSc6JyBhYyBjbyBnb3YgbWlsIG5hbWUgbmV0IG9yZyBwcm8gc2NoICcsXG4gICAgICAnYWYnOicgY29tIGVkdSBnb3YgbmV0IG9yZyAnLFxuICAgICAgJ2FsJzonIGNvbSBlZHUgZ292IG1pbCBuZXQgb3JnICcsXG4gICAgICAnYW8nOicgY28gZWQgZ3YgaXQgb2cgcGIgJyxcbiAgICAgICdhcic6JyBjb20gZWR1IGdvYiBnb3YgaW50IG1pbCBuZXQgb3JnIHR1ciAnLFxuICAgICAgJ2F0JzonIGFjIGNvIGd2IG9yICcsXG4gICAgICAnYXUnOicgYXNuIGNvbSBjc2lybyBlZHUgZ292IGlkIG5ldCBvcmcgJyxcbiAgICAgICdiYSc6JyBjbyBjb20gZWR1IGdvdiBtaWwgbmV0IG9yZyBycyB1bmJpIHVubW8gdW5zYSB1bnR6IHVuemUgJyxcbiAgICAgICdiYic6JyBiaXogY28gY29tIGVkdSBnb3YgaW5mbyBuZXQgb3JnIHN0b3JlIHR2ICcsXG4gICAgICAnYmgnOicgYml6IGNjIGNvbSBlZHUgZ292IGluZm8gbmV0IG9yZyAnLFxuICAgICAgJ2JuJzonIGNvbSBlZHUgZ292IG5ldCBvcmcgJyxcbiAgICAgICdibyc6JyBjb20gZWR1IGdvYiBnb3YgaW50IG1pbCBuZXQgb3JnIHR2ICcsXG4gICAgICAnYnInOicgYWRtIGFkdiBhZ3IgYW0gYXJxIGFydCBhdG8gYiBiaW8gYmxvZyBibWQgY2ltIGNuZyBjbnQgY29tIGNvb3AgZWNuIGVkdSBlbmcgZXNwIGV0YyBldGkgZmFyIGZsb2cgZm0gZm5kIGZvdCBmc3QgZzEyIGdnZiBnb3YgaW1iIGluZCBpbmYgam9yIGp1cyBsZWwgbWF0IG1lZCBtaWwgbXVzIG5ldCBub20gbm90IG50ciBvZG8gb3JnIHBwZyBwcm8gcHNjIHBzaSBxc2wgcmVjIHNsZyBzcnYgdG1wIHRyZCB0dXIgdHYgdmV0IHZsb2cgd2lraSB6bGcgJyxcbiAgICAgICdicyc6JyBjb20gZWR1IGdvdiBuZXQgb3JnICcsXG4gICAgICAnYnonOicgZHUgZXQgb20gb3YgcmcgJyxcbiAgICAgICdjYSc6JyBhYiBiYyBtYiBuYiBuZiBubCBucyBudCBudSBvbiBwZSBxYyBzayB5ayAnLFxuICAgICAgJ2NrJzonIGJpeiBjbyBlZHUgZ2VuIGdvdiBpbmZvIG5ldCBvcmcgJyxcbiAgICAgICdjbic6JyBhYyBhaCBiaiBjb20gY3EgZWR1IGZqIGdkIGdvdiBncyBneCBneiBoYSBoYiBoZSBoaSBobCBobiBqbCBqcyBqeCBsbiBtaWwgbmV0IG5tIG54IG9yZyBxaCBzYyBzZCBzaCBzbiBzeCB0aiB0dyB4aiB4eiB5biB6aiAnLFxuICAgICAgJ2NvJzonIGNvbSBlZHUgZ292IG1pbCBuZXQgbm9tIG9yZyAnLFxuICAgICAgJ2NyJzonIGFjIGMgY28gZWQgZmkgZ28gb3Igc2EgJyxcbiAgICAgICdjeSc6JyBhYyBiaXogY29tIGVrbG9nZXMgZ292IGx0ZCBuYW1lIG5ldCBvcmcgcGFybGlhbWVudCBwcmVzcyBwcm8gdG0gJyxcbiAgICAgICdkbyc6JyBhcnQgY29tIGVkdSBnb2IgZ292IG1pbCBuZXQgb3JnIHNsZCB3ZWIgJyxcbiAgICAgICdkeic6JyBhcnQgYXNzbyBjb20gZWR1IGdvdiBuZXQgb3JnIHBvbCAnLFxuICAgICAgJ2VjJzonIGNvbSBlZHUgZmluIGdvdiBpbmZvIG1lZCBtaWwgbmV0IG9yZyBwcm8gJyxcbiAgICAgICdlZyc6JyBjb20gZWR1IGV1biBnb3YgbWlsIG5hbWUgbmV0IG9yZyBzY2kgJyxcbiAgICAgICdlcic6JyBjb20gZWR1IGdvdiBpbmQgbWlsIG5ldCBvcmcgcm9jaGVzdCB3ICcsXG4gICAgICAnZXMnOicgY29tIGVkdSBnb2Igbm9tIG9yZyAnLFxuICAgICAgJ2V0JzonIGJpeiBjb20gZWR1IGdvdiBpbmZvIG5hbWUgbmV0IG9yZyAnLFxuICAgICAgJ2ZqJzonIGFjIGJpeiBjb20gaW5mbyBtaWwgbmFtZSBuZXQgb3JnIHBybyAnLFxuICAgICAgJ2ZrJzonIGFjIGNvIGdvdiBuZXQgbm9tIG9yZyAnLFxuICAgICAgJ2ZyJzonIGFzc28gY29tIGYgZ291diBub20gcHJkIHByZXNzZSB0bSAnLFxuICAgICAgJ2dnJzonIGNvIG5ldCBvcmcgJyxcbiAgICAgICdnaCc6JyBjb20gZWR1IGdvdiBtaWwgb3JnICcsXG4gICAgICAnZ24nOicgYWMgY29tIGdvdiBuZXQgb3JnICcsXG4gICAgICAnZ3InOicgY29tIGVkdSBnb3YgbWlsIG5ldCBvcmcgJyxcbiAgICAgICdndCc6JyBjb20gZWR1IGdvYiBpbmQgbWlsIG5ldCBvcmcgJyxcbiAgICAgICdndSc6JyBjb20gZWR1IGdvdiBuZXQgb3JnICcsXG4gICAgICAnaGsnOicgY29tIGVkdSBnb3YgaWR2IG5ldCBvcmcgJyxcbiAgICAgICdodSc6JyAyMDAwIGFncmFyIGJvbHQgY2FzaW5vIGNpdHkgY28gZXJvdGljYSBlcm90aWthIGZpbG0gZm9ydW0gZ2FtZXMgaG90ZWwgaW5mbyBpbmdhdGxhbiBqb2dhc3oga29ueXZlbG8gbGFrYXMgbWVkaWEgbmV3cyBvcmcgcHJpdiByZWtsYW0gc2V4IHNob3Agc3BvcnQgc3VsaSBzemV4IHRtIHRvenNkZSB1dGF6YXMgdmlkZW8gJyxcbiAgICAgICdpZCc6JyBhYyBjbyBnbyBtaWwgbmV0IG9yIHNjaCB3ZWIgJyxcbiAgICAgICdpbCc6JyBhYyBjbyBnb3YgaWRmIGsxMiBtdW5pIG5ldCBvcmcgJyxcbiAgICAgICdpbic6JyBhYyBjbyBlZHUgZXJuZXQgZmlybSBnZW4gZ292IGkgaW5kIG1pbCBuZXQgbmljIG9yZyByZXMgJyxcbiAgICAgICdpcSc6JyBjb20gZWR1IGdvdiBpIG1pbCBuZXQgb3JnICcsXG4gICAgICAnaXInOicgYWMgY28gZG5zc2VjIGdvdiBpIGlkIG5ldCBvcmcgc2NoICcsXG4gICAgICAnaXQnOicgZWR1IGdvdiAnLFxuICAgICAgJ2plJzonIGNvIG5ldCBvcmcgJyxcbiAgICAgICdqbyc6JyBjb20gZWR1IGdvdiBtaWwgbmFtZSBuZXQgb3JnIHNjaCAnLFxuICAgICAgJ2pwJzonIGFjIGFkIGNvIGVkIGdvIGdyIGxnIG5lIG9yICcsXG4gICAgICAna2UnOicgYWMgY28gZ28gaW5mbyBtZSBtb2JpIG5lIG9yIHNjICcsXG4gICAgICAna2gnOicgY29tIGVkdSBnb3YgbWlsIG5ldCBvcmcgcGVyICcsXG4gICAgICAna2knOicgYml6IGNvbSBkZSBlZHUgZ292IGluZm8gbW9iIG5ldCBvcmcgdGVsICcsXG4gICAgICAna20nOicgYXNzbyBjb20gY29vcCBlZHUgZ291diBrIG1lZGVjaW4gbWlsIG5vbSBub3RhaXJlcyBwaGFybWFjaWVucyBwcmVzc2UgdG0gdmV0ZXJpbmFpcmUgJyxcbiAgICAgICdrbic6JyBlZHUgZ292IG5ldCBvcmcgJyxcbiAgICAgICdrcic6JyBhYyBidXNhbiBjaHVuZ2J1ayBjaHVuZ25hbSBjbyBkYWVndSBkYWVqZW9uIGVzIGdhbmd3b24gZ28gZ3dhbmdqdSBneWVvbmdidWsgZ3llb25nZ2kgZ3llb25nbmFtIGhzIGluY2hlb24gamVqdSBqZW9uYnVrIGplb25uYW0gayBrZyBtaWwgbXMgbmUgb3IgcGUgcmUgc2Mgc2VvdWwgdWxzYW4gJyxcbiAgICAgICdrdyc6JyBjb20gZWR1IGdvdiBuZXQgb3JnICcsXG4gICAgICAna3knOicgY29tIGVkdSBnb3YgbmV0IG9yZyAnLFxuICAgICAgJ2t6JzonIGNvbSBlZHUgZ292IG1pbCBuZXQgb3JnICcsXG4gICAgICAnbGInOicgY29tIGVkdSBnb3YgbmV0IG9yZyAnLFxuICAgICAgJ2xrJzonIGFzc24gY29tIGVkdSBnb3YgZ3JwIGhvdGVsIGludCBsdGQgbmV0IG5nbyBvcmcgc2NoIHNvYyB3ZWIgJyxcbiAgICAgICdscic6JyBjb20gZWR1IGdvdiBuZXQgb3JnICcsXG4gICAgICAnbHYnOicgYXNuIGNvbSBjb25mIGVkdSBnb3YgaWQgbWlsIG5ldCBvcmcgJyxcbiAgICAgICdseSc6JyBjb20gZWR1IGdvdiBpZCBtZWQgbmV0IG9yZyBwbGMgc2NoICcsXG4gICAgICAnbWEnOicgYWMgY28gZ292IG0gbmV0IG9yZyBwcmVzcyAnLFxuICAgICAgJ21jJzonIGFzc28gdG0gJyxcbiAgICAgICdtZSc6JyBhYyBjbyBlZHUgZ292IGl0cyBuZXQgb3JnIHByaXYgJyxcbiAgICAgICdtZyc6JyBjb20gZWR1IGdvdiBtaWwgbm9tIG9yZyBwcmQgdG0gJyxcbiAgICAgICdtayc6JyBjb20gZWR1IGdvdiBpbmYgbmFtZSBuZXQgb3JnIHBybyAnLFxuICAgICAgJ21sJzonIGNvbSBlZHUgZ292IG5ldCBvcmcgcHJlc3NlICcsXG4gICAgICAnbW4nOicgZWR1IGdvdiBvcmcgJyxcbiAgICAgICdtbyc6JyBjb20gZWR1IGdvdiBuZXQgb3JnICcsXG4gICAgICAnbXQnOicgY29tIGVkdSBnb3YgbmV0IG9yZyAnLFxuICAgICAgJ212JzonIGFlcm8gYml6IGNvbSBjb29wIGVkdSBnb3YgaW5mbyBpbnQgbWlsIG11c2V1bSBuYW1lIG5ldCBvcmcgcHJvICcsXG4gICAgICAnbXcnOicgYWMgY28gY29tIGNvb3AgZWR1IGdvdiBpbnQgbXVzZXVtIG5ldCBvcmcgJyxcbiAgICAgICdteCc6JyBjb20gZWR1IGdvYiBuZXQgb3JnICcsXG4gICAgICAnbXknOicgY29tIGVkdSBnb3YgbWlsIG5hbWUgbmV0IG9yZyBzY2ggJyxcbiAgICAgICduZic6JyBhcnRzIGNvbSBmaXJtIGluZm8gbmV0IG90aGVyIHBlciByZWMgc3RvcmUgd2ViICcsXG4gICAgICAnbmcnOicgYml6IGNvbSBlZHUgZ292IG1pbCBtb2JpIG5hbWUgbmV0IG9yZyBzY2ggJyxcbiAgICAgICduaSc6JyBhYyBjbyBjb20gZWR1IGdvYiBtaWwgbmV0IG5vbSBvcmcgJyxcbiAgICAgICducCc6JyBjb20gZWR1IGdvdiBtaWwgbmV0IG9yZyAnLFxuICAgICAgJ25yJzonIGJpeiBjb20gZWR1IGdvdiBpbmZvIG5ldCBvcmcgJyxcbiAgICAgICdvbSc6JyBhYyBiaXogY28gY29tIGVkdSBnb3YgbWVkIG1pbCBtdXNldW0gbmV0IG9yZyBwcm8gc2NoICcsXG4gICAgICAncGUnOicgY29tIGVkdSBnb2IgbWlsIG5ldCBub20gb3JnIHNsZCAnLFxuICAgICAgJ3BoJzonIGNvbSBlZHUgZ292IGkgbWlsIG5ldCBuZ28gb3JnICcsXG4gICAgICAncGsnOicgYml6IGNvbSBlZHUgZmFtIGdvYiBnb2sgZ29uIGdvcCBnb3MgZ292IG5ldCBvcmcgd2ViICcsXG4gICAgICAncGwnOicgYXJ0IGJpYWx5c3RvayBiaXogY29tIGVkdSBnZGEgZ2RhbnNrIGdvcnpvdyBnb3YgaW5mbyBrYXRvd2ljZSBrcmFrb3cgbG9keiBsdWJsaW4gbWlsIG5ldCBuZ28gb2xzenR5biBvcmcgcG96bmFuIHB3ciByYWRvbSBzbHVwc2sgc3pjemVjaW4gdG9ydW4gd2Fyc3phd2Egd2F3IHdyb2Mgd3JvY2xhdyB6Z29yYSAnLFxuICAgICAgJ3ByJzonIGFjIGJpeiBjb20gZWR1IGVzdCBnb3YgaW5mbyBpc2xhIG5hbWUgbmV0IG9yZyBwcm8gcHJvZiAnLFxuICAgICAgJ3BzJzonIGNvbSBlZHUgZ292IG5ldCBvcmcgcGxvIHNlYyAnLFxuICAgICAgJ3B3JzonIGJlbGF1IGNvIGVkIGdvIG5lIG9yICcsXG4gICAgICAncm8nOicgYXJ0cyBjb20gZmlybSBpbmZvIG5vbSBudCBvcmcgcmVjIHN0b3JlIHRtIHd3dyAnLFxuICAgICAgJ3JzJzonIGFjIGNvIGVkdSBnb3YgaW4gb3JnICcsXG4gICAgICAnc2InOicgY29tIGVkdSBnb3YgbmV0IG9yZyAnLFxuICAgICAgJ3NjJzonIGNvbSBlZHUgZ292IG5ldCBvcmcgJyxcbiAgICAgICdzaCc6JyBjbyBjb20gZWR1IGdvdiBuZXQgbm9tIG9yZyAnLFxuICAgICAgJ3NsJzonIGNvbSBlZHUgZ292IG5ldCBvcmcgJyxcbiAgICAgICdzdCc6JyBjbyBjb20gY29uc3VsYWRvIGVkdSBlbWJhaXhhZGEgZ292IG1pbCBuZXQgb3JnIHByaW5jaXBlIHNhb3RvbWUgc3RvcmUgJyxcbiAgICAgICdzdic6JyBjb20gZWR1IGdvYiBvcmcgcmVkICcsXG4gICAgICAnc3onOicgYWMgY28gb3JnICcsXG4gICAgICAndHInOicgYXYgYmJzIGJlbCBiaXogY29tIGRyIGVkdSBnZW4gZ292IGluZm8gazEyIG5hbWUgbmV0IG9yZyBwb2wgdGVsIHRzayB0diB3ZWIgJyxcbiAgICAgICd0dCc6JyBhZXJvIGJpeiBjYXQgY28gY29tIGNvb3AgZWR1IGdvdiBpbmZvIGludCBqb2JzIG1pbCBtb2JpIG11c2V1bSBuYW1lIG5ldCBvcmcgcHJvIHRlbCB0cmF2ZWwgJyxcbiAgICAgICd0dyc6JyBjbHViIGNvbSBlYml6IGVkdSBnYW1lIGdvdiBpZHYgbWlsIG5ldCBvcmcgJyxcbiAgICAgICdtdSc6JyBhYyBjbyBjb20gZ292IG5ldCBvciBvcmcgJyxcbiAgICAgICdteic6JyBhYyBjbyBlZHUgZ292IG9yZyAnLFxuICAgICAgJ25hJzonIGNvIGNvbSAnLFxuICAgICAgJ256JzonIGFjIGNvIGNyaSBnZWVrIGdlbiBnb3Z0IGhlYWx0aCBpd2kgbWFvcmkgbWlsIG5ldCBvcmcgcGFybGlhbWVudCBzY2hvb2wgJyxcbiAgICAgICdwYSc6JyBhYm8gYWMgY29tIGVkdSBnb2IgaW5nIG1lZCBuZXQgbm9tIG9yZyBzbGQgJyxcbiAgICAgICdwdCc6JyBjb20gZWR1IGdvdiBpbnQgbmV0IG5vbWUgb3JnIHB1YmwgJyxcbiAgICAgICdweSc6JyBjb20gZWR1IGdvdiBtaWwgbmV0IG9yZyAnLFxuICAgICAgJ3FhJzonIGNvbSBlZHUgZ292IG1pbCBuZXQgb3JnICcsXG4gICAgICAncmUnOicgYXNzbyBjb20gbm9tICcsXG4gICAgICAncnUnOicgYWMgYWR5Z2V5YSBhbHRhaSBhbXVyIGFya2hhbmdlbHNrIGFzdHJha2hhbiBiYXNoa2lyaWEgYmVsZ29yb2QgYmlyIGJyeWFuc2sgYnVyeWF0aWEgY2JnIGNoZWwgY2hlbHlhYmluc2sgY2hpdGEgY2h1a290a2EgY2h1dmFzaGlhIGNvbSBkYWdlc3RhbiBlLWJ1cmcgZWR1IGdvdiBncm96bnkgaW50IGlya3V0c2sgaXZhbm92byBpemhldnNrIGphciBqb3Noa2FyLW9sYSBrYWxteWtpYSBrYWx1Z2Ega2FtY2hhdGthIGthcmVsaWEga2F6YW4ga2NociBrZW1lcm92byBraGFiYXJvdnNrIGtoYWthc3NpYSBraHYga2lyb3Yga29lbmlnIGtvbWkga29zdHJvbWEga3Jhbm95YXJzayBrdWJhbiBrdXJnYW4ga3Vyc2sgbGlwZXRzayBtYWdhZGFuIG1hcmkgbWFyaS1lbCBtYXJpbmUgbWlsIG1vcmRvdmlhIG1vc3JlZyBtc2sgbXVybWFuc2sgbmFsY2hpayBuZXQgbm5vdiBub3Ygbm92b3NpYmlyc2sgbnNrIG9tc2sgb3JlbmJ1cmcgb3JnIG9yeW9sIHBlbnphIHBlcm0gcHAgcHNrb3YgcHR6IHJuZCByeWF6YW4gc2FraGFsaW4gc2FtYXJhIHNhcmF0b3Ygc2ltYmlyc2sgc21vbGVuc2sgc3BiIHN0YXZyb3BvbCBzdHYgc3VyZ3V0IHRhbWJvdiB0YXRhcnN0YW4gdG9tIHRvbXNrIHRzYXJpdHN5biB0c2sgdHVsYSB0dXZhIHR2ZXIgdHl1bWVuIHVkbSB1ZG11cnRpYSB1bGFuLXVkZSB2bGFkaWthdmtheiB2bGFkaW1pciB2bGFkaXZvc3RvayB2b2xnb2dyYWQgdm9sb2dkYSB2b3JvbmV6aCB2cm4gdnlhdGthIHlha3V0aWEgeWFtYWwgeWVrYXRlcmluYnVyZyB5dXpobm8tc2FraGFsaW5zayAnLFxuICAgICAgJ3J3JzonIGFjIGNvIGNvbSBlZHUgZ291diBnb3YgaW50IG1pbCBuZXQgJyxcbiAgICAgICdzYSc6JyBjb20gZWR1IGdvdiBtZWQgbmV0IG9yZyBwdWIgc2NoICcsXG4gICAgICAnc2QnOicgY29tIGVkdSBnb3YgaW5mbyBtZWQgbmV0IG9yZyB0diAnLFxuICAgICAgJ3NlJzonIGEgYWMgYiBiZCBjIGQgZSBmIGcgaCBpIGsgbCBtIG4gbyBvcmcgcCBwYXJ0aSBwcCBwcmVzcyByIHMgdCB0bSB1IHcgeCB5IHogJyxcbiAgICAgICdzZyc6JyBjb20gZWR1IGdvdiBpZG4gbmV0IG9yZyBwZXIgJyxcbiAgICAgICdzbic6JyBhcnQgY29tIGVkdSBnb3V2IG9yZyBwZXJzbyB1bml2ICcsXG4gICAgICAnc3knOicgY29tIGVkdSBnb3YgbWlsIG5ldCBuZXdzIG9yZyAnLFxuICAgICAgJ3RoJzonIGFjIGNvIGdvIGluIG1pIG5ldCBvciAnLFxuICAgICAgJ3RqJzonIGFjIGJpeiBjbyBjb20gZWR1IGdvIGdvdiBpbmZvIGludCBtaWwgbmFtZSBuZXQgbmljIG9yZyB0ZXN0IHdlYiAnLFxuICAgICAgJ3RuJzonIGFncmluZXQgY29tIGRlZmVuc2UgZWR1bmV0IGVucyBmaW4gZ292IGluZCBpbmZvIGludGwgbWluY29tIG5hdCBuZXQgb3JnIHBlcnNvIHJucnQgcm5zIHJudSB0b3VyaXNtICcsXG4gICAgICAndHonOicgYWMgY28gZ28gbmUgb3IgJyxcbiAgICAgICd1YSc6JyBiaXogY2hlcmthc3N5IGNoZXJuaWdvdiBjaGVybm92dHN5IGNrIGNuIGNvIGNvbSBjcmltZWEgY3YgZG4gZG5lcHJvcGV0cm92c2sgZG9uZXRzayBkcCBlZHUgZ292IGlmIGluIGl2YW5vLWZyYW5raXZzayBraCBraGFya292IGtoZXJzb24ga2htZWxuaXRza2l5IGtpZXYga2lyb3ZvZ3JhZCBrbSBrciBrcyBrdiBsZyBsdWdhbnNrIGx1dHNrIGx2aXYgbWUgbWsgbmV0IG5pa29sYWV2IG9kIG9kZXNzYSBvcmcgcGwgcG9sdGF2YSBwcCByb3ZubyBydiBzZWJhc3RvcG9sIHN1bXkgdGUgdGVybm9waWwgdXpoZ29yb2QgdmlubmljYSB2biB6YXBvcml6aHpoZSB6aGl0b21pciB6cCB6dCAnLFxuICAgICAgJ3VnJzonIGFjIGNvIGdvIG5lIG9yIG9yZyBzYyAnLFxuICAgICAgJ3VrJzonIGFjIGJsIGJyaXRpc2gtbGlicmFyeSBjbyBjeW0gZ292IGdvdnQgaWNuZXQgamV0IGxlYSBsdGQgbWUgbWlsIG1vZCBuYXRpb25hbC1saWJyYXJ5LXNjb3RsYW5kIG5lbCBuZXQgbmhzIG5pYyBubHMgb3JnIG9yZ24gcGFybGlhbWVudCBwbGMgcG9saWNlIHNjaCBzY290IHNvYyAnLFxuICAgICAgJ3VzJzonIGRuaSBmZWQgaXNhIGtpZHMgbnNuICcsXG4gICAgICAndXknOicgY29tIGVkdSBndWIgbWlsIG5ldCBvcmcgJyxcbiAgICAgICd2ZSc6JyBjbyBjb20gZWR1IGdvYiBpbmZvIG1pbCBuZXQgb3JnIHdlYiAnLFxuICAgICAgJ3ZpJzonIGNvIGNvbSBrMTIgbmV0IG9yZyAnLFxuICAgICAgJ3ZuJzonIGFjIGJpeiBjb20gZWR1IGdvdiBoZWFsdGggaW5mbyBpbnQgbmFtZSBuZXQgb3JnIHBybyAnLFxuICAgICAgJ3llJzonIGNvIGNvbSBnb3YgbHRkIG1lIG5ldCBvcmcgcGxjICcsXG4gICAgICAneXUnOicgYWMgY28gZWR1IGdvdiBvcmcgJyxcbiAgICAgICd6YSc6JyBhYyBhZ3JpYyBhbHQgYm91cnNlIGNpdHkgY28gY3liZXJuZXQgZGIgZWR1IGdvdiBncm9uZGFyIGlhY2Nlc3MgaW10IGluY2EgbGFuZGVzaWduIGxhdyBtaWwgbmV0IG5nbyBuaXMgbm9tIG9saXZldHRpIG9yZyBwaXggc2Nob29sIHRtIHdlYiAnLFxuICAgICAgJ3ptJzonIGFjIGNvIGNvbSBlZHUgZ292IG5ldCBvcmcgc2NoICdcbiAgICB9LFxuICAgIC8vIGdvcmhpbGwgMjAxMy0xMC0yNTogVXNpbmcgaW5kZXhPZigpIGluc3RlYWQgUmVnZXhwKCkuIFNpZ25pZmljYW50IGJvb3N0XG4gICAgLy8gaW4gYm90aCBwZXJmb3JtYW5jZSBhbmQgbWVtb3J5IGZvb3RwcmludC4gTm8gaW5pdGlhbGl6YXRpb24gcmVxdWlyZWQuXG4gICAgLy8gaHR0cDovL2pzcGVyZi5jb20vdXJpLWpzLXNsZC1yZWdleC12cy1iaW5hcnktc2VhcmNoLzRcbiAgICAvLyBGb2xsb3dpbmcgbWV0aG9kcyB1c2UgbGFzdEluZGV4T2YoKSByYXRoZXIgdGhhbiBhcnJheS5zcGxpdCgpIGluIG9yZGVyXG4gICAgLy8gdG8gYXZvaWQgYW55IG1lbW9yeSBhbGxvY2F0aW9ucy5cbiAgICBoYXM6IGZ1bmN0aW9uKGRvbWFpbikge1xuICAgICAgdmFyIHRsZE9mZnNldCA9IGRvbWFpbi5sYXN0SW5kZXhPZignLicpO1xuICAgICAgaWYgKHRsZE9mZnNldCA8PSAwIHx8IHRsZE9mZnNldCA+PSAoZG9tYWluLmxlbmd0aC0xKSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICB9XG4gICAgICB2YXIgc2xkT2Zmc2V0ID0gZG9tYWluLmxhc3RJbmRleE9mKCcuJywgdGxkT2Zmc2V0LTEpO1xuICAgICAgaWYgKHNsZE9mZnNldCA8PSAwIHx8IHNsZE9mZnNldCA+PSAodGxkT2Zmc2V0LTEpKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgIH1cbiAgICAgIHZhciBzbGRMaXN0ID0gU0xELmxpc3RbZG9tYWluLnNsaWNlKHRsZE9mZnNldCsxKV07XG4gICAgICBpZiAoIXNsZExpc3QpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHNsZExpc3QuaW5kZXhPZignICcgKyBkb21haW4uc2xpY2Uoc2xkT2Zmc2V0KzEsIHRsZE9mZnNldCkgKyAnICcpID49IDA7XG4gICAgfSxcbiAgICBpczogZnVuY3Rpb24oZG9tYWluKSB7XG4gICAgICB2YXIgdGxkT2Zmc2V0ID0gZG9tYWluLmxhc3RJbmRleE9mKCcuJyk7XG4gICAgICBpZiAodGxkT2Zmc2V0IDw9IDAgfHwgdGxkT2Zmc2V0ID49IChkb21haW4ubGVuZ3RoLTEpKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgIH1cbiAgICAgIHZhciBzbGRPZmZzZXQgPSBkb21haW4ubGFzdEluZGV4T2YoJy4nLCB0bGRPZmZzZXQtMSk7XG4gICAgICBpZiAoc2xkT2Zmc2V0ID49IDApIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgfVxuICAgICAgdmFyIHNsZExpc3QgPSBTTEQubGlzdFtkb21haW4uc2xpY2UodGxkT2Zmc2V0KzEpXTtcbiAgICAgIGlmICghc2xkTGlzdCkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICB9XG4gICAgICByZXR1cm4gc2xkTGlzdC5pbmRleE9mKCcgJyArIGRvbWFpbi5zbGljZSgwLCB0bGRPZmZzZXQpICsgJyAnKSA+PSAwO1xuICAgIH0sXG4gICAgZ2V0OiBmdW5jdGlvbihkb21haW4pIHtcbiAgICAgIHZhciB0bGRPZmZzZXQgPSBkb21haW4ubGFzdEluZGV4T2YoJy4nKTtcbiAgICAgIGlmICh0bGRPZmZzZXQgPD0gMCB8fCB0bGRPZmZzZXQgPj0gKGRvbWFpbi5sZW5ndGgtMSkpIHtcbiAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICB9XG4gICAgICB2YXIgc2xkT2Zmc2V0ID0gZG9tYWluLmxhc3RJbmRleE9mKCcuJywgdGxkT2Zmc2V0LTEpO1xuICAgICAgaWYgKHNsZE9mZnNldCA8PSAwIHx8IHNsZE9mZnNldCA+PSAodGxkT2Zmc2V0LTEpKSB7XG4gICAgICAgIHJldHVybiBudWxsO1xuICAgICAgfVxuICAgICAgdmFyIHNsZExpc3QgPSBTTEQubGlzdFtkb21haW4uc2xpY2UodGxkT2Zmc2V0KzEpXTtcbiAgICAgIGlmICghc2xkTGlzdCkge1xuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgIH1cbiAgICAgIGlmIChzbGRMaXN0LmluZGV4T2YoJyAnICsgZG9tYWluLnNsaWNlKHNsZE9mZnNldCsxLCB0bGRPZmZzZXQpICsgJyAnKSA8IDApIHtcbiAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICB9XG4gICAgICByZXR1cm4gZG9tYWluLnNsaWNlKHNsZE9mZnNldCsxKTtcbiAgICB9LFxuICAgIG5vQ29uZmxpY3Q6IGZ1bmN0aW9uKCl7XG4gICAgICBpZiAocm9vdC5TZWNvbmRMZXZlbERvbWFpbnMgPT09IHRoaXMpIHtcbiAgICAgICAgcm9vdC5TZWNvbmRMZXZlbERvbWFpbnMgPSBfU2Vjb25kTGV2ZWxEb21haW5zO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuICB9O1xuXG4gIHJldHVybiBTTEQ7XG59KSk7XG4iLCIvKiFcbiAqIFVSSS5qcyAtIE11dGF0aW5nIFVSTHNcbiAqXG4gKiBWZXJzaW9uOiAxLjE3LjBcbiAqXG4gKiBBdXRob3I6IFJvZG5leSBSZWhtXG4gKiBXZWI6IGh0dHA6Ly9tZWRpYWxpemUuZ2l0aHViLmlvL1VSSS5qcy9cbiAqXG4gKiBMaWNlbnNlZCB1bmRlclxuICogICBNSVQgTGljZW5zZSBodHRwOi8vd3d3Lm9wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL21pdC1saWNlbnNlXG4gKiAgIEdQTCB2MyBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvR1BMLTMuMFxuICpcbiAqL1xuKGZ1bmN0aW9uIChyb290LCBmYWN0b3J5KSB7XG4gICd1c2Ugc3RyaWN0JztcbiAgLy8gaHR0cHM6Ly9naXRodWIuY29tL3VtZGpzL3VtZC9ibG9iL21hc3Rlci9yZXR1cm5FeHBvcnRzLmpzXG4gIGlmICh0eXBlb2YgZXhwb3J0cyA9PT0gJ29iamVjdCcpIHtcbiAgICAvLyBOb2RlXG4gICAgbW9kdWxlLmV4cG9ydHMgPSBmYWN0b3J5KHJlcXVpcmUoJy4vcHVueWNvZGUnKSwgcmVxdWlyZSgnLi9JUHY2JyksIHJlcXVpcmUoJy4vU2Vjb25kTGV2ZWxEb21haW5zJykpO1xuICB9IGVsc2UgaWYgKHR5cGVvZiBkZWZpbmUgPT09ICdmdW5jdGlvbicgJiYgZGVmaW5lLmFtZCkge1xuICAgIC8vIEFNRC4gUmVnaXN0ZXIgYXMgYW4gYW5vbnltb3VzIG1vZHVsZS5cbiAgICBkZWZpbmUoWycuL3B1bnljb2RlJywgJy4vSVB2NicsICcuL1NlY29uZExldmVsRG9tYWlucyddLCBmYWN0b3J5KTtcbiAgfSBlbHNlIHtcbiAgICAvLyBCcm93c2VyIGdsb2JhbHMgKHJvb3QgaXMgd2luZG93KVxuICAgIHJvb3QuVVJJID0gZmFjdG9yeShyb290LnB1bnljb2RlLCByb290LklQdjYsIHJvb3QuU2Vjb25kTGV2ZWxEb21haW5zLCByb290KTtcbiAgfVxufSh0aGlzLCBmdW5jdGlvbiAocHVueWNvZGUsIElQdjYsIFNMRCwgcm9vdCkge1xuICAndXNlIHN0cmljdCc7XG4gIC8qZ2xvYmFsIGxvY2F0aW9uLCBlc2NhcGUsIHVuZXNjYXBlICovXG4gIC8vIEZJWE1FOiB2Mi4wLjAgcmVuYW1jZSBub24tY2FtZWxDYXNlIHByb3BlcnRpZXMgdG8gdXBwZXJjYXNlXG4gIC8qanNoaW50IGNhbWVsY2FzZTogZmFsc2UgKi9cblxuICAvLyBzYXZlIGN1cnJlbnQgVVJJIHZhcmlhYmxlLCBpZiBhbnlcbiAgdmFyIF9VUkkgPSByb290ICYmIHJvb3QuVVJJO1xuXG4gIGZ1bmN0aW9uIFVSSSh1cmwsIGJhc2UpIHtcbiAgICB2YXIgX3VybFN1cHBsaWVkID0gYXJndW1lbnRzLmxlbmd0aCA+PSAxO1xuICAgIHZhciBfYmFzZVN1cHBsaWVkID0gYXJndW1lbnRzLmxlbmd0aCA+PSAyO1xuXG4gICAgLy8gQWxsb3cgaW5zdGFudGlhdGlvbiB3aXRob3V0IHRoZSAnbmV3JyBrZXl3b3JkXG4gICAgaWYgKCEodGhpcyBpbnN0YW5jZW9mIFVSSSkpIHtcbiAgICAgIGlmIChfdXJsU3VwcGxpZWQpIHtcbiAgICAgICAgaWYgKF9iYXNlU3VwcGxpZWQpIHtcbiAgICAgICAgICByZXR1cm4gbmV3IFVSSSh1cmwsIGJhc2UpO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIG5ldyBVUkkodXJsKTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIG5ldyBVUkkoKTtcbiAgICB9XG5cbiAgICBpZiAodXJsID09PSB1bmRlZmluZWQpIHtcbiAgICAgIGlmIChfdXJsU3VwcGxpZWQpIHtcbiAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcigndW5kZWZpbmVkIGlzIG5vdCBhIHZhbGlkIGFyZ3VtZW50IGZvciBVUkknKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHR5cGVvZiBsb2NhdGlvbiAhPT0gJ3VuZGVmaW5lZCcpIHtcbiAgICAgICAgdXJsID0gbG9jYXRpb24uaHJlZiArICcnO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdXJsID0gJyc7XG4gICAgICB9XG4gICAgfVxuXG4gICAgdGhpcy5ocmVmKHVybCk7XG5cbiAgICAvLyByZXNvbHZlIHRvIGJhc2UgYWNjb3JkaW5nIHRvIGh0dHA6Ly9kdmNzLnczLm9yZy9oZy91cmwvcmF3LWZpbGUvdGlwL092ZXJ2aWV3Lmh0bWwjY29uc3RydWN0b3JcbiAgICBpZiAoYmFzZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICByZXR1cm4gdGhpcy5hYnNvbHV0ZVRvKGJhc2UpO1xuICAgIH1cblxuICAgIHJldHVybiB0aGlzO1xuICB9XG5cbiAgVVJJLnZlcnNpb24gPSAnMS4xNy4wJztcblxuICB2YXIgcCA9IFVSSS5wcm90b3R5cGU7XG4gIHZhciBoYXNPd24gPSBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5O1xuXG4gIGZ1bmN0aW9uIGVzY2FwZVJlZ0V4KHN0cmluZykge1xuICAgIC8vIGh0dHBzOi8vZ2l0aHViLmNvbS9tZWRpYWxpemUvVVJJLmpzL2NvbW1pdC84NWFjMjE3ODNjMTFmOGNjYWIwNjEwNmRiYTk3MzVhMzFhODY5MjRkI2NvbW1pdGNvbW1lbnQtODIxOTYzXG4gICAgcmV0dXJuIHN0cmluZy5yZXBsYWNlKC8oWy4qKz9ePSE6JHt9KCl8W1xcXVxcL1xcXFxdKS9nLCAnXFxcXCQxJyk7XG4gIH1cblxuICBmdW5jdGlvbiBnZXRUeXBlKHZhbHVlKSB7XG4gICAgLy8gSUU4IGRvZXNuJ3QgcmV0dXJuIFtPYmplY3QgVW5kZWZpbmVkXSBidXQgW09iamVjdCBPYmplY3RdIGZvciB1bmRlZmluZWQgdmFsdWVcbiAgICBpZiAodmFsdWUgPT09IHVuZGVmaW5lZCkge1xuICAgICAgcmV0dXJuICdVbmRlZmluZWQnO1xuICAgIH1cblxuICAgIHJldHVybiBTdHJpbmcoT2JqZWN0LnByb3RvdHlwZS50b1N0cmluZy5jYWxsKHZhbHVlKSkuc2xpY2UoOCwgLTEpO1xuICB9XG5cbiAgZnVuY3Rpb24gaXNBcnJheShvYmopIHtcbiAgICByZXR1cm4gZ2V0VHlwZShvYmopID09PSAnQXJyYXknO1xuICB9XG5cbiAgZnVuY3Rpb24gZmlsdGVyQXJyYXlWYWx1ZXMoZGF0YSwgdmFsdWUpIHtcbiAgICB2YXIgbG9va3VwID0ge307XG4gICAgdmFyIGksIGxlbmd0aDtcblxuICAgIGlmIChnZXRUeXBlKHZhbHVlKSA9PT0gJ1JlZ0V4cCcpIHtcbiAgICAgIGxvb2t1cCA9IG51bGw7XG4gICAgfSBlbHNlIGlmIChpc0FycmF5KHZhbHVlKSkge1xuICAgICAgZm9yIChpID0gMCwgbGVuZ3RoID0gdmFsdWUubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgbG9va3VwW3ZhbHVlW2ldXSA9IHRydWU7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIGxvb2t1cFt2YWx1ZV0gPSB0cnVlO1xuICAgIH1cblxuICAgIGZvciAoaSA9IDAsIGxlbmd0aCA9IGRhdGEubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgIC8qanNoaW50IGxheGJyZWFrOiB0cnVlICovXG4gICAgICB2YXIgX21hdGNoID0gbG9va3VwICYmIGxvb2t1cFtkYXRhW2ldXSAhPT0gdW5kZWZpbmVkXG4gICAgICAgIHx8ICFsb29rdXAgJiYgdmFsdWUudGVzdChkYXRhW2ldKTtcbiAgICAgIC8qanNoaW50IGxheGJyZWFrOiBmYWxzZSAqL1xuICAgICAgaWYgKF9tYXRjaCkge1xuICAgICAgICBkYXRhLnNwbGljZShpLCAxKTtcbiAgICAgICAgbGVuZ3RoLS07XG4gICAgICAgIGktLTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gZGF0YTtcbiAgfVxuXG4gIGZ1bmN0aW9uIGFycmF5Q29udGFpbnMobGlzdCwgdmFsdWUpIHtcbiAgICB2YXIgaSwgbGVuZ3RoO1xuXG4gICAgLy8gdmFsdWUgbWF5IGJlIHN0cmluZywgbnVtYmVyLCBhcnJheSwgcmVnZXhwXG4gICAgaWYgKGlzQXJyYXkodmFsdWUpKSB7XG4gICAgICAvLyBOb3RlOiB0aGlzIGNhbiBiZSBvcHRpbWl6ZWQgdG8gTyhuKSAoaW5zdGVhZCBvZiBjdXJyZW50IE8obSAqIG4pKVxuICAgICAgZm9yIChpID0gMCwgbGVuZ3RoID0gdmFsdWUubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgaWYgKCFhcnJheUNvbnRhaW5zKGxpc3QsIHZhbHVlW2ldKSkge1xuICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9XG5cbiAgICB2YXIgX3R5cGUgPSBnZXRUeXBlKHZhbHVlKTtcbiAgICBmb3IgKGkgPSAwLCBsZW5ndGggPSBsaXN0Lmxlbmd0aDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICBpZiAoX3R5cGUgPT09ICdSZWdFeHAnKSB7XG4gICAgICAgIGlmICh0eXBlb2YgbGlzdFtpXSA9PT0gJ3N0cmluZycgJiYgbGlzdFtpXS5tYXRjaCh2YWx1ZSkpIHtcbiAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIGlmIChsaXN0W2ldID09PSB2YWx1ZSkge1xuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICBmdW5jdGlvbiBhcnJheXNFcXVhbChvbmUsIHR3bykge1xuICAgIGlmICghaXNBcnJheShvbmUpIHx8ICFpc0FycmF5KHR3bykpIHtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICAvLyBhcnJheXMgY2FuJ3QgYmUgZXF1YWwgaWYgdGhleSBoYXZlIGRpZmZlcmVudCBhbW91bnQgb2YgY29udGVudFxuICAgIGlmIChvbmUubGVuZ3RoICE9PSB0d28ubGVuZ3RoKSB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgb25lLnNvcnQoKTtcbiAgICB0d28uc29ydCgpO1xuXG4gICAgZm9yICh2YXIgaSA9IDAsIGwgPSBvbmUubGVuZ3RoOyBpIDwgbDsgaSsrKSB7XG4gICAgICBpZiAob25lW2ldICE9PSB0d29baV0pIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiB0cnVlO1xuICB9XG5cbiAgZnVuY3Rpb24gdHJpbVNsYXNoZXModGV4dCkge1xuICAgIHZhciB0cmltX2V4cHJlc3Npb24gPSAvXlxcLyt8XFwvKyQvZztcbiAgICByZXR1cm4gdGV4dC5yZXBsYWNlKHRyaW1fZXhwcmVzc2lvbiwgJycpO1xuICB9XG5cbiAgVVJJLl9wYXJ0cyA9IGZ1bmN0aW9uKCkge1xuICAgIHJldHVybiB7XG4gICAgICBwcm90b2NvbDogbnVsbCxcbiAgICAgIHVzZXJuYW1lOiBudWxsLFxuICAgICAgcGFzc3dvcmQ6IG51bGwsXG4gICAgICBob3N0bmFtZTogbnVsbCxcbiAgICAgIHVybjogbnVsbCxcbiAgICAgIHBvcnQ6IG51bGwsXG4gICAgICBwYXRoOiBudWxsLFxuICAgICAgcXVlcnk6IG51bGwsXG4gICAgICBmcmFnbWVudDogbnVsbCxcbiAgICAgIC8vIHN0YXRlXG4gICAgICBkdXBsaWNhdGVRdWVyeVBhcmFtZXRlcnM6IFVSSS5kdXBsaWNhdGVRdWVyeVBhcmFtZXRlcnMsXG4gICAgICBlc2NhcGVRdWVyeVNwYWNlOiBVUkkuZXNjYXBlUXVlcnlTcGFjZVxuICAgIH07XG4gIH07XG4gIC8vIHN0YXRlOiBhbGxvdyBkdXBsaWNhdGUgcXVlcnkgcGFyYW1ldGVycyAoYT0xJmE9MSlcbiAgVVJJLmR1cGxpY2F0ZVF1ZXJ5UGFyYW1ldGVycyA9IGZhbHNlO1xuICAvLyBzdGF0ZTogcmVwbGFjZXMgKyB3aXRoICUyMCAoc3BhY2UgaW4gcXVlcnkgc3RyaW5ncylcbiAgVVJJLmVzY2FwZVF1ZXJ5U3BhY2UgPSB0cnVlO1xuICAvLyBzdGF0aWMgcHJvcGVydGllc1xuICBVUkkucHJvdG9jb2xfZXhwcmVzc2lvbiA9IC9eW2Etel1bYS16MC05ListXSokL2k7XG4gIFVSSS5pZG5fZXhwcmVzc2lvbiA9IC9bXmEtejAtOVxcLi1dL2k7XG4gIFVSSS5wdW55Y29kZV9leHByZXNzaW9uID0gLyh4bi0tKS9pO1xuICAvLyB3ZWxsLCAzMzMuNDQ0LjU1NS42NjYgbWF0Y2hlcywgYnV0IGl0IHN1cmUgYWluJ3Qgbm8gSVB2NCAtIGRvIHdlIGNhcmU/XG4gIFVSSS5pcDRfZXhwcmVzc2lvbiA9IC9eXFxkezEsM31cXC5cXGR7MSwzfVxcLlxcZHsxLDN9XFwuXFxkezEsM30kLztcbiAgLy8gY3JlZGl0cyB0byBSaWNoIEJyb3duXG4gIC8vIHNvdXJjZTogaHR0cDovL2ZvcnVtcy5pbnRlcm1hcHBlci5jb20vdmlld3RvcGljLnBocD9wPTEwOTYjMTA5NlxuICAvLyBzcGVjaWZpY2F0aW9uOiBodHRwOi8vd3d3LmlldGYub3JnL3JmYy9yZmM0MjkxLnR4dFxuICBVUkkuaXA2X2V4cHJlc3Npb24gPSAvXlxccyooKChbMC05QS1GYS1mXXsxLDR9Oil7N30oWzAtOUEtRmEtZl17MSw0fXw6KSl8KChbMC05QS1GYS1mXXsxLDR9Oil7Nn0oOlswLTlBLUZhLWZdezEsNH18KCgyNVswLTVdfDJbMC00XVxcZHwxXFxkXFxkfFsxLTldP1xcZCkoXFwuKDI1WzAtNV18MlswLTRdXFxkfDFcXGRcXGR8WzEtOV0/XFxkKSl7M30pfDopKXwoKFswLTlBLUZhLWZdezEsNH06KXs1fSgoKDpbMC05QS1GYS1mXXsxLDR9KXsxLDJ9KXw6KCgyNVswLTVdfDJbMC00XVxcZHwxXFxkXFxkfFsxLTldP1xcZCkoXFwuKDI1WzAtNV18MlswLTRdXFxkfDFcXGRcXGR8WzEtOV0/XFxkKSl7M30pfDopKXwoKFswLTlBLUZhLWZdezEsNH06KXs0fSgoKDpbMC05QS1GYS1mXXsxLDR9KXsxLDN9KXwoKDpbMC05QS1GYS1mXXsxLDR9KT86KCgyNVswLTVdfDJbMC00XVxcZHwxXFxkXFxkfFsxLTldP1xcZCkoXFwuKDI1WzAtNV18MlswLTRdXFxkfDFcXGRcXGR8WzEtOV0/XFxkKSl7M30pKXw6KSl8KChbMC05QS1GYS1mXXsxLDR9Oil7M30oKCg6WzAtOUEtRmEtZl17MSw0fSl7MSw0fSl8KCg6WzAtOUEtRmEtZl17MSw0fSl7MCwyfTooKDI1WzAtNV18MlswLTRdXFxkfDFcXGRcXGR8WzEtOV0/XFxkKShcXC4oMjVbMC01XXwyWzAtNF1cXGR8MVxcZFxcZHxbMS05XT9cXGQpKXszfSkpfDopKXwoKFswLTlBLUZhLWZdezEsNH06KXsyfSgoKDpbMC05QS1GYS1mXXsxLDR9KXsxLDV9KXwoKDpbMC05QS1GYS1mXXsxLDR9KXswLDN9OigoMjVbMC01XXwyWzAtNF1cXGR8MVxcZFxcZHxbMS05XT9cXGQpKFxcLigyNVswLTVdfDJbMC00XVxcZHwxXFxkXFxkfFsxLTldP1xcZCkpezN9KSl8OikpfCgoWzAtOUEtRmEtZl17MSw0fTopezF9KCgoOlswLTlBLUZhLWZdezEsNH0pezEsNn0pfCgoOlswLTlBLUZhLWZdezEsNH0pezAsNH06KCgyNVswLTVdfDJbMC00XVxcZHwxXFxkXFxkfFsxLTldP1xcZCkoXFwuKDI1WzAtNV18MlswLTRdXFxkfDFcXGRcXGR8WzEtOV0/XFxkKSl7M30pKXw6KSl8KDooKCg6WzAtOUEtRmEtZl17MSw0fSl7MSw3fSl8KCg6WzAtOUEtRmEtZl17MSw0fSl7MCw1fTooKDI1WzAtNV18MlswLTRdXFxkfDFcXGRcXGR8WzEtOV0/XFxkKShcXC4oMjVbMC01XXwyWzAtNF1cXGR8MVxcZFxcZHxbMS05XT9cXGQpKXszfSkpfDopKSkoJS4rKT9cXHMqJC87XG4gIC8vIGV4cHJlc3Npb24gdXNlZCBpcyBcImdydWJlciByZXZpc2VkXCIgKEBncnViZXIgdjIpIGRldGVybWluZWQgdG8gYmUgdGhlXG4gIC8vIGJlc3Qgc29sdXRpb24gaW4gYSByZWdleC1nb2xmIHdlIGRpZCBhIGNvdXBsZSBvZiBhZ2VzIGFnbyBhdFxuICAvLyAqIGh0dHA6Ly9tYXRoaWFzYnluZW5zLmJlL2RlbW8vdXJsLXJlZ2V4XG4gIC8vICogaHR0cDovL3JvZG5leXJlaG0uZGUvdC91cmwtcmVnZXguaHRtbFxuICBVUkkuZmluZF91cmlfZXhwcmVzc2lvbiA9IC9cXGIoKD86W2Etel1bXFx3LV0rOig/OlxcL3sxLDN9fFthLXowLTklXSl8d3d3XFxkezAsM31bLl18W2EtejAtOS5cXC1dK1suXVthLXpdezIsNH1cXC8pKD86W15cXHMoKTw+XSt8XFwoKFteXFxzKCk8Pl0rfChcXChbXlxccygpPD5dK1xcKSkpKlxcKSkrKD86XFwoKFteXFxzKCk8Pl0rfChcXChbXlxccygpPD5dK1xcKSkpKlxcKXxbXlxcc2AhKClcXFtcXF17fTs6J1wiLiw8Pj/Cq8K74oCc4oCd4oCY4oCZXSkpL2lnO1xuICBVUkkuZmluZFVyaSA9IHtcbiAgICAvLyB2YWxpZCBcInNjaGVtZTovL1wiIG9yIFwid3d3LlwiXG4gICAgc3RhcnQ6IC9cXGIoPzooW2Etel1bYS16MC05ListXSo6XFwvXFwvKXx3d3dcXC4pL2dpLFxuICAgIC8vIGV2ZXJ5dGhpbmcgdXAgdG8gdGhlIG5leHQgd2hpdGVzcGFjZVxuICAgIGVuZDogL1tcXHNcXHJcXG5dfCQvLFxuICAgIC8vIHRyaW0gdHJhaWxpbmcgcHVuY3R1YXRpb24gY2FwdHVyZWQgYnkgZW5kIFJlZ0V4cFxuICAgIHRyaW06IC9bYCEoKVxcW1xcXXt9OzonXCIuLDw+P8KrwrvigJzigJ3igJ7igJjigJldKyQvXG4gIH07XG4gIC8vIGh0dHA6Ly93d3cuaWFuYS5vcmcvYXNzaWdubWVudHMvdXJpLXNjaGVtZXMuaHRtbFxuICAvLyBodHRwOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0xpc3Rfb2ZfVENQX2FuZF9VRFBfcG9ydF9udW1iZXJzI1dlbGwta25vd25fcG9ydHNcbiAgVVJJLmRlZmF1bHRQb3J0cyA9IHtcbiAgICBodHRwOiAnODAnLFxuICAgIGh0dHBzOiAnNDQzJyxcbiAgICBmdHA6ICcyMScsXG4gICAgZ29waGVyOiAnNzAnLFxuICAgIHdzOiAnODAnLFxuICAgIHdzczogJzQ0MydcbiAgfTtcbiAgLy8gYWxsb3dlZCBob3N0bmFtZSBjaGFyYWN0ZXJzIGFjY29yZGluZyB0byBSRkMgMzk4NlxuICAvLyBBTFBIQSBESUdJVCBcIi1cIiBcIi5cIiBcIl9cIiBcIn5cIiBcIiFcIiBcIiRcIiBcIiZcIiBcIidcIiBcIihcIiBcIilcIiBcIipcIiBcIitcIiBcIixcIiBcIjtcIiBcIj1cIiAlZW5jb2RlZFxuICAvLyBJJ3ZlIG5ldmVyIHNlZW4gYSAobm9uLUlETikgaG9zdG5hbWUgb3RoZXIgdGhhbjogQUxQSEEgRElHSVQgLiAtXG4gIFVSSS5pbnZhbGlkX2hvc3RuYW1lX2NoYXJhY3RlcnMgPSAvW15hLXpBLVowLTlcXC4tXS87XG4gIC8vIG1hcCBET00gRWxlbWVudHMgdG8gdGhlaXIgVVJJIGF0dHJpYnV0ZVxuICBVUkkuZG9tQXR0cmlidXRlcyA9IHtcbiAgICAnYSc6ICdocmVmJyxcbiAgICAnYmxvY2txdW90ZSc6ICdjaXRlJyxcbiAgICAnbGluayc6ICdocmVmJyxcbiAgICAnYmFzZSc6ICdocmVmJyxcbiAgICAnc2NyaXB0JzogJ3NyYycsXG4gICAgJ2Zvcm0nOiAnYWN0aW9uJyxcbiAgICAnaW1nJzogJ3NyYycsXG4gICAgJ2FyZWEnOiAnaHJlZicsXG4gICAgJ2lmcmFtZSc6ICdzcmMnLFxuICAgICdlbWJlZCc6ICdzcmMnLFxuICAgICdzb3VyY2UnOiAnc3JjJyxcbiAgICAndHJhY2snOiAnc3JjJyxcbiAgICAnaW5wdXQnOiAnc3JjJywgLy8gYnV0IG9ubHkgaWYgdHlwZT1cImltYWdlXCJcbiAgICAnYXVkaW8nOiAnc3JjJyxcbiAgICAndmlkZW8nOiAnc3JjJ1xuICB9O1xuICBVUkkuZ2V0RG9tQXR0cmlidXRlID0gZnVuY3Rpb24obm9kZSkge1xuICAgIGlmICghbm9kZSB8fCAhbm9kZS5ub2RlTmFtZSkge1xuICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICB9XG5cbiAgICB2YXIgbm9kZU5hbWUgPSBub2RlLm5vZGVOYW1lLnRvTG93ZXJDYXNlKCk7XG4gICAgLy8gPGlucHV0PiBzaG91bGQgb25seSBleHBvc2Ugc3JjIGZvciB0eXBlPVwiaW1hZ2VcIlxuICAgIGlmIChub2RlTmFtZSA9PT0gJ2lucHV0JyAmJiBub2RlLnR5cGUgIT09ICdpbWFnZScpIHtcbiAgICAgIHJldHVybiB1bmRlZmluZWQ7XG4gICAgfVxuXG4gICAgcmV0dXJuIFVSSS5kb21BdHRyaWJ1dGVzW25vZGVOYW1lXTtcbiAgfTtcblxuICBmdW5jdGlvbiBlc2NhcGVGb3JEdW1iRmlyZWZveDM2KHZhbHVlKSB7XG4gICAgLy8gaHR0cHM6Ly9naXRodWIuY29tL21lZGlhbGl6ZS9VUkkuanMvaXNzdWVzLzkxXG4gICAgcmV0dXJuIGVzY2FwZSh2YWx1ZSk7XG4gIH1cblxuICAvLyBlbmNvZGluZyAvIGRlY29kaW5nIGFjY29yZGluZyB0byBSRkMzOTg2XG4gIGZ1bmN0aW9uIHN0cmljdEVuY29kZVVSSUNvbXBvbmVudChzdHJpbmcpIHtcbiAgICAvLyBzZWUgaHR0cHM6Ly9kZXZlbG9wZXIubW96aWxsYS5vcmcvZW4tVVMvZG9jcy9KYXZhU2NyaXB0L1JlZmVyZW5jZS9HbG9iYWxfT2JqZWN0cy9lbmNvZGVVUklDb21wb25lbnRcbiAgICByZXR1cm4gZW5jb2RlVVJJQ29tcG9uZW50KHN0cmluZylcbiAgICAgIC5yZXBsYWNlKC9bIScoKSpdL2csIGVzY2FwZUZvckR1bWJGaXJlZm94MzYpXG4gICAgICAucmVwbGFjZSgvXFwqL2csICclMkEnKTtcbiAgfVxuICBVUkkuZW5jb2RlID0gc3RyaWN0RW5jb2RlVVJJQ29tcG9uZW50O1xuICBVUkkuZGVjb2RlID0gZGVjb2RlVVJJQ29tcG9uZW50O1xuICBVUkkuaXNvODg1OSA9IGZ1bmN0aW9uKCkge1xuICAgIFVSSS5lbmNvZGUgPSBlc2NhcGU7XG4gICAgVVJJLmRlY29kZSA9IHVuZXNjYXBlO1xuICB9O1xuICBVUkkudW5pY29kZSA9IGZ1bmN0aW9uKCkge1xuICAgIFVSSS5lbmNvZGUgPSBzdHJpY3RFbmNvZGVVUklDb21wb25lbnQ7XG4gICAgVVJJLmRlY29kZSA9IGRlY29kZVVSSUNvbXBvbmVudDtcbiAgfTtcbiAgVVJJLmNoYXJhY3RlcnMgPSB7XG4gICAgcGF0aG5hbWU6IHtcbiAgICAgIGVuY29kZToge1xuICAgICAgICAvLyBSRkMzOTg2IDIuMTogRm9yIGNvbnNpc3RlbmN5LCBVUkkgcHJvZHVjZXJzIGFuZCBub3JtYWxpemVycyBzaG91bGRcbiAgICAgICAgLy8gdXNlIHVwcGVyY2FzZSBoZXhhZGVjaW1hbCBkaWdpdHMgZm9yIGFsbCBwZXJjZW50LWVuY29kaW5ncy5cbiAgICAgICAgZXhwcmVzc2lvbjogLyUoMjR8MjZ8MkJ8MkN8M0J8M0R8M0F8NDApL2lnLFxuICAgICAgICBtYXA6IHtcbiAgICAgICAgICAvLyAtLl9+IScoKSpcbiAgICAgICAgICAnJTI0JzogJyQnLFxuICAgICAgICAgICclMjYnOiAnJicsXG4gICAgICAgICAgJyUyQic6ICcrJyxcbiAgICAgICAgICAnJTJDJzogJywnLFxuICAgICAgICAgICclM0InOiAnOycsXG4gICAgICAgICAgJyUzRCc6ICc9JyxcbiAgICAgICAgICAnJTNBJzogJzonLFxuICAgICAgICAgICclNDAnOiAnQCdcbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgIGRlY29kZToge1xuICAgICAgICBleHByZXNzaW9uOiAvW1xcL1xcPyNdL2csXG4gICAgICAgIG1hcDoge1xuICAgICAgICAgICcvJzogJyUyRicsXG4gICAgICAgICAgJz8nOiAnJTNGJyxcbiAgICAgICAgICAnIyc6ICclMjMnXG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9LFxuICAgIHJlc2VydmVkOiB7XG4gICAgICBlbmNvZGU6IHtcbiAgICAgICAgLy8gUkZDMzk4NiAyLjE6IEZvciBjb25zaXN0ZW5jeSwgVVJJIHByb2R1Y2VycyBhbmQgbm9ybWFsaXplcnMgc2hvdWxkXG4gICAgICAgIC8vIHVzZSB1cHBlcmNhc2UgaGV4YWRlY2ltYWwgZGlnaXRzIGZvciBhbGwgcGVyY2VudC1lbmNvZGluZ3MuXG4gICAgICAgIGV4cHJlc3Npb246IC8lKDIxfDIzfDI0fDI2fDI3fDI4fDI5fDJBfDJCfDJDfDJGfDNBfDNCfDNEfDNGfDQwfDVCfDVEKS9pZyxcbiAgICAgICAgbWFwOiB7XG4gICAgICAgICAgLy8gZ2VuLWRlbGltc1xuICAgICAgICAgICclM0EnOiAnOicsXG4gICAgICAgICAgJyUyRic6ICcvJyxcbiAgICAgICAgICAnJTNGJzogJz8nLFxuICAgICAgICAgICclMjMnOiAnIycsXG4gICAgICAgICAgJyU1Qic6ICdbJyxcbiAgICAgICAgICAnJTVEJzogJ10nLFxuICAgICAgICAgICclNDAnOiAnQCcsXG4gICAgICAgICAgLy8gc3ViLWRlbGltc1xuICAgICAgICAgICclMjEnOiAnIScsXG4gICAgICAgICAgJyUyNCc6ICckJyxcbiAgICAgICAgICAnJTI2JzogJyYnLFxuICAgICAgICAgICclMjcnOiAnXFwnJyxcbiAgICAgICAgICAnJTI4JzogJygnLFxuICAgICAgICAgICclMjknOiAnKScsXG4gICAgICAgICAgJyUyQSc6ICcqJyxcbiAgICAgICAgICAnJTJCJzogJysnLFxuICAgICAgICAgICclMkMnOiAnLCcsXG4gICAgICAgICAgJyUzQic6ICc7JyxcbiAgICAgICAgICAnJTNEJzogJz0nXG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9LFxuICAgIHVybnBhdGg6IHtcbiAgICAgIC8vIFRoZSBjaGFyYWN0ZXJzIHVuZGVyIGBlbmNvZGVgIGFyZSB0aGUgY2hhcmFjdGVycyBjYWxsZWQgb3V0IGJ5IFJGQyAyMTQxIGFzIGJlaW5nIGFjY2VwdGFibGVcbiAgICAgIC8vIGZvciB1c2FnZSBpbiBhIFVSTi4gUkZDMjE0MSBhbHNvIGNhbGxzIG91dCBcIi1cIiwgXCIuXCIsIGFuZCBcIl9cIiBhcyBhY2NlcHRhYmxlIGNoYXJhY3RlcnMsIGJ1dFxuICAgICAgLy8gdGhlc2UgYXJlbid0IGVuY29kZWQgYnkgZW5jb2RlVVJJQ29tcG9uZW50LCBzbyB3ZSBkb24ndCBoYXZlIHRvIGNhbGwgdGhlbSBvdXQgaGVyZS4gQWxzb1xuICAgICAgLy8gbm90ZSB0aGF0IHRoZSBjb2xvbiBjaGFyYWN0ZXIgaXMgbm90IGZlYXR1cmVkIGluIHRoZSBlbmNvZGluZyBtYXA7IHRoaXMgaXMgYmVjYXVzZSBVUkkuanNcbiAgICAgIC8vIGdpdmVzIHRoZSBjb2xvbnMgaW4gVVJOcyBzZW1hbnRpYyBtZWFuaW5nIGFzIHRoZSBkZWxpbWl0ZXJzIG9mIHBhdGggc2VnZW1lbnRzLCBhbmQgc28gaXRcbiAgICAgIC8vIHNob3VsZCBub3QgYXBwZWFyIHVuZW5jb2RlZCBpbiBhIHNlZ21lbnQgaXRzZWxmLlxuICAgICAgLy8gU2VlIGFsc28gdGhlIG5vdGUgYWJvdmUgYWJvdXQgUkZDMzk4NiBhbmQgY2FwaXRhbGFsaXplZCBoZXggZGlnaXRzLlxuICAgICAgZW5jb2RlOiB7XG4gICAgICAgIGV4cHJlc3Npb246IC8lKDIxfDI0fDI3fDI4fDI5fDJBfDJCfDJDfDNCfDNEfDQwKS9pZyxcbiAgICAgICAgbWFwOiB7XG4gICAgICAgICAgJyUyMSc6ICchJyxcbiAgICAgICAgICAnJTI0JzogJyQnLFxuICAgICAgICAgICclMjcnOiAnXFwnJyxcbiAgICAgICAgICAnJTI4JzogJygnLFxuICAgICAgICAgICclMjknOiAnKScsXG4gICAgICAgICAgJyUyQSc6ICcqJyxcbiAgICAgICAgICAnJTJCJzogJysnLFxuICAgICAgICAgICclMkMnOiAnLCcsXG4gICAgICAgICAgJyUzQic6ICc7JyxcbiAgICAgICAgICAnJTNEJzogJz0nLFxuICAgICAgICAgICclNDAnOiAnQCdcbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgIC8vIFRoZXNlIGNoYXJhY3RlcnMgYXJlIHRoZSBjaGFyYWN0ZXJzIGNhbGxlZCBvdXQgYnkgUkZDMjE0MSBhcyBcInJlc2VydmVkXCIgY2hhcmFjdGVycyB0aGF0XG4gICAgICAvLyBzaG91bGQgbmV2ZXIgYXBwZWFyIGluIGEgVVJOLCBwbHVzIHRoZSBjb2xvbiBjaGFyYWN0ZXIgKHNlZSBub3RlIGFib3ZlKS5cbiAgICAgIGRlY29kZToge1xuICAgICAgICBleHByZXNzaW9uOiAvW1xcL1xcPyM6XS9nLFxuICAgICAgICBtYXA6IHtcbiAgICAgICAgICAnLyc6ICclMkYnLFxuICAgICAgICAgICc/JzogJyUzRicsXG4gICAgICAgICAgJyMnOiAnJTIzJyxcbiAgICAgICAgICAnOic6ICclM0EnXG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH07XG4gIFVSSS5lbmNvZGVRdWVyeSA9IGZ1bmN0aW9uKHN0cmluZywgZXNjYXBlUXVlcnlTcGFjZSkge1xuICAgIHZhciBlc2NhcGVkID0gVVJJLmVuY29kZShzdHJpbmcgKyAnJyk7XG4gICAgaWYgKGVzY2FwZVF1ZXJ5U3BhY2UgPT09IHVuZGVmaW5lZCkge1xuICAgICAgZXNjYXBlUXVlcnlTcGFjZSA9IFVSSS5lc2NhcGVRdWVyeVNwYWNlO1xuICAgIH1cblxuICAgIHJldHVybiBlc2NhcGVRdWVyeVNwYWNlID8gZXNjYXBlZC5yZXBsYWNlKC8lMjAvZywgJysnKSA6IGVzY2FwZWQ7XG4gIH07XG4gIFVSSS5kZWNvZGVRdWVyeSA9IGZ1bmN0aW9uKHN0cmluZywgZXNjYXBlUXVlcnlTcGFjZSkge1xuICAgIHN0cmluZyArPSAnJztcbiAgICBpZiAoZXNjYXBlUXVlcnlTcGFjZSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICBlc2NhcGVRdWVyeVNwYWNlID0gVVJJLmVzY2FwZVF1ZXJ5U3BhY2U7XG4gICAgfVxuXG4gICAgdHJ5IHtcbiAgICAgIHJldHVybiBVUkkuZGVjb2RlKGVzY2FwZVF1ZXJ5U3BhY2UgPyBzdHJpbmcucmVwbGFjZSgvXFwrL2csICclMjAnKSA6IHN0cmluZyk7XG4gICAgfSBjYXRjaChlKSB7XG4gICAgICAvLyB3ZSdyZSBub3QgZ29pbmcgdG8gbWVzcyB3aXRoIHdlaXJkIGVuY29kaW5ncyxcbiAgICAgIC8vIGdpdmUgdXAgYW5kIHJldHVybiB0aGUgdW5kZWNvZGVkIG9yaWdpbmFsIHN0cmluZ1xuICAgICAgLy8gc2VlIGh0dHBzOi8vZ2l0aHViLmNvbS9tZWRpYWxpemUvVVJJLmpzL2lzc3Vlcy84N1xuICAgICAgLy8gc2VlIGh0dHBzOi8vZ2l0aHViLmNvbS9tZWRpYWxpemUvVVJJLmpzL2lzc3Vlcy85MlxuICAgICAgcmV0dXJuIHN0cmluZztcbiAgICB9XG4gIH07XG4gIC8vIGdlbmVyYXRlIGVuY29kZS9kZWNvZGUgcGF0aCBmdW5jdGlvbnNcbiAgdmFyIF9wYXJ0cyA9IHsnZW5jb2RlJzonZW5jb2RlJywgJ2RlY29kZSc6J2RlY29kZSd9O1xuICB2YXIgX3BhcnQ7XG4gIHZhciBnZW5lcmF0ZUFjY2Vzc29yID0gZnVuY3Rpb24oX2dyb3VwLCBfcGFydCkge1xuICAgIHJldHVybiBmdW5jdGlvbihzdHJpbmcpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIHJldHVybiBVUklbX3BhcnRdKHN0cmluZyArICcnKS5yZXBsYWNlKFVSSS5jaGFyYWN0ZXJzW19ncm91cF1bX3BhcnRdLmV4cHJlc3Npb24sIGZ1bmN0aW9uKGMpIHtcbiAgICAgICAgICByZXR1cm4gVVJJLmNoYXJhY3RlcnNbX2dyb3VwXVtfcGFydF0ubWFwW2NdO1xuICAgICAgICB9KTtcbiAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgLy8gd2UncmUgbm90IGdvaW5nIHRvIG1lc3Mgd2l0aCB3ZWlyZCBlbmNvZGluZ3MsXG4gICAgICAgIC8vIGdpdmUgdXAgYW5kIHJldHVybiB0aGUgdW5kZWNvZGVkIG9yaWdpbmFsIHN0cmluZ1xuICAgICAgICAvLyBzZWUgaHR0cHM6Ly9naXRodWIuY29tL21lZGlhbGl6ZS9VUkkuanMvaXNzdWVzLzg3XG4gICAgICAgIC8vIHNlZSBodHRwczovL2dpdGh1Yi5jb20vbWVkaWFsaXplL1VSSS5qcy9pc3N1ZXMvOTJcbiAgICAgICAgcmV0dXJuIHN0cmluZztcbiAgICAgIH1cbiAgICB9O1xuICB9O1xuXG4gIGZvciAoX3BhcnQgaW4gX3BhcnRzKSB7XG4gICAgVVJJW19wYXJ0ICsgJ1BhdGhTZWdtZW50J10gPSBnZW5lcmF0ZUFjY2Vzc29yKCdwYXRobmFtZScsIF9wYXJ0c1tfcGFydF0pO1xuICAgIFVSSVtfcGFydCArICdVcm5QYXRoU2VnbWVudCddID0gZ2VuZXJhdGVBY2Nlc3NvcigndXJucGF0aCcsIF9wYXJ0c1tfcGFydF0pO1xuICB9XG5cbiAgdmFyIGdlbmVyYXRlU2VnbWVudGVkUGF0aEZ1bmN0aW9uID0gZnVuY3Rpb24oX3NlcCwgX2NvZGluZ0Z1bmNOYW1lLCBfaW5uZXJDb2RpbmdGdW5jTmFtZSkge1xuICAgIHJldHVybiBmdW5jdGlvbihzdHJpbmcpIHtcbiAgICAgIC8vIFdoeSBwYXNzIGluIG5hbWVzIG9mIGZ1bmN0aW9ucywgcmF0aGVyIHRoYW4gdGhlIGZ1bmN0aW9uIG9iamVjdHMgdGhlbXNlbHZlcz8gVGhlXG4gICAgICAvLyBkZWZpbml0aW9ucyBvZiBzb21lIGZ1bmN0aW9ucyAoYnV0IGluIHBhcnRpY3VsYXIsIFVSSS5kZWNvZGUpIHdpbGwgb2NjYXNpb25hbGx5IGNoYW5nZSBkdWVcbiAgICAgIC8vIHRvIFVSSS5qcyBoYXZpbmcgSVNPODg1OSBhbmQgVW5pY29kZSBtb2Rlcy4gUGFzc2luZyBpbiB0aGUgbmFtZSBhbmQgZ2V0dGluZyBpdCB3aWxsIGVuc3VyZVxuICAgICAgLy8gdGhhdCB0aGUgZnVuY3Rpb25zIHdlIHVzZSBoZXJlIGFyZSBcImZyZXNoXCIuXG4gICAgICB2YXIgYWN0dWFsQ29kaW5nRnVuYztcbiAgICAgIGlmICghX2lubmVyQ29kaW5nRnVuY05hbWUpIHtcbiAgICAgICAgYWN0dWFsQ29kaW5nRnVuYyA9IFVSSVtfY29kaW5nRnVuY05hbWVdO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgYWN0dWFsQ29kaW5nRnVuYyA9IGZ1bmN0aW9uKHN0cmluZykge1xuICAgICAgICAgIHJldHVybiBVUklbX2NvZGluZ0Z1bmNOYW1lXShVUklbX2lubmVyQ29kaW5nRnVuY05hbWVdKHN0cmluZykpO1xuICAgICAgICB9O1xuICAgICAgfVxuXG4gICAgICB2YXIgc2VnbWVudHMgPSAoc3RyaW5nICsgJycpLnNwbGl0KF9zZXApO1xuXG4gICAgICBmb3IgKHZhciBpID0gMCwgbGVuZ3RoID0gc2VnbWVudHMubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgc2VnbWVudHNbaV0gPSBhY3R1YWxDb2RpbmdGdW5jKHNlZ21lbnRzW2ldKTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHNlZ21lbnRzLmpvaW4oX3NlcCk7XG4gICAgfTtcbiAgfTtcblxuICAvLyBUaGlzIHRha2VzIHBsYWNlIG91dHNpZGUgdGhlIGFib3ZlIGxvb3AgYmVjYXVzZSB3ZSBkb24ndCB3YW50LCBlLmcuLCBlbmNvZGVVcm5QYXRoIGZ1bmN0aW9ucy5cbiAgVVJJLmRlY29kZVBhdGggPSBnZW5lcmF0ZVNlZ21lbnRlZFBhdGhGdW5jdGlvbignLycsICdkZWNvZGVQYXRoU2VnbWVudCcpO1xuICBVUkkuZGVjb2RlVXJuUGF0aCA9IGdlbmVyYXRlU2VnbWVudGVkUGF0aEZ1bmN0aW9uKCc6JywgJ2RlY29kZVVyblBhdGhTZWdtZW50Jyk7XG4gIFVSSS5yZWNvZGVQYXRoID0gZ2VuZXJhdGVTZWdtZW50ZWRQYXRoRnVuY3Rpb24oJy8nLCAnZW5jb2RlUGF0aFNlZ21lbnQnLCAnZGVjb2RlJyk7XG4gIFVSSS5yZWNvZGVVcm5QYXRoID0gZ2VuZXJhdGVTZWdtZW50ZWRQYXRoRnVuY3Rpb24oJzonLCAnZW5jb2RlVXJuUGF0aFNlZ21lbnQnLCAnZGVjb2RlJyk7XG5cbiAgVVJJLmVuY29kZVJlc2VydmVkID0gZ2VuZXJhdGVBY2Nlc3NvcigncmVzZXJ2ZWQnLCAnZW5jb2RlJyk7XG5cbiAgVVJJLnBhcnNlID0gZnVuY3Rpb24oc3RyaW5nLCBwYXJ0cykge1xuICAgIHZhciBwb3M7XG4gICAgaWYgKCFwYXJ0cykge1xuICAgICAgcGFydHMgPSB7fTtcbiAgICB9XG4gICAgLy8gW3Byb3RvY29sXCI6Ly9cIlt1c2VybmFtZVtcIjpcInBhc3N3b3JkXVwiQFwiXWhvc3RuYW1lW1wiOlwicG9ydF1cIi9cIj9dW3BhdGhdW1wiP1wicXVlcnlzdHJpbmddW1wiI1wiZnJhZ21lbnRdXG5cbiAgICAvLyBleHRyYWN0IGZyYWdtZW50XG4gICAgcG9zID0gc3RyaW5nLmluZGV4T2YoJyMnKTtcbiAgICBpZiAocG9zID4gLTEpIHtcbiAgICAgIC8vIGVzY2FwaW5nP1xuICAgICAgcGFydHMuZnJhZ21lbnQgPSBzdHJpbmcuc3Vic3RyaW5nKHBvcyArIDEpIHx8IG51bGw7XG4gICAgICBzdHJpbmcgPSBzdHJpbmcuc3Vic3RyaW5nKDAsIHBvcyk7XG4gICAgfVxuXG4gICAgLy8gZXh0cmFjdCBxdWVyeVxuICAgIHBvcyA9IHN0cmluZy5pbmRleE9mKCc/Jyk7XG4gICAgaWYgKHBvcyA+IC0xKSB7XG4gICAgICAvLyBlc2NhcGluZz9cbiAgICAgIHBhcnRzLnF1ZXJ5ID0gc3RyaW5nLnN1YnN0cmluZyhwb3MgKyAxKSB8fCBudWxsO1xuICAgICAgc3RyaW5nID0gc3RyaW5nLnN1YnN0cmluZygwLCBwb3MpO1xuICAgIH1cblxuICAgIC8vIGV4dHJhY3QgcHJvdG9jb2xcbiAgICBpZiAoc3RyaW5nLnN1YnN0cmluZygwLCAyKSA9PT0gJy8vJykge1xuICAgICAgLy8gcmVsYXRpdmUtc2NoZW1lXG4gICAgICBwYXJ0cy5wcm90b2NvbCA9IG51bGw7XG4gICAgICBzdHJpbmcgPSBzdHJpbmcuc3Vic3RyaW5nKDIpO1xuICAgICAgLy8gZXh0cmFjdCBcInVzZXI6cGFzc0Bob3N0OnBvcnRcIlxuICAgICAgc3RyaW5nID0gVVJJLnBhcnNlQXV0aG9yaXR5KHN0cmluZywgcGFydHMpO1xuICAgIH0gZWxzZSB7XG4gICAgICBwb3MgPSBzdHJpbmcuaW5kZXhPZignOicpO1xuICAgICAgaWYgKHBvcyA+IC0xKSB7XG4gICAgICAgIHBhcnRzLnByb3RvY29sID0gc3RyaW5nLnN1YnN0cmluZygwLCBwb3MpIHx8IG51bGw7XG4gICAgICAgIGlmIChwYXJ0cy5wcm90b2NvbCAmJiAhcGFydHMucHJvdG9jb2wubWF0Y2goVVJJLnByb3RvY29sX2V4cHJlc3Npb24pKSB7XG4gICAgICAgICAgLy8gOiBtYXkgYmUgd2l0aGluIHRoZSBwYXRoXG4gICAgICAgICAgcGFydHMucHJvdG9jb2wgPSB1bmRlZmluZWQ7XG4gICAgICAgIH0gZWxzZSBpZiAoc3RyaW5nLnN1YnN0cmluZyhwb3MgKyAxLCBwb3MgKyAzKSA9PT0gJy8vJykge1xuICAgICAgICAgIHN0cmluZyA9IHN0cmluZy5zdWJzdHJpbmcocG9zICsgMyk7XG5cbiAgICAgICAgICAvLyBleHRyYWN0IFwidXNlcjpwYXNzQGhvc3Q6cG9ydFwiXG4gICAgICAgICAgc3RyaW5nID0gVVJJLnBhcnNlQXV0aG9yaXR5KHN0cmluZywgcGFydHMpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHN0cmluZyA9IHN0cmluZy5zdWJzdHJpbmcocG9zICsgMSk7XG4gICAgICAgICAgcGFydHMudXJuID0gdHJ1ZTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cblxuICAgIC8vIHdoYXQncyBsZWZ0IG11c3QgYmUgdGhlIHBhdGhcbiAgICBwYXJ0cy5wYXRoID0gc3RyaW5nO1xuXG4gICAgLy8gYW5kIHdlJ3JlIGRvbmVcbiAgICByZXR1cm4gcGFydHM7XG4gIH07XG4gIFVSSS5wYXJzZUhvc3QgPSBmdW5jdGlvbihzdHJpbmcsIHBhcnRzKSB7XG4gICAgLy8gQ29weSBjaHJvbWUsIElFLCBvcGVyYSBiYWNrc2xhc2gtaGFuZGxpbmcgYmVoYXZpb3IuXG4gICAgLy8gQmFjayBzbGFzaGVzIGJlZm9yZSB0aGUgcXVlcnkgc3RyaW5nIGdldCBjb252ZXJ0ZWQgdG8gZm9yd2FyZCBzbGFzaGVzXG4gICAgLy8gU2VlOiBodHRwczovL2dpdGh1Yi5jb20vam95ZW50L25vZGUvYmxvYi8zODZmZDI0ZjQ5YjBlOWQxYThhMDc2NTkyYTQwNDE2OGZhZWVjYzM0L2xpYi91cmwuanMjTDExNS1MMTI0XG4gICAgLy8gU2VlOiBodHRwczovL2NvZGUuZ29vZ2xlLmNvbS9wL2Nocm9taXVtL2lzc3Vlcy9kZXRhaWw/aWQ9MjU5MTZcbiAgICAvLyBodHRwczovL2dpdGh1Yi5jb20vbWVkaWFsaXplL1VSSS5qcy9wdWxsLzIzM1xuICAgIHN0cmluZyA9IHN0cmluZy5yZXBsYWNlKC9cXFxcL2csICcvJyk7XG5cbiAgICAvLyBleHRyYWN0IGhvc3Q6cG9ydFxuICAgIHZhciBwb3MgPSBzdHJpbmcuaW5kZXhPZignLycpO1xuICAgIHZhciBicmFja2V0UG9zO1xuICAgIHZhciB0O1xuXG4gICAgaWYgKHBvcyA9PT0gLTEpIHtcbiAgICAgIHBvcyA9IHN0cmluZy5sZW5ndGg7XG4gICAgfVxuXG4gICAgaWYgKHN0cmluZy5jaGFyQXQoMCkgPT09ICdbJykge1xuICAgICAgLy8gSVB2NiBob3N0IC0gaHR0cDovL3Rvb2xzLmlldGYub3JnL2h0bWwvZHJhZnQtaWV0Zi02bWFuLXRleHQtYWRkci1yZXByZXNlbnRhdGlvbi0wNCNzZWN0aW9uLTZcbiAgICAgIC8vIEkgY2xhaW0gbW9zdCBjbGllbnQgc29mdHdhcmUgYnJlYWtzIG9uIElQdjYgYW55d2F5cy4gVG8gc2ltcGxpZnkgdGhpbmdzLCBVUkkgb25seSBhY2NlcHRzXG4gICAgICAvLyBJUHY2K3BvcnQgaW4gdGhlIGZvcm1hdCBbMjAwMTpkYjg6OjFdOjgwIChmb3IgdGhlIHRpbWUgYmVpbmcpXG4gICAgICBicmFja2V0UG9zID0gc3RyaW5nLmluZGV4T2YoJ10nKTtcbiAgICAgIHBhcnRzLmhvc3RuYW1lID0gc3RyaW5nLnN1YnN0cmluZygxLCBicmFja2V0UG9zKSB8fCBudWxsO1xuICAgICAgcGFydHMucG9ydCA9IHN0cmluZy5zdWJzdHJpbmcoYnJhY2tldFBvcyArIDIsIHBvcykgfHwgbnVsbDtcbiAgICAgIGlmIChwYXJ0cy5wb3J0ID09PSAnLycpIHtcbiAgICAgICAgcGFydHMucG9ydCA9IG51bGw7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIHZhciBmaXJzdENvbG9uID0gc3RyaW5nLmluZGV4T2YoJzonKTtcbiAgICAgIHZhciBmaXJzdFNsYXNoID0gc3RyaW5nLmluZGV4T2YoJy8nKTtcbiAgICAgIHZhciBuZXh0Q29sb24gPSBzdHJpbmcuaW5kZXhPZignOicsIGZpcnN0Q29sb24gKyAxKTtcbiAgICAgIGlmIChuZXh0Q29sb24gIT09IC0xICYmIChmaXJzdFNsYXNoID09PSAtMSB8fCBuZXh0Q29sb24gPCBmaXJzdFNsYXNoKSkge1xuICAgICAgICAvLyBJUHY2IGhvc3QgY29udGFpbnMgbXVsdGlwbGUgY29sb25zIC0gYnV0IG5vIHBvcnRcbiAgICAgICAgLy8gdGhpcyBub3RhdGlvbiBpcyBhY3R1YWxseSBub3QgYWxsb3dlZCBieSBSRkMgMzk4NiwgYnV0IHdlJ3JlIGEgbGliZXJhbCBwYXJzZXJcbiAgICAgICAgcGFydHMuaG9zdG5hbWUgPSBzdHJpbmcuc3Vic3RyaW5nKDAsIHBvcykgfHwgbnVsbDtcbiAgICAgICAgcGFydHMucG9ydCA9IG51bGw7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0ID0gc3RyaW5nLnN1YnN0cmluZygwLCBwb3MpLnNwbGl0KCc6Jyk7XG4gICAgICAgIHBhcnRzLmhvc3RuYW1lID0gdFswXSB8fCBudWxsO1xuICAgICAgICBwYXJ0cy5wb3J0ID0gdFsxXSB8fCBudWxsO1xuICAgICAgfVxuICAgIH1cblxuICAgIGlmIChwYXJ0cy5ob3N0bmFtZSAmJiBzdHJpbmcuc3Vic3RyaW5nKHBvcykuY2hhckF0KDApICE9PSAnLycpIHtcbiAgICAgIHBvcysrO1xuICAgICAgc3RyaW5nID0gJy8nICsgc3RyaW5nO1xuICAgIH1cblxuICAgIHJldHVybiBzdHJpbmcuc3Vic3RyaW5nKHBvcykgfHwgJy8nO1xuICB9O1xuICBVUkkucGFyc2VBdXRob3JpdHkgPSBmdW5jdGlvbihzdHJpbmcsIHBhcnRzKSB7XG4gICAgc3RyaW5nID0gVVJJLnBhcnNlVXNlcmluZm8oc3RyaW5nLCBwYXJ0cyk7XG4gICAgcmV0dXJuIFVSSS5wYXJzZUhvc3Qoc3RyaW5nLCBwYXJ0cyk7XG4gIH07XG4gIFVSSS5wYXJzZVVzZXJpbmZvID0gZnVuY3Rpb24oc3RyaW5nLCBwYXJ0cykge1xuICAgIC8vIGV4dHJhY3QgdXNlcm5hbWU6cGFzc3dvcmRcbiAgICB2YXIgZmlyc3RTbGFzaCA9IHN0cmluZy5pbmRleE9mKCcvJyk7XG4gICAgdmFyIHBvcyA9IHN0cmluZy5sYXN0SW5kZXhPZignQCcsIGZpcnN0U2xhc2ggPiAtMSA/IGZpcnN0U2xhc2ggOiBzdHJpbmcubGVuZ3RoIC0gMSk7XG4gICAgdmFyIHQ7XG5cbiAgICAvLyBhdXRob3JpdHlAIG11c3QgY29tZSBiZWZvcmUgL3BhdGhcbiAgICBpZiAocG9zID4gLTEgJiYgKGZpcnN0U2xhc2ggPT09IC0xIHx8IHBvcyA8IGZpcnN0U2xhc2gpKSB7XG4gICAgICB0ID0gc3RyaW5nLnN1YnN0cmluZygwLCBwb3MpLnNwbGl0KCc6Jyk7XG4gICAgICBwYXJ0cy51c2VybmFtZSA9IHRbMF0gPyBVUkkuZGVjb2RlKHRbMF0pIDogbnVsbDtcbiAgICAgIHQuc2hpZnQoKTtcbiAgICAgIHBhcnRzLnBhc3N3b3JkID0gdFswXSA/IFVSSS5kZWNvZGUodC5qb2luKCc6JykpIDogbnVsbDtcbiAgICAgIHN0cmluZyA9IHN0cmluZy5zdWJzdHJpbmcocG9zICsgMSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHBhcnRzLnVzZXJuYW1lID0gbnVsbDtcbiAgICAgIHBhcnRzLnBhc3N3b3JkID0gbnVsbDtcbiAgICB9XG5cbiAgICByZXR1cm4gc3RyaW5nO1xuICB9O1xuICBVUkkucGFyc2VRdWVyeSA9IGZ1bmN0aW9uKHN0cmluZywgZXNjYXBlUXVlcnlTcGFjZSkge1xuICAgIGlmICghc3RyaW5nKSB7XG4gICAgICByZXR1cm4ge307XG4gICAgfVxuXG4gICAgLy8gdGhyb3cgb3V0IHRoZSBmdW5reSBidXNpbmVzcyAtIFwiP1wiW25hbWVcIj1cInZhbHVlXCImXCJdK1xuICAgIHN0cmluZyA9IHN0cmluZy5yZXBsYWNlKC8mKy9nLCAnJicpLnJlcGxhY2UoL15cXD8qJip8JiskL2csICcnKTtcblxuICAgIGlmICghc3RyaW5nKSB7XG4gICAgICByZXR1cm4ge307XG4gICAgfVxuXG4gICAgdmFyIGl0ZW1zID0ge307XG4gICAgdmFyIHNwbGl0cyA9IHN0cmluZy5zcGxpdCgnJicpO1xuICAgIHZhciBsZW5ndGggPSBzcGxpdHMubGVuZ3RoO1xuICAgIHZhciB2LCBuYW1lLCB2YWx1ZTtcblxuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgIHYgPSBzcGxpdHNbaV0uc3BsaXQoJz0nKTtcbiAgICAgIG5hbWUgPSBVUkkuZGVjb2RlUXVlcnkodi5zaGlmdCgpLCBlc2NhcGVRdWVyeVNwYWNlKTtcbiAgICAgIC8vIG5vIFwiPVwiIGlzIG51bGwgYWNjb3JkaW5nIHRvIGh0dHA6Ly9kdmNzLnczLm9yZy9oZy91cmwvcmF3LWZpbGUvdGlwL092ZXJ2aWV3Lmh0bWwjY29sbGVjdC11cmwtcGFyYW1ldGVyc1xuICAgICAgdmFsdWUgPSB2Lmxlbmd0aCA/IFVSSS5kZWNvZGVRdWVyeSh2LmpvaW4oJz0nKSwgZXNjYXBlUXVlcnlTcGFjZSkgOiBudWxsO1xuXG4gICAgICBpZiAoaGFzT3duLmNhbGwoaXRlbXMsIG5hbWUpKSB7XG4gICAgICAgIGlmICh0eXBlb2YgaXRlbXNbbmFtZV0gPT09ICdzdHJpbmcnIHx8IGl0ZW1zW25hbWVdID09PSBudWxsKSB7XG4gICAgICAgICAgaXRlbXNbbmFtZV0gPSBbaXRlbXNbbmFtZV1dO1xuICAgICAgICB9XG5cbiAgICAgICAgaXRlbXNbbmFtZV0ucHVzaCh2YWx1ZSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBpdGVtc1tuYW1lXSA9IHZhbHVlO1xuICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiBpdGVtcztcbiAgfTtcblxuICBVUkkuYnVpbGQgPSBmdW5jdGlvbihwYXJ0cykge1xuICAgIHZhciB0ID0gJyc7XG5cbiAgICBpZiAocGFydHMucHJvdG9jb2wpIHtcbiAgICAgIHQgKz0gcGFydHMucHJvdG9jb2wgKyAnOic7XG4gICAgfVxuXG4gICAgaWYgKCFwYXJ0cy51cm4gJiYgKHQgfHwgcGFydHMuaG9zdG5hbWUpKSB7XG4gICAgICB0ICs9ICcvLyc7XG4gICAgfVxuXG4gICAgdCArPSAoVVJJLmJ1aWxkQXV0aG9yaXR5KHBhcnRzKSB8fCAnJyk7XG5cbiAgICBpZiAodHlwZW9mIHBhcnRzLnBhdGggPT09ICdzdHJpbmcnKSB7XG4gICAgICBpZiAocGFydHMucGF0aC5jaGFyQXQoMCkgIT09ICcvJyAmJiB0eXBlb2YgcGFydHMuaG9zdG5hbWUgPT09ICdzdHJpbmcnKSB7XG4gICAgICAgIHQgKz0gJy8nO1xuICAgICAgfVxuXG4gICAgICB0ICs9IHBhcnRzLnBhdGg7XG4gICAgfVxuXG4gICAgaWYgKHR5cGVvZiBwYXJ0cy5xdWVyeSA9PT0gJ3N0cmluZycgJiYgcGFydHMucXVlcnkpIHtcbiAgICAgIHQgKz0gJz8nICsgcGFydHMucXVlcnk7XG4gICAgfVxuXG4gICAgaWYgKHR5cGVvZiBwYXJ0cy5mcmFnbWVudCA9PT0gJ3N0cmluZycgJiYgcGFydHMuZnJhZ21lbnQpIHtcbiAgICAgIHQgKz0gJyMnICsgcGFydHMuZnJhZ21lbnQ7XG4gICAgfVxuICAgIHJldHVybiB0O1xuICB9O1xuICBVUkkuYnVpbGRIb3N0ID0gZnVuY3Rpb24ocGFydHMpIHtcbiAgICB2YXIgdCA9ICcnO1xuXG4gICAgaWYgKCFwYXJ0cy5ob3N0bmFtZSkge1xuICAgICAgcmV0dXJuICcnO1xuICAgIH0gZWxzZSBpZiAoVVJJLmlwNl9leHByZXNzaW9uLnRlc3QocGFydHMuaG9zdG5hbWUpKSB7XG4gICAgICB0ICs9ICdbJyArIHBhcnRzLmhvc3RuYW1lICsgJ10nO1xuICAgIH0gZWxzZSB7XG4gICAgICB0ICs9IHBhcnRzLmhvc3RuYW1lO1xuICAgIH1cblxuICAgIGlmIChwYXJ0cy5wb3J0KSB7XG4gICAgICB0ICs9ICc6JyArIHBhcnRzLnBvcnQ7XG4gICAgfVxuXG4gICAgcmV0dXJuIHQ7XG4gIH07XG4gIFVSSS5idWlsZEF1dGhvcml0eSA9IGZ1bmN0aW9uKHBhcnRzKSB7XG4gICAgcmV0dXJuIFVSSS5idWlsZFVzZXJpbmZvKHBhcnRzKSArIFVSSS5idWlsZEhvc3QocGFydHMpO1xuICB9O1xuICBVUkkuYnVpbGRVc2VyaW5mbyA9IGZ1bmN0aW9uKHBhcnRzKSB7XG4gICAgdmFyIHQgPSAnJztcblxuICAgIGlmIChwYXJ0cy51c2VybmFtZSkge1xuICAgICAgdCArPSBVUkkuZW5jb2RlKHBhcnRzLnVzZXJuYW1lKTtcblxuICAgICAgaWYgKHBhcnRzLnBhc3N3b3JkKSB7XG4gICAgICAgIHQgKz0gJzonICsgVVJJLmVuY29kZShwYXJ0cy5wYXNzd29yZCk7XG4gICAgICB9XG5cbiAgICAgIHQgKz0gJ0AnO1xuICAgIH1cblxuICAgIHJldHVybiB0O1xuICB9O1xuICBVUkkuYnVpbGRRdWVyeSA9IGZ1bmN0aW9uKGRhdGEsIGR1cGxpY2F0ZVF1ZXJ5UGFyYW1ldGVycywgZXNjYXBlUXVlcnlTcGFjZSkge1xuICAgIC8vIGFjY29yZGluZyB0byBodHRwOi8vdG9vbHMuaWV0Zi5vcmcvaHRtbC9yZmMzOTg2IG9yIGh0dHA6Ly9sYWJzLmFwYWNoZS5vcmcvd2ViYXJjaC91cmkvcmZjL3JmYzM5ODYuaHRtbFxuICAgIC8vIGJlaW5nIMK7LS5ffiEkJicoKSorLDs9OkAvP8KrICVIRVggYW5kIGFsbnVtIGFyZSBhbGxvd2VkXG4gICAgLy8gdGhlIFJGQyBleHBsaWNpdGx5IHN0YXRlcyA/L2ZvbyBiZWluZyBhIHZhbGlkIHVzZSBjYXNlLCBubyBtZW50aW9uIG9mIHBhcmFtZXRlciBzeW50YXghXG4gICAgLy8gVVJJLmpzIHRyZWF0cyB0aGUgcXVlcnkgc3RyaW5nIGFzIGJlaW5nIGFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZFxuICAgIC8vIHNlZSBodHRwOi8vd3d3LnczLm9yZy9UUi9SRUMtaHRtbDQwL2ludGVyYWN0L2Zvcm1zLmh0bWwjZm9ybS1jb250ZW50LXR5cGVcblxuICAgIHZhciB0ID0gJyc7XG4gICAgdmFyIHVuaXF1ZSwga2V5LCBpLCBsZW5ndGg7XG4gICAgZm9yIChrZXkgaW4gZGF0YSkge1xuICAgICAgaWYgKGhhc093bi5jYWxsKGRhdGEsIGtleSkgJiYga2V5KSB7XG4gICAgICAgIGlmIChpc0FycmF5KGRhdGFba2V5XSkpIHtcbiAgICAgICAgICB1bmlxdWUgPSB7fTtcbiAgICAgICAgICBmb3IgKGkgPSAwLCBsZW5ndGggPSBkYXRhW2tleV0ubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIGlmIChkYXRhW2tleV1baV0gIT09IHVuZGVmaW5lZCAmJiB1bmlxdWVbZGF0YVtrZXldW2ldICsgJyddID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgICAgdCArPSAnJicgKyBVUkkuYnVpbGRRdWVyeVBhcmFtZXRlcihrZXksIGRhdGFba2V5XVtpXSwgZXNjYXBlUXVlcnlTcGFjZSk7XG4gICAgICAgICAgICAgIGlmIChkdXBsaWNhdGVRdWVyeVBhcmFtZXRlcnMgIT09IHRydWUpIHtcbiAgICAgICAgICAgICAgICB1bmlxdWVbZGF0YVtrZXldW2ldICsgJyddID0gdHJ1ZTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIGlmIChkYXRhW2tleV0gIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgIHQgKz0gJyYnICsgVVJJLmJ1aWxkUXVlcnlQYXJhbWV0ZXIoa2V5LCBkYXRhW2tleV0sIGVzY2FwZVF1ZXJ5U3BhY2UpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuIHQuc3Vic3RyaW5nKDEpO1xuICB9O1xuICBVUkkuYnVpbGRRdWVyeVBhcmFtZXRlciA9IGZ1bmN0aW9uKG5hbWUsIHZhbHVlLCBlc2NhcGVRdWVyeVNwYWNlKSB7XG4gICAgLy8gaHR0cDovL3d3dy53My5vcmcvVFIvUkVDLWh0bWw0MC9pbnRlcmFjdC9mb3Jtcy5odG1sI2Zvcm0tY29udGVudC10eXBlIC0tIGFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZFxuICAgIC8vIGRvbid0IGFwcGVuZCBcIj1cIiBmb3IgbnVsbCB2YWx1ZXMsIGFjY29yZGluZyB0byBodHRwOi8vZHZjcy53My5vcmcvaGcvdXJsL3Jhdy1maWxlL3RpcC9PdmVydmlldy5odG1sI3VybC1wYXJhbWV0ZXItc2VyaWFsaXphdGlvblxuICAgIHJldHVybiBVUkkuZW5jb2RlUXVlcnkobmFtZSwgZXNjYXBlUXVlcnlTcGFjZSkgKyAodmFsdWUgIT09IG51bGwgPyAnPScgKyBVUkkuZW5jb2RlUXVlcnkodmFsdWUsIGVzY2FwZVF1ZXJ5U3BhY2UpIDogJycpO1xuICB9O1xuXG4gIFVSSS5hZGRRdWVyeSA9IGZ1bmN0aW9uKGRhdGEsIG5hbWUsIHZhbHVlKSB7XG4gICAgaWYgKHR5cGVvZiBuYW1lID09PSAnb2JqZWN0Jykge1xuICAgICAgZm9yICh2YXIga2V5IGluIG5hbWUpIHtcbiAgICAgICAgaWYgKGhhc093bi5jYWxsKG5hbWUsIGtleSkpIHtcbiAgICAgICAgICBVUkkuYWRkUXVlcnkoZGF0YSwga2V5LCBuYW1lW2tleV0pO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSBlbHNlIGlmICh0eXBlb2YgbmFtZSA9PT0gJ3N0cmluZycpIHtcbiAgICAgIGlmIChkYXRhW25hbWVdID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgZGF0YVtuYW1lXSA9IHZhbHVlO1xuICAgICAgICByZXR1cm47XG4gICAgICB9IGVsc2UgaWYgKHR5cGVvZiBkYXRhW25hbWVdID09PSAnc3RyaW5nJykge1xuICAgICAgICBkYXRhW25hbWVdID0gW2RhdGFbbmFtZV1dO1xuICAgICAgfVxuXG4gICAgICBpZiAoIWlzQXJyYXkodmFsdWUpKSB7XG4gICAgICAgIHZhbHVlID0gW3ZhbHVlXTtcbiAgICAgIH1cblxuICAgICAgZGF0YVtuYW1lXSA9IChkYXRhW25hbWVdIHx8IFtdKS5jb25jYXQodmFsdWUpO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdVUkkuYWRkUXVlcnkoKSBhY2NlcHRzIGFuIG9iamVjdCwgc3RyaW5nIGFzIHRoZSBuYW1lIHBhcmFtZXRlcicpO1xuICAgIH1cbiAgfTtcbiAgVVJJLnJlbW92ZVF1ZXJ5ID0gZnVuY3Rpb24oZGF0YSwgbmFtZSwgdmFsdWUpIHtcbiAgICB2YXIgaSwgbGVuZ3RoLCBrZXk7XG5cbiAgICBpZiAoaXNBcnJheShuYW1lKSkge1xuICAgICAgZm9yIChpID0gMCwgbGVuZ3RoID0gbmFtZS5sZW5ndGg7IGkgPCBsZW5ndGg7IGkrKykge1xuICAgICAgICBkYXRhW25hbWVbaV1dID0gdW5kZWZpbmVkO1xuICAgICAgfVxuICAgIH0gZWxzZSBpZiAoZ2V0VHlwZShuYW1lKSA9PT0gJ1JlZ0V4cCcpIHtcbiAgICAgIGZvciAoa2V5IGluIGRhdGEpIHtcbiAgICAgICAgaWYgKG5hbWUudGVzdChrZXkpKSB7XG4gICAgICAgICAgZGF0YVtrZXldID0gdW5kZWZpbmVkO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSBlbHNlIGlmICh0eXBlb2YgbmFtZSA9PT0gJ29iamVjdCcpIHtcbiAgICAgIGZvciAoa2V5IGluIG5hbWUpIHtcbiAgICAgICAgaWYgKGhhc093bi5jYWxsKG5hbWUsIGtleSkpIHtcbiAgICAgICAgICBVUkkucmVtb3ZlUXVlcnkoZGF0YSwga2V5LCBuYW1lW2tleV0pO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSBlbHNlIGlmICh0eXBlb2YgbmFtZSA9PT0gJ3N0cmluZycpIHtcbiAgICAgIGlmICh2YWx1ZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIGlmIChnZXRUeXBlKHZhbHVlKSA9PT0gJ1JlZ0V4cCcpIHtcbiAgICAgICAgICBpZiAoIWlzQXJyYXkoZGF0YVtuYW1lXSkgJiYgdmFsdWUudGVzdChkYXRhW25hbWVdKSkge1xuICAgICAgICAgICAgZGF0YVtuYW1lXSA9IHVuZGVmaW5lZDtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgZGF0YVtuYW1lXSA9IGZpbHRlckFycmF5VmFsdWVzKGRhdGFbbmFtZV0sIHZhbHVlKTtcbiAgICAgICAgICB9XG4gICAgICAgIH0gZWxzZSBpZiAoZGF0YVtuYW1lXSA9PT0gU3RyaW5nKHZhbHVlKSAmJiAoIWlzQXJyYXkodmFsdWUpIHx8IHZhbHVlLmxlbmd0aCA9PT0gMSkpIHtcbiAgICAgICAgICBkYXRhW25hbWVdID0gdW5kZWZpbmVkO1xuICAgICAgICB9IGVsc2UgaWYgKGlzQXJyYXkoZGF0YVtuYW1lXSkpIHtcbiAgICAgICAgICBkYXRhW25hbWVdID0gZmlsdGVyQXJyYXlWYWx1ZXMoZGF0YVtuYW1lXSwgdmFsdWUpO1xuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBkYXRhW25hbWVdID0gdW5kZWZpbmVkO1xuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdVUkkucmVtb3ZlUXVlcnkoKSBhY2NlcHRzIGFuIG9iamVjdCwgc3RyaW5nLCBSZWdFeHAgYXMgdGhlIGZpcnN0IHBhcmFtZXRlcicpO1xuICAgIH1cbiAgfTtcbiAgVVJJLmhhc1F1ZXJ5ID0gZnVuY3Rpb24oZGF0YSwgbmFtZSwgdmFsdWUsIHdpdGhpbkFycmF5KSB7XG4gICAgaWYgKHR5cGVvZiBuYW1lID09PSAnb2JqZWN0Jykge1xuICAgICAgZm9yICh2YXIga2V5IGluIG5hbWUpIHtcbiAgICAgICAgaWYgKGhhc093bi5jYWxsKG5hbWUsIGtleSkpIHtcbiAgICAgICAgICBpZiAoIVVSSS5oYXNRdWVyeShkYXRhLCBrZXksIG5hbWVba2V5XSkpIHtcbiAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfSBlbHNlIGlmICh0eXBlb2YgbmFtZSAhPT0gJ3N0cmluZycpIHtcbiAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ1VSSS5oYXNRdWVyeSgpIGFjY2VwdHMgYW4gb2JqZWN0LCBzdHJpbmcgYXMgdGhlIG5hbWUgcGFyYW1ldGVyJyk7XG4gICAgfVxuXG4gICAgc3dpdGNoIChnZXRUeXBlKHZhbHVlKSkge1xuICAgICAgY2FzZSAnVW5kZWZpbmVkJzpcbiAgICAgICAgLy8gdHJ1ZSBpZiBleGlzdHMgKGJ1dCBtYXkgYmUgZW1wdHkpXG4gICAgICAgIHJldHVybiBuYW1lIGluIGRhdGE7IC8vIGRhdGFbbmFtZV0gIT09IHVuZGVmaW5lZDtcblxuICAgICAgY2FzZSAnQm9vbGVhbic6XG4gICAgICAgIC8vIHRydWUgaWYgZXhpc3RzIGFuZCBub24tZW1wdHlcbiAgICAgICAgdmFyIF9ib29seSA9IEJvb2xlYW4oaXNBcnJheShkYXRhW25hbWVdKSA/IGRhdGFbbmFtZV0ubGVuZ3RoIDogZGF0YVtuYW1lXSk7XG4gICAgICAgIHJldHVybiB2YWx1ZSA9PT0gX2Jvb2x5O1xuXG4gICAgICBjYXNlICdGdW5jdGlvbic6XG4gICAgICAgIC8vIGFsbG93IGNvbXBsZXggY29tcGFyaXNvblxuICAgICAgICByZXR1cm4gISF2YWx1ZShkYXRhW25hbWVdLCBuYW1lLCBkYXRhKTtcblxuICAgICAgY2FzZSAnQXJyYXknOlxuICAgICAgICBpZiAoIWlzQXJyYXkoZGF0YVtuYW1lXSkpIHtcbiAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgb3AgPSB3aXRoaW5BcnJheSA/IGFycmF5Q29udGFpbnMgOiBhcnJheXNFcXVhbDtcbiAgICAgICAgcmV0dXJuIG9wKGRhdGFbbmFtZV0sIHZhbHVlKTtcblxuICAgICAgY2FzZSAnUmVnRXhwJzpcbiAgICAgICAgaWYgKCFpc0FycmF5KGRhdGFbbmFtZV0pKSB7XG4gICAgICAgICAgcmV0dXJuIEJvb2xlYW4oZGF0YVtuYW1lXSAmJiBkYXRhW25hbWVdLm1hdGNoKHZhbHVlKSk7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoIXdpdGhpbkFycmF5KSB7XG4gICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIGFycmF5Q29udGFpbnMoZGF0YVtuYW1lXSwgdmFsdWUpO1xuXG4gICAgICBjYXNlICdOdW1iZXInOlxuICAgICAgICB2YWx1ZSA9IFN0cmluZyh2YWx1ZSk7XG4gICAgICAgIC8qIGZhbGxzIHRocm91Z2ggKi9cbiAgICAgIGNhc2UgJ1N0cmluZyc6XG4gICAgICAgIGlmICghaXNBcnJheShkYXRhW25hbWVdKSkge1xuICAgICAgICAgIHJldHVybiBkYXRhW25hbWVdID09PSB2YWx1ZTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICghd2l0aGluQXJyYXkpIHtcbiAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gYXJyYXlDb250YWlucyhkYXRhW25hbWVdLCB2YWx1ZSk7XG5cbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ1VSSS5oYXNRdWVyeSgpIGFjY2VwdHMgdW5kZWZpbmVkLCBib29sZWFuLCBzdHJpbmcsIG51bWJlciwgUmVnRXhwLCBGdW5jdGlvbiBhcyB0aGUgdmFsdWUgcGFyYW1ldGVyJyk7XG4gICAgfVxuICB9O1xuXG5cbiAgVVJJLmNvbW1vblBhdGggPSBmdW5jdGlvbihvbmUsIHR3bykge1xuICAgIHZhciBsZW5ndGggPSBNYXRoLm1pbihvbmUubGVuZ3RoLCB0d28ubGVuZ3RoKTtcbiAgICB2YXIgcG9zO1xuXG4gICAgLy8gZmluZCBmaXJzdCBub24tbWF0Y2hpbmcgY2hhcmFjdGVyXG4gICAgZm9yIChwb3MgPSAwOyBwb3MgPCBsZW5ndGg7IHBvcysrKSB7XG4gICAgICBpZiAob25lLmNoYXJBdChwb3MpICE9PSB0d28uY2hhckF0KHBvcykpIHtcbiAgICAgICAgcG9zLS07XG4gICAgICAgIGJyZWFrO1xuICAgICAgfVxuICAgIH1cblxuICAgIGlmIChwb3MgPCAxKSB7XG4gICAgICByZXR1cm4gb25lLmNoYXJBdCgwKSA9PT0gdHdvLmNoYXJBdCgwKSAmJiBvbmUuY2hhckF0KDApID09PSAnLycgPyAnLycgOiAnJztcbiAgICB9XG5cbiAgICAvLyByZXZlcnQgdG8gbGFzdCAvXG4gICAgaWYgKG9uZS5jaGFyQXQocG9zKSAhPT0gJy8nIHx8IHR3by5jaGFyQXQocG9zKSAhPT0gJy8nKSB7XG4gICAgICBwb3MgPSBvbmUuc3Vic3RyaW5nKDAsIHBvcykubGFzdEluZGV4T2YoJy8nKTtcbiAgICB9XG5cbiAgICByZXR1cm4gb25lLnN1YnN0cmluZygwLCBwb3MgKyAxKTtcbiAgfTtcblxuICBVUkkud2l0aGluU3RyaW5nID0gZnVuY3Rpb24oc3RyaW5nLCBjYWxsYmFjaywgb3B0aW9ucykge1xuICAgIG9wdGlvbnMgfHwgKG9wdGlvbnMgPSB7fSk7XG4gICAgdmFyIF9zdGFydCA9IG9wdGlvbnMuc3RhcnQgfHwgVVJJLmZpbmRVcmkuc3RhcnQ7XG4gICAgdmFyIF9lbmQgPSBvcHRpb25zLmVuZCB8fCBVUkkuZmluZFVyaS5lbmQ7XG4gICAgdmFyIF90cmltID0gb3B0aW9ucy50cmltIHx8IFVSSS5maW5kVXJpLnRyaW07XG4gICAgdmFyIF9hdHRyaWJ1dGVPcGVuID0gL1thLXowLTktXT1bXCInXT8kL2k7XG5cbiAgICBfc3RhcnQubGFzdEluZGV4ID0gMDtcbiAgICB3aGlsZSAodHJ1ZSkge1xuICAgICAgdmFyIG1hdGNoID0gX3N0YXJ0LmV4ZWMoc3RyaW5nKTtcbiAgICAgIGlmICghbWF0Y2gpIHtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG5cbiAgICAgIHZhciBzdGFydCA9IG1hdGNoLmluZGV4O1xuICAgICAgaWYgKG9wdGlvbnMuaWdub3JlSHRtbCkge1xuICAgICAgICAvLyBhdHRyaWJ1dChlPVtcIiddPyQpXG4gICAgICAgIHZhciBhdHRyaWJ1dGVPcGVuID0gc3RyaW5nLnNsaWNlKE1hdGgubWF4KHN0YXJ0IC0gMywgMCksIHN0YXJ0KTtcbiAgICAgICAgaWYgKGF0dHJpYnV0ZU9wZW4gJiYgX2F0dHJpYnV0ZU9wZW4udGVzdChhdHRyaWJ1dGVPcGVuKSkge1xuICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHZhciBlbmQgPSBzdGFydCArIHN0cmluZy5zbGljZShzdGFydCkuc2VhcmNoKF9lbmQpO1xuICAgICAgdmFyIHNsaWNlID0gc3RyaW5nLnNsaWNlKHN0YXJ0LCBlbmQpLnJlcGxhY2UoX3RyaW0sICcnKTtcbiAgICAgIGlmIChvcHRpb25zLmlnbm9yZSAmJiBvcHRpb25zLmlnbm9yZS50ZXN0KHNsaWNlKSkge1xuICAgICAgICBjb250aW51ZTtcbiAgICAgIH1cblxuICAgICAgZW5kID0gc3RhcnQgKyBzbGljZS5sZW5ndGg7XG4gICAgICB2YXIgcmVzdWx0ID0gY2FsbGJhY2soc2xpY2UsIHN0YXJ0LCBlbmQsIHN0cmluZyk7XG4gICAgICBzdHJpbmcgPSBzdHJpbmcuc2xpY2UoMCwgc3RhcnQpICsgcmVzdWx0ICsgc3RyaW5nLnNsaWNlKGVuZCk7XG4gICAgICBfc3RhcnQubGFzdEluZGV4ID0gc3RhcnQgKyByZXN1bHQubGVuZ3RoO1xuICAgIH1cblxuICAgIF9zdGFydC5sYXN0SW5kZXggPSAwO1xuICAgIHJldHVybiBzdHJpbmc7XG4gIH07XG5cbiAgVVJJLmVuc3VyZVZhbGlkSG9zdG5hbWUgPSBmdW5jdGlvbih2KSB7XG4gICAgLy8gVGhlb3JldGljYWxseSBVUklzIGFsbG93IHBlcmNlbnQtZW5jb2RpbmcgaW4gSG9zdG5hbWVzIChhY2NvcmRpbmcgdG8gUkZDIDM5ODYpXG4gICAgLy8gdGhleSBhcmUgbm90IHBhcnQgb2YgRE5TIGFuZCB0aGVyZWZvcmUgaWdub3JlZCBieSBVUkkuanNcblxuICAgIGlmICh2Lm1hdGNoKFVSSS5pbnZhbGlkX2hvc3RuYW1lX2NoYXJhY3RlcnMpKSB7XG4gICAgICAvLyB0ZXN0IHB1bnljb2RlXG4gICAgICBpZiAoIXB1bnljb2RlKSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ0hvc3RuYW1lIFwiJyArIHYgKyAnXCIgY29udGFpbnMgY2hhcmFjdGVycyBvdGhlciB0aGFuIFtBLVowLTkuLV0gYW5kIFB1bnljb2RlLmpzIGlzIG5vdCBhdmFpbGFibGUnKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHB1bnljb2RlLnRvQVNDSUkodikubWF0Y2goVVJJLmludmFsaWRfaG9zdG5hbWVfY2hhcmFjdGVycykpIHtcbiAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignSG9zdG5hbWUgXCInICsgdiArICdcIiBjb250YWlucyBjaGFyYWN0ZXJzIG90aGVyIHRoYW4gW0EtWjAtOS4tXScpO1xuICAgICAgfVxuICAgIH1cbiAgfTtcblxuICAvLyBub0NvbmZsaWN0XG4gIFVSSS5ub0NvbmZsaWN0ID0gZnVuY3Rpb24ocmVtb3ZlQWxsKSB7XG4gICAgaWYgKHJlbW92ZUFsbCkge1xuICAgICAgdmFyIHVuY29uZmxpY3RlZCA9IHtcbiAgICAgICAgVVJJOiB0aGlzLm5vQ29uZmxpY3QoKVxuICAgICAgfTtcblxuICAgICAgaWYgKHJvb3QuVVJJVGVtcGxhdGUgJiYgdHlwZW9mIHJvb3QuVVJJVGVtcGxhdGUubm9Db25mbGljdCA9PT0gJ2Z1bmN0aW9uJykge1xuICAgICAgICB1bmNvbmZsaWN0ZWQuVVJJVGVtcGxhdGUgPSByb290LlVSSVRlbXBsYXRlLm5vQ29uZmxpY3QoKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHJvb3QuSVB2NiAmJiB0eXBlb2Ygcm9vdC5JUHY2Lm5vQ29uZmxpY3QgPT09ICdmdW5jdGlvbicpIHtcbiAgICAgICAgdW5jb25mbGljdGVkLklQdjYgPSByb290LklQdjYubm9Db25mbGljdCgpO1xuICAgICAgfVxuXG4gICAgICBpZiAocm9vdC5TZWNvbmRMZXZlbERvbWFpbnMgJiYgdHlwZW9mIHJvb3QuU2Vjb25kTGV2ZWxEb21haW5zLm5vQ29uZmxpY3QgPT09ICdmdW5jdGlvbicpIHtcbiAgICAgICAgdW5jb25mbGljdGVkLlNlY29uZExldmVsRG9tYWlucyA9IHJvb3QuU2Vjb25kTGV2ZWxEb21haW5zLm5vQ29uZmxpY3QoKTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHVuY29uZmxpY3RlZDtcbiAgICB9IGVsc2UgaWYgKHJvb3QuVVJJID09PSB0aGlzKSB7XG4gICAgICByb290LlVSSSA9IF9VUkk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRoaXM7XG4gIH07XG5cbiAgcC5idWlsZCA9IGZ1bmN0aW9uKGRlZmVyQnVpbGQpIHtcbiAgICBpZiAoZGVmZXJCdWlsZCA9PT0gdHJ1ZSkge1xuICAgICAgdGhpcy5fZGVmZXJyZWRfYnVpbGQgPSB0cnVlO1xuICAgIH0gZWxzZSBpZiAoZGVmZXJCdWlsZCA9PT0gdW5kZWZpbmVkIHx8IHRoaXMuX2RlZmVycmVkX2J1aWxkKSB7XG4gICAgICB0aGlzLl9zdHJpbmcgPSBVUkkuYnVpbGQodGhpcy5fcGFydHMpO1xuICAgICAgdGhpcy5fZGVmZXJyZWRfYnVpbGQgPSBmYWxzZTtcbiAgICB9XG5cbiAgICByZXR1cm4gdGhpcztcbiAgfTtcblxuICBwLmNsb25lID0gZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIG5ldyBVUkkodGhpcyk7XG4gIH07XG5cbiAgcC52YWx1ZU9mID0gcC50b1N0cmluZyA9IGZ1bmN0aW9uKCkge1xuICAgIHJldHVybiB0aGlzLmJ1aWxkKGZhbHNlKS5fc3RyaW5nO1xuICB9O1xuXG5cbiAgZnVuY3Rpb24gZ2VuZXJhdGVTaW1wbGVBY2Nlc3NvcihfcGFydCl7XG4gICAgcmV0dXJuIGZ1bmN0aW9uKHYsIGJ1aWxkKSB7XG4gICAgICBpZiAodiA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHJldHVybiB0aGlzLl9wYXJ0c1tfcGFydF0gfHwgJyc7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0aGlzLl9wYXJ0c1tfcGFydF0gPSB2IHx8IG51bGw7XG4gICAgICAgIHRoaXMuYnVpbGQoIWJ1aWxkKTtcbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICB9XG4gICAgfTtcbiAgfVxuXG4gIGZ1bmN0aW9uIGdlbmVyYXRlUHJlZml4QWNjZXNzb3IoX3BhcnQsIF9rZXkpe1xuICAgIHJldHVybiBmdW5jdGlvbih2LCBidWlsZCkge1xuICAgICAgaWYgKHYgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICByZXR1cm4gdGhpcy5fcGFydHNbX3BhcnRdIHx8ICcnO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgaWYgKHYgIT09IG51bGwpIHtcbiAgICAgICAgICB2ID0gdiArICcnO1xuICAgICAgICAgIGlmICh2LmNoYXJBdCgwKSA9PT0gX2tleSkge1xuICAgICAgICAgICAgdiA9IHYuc3Vic3RyaW5nKDEpO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMuX3BhcnRzW19wYXJ0XSA9IHY7XG4gICAgICAgIHRoaXMuYnVpbGQoIWJ1aWxkKTtcbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICB9XG4gICAgfTtcbiAgfVxuXG4gIHAucHJvdG9jb2wgPSBnZW5lcmF0ZVNpbXBsZUFjY2Vzc29yKCdwcm90b2NvbCcpO1xuICBwLnVzZXJuYW1lID0gZ2VuZXJhdGVTaW1wbGVBY2Nlc3NvcigndXNlcm5hbWUnKTtcbiAgcC5wYXNzd29yZCA9IGdlbmVyYXRlU2ltcGxlQWNjZXNzb3IoJ3Bhc3N3b3JkJyk7XG4gIHAuaG9zdG5hbWUgPSBnZW5lcmF0ZVNpbXBsZUFjY2Vzc29yKCdob3N0bmFtZScpO1xuICBwLnBvcnQgPSBnZW5lcmF0ZVNpbXBsZUFjY2Vzc29yKCdwb3J0Jyk7XG4gIHAucXVlcnkgPSBnZW5lcmF0ZVByZWZpeEFjY2Vzc29yKCdxdWVyeScsICc/Jyk7XG4gIHAuZnJhZ21lbnQgPSBnZW5lcmF0ZVByZWZpeEFjY2Vzc29yKCdmcmFnbWVudCcsICcjJyk7XG5cbiAgcC5zZWFyY2ggPSBmdW5jdGlvbih2LCBidWlsZCkge1xuICAgIHZhciB0ID0gdGhpcy5xdWVyeSh2LCBidWlsZCk7XG4gICAgcmV0dXJuIHR5cGVvZiB0ID09PSAnc3RyaW5nJyAmJiB0Lmxlbmd0aCA/ICgnPycgKyB0KSA6IHQ7XG4gIH07XG4gIHAuaGFzaCA9IGZ1bmN0aW9uKHYsIGJ1aWxkKSB7XG4gICAgdmFyIHQgPSB0aGlzLmZyYWdtZW50KHYsIGJ1aWxkKTtcbiAgICByZXR1cm4gdHlwZW9mIHQgPT09ICdzdHJpbmcnICYmIHQubGVuZ3RoID8gKCcjJyArIHQpIDogdDtcbiAgfTtcblxuICBwLnBhdGhuYW1lID0gZnVuY3Rpb24odiwgYnVpbGQpIHtcbiAgICBpZiAodiA9PT0gdW5kZWZpbmVkIHx8IHYgPT09IHRydWUpIHtcbiAgICAgIHZhciByZXMgPSB0aGlzLl9wYXJ0cy5wYXRoIHx8ICh0aGlzLl9wYXJ0cy5ob3N0bmFtZSA/ICcvJyA6ICcnKTtcbiAgICAgIHJldHVybiB2ID8gKHRoaXMuX3BhcnRzLnVybiA/IFVSSS5kZWNvZGVVcm5QYXRoIDogVVJJLmRlY29kZVBhdGgpKHJlcykgOiByZXM7XG4gICAgfSBlbHNlIHtcbiAgICAgIGlmICh0aGlzLl9wYXJ0cy51cm4pIHtcbiAgICAgICAgdGhpcy5fcGFydHMucGF0aCA9IHYgPyBVUkkucmVjb2RlVXJuUGF0aCh2KSA6ICcnO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhpcy5fcGFydHMucGF0aCA9IHYgPyBVUkkucmVjb2RlUGF0aCh2KSA6ICcvJztcbiAgICAgIH1cbiAgICAgIHRoaXMuYnVpbGQoIWJ1aWxkKTtcbiAgICAgIHJldHVybiB0aGlzO1xuICAgIH1cbiAgfTtcbiAgcC5wYXRoID0gcC5wYXRobmFtZTtcbiAgcC5ocmVmID0gZnVuY3Rpb24oaHJlZiwgYnVpbGQpIHtcbiAgICB2YXIga2V5O1xuXG4gICAgaWYgKGhyZWYgPT09IHVuZGVmaW5lZCkge1xuICAgICAgcmV0dXJuIHRoaXMudG9TdHJpbmcoKTtcbiAgICB9XG5cbiAgICB0aGlzLl9zdHJpbmcgPSAnJztcbiAgICB0aGlzLl9wYXJ0cyA9IFVSSS5fcGFydHMoKTtcblxuICAgIHZhciBfVVJJID0gaHJlZiBpbnN0YW5jZW9mIFVSSTtcbiAgICB2YXIgX29iamVjdCA9IHR5cGVvZiBocmVmID09PSAnb2JqZWN0JyAmJiAoaHJlZi5ob3N0bmFtZSB8fCBocmVmLnBhdGggfHwgaHJlZi5wYXRobmFtZSk7XG4gICAgaWYgKGhyZWYubm9kZU5hbWUpIHtcbiAgICAgIHZhciBhdHRyaWJ1dGUgPSBVUkkuZ2V0RG9tQXR0cmlidXRlKGhyZWYpO1xuICAgICAgaHJlZiA9IGhyZWZbYXR0cmlidXRlXSB8fCAnJztcbiAgICAgIF9vYmplY3QgPSBmYWxzZTtcbiAgICB9XG5cbiAgICAvLyB3aW5kb3cubG9jYXRpb24gaXMgcmVwb3J0ZWQgdG8gYmUgYW4gb2JqZWN0LCBidXQgaXQncyBub3QgdGhlIHNvcnRcbiAgICAvLyBvZiBvYmplY3Qgd2UncmUgbG9va2luZyBmb3I6XG4gICAgLy8gKiBsb2NhdGlvbi5wcm90b2NvbCBlbmRzIHdpdGggYSBjb2xvblxuICAgIC8vICogbG9jYXRpb24ucXVlcnkgIT0gb2JqZWN0LnNlYXJjaFxuICAgIC8vICogbG9jYXRpb24uaGFzaCAhPSBvYmplY3QuZnJhZ21lbnRcbiAgICAvLyBzaW1wbHkgc2VyaWFsaXppbmcgdGhlIHVua25vd24gb2JqZWN0IHNob3VsZCBkbyB0aGUgdHJpY2tcbiAgICAvLyAoZm9yIGxvY2F0aW9uLCBub3QgZm9yIGV2ZXJ5dGhpbmcuLi4pXG4gICAgaWYgKCFfVVJJICYmIF9vYmplY3QgJiYgaHJlZi5wYXRobmFtZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICBocmVmID0gaHJlZi50b1N0cmluZygpO1xuICAgIH1cblxuICAgIGlmICh0eXBlb2YgaHJlZiA9PT0gJ3N0cmluZycgfHwgaHJlZiBpbnN0YW5jZW9mIFN0cmluZykge1xuICAgICAgdGhpcy5fcGFydHMgPSBVUkkucGFyc2UoU3RyaW5nKGhyZWYpLCB0aGlzLl9wYXJ0cyk7XG4gICAgfSBlbHNlIGlmIChfVVJJIHx8IF9vYmplY3QpIHtcbiAgICAgIHZhciBzcmMgPSBfVVJJID8gaHJlZi5fcGFydHMgOiBocmVmO1xuICAgICAgZm9yIChrZXkgaW4gc3JjKSB7XG4gICAgICAgIGlmIChoYXNPd24uY2FsbCh0aGlzLl9wYXJ0cywga2V5KSkge1xuICAgICAgICAgIHRoaXMuX3BhcnRzW2tleV0gPSBzcmNba2V5XTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdpbnZhbGlkIGlucHV0Jyk7XG4gICAgfVxuXG4gICAgdGhpcy5idWlsZCghYnVpbGQpO1xuICAgIHJldHVybiB0aGlzO1xuICB9O1xuXG4gIC8vIGlkZW50aWZpY2F0aW9uIGFjY2Vzc29yc1xuICBwLmlzID0gZnVuY3Rpb24od2hhdCkge1xuICAgIHZhciBpcCA9IGZhbHNlO1xuICAgIHZhciBpcDQgPSBmYWxzZTtcbiAgICB2YXIgaXA2ID0gZmFsc2U7XG4gICAgdmFyIG5hbWUgPSBmYWxzZTtcbiAgICB2YXIgc2xkID0gZmFsc2U7XG4gICAgdmFyIGlkbiA9IGZhbHNlO1xuICAgIHZhciBwdW55Y29kZSA9IGZhbHNlO1xuICAgIHZhciByZWxhdGl2ZSA9ICF0aGlzLl9wYXJ0cy51cm47XG5cbiAgICBpZiAodGhpcy5fcGFydHMuaG9zdG5hbWUpIHtcbiAgICAgIHJlbGF0aXZlID0gZmFsc2U7XG4gICAgICBpcDQgPSBVUkkuaXA0X2V4cHJlc3Npb24udGVzdCh0aGlzLl9wYXJ0cy5ob3N0bmFtZSk7XG4gICAgICBpcDYgPSBVUkkuaXA2X2V4cHJlc3Npb24udGVzdCh0aGlzLl9wYXJ0cy5ob3N0bmFtZSk7XG4gICAgICBpcCA9IGlwNCB8fCBpcDY7XG4gICAgICBuYW1lID0gIWlwO1xuICAgICAgc2xkID0gbmFtZSAmJiBTTEQgJiYgU0xELmhhcyh0aGlzLl9wYXJ0cy5ob3N0bmFtZSk7XG4gICAgICBpZG4gPSBuYW1lICYmIFVSSS5pZG5fZXhwcmVzc2lvbi50ZXN0KHRoaXMuX3BhcnRzLmhvc3RuYW1lKTtcbiAgICAgIHB1bnljb2RlID0gbmFtZSAmJiBVUkkucHVueWNvZGVfZXhwcmVzc2lvbi50ZXN0KHRoaXMuX3BhcnRzLmhvc3RuYW1lKTtcbiAgICB9XG5cbiAgICBzd2l0Y2ggKHdoYXQudG9Mb3dlckNhc2UoKSkge1xuICAgICAgY2FzZSAncmVsYXRpdmUnOlxuICAgICAgICByZXR1cm4gcmVsYXRpdmU7XG5cbiAgICAgIGNhc2UgJ2Fic29sdXRlJzpcbiAgICAgICAgcmV0dXJuICFyZWxhdGl2ZTtcblxuICAgICAgLy8gaG9zdG5hbWUgaWRlbnRpZmljYXRpb25cbiAgICAgIGNhc2UgJ2RvbWFpbic6XG4gICAgICBjYXNlICduYW1lJzpcbiAgICAgICAgcmV0dXJuIG5hbWU7XG5cbiAgICAgIGNhc2UgJ3NsZCc6XG4gICAgICAgIHJldHVybiBzbGQ7XG5cbiAgICAgIGNhc2UgJ2lwJzpcbiAgICAgICAgcmV0dXJuIGlwO1xuXG4gICAgICBjYXNlICdpcDQnOlxuICAgICAgY2FzZSAnaXB2NCc6XG4gICAgICBjYXNlICdpbmV0NCc6XG4gICAgICAgIHJldHVybiBpcDQ7XG5cbiAgICAgIGNhc2UgJ2lwNic6XG4gICAgICBjYXNlICdpcHY2JzpcbiAgICAgIGNhc2UgJ2luZXQ2JzpcbiAgICAgICAgcmV0dXJuIGlwNjtcblxuICAgICAgY2FzZSAnaWRuJzpcbiAgICAgICAgcmV0dXJuIGlkbjtcblxuICAgICAgY2FzZSAndXJsJzpcbiAgICAgICAgcmV0dXJuICF0aGlzLl9wYXJ0cy51cm47XG5cbiAgICAgIGNhc2UgJ3Vybic6XG4gICAgICAgIHJldHVybiAhIXRoaXMuX3BhcnRzLnVybjtcblxuICAgICAgY2FzZSAncHVueWNvZGUnOlxuICAgICAgICByZXR1cm4gcHVueWNvZGU7XG4gICAgfVxuXG4gICAgcmV0dXJuIG51bGw7XG4gIH07XG5cbiAgLy8gY29tcG9uZW50IHNwZWNpZmljIGlucHV0IHZhbGlkYXRpb25cbiAgdmFyIF9wcm90b2NvbCA9IHAucHJvdG9jb2w7XG4gIHZhciBfcG9ydCA9IHAucG9ydDtcbiAgdmFyIF9ob3N0bmFtZSA9IHAuaG9zdG5hbWU7XG5cbiAgcC5wcm90b2NvbCA9IGZ1bmN0aW9uKHYsIGJ1aWxkKSB7XG4gICAgaWYgKHYgIT09IHVuZGVmaW5lZCkge1xuICAgICAgaWYgKHYpIHtcbiAgICAgICAgLy8gYWNjZXB0IHRyYWlsaW5nIDovL1xuICAgICAgICB2ID0gdi5yZXBsYWNlKC86KFxcL1xcLyk/JC8sICcnKTtcblxuICAgICAgICBpZiAoIXYubWF0Y2goVVJJLnByb3RvY29sX2V4cHJlc3Npb24pKSB7XG4gICAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignUHJvdG9jb2wgXCInICsgdiArICdcIiBjb250YWlucyBjaGFyYWN0ZXJzIG90aGVyIHRoYW4gW0EtWjAtOS4rLV0gb3IgZG9lc25cXCd0IHN0YXJ0IHdpdGggW0EtWl0nKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gX3Byb3RvY29sLmNhbGwodGhpcywgdiwgYnVpbGQpO1xuICB9O1xuICBwLnNjaGVtZSA9IHAucHJvdG9jb2w7XG4gIHAucG9ydCA9IGZ1bmN0aW9uKHYsIGJ1aWxkKSB7XG4gICAgaWYgKHRoaXMuX3BhcnRzLnVybikge1xuICAgICAgcmV0dXJuIHYgPT09IHVuZGVmaW5lZCA/ICcnIDogdGhpcztcbiAgICB9XG5cbiAgICBpZiAodiAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICBpZiAodiA9PT0gMCkge1xuICAgICAgICB2ID0gbnVsbDtcbiAgICAgIH1cblxuICAgICAgaWYgKHYpIHtcbiAgICAgICAgdiArPSAnJztcbiAgICAgICAgaWYgKHYuY2hhckF0KDApID09PSAnOicpIHtcbiAgICAgICAgICB2ID0gdi5zdWJzdHJpbmcoMSk7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAodi5tYXRjaCgvW14wLTldLykpIHtcbiAgICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdQb3J0IFwiJyArIHYgKyAnXCIgY29udGFpbnMgY2hhcmFjdGVycyBvdGhlciB0aGFuIFswLTldJyk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIF9wb3J0LmNhbGwodGhpcywgdiwgYnVpbGQpO1xuICB9O1xuICBwLmhvc3RuYW1lID0gZnVuY3Rpb24odiwgYnVpbGQpIHtcbiAgICBpZiAodGhpcy5fcGFydHMudXJuKSB7XG4gICAgICByZXR1cm4gdiA9PT0gdW5kZWZpbmVkID8gJycgOiB0aGlzO1xuICAgIH1cblxuICAgIGlmICh2ICE9PSB1bmRlZmluZWQpIHtcbiAgICAgIHZhciB4ID0ge307XG4gICAgICB2YXIgcmVzID0gVVJJLnBhcnNlSG9zdCh2LCB4KTtcbiAgICAgIGlmIChyZXMgIT09ICcvJykge1xuICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdIb3N0bmFtZSBcIicgKyB2ICsgJ1wiIGNvbnRhaW5zIGNoYXJhY3RlcnMgb3RoZXIgdGhhbiBbQS1aMC05Li1dJyk7XG4gICAgICB9XG5cbiAgICAgIHYgPSB4Lmhvc3RuYW1lO1xuICAgIH1cbiAgICByZXR1cm4gX2hvc3RuYW1lLmNhbGwodGhpcywgdiwgYnVpbGQpO1xuICB9O1xuXG4gIC8vIGNvbXBvdW5kIGFjY2Vzc29yc1xuICBwLm9yaWdpbiA9IGZ1bmN0aW9uKHYsIGJ1aWxkKSB7XG4gICAgdmFyIHBhcnRzO1xuXG4gICAgaWYgKHRoaXMuX3BhcnRzLnVybikge1xuICAgICAgcmV0dXJuIHYgPT09IHVuZGVmaW5lZCA/ICcnIDogdGhpcztcbiAgICB9XG5cbiAgICBpZiAodiA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICB2YXIgcHJvdG9jb2wgPSB0aGlzLnByb3RvY29sKCk7XG4gICAgICB2YXIgYXV0aG9yaXR5ID0gdGhpcy5hdXRob3JpdHkoKTtcbiAgICAgIGlmICghYXV0aG9yaXR5KSByZXR1cm4gJyc7XG4gICAgICByZXR1cm4gKHByb3RvY29sID8gcHJvdG9jb2wgKyAnOi8vJyA6ICcnKSArIHRoaXMuYXV0aG9yaXR5KCk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHZhciBvcmlnaW4gPSBVUkkodik7XG4gICAgICB0aGlzXG4gICAgICAgIC5wcm90b2NvbChvcmlnaW4ucHJvdG9jb2woKSlcbiAgICAgICAgLmF1dGhvcml0eShvcmlnaW4uYXV0aG9yaXR5KCkpXG4gICAgICAgIC5idWlsZCghYnVpbGQpO1xuICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuICB9O1xuICBwLmhvc3QgPSBmdW5jdGlvbih2LCBidWlsZCkge1xuICAgIGlmICh0aGlzLl9wYXJ0cy51cm4pIHtcbiAgICAgIHJldHVybiB2ID09PSB1bmRlZmluZWQgPyAnJyA6IHRoaXM7XG4gICAgfVxuXG4gICAgaWYgKHYgPT09IHVuZGVmaW5lZCkge1xuICAgICAgcmV0dXJuIHRoaXMuX3BhcnRzLmhvc3RuYW1lID8gVVJJLmJ1aWxkSG9zdCh0aGlzLl9wYXJ0cykgOiAnJztcbiAgICB9IGVsc2Uge1xuICAgICAgdmFyIHJlcyA9IFVSSS5wYXJzZUhvc3QodiwgdGhpcy5fcGFydHMpO1xuICAgICAgaWYgKHJlcyAhPT0gJy8nKSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ0hvc3RuYW1lIFwiJyArIHYgKyAnXCIgY29udGFpbnMgY2hhcmFjdGVycyBvdGhlciB0aGFuIFtBLVowLTkuLV0nKTtcbiAgICAgIH1cblxuICAgICAgdGhpcy5idWlsZCghYnVpbGQpO1xuICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuICB9O1xuICBwLmF1dGhvcml0eSA9IGZ1bmN0aW9uKHYsIGJ1aWxkKSB7XG4gICAgaWYgKHRoaXMuX3BhcnRzLnVybikge1xuICAgICAgcmV0dXJuIHYgPT09IHVuZGVmaW5lZCA/ICcnIDogdGhpcztcbiAgICB9XG5cbiAgICBpZiAodiA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICByZXR1cm4gdGhpcy5fcGFydHMuaG9zdG5hbWUgPyBVUkkuYnVpbGRBdXRob3JpdHkodGhpcy5fcGFydHMpIDogJyc7XG4gICAgfSBlbHNlIHtcbiAgICAgIHZhciByZXMgPSBVUkkucGFyc2VBdXRob3JpdHkodiwgdGhpcy5fcGFydHMpO1xuICAgICAgaWYgKHJlcyAhPT0gJy8nKSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ0hvc3RuYW1lIFwiJyArIHYgKyAnXCIgY29udGFpbnMgY2hhcmFjdGVycyBvdGhlciB0aGFuIFtBLVowLTkuLV0nKTtcbiAgICAgIH1cblxuICAgICAgdGhpcy5idWlsZCghYnVpbGQpO1xuICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuICB9O1xuICBwLnVzZXJpbmZvID0gZnVuY3Rpb24odiwgYnVpbGQpIHtcbiAgICBpZiAodGhpcy5fcGFydHMudXJuKSB7XG4gICAgICByZXR1cm4gdiA9PT0gdW5kZWZpbmVkID8gJycgOiB0aGlzO1xuICAgIH1cblxuICAgIGlmICh2ID09PSB1bmRlZmluZWQpIHtcbiAgICAgIGlmICghdGhpcy5fcGFydHMudXNlcm5hbWUpIHtcbiAgICAgICAgcmV0dXJuICcnO1xuICAgICAgfVxuXG4gICAgICB2YXIgdCA9IFVSSS5idWlsZFVzZXJpbmZvKHRoaXMuX3BhcnRzKTtcbiAgICAgIHJldHVybiB0LnN1YnN0cmluZygwLCB0Lmxlbmd0aCAtMSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIGlmICh2W3YubGVuZ3RoLTFdICE9PSAnQCcpIHtcbiAgICAgICAgdiArPSAnQCc7XG4gICAgICB9XG5cbiAgICAgIFVSSS5wYXJzZVVzZXJpbmZvKHYsIHRoaXMuX3BhcnRzKTtcbiAgICAgIHRoaXMuYnVpbGQoIWJ1aWxkKTtcbiAgICAgIHJldHVybiB0aGlzO1xuICAgIH1cbiAgfTtcbiAgcC5yZXNvdXJjZSA9IGZ1bmN0aW9uKHYsIGJ1aWxkKSB7XG4gICAgdmFyIHBhcnRzO1xuXG4gICAgaWYgKHYgPT09IHVuZGVmaW5lZCkge1xuICAgICAgcmV0dXJuIHRoaXMucGF0aCgpICsgdGhpcy5zZWFyY2goKSArIHRoaXMuaGFzaCgpO1xuICAgIH1cblxuICAgIHBhcnRzID0gVVJJLnBhcnNlKHYpO1xuICAgIHRoaXMuX3BhcnRzLnBhdGggPSBwYXJ0cy5wYXRoO1xuICAgIHRoaXMuX3BhcnRzLnF1ZXJ5ID0gcGFydHMucXVlcnk7XG4gICAgdGhpcy5fcGFydHMuZnJhZ21lbnQgPSBwYXJ0cy5mcmFnbWVudDtcbiAgICB0aGlzLmJ1aWxkKCFidWlsZCk7XG4gICAgcmV0dXJuIHRoaXM7XG4gIH07XG5cbiAgLy8gZnJhY3Rpb24gYWNjZXNzb3JzXG4gIHAuc3ViZG9tYWluID0gZnVuY3Rpb24odiwgYnVpbGQpIHtcbiAgICBpZiAodGhpcy5fcGFydHMudXJuKSB7XG4gICAgICByZXR1cm4gdiA9PT0gdW5kZWZpbmVkID8gJycgOiB0aGlzO1xuICAgIH1cblxuICAgIC8vIGNvbnZlbmllbmNlLCByZXR1cm4gXCJ3d3dcIiBmcm9tIFwid3d3LmV4YW1wbGUub3JnXCJcbiAgICBpZiAodiA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICBpZiAoIXRoaXMuX3BhcnRzLmhvc3RuYW1lIHx8IHRoaXMuaXMoJ0lQJykpIHtcbiAgICAgICAgcmV0dXJuICcnO1xuICAgICAgfVxuXG4gICAgICAvLyBncmFiIGRvbWFpbiBhbmQgYWRkIGFub3RoZXIgc2VnbWVudFxuICAgICAgdmFyIGVuZCA9IHRoaXMuX3BhcnRzLmhvc3RuYW1lLmxlbmd0aCAtIHRoaXMuZG9tYWluKCkubGVuZ3RoIC0gMTtcbiAgICAgIHJldHVybiB0aGlzLl9wYXJ0cy5ob3N0bmFtZS5zdWJzdHJpbmcoMCwgZW5kKSB8fCAnJztcbiAgICB9IGVsc2Uge1xuICAgICAgdmFyIGUgPSB0aGlzLl9wYXJ0cy5ob3N0bmFtZS5sZW5ndGggLSB0aGlzLmRvbWFpbigpLmxlbmd0aDtcbiAgICAgIHZhciBzdWIgPSB0aGlzLl9wYXJ0cy5ob3N0bmFtZS5zdWJzdHJpbmcoMCwgZSk7XG4gICAgICB2YXIgcmVwbGFjZSA9IG5ldyBSZWdFeHAoJ14nICsgZXNjYXBlUmVnRXgoc3ViKSk7XG5cbiAgICAgIGlmICh2ICYmIHYuY2hhckF0KHYubGVuZ3RoIC0gMSkgIT09ICcuJykge1xuICAgICAgICB2ICs9ICcuJztcbiAgICAgIH1cblxuICAgICAgaWYgKHYpIHtcbiAgICAgICAgVVJJLmVuc3VyZVZhbGlkSG9zdG5hbWUodik7XG4gICAgICB9XG5cbiAgICAgIHRoaXMuX3BhcnRzLmhvc3RuYW1lID0gdGhpcy5fcGFydHMuaG9zdG5hbWUucmVwbGFjZShyZXBsYWNlLCB2KTtcbiAgICAgIHRoaXMuYnVpbGQoIWJ1aWxkKTtcbiAgICAgIHJldHVybiB0aGlzO1xuICAgIH1cbiAgfTtcbiAgcC5kb21haW4gPSBmdW5jdGlvbih2LCBidWlsZCkge1xuICAgIGlmICh0aGlzLl9wYXJ0cy51cm4pIHtcbiAgICAgIHJldHVybiB2ID09PSB1bmRlZmluZWQgPyAnJyA6IHRoaXM7XG4gICAgfVxuXG4gICAgaWYgKHR5cGVvZiB2ID09PSAnYm9vbGVhbicpIHtcbiAgICAgIGJ1aWxkID0gdjtcbiAgICAgIHYgPSB1bmRlZmluZWQ7XG4gICAgfVxuXG4gICAgLy8gY29udmVuaWVuY2UsIHJldHVybiBcImV4YW1wbGUub3JnXCIgZnJvbSBcInd3dy5leGFtcGxlLm9yZ1wiXG4gICAgaWYgKHYgPT09IHVuZGVmaW5lZCkge1xuICAgICAgaWYgKCF0aGlzLl9wYXJ0cy5ob3N0bmFtZSB8fCB0aGlzLmlzKCdJUCcpKSB7XG4gICAgICAgIHJldHVybiAnJztcbiAgICAgIH1cblxuICAgICAgLy8gaWYgaG9zdG5hbWUgY29uc2lzdHMgb2YgMSBvciAyIHNlZ21lbnRzLCBpdCBtdXN0IGJlIHRoZSBkb21haW5cbiAgICAgIHZhciB0ID0gdGhpcy5fcGFydHMuaG9zdG5hbWUubWF0Y2goL1xcLi9nKTtcbiAgICAgIGlmICh0ICYmIHQubGVuZ3RoIDwgMikge1xuICAgICAgICByZXR1cm4gdGhpcy5fcGFydHMuaG9zdG5hbWU7XG4gICAgICB9XG5cbiAgICAgIC8vIGdyYWIgdGxkIGFuZCBhZGQgYW5vdGhlciBzZWdtZW50XG4gICAgICB2YXIgZW5kID0gdGhpcy5fcGFydHMuaG9zdG5hbWUubGVuZ3RoIC0gdGhpcy50bGQoYnVpbGQpLmxlbmd0aCAtIDE7XG4gICAgICBlbmQgPSB0aGlzLl9wYXJ0cy5ob3N0bmFtZS5sYXN0SW5kZXhPZignLicsIGVuZCAtMSkgKyAxO1xuICAgICAgcmV0dXJuIHRoaXMuX3BhcnRzLmhvc3RuYW1lLnN1YnN0cmluZyhlbmQpIHx8ICcnO1xuICAgIH0gZWxzZSB7XG4gICAgICBpZiAoIXYpIHtcbiAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignY2Fubm90IHNldCBkb21haW4gZW1wdHknKTtcbiAgICAgIH1cblxuICAgICAgVVJJLmVuc3VyZVZhbGlkSG9zdG5hbWUodik7XG5cbiAgICAgIGlmICghdGhpcy5fcGFydHMuaG9zdG5hbWUgfHwgdGhpcy5pcygnSVAnKSkge1xuICAgICAgICB0aGlzLl9wYXJ0cy5ob3N0bmFtZSA9IHY7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB2YXIgcmVwbGFjZSA9IG5ldyBSZWdFeHAoZXNjYXBlUmVnRXgodGhpcy5kb21haW4oKSkgKyAnJCcpO1xuICAgICAgICB0aGlzLl9wYXJ0cy5ob3N0bmFtZSA9IHRoaXMuX3BhcnRzLmhvc3RuYW1lLnJlcGxhY2UocmVwbGFjZSwgdik7XG4gICAgICB9XG5cbiAgICAgIHRoaXMuYnVpbGQoIWJ1aWxkKTtcbiAgICAgIHJldHVybiB0aGlzO1xuICAgIH1cbiAgfTtcbiAgcC50bGQgPSBmdW5jdGlvbih2LCBidWlsZCkge1xuICAgIGlmICh0aGlzLl9wYXJ0cy51cm4pIHtcbiAgICAgIHJldHVybiB2ID09PSB1bmRlZmluZWQgPyAnJyA6IHRoaXM7XG4gICAgfVxuXG4gICAgaWYgKHR5cGVvZiB2ID09PSAnYm9vbGVhbicpIHtcbiAgICAgIGJ1aWxkID0gdjtcbiAgICAgIHYgPSB1bmRlZmluZWQ7XG4gICAgfVxuXG4gICAgLy8gcmV0dXJuIFwib3JnXCIgZnJvbSBcInd3dy5leGFtcGxlLm9yZ1wiXG4gICAgaWYgKHYgPT09IHVuZGVmaW5lZCkge1xuICAgICAgaWYgKCF0aGlzLl9wYXJ0cy5ob3N0bmFtZSB8fCB0aGlzLmlzKCdJUCcpKSB7XG4gICAgICAgIHJldHVybiAnJztcbiAgICAgIH1cblxuICAgICAgdmFyIHBvcyA9IHRoaXMuX3BhcnRzLmhvc3RuYW1lLmxhc3RJbmRleE9mKCcuJyk7XG4gICAgICB2YXIgdGxkID0gdGhpcy5fcGFydHMuaG9zdG5hbWUuc3Vic3RyaW5nKHBvcyArIDEpO1xuXG4gICAgICBpZiAoYnVpbGQgIT09IHRydWUgJiYgU0xEICYmIFNMRC5saXN0W3RsZC50b0xvd2VyQ2FzZSgpXSkge1xuICAgICAgICByZXR1cm4gU0xELmdldCh0aGlzLl9wYXJ0cy5ob3N0bmFtZSkgfHwgdGxkO1xuICAgICAgfVxuXG4gICAgICByZXR1cm4gdGxkO1xuICAgIH0gZWxzZSB7XG4gICAgICB2YXIgcmVwbGFjZTtcblxuICAgICAgaWYgKCF2KSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ2Nhbm5vdCBzZXQgVExEIGVtcHR5Jyk7XG4gICAgICB9IGVsc2UgaWYgKHYubWF0Y2goL1teYS16QS1aMC05LV0vKSkge1xuICAgICAgICBpZiAoU0xEICYmIFNMRC5pcyh2KSkge1xuICAgICAgICAgIHJlcGxhY2UgPSBuZXcgUmVnRXhwKGVzY2FwZVJlZ0V4KHRoaXMudGxkKCkpICsgJyQnKTtcbiAgICAgICAgICB0aGlzLl9wYXJ0cy5ob3N0bmFtZSA9IHRoaXMuX3BhcnRzLmhvc3RuYW1lLnJlcGxhY2UocmVwbGFjZSwgdik7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignVExEIFwiJyArIHYgKyAnXCIgY29udGFpbnMgY2hhcmFjdGVycyBvdGhlciB0aGFuIFtBLVowLTldJyk7XG4gICAgICAgIH1cbiAgICAgIH0gZWxzZSBpZiAoIXRoaXMuX3BhcnRzLmhvc3RuYW1lIHx8IHRoaXMuaXMoJ0lQJykpIHtcbiAgICAgICAgdGhyb3cgbmV3IFJlZmVyZW5jZUVycm9yKCdjYW5ub3Qgc2V0IFRMRCBvbiBub24tZG9tYWluIGhvc3QnKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHJlcGxhY2UgPSBuZXcgUmVnRXhwKGVzY2FwZVJlZ0V4KHRoaXMudGxkKCkpICsgJyQnKTtcbiAgICAgICAgdGhpcy5fcGFydHMuaG9zdG5hbWUgPSB0aGlzLl9wYXJ0cy5ob3N0bmFtZS5yZXBsYWNlKHJlcGxhY2UsIHYpO1xuICAgICAgfVxuXG4gICAgICB0aGlzLmJ1aWxkKCFidWlsZCk7XG4gICAgICByZXR1cm4gdGhpcztcbiAgICB9XG4gIH07XG4gIHAuZGlyZWN0b3J5ID0gZnVuY3Rpb24odiwgYnVpbGQpIHtcbiAgICBpZiAodGhpcy5fcGFydHMudXJuKSB7XG4gICAgICByZXR1cm4gdiA9PT0gdW5kZWZpbmVkID8gJycgOiB0aGlzO1xuICAgIH1cblxuICAgIGlmICh2ID09PSB1bmRlZmluZWQgfHwgdiA9PT0gdHJ1ZSkge1xuICAgICAgaWYgKCF0aGlzLl9wYXJ0cy5wYXRoICYmICF0aGlzLl9wYXJ0cy5ob3N0bmFtZSkge1xuICAgICAgICByZXR1cm4gJyc7XG4gICAgICB9XG5cbiAgICAgIGlmICh0aGlzLl9wYXJ0cy5wYXRoID09PSAnLycpIHtcbiAgICAgICAgcmV0dXJuICcvJztcbiAgICAgIH1cblxuICAgICAgdmFyIGVuZCA9IHRoaXMuX3BhcnRzLnBhdGgubGVuZ3RoIC0gdGhpcy5maWxlbmFtZSgpLmxlbmd0aCAtIDE7XG4gICAgICB2YXIgcmVzID0gdGhpcy5fcGFydHMucGF0aC5zdWJzdHJpbmcoMCwgZW5kKSB8fCAodGhpcy5fcGFydHMuaG9zdG5hbWUgPyAnLycgOiAnJyk7XG5cbiAgICAgIHJldHVybiB2ID8gVVJJLmRlY29kZVBhdGgocmVzKSA6IHJlcztcblxuICAgIH0gZWxzZSB7XG4gICAgICB2YXIgZSA9IHRoaXMuX3BhcnRzLnBhdGgubGVuZ3RoIC0gdGhpcy5maWxlbmFtZSgpLmxlbmd0aDtcbiAgICAgIHZhciBkaXJlY3RvcnkgPSB0aGlzLl9wYXJ0cy5wYXRoLnN1YnN0cmluZygwLCBlKTtcbiAgICAgIHZhciByZXBsYWNlID0gbmV3IFJlZ0V4cCgnXicgKyBlc2NhcGVSZWdFeChkaXJlY3RvcnkpKTtcblxuICAgICAgLy8gZnVsbHkgcXVhbGlmaWVyIGRpcmVjdG9yaWVzIGJlZ2luIHdpdGggYSBzbGFzaFxuICAgICAgaWYgKCF0aGlzLmlzKCdyZWxhdGl2ZScpKSB7XG4gICAgICAgIGlmICghdikge1xuICAgICAgICAgIHYgPSAnLyc7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAodi5jaGFyQXQoMCkgIT09ICcvJykge1xuICAgICAgICAgIHYgPSAnLycgKyB2O1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIC8vIGRpcmVjdG9yaWVzIGFsd2F5cyBlbmQgd2l0aCBhIHNsYXNoXG4gICAgICBpZiAodiAmJiB2LmNoYXJBdCh2Lmxlbmd0aCAtIDEpICE9PSAnLycpIHtcbiAgICAgICAgdiArPSAnLyc7XG4gICAgICB9XG5cbiAgICAgIHYgPSBVUkkucmVjb2RlUGF0aCh2KTtcbiAgICAgIHRoaXMuX3BhcnRzLnBhdGggPSB0aGlzLl9wYXJ0cy5wYXRoLnJlcGxhY2UocmVwbGFjZSwgdik7XG4gICAgICB0aGlzLmJ1aWxkKCFidWlsZCk7XG4gICAgICByZXR1cm4gdGhpcztcbiAgICB9XG4gIH07XG4gIHAuZmlsZW5hbWUgPSBmdW5jdGlvbih2LCBidWlsZCkge1xuICAgIGlmICh0aGlzLl9wYXJ0cy51cm4pIHtcbiAgICAgIHJldHVybiB2ID09PSB1bmRlZmluZWQgPyAnJyA6IHRoaXM7XG4gICAgfVxuXG4gICAgaWYgKHYgPT09IHVuZGVmaW5lZCB8fCB2ID09PSB0cnVlKSB7XG4gICAgICBpZiAoIXRoaXMuX3BhcnRzLnBhdGggfHwgdGhpcy5fcGFydHMucGF0aCA9PT0gJy8nKSB7XG4gICAgICAgIHJldHVybiAnJztcbiAgICAgIH1cblxuICAgICAgdmFyIHBvcyA9IHRoaXMuX3BhcnRzLnBhdGgubGFzdEluZGV4T2YoJy8nKTtcbiAgICAgIHZhciByZXMgPSB0aGlzLl9wYXJ0cy5wYXRoLnN1YnN0cmluZyhwb3MrMSk7XG5cbiAgICAgIHJldHVybiB2ID8gVVJJLmRlY29kZVBhdGhTZWdtZW50KHJlcykgOiByZXM7XG4gICAgfSBlbHNlIHtcbiAgICAgIHZhciBtdXRhdGVkRGlyZWN0b3J5ID0gZmFsc2U7XG5cbiAgICAgIGlmICh2LmNoYXJBdCgwKSA9PT0gJy8nKSB7XG4gICAgICAgIHYgPSB2LnN1YnN0cmluZygxKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHYubWF0Y2goL1xcLj9cXC8vKSkge1xuICAgICAgICBtdXRhdGVkRGlyZWN0b3J5ID0gdHJ1ZTtcbiAgICAgIH1cblxuICAgICAgdmFyIHJlcGxhY2UgPSBuZXcgUmVnRXhwKGVzY2FwZVJlZ0V4KHRoaXMuZmlsZW5hbWUoKSkgKyAnJCcpO1xuICAgICAgdiA9IFVSSS5yZWNvZGVQYXRoKHYpO1xuICAgICAgdGhpcy5fcGFydHMucGF0aCA9IHRoaXMuX3BhcnRzLnBhdGgucmVwbGFjZShyZXBsYWNlLCB2KTtcblxuICAgICAgaWYgKG11dGF0ZWREaXJlY3RvcnkpIHtcbiAgICAgICAgdGhpcy5ub3JtYWxpemVQYXRoKGJ1aWxkKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRoaXMuYnVpbGQoIWJ1aWxkKTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuICB9O1xuICBwLnN1ZmZpeCA9IGZ1bmN0aW9uKHYsIGJ1aWxkKSB7XG4gICAgaWYgKHRoaXMuX3BhcnRzLnVybikge1xuICAgICAgcmV0dXJuIHYgPT09IHVuZGVmaW5lZCA/ICcnIDogdGhpcztcbiAgICB9XG5cbiAgICBpZiAodiA9PT0gdW5kZWZpbmVkIHx8IHYgPT09IHRydWUpIHtcbiAgICAgIGlmICghdGhpcy5fcGFydHMucGF0aCB8fCB0aGlzLl9wYXJ0cy5wYXRoID09PSAnLycpIHtcbiAgICAgICAgcmV0dXJuICcnO1xuICAgICAgfVxuXG4gICAgICB2YXIgZmlsZW5hbWUgPSB0aGlzLmZpbGVuYW1lKCk7XG4gICAgICB2YXIgcG9zID0gZmlsZW5hbWUubGFzdEluZGV4T2YoJy4nKTtcbiAgICAgIHZhciBzLCByZXM7XG5cbiAgICAgIGlmIChwb3MgPT09IC0xKSB7XG4gICAgICAgIHJldHVybiAnJztcbiAgICAgIH1cblxuICAgICAgLy8gc3VmZml4IG1heSBvbmx5IGNvbnRhaW4gYWxudW0gY2hhcmFjdGVycyAoeXVwLCBJIG1hZGUgdGhpcyB1cC4pXG4gICAgICBzID0gZmlsZW5hbWUuc3Vic3RyaW5nKHBvcysxKTtcbiAgICAgIHJlcyA9ICgvXlthLXowLTklXSskL2kpLnRlc3QocykgPyBzIDogJyc7XG4gICAgICByZXR1cm4gdiA/IFVSSS5kZWNvZGVQYXRoU2VnbWVudChyZXMpIDogcmVzO1xuICAgIH0gZWxzZSB7XG4gICAgICBpZiAodi5jaGFyQXQoMCkgPT09ICcuJykge1xuICAgICAgICB2ID0gdi5zdWJzdHJpbmcoMSk7XG4gICAgICB9XG5cbiAgICAgIHZhciBzdWZmaXggPSB0aGlzLnN1ZmZpeCgpO1xuICAgICAgdmFyIHJlcGxhY2U7XG5cbiAgICAgIGlmICghc3VmZml4KSB7XG4gICAgICAgIGlmICghdikge1xuICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy5fcGFydHMucGF0aCArPSAnLicgKyBVUkkucmVjb2RlUGF0aCh2KTtcbiAgICAgIH0gZWxzZSBpZiAoIXYpIHtcbiAgICAgICAgcmVwbGFjZSA9IG5ldyBSZWdFeHAoZXNjYXBlUmVnRXgoJy4nICsgc3VmZml4KSArICckJyk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICByZXBsYWNlID0gbmV3IFJlZ0V4cChlc2NhcGVSZWdFeChzdWZmaXgpICsgJyQnKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHJlcGxhY2UpIHtcbiAgICAgICAgdiA9IFVSSS5yZWNvZGVQYXRoKHYpO1xuICAgICAgICB0aGlzLl9wYXJ0cy5wYXRoID0gdGhpcy5fcGFydHMucGF0aC5yZXBsYWNlKHJlcGxhY2UsIHYpO1xuICAgICAgfVxuXG4gICAgICB0aGlzLmJ1aWxkKCFidWlsZCk7XG4gICAgICByZXR1cm4gdGhpcztcbiAgICB9XG4gIH07XG4gIHAuc2VnbWVudCA9IGZ1bmN0aW9uKHNlZ21lbnQsIHYsIGJ1aWxkKSB7XG4gICAgdmFyIHNlcGFyYXRvciA9IHRoaXMuX3BhcnRzLnVybiA/ICc6JyA6ICcvJztcbiAgICB2YXIgcGF0aCA9IHRoaXMucGF0aCgpO1xuICAgIHZhciBhYnNvbHV0ZSA9IHBhdGguc3Vic3RyaW5nKDAsIDEpID09PSAnLyc7XG4gICAgdmFyIHNlZ21lbnRzID0gcGF0aC5zcGxpdChzZXBhcmF0b3IpO1xuXG4gICAgaWYgKHNlZ21lbnQgIT09IHVuZGVmaW5lZCAmJiB0eXBlb2Ygc2VnbWVudCAhPT0gJ251bWJlcicpIHtcbiAgICAgIGJ1aWxkID0gdjtcbiAgICAgIHYgPSBzZWdtZW50O1xuICAgICAgc2VnbWVudCA9IHVuZGVmaW5lZDtcbiAgICB9XG5cbiAgICBpZiAoc2VnbWVudCAhPT0gdW5kZWZpbmVkICYmIHR5cGVvZiBzZWdtZW50ICE9PSAnbnVtYmVyJykge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdCYWQgc2VnbWVudCBcIicgKyBzZWdtZW50ICsgJ1wiLCBtdXN0IGJlIDAtYmFzZWQgaW50ZWdlcicpO1xuICAgIH1cblxuICAgIGlmIChhYnNvbHV0ZSkge1xuICAgICAgc2VnbWVudHMuc2hpZnQoKTtcbiAgICB9XG5cbiAgICBpZiAoc2VnbWVudCA8IDApIHtcbiAgICAgIC8vIGFsbG93IG5lZ2F0aXZlIGluZGV4ZXMgdG8gYWRkcmVzcyBmcm9tIHRoZSBlbmRcbiAgICAgIHNlZ21lbnQgPSBNYXRoLm1heChzZWdtZW50cy5sZW5ndGggKyBzZWdtZW50LCAwKTtcbiAgICB9XG5cbiAgICBpZiAodiA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAvKmpzaGludCBsYXhicmVhazogdHJ1ZSAqL1xuICAgICAgcmV0dXJuIHNlZ21lbnQgPT09IHVuZGVmaW5lZFxuICAgICAgICA/IHNlZ21lbnRzXG4gICAgICAgIDogc2VnbWVudHNbc2VnbWVudF07XG4gICAgICAvKmpzaGludCBsYXhicmVhazogZmFsc2UgKi9cbiAgICB9IGVsc2UgaWYgKHNlZ21lbnQgPT09IG51bGwgfHwgc2VnbWVudHNbc2VnbWVudF0gPT09IHVuZGVmaW5lZCkge1xuICAgICAgaWYgKGlzQXJyYXkodikpIHtcbiAgICAgICAgc2VnbWVudHMgPSBbXTtcbiAgICAgICAgLy8gY29sbGFwc2UgZW1wdHkgZWxlbWVudHMgd2l0aGluIGFycmF5XG4gICAgICAgIGZvciAodmFyIGk9MCwgbD12Lmxlbmd0aDsgaSA8IGw7IGkrKykge1xuICAgICAgICAgIGlmICghdltpXS5sZW5ndGggJiYgKCFzZWdtZW50cy5sZW5ndGggfHwgIXNlZ21lbnRzW3NlZ21lbnRzLmxlbmd0aCAtMV0ubGVuZ3RoKSkge1xuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgaWYgKHNlZ21lbnRzLmxlbmd0aCAmJiAhc2VnbWVudHNbc2VnbWVudHMubGVuZ3RoIC0xXS5sZW5ndGgpIHtcbiAgICAgICAgICAgIHNlZ21lbnRzLnBvcCgpO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIHNlZ21lbnRzLnB1c2godHJpbVNsYXNoZXModltpXSkpO1xuICAgICAgICB9XG4gICAgICB9IGVsc2UgaWYgKHYgfHwgdHlwZW9mIHYgPT09ICdzdHJpbmcnKSB7XG4gICAgICAgIHYgPSB0cmltU2xhc2hlcyh2KTtcbiAgICAgICAgaWYgKHNlZ21lbnRzW3NlZ21lbnRzLmxlbmd0aCAtMV0gPT09ICcnKSB7XG4gICAgICAgICAgLy8gZW1wdHkgdHJhaWxpbmcgZWxlbWVudHMgaGF2ZSB0byBiZSBvdmVyd3JpdHRlblxuICAgICAgICAgIC8vIHRvIHByZXZlbnQgcmVzdWx0cyBzdWNoIGFzIC9mb28vL2JhclxuICAgICAgICAgIHNlZ21lbnRzW3NlZ21lbnRzLmxlbmd0aCAtMV0gPSB2O1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHNlZ21lbnRzLnB1c2godik7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9IGVsc2Uge1xuICAgICAgaWYgKHYpIHtcbiAgICAgICAgc2VnbWVudHNbc2VnbWVudF0gPSB0cmltU2xhc2hlcyh2KTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHNlZ21lbnRzLnNwbGljZShzZWdtZW50LCAxKTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAoYWJzb2x1dGUpIHtcbiAgICAgIHNlZ21lbnRzLnVuc2hpZnQoJycpO1xuICAgIH1cblxuICAgIHJldHVybiB0aGlzLnBhdGgoc2VnbWVudHMuam9pbihzZXBhcmF0b3IpLCBidWlsZCk7XG4gIH07XG4gIHAuc2VnbWVudENvZGVkID0gZnVuY3Rpb24oc2VnbWVudCwgdiwgYnVpbGQpIHtcbiAgICB2YXIgc2VnbWVudHMsIGksIGw7XG5cbiAgICBpZiAodHlwZW9mIHNlZ21lbnQgIT09ICdudW1iZXInKSB7XG4gICAgICBidWlsZCA9IHY7XG4gICAgICB2ID0gc2VnbWVudDtcbiAgICAgIHNlZ21lbnQgPSB1bmRlZmluZWQ7XG4gICAgfVxuXG4gICAgaWYgKHYgPT09IHVuZGVmaW5lZCkge1xuICAgICAgc2VnbWVudHMgPSB0aGlzLnNlZ21lbnQoc2VnbWVudCwgdiwgYnVpbGQpO1xuICAgICAgaWYgKCFpc0FycmF5KHNlZ21lbnRzKSkge1xuICAgICAgICBzZWdtZW50cyA9IHNlZ21lbnRzICE9PSB1bmRlZmluZWQgPyBVUkkuZGVjb2RlKHNlZ21lbnRzKSA6IHVuZGVmaW5lZDtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGZvciAoaSA9IDAsIGwgPSBzZWdtZW50cy5sZW5ndGg7IGkgPCBsOyBpKyspIHtcbiAgICAgICAgICBzZWdtZW50c1tpXSA9IFVSSS5kZWNvZGUoc2VnbWVudHNbaV0pO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHJldHVybiBzZWdtZW50cztcbiAgICB9XG5cbiAgICBpZiAoIWlzQXJyYXkodikpIHtcbiAgICAgIHYgPSAodHlwZW9mIHYgPT09ICdzdHJpbmcnIHx8IHYgaW5zdGFuY2VvZiBTdHJpbmcpID8gVVJJLmVuY29kZSh2KSA6IHY7XG4gICAgfSBlbHNlIHtcbiAgICAgIGZvciAoaSA9IDAsIGwgPSB2Lmxlbmd0aDsgaSA8IGw7IGkrKykge1xuICAgICAgICB2W2ldID0gVVJJLmVuY29kZSh2W2ldKTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gdGhpcy5zZWdtZW50KHNlZ21lbnQsIHYsIGJ1aWxkKTtcbiAgfTtcblxuICAvLyBtdXRhdGluZyBxdWVyeSBzdHJpbmdcbiAgdmFyIHEgPSBwLnF1ZXJ5O1xuICBwLnF1ZXJ5ID0gZnVuY3Rpb24odiwgYnVpbGQpIHtcbiAgICBpZiAodiA9PT0gdHJ1ZSkge1xuICAgICAgcmV0dXJuIFVSSS5wYXJzZVF1ZXJ5KHRoaXMuX3BhcnRzLnF1ZXJ5LCB0aGlzLl9wYXJ0cy5lc2NhcGVRdWVyeVNwYWNlKTtcbiAgICB9IGVsc2UgaWYgKHR5cGVvZiB2ID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICB2YXIgZGF0YSA9IFVSSS5wYXJzZVF1ZXJ5KHRoaXMuX3BhcnRzLnF1ZXJ5LCB0aGlzLl9wYXJ0cy5lc2NhcGVRdWVyeVNwYWNlKTtcbiAgICAgIHZhciByZXN1bHQgPSB2LmNhbGwodGhpcywgZGF0YSk7XG4gICAgICB0aGlzLl9wYXJ0cy5xdWVyeSA9IFVSSS5idWlsZFF1ZXJ5KHJlc3VsdCB8fCBkYXRhLCB0aGlzLl9wYXJ0cy5kdXBsaWNhdGVRdWVyeVBhcmFtZXRlcnMsIHRoaXMuX3BhcnRzLmVzY2FwZVF1ZXJ5U3BhY2UpO1xuICAgICAgdGhpcy5idWlsZCghYnVpbGQpO1xuICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfSBlbHNlIGlmICh2ICE9PSB1bmRlZmluZWQgJiYgdHlwZW9mIHYgIT09ICdzdHJpbmcnKSB7XG4gICAgICB0aGlzLl9wYXJ0cy5xdWVyeSA9IFVSSS5idWlsZFF1ZXJ5KHYsIHRoaXMuX3BhcnRzLmR1cGxpY2F0ZVF1ZXJ5UGFyYW1ldGVycywgdGhpcy5fcGFydHMuZXNjYXBlUXVlcnlTcGFjZSk7XG4gICAgICB0aGlzLmJ1aWxkKCFidWlsZCk7XG4gICAgICByZXR1cm4gdGhpcztcbiAgICB9IGVsc2Uge1xuICAgICAgcmV0dXJuIHEuY2FsbCh0aGlzLCB2LCBidWlsZCk7XG4gICAgfVxuICB9O1xuICBwLnNldFF1ZXJ5ID0gZnVuY3Rpb24obmFtZSwgdmFsdWUsIGJ1aWxkKSB7XG4gICAgdmFyIGRhdGEgPSBVUkkucGFyc2VRdWVyeSh0aGlzLl9wYXJ0cy5xdWVyeSwgdGhpcy5fcGFydHMuZXNjYXBlUXVlcnlTcGFjZSk7XG5cbiAgICBpZiAodHlwZW9mIG5hbWUgPT09ICdzdHJpbmcnIHx8IG5hbWUgaW5zdGFuY2VvZiBTdHJpbmcpIHtcbiAgICAgIGRhdGFbbmFtZV0gPSB2YWx1ZSAhPT0gdW5kZWZpbmVkID8gdmFsdWUgOiBudWxsO1xuICAgIH0gZWxzZSBpZiAodHlwZW9mIG5hbWUgPT09ICdvYmplY3QnKSB7XG4gICAgICBmb3IgKHZhciBrZXkgaW4gbmFtZSkge1xuICAgICAgICBpZiAoaGFzT3duLmNhbGwobmFtZSwga2V5KSkge1xuICAgICAgICAgIGRhdGFba2V5XSA9IG5hbWVba2V5XTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdVUkkuYWRkUXVlcnkoKSBhY2NlcHRzIGFuIG9iamVjdCwgc3RyaW5nIGFzIHRoZSBuYW1lIHBhcmFtZXRlcicpO1xuICAgIH1cblxuICAgIHRoaXMuX3BhcnRzLnF1ZXJ5ID0gVVJJLmJ1aWxkUXVlcnkoZGF0YSwgdGhpcy5fcGFydHMuZHVwbGljYXRlUXVlcnlQYXJhbWV0ZXJzLCB0aGlzLl9wYXJ0cy5lc2NhcGVRdWVyeVNwYWNlKTtcbiAgICBpZiAodHlwZW9mIG5hbWUgIT09ICdzdHJpbmcnKSB7XG4gICAgICBidWlsZCA9IHZhbHVlO1xuICAgIH1cblxuICAgIHRoaXMuYnVpbGQoIWJ1aWxkKTtcbiAgICByZXR1cm4gdGhpcztcbiAgfTtcbiAgcC5hZGRRdWVyeSA9IGZ1bmN0aW9uKG5hbWUsIHZhbHVlLCBidWlsZCkge1xuICAgIHZhciBkYXRhID0gVVJJLnBhcnNlUXVlcnkodGhpcy5fcGFydHMucXVlcnksIHRoaXMuX3BhcnRzLmVzY2FwZVF1ZXJ5U3BhY2UpO1xuICAgIFVSSS5hZGRRdWVyeShkYXRhLCBuYW1lLCB2YWx1ZSA9PT0gdW5kZWZpbmVkID8gbnVsbCA6IHZhbHVlKTtcbiAgICB0aGlzLl9wYXJ0cy5xdWVyeSA9IFVSSS5idWlsZFF1ZXJ5KGRhdGEsIHRoaXMuX3BhcnRzLmR1cGxpY2F0ZVF1ZXJ5UGFyYW1ldGVycywgdGhpcy5fcGFydHMuZXNjYXBlUXVlcnlTcGFjZSk7XG4gICAgaWYgKHR5cGVvZiBuYW1lICE9PSAnc3RyaW5nJykge1xuICAgICAgYnVpbGQgPSB2YWx1ZTtcbiAgICB9XG5cbiAgICB0aGlzLmJ1aWxkKCFidWlsZCk7XG4gICAgcmV0dXJuIHRoaXM7XG4gIH07XG4gIHAucmVtb3ZlUXVlcnkgPSBmdW5jdGlvbihuYW1lLCB2YWx1ZSwgYnVpbGQpIHtcbiAgICB2YXIgZGF0YSA9IFVSSS5wYXJzZVF1ZXJ5KHRoaXMuX3BhcnRzLnF1ZXJ5LCB0aGlzLl9wYXJ0cy5lc2NhcGVRdWVyeVNwYWNlKTtcbiAgICBVUkkucmVtb3ZlUXVlcnkoZGF0YSwgbmFtZSwgdmFsdWUpO1xuICAgIHRoaXMuX3BhcnRzLnF1ZXJ5ID0gVVJJLmJ1aWxkUXVlcnkoZGF0YSwgdGhpcy5fcGFydHMuZHVwbGljYXRlUXVlcnlQYXJhbWV0ZXJzLCB0aGlzLl9wYXJ0cy5lc2NhcGVRdWVyeVNwYWNlKTtcbiAgICBpZiAodHlwZW9mIG5hbWUgIT09ICdzdHJpbmcnKSB7XG4gICAgICBidWlsZCA9IHZhbHVlO1xuICAgIH1cblxuICAgIHRoaXMuYnVpbGQoIWJ1aWxkKTtcbiAgICByZXR1cm4gdGhpcztcbiAgfTtcbiAgcC5oYXNRdWVyeSA9IGZ1bmN0aW9uKG5hbWUsIHZhbHVlLCB3aXRoaW5BcnJheSkge1xuICAgIHZhciBkYXRhID0gVVJJLnBhcnNlUXVlcnkodGhpcy5fcGFydHMucXVlcnksIHRoaXMuX3BhcnRzLmVzY2FwZVF1ZXJ5U3BhY2UpO1xuICAgIHJldHVybiBVUkkuaGFzUXVlcnkoZGF0YSwgbmFtZSwgdmFsdWUsIHdpdGhpbkFycmF5KTtcbiAgfTtcbiAgcC5zZXRTZWFyY2ggPSBwLnNldFF1ZXJ5O1xuICBwLmFkZFNlYXJjaCA9IHAuYWRkUXVlcnk7XG4gIHAucmVtb3ZlU2VhcmNoID0gcC5yZW1vdmVRdWVyeTtcbiAgcC5oYXNTZWFyY2ggPSBwLmhhc1F1ZXJ5O1xuXG4gIC8vIHNhbml0aXppbmcgVVJMc1xuICBwLm5vcm1hbGl6ZSA9IGZ1bmN0aW9uKCkge1xuICAgIGlmICh0aGlzLl9wYXJ0cy51cm4pIHtcbiAgICAgIHJldHVybiB0aGlzXG4gICAgICAgIC5ub3JtYWxpemVQcm90b2NvbChmYWxzZSlcbiAgICAgICAgLm5vcm1hbGl6ZVBhdGgoZmFsc2UpXG4gICAgICAgIC5ub3JtYWxpemVRdWVyeShmYWxzZSlcbiAgICAgICAgLm5vcm1hbGl6ZUZyYWdtZW50KGZhbHNlKVxuICAgICAgICAuYnVpbGQoKTtcbiAgICB9XG5cbiAgICByZXR1cm4gdGhpc1xuICAgICAgLm5vcm1hbGl6ZVByb3RvY29sKGZhbHNlKVxuICAgICAgLm5vcm1hbGl6ZUhvc3RuYW1lKGZhbHNlKVxuICAgICAgLm5vcm1hbGl6ZVBvcnQoZmFsc2UpXG4gICAgICAubm9ybWFsaXplUGF0aChmYWxzZSlcbiAgICAgIC5ub3JtYWxpemVRdWVyeShmYWxzZSlcbiAgICAgIC5ub3JtYWxpemVGcmFnbWVudChmYWxzZSlcbiAgICAgIC5idWlsZCgpO1xuICB9O1xuICBwLm5vcm1hbGl6ZVByb3RvY29sID0gZnVuY3Rpb24oYnVpbGQpIHtcbiAgICBpZiAodHlwZW9mIHRoaXMuX3BhcnRzLnByb3RvY29sID09PSAnc3RyaW5nJykge1xuICAgICAgdGhpcy5fcGFydHMucHJvdG9jb2wgPSB0aGlzLl9wYXJ0cy5wcm90b2NvbC50b0xvd2VyQ2FzZSgpO1xuICAgICAgdGhpcy5idWlsZCghYnVpbGQpO1xuICAgIH1cblxuICAgIHJldHVybiB0aGlzO1xuICB9O1xuICBwLm5vcm1hbGl6ZUhvc3RuYW1lID0gZnVuY3Rpb24oYnVpbGQpIHtcbiAgICBpZiAodGhpcy5fcGFydHMuaG9zdG5hbWUpIHtcbiAgICAgIGlmICh0aGlzLmlzKCdJRE4nKSAmJiBwdW55Y29kZSkge1xuICAgICAgICB0aGlzLl9wYXJ0cy5ob3N0bmFtZSA9IHB1bnljb2RlLnRvQVNDSUkodGhpcy5fcGFydHMuaG9zdG5hbWUpO1xuICAgICAgfSBlbHNlIGlmICh0aGlzLmlzKCdJUHY2JykgJiYgSVB2Nikge1xuICAgICAgICB0aGlzLl9wYXJ0cy5ob3N0bmFtZSA9IElQdjYuYmVzdCh0aGlzLl9wYXJ0cy5ob3N0bmFtZSk7XG4gICAgICB9XG5cbiAgICAgIHRoaXMuX3BhcnRzLmhvc3RuYW1lID0gdGhpcy5fcGFydHMuaG9zdG5hbWUudG9Mb3dlckNhc2UoKTtcbiAgICAgIHRoaXMuYnVpbGQoIWJ1aWxkKTtcbiAgICB9XG5cbiAgICByZXR1cm4gdGhpcztcbiAgfTtcbiAgcC5ub3JtYWxpemVQb3J0ID0gZnVuY3Rpb24oYnVpbGQpIHtcbiAgICAvLyByZW1vdmUgcG9ydCBvZiBpdCdzIHRoZSBwcm90b2NvbCdzIGRlZmF1bHRcbiAgICBpZiAodHlwZW9mIHRoaXMuX3BhcnRzLnByb3RvY29sID09PSAnc3RyaW5nJyAmJiB0aGlzLl9wYXJ0cy5wb3J0ID09PSBVUkkuZGVmYXVsdFBvcnRzW3RoaXMuX3BhcnRzLnByb3RvY29sXSkge1xuICAgICAgdGhpcy5fcGFydHMucG9ydCA9IG51bGw7XG4gICAgICB0aGlzLmJ1aWxkKCFidWlsZCk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRoaXM7XG4gIH07XG4gIHAubm9ybWFsaXplUGF0aCA9IGZ1bmN0aW9uKGJ1aWxkKSB7XG4gICAgdmFyIF9wYXRoID0gdGhpcy5fcGFydHMucGF0aDtcbiAgICBpZiAoIV9wYXRoKSB7XG4gICAgICByZXR1cm4gdGhpcztcbiAgICB9XG5cbiAgICBpZiAodGhpcy5fcGFydHMudXJuKSB7XG4gICAgICB0aGlzLl9wYXJ0cy5wYXRoID0gVVJJLnJlY29kZVVyblBhdGgodGhpcy5fcGFydHMucGF0aCk7XG4gICAgICB0aGlzLmJ1aWxkKCFidWlsZCk7XG4gICAgICByZXR1cm4gdGhpcztcbiAgICB9XG5cbiAgICBpZiAodGhpcy5fcGFydHMucGF0aCA9PT0gJy8nKSB7XG4gICAgICByZXR1cm4gdGhpcztcbiAgICB9XG5cbiAgICB2YXIgX3dhc19yZWxhdGl2ZTtcbiAgICB2YXIgX2xlYWRpbmdQYXJlbnRzID0gJyc7XG4gICAgdmFyIF9wYXJlbnQsIF9wb3M7XG5cbiAgICAvLyBoYW5kbGUgcmVsYXRpdmUgcGF0aHNcbiAgICBpZiAoX3BhdGguY2hhckF0KDApICE9PSAnLycpIHtcbiAgICAgIF93YXNfcmVsYXRpdmUgPSB0cnVlO1xuICAgICAgX3BhdGggPSAnLycgKyBfcGF0aDtcbiAgICB9XG5cbiAgICAvLyBoYW5kbGUgcmVsYXRpdmUgZmlsZXMgKGFzIG9wcG9zZWQgdG8gZGlyZWN0b3JpZXMpXG4gICAgaWYgKF9wYXRoLnNsaWNlKC0zKSA9PT0gJy8uLicgfHwgX3BhdGguc2xpY2UoLTIpID09PSAnLy4nKSB7XG4gICAgICBfcGF0aCArPSAnLyc7XG4gICAgfVxuXG4gICAgLy8gcmVzb2x2ZSBzaW1wbGVzXG4gICAgX3BhdGggPSBfcGF0aFxuICAgICAgLnJlcGxhY2UoLyhcXC8oXFwuXFwvKSspfChcXC9cXC4kKS9nLCAnLycpXG4gICAgICAucmVwbGFjZSgvXFwvezIsfS9nLCAnLycpO1xuXG4gICAgLy8gcmVtZW1iZXIgbGVhZGluZyBwYXJlbnRzXG4gICAgaWYgKF93YXNfcmVsYXRpdmUpIHtcbiAgICAgIF9sZWFkaW5nUGFyZW50cyA9IF9wYXRoLnN1YnN0cmluZygxKS5tYXRjaCgvXihcXC5cXC5cXC8pKy8pIHx8ICcnO1xuICAgICAgaWYgKF9sZWFkaW5nUGFyZW50cykge1xuICAgICAgICBfbGVhZGluZ1BhcmVudHMgPSBfbGVhZGluZ1BhcmVudHNbMF07XG4gICAgICB9XG4gICAgfVxuXG4gICAgLy8gcmVzb2x2ZSBwYXJlbnRzXG4gICAgd2hpbGUgKHRydWUpIHtcbiAgICAgIF9wYXJlbnQgPSBfcGF0aC5pbmRleE9mKCcvLi4nKTtcbiAgICAgIGlmIChfcGFyZW50ID09PSAtMSkge1xuICAgICAgICAvLyBubyBtb3JlIC4uLyB0byByZXNvbHZlXG4gICAgICAgIGJyZWFrO1xuICAgICAgfSBlbHNlIGlmIChfcGFyZW50ID09PSAwKSB7XG4gICAgICAgIC8vIHRvcCBsZXZlbCBjYW5ub3QgYmUgcmVsYXRpdmUsIHNraXAgaXRcbiAgICAgICAgX3BhdGggPSBfcGF0aC5zdWJzdHJpbmcoMyk7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuXG4gICAgICBfcG9zID0gX3BhdGguc3Vic3RyaW5nKDAsIF9wYXJlbnQpLmxhc3RJbmRleE9mKCcvJyk7XG4gICAgICBpZiAoX3BvcyA9PT0gLTEpIHtcbiAgICAgICAgX3BvcyA9IF9wYXJlbnQ7XG4gICAgICB9XG4gICAgICBfcGF0aCA9IF9wYXRoLnN1YnN0cmluZygwLCBfcG9zKSArIF9wYXRoLnN1YnN0cmluZyhfcGFyZW50ICsgMyk7XG4gICAgfVxuXG4gICAgLy8gcmV2ZXJ0IHRvIHJlbGF0aXZlXG4gICAgaWYgKF93YXNfcmVsYXRpdmUgJiYgdGhpcy5pcygncmVsYXRpdmUnKSkge1xuICAgICAgX3BhdGggPSBfbGVhZGluZ1BhcmVudHMgKyBfcGF0aC5zdWJzdHJpbmcoMSk7XG4gICAgfVxuXG4gICAgX3BhdGggPSBVUkkucmVjb2RlUGF0aChfcGF0aCk7XG4gICAgdGhpcy5fcGFydHMucGF0aCA9IF9wYXRoO1xuICAgIHRoaXMuYnVpbGQoIWJ1aWxkKTtcbiAgICByZXR1cm4gdGhpcztcbiAgfTtcbiAgcC5ub3JtYWxpemVQYXRobmFtZSA9IHAubm9ybWFsaXplUGF0aDtcbiAgcC5ub3JtYWxpemVRdWVyeSA9IGZ1bmN0aW9uKGJ1aWxkKSB7XG4gICAgaWYgKHR5cGVvZiB0aGlzLl9wYXJ0cy5xdWVyeSA9PT0gJ3N0cmluZycpIHtcbiAgICAgIGlmICghdGhpcy5fcGFydHMucXVlcnkubGVuZ3RoKSB7XG4gICAgICAgIHRoaXMuX3BhcnRzLnF1ZXJ5ID0gbnVsbDtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRoaXMucXVlcnkoVVJJLnBhcnNlUXVlcnkodGhpcy5fcGFydHMucXVlcnksIHRoaXMuX3BhcnRzLmVzY2FwZVF1ZXJ5U3BhY2UpKTtcbiAgICAgIH1cblxuICAgICAgdGhpcy5idWlsZCghYnVpbGQpO1xuICAgIH1cblxuICAgIHJldHVybiB0aGlzO1xuICB9O1xuICBwLm5vcm1hbGl6ZUZyYWdtZW50ID0gZnVuY3Rpb24oYnVpbGQpIHtcbiAgICBpZiAoIXRoaXMuX3BhcnRzLmZyYWdtZW50KSB7XG4gICAgICB0aGlzLl9wYXJ0cy5mcmFnbWVudCA9IG51bGw7XG4gICAgICB0aGlzLmJ1aWxkKCFidWlsZCk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRoaXM7XG4gIH07XG4gIHAubm9ybWFsaXplU2VhcmNoID0gcC5ub3JtYWxpemVRdWVyeTtcbiAgcC5ub3JtYWxpemVIYXNoID0gcC5ub3JtYWxpemVGcmFnbWVudDtcblxuICBwLmlzbzg4NTkgPSBmdW5jdGlvbigpIHtcbiAgICAvLyBleHBlY3QgdW5pY29kZSBpbnB1dCwgaXNvODg1OSBvdXRwdXRcbiAgICB2YXIgZSA9IFVSSS5lbmNvZGU7XG4gICAgdmFyIGQgPSBVUkkuZGVjb2RlO1xuXG4gICAgVVJJLmVuY29kZSA9IGVzY2FwZTtcbiAgICBVUkkuZGVjb2RlID0gZGVjb2RlVVJJQ29tcG9uZW50O1xuICAgIHRyeSB7XG4gICAgICB0aGlzLm5vcm1hbGl6ZSgpO1xuICAgIH0gZmluYWxseSB7XG4gICAgICBVUkkuZW5jb2RlID0gZTtcbiAgICAgIFVSSS5kZWNvZGUgPSBkO1xuICAgIH1cbiAgICByZXR1cm4gdGhpcztcbiAgfTtcblxuICBwLnVuaWNvZGUgPSBmdW5jdGlvbigpIHtcbiAgICAvLyBleHBlY3QgaXNvODg1OSBpbnB1dCwgdW5pY29kZSBvdXRwdXRcbiAgICB2YXIgZSA9IFVSSS5lbmNvZGU7XG4gICAgdmFyIGQgPSBVUkkuZGVjb2RlO1xuXG4gICAgVVJJLmVuY29kZSA9IHN0cmljdEVuY29kZVVSSUNvbXBvbmVudDtcbiAgICBVUkkuZGVjb2RlID0gdW5lc2NhcGU7XG4gICAgdHJ5IHtcbiAgICAgIHRoaXMubm9ybWFsaXplKCk7XG4gICAgfSBmaW5hbGx5IHtcbiAgICAgIFVSSS5lbmNvZGUgPSBlO1xuICAgICAgVVJJLmRlY29kZSA9IGQ7XG4gICAgfVxuICAgIHJldHVybiB0aGlzO1xuICB9O1xuXG4gIHAucmVhZGFibGUgPSBmdW5jdGlvbigpIHtcbiAgICB2YXIgdXJpID0gdGhpcy5jbG9uZSgpO1xuICAgIC8vIHJlbW92aW5nIHVzZXJuYW1lLCBwYXNzd29yZCwgYmVjYXVzZSB0aGV5IHNob3VsZG4ndCBiZSBkaXNwbGF5ZWQgYWNjb3JkaW5nIHRvIFJGQyAzOTg2XG4gICAgdXJpLnVzZXJuYW1lKCcnKS5wYXNzd29yZCgnJykubm9ybWFsaXplKCk7XG4gICAgdmFyIHQgPSAnJztcbiAgICBpZiAodXJpLl9wYXJ0cy5wcm90b2NvbCkge1xuICAgICAgdCArPSB1cmkuX3BhcnRzLnByb3RvY29sICsgJzovLyc7XG4gICAgfVxuXG4gICAgaWYgKHVyaS5fcGFydHMuaG9zdG5hbWUpIHtcbiAgICAgIGlmICh1cmkuaXMoJ3B1bnljb2RlJykgJiYgcHVueWNvZGUpIHtcbiAgICAgICAgdCArPSBwdW55Y29kZS50b1VuaWNvZGUodXJpLl9wYXJ0cy5ob3N0bmFtZSk7XG4gICAgICAgIGlmICh1cmkuX3BhcnRzLnBvcnQpIHtcbiAgICAgICAgICB0ICs9ICc6JyArIHVyaS5fcGFydHMucG9ydDtcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdCArPSB1cmkuaG9zdCgpO1xuICAgICAgfVxuICAgIH1cblxuICAgIGlmICh1cmkuX3BhcnRzLmhvc3RuYW1lICYmIHVyaS5fcGFydHMucGF0aCAmJiB1cmkuX3BhcnRzLnBhdGguY2hhckF0KDApICE9PSAnLycpIHtcbiAgICAgIHQgKz0gJy8nO1xuICAgIH1cblxuICAgIHQgKz0gdXJpLnBhdGgodHJ1ZSk7XG4gICAgaWYgKHVyaS5fcGFydHMucXVlcnkpIHtcbiAgICAgIHZhciBxID0gJyc7XG4gICAgICBmb3IgKHZhciBpID0gMCwgcXAgPSB1cmkuX3BhcnRzLnF1ZXJ5LnNwbGl0KCcmJyksIGwgPSBxcC5sZW5ndGg7IGkgPCBsOyBpKyspIHtcbiAgICAgICAgdmFyIGt2ID0gKHFwW2ldIHx8ICcnKS5zcGxpdCgnPScpO1xuICAgICAgICBxICs9ICcmJyArIFVSSS5kZWNvZGVRdWVyeShrdlswXSwgdGhpcy5fcGFydHMuZXNjYXBlUXVlcnlTcGFjZSlcbiAgICAgICAgICAucmVwbGFjZSgvJi9nLCAnJTI2Jyk7XG5cbiAgICAgICAgaWYgKGt2WzFdICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICBxICs9ICc9JyArIFVSSS5kZWNvZGVRdWVyeShrdlsxXSwgdGhpcy5fcGFydHMuZXNjYXBlUXVlcnlTcGFjZSlcbiAgICAgICAgICAgIC5yZXBsYWNlKC8mL2csICclMjYnKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgdCArPSAnPycgKyBxLnN1YnN0cmluZygxKTtcbiAgICB9XG5cbiAgICB0ICs9IFVSSS5kZWNvZGVRdWVyeSh1cmkuaGFzaCgpLCB0cnVlKTtcbiAgICByZXR1cm4gdDtcbiAgfTtcblxuICAvLyByZXNvbHZpbmcgcmVsYXRpdmUgYW5kIGFic29sdXRlIFVSTHNcbiAgcC5hYnNvbHV0ZVRvID0gZnVuY3Rpb24oYmFzZSkge1xuICAgIHZhciByZXNvbHZlZCA9IHRoaXMuY2xvbmUoKTtcbiAgICB2YXIgcHJvcGVydGllcyA9IFsncHJvdG9jb2wnLCAndXNlcm5hbWUnLCAncGFzc3dvcmQnLCAnaG9zdG5hbWUnLCAncG9ydCddO1xuICAgIHZhciBiYXNlZGlyLCBpLCBwO1xuXG4gICAgaWYgKHRoaXMuX3BhcnRzLnVybikge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdVUk5zIGRvIG5vdCBoYXZlIGFueSBnZW5lcmFsbHkgZGVmaW5lZCBoaWVyYXJjaGljYWwgY29tcG9uZW50cycpO1xuICAgIH1cblxuICAgIGlmICghKGJhc2UgaW5zdGFuY2VvZiBVUkkpKSB7XG4gICAgICBiYXNlID0gbmV3IFVSSShiYXNlKTtcbiAgICB9XG5cbiAgICBpZiAoIXJlc29sdmVkLl9wYXJ0cy5wcm90b2NvbCkge1xuICAgICAgcmVzb2x2ZWQuX3BhcnRzLnByb3RvY29sID0gYmFzZS5fcGFydHMucHJvdG9jb2w7XG4gICAgfVxuXG4gICAgaWYgKHRoaXMuX3BhcnRzLmhvc3RuYW1lKSB7XG4gICAgICByZXR1cm4gcmVzb2x2ZWQ7XG4gICAgfVxuXG4gICAgZm9yIChpID0gMDsgKHAgPSBwcm9wZXJ0aWVzW2ldKTsgaSsrKSB7XG4gICAgICByZXNvbHZlZC5fcGFydHNbcF0gPSBiYXNlLl9wYXJ0c1twXTtcbiAgICB9XG5cbiAgICBpZiAoIXJlc29sdmVkLl9wYXJ0cy5wYXRoKSB7XG4gICAgICByZXNvbHZlZC5fcGFydHMucGF0aCA9IGJhc2UuX3BhcnRzLnBhdGg7XG4gICAgICBpZiAoIXJlc29sdmVkLl9wYXJ0cy5xdWVyeSkge1xuICAgICAgICByZXNvbHZlZC5fcGFydHMucXVlcnkgPSBiYXNlLl9wYXJ0cy5xdWVyeTtcbiAgICAgIH1cbiAgICB9IGVsc2UgaWYgKHJlc29sdmVkLl9wYXJ0cy5wYXRoLnN1YnN0cmluZygtMikgPT09ICcuLicpIHtcbiAgICAgIHJlc29sdmVkLl9wYXJ0cy5wYXRoICs9ICcvJztcbiAgICB9XG5cbiAgICBpZiAocmVzb2x2ZWQucGF0aCgpLmNoYXJBdCgwKSAhPT0gJy8nKSB7XG4gICAgICBiYXNlZGlyID0gYmFzZS5kaXJlY3RvcnkoKTtcbiAgICAgIGJhc2VkaXIgPSBiYXNlZGlyID8gYmFzZWRpciA6IGJhc2UucGF0aCgpLmluZGV4T2YoJy8nKSA9PT0gMCA/ICcvJyA6ICcnO1xuICAgICAgcmVzb2x2ZWQuX3BhcnRzLnBhdGggPSAoYmFzZWRpciA/IChiYXNlZGlyICsgJy8nKSA6ICcnKSArIHJlc29sdmVkLl9wYXJ0cy5wYXRoO1xuICAgICAgcmVzb2x2ZWQubm9ybWFsaXplUGF0aCgpO1xuICAgIH1cblxuICAgIHJlc29sdmVkLmJ1aWxkKCk7XG4gICAgcmV0dXJuIHJlc29sdmVkO1xuICB9O1xuICBwLnJlbGF0aXZlVG8gPSBmdW5jdGlvbihiYXNlKSB7XG4gICAgdmFyIHJlbGF0aXZlID0gdGhpcy5jbG9uZSgpLm5vcm1hbGl6ZSgpO1xuICAgIHZhciByZWxhdGl2ZVBhcnRzLCBiYXNlUGFydHMsIGNvbW1vbiwgcmVsYXRpdmVQYXRoLCBiYXNlUGF0aDtcblxuICAgIGlmIChyZWxhdGl2ZS5fcGFydHMudXJuKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ1VSTnMgZG8gbm90IGhhdmUgYW55IGdlbmVyYWxseSBkZWZpbmVkIGhpZXJhcmNoaWNhbCBjb21wb25lbnRzJyk7XG4gICAgfVxuXG4gICAgYmFzZSA9IG5ldyBVUkkoYmFzZSkubm9ybWFsaXplKCk7XG4gICAgcmVsYXRpdmVQYXJ0cyA9IHJlbGF0aXZlLl9wYXJ0cztcbiAgICBiYXNlUGFydHMgPSBiYXNlLl9wYXJ0cztcbiAgICByZWxhdGl2ZVBhdGggPSByZWxhdGl2ZS5wYXRoKCk7XG4gICAgYmFzZVBhdGggPSBiYXNlLnBhdGgoKTtcblxuICAgIGlmIChyZWxhdGl2ZVBhdGguY2hhckF0KDApICE9PSAnLycpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignVVJJIGlzIGFscmVhZHkgcmVsYXRpdmUnKTtcbiAgICB9XG5cbiAgICBpZiAoYmFzZVBhdGguY2hhckF0KDApICE9PSAnLycpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignQ2Fubm90IGNhbGN1bGF0ZSBhIFVSSSByZWxhdGl2ZSB0byBhbm90aGVyIHJlbGF0aXZlIFVSSScpO1xuICAgIH1cblxuICAgIGlmIChyZWxhdGl2ZVBhcnRzLnByb3RvY29sID09PSBiYXNlUGFydHMucHJvdG9jb2wpIHtcbiAgICAgIHJlbGF0aXZlUGFydHMucHJvdG9jb2wgPSBudWxsO1xuICAgIH1cblxuICAgIGlmIChyZWxhdGl2ZVBhcnRzLnVzZXJuYW1lICE9PSBiYXNlUGFydHMudXNlcm5hbWUgfHwgcmVsYXRpdmVQYXJ0cy5wYXNzd29yZCAhPT0gYmFzZVBhcnRzLnBhc3N3b3JkKSB7XG4gICAgICByZXR1cm4gcmVsYXRpdmUuYnVpbGQoKTtcbiAgICB9XG5cbiAgICBpZiAocmVsYXRpdmVQYXJ0cy5wcm90b2NvbCAhPT0gbnVsbCB8fCByZWxhdGl2ZVBhcnRzLnVzZXJuYW1lICE9PSBudWxsIHx8IHJlbGF0aXZlUGFydHMucGFzc3dvcmQgIT09IG51bGwpIHtcbiAgICAgIHJldHVybiByZWxhdGl2ZS5idWlsZCgpO1xuICAgIH1cblxuICAgIGlmIChyZWxhdGl2ZVBhcnRzLmhvc3RuYW1lID09PSBiYXNlUGFydHMuaG9zdG5hbWUgJiYgcmVsYXRpdmVQYXJ0cy5wb3J0ID09PSBiYXNlUGFydHMucG9ydCkge1xuICAgICAgcmVsYXRpdmVQYXJ0cy5ob3N0bmFtZSA9IG51bGw7XG4gICAgICByZWxhdGl2ZVBhcnRzLnBvcnQgPSBudWxsO1xuICAgIH0gZWxzZSB7XG4gICAgICByZXR1cm4gcmVsYXRpdmUuYnVpbGQoKTtcbiAgICB9XG5cbiAgICBpZiAocmVsYXRpdmVQYXRoID09PSBiYXNlUGF0aCkge1xuICAgICAgcmVsYXRpdmVQYXJ0cy5wYXRoID0gJyc7XG4gICAgICByZXR1cm4gcmVsYXRpdmUuYnVpbGQoKTtcbiAgICB9XG5cbiAgICAvLyBkZXRlcm1pbmUgY29tbW9uIHN1YiBwYXRoXG4gICAgY29tbW9uID0gVVJJLmNvbW1vblBhdGgocmVsYXRpdmVQYXRoLCBiYXNlUGF0aCk7XG5cbiAgICAvLyBJZiB0aGUgcGF0aHMgaGF2ZSBub3RoaW5nIGluIGNvbW1vbiwgcmV0dXJuIGEgcmVsYXRpdmUgVVJMIHdpdGggdGhlIGFic29sdXRlIHBhdGguXG4gICAgaWYgKCFjb21tb24pIHtcbiAgICAgIHJldHVybiByZWxhdGl2ZS5idWlsZCgpO1xuICAgIH1cblxuICAgIHZhciBwYXJlbnRzID0gYmFzZVBhcnRzLnBhdGhcbiAgICAgIC5zdWJzdHJpbmcoY29tbW9uLmxlbmd0aClcbiAgICAgIC5yZXBsYWNlKC9bXlxcL10qJC8sICcnKVxuICAgICAgLnJlcGxhY2UoLy4qP1xcLy9nLCAnLi4vJyk7XG5cbiAgICByZWxhdGl2ZVBhcnRzLnBhdGggPSAocGFyZW50cyArIHJlbGF0aXZlUGFydHMucGF0aC5zdWJzdHJpbmcoY29tbW9uLmxlbmd0aCkpIHx8ICcuLyc7XG5cbiAgICByZXR1cm4gcmVsYXRpdmUuYnVpbGQoKTtcbiAgfTtcblxuICAvLyBjb21wYXJpbmcgVVJJc1xuICBwLmVxdWFscyA9IGZ1bmN0aW9uKHVyaSkge1xuICAgIHZhciBvbmUgPSB0aGlzLmNsb25lKCk7XG4gICAgdmFyIHR3byA9IG5ldyBVUkkodXJpKTtcbiAgICB2YXIgb25lX21hcCA9IHt9O1xuICAgIHZhciB0d29fbWFwID0ge307XG4gICAgdmFyIGNoZWNrZWQgPSB7fTtcbiAgICB2YXIgb25lX3F1ZXJ5LCB0d29fcXVlcnksIGtleTtcblxuICAgIG9uZS5ub3JtYWxpemUoKTtcbiAgICB0d28ubm9ybWFsaXplKCk7XG5cbiAgICAvLyBleGFjdCBtYXRjaFxuICAgIGlmIChvbmUudG9TdHJpbmcoKSA9PT0gdHdvLnRvU3RyaW5nKCkpIHtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cblxuICAgIC8vIGV4dHJhY3QgcXVlcnkgc3RyaW5nXG4gICAgb25lX3F1ZXJ5ID0gb25lLnF1ZXJ5KCk7XG4gICAgdHdvX3F1ZXJ5ID0gdHdvLnF1ZXJ5KCk7XG4gICAgb25lLnF1ZXJ5KCcnKTtcbiAgICB0d28ucXVlcnkoJycpO1xuXG4gICAgLy8gZGVmaW5pdGVseSBub3QgZXF1YWwgaWYgbm90IGV2ZW4gbm9uLXF1ZXJ5IHBhcnRzIG1hdGNoXG4gICAgaWYgKG9uZS50b1N0cmluZygpICE9PSB0d28udG9TdHJpbmcoKSkge1xuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cblxuICAgIC8vIHF1ZXJ5IHBhcmFtZXRlcnMgaGF2ZSB0aGUgc2FtZSBsZW5ndGgsIGV2ZW4gaWYgdGhleSdyZSBwZXJtdXRlZFxuICAgIGlmIChvbmVfcXVlcnkubGVuZ3RoICE9PSB0d29fcXVlcnkubGVuZ3RoKSB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgb25lX21hcCA9IFVSSS5wYXJzZVF1ZXJ5KG9uZV9xdWVyeSwgdGhpcy5fcGFydHMuZXNjYXBlUXVlcnlTcGFjZSk7XG4gICAgdHdvX21hcCA9IFVSSS5wYXJzZVF1ZXJ5KHR3b19xdWVyeSwgdGhpcy5fcGFydHMuZXNjYXBlUXVlcnlTcGFjZSk7XG5cbiAgICBmb3IgKGtleSBpbiBvbmVfbWFwKSB7XG4gICAgICBpZiAoaGFzT3duLmNhbGwob25lX21hcCwga2V5KSkge1xuICAgICAgICBpZiAoIWlzQXJyYXkob25lX21hcFtrZXldKSkge1xuICAgICAgICAgIGlmIChvbmVfbWFwW2tleV0gIT09IHR3b19tYXBba2V5XSkge1xuICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIGlmICghYXJyYXlzRXF1YWwob25lX21hcFtrZXldLCB0d29fbWFwW2tleV0pKSB7XG4gICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICB9XG5cbiAgICAgICAgY2hlY2tlZFtrZXldID0gdHJ1ZTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICBmb3IgKGtleSBpbiB0d29fbWFwKSB7XG4gICAgICBpZiAoaGFzT3duLmNhbGwodHdvX21hcCwga2V5KSkge1xuICAgICAgICBpZiAoIWNoZWNrZWRba2V5XSkge1xuICAgICAgICAgIC8vIHR3byBjb250YWlucyBhIHBhcmFtZXRlciBub3QgcHJlc2VudCBpbiBvbmVcbiAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gdHJ1ZTtcbiAgfTtcblxuICAvLyBzdGF0ZVxuICBwLmR1cGxpY2F0ZVF1ZXJ5UGFyYW1ldGVycyA9IGZ1bmN0aW9uKHYpIHtcbiAgICB0aGlzLl9wYXJ0cy5kdXBsaWNhdGVRdWVyeVBhcmFtZXRlcnMgPSAhIXY7XG4gICAgcmV0dXJuIHRoaXM7XG4gIH07XG5cbiAgcC5lc2NhcGVRdWVyeVNwYWNlID0gZnVuY3Rpb24odikge1xuICAgIHRoaXMuX3BhcnRzLmVzY2FwZVF1ZXJ5U3BhY2UgPSAhIXY7XG4gICAgcmV0dXJuIHRoaXM7XG4gIH07XG5cbiAgcmV0dXJuIFVSSTtcbn0pKTtcbiIsIi8qISBodHRwOi8vbXRocy5iZS9wdW55Y29kZSB2MS4yLjMgYnkgQG1hdGhpYXMgKi9cbjsoZnVuY3Rpb24ocm9vdCkge1xuXG5cdC8qKiBEZXRlY3QgZnJlZSB2YXJpYWJsZXMgKi9cblx0dmFyIGZyZWVFeHBvcnRzID0gdHlwZW9mIGV4cG9ydHMgPT0gJ29iamVjdCcgJiYgZXhwb3J0cztcblx0dmFyIGZyZWVNb2R1bGUgPSB0eXBlb2YgbW9kdWxlID09ICdvYmplY3QnICYmIG1vZHVsZSAmJlxuXHRcdG1vZHVsZS5leHBvcnRzID09IGZyZWVFeHBvcnRzICYmIG1vZHVsZTtcblx0dmFyIGZyZWVHbG9iYWwgPSB0eXBlb2YgZ2xvYmFsID09ICdvYmplY3QnICYmIGdsb2JhbDtcblx0aWYgKGZyZWVHbG9iYWwuZ2xvYmFsID09PSBmcmVlR2xvYmFsIHx8IGZyZWVHbG9iYWwud2luZG93ID09PSBmcmVlR2xvYmFsKSB7XG5cdFx0cm9vdCA9IGZyZWVHbG9iYWw7XG5cdH1cblxuXHQvKipcblx0ICogVGhlIGBwdW55Y29kZWAgb2JqZWN0LlxuXHQgKiBAbmFtZSBwdW55Y29kZVxuXHQgKiBAdHlwZSBPYmplY3Rcblx0ICovXG5cdHZhciBwdW55Y29kZSxcblxuXHQvKiogSGlnaGVzdCBwb3NpdGl2ZSBzaWduZWQgMzItYml0IGZsb2F0IHZhbHVlICovXG5cdG1heEludCA9IDIxNDc0ODM2NDcsIC8vIGFrYS4gMHg3RkZGRkZGRiBvciAyXjMxLTFcblxuXHQvKiogQm9vdHN0cmluZyBwYXJhbWV0ZXJzICovXG5cdGJhc2UgPSAzNixcblx0dE1pbiA9IDEsXG5cdHRNYXggPSAyNixcblx0c2tldyA9IDM4LFxuXHRkYW1wID0gNzAwLFxuXHRpbml0aWFsQmlhcyA9IDcyLFxuXHRpbml0aWFsTiA9IDEyOCwgLy8gMHg4MFxuXHRkZWxpbWl0ZXIgPSAnLScsIC8vICdcXHgyRCdcblxuXHQvKiogUmVndWxhciBleHByZXNzaW9ucyAqL1xuXHRyZWdleFB1bnljb2RlID0gL154bi0tLyxcblx0cmVnZXhOb25BU0NJSSA9IC9bXiAtfl0vLCAvLyB1bnByaW50YWJsZSBBU0NJSSBjaGFycyArIG5vbi1BU0NJSSBjaGFyc1xuXHRyZWdleFNlcGFyYXRvcnMgPSAvXFx4MkV8XFx1MzAwMnxcXHVGRjBFfFxcdUZGNjEvZywgLy8gUkZDIDM0OTAgc2VwYXJhdG9yc1xuXG5cdC8qKiBFcnJvciBtZXNzYWdlcyAqL1xuXHRlcnJvcnMgPSB7XG5cdFx0J292ZXJmbG93JzogJ092ZXJmbG93OiBpbnB1dCBuZWVkcyB3aWRlciBpbnRlZ2VycyB0byBwcm9jZXNzJyxcblx0XHQnbm90LWJhc2ljJzogJ0lsbGVnYWwgaW5wdXQgPj0gMHg4MCAobm90IGEgYmFzaWMgY29kZSBwb2ludCknLFxuXHRcdCdpbnZhbGlkLWlucHV0JzogJ0ludmFsaWQgaW5wdXQnXG5cdH0sXG5cblx0LyoqIENvbnZlbmllbmNlIHNob3J0Y3V0cyAqL1xuXHRiYXNlTWludXNUTWluID0gYmFzZSAtIHRNaW4sXG5cdGZsb29yID0gTWF0aC5mbG9vcixcblx0c3RyaW5nRnJvbUNoYXJDb2RlID0gU3RyaW5nLmZyb21DaGFyQ29kZSxcblxuXHQvKiogVGVtcG9yYXJ5IHZhcmlhYmxlICovXG5cdGtleTtcblxuXHQvKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuXHQvKipcblx0ICogQSBnZW5lcmljIGVycm9yIHV0aWxpdHkgZnVuY3Rpb24uXG5cdCAqIEBwcml2YXRlXG5cdCAqIEBwYXJhbSB7U3RyaW5nfSB0eXBlIFRoZSBlcnJvciB0eXBlLlxuXHQgKiBAcmV0dXJucyB7RXJyb3J9IFRocm93cyBhIGBSYW5nZUVycm9yYCB3aXRoIHRoZSBhcHBsaWNhYmxlIGVycm9yIG1lc3NhZ2UuXG5cdCAqL1xuXHRmdW5jdGlvbiBlcnJvcih0eXBlKSB7XG5cdFx0dGhyb3cgUmFuZ2VFcnJvcihlcnJvcnNbdHlwZV0pO1xuXHR9XG5cblx0LyoqXG5cdCAqIEEgZ2VuZXJpYyBgQXJyYXkjbWFwYCB1dGlsaXR5IGZ1bmN0aW9uLlxuXHQgKiBAcHJpdmF0ZVxuXHQgKiBAcGFyYW0ge0FycmF5fSBhcnJheSBUaGUgYXJyYXkgdG8gaXRlcmF0ZSBvdmVyLlxuXHQgKiBAcGFyYW0ge0Z1bmN0aW9ufSBjYWxsYmFjayBUaGUgZnVuY3Rpb24gdGhhdCBnZXRzIGNhbGxlZCBmb3IgZXZlcnkgYXJyYXlcblx0ICogaXRlbS5cblx0ICogQHJldHVybnMge0FycmF5fSBBIG5ldyBhcnJheSBvZiB2YWx1ZXMgcmV0dXJuZWQgYnkgdGhlIGNhbGxiYWNrIGZ1bmN0aW9uLlxuXHQgKi9cblx0ZnVuY3Rpb24gbWFwKGFycmF5LCBmbikge1xuXHRcdHZhciBsZW5ndGggPSBhcnJheS5sZW5ndGg7XG5cdFx0d2hpbGUgKGxlbmd0aC0tKSB7XG5cdFx0XHRhcnJheVtsZW5ndGhdID0gZm4oYXJyYXlbbGVuZ3RoXSk7XG5cdFx0fVxuXHRcdHJldHVybiBhcnJheTtcblx0fVxuXG5cdC8qKlxuXHQgKiBBIHNpbXBsZSBgQXJyYXkjbWFwYC1saWtlIHdyYXBwZXIgdG8gd29yayB3aXRoIGRvbWFpbiBuYW1lIHN0cmluZ3MuXG5cdCAqIEBwcml2YXRlXG5cdCAqIEBwYXJhbSB7U3RyaW5nfSBkb21haW4gVGhlIGRvbWFpbiBuYW1lLlxuXHQgKiBAcGFyYW0ge0Z1bmN0aW9ufSBjYWxsYmFjayBUaGUgZnVuY3Rpb24gdGhhdCBnZXRzIGNhbGxlZCBmb3IgZXZlcnlcblx0ICogY2hhcmFjdGVyLlxuXHQgKiBAcmV0dXJucyB7QXJyYXl9IEEgbmV3IHN0cmluZyBvZiBjaGFyYWN0ZXJzIHJldHVybmVkIGJ5IHRoZSBjYWxsYmFja1xuXHQgKiBmdW5jdGlvbi5cblx0ICovXG5cdGZ1bmN0aW9uIG1hcERvbWFpbihzdHJpbmcsIGZuKSB7XG5cdFx0cmV0dXJuIG1hcChzdHJpbmcuc3BsaXQocmVnZXhTZXBhcmF0b3JzKSwgZm4pLmpvaW4oJy4nKTtcblx0fVxuXG5cdC8qKlxuXHQgKiBDcmVhdGVzIGFuIGFycmF5IGNvbnRhaW5pbmcgdGhlIG51bWVyaWMgY29kZSBwb2ludHMgb2YgZWFjaCBVbmljb2RlXG5cdCAqIGNoYXJhY3RlciBpbiB0aGUgc3RyaW5nLiBXaGlsZSBKYXZhU2NyaXB0IHVzZXMgVUNTLTIgaW50ZXJuYWxseSxcblx0ICogdGhpcyBmdW5jdGlvbiB3aWxsIGNvbnZlcnQgYSBwYWlyIG9mIHN1cnJvZ2F0ZSBoYWx2ZXMgKGVhY2ggb2Ygd2hpY2hcblx0ICogVUNTLTIgZXhwb3NlcyBhcyBzZXBhcmF0ZSBjaGFyYWN0ZXJzKSBpbnRvIGEgc2luZ2xlIGNvZGUgcG9pbnQsXG5cdCAqIG1hdGNoaW5nIFVURi0xNi5cblx0ICogQHNlZSBgcHVueWNvZGUudWNzMi5lbmNvZGVgXG5cdCAqIEBzZWUgPGh0dHA6Ly9tYXRoaWFzYnluZW5zLmJlL25vdGVzL2phdmFzY3JpcHQtZW5jb2Rpbmc+XG5cdCAqIEBtZW1iZXJPZiBwdW55Y29kZS51Y3MyXG5cdCAqIEBuYW1lIGRlY29kZVxuXHQgKiBAcGFyYW0ge1N0cmluZ30gc3RyaW5nIFRoZSBVbmljb2RlIGlucHV0IHN0cmluZyAoVUNTLTIpLlxuXHQgKiBAcmV0dXJucyB7QXJyYXl9IFRoZSBuZXcgYXJyYXkgb2YgY29kZSBwb2ludHMuXG5cdCAqL1xuXHRmdW5jdGlvbiB1Y3MyZGVjb2RlKHN0cmluZykge1xuXHRcdHZhciBvdXRwdXQgPSBbXSxcblx0XHQgICAgY291bnRlciA9IDAsXG5cdFx0ICAgIGxlbmd0aCA9IHN0cmluZy5sZW5ndGgsXG5cdFx0ICAgIHZhbHVlLFxuXHRcdCAgICBleHRyYTtcblx0XHR3aGlsZSAoY291bnRlciA8IGxlbmd0aCkge1xuXHRcdFx0dmFsdWUgPSBzdHJpbmcuY2hhckNvZGVBdChjb3VudGVyKyspO1xuXHRcdFx0aWYgKHZhbHVlID49IDB4RDgwMCAmJiB2YWx1ZSA8PSAweERCRkYgJiYgY291bnRlciA8IGxlbmd0aCkge1xuXHRcdFx0XHQvLyBoaWdoIHN1cnJvZ2F0ZSwgYW5kIHRoZXJlIGlzIGEgbmV4dCBjaGFyYWN0ZXJcblx0XHRcdFx0ZXh0cmEgPSBzdHJpbmcuY2hhckNvZGVBdChjb3VudGVyKyspO1xuXHRcdFx0XHRpZiAoKGV4dHJhICYgMHhGQzAwKSA9PSAweERDMDApIHsgLy8gbG93IHN1cnJvZ2F0ZVxuXHRcdFx0XHRcdG91dHB1dC5wdXNoKCgodmFsdWUgJiAweDNGRikgPDwgMTApICsgKGV4dHJhICYgMHgzRkYpICsgMHgxMDAwMCk7XG5cdFx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdFx0Ly8gdW5tYXRjaGVkIHN1cnJvZ2F0ZTsgb25seSBhcHBlbmQgdGhpcyBjb2RlIHVuaXQsIGluIGNhc2UgdGhlIG5leHRcblx0XHRcdFx0XHQvLyBjb2RlIHVuaXQgaXMgdGhlIGhpZ2ggc3Vycm9nYXRlIG9mIGEgc3Vycm9nYXRlIHBhaXJcblx0XHRcdFx0XHRvdXRwdXQucHVzaCh2YWx1ZSk7XG5cdFx0XHRcdFx0Y291bnRlci0tO1xuXHRcdFx0XHR9XG5cdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRvdXRwdXQucHVzaCh2YWx1ZSk7XG5cdFx0XHR9XG5cdFx0fVxuXHRcdHJldHVybiBvdXRwdXQ7XG5cdH1cblxuXHQvKipcblx0ICogQ3JlYXRlcyBhIHN0cmluZyBiYXNlZCBvbiBhbiBhcnJheSBvZiBudW1lcmljIGNvZGUgcG9pbnRzLlxuXHQgKiBAc2VlIGBwdW55Y29kZS51Y3MyLmRlY29kZWBcblx0ICogQG1lbWJlck9mIHB1bnljb2RlLnVjczJcblx0ICogQG5hbWUgZW5jb2RlXG5cdCAqIEBwYXJhbSB7QXJyYXl9IGNvZGVQb2ludHMgVGhlIGFycmF5IG9mIG51bWVyaWMgY29kZSBwb2ludHMuXG5cdCAqIEByZXR1cm5zIHtTdHJpbmd9IFRoZSBuZXcgVW5pY29kZSBzdHJpbmcgKFVDUy0yKS5cblx0ICovXG5cdGZ1bmN0aW9uIHVjczJlbmNvZGUoYXJyYXkpIHtcblx0XHRyZXR1cm4gbWFwKGFycmF5LCBmdW5jdGlvbih2YWx1ZSkge1xuXHRcdFx0dmFyIG91dHB1dCA9ICcnO1xuXHRcdFx0aWYgKHZhbHVlID4gMHhGRkZGKSB7XG5cdFx0XHRcdHZhbHVlIC09IDB4MTAwMDA7XG5cdFx0XHRcdG91dHB1dCArPSBzdHJpbmdGcm9tQ2hhckNvZGUodmFsdWUgPj4+IDEwICYgMHgzRkYgfCAweEQ4MDApO1xuXHRcdFx0XHR2YWx1ZSA9IDB4REMwMCB8IHZhbHVlICYgMHgzRkY7XG5cdFx0XHR9XG5cdFx0XHRvdXRwdXQgKz0gc3RyaW5nRnJvbUNoYXJDb2RlKHZhbHVlKTtcblx0XHRcdHJldHVybiBvdXRwdXQ7XG5cdFx0fSkuam9pbignJyk7XG5cdH1cblxuXHQvKipcblx0ICogQ29udmVydHMgYSBiYXNpYyBjb2RlIHBvaW50IGludG8gYSBkaWdpdC9pbnRlZ2VyLlxuXHQgKiBAc2VlIGBkaWdpdFRvQmFzaWMoKWBcblx0ICogQHByaXZhdGVcblx0ICogQHBhcmFtIHtOdW1iZXJ9IGNvZGVQb2ludCBUaGUgYmFzaWMgbnVtZXJpYyBjb2RlIHBvaW50IHZhbHVlLlxuXHQgKiBAcmV0dXJucyB7TnVtYmVyfSBUaGUgbnVtZXJpYyB2YWx1ZSBvZiBhIGJhc2ljIGNvZGUgcG9pbnQgKGZvciB1c2UgaW5cblx0ICogcmVwcmVzZW50aW5nIGludGVnZXJzKSBpbiB0aGUgcmFuZ2UgYDBgIHRvIGBiYXNlIC0gMWAsIG9yIGBiYXNlYCBpZlxuXHQgKiB0aGUgY29kZSBwb2ludCBkb2VzIG5vdCByZXByZXNlbnQgYSB2YWx1ZS5cblx0ICovXG5cdGZ1bmN0aW9uIGJhc2ljVG9EaWdpdChjb2RlUG9pbnQpIHtcblx0XHRpZiAoY29kZVBvaW50IC0gNDggPCAxMCkge1xuXHRcdFx0cmV0dXJuIGNvZGVQb2ludCAtIDIyO1xuXHRcdH1cblx0XHRpZiAoY29kZVBvaW50IC0gNjUgPCAyNikge1xuXHRcdFx0cmV0dXJuIGNvZGVQb2ludCAtIDY1O1xuXHRcdH1cblx0XHRpZiAoY29kZVBvaW50IC0gOTcgPCAyNikge1xuXHRcdFx0cmV0dXJuIGNvZGVQb2ludCAtIDk3O1xuXHRcdH1cblx0XHRyZXR1cm4gYmFzZTtcblx0fVxuXG5cdC8qKlxuXHQgKiBDb252ZXJ0cyBhIGRpZ2l0L2ludGVnZXIgaW50byBhIGJhc2ljIGNvZGUgcG9pbnQuXG5cdCAqIEBzZWUgYGJhc2ljVG9EaWdpdCgpYFxuXHQgKiBAcHJpdmF0ZVxuXHQgKiBAcGFyYW0ge051bWJlcn0gZGlnaXQgVGhlIG51bWVyaWMgdmFsdWUgb2YgYSBiYXNpYyBjb2RlIHBvaW50LlxuXHQgKiBAcmV0dXJucyB7TnVtYmVyfSBUaGUgYmFzaWMgY29kZSBwb2ludCB3aG9zZSB2YWx1ZSAod2hlbiB1c2VkIGZvclxuXHQgKiByZXByZXNlbnRpbmcgaW50ZWdlcnMpIGlzIGBkaWdpdGAsIHdoaWNoIG5lZWRzIHRvIGJlIGluIHRoZSByYW5nZVxuXHQgKiBgMGAgdG8gYGJhc2UgLSAxYC4gSWYgYGZsYWdgIGlzIG5vbi16ZXJvLCB0aGUgdXBwZXJjYXNlIGZvcm0gaXNcblx0ICogdXNlZDsgZWxzZSwgdGhlIGxvd2VyY2FzZSBmb3JtIGlzIHVzZWQuIFRoZSBiZWhhdmlvciBpcyB1bmRlZmluZWRcblx0ICogaWYgYGZsYWdgIGlzIG5vbi16ZXJvIGFuZCBgZGlnaXRgIGhhcyBubyB1cHBlcmNhc2UgZm9ybS5cblx0ICovXG5cdGZ1bmN0aW9uIGRpZ2l0VG9CYXNpYyhkaWdpdCwgZmxhZykge1xuXHRcdC8vICAwLi4yNSBtYXAgdG8gQVNDSUkgYS4ueiBvciBBLi5aXG5cdFx0Ly8gMjYuLjM1IG1hcCB0byBBU0NJSSAwLi45XG5cdFx0cmV0dXJuIGRpZ2l0ICsgMjIgKyA3NSAqIChkaWdpdCA8IDI2KSAtICgoZmxhZyAhPSAwKSA8PCA1KTtcblx0fVxuXG5cdC8qKlxuXHQgKiBCaWFzIGFkYXB0YXRpb24gZnVuY3Rpb24gYXMgcGVyIHNlY3Rpb24gMy40IG9mIFJGQyAzNDkyLlxuXHQgKiBodHRwOi8vdG9vbHMuaWV0Zi5vcmcvaHRtbC9yZmMzNDkyI3NlY3Rpb24tMy40XG5cdCAqIEBwcml2YXRlXG5cdCAqL1xuXHRmdW5jdGlvbiBhZGFwdChkZWx0YSwgbnVtUG9pbnRzLCBmaXJzdFRpbWUpIHtcblx0XHR2YXIgayA9IDA7XG5cdFx0ZGVsdGEgPSBmaXJzdFRpbWUgPyBmbG9vcihkZWx0YSAvIGRhbXApIDogZGVsdGEgPj4gMTtcblx0XHRkZWx0YSArPSBmbG9vcihkZWx0YSAvIG51bVBvaW50cyk7XG5cdFx0Zm9yICgvKiBubyBpbml0aWFsaXphdGlvbiAqLzsgZGVsdGEgPiBiYXNlTWludXNUTWluICogdE1heCA+PiAxOyBrICs9IGJhc2UpIHtcblx0XHRcdGRlbHRhID0gZmxvb3IoZGVsdGEgLyBiYXNlTWludXNUTWluKTtcblx0XHR9XG5cdFx0cmV0dXJuIGZsb29yKGsgKyAoYmFzZU1pbnVzVE1pbiArIDEpICogZGVsdGEgLyAoZGVsdGEgKyBza2V3KSk7XG5cdH1cblxuXHQvKipcblx0ICogQ29udmVydHMgYSBQdW55Y29kZSBzdHJpbmcgb2YgQVNDSUktb25seSBzeW1ib2xzIHRvIGEgc3RyaW5nIG9mIFVuaWNvZGVcblx0ICogc3ltYm9scy5cblx0ICogQG1lbWJlck9mIHB1bnljb2RlXG5cdCAqIEBwYXJhbSB7U3RyaW5nfSBpbnB1dCBUaGUgUHVueWNvZGUgc3RyaW5nIG9mIEFTQ0lJLW9ubHkgc3ltYm9scy5cblx0ICogQHJldHVybnMge1N0cmluZ30gVGhlIHJlc3VsdGluZyBzdHJpbmcgb2YgVW5pY29kZSBzeW1ib2xzLlxuXHQgKi9cblx0ZnVuY3Rpb24gZGVjb2RlKGlucHV0KSB7XG5cdFx0Ly8gRG9uJ3QgdXNlIFVDUy0yXG5cdFx0dmFyIG91dHB1dCA9IFtdLFxuXHRcdCAgICBpbnB1dExlbmd0aCA9IGlucHV0Lmxlbmd0aCxcblx0XHQgICAgb3V0LFxuXHRcdCAgICBpID0gMCxcblx0XHQgICAgbiA9IGluaXRpYWxOLFxuXHRcdCAgICBiaWFzID0gaW5pdGlhbEJpYXMsXG5cdFx0ICAgIGJhc2ljLFxuXHRcdCAgICBqLFxuXHRcdCAgICBpbmRleCxcblx0XHQgICAgb2xkaSxcblx0XHQgICAgdyxcblx0XHQgICAgayxcblx0XHQgICAgZGlnaXQsXG5cdFx0ICAgIHQsXG5cdFx0ICAgIGxlbmd0aCxcblx0XHQgICAgLyoqIENhY2hlZCBjYWxjdWxhdGlvbiByZXN1bHRzICovXG5cdFx0ICAgIGJhc2VNaW51c1Q7XG5cblx0XHQvLyBIYW5kbGUgdGhlIGJhc2ljIGNvZGUgcG9pbnRzOiBsZXQgYGJhc2ljYCBiZSB0aGUgbnVtYmVyIG9mIGlucHV0IGNvZGVcblx0XHQvLyBwb2ludHMgYmVmb3JlIHRoZSBsYXN0IGRlbGltaXRlciwgb3IgYDBgIGlmIHRoZXJlIGlzIG5vbmUsIHRoZW4gY29weVxuXHRcdC8vIHRoZSBmaXJzdCBiYXNpYyBjb2RlIHBvaW50cyB0byB0aGUgb3V0cHV0LlxuXG5cdFx0YmFzaWMgPSBpbnB1dC5sYXN0SW5kZXhPZihkZWxpbWl0ZXIpO1xuXHRcdGlmIChiYXNpYyA8IDApIHtcblx0XHRcdGJhc2ljID0gMDtcblx0XHR9XG5cblx0XHRmb3IgKGogPSAwOyBqIDwgYmFzaWM7ICsraikge1xuXHRcdFx0Ly8gaWYgaXQncyBub3QgYSBiYXNpYyBjb2RlIHBvaW50XG5cdFx0XHRpZiAoaW5wdXQuY2hhckNvZGVBdChqKSA+PSAweDgwKSB7XG5cdFx0XHRcdGVycm9yKCdub3QtYmFzaWMnKTtcblx0XHRcdH1cblx0XHRcdG91dHB1dC5wdXNoKGlucHV0LmNoYXJDb2RlQXQoaikpO1xuXHRcdH1cblxuXHRcdC8vIE1haW4gZGVjb2RpbmcgbG9vcDogc3RhcnQganVzdCBhZnRlciB0aGUgbGFzdCBkZWxpbWl0ZXIgaWYgYW55IGJhc2ljIGNvZGVcblx0XHQvLyBwb2ludHMgd2VyZSBjb3BpZWQ7IHN0YXJ0IGF0IHRoZSBiZWdpbm5pbmcgb3RoZXJ3aXNlLlxuXG5cdFx0Zm9yIChpbmRleCA9IGJhc2ljID4gMCA/IGJhc2ljICsgMSA6IDA7IGluZGV4IDwgaW5wdXRMZW5ndGg7IC8qIG5vIGZpbmFsIGV4cHJlc3Npb24gKi8pIHtcblxuXHRcdFx0Ly8gYGluZGV4YCBpcyB0aGUgaW5kZXggb2YgdGhlIG5leHQgY2hhcmFjdGVyIHRvIGJlIGNvbnN1bWVkLlxuXHRcdFx0Ly8gRGVjb2RlIGEgZ2VuZXJhbGl6ZWQgdmFyaWFibGUtbGVuZ3RoIGludGVnZXIgaW50byBgZGVsdGFgLFxuXHRcdFx0Ly8gd2hpY2ggZ2V0cyBhZGRlZCB0byBgaWAuIFRoZSBvdmVyZmxvdyBjaGVja2luZyBpcyBlYXNpZXJcblx0XHRcdC8vIGlmIHdlIGluY3JlYXNlIGBpYCBhcyB3ZSBnbywgdGhlbiBzdWJ0cmFjdCBvZmYgaXRzIHN0YXJ0aW5nXG5cdFx0XHQvLyB2YWx1ZSBhdCB0aGUgZW5kIHRvIG9idGFpbiBgZGVsdGFgLlxuXHRcdFx0Zm9yIChvbGRpID0gaSwgdyA9IDEsIGsgPSBiYXNlOyAvKiBubyBjb25kaXRpb24gKi87IGsgKz0gYmFzZSkge1xuXG5cdFx0XHRcdGlmIChpbmRleCA+PSBpbnB1dExlbmd0aCkge1xuXHRcdFx0XHRcdGVycm9yKCdpbnZhbGlkLWlucHV0Jyk7XG5cdFx0XHRcdH1cblxuXHRcdFx0XHRkaWdpdCA9IGJhc2ljVG9EaWdpdChpbnB1dC5jaGFyQ29kZUF0KGluZGV4KyspKTtcblxuXHRcdFx0XHRpZiAoZGlnaXQgPj0gYmFzZSB8fCBkaWdpdCA+IGZsb29yKChtYXhJbnQgLSBpKSAvIHcpKSB7XG5cdFx0XHRcdFx0ZXJyb3IoJ292ZXJmbG93Jyk7XG5cdFx0XHRcdH1cblxuXHRcdFx0XHRpICs9IGRpZ2l0ICogdztcblx0XHRcdFx0dCA9IGsgPD0gYmlhcyA/IHRNaW4gOiAoayA+PSBiaWFzICsgdE1heCA/IHRNYXggOiBrIC0gYmlhcyk7XG5cblx0XHRcdFx0aWYgKGRpZ2l0IDwgdCkge1xuXHRcdFx0XHRcdGJyZWFrO1xuXHRcdFx0XHR9XG5cblx0XHRcdFx0YmFzZU1pbnVzVCA9IGJhc2UgLSB0O1xuXHRcdFx0XHRpZiAodyA+IGZsb29yKG1heEludCAvIGJhc2VNaW51c1QpKSB7XG5cdFx0XHRcdFx0ZXJyb3IoJ292ZXJmbG93Jyk7XG5cdFx0XHRcdH1cblxuXHRcdFx0XHR3ICo9IGJhc2VNaW51c1Q7XG5cblx0XHRcdH1cblxuXHRcdFx0b3V0ID0gb3V0cHV0Lmxlbmd0aCArIDE7XG5cdFx0XHRiaWFzID0gYWRhcHQoaSAtIG9sZGksIG91dCwgb2xkaSA9PSAwKTtcblxuXHRcdFx0Ly8gYGlgIHdhcyBzdXBwb3NlZCB0byB3cmFwIGFyb3VuZCBmcm9tIGBvdXRgIHRvIGAwYCxcblx0XHRcdC8vIGluY3JlbWVudGluZyBgbmAgZWFjaCB0aW1lLCBzbyB3ZSdsbCBmaXggdGhhdCBub3c6XG5cdFx0XHRpZiAoZmxvb3IoaSAvIG91dCkgPiBtYXhJbnQgLSBuKSB7XG5cdFx0XHRcdGVycm9yKCdvdmVyZmxvdycpO1xuXHRcdFx0fVxuXG5cdFx0XHRuICs9IGZsb29yKGkgLyBvdXQpO1xuXHRcdFx0aSAlPSBvdXQ7XG5cblx0XHRcdC8vIEluc2VydCBgbmAgYXQgcG9zaXRpb24gYGlgIG9mIHRoZSBvdXRwdXRcblx0XHRcdG91dHB1dC5zcGxpY2UoaSsrLCAwLCBuKTtcblxuXHRcdH1cblxuXHRcdHJldHVybiB1Y3MyZW5jb2RlKG91dHB1dCk7XG5cdH1cblxuXHQvKipcblx0ICogQ29udmVydHMgYSBzdHJpbmcgb2YgVW5pY29kZSBzeW1ib2xzIHRvIGEgUHVueWNvZGUgc3RyaW5nIG9mIEFTQ0lJLW9ubHlcblx0ICogc3ltYm9scy5cblx0ICogQG1lbWJlck9mIHB1bnljb2RlXG5cdCAqIEBwYXJhbSB7U3RyaW5nfSBpbnB1dCBUaGUgc3RyaW5nIG9mIFVuaWNvZGUgc3ltYm9scy5cblx0ICogQHJldHVybnMge1N0cmluZ30gVGhlIHJlc3VsdGluZyBQdW55Y29kZSBzdHJpbmcgb2YgQVNDSUktb25seSBzeW1ib2xzLlxuXHQgKi9cblx0ZnVuY3Rpb24gZW5jb2RlKGlucHV0KSB7XG5cdFx0dmFyIG4sXG5cdFx0ICAgIGRlbHRhLFxuXHRcdCAgICBoYW5kbGVkQ1BDb3VudCxcblx0XHQgICAgYmFzaWNMZW5ndGgsXG5cdFx0ICAgIGJpYXMsXG5cdFx0ICAgIGosXG5cdFx0ICAgIG0sXG5cdFx0ICAgIHEsXG5cdFx0ICAgIGssXG5cdFx0ICAgIHQsXG5cdFx0ICAgIGN1cnJlbnRWYWx1ZSxcblx0XHQgICAgb3V0cHV0ID0gW10sXG5cdFx0ICAgIC8qKiBgaW5wdXRMZW5ndGhgIHdpbGwgaG9sZCB0aGUgbnVtYmVyIG9mIGNvZGUgcG9pbnRzIGluIGBpbnB1dGAuICovXG5cdFx0ICAgIGlucHV0TGVuZ3RoLFxuXHRcdCAgICAvKiogQ2FjaGVkIGNhbGN1bGF0aW9uIHJlc3VsdHMgKi9cblx0XHQgICAgaGFuZGxlZENQQ291bnRQbHVzT25lLFxuXHRcdCAgICBiYXNlTWludXNULFxuXHRcdCAgICBxTWludXNUO1xuXG5cdFx0Ly8gQ29udmVydCB0aGUgaW5wdXQgaW4gVUNTLTIgdG8gVW5pY29kZVxuXHRcdGlucHV0ID0gdWNzMmRlY29kZShpbnB1dCk7XG5cblx0XHQvLyBDYWNoZSB0aGUgbGVuZ3RoXG5cdFx0aW5wdXRMZW5ndGggPSBpbnB1dC5sZW5ndGg7XG5cblx0XHQvLyBJbml0aWFsaXplIHRoZSBzdGF0ZVxuXHRcdG4gPSBpbml0aWFsTjtcblx0XHRkZWx0YSA9IDA7XG5cdFx0YmlhcyA9IGluaXRpYWxCaWFzO1xuXG5cdFx0Ly8gSGFuZGxlIHRoZSBiYXNpYyBjb2RlIHBvaW50c1xuXHRcdGZvciAoaiA9IDA7IGogPCBpbnB1dExlbmd0aDsgKytqKSB7XG5cdFx0XHRjdXJyZW50VmFsdWUgPSBpbnB1dFtqXTtcblx0XHRcdGlmIChjdXJyZW50VmFsdWUgPCAweDgwKSB7XG5cdFx0XHRcdG91dHB1dC5wdXNoKHN0cmluZ0Zyb21DaGFyQ29kZShjdXJyZW50VmFsdWUpKTtcblx0XHRcdH1cblx0XHR9XG5cblx0XHRoYW5kbGVkQ1BDb3VudCA9IGJhc2ljTGVuZ3RoID0gb3V0cHV0Lmxlbmd0aDtcblxuXHRcdC8vIGBoYW5kbGVkQ1BDb3VudGAgaXMgdGhlIG51bWJlciBvZiBjb2RlIHBvaW50cyB0aGF0IGhhdmUgYmVlbiBoYW5kbGVkO1xuXHRcdC8vIGBiYXNpY0xlbmd0aGAgaXMgdGhlIG51bWJlciBvZiBiYXNpYyBjb2RlIHBvaW50cy5cblxuXHRcdC8vIEZpbmlzaCB0aGUgYmFzaWMgc3RyaW5nIC0gaWYgaXQgaXMgbm90IGVtcHR5IC0gd2l0aCBhIGRlbGltaXRlclxuXHRcdGlmIChiYXNpY0xlbmd0aCkge1xuXHRcdFx0b3V0cHV0LnB1c2goZGVsaW1pdGVyKTtcblx0XHR9XG5cblx0XHQvLyBNYWluIGVuY29kaW5nIGxvb3A6XG5cdFx0d2hpbGUgKGhhbmRsZWRDUENvdW50IDwgaW5wdXRMZW5ndGgpIHtcblxuXHRcdFx0Ly8gQWxsIG5vbi1iYXNpYyBjb2RlIHBvaW50cyA8IG4gaGF2ZSBiZWVuIGhhbmRsZWQgYWxyZWFkeS4gRmluZCB0aGUgbmV4dFxuXHRcdFx0Ly8gbGFyZ2VyIG9uZTpcblx0XHRcdGZvciAobSA9IG1heEludCwgaiA9IDA7IGogPCBpbnB1dExlbmd0aDsgKytqKSB7XG5cdFx0XHRcdGN1cnJlbnRWYWx1ZSA9IGlucHV0W2pdO1xuXHRcdFx0XHRpZiAoY3VycmVudFZhbHVlID49IG4gJiYgY3VycmVudFZhbHVlIDwgbSkge1xuXHRcdFx0XHRcdG0gPSBjdXJyZW50VmFsdWU7XG5cdFx0XHRcdH1cblx0XHRcdH1cblxuXHRcdFx0Ly8gSW5jcmVhc2UgYGRlbHRhYCBlbm91Z2ggdG8gYWR2YW5jZSB0aGUgZGVjb2RlcidzIDxuLGk+IHN0YXRlIHRvIDxtLDA+LFxuXHRcdFx0Ly8gYnV0IGd1YXJkIGFnYWluc3Qgb3ZlcmZsb3dcblx0XHRcdGhhbmRsZWRDUENvdW50UGx1c09uZSA9IGhhbmRsZWRDUENvdW50ICsgMTtcblx0XHRcdGlmIChtIC0gbiA+IGZsb29yKChtYXhJbnQgLSBkZWx0YSkgLyBoYW5kbGVkQ1BDb3VudFBsdXNPbmUpKSB7XG5cdFx0XHRcdGVycm9yKCdvdmVyZmxvdycpO1xuXHRcdFx0fVxuXG5cdFx0XHRkZWx0YSArPSAobSAtIG4pICogaGFuZGxlZENQQ291bnRQbHVzT25lO1xuXHRcdFx0biA9IG07XG5cblx0XHRcdGZvciAoaiA9IDA7IGogPCBpbnB1dExlbmd0aDsgKytqKSB7XG5cdFx0XHRcdGN1cnJlbnRWYWx1ZSA9IGlucHV0W2pdO1xuXG5cdFx0XHRcdGlmIChjdXJyZW50VmFsdWUgPCBuICYmICsrZGVsdGEgPiBtYXhJbnQpIHtcblx0XHRcdFx0XHRlcnJvcignb3ZlcmZsb3cnKTtcblx0XHRcdFx0fVxuXG5cdFx0XHRcdGlmIChjdXJyZW50VmFsdWUgPT0gbikge1xuXHRcdFx0XHRcdC8vIFJlcHJlc2VudCBkZWx0YSBhcyBhIGdlbmVyYWxpemVkIHZhcmlhYmxlLWxlbmd0aCBpbnRlZ2VyXG5cdFx0XHRcdFx0Zm9yIChxID0gZGVsdGEsIGsgPSBiYXNlOyAvKiBubyBjb25kaXRpb24gKi87IGsgKz0gYmFzZSkge1xuXHRcdFx0XHRcdFx0dCA9IGsgPD0gYmlhcyA/IHRNaW4gOiAoayA+PSBiaWFzICsgdE1heCA/IHRNYXggOiBrIC0gYmlhcyk7XG5cdFx0XHRcdFx0XHRpZiAocSA8IHQpIHtcblx0XHRcdFx0XHRcdFx0YnJlYWs7XG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0XHRxTWludXNUID0gcSAtIHQ7XG5cdFx0XHRcdFx0XHRiYXNlTWludXNUID0gYmFzZSAtIHQ7XG5cdFx0XHRcdFx0XHRvdXRwdXQucHVzaChcblx0XHRcdFx0XHRcdFx0c3RyaW5nRnJvbUNoYXJDb2RlKGRpZ2l0VG9CYXNpYyh0ICsgcU1pbnVzVCAlIGJhc2VNaW51c1QsIDApKVxuXHRcdFx0XHRcdFx0KTtcblx0XHRcdFx0XHRcdHEgPSBmbG9vcihxTWludXNUIC8gYmFzZU1pbnVzVCk7XG5cdFx0XHRcdFx0fVxuXG5cdFx0XHRcdFx0b3V0cHV0LnB1c2goc3RyaW5nRnJvbUNoYXJDb2RlKGRpZ2l0VG9CYXNpYyhxLCAwKSkpO1xuXHRcdFx0XHRcdGJpYXMgPSBhZGFwdChkZWx0YSwgaGFuZGxlZENQQ291bnRQbHVzT25lLCBoYW5kbGVkQ1BDb3VudCA9PSBiYXNpY0xlbmd0aCk7XG5cdFx0XHRcdFx0ZGVsdGEgPSAwO1xuXHRcdFx0XHRcdCsraGFuZGxlZENQQ291bnQ7XG5cdFx0XHRcdH1cblx0XHRcdH1cblxuXHRcdFx0KytkZWx0YTtcblx0XHRcdCsrbjtcblxuXHRcdH1cblx0XHRyZXR1cm4gb3V0cHV0LmpvaW4oJycpO1xuXHR9XG5cblx0LyoqXG5cdCAqIENvbnZlcnRzIGEgUHVueWNvZGUgc3RyaW5nIHJlcHJlc2VudGluZyBhIGRvbWFpbiBuYW1lIHRvIFVuaWNvZGUuIE9ubHkgdGhlXG5cdCAqIFB1bnljb2RlZCBwYXJ0cyBvZiB0aGUgZG9tYWluIG5hbWUgd2lsbCBiZSBjb252ZXJ0ZWQsIGkuZS4gaXQgZG9lc24ndFxuXHQgKiBtYXR0ZXIgaWYgeW91IGNhbGwgaXQgb24gYSBzdHJpbmcgdGhhdCBoYXMgYWxyZWFkeSBiZWVuIGNvbnZlcnRlZCB0b1xuXHQgKiBVbmljb2RlLlxuXHQgKiBAbWVtYmVyT2YgcHVueWNvZGVcblx0ICogQHBhcmFtIHtTdHJpbmd9IGRvbWFpbiBUaGUgUHVueWNvZGUgZG9tYWluIG5hbWUgdG8gY29udmVydCB0byBVbmljb2RlLlxuXHQgKiBAcmV0dXJucyB7U3RyaW5nfSBUaGUgVW5pY29kZSByZXByZXNlbnRhdGlvbiBvZiB0aGUgZ2l2ZW4gUHVueWNvZGVcblx0ICogc3RyaW5nLlxuXHQgKi9cblx0ZnVuY3Rpb24gdG9Vbmljb2RlKGRvbWFpbikge1xuXHRcdHJldHVybiBtYXBEb21haW4oZG9tYWluLCBmdW5jdGlvbihzdHJpbmcpIHtcblx0XHRcdHJldHVybiByZWdleFB1bnljb2RlLnRlc3Qoc3RyaW5nKVxuXHRcdFx0XHQ/IGRlY29kZShzdHJpbmcuc2xpY2UoNCkudG9Mb3dlckNhc2UoKSlcblx0XHRcdFx0OiBzdHJpbmc7XG5cdFx0fSk7XG5cdH1cblxuXHQvKipcblx0ICogQ29udmVydHMgYSBVbmljb2RlIHN0cmluZyByZXByZXNlbnRpbmcgYSBkb21haW4gbmFtZSB0byBQdW55Y29kZS4gT25seSB0aGVcblx0ICogbm9uLUFTQ0lJIHBhcnRzIG9mIHRoZSBkb21haW4gbmFtZSB3aWxsIGJlIGNvbnZlcnRlZCwgaS5lLiBpdCBkb2Vzbid0XG5cdCAqIG1hdHRlciBpZiB5b3UgY2FsbCBpdCB3aXRoIGEgZG9tYWluIHRoYXQncyBhbHJlYWR5IGluIEFTQ0lJLlxuXHQgKiBAbWVtYmVyT2YgcHVueWNvZGVcblx0ICogQHBhcmFtIHtTdHJpbmd9IGRvbWFpbiBUaGUgZG9tYWluIG5hbWUgdG8gY29udmVydCwgYXMgYSBVbmljb2RlIHN0cmluZy5cblx0ICogQHJldHVybnMge1N0cmluZ30gVGhlIFB1bnljb2RlIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBnaXZlbiBkb21haW4gbmFtZS5cblx0ICovXG5cdGZ1bmN0aW9uIHRvQVNDSUkoZG9tYWluKSB7XG5cdFx0cmV0dXJuIG1hcERvbWFpbihkb21haW4sIGZ1bmN0aW9uKHN0cmluZykge1xuXHRcdFx0cmV0dXJuIHJlZ2V4Tm9uQVNDSUkudGVzdChzdHJpbmcpXG5cdFx0XHRcdD8gJ3huLS0nICsgZW5jb2RlKHN0cmluZylcblx0XHRcdFx0OiBzdHJpbmc7XG5cdFx0fSk7XG5cdH1cblxuXHQvKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuXHQvKiogRGVmaW5lIHRoZSBwdWJsaWMgQVBJICovXG5cdHB1bnljb2RlID0ge1xuXHRcdC8qKlxuXHRcdCAqIEEgc3RyaW5nIHJlcHJlc2VudGluZyB0aGUgY3VycmVudCBQdW55Y29kZS5qcyB2ZXJzaW9uIG51bWJlci5cblx0XHQgKiBAbWVtYmVyT2YgcHVueWNvZGVcblx0XHQgKiBAdHlwZSBTdHJpbmdcblx0XHQgKi9cblx0XHQndmVyc2lvbic6ICcxLjIuMycsXG5cdFx0LyoqXG5cdFx0ICogQW4gb2JqZWN0IG9mIG1ldGhvZHMgdG8gY29udmVydCBmcm9tIEphdmFTY3JpcHQncyBpbnRlcm5hbCBjaGFyYWN0ZXJcblx0XHQgKiByZXByZXNlbnRhdGlvbiAoVUNTLTIpIHRvIFVuaWNvZGUgY29kZSBwb2ludHMsIGFuZCBiYWNrLlxuXHRcdCAqIEBzZWUgPGh0dHA6Ly9tYXRoaWFzYnluZW5zLmJlL25vdGVzL2phdmFzY3JpcHQtZW5jb2Rpbmc+XG5cdFx0ICogQG1lbWJlck9mIHB1bnljb2RlXG5cdFx0ICogQHR5cGUgT2JqZWN0XG5cdFx0ICovXG5cdFx0J3VjczInOiB7XG5cdFx0XHQnZGVjb2RlJzogdWNzMmRlY29kZSxcblx0XHRcdCdlbmNvZGUnOiB1Y3MyZW5jb2RlXG5cdFx0fSxcblx0XHQnZGVjb2RlJzogZGVjb2RlLFxuXHRcdCdlbmNvZGUnOiBlbmNvZGUsXG5cdFx0J3RvQVNDSUknOiB0b0FTQ0lJLFxuXHRcdCd0b1VuaWNvZGUnOiB0b1VuaWNvZGVcblx0fTtcblxuXHQvKiogRXhwb3NlIGBwdW55Y29kZWAgKi9cblx0Ly8gU29tZSBBTUQgYnVpbGQgb3B0aW1pemVycywgbGlrZSByLmpzLCBjaGVjayBmb3Igc3BlY2lmaWMgY29uZGl0aW9uIHBhdHRlcm5zXG5cdC8vIGxpa2UgdGhlIGZvbGxvd2luZzpcblx0aWYgKFxuXHRcdHR5cGVvZiBkZWZpbmUgPT0gJ2Z1bmN0aW9uJyAmJlxuXHRcdHR5cGVvZiBkZWZpbmUuYW1kID09ICdvYmplY3QnICYmXG5cdFx0ZGVmaW5lLmFtZFxuXHQpIHtcblx0XHRkZWZpbmUoZnVuY3Rpb24oKSB7XG5cdFx0XHRyZXR1cm4gcHVueWNvZGU7XG5cdFx0fSk7XG5cdH1cdGVsc2UgaWYgKGZyZWVFeHBvcnRzICYmICFmcmVlRXhwb3J0cy5ub2RlVHlwZSkge1xuXHRcdGlmIChmcmVlTW9kdWxlKSB7IC8vIGluIE5vZGUuanMgb3IgUmluZ29KUyB2MC44LjArXG5cdFx0XHRmcmVlTW9kdWxlLmV4cG9ydHMgPSBwdW55Y29kZTtcblx0XHR9IGVsc2UgeyAvLyBpbiBOYXJ3aGFsIG9yIFJpbmdvSlMgdjAuNy4wLVxuXHRcdFx0Zm9yIChrZXkgaW4gcHVueWNvZGUpIHtcblx0XHRcdFx0cHVueWNvZGUuaGFzT3duUHJvcGVydHkoa2V5KSAmJiAoZnJlZUV4cG9ydHNba2V5XSA9IHB1bnljb2RlW2tleV0pO1xuXHRcdFx0fVxuXHRcdH1cblx0fSBlbHNlIHsgLy8gaW4gUmhpbm8gb3IgYSB3ZWIgYnJvd3NlclxuXHRcdHJvb3QucHVueWNvZGUgPSBwdW55Y29kZTtcblx0fVxuXG59KHRoaXMpKTtcbiIsIid1c2Ugc3RyaWN0JztcblxuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsICdfX2VzTW9kdWxlJywge1xuICB2YWx1ZTogdHJ1ZVxufSk7XG5cbmZ1bmN0aW9uIF9pbnRlcm9wUmVxdWlyZURlZmF1bHQob2JqKSB7IHJldHVybiBvYmogJiYgb2JqLl9fZXNNb2R1bGUgPyBvYmogOiB7ICdkZWZhdWx0Jzogb2JqIH07IH1cblxudmFyIF9oZWxwZXJzRXZlbnQgPSByZXF1aXJlKCcuL2hlbHBlcnMvZXZlbnQnKTtcblxudmFyIF9oZWxwZXJzRXZlbnQyID0gX2ludGVyb3BSZXF1aXJlRGVmYXVsdChfaGVscGVyc0V2ZW50KTtcblxudmFyIF9oZWxwZXJzTWVzc2FnZUV2ZW50ID0gcmVxdWlyZSgnLi9oZWxwZXJzL21lc3NhZ2UtZXZlbnQnKTtcblxudmFyIF9oZWxwZXJzTWVzc2FnZUV2ZW50MiA9IF9pbnRlcm9wUmVxdWlyZURlZmF1bHQoX2hlbHBlcnNNZXNzYWdlRXZlbnQpO1xuXG52YXIgX2hlbHBlcnNDbG9zZUV2ZW50ID0gcmVxdWlyZSgnLi9oZWxwZXJzL2Nsb3NlLWV2ZW50Jyk7XG5cbnZhciBfaGVscGVyc0Nsb3NlRXZlbnQyID0gX2ludGVyb3BSZXF1aXJlRGVmYXVsdChfaGVscGVyc0Nsb3NlRXZlbnQpO1xuXG4vKlxuKiBDcmVhdGVzIGFuIEV2ZW50IG9iamVjdCBhbmQgZXh0ZW5kcyBpdCB0byBhbGxvdyBmdWxsIG1vZGlmaWNhdGlvbiBvZlxuKiBpdHMgcHJvcGVydGllcy5cbipcbiogQHBhcmFtIHtvYmplY3R9IGNvbmZpZyAtIHdpdGhpbiBjb25maWcgeW91IHdpbGwgbmVlZCB0byBwYXNzIHR5cGUgYW5kIG9wdGlvbmFsbHkgdGFyZ2V0XG4qL1xuZnVuY3Rpb24gY3JlYXRlRXZlbnQoY29uZmlnKSB7XG4gIHZhciB0eXBlID0gY29uZmlnLnR5cGU7XG4gIHZhciB0YXJnZXQgPSBjb25maWcudGFyZ2V0O1xuXG4gIHZhciBldmVudE9iamVjdCA9IG5ldyBfaGVscGVyc0V2ZW50MlsnZGVmYXVsdCddKHR5cGUpO1xuXG4gIGlmICh0YXJnZXQpIHtcbiAgICBldmVudE9iamVjdC50YXJnZXQgPSB0YXJnZXQ7XG4gICAgZXZlbnRPYmplY3Quc3JjRWxlbWVudCA9IHRhcmdldDtcbiAgICBldmVudE9iamVjdC5jdXJyZW50VGFyZ2V0ID0gdGFyZ2V0O1xuICB9XG5cbiAgcmV0dXJuIGV2ZW50T2JqZWN0O1xufVxuXG4vKlxuKiBDcmVhdGVzIGEgTWVzc2FnZUV2ZW50IG9iamVjdCBhbmQgZXh0ZW5kcyBpdCB0byBhbGxvdyBmdWxsIG1vZGlmaWNhdGlvbiBvZlxuKiBpdHMgcHJvcGVydGllcy5cbipcbiogQHBhcmFtIHtvYmplY3R9IGNvbmZpZyAtIHdpdGhpbiBjb25maWcgeW91IHdpbGwgbmVlZCB0byBwYXNzIHR5cGUsIG9yaWdpbiwgZGF0YSBhbmQgb3B0aW9uYWxseSB0YXJnZXRcbiovXG5mdW5jdGlvbiBjcmVhdGVNZXNzYWdlRXZlbnQoY29uZmlnKSB7XG4gIHZhciB0eXBlID0gY29uZmlnLnR5cGU7XG4gIHZhciBvcmlnaW4gPSBjb25maWcub3JpZ2luO1xuICB2YXIgZGF0YSA9IGNvbmZpZy5kYXRhO1xuICB2YXIgdGFyZ2V0ID0gY29uZmlnLnRhcmdldDtcblxuICB2YXIgbWVzc2FnZUV2ZW50ID0gbmV3IF9oZWxwZXJzTWVzc2FnZUV2ZW50MlsnZGVmYXVsdCddKHR5cGUsIHtcbiAgICBkYXRhOiBkYXRhLFxuICAgIG9yaWdpbjogb3JpZ2luXG4gIH0pO1xuXG4gIGlmICh0YXJnZXQpIHtcbiAgICBtZXNzYWdlRXZlbnQudGFyZ2V0ID0gdGFyZ2V0O1xuICAgIG1lc3NhZ2VFdmVudC5zcmNFbGVtZW50ID0gdGFyZ2V0O1xuICAgIG1lc3NhZ2VFdmVudC5jdXJyZW50VGFyZ2V0ID0gdGFyZ2V0O1xuICB9XG5cbiAgcmV0dXJuIG1lc3NhZ2VFdmVudDtcbn1cblxuLypcbiogQ3JlYXRlcyBhIENsb3NlRXZlbnQgb2JqZWN0IGFuZCBleHRlbmRzIGl0IHRvIGFsbG93IGZ1bGwgbW9kaWZpY2F0aW9uIG9mXG4qIGl0cyBwcm9wZXJ0aWVzLlxuKlxuKiBAcGFyYW0ge29iamVjdH0gY29uZmlnIC0gd2l0aGluIGNvbmZpZyB5b3Ugd2lsbCBuZWVkIHRvIHBhc3MgdHlwZSBhbmQgb3B0aW9uYWxseSB0YXJnZXQsIGNvZGUsIGFuZCByZWFzb25cbiovXG5mdW5jdGlvbiBjcmVhdGVDbG9zZUV2ZW50KGNvbmZpZykge1xuICB2YXIgY29kZSA9IGNvbmZpZy5jb2RlO1xuICB2YXIgcmVhc29uID0gY29uZmlnLnJlYXNvbjtcbiAgdmFyIHR5cGUgPSBjb25maWcudHlwZTtcbiAgdmFyIHRhcmdldCA9IGNvbmZpZy50YXJnZXQ7XG4gIHZhciB3YXNDbGVhbiA9IGNvbmZpZy53YXNDbGVhbjtcblxuICBpZiAoIXdhc0NsZWFuKSB7XG4gICAgd2FzQ2xlYW4gPSBjb2RlID09PSAxMDAwO1xuICB9XG5cbiAgdmFyIGNsb3NlRXZlbnQgPSBuZXcgX2hlbHBlcnNDbG9zZUV2ZW50MlsnZGVmYXVsdCddKHR5cGUsIHtcbiAgICBjb2RlOiBjb2RlLFxuICAgIHJlYXNvbjogcmVhc29uLFxuICAgIHdhc0NsZWFuOiB3YXNDbGVhblxuICB9KTtcblxuICBpZiAodGFyZ2V0KSB7XG4gICAgY2xvc2VFdmVudC50YXJnZXQgPSB0YXJnZXQ7XG4gICAgY2xvc2VFdmVudC5zcmNFbGVtZW50ID0gdGFyZ2V0O1xuICAgIGNsb3NlRXZlbnQuY3VycmVudFRhcmdldCA9IHRhcmdldDtcbiAgfVxuXG4gIHJldHVybiBjbG9zZUV2ZW50O1xufVxuXG5leHBvcnRzLmNyZWF0ZUV2ZW50ID0gY3JlYXRlRXZlbnQ7XG5leHBvcnRzLmNyZWF0ZU1lc3NhZ2VFdmVudCA9IGNyZWF0ZU1lc3NhZ2VFdmVudDtcbmV4cG9ydHMuY3JlYXRlQ2xvc2VFdmVudCA9IGNyZWF0ZUNsb3NlRXZlbnQ7IiwiJ3VzZSBzdHJpY3QnO1xuXG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgJ19fZXNNb2R1bGUnLCB7XG4gIHZhbHVlOiB0cnVlXG59KTtcblxudmFyIF9jcmVhdGVDbGFzcyA9IChmdW5jdGlvbiAoKSB7IGZ1bmN0aW9uIGRlZmluZVByb3BlcnRpZXModGFyZ2V0LCBwcm9wcykgeyBmb3IgKHZhciBpID0gMDsgaSA8IHByb3BzLmxlbmd0aDsgaSsrKSB7IHZhciBkZXNjcmlwdG9yID0gcHJvcHNbaV07IGRlc2NyaXB0b3IuZW51bWVyYWJsZSA9IGRlc2NyaXB0b3IuZW51bWVyYWJsZSB8fCBmYWxzZTsgZGVzY3JpcHRvci5jb25maWd1cmFibGUgPSB0cnVlOyBpZiAoJ3ZhbHVlJyBpbiBkZXNjcmlwdG9yKSBkZXNjcmlwdG9yLndyaXRhYmxlID0gdHJ1ZTsgT2JqZWN0LmRlZmluZVByb3BlcnR5KHRhcmdldCwgZGVzY3JpcHRvci5rZXksIGRlc2NyaXB0b3IpOyB9IH0gcmV0dXJuIGZ1bmN0aW9uIChDb25zdHJ1Y3RvciwgcHJvdG9Qcm9wcywgc3RhdGljUHJvcHMpIHsgaWYgKHByb3RvUHJvcHMpIGRlZmluZVByb3BlcnRpZXMoQ29uc3RydWN0b3IucHJvdG90eXBlLCBwcm90b1Byb3BzKTsgaWYgKHN0YXRpY1Byb3BzKSBkZWZpbmVQcm9wZXJ0aWVzKENvbnN0cnVjdG9yLCBzdGF0aWNQcm9wcyk7IHJldHVybiBDb25zdHJ1Y3RvcjsgfTsgfSkoKTtcblxuZnVuY3Rpb24gX2NsYXNzQ2FsbENoZWNrKGluc3RhbmNlLCBDb25zdHJ1Y3RvcikgeyBpZiAoIShpbnN0YW5jZSBpbnN0YW5jZW9mIENvbnN0cnVjdG9yKSkgeyB0aHJvdyBuZXcgVHlwZUVycm9yKCdDYW5ub3QgY2FsbCBhIGNsYXNzIGFzIGEgZnVuY3Rpb24nKTsgfSB9XG5cbnZhciBfaGVscGVyc0FycmF5SGVscGVycyA9IHJlcXVpcmUoJy4vaGVscGVycy9hcnJheS1oZWxwZXJzJyk7XG5cbi8qXG4qIEV2ZW50VGFyZ2V0IGlzIGFuIGludGVyZmFjZSBpbXBsZW1lbnRlZCBieSBvYmplY3RzIHRoYXQgY2FuXG4qIHJlY2VpdmUgZXZlbnRzIGFuZCBtYXkgaGF2ZSBsaXN0ZW5lcnMgZm9yIHRoZW0uXG4qXG4qIGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTL2RvY3MvV2ViL0FQSS9FdmVudFRhcmdldFxuKi9cblxudmFyIEV2ZW50VGFyZ2V0ID0gKGZ1bmN0aW9uICgpIHtcbiAgZnVuY3Rpb24gRXZlbnRUYXJnZXQoKSB7XG4gICAgX2NsYXNzQ2FsbENoZWNrKHRoaXMsIEV2ZW50VGFyZ2V0KTtcblxuICAgIHRoaXMubGlzdGVuZXJzID0ge307XG4gIH1cblxuICAvKlxuICAqIFRpZXMgYSBsaXN0ZW5lciBmdW5jdGlvbiB0byBhIGV2ZW50IHR5cGUgd2hpY2ggY2FuIGxhdGVyIGJlIGludm9rZWQgdmlhIHRoZVxuICAqIGRpc3BhdGNoRXZlbnQgbWV0aG9kLlxuICAqXG4gICogQHBhcmFtIHtzdHJpbmd9IHR5cGUgLSB0aGUgdHlwZSBvZiBldmVudCAoaWU6ICdvcGVuJywgJ21lc3NhZ2UnLCBldGMuKVxuICAqIEBwYXJhbSB7ZnVuY3Rpb259IGxpc3RlbmVyIC0gdGhlIGNhbGxiYWNrIGZ1bmN0aW9uIHRvIGludm9rZSB3aGVuZXZlciBhIGV2ZW50IGlzIGRpc3BhdGNoZWQgbWF0Y2hpbmcgdGhlIGdpdmVuIHR5cGVcbiAgKiBAcGFyYW0ge2Jvb2xlYW59IHVzZUNhcHR1cmUgLSBOL0EgVE9ETzogaW1wbGVtZW50IHVzZUNhcHR1cmUgZnVuY3Rpb25hbGl0eVxuICAqL1xuXG4gIF9jcmVhdGVDbGFzcyhFdmVudFRhcmdldCwgW3tcbiAgICBrZXk6ICdhZGRFdmVudExpc3RlbmVyJyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gYWRkRXZlbnRMaXN0ZW5lcih0eXBlLCBsaXN0ZW5lciAvKiAsIHVzZUNhcHR1cmUgKi8pIHtcbiAgICAgIGlmICh0eXBlb2YgbGlzdGVuZXIgPT09ICdmdW5jdGlvbicpIHtcbiAgICAgICAgaWYgKCFBcnJheS5pc0FycmF5KHRoaXMubGlzdGVuZXJzW3R5cGVdKSkge1xuICAgICAgICAgIHRoaXMubGlzdGVuZXJzW3R5cGVdID0gW107XG4gICAgICAgIH1cblxuICAgICAgICAvLyBPbmx5IGFkZCB0aGUgc2FtZSBmdW5jdGlvbiBvbmNlXG4gICAgICAgIGlmICgoMCwgX2hlbHBlcnNBcnJheUhlbHBlcnMuZmlsdGVyKSh0aGlzLmxpc3RlbmVyc1t0eXBlXSwgZnVuY3Rpb24gKGl0ZW0pIHtcbiAgICAgICAgICByZXR1cm4gaXRlbSA9PT0gbGlzdGVuZXI7XG4gICAgICAgIH0pLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICAgIHRoaXMubGlzdGVuZXJzW3R5cGVdLnB1c2gobGlzdGVuZXIpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuXG4gICAgLypcbiAgICAqIFJlbW92ZXMgdGhlIGxpc3RlbmVyIHNvIGl0IHdpbGwgbm8gbG9uZ2VyIGJlIGludm9rZWQgdmlhIHRoZSBkaXNwYXRjaEV2ZW50IG1ldGhvZC5cbiAgICAqXG4gICAgKiBAcGFyYW0ge3N0cmluZ30gdHlwZSAtIHRoZSB0eXBlIG9mIGV2ZW50IChpZTogJ29wZW4nLCAnbWVzc2FnZScsIGV0Yy4pXG4gICAgKiBAcGFyYW0ge2Z1bmN0aW9ufSBsaXN0ZW5lciAtIHRoZSBjYWxsYmFjayBmdW5jdGlvbiB0byBpbnZva2Ugd2hlbmV2ZXIgYSBldmVudCBpcyBkaXNwYXRjaGVkIG1hdGNoaW5nIHRoZSBnaXZlbiB0eXBlXG4gICAgKiBAcGFyYW0ge2Jvb2xlYW59IHVzZUNhcHR1cmUgLSBOL0EgVE9ETzogaW1wbGVtZW50IHVzZUNhcHR1cmUgZnVuY3Rpb25hbGl0eVxuICAgICovXG4gIH0sIHtcbiAgICBrZXk6ICdyZW1vdmVFdmVudExpc3RlbmVyJyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gcmVtb3ZlRXZlbnRMaXN0ZW5lcih0eXBlLCByZW1vdmluZ0xpc3RlbmVyIC8qICwgdXNlQ2FwdHVyZSAqLykge1xuICAgICAgdmFyIGFycmF5T2ZMaXN0ZW5lcnMgPSB0aGlzLmxpc3RlbmVyc1t0eXBlXTtcbiAgICAgIHRoaXMubGlzdGVuZXJzW3R5cGVdID0gKDAsIF9oZWxwZXJzQXJyYXlIZWxwZXJzLnJlamVjdCkoYXJyYXlPZkxpc3RlbmVycywgZnVuY3Rpb24gKGxpc3RlbmVyKSB7XG4gICAgICAgIHJldHVybiBsaXN0ZW5lciA9PT0gcmVtb3ZpbmdMaXN0ZW5lcjtcbiAgICAgIH0pO1xuICAgIH1cblxuICAgIC8qXG4gICAgKiBJbnZva2VzIGFsbCBsaXN0ZW5lciBmdW5jdGlvbnMgdGhhdCBhcmUgbGlzdGVuaW5nIHRvIHRoZSBnaXZlbiBldmVudC50eXBlIHByb3BlcnR5LiBFYWNoXG4gICAgKiBsaXN0ZW5lciB3aWxsIGJlIHBhc3NlZCB0aGUgZXZlbnQgYXMgdGhlIGZpcnN0IGFyZ3VtZW50LlxuICAgICpcbiAgICAqIEBwYXJhbSB7b2JqZWN0fSBldmVudCAtIGV2ZW50IG9iamVjdCB3aGljaCB3aWxsIGJlIHBhc3NlZCB0byBhbGwgbGlzdGVuZXJzIG9mIHRoZSBldmVudC50eXBlIHByb3BlcnR5XG4gICAgKi9cbiAgfSwge1xuICAgIGtleTogJ2Rpc3BhdGNoRXZlbnQnLFxuICAgIHZhbHVlOiBmdW5jdGlvbiBkaXNwYXRjaEV2ZW50KGV2ZW50KSB7XG4gICAgICB2YXIgX3RoaXMgPSB0aGlzO1xuXG4gICAgICBmb3IgKHZhciBfbGVuID0gYXJndW1lbnRzLmxlbmd0aCwgY3VzdG9tQXJndW1lbnRzID0gQXJyYXkoX2xlbiA+IDEgPyBfbGVuIC0gMSA6IDApLCBfa2V5ID0gMTsgX2tleSA8IF9sZW47IF9rZXkrKykge1xuICAgICAgICBjdXN0b21Bcmd1bWVudHNbX2tleSAtIDFdID0gYXJndW1lbnRzW19rZXldO1xuICAgICAgfVxuXG4gICAgICB2YXIgZXZlbnROYW1lID0gZXZlbnQudHlwZTtcbiAgICAgIHZhciBsaXN0ZW5lcnMgPSB0aGlzLmxpc3RlbmVyc1tldmVudE5hbWVdO1xuXG4gICAgICBpZiAoIUFycmF5LmlzQXJyYXkobGlzdGVuZXJzKSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICB9XG5cbiAgICAgIGxpc3RlbmVycy5mb3JFYWNoKGZ1bmN0aW9uIChsaXN0ZW5lcikge1xuICAgICAgICBpZiAoY3VzdG9tQXJndW1lbnRzLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICBsaXN0ZW5lci5hcHBseShfdGhpcywgY3VzdG9tQXJndW1lbnRzKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBsaXN0ZW5lci5jYWxsKF90aGlzLCBldmVudCk7XG4gICAgICAgIH1cbiAgICAgIH0pO1xuICAgIH1cbiAgfV0pO1xuXG4gIHJldHVybiBFdmVudFRhcmdldDtcbn0pKCk7XG5cbmV4cG9ydHNbJ2RlZmF1bHQnXSA9IEV2ZW50VGFyZ2V0O1xubW9kdWxlLmV4cG9ydHMgPSBleHBvcnRzWydkZWZhdWx0J107IiwiXCJ1c2Ugc3RyaWN0XCI7XG5cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBcIl9fZXNNb2R1bGVcIiwge1xuICB2YWx1ZTogdHJ1ZVxufSk7XG5leHBvcnRzLnJlamVjdCA9IHJlamVjdDtcbmV4cG9ydHMuZmlsdGVyID0gZmlsdGVyO1xuXG5mdW5jdGlvbiByZWplY3QoYXJyYXksIGNhbGxiYWNrKSB7XG4gIHZhciByZXN1bHRzID0gW107XG4gIGFycmF5LmZvckVhY2goZnVuY3Rpb24gKGl0ZW1JbkFycmF5KSB7XG4gICAgaWYgKCFjYWxsYmFjayhpdGVtSW5BcnJheSkpIHtcbiAgICAgIHJlc3VsdHMucHVzaChpdGVtSW5BcnJheSk7XG4gICAgfVxuICB9KTtcblxuICByZXR1cm4gcmVzdWx0cztcbn1cblxuZnVuY3Rpb24gZmlsdGVyKGFycmF5LCBjYWxsYmFjaykge1xuICB2YXIgcmVzdWx0cyA9IFtdO1xuICBhcnJheS5mb3JFYWNoKGZ1bmN0aW9uIChpdGVtSW5BcnJheSkge1xuICAgIGlmIChjYWxsYmFjayhpdGVtSW5BcnJheSkpIHtcbiAgICAgIHJlc3VsdHMucHVzaChpdGVtSW5BcnJheSk7XG4gICAgfVxuICB9KTtcblxuICByZXR1cm4gcmVzdWx0cztcbn0iLCIvKlxuKiBodHRwczovL2RldmVsb3Blci5tb3ppbGxhLm9yZy9lbi1VUy9kb2NzL1dlYi9BUEkvQ2xvc2VFdmVudFxuKi9cblwidXNlIHN0cmljdFwiO1xuXG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgXCJfX2VzTW9kdWxlXCIsIHtcbiAgdmFsdWU6IHRydWVcbn0pO1xudmFyIGNvZGVzID0ge1xuICBDTE9TRV9OT1JNQUw6IDEwMDAsXG4gIENMT1NFX0dPSU5HX0FXQVk6IDEwMDEsXG4gIENMT1NFX1BST1RPQ09MX0VSUk9SOiAxMDAyLFxuICBDTE9TRV9VTlNVUFBPUlRFRDogMTAwMyxcbiAgQ0xPU0VfTk9fU1RBVFVTOiAxMDA1LFxuICBDTE9TRV9BQk5PUk1BTDogMTAwNixcbiAgQ0xPU0VfVE9PX0xBUkdFOiAxMDA5XG59O1xuXG5leHBvcnRzW1wiZGVmYXVsdFwiXSA9IGNvZGVzO1xubW9kdWxlLmV4cG9ydHMgPSBleHBvcnRzW1wiZGVmYXVsdFwiXTsiLCIndXNlIHN0cmljdCc7XG5cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCAnX19lc01vZHVsZScsIHtcbiAgdmFsdWU6IHRydWVcbn0pO1xuXG52YXIgX2dldCA9IGZ1bmN0aW9uIGdldChfeDIsIF94MywgX3g0KSB7IHZhciBfYWdhaW4gPSB0cnVlOyBfZnVuY3Rpb246IHdoaWxlIChfYWdhaW4pIHsgdmFyIG9iamVjdCA9IF94MiwgcHJvcGVydHkgPSBfeDMsIHJlY2VpdmVyID0gX3g0OyBfYWdhaW4gPSBmYWxzZTsgaWYgKG9iamVjdCA9PT0gbnVsbCkgb2JqZWN0ID0gRnVuY3Rpb24ucHJvdG90eXBlOyB2YXIgZGVzYyA9IE9iamVjdC5nZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3Iob2JqZWN0LCBwcm9wZXJ0eSk7IGlmIChkZXNjID09PSB1bmRlZmluZWQpIHsgdmFyIHBhcmVudCA9IE9iamVjdC5nZXRQcm90b3R5cGVPZihvYmplY3QpOyBpZiAocGFyZW50ID09PSBudWxsKSB7IHJldHVybiB1bmRlZmluZWQ7IH0gZWxzZSB7IF94MiA9IHBhcmVudDsgX3gzID0gcHJvcGVydHk7IF94NCA9IHJlY2VpdmVyOyBfYWdhaW4gPSB0cnVlOyBkZXNjID0gcGFyZW50ID0gdW5kZWZpbmVkOyBjb250aW51ZSBfZnVuY3Rpb247IH0gfSBlbHNlIGlmICgndmFsdWUnIGluIGRlc2MpIHsgcmV0dXJuIGRlc2MudmFsdWU7IH0gZWxzZSB7IHZhciBnZXR0ZXIgPSBkZXNjLmdldDsgaWYgKGdldHRlciA9PT0gdW5kZWZpbmVkKSB7IHJldHVybiB1bmRlZmluZWQ7IH0gcmV0dXJuIGdldHRlci5jYWxsKHJlY2VpdmVyKTsgfSB9IH07XG5cbmZ1bmN0aW9uIF9pbnRlcm9wUmVxdWlyZURlZmF1bHQob2JqKSB7IHJldHVybiBvYmogJiYgb2JqLl9fZXNNb2R1bGUgPyBvYmogOiB7ICdkZWZhdWx0Jzogb2JqIH07IH1cblxuZnVuY3Rpb24gX2NsYXNzQ2FsbENoZWNrKGluc3RhbmNlLCBDb25zdHJ1Y3RvcikgeyBpZiAoIShpbnN0YW5jZSBpbnN0YW5jZW9mIENvbnN0cnVjdG9yKSkgeyB0aHJvdyBuZXcgVHlwZUVycm9yKCdDYW5ub3QgY2FsbCBhIGNsYXNzIGFzIGEgZnVuY3Rpb24nKTsgfSB9XG5cbmZ1bmN0aW9uIF9pbmhlcml0cyhzdWJDbGFzcywgc3VwZXJDbGFzcykgeyBpZiAodHlwZW9mIHN1cGVyQ2xhc3MgIT09ICdmdW5jdGlvbicgJiYgc3VwZXJDbGFzcyAhPT0gbnVsbCkgeyB0aHJvdyBuZXcgVHlwZUVycm9yKCdTdXBlciBleHByZXNzaW9uIG11c3QgZWl0aGVyIGJlIG51bGwgb3IgYSBmdW5jdGlvbiwgbm90ICcgKyB0eXBlb2Ygc3VwZXJDbGFzcyk7IH0gc3ViQ2xhc3MucHJvdG90eXBlID0gT2JqZWN0LmNyZWF0ZShzdXBlckNsYXNzICYmIHN1cGVyQ2xhc3MucHJvdG90eXBlLCB7IGNvbnN0cnVjdG9yOiB7IHZhbHVlOiBzdWJDbGFzcywgZW51bWVyYWJsZTogZmFsc2UsIHdyaXRhYmxlOiB0cnVlLCBjb25maWd1cmFibGU6IHRydWUgfSB9KTsgaWYgKHN1cGVyQ2xhc3MpIE9iamVjdC5zZXRQcm90b3R5cGVPZiA/IE9iamVjdC5zZXRQcm90b3R5cGVPZihzdWJDbGFzcywgc3VwZXJDbGFzcykgOiBzdWJDbGFzcy5fX3Byb3RvX18gPSBzdXBlckNsYXNzOyB9XG5cbnZhciBfZXZlbnRQcm90b3R5cGUgPSByZXF1aXJlKCcuL2V2ZW50LXByb3RvdHlwZScpO1xuXG52YXIgX2V2ZW50UHJvdG90eXBlMiA9IF9pbnRlcm9wUmVxdWlyZURlZmF1bHQoX2V2ZW50UHJvdG90eXBlKTtcblxudmFyIENsb3NlRXZlbnQgPSAoZnVuY3Rpb24gKF9FdmVudFByb3RvdHlwZSkge1xuICBfaW5oZXJpdHMoQ2xvc2VFdmVudCwgX0V2ZW50UHJvdG90eXBlKTtcblxuICBmdW5jdGlvbiBDbG9zZUV2ZW50KHR5cGUpIHtcbiAgICB2YXIgZXZlbnRJbml0Q29uZmlnID0gYXJndW1lbnRzLmxlbmd0aCA8PSAxIHx8IGFyZ3VtZW50c1sxXSA9PT0gdW5kZWZpbmVkID8ge30gOiBhcmd1bWVudHNbMV07XG5cbiAgICBfY2xhc3NDYWxsQ2hlY2sodGhpcywgQ2xvc2VFdmVudCk7XG5cbiAgICBfZ2V0KE9iamVjdC5nZXRQcm90b3R5cGVPZihDbG9zZUV2ZW50LnByb3RvdHlwZSksICdjb25zdHJ1Y3RvcicsIHRoaXMpLmNhbGwodGhpcyk7XG5cbiAgICBpZiAoIXR5cGUpIHtcbiAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ0ZhaWxlZCB0byBjb25zdHJ1Y3QgXFwnQ2xvc2VFdmVudFxcJzogMSBhcmd1bWVudCByZXF1aXJlZCwgYnV0IG9ubHkgMCBwcmVzZW50LicpO1xuICAgIH1cblxuICAgIGlmICh0eXBlb2YgZXZlbnRJbml0Q29uZmlnICE9PSAnb2JqZWN0Jykge1xuICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignRmFpbGVkIHRvIGNvbnN0cnVjdCBcXCdDbG9zZUV2ZW50XFwnOiBwYXJhbWV0ZXIgMiAoXFwnZXZlbnRJbml0RGljdFxcJykgaXMgbm90IGFuIG9iamVjdCcpO1xuICAgIH1cblxuICAgIHZhciBidWJibGVzID0gZXZlbnRJbml0Q29uZmlnLmJ1YmJsZXM7XG4gICAgdmFyIGNhbmNlbGFibGUgPSBldmVudEluaXRDb25maWcuY2FuY2VsYWJsZTtcbiAgICB2YXIgY29kZSA9IGV2ZW50SW5pdENvbmZpZy5jb2RlO1xuICAgIHZhciByZWFzb24gPSBldmVudEluaXRDb25maWcucmVhc29uO1xuICAgIHZhciB3YXNDbGVhbiA9IGV2ZW50SW5pdENvbmZpZy53YXNDbGVhbjtcblxuICAgIHRoaXMudHlwZSA9IFN0cmluZyh0eXBlKTtcbiAgICB0aGlzLnRpbWVTdGFtcCA9IERhdGUubm93KCk7XG4gICAgdGhpcy50YXJnZXQgPSBudWxsO1xuICAgIHRoaXMuc3JjRWxlbWVudCA9IG51bGw7XG4gICAgdGhpcy5yZXR1cm5WYWx1ZSA9IHRydWU7XG4gICAgdGhpcy5pc1RydXN0ZWQgPSBmYWxzZTtcbiAgICB0aGlzLmV2ZW50UGhhc2UgPSAwO1xuICAgIHRoaXMuZGVmYXVsdFByZXZlbnRlZCA9IGZhbHNlO1xuICAgIHRoaXMuY3VycmVudFRhcmdldCA9IG51bGw7XG4gICAgdGhpcy5jYW5jZWxhYmxlID0gY2FuY2VsYWJsZSA/IEJvb2xlYW4oY2FuY2VsYWJsZSkgOiBmYWxzZTtcbiAgICB0aGlzLmNhbm5jZWxCdWJibGUgPSBmYWxzZTtcbiAgICB0aGlzLmJ1YmJsZXMgPSBidWJibGVzID8gQm9vbGVhbihidWJibGVzKSA6IGZhbHNlO1xuICAgIHRoaXMuY29kZSA9IHR5cGVvZiBjb2RlID09PSAnbnVtYmVyJyA/IE51bWJlcihjb2RlKSA6IDA7XG4gICAgdGhpcy5yZWFzb24gPSByZWFzb24gPyBTdHJpbmcocmVhc29uKSA6ICcnO1xuICAgIHRoaXMud2FzQ2xlYW4gPSB3YXNDbGVhbiA/IEJvb2xlYW4od2FzQ2xlYW4pIDogZmFsc2U7XG4gIH1cblxuICByZXR1cm4gQ2xvc2VFdmVudDtcbn0pKF9ldmVudFByb3RvdHlwZTJbJ2RlZmF1bHQnXSk7XG5cbmV4cG9ydHNbJ2RlZmF1bHQnXSA9IENsb3NlRXZlbnQ7XG5tb2R1bGUuZXhwb3J0cyA9IGV4cG9ydHNbJ2RlZmF1bHQnXTsiLCIvKlxuKiBUaGlzIGRlbGF5IGFsbG93cyB0aGUgdGhyZWFkIHRvIGZpbmlzaCBhc3NpZ25pbmcgaXRzIG9uKiBtZXRob2RzXG4qIGJlZm9yZSBpbnZva2luZyB0aGUgZGVsYXkgY2FsbGJhY2suIFRoaXMgaXMgcHVyZWx5IGEgdGltaW5nIGhhY2suXG4qIGh0dHA6Ly9nZWVrYWJ5dGUuYmxvZ3Nwb3QuY29tLzIwMTQvMDEvamF2YXNjcmlwdC1lZmZlY3Qtb2Ytc2V0dGluZy1zZXR0aW1lb3V0Lmh0bWxcbipcbiogQHBhcmFtIHtjYWxsYmFjazogZnVuY3Rpb259IHRoZSBjYWxsYmFjayB3aGljaCB3aWxsIGJlIGludm9rZWQgYWZ0ZXIgdGhlIHRpbWVvdXRcbiogQHBhcm1hIHtjb250ZXh0OiBvYmplY3R9IHRoZSBjb250ZXh0IGluIHdoaWNoIHRvIGludm9rZSB0aGUgZnVuY3Rpb25cbiovXG5cInVzZSBzdHJpY3RcIjtcblxuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7XG4gIHZhbHVlOiB0cnVlXG59KTtcbmZ1bmN0aW9uIGRlbGF5KGNhbGxiYWNrLCBjb250ZXh0KSB7XG4gIHNldFRpbWVvdXQoZnVuY3Rpb24gdGltZW91dCh0aW1lb3V0Q29udGV4dCkge1xuICAgIGNhbGxiYWNrLmNhbGwodGltZW91dENvbnRleHQpO1xuICB9LCA0LCBjb250ZXh0KTtcbn1cblxuZXhwb3J0c1tcImRlZmF1bHRcIl0gPSBkZWxheTtcbm1vZHVsZS5leHBvcnRzID0gZXhwb3J0c1tcImRlZmF1bHRcIl07IiwiJ3VzZSBzdHJpY3QnO1xuXG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgJ19fZXNNb2R1bGUnLCB7XG4gIHZhbHVlOiB0cnVlXG59KTtcblxudmFyIF9jcmVhdGVDbGFzcyA9IChmdW5jdGlvbiAoKSB7IGZ1bmN0aW9uIGRlZmluZVByb3BlcnRpZXModGFyZ2V0LCBwcm9wcykgeyBmb3IgKHZhciBpID0gMDsgaSA8IHByb3BzLmxlbmd0aDsgaSsrKSB7IHZhciBkZXNjcmlwdG9yID0gcHJvcHNbaV07IGRlc2NyaXB0b3IuZW51bWVyYWJsZSA9IGRlc2NyaXB0b3IuZW51bWVyYWJsZSB8fCBmYWxzZTsgZGVzY3JpcHRvci5jb25maWd1cmFibGUgPSB0cnVlOyBpZiAoJ3ZhbHVlJyBpbiBkZXNjcmlwdG9yKSBkZXNjcmlwdG9yLndyaXRhYmxlID0gdHJ1ZTsgT2JqZWN0LmRlZmluZVByb3BlcnR5KHRhcmdldCwgZGVzY3JpcHRvci5rZXksIGRlc2NyaXB0b3IpOyB9IH0gcmV0dXJuIGZ1bmN0aW9uIChDb25zdHJ1Y3RvciwgcHJvdG9Qcm9wcywgc3RhdGljUHJvcHMpIHsgaWYgKHByb3RvUHJvcHMpIGRlZmluZVByb3BlcnRpZXMoQ29uc3RydWN0b3IucHJvdG90eXBlLCBwcm90b1Byb3BzKTsgaWYgKHN0YXRpY1Byb3BzKSBkZWZpbmVQcm9wZXJ0aWVzKENvbnN0cnVjdG9yLCBzdGF0aWNQcm9wcyk7IHJldHVybiBDb25zdHJ1Y3RvcjsgfTsgfSkoKTtcblxuZnVuY3Rpb24gX2NsYXNzQ2FsbENoZWNrKGluc3RhbmNlLCBDb25zdHJ1Y3RvcikgeyBpZiAoIShpbnN0YW5jZSBpbnN0YW5jZW9mIENvbnN0cnVjdG9yKSkgeyB0aHJvdyBuZXcgVHlwZUVycm9yKCdDYW5ub3QgY2FsbCBhIGNsYXNzIGFzIGEgZnVuY3Rpb24nKTsgfSB9XG5cbnZhciBFdmVudFByb3RvdHlwZSA9IChmdW5jdGlvbiAoKSB7XG4gIGZ1bmN0aW9uIEV2ZW50UHJvdG90eXBlKCkge1xuICAgIF9jbGFzc0NhbGxDaGVjayh0aGlzLCBFdmVudFByb3RvdHlwZSk7XG4gIH1cblxuICBfY3JlYXRlQ2xhc3MoRXZlbnRQcm90b3R5cGUsIFt7XG4gICAga2V5OiAnc3RvcFByb3BhZ2F0aW9uJyxcblxuICAgIC8vIE5vb3BzXG4gICAgdmFsdWU6IGZ1bmN0aW9uIHN0b3BQcm9wYWdhdGlvbigpIHt9XG4gIH0sIHtcbiAgICBrZXk6ICdzdG9wSW1tZWRpYXRlUHJvcGFnYXRpb24nLFxuICAgIHZhbHVlOiBmdW5jdGlvbiBzdG9wSW1tZWRpYXRlUHJvcGFnYXRpb24oKSB7fVxuXG4gICAgLy8gaWYgbm8gYXJndW1lbnRzIGFyZSBwYXNzZWQgdGhlbiB0aGUgdHlwZSBpcyBzZXQgdG8gXCJ1bmRlZmluZWRcIiBvblxuICAgIC8vIGNocm9tZSBhbmQgc2FmYXJpLlxuICB9LCB7XG4gICAga2V5OiAnaW5pdEV2ZW50JyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gaW5pdEV2ZW50KCkge1xuICAgICAgdmFyIHR5cGUgPSBhcmd1bWVudHMubGVuZ3RoIDw9IDAgfHwgYXJndW1lbnRzWzBdID09PSB1bmRlZmluZWQgPyAndW5kZWZpbmVkJyA6IGFyZ3VtZW50c1swXTtcbiAgICAgIHZhciBidWJibGVzID0gYXJndW1lbnRzLmxlbmd0aCA8PSAxIHx8IGFyZ3VtZW50c1sxXSA9PT0gdW5kZWZpbmVkID8gZmFsc2UgOiBhcmd1bWVudHNbMV07XG4gICAgICB2YXIgY2FuY2VsYWJsZSA9IGFyZ3VtZW50cy5sZW5ndGggPD0gMiB8fCBhcmd1bWVudHNbMl0gPT09IHVuZGVmaW5lZCA/IGZhbHNlIDogYXJndW1lbnRzWzJdO1xuXG4gICAgICBPYmplY3QuYXNzaWduKHRoaXMsIHtcbiAgICAgICAgdHlwZTogU3RyaW5nKHR5cGUpLFxuICAgICAgICBidWJibGVzOiBCb29sZWFuKGJ1YmJsZXMpLFxuICAgICAgICBjYW5jZWxhYmxlOiBCb29sZWFuKGNhbmNlbGFibGUpXG4gICAgICB9KTtcbiAgICB9XG4gIH1dKTtcblxuICByZXR1cm4gRXZlbnRQcm90b3R5cGU7XG59KSgpO1xuXG5leHBvcnRzWydkZWZhdWx0J10gPSBFdmVudFByb3RvdHlwZTtcbm1vZHVsZS5leHBvcnRzID0gZXhwb3J0c1snZGVmYXVsdCddOyIsIid1c2Ugc3RyaWN0JztcblxuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsICdfX2VzTW9kdWxlJywge1xuICB2YWx1ZTogdHJ1ZVxufSk7XG5cbnZhciBfZ2V0ID0gZnVuY3Rpb24gZ2V0KF94MiwgX3gzLCBfeDQpIHsgdmFyIF9hZ2FpbiA9IHRydWU7IF9mdW5jdGlvbjogd2hpbGUgKF9hZ2FpbikgeyB2YXIgb2JqZWN0ID0gX3gyLCBwcm9wZXJ0eSA9IF94MywgcmVjZWl2ZXIgPSBfeDQ7IF9hZ2FpbiA9IGZhbHNlOyBpZiAob2JqZWN0ID09PSBudWxsKSBvYmplY3QgPSBGdW5jdGlvbi5wcm90b3R5cGU7IHZhciBkZXNjID0gT2JqZWN0LmdldE93blByb3BlcnR5RGVzY3JpcHRvcihvYmplY3QsIHByb3BlcnR5KTsgaWYgKGRlc2MgPT09IHVuZGVmaW5lZCkgeyB2YXIgcGFyZW50ID0gT2JqZWN0LmdldFByb3RvdHlwZU9mKG9iamVjdCk7IGlmIChwYXJlbnQgPT09IG51bGwpIHsgcmV0dXJuIHVuZGVmaW5lZDsgfSBlbHNlIHsgX3gyID0gcGFyZW50OyBfeDMgPSBwcm9wZXJ0eTsgX3g0ID0gcmVjZWl2ZXI7IF9hZ2FpbiA9IHRydWU7IGRlc2MgPSBwYXJlbnQgPSB1bmRlZmluZWQ7IGNvbnRpbnVlIF9mdW5jdGlvbjsgfSB9IGVsc2UgaWYgKCd2YWx1ZScgaW4gZGVzYykgeyByZXR1cm4gZGVzYy52YWx1ZTsgfSBlbHNlIHsgdmFyIGdldHRlciA9IGRlc2MuZ2V0OyBpZiAoZ2V0dGVyID09PSB1bmRlZmluZWQpIHsgcmV0dXJuIHVuZGVmaW5lZDsgfSByZXR1cm4gZ2V0dGVyLmNhbGwocmVjZWl2ZXIpOyB9IH0gfTtcblxuZnVuY3Rpb24gX2ludGVyb3BSZXF1aXJlRGVmYXVsdChvYmopIHsgcmV0dXJuIG9iaiAmJiBvYmouX19lc01vZHVsZSA/IG9iaiA6IHsgJ2RlZmF1bHQnOiBvYmogfTsgfVxuXG5mdW5jdGlvbiBfY2xhc3NDYWxsQ2hlY2soaW5zdGFuY2UsIENvbnN0cnVjdG9yKSB7IGlmICghKGluc3RhbmNlIGluc3RhbmNlb2YgQ29uc3RydWN0b3IpKSB7IHRocm93IG5ldyBUeXBlRXJyb3IoJ0Nhbm5vdCBjYWxsIGEgY2xhc3MgYXMgYSBmdW5jdGlvbicpOyB9IH1cblxuZnVuY3Rpb24gX2luaGVyaXRzKHN1YkNsYXNzLCBzdXBlckNsYXNzKSB7IGlmICh0eXBlb2Ygc3VwZXJDbGFzcyAhPT0gJ2Z1bmN0aW9uJyAmJiBzdXBlckNsYXNzICE9PSBudWxsKSB7IHRocm93IG5ldyBUeXBlRXJyb3IoJ1N1cGVyIGV4cHJlc3Npb24gbXVzdCBlaXRoZXIgYmUgbnVsbCBvciBhIGZ1bmN0aW9uLCBub3QgJyArIHR5cGVvZiBzdXBlckNsYXNzKTsgfSBzdWJDbGFzcy5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKHN1cGVyQ2xhc3MgJiYgc3VwZXJDbGFzcy5wcm90b3R5cGUsIHsgY29uc3RydWN0b3I6IHsgdmFsdWU6IHN1YkNsYXNzLCBlbnVtZXJhYmxlOiBmYWxzZSwgd3JpdGFibGU6IHRydWUsIGNvbmZpZ3VyYWJsZTogdHJ1ZSB9IH0pOyBpZiAoc3VwZXJDbGFzcykgT2JqZWN0LnNldFByb3RvdHlwZU9mID8gT2JqZWN0LnNldFByb3RvdHlwZU9mKHN1YkNsYXNzLCBzdXBlckNsYXNzKSA6IHN1YkNsYXNzLl9fcHJvdG9fXyA9IHN1cGVyQ2xhc3M7IH1cblxudmFyIF9ldmVudFByb3RvdHlwZSA9IHJlcXVpcmUoJy4vZXZlbnQtcHJvdG90eXBlJyk7XG5cbnZhciBfZXZlbnRQcm90b3R5cGUyID0gX2ludGVyb3BSZXF1aXJlRGVmYXVsdChfZXZlbnRQcm90b3R5cGUpO1xuXG52YXIgRXZlbnQgPSAoZnVuY3Rpb24gKF9FdmVudFByb3RvdHlwZSkge1xuICBfaW5oZXJpdHMoRXZlbnQsIF9FdmVudFByb3RvdHlwZSk7XG5cbiAgZnVuY3Rpb24gRXZlbnQodHlwZSkge1xuICAgIHZhciBldmVudEluaXRDb25maWcgPSBhcmd1bWVudHMubGVuZ3RoIDw9IDEgfHwgYXJndW1lbnRzWzFdID09PSB1bmRlZmluZWQgPyB7fSA6IGFyZ3VtZW50c1sxXTtcblxuICAgIF9jbGFzc0NhbGxDaGVjayh0aGlzLCBFdmVudCk7XG5cbiAgICBfZ2V0KE9iamVjdC5nZXRQcm90b3R5cGVPZihFdmVudC5wcm90b3R5cGUpLCAnY29uc3RydWN0b3InLCB0aGlzKS5jYWxsKHRoaXMpO1xuXG4gICAgaWYgKCF0eXBlKSB7XG4gICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdGYWlsZWQgdG8gY29uc3RydWN0IFxcJ0V2ZW50XFwnOiAxIGFyZ3VtZW50IHJlcXVpcmVkLCBidXQgb25seSAwIHByZXNlbnQuJyk7XG4gICAgfVxuXG4gICAgaWYgKHR5cGVvZiBldmVudEluaXRDb25maWcgIT09ICdvYmplY3QnKSB7XG4gICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdGYWlsZWQgdG8gY29uc3RydWN0IFxcJ0V2ZW50XFwnOiBwYXJhbWV0ZXIgMiAoXFwnZXZlbnRJbml0RGljdFxcJykgaXMgbm90IGFuIG9iamVjdCcpO1xuICAgIH1cblxuICAgIHZhciBidWJibGVzID0gZXZlbnRJbml0Q29uZmlnLmJ1YmJsZXM7XG4gICAgdmFyIGNhbmNlbGFibGUgPSBldmVudEluaXRDb25maWcuY2FuY2VsYWJsZTtcblxuICAgIHRoaXMudHlwZSA9IFN0cmluZyh0eXBlKTtcbiAgICB0aGlzLnRpbWVTdGFtcCA9IERhdGUubm93KCk7XG4gICAgdGhpcy50YXJnZXQgPSBudWxsO1xuICAgIHRoaXMuc3JjRWxlbWVudCA9IG51bGw7XG4gICAgdGhpcy5yZXR1cm5WYWx1ZSA9IHRydWU7XG4gICAgdGhpcy5pc1RydXN0ZWQgPSBmYWxzZTtcbiAgICB0aGlzLmV2ZW50UGhhc2UgPSAwO1xuICAgIHRoaXMuZGVmYXVsdFByZXZlbnRlZCA9IGZhbHNlO1xuICAgIHRoaXMuY3VycmVudFRhcmdldCA9IG51bGw7XG4gICAgdGhpcy5jYW5jZWxhYmxlID0gY2FuY2VsYWJsZSA/IEJvb2xlYW4oY2FuY2VsYWJsZSkgOiBmYWxzZTtcbiAgICB0aGlzLmNhbm5jZWxCdWJibGUgPSBmYWxzZTtcbiAgICB0aGlzLmJ1YmJsZXMgPSBidWJibGVzID8gQm9vbGVhbihidWJibGVzKSA6IGZhbHNlO1xuICB9XG5cbiAgcmV0dXJuIEV2ZW50O1xufSkoX2V2ZW50UHJvdG90eXBlMlsnZGVmYXVsdCddKTtcblxuZXhwb3J0c1snZGVmYXVsdCddID0gRXZlbnQ7XG5tb2R1bGUuZXhwb3J0cyA9IGV4cG9ydHNbJ2RlZmF1bHQnXTsiLCIndXNlIHN0cmljdCc7XG5cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCAnX19lc01vZHVsZScsIHtcbiAgdmFsdWU6IHRydWVcbn0pO1xuXG52YXIgX2dldCA9IGZ1bmN0aW9uIGdldChfeDIsIF94MywgX3g0KSB7IHZhciBfYWdhaW4gPSB0cnVlOyBfZnVuY3Rpb246IHdoaWxlIChfYWdhaW4pIHsgdmFyIG9iamVjdCA9IF94MiwgcHJvcGVydHkgPSBfeDMsIHJlY2VpdmVyID0gX3g0OyBfYWdhaW4gPSBmYWxzZTsgaWYgKG9iamVjdCA9PT0gbnVsbCkgb2JqZWN0ID0gRnVuY3Rpb24ucHJvdG90eXBlOyB2YXIgZGVzYyA9IE9iamVjdC5nZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3Iob2JqZWN0LCBwcm9wZXJ0eSk7IGlmIChkZXNjID09PSB1bmRlZmluZWQpIHsgdmFyIHBhcmVudCA9IE9iamVjdC5nZXRQcm90b3R5cGVPZihvYmplY3QpOyBpZiAocGFyZW50ID09PSBudWxsKSB7IHJldHVybiB1bmRlZmluZWQ7IH0gZWxzZSB7IF94MiA9IHBhcmVudDsgX3gzID0gcHJvcGVydHk7IF94NCA9IHJlY2VpdmVyOyBfYWdhaW4gPSB0cnVlOyBkZXNjID0gcGFyZW50ID0gdW5kZWZpbmVkOyBjb250aW51ZSBfZnVuY3Rpb247IH0gfSBlbHNlIGlmICgndmFsdWUnIGluIGRlc2MpIHsgcmV0dXJuIGRlc2MudmFsdWU7IH0gZWxzZSB7IHZhciBnZXR0ZXIgPSBkZXNjLmdldDsgaWYgKGdldHRlciA9PT0gdW5kZWZpbmVkKSB7IHJldHVybiB1bmRlZmluZWQ7IH0gcmV0dXJuIGdldHRlci5jYWxsKHJlY2VpdmVyKTsgfSB9IH07XG5cbmZ1bmN0aW9uIF9pbnRlcm9wUmVxdWlyZURlZmF1bHQob2JqKSB7IHJldHVybiBvYmogJiYgb2JqLl9fZXNNb2R1bGUgPyBvYmogOiB7ICdkZWZhdWx0Jzogb2JqIH07IH1cblxuZnVuY3Rpb24gX2NsYXNzQ2FsbENoZWNrKGluc3RhbmNlLCBDb25zdHJ1Y3RvcikgeyBpZiAoIShpbnN0YW5jZSBpbnN0YW5jZW9mIENvbnN0cnVjdG9yKSkgeyB0aHJvdyBuZXcgVHlwZUVycm9yKCdDYW5ub3QgY2FsbCBhIGNsYXNzIGFzIGEgZnVuY3Rpb24nKTsgfSB9XG5cbmZ1bmN0aW9uIF9pbmhlcml0cyhzdWJDbGFzcywgc3VwZXJDbGFzcykgeyBpZiAodHlwZW9mIHN1cGVyQ2xhc3MgIT09ICdmdW5jdGlvbicgJiYgc3VwZXJDbGFzcyAhPT0gbnVsbCkgeyB0aHJvdyBuZXcgVHlwZUVycm9yKCdTdXBlciBleHByZXNzaW9uIG11c3QgZWl0aGVyIGJlIG51bGwgb3IgYSBmdW5jdGlvbiwgbm90ICcgKyB0eXBlb2Ygc3VwZXJDbGFzcyk7IH0gc3ViQ2xhc3MucHJvdG90eXBlID0gT2JqZWN0LmNyZWF0ZShzdXBlckNsYXNzICYmIHN1cGVyQ2xhc3MucHJvdG90eXBlLCB7IGNvbnN0cnVjdG9yOiB7IHZhbHVlOiBzdWJDbGFzcywgZW51bWVyYWJsZTogZmFsc2UsIHdyaXRhYmxlOiB0cnVlLCBjb25maWd1cmFibGU6IHRydWUgfSB9KTsgaWYgKHN1cGVyQ2xhc3MpIE9iamVjdC5zZXRQcm90b3R5cGVPZiA/IE9iamVjdC5zZXRQcm90b3R5cGVPZihzdWJDbGFzcywgc3VwZXJDbGFzcykgOiBzdWJDbGFzcy5fX3Byb3RvX18gPSBzdXBlckNsYXNzOyB9XG5cbnZhciBfZXZlbnRQcm90b3R5cGUgPSByZXF1aXJlKCcuL2V2ZW50LXByb3RvdHlwZScpO1xuXG52YXIgX2V2ZW50UHJvdG90eXBlMiA9IF9pbnRlcm9wUmVxdWlyZURlZmF1bHQoX2V2ZW50UHJvdG90eXBlKTtcblxudmFyIE1lc3NhZ2VFdmVudCA9IChmdW5jdGlvbiAoX0V2ZW50UHJvdG90eXBlKSB7XG4gIF9pbmhlcml0cyhNZXNzYWdlRXZlbnQsIF9FdmVudFByb3RvdHlwZSk7XG5cbiAgZnVuY3Rpb24gTWVzc2FnZUV2ZW50KHR5cGUpIHtcbiAgICB2YXIgZXZlbnRJbml0Q29uZmlnID0gYXJndW1lbnRzLmxlbmd0aCA8PSAxIHx8IGFyZ3VtZW50c1sxXSA9PT0gdW5kZWZpbmVkID8ge30gOiBhcmd1bWVudHNbMV07XG5cbiAgICBfY2xhc3NDYWxsQ2hlY2sodGhpcywgTWVzc2FnZUV2ZW50KTtcblxuICAgIF9nZXQoT2JqZWN0LmdldFByb3RvdHlwZU9mKE1lc3NhZ2VFdmVudC5wcm90b3R5cGUpLCAnY29uc3RydWN0b3InLCB0aGlzKS5jYWxsKHRoaXMpO1xuXG4gICAgaWYgKCF0eXBlKSB7XG4gICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdGYWlsZWQgdG8gY29uc3RydWN0IFxcJ01lc3NhZ2VFdmVudFxcJzogMSBhcmd1bWVudCByZXF1aXJlZCwgYnV0IG9ubHkgMCBwcmVzZW50LicpO1xuICAgIH1cblxuICAgIGlmICh0eXBlb2YgZXZlbnRJbml0Q29uZmlnICE9PSAnb2JqZWN0Jykge1xuICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignRmFpbGVkIHRvIGNvbnN0cnVjdCBcXCdNZXNzYWdlRXZlbnRcXCc6IHBhcmFtZXRlciAyIChcXCdldmVudEluaXREaWN0XFwnKSBpcyBub3QgYW4gb2JqZWN0Jyk7XG4gICAgfVxuXG4gICAgdmFyIGJ1YmJsZXMgPSBldmVudEluaXRDb25maWcuYnViYmxlcztcbiAgICB2YXIgY2FuY2VsYWJsZSA9IGV2ZW50SW5pdENvbmZpZy5jYW5jZWxhYmxlO1xuICAgIHZhciBkYXRhID0gZXZlbnRJbml0Q29uZmlnLmRhdGE7XG4gICAgdmFyIG9yaWdpbiA9IGV2ZW50SW5pdENvbmZpZy5vcmlnaW47XG4gICAgdmFyIGxhc3RFdmVudElkID0gZXZlbnRJbml0Q29uZmlnLmxhc3RFdmVudElkO1xuICAgIHZhciBwb3J0cyA9IGV2ZW50SW5pdENvbmZpZy5wb3J0cztcblxuICAgIHRoaXMudHlwZSA9IFN0cmluZyh0eXBlKTtcbiAgICB0aGlzLnRpbWVTdGFtcCA9IERhdGUubm93KCk7XG4gICAgdGhpcy50YXJnZXQgPSBudWxsO1xuICAgIHRoaXMuc3JjRWxlbWVudCA9IG51bGw7XG4gICAgdGhpcy5yZXR1cm5WYWx1ZSA9IHRydWU7XG4gICAgdGhpcy5pc1RydXN0ZWQgPSBmYWxzZTtcbiAgICB0aGlzLmV2ZW50UGhhc2UgPSAwO1xuICAgIHRoaXMuZGVmYXVsdFByZXZlbnRlZCA9IGZhbHNlO1xuICAgIHRoaXMuY3VycmVudFRhcmdldCA9IG51bGw7XG4gICAgdGhpcy5jYW5jZWxhYmxlID0gY2FuY2VsYWJsZSA/IEJvb2xlYW4oY2FuY2VsYWJsZSkgOiBmYWxzZTtcbiAgICB0aGlzLmNhbm5jZWxCdWJibGUgPSBmYWxzZTtcbiAgICB0aGlzLmJ1YmJsZXMgPSBidWJibGVzID8gQm9vbGVhbihidWJibGVzKSA6IGZhbHNlO1xuICAgIHRoaXMub3JpZ2luID0gb3JpZ2luID8gU3RyaW5nKG9yaWdpbikgOiAnJztcbiAgICB0aGlzLnBvcnRzID0gdHlwZW9mIHBvcnRzID09PSAndW5kZWZpbmVkJyA/IG51bGwgOiBwb3J0cztcbiAgICB0aGlzLmRhdGEgPSB0eXBlb2YgZGF0YSA9PT0gJ3VuZGVmaW5lZCcgPyBudWxsIDogZGF0YTtcbiAgICB0aGlzLmxhc3RFdmVudElkID0gbGFzdEV2ZW50SWQgPyBTdHJpbmcobGFzdEV2ZW50SWQpIDogJyc7XG4gIH1cblxuICByZXR1cm4gTWVzc2FnZUV2ZW50O1xufSkoX2V2ZW50UHJvdG90eXBlMlsnZGVmYXVsdCddKTtcblxuZXhwb3J0c1snZGVmYXVsdCddID0gTWVzc2FnZUV2ZW50O1xubW9kdWxlLmV4cG9ydHMgPSBleHBvcnRzWydkZWZhdWx0J107IiwiJ3VzZSBzdHJpY3QnO1xuXG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgJ19fZXNNb2R1bGUnLCB7XG4gIHZhbHVlOiB0cnVlXG59KTtcblxuZnVuY3Rpb24gX2ludGVyb3BSZXF1aXJlRGVmYXVsdChvYmopIHsgcmV0dXJuIG9iaiAmJiBvYmouX19lc01vZHVsZSA/IG9iaiA6IHsgJ2RlZmF1bHQnOiBvYmogfTsgfVxuXG52YXIgX3NlcnZlciA9IHJlcXVpcmUoJy4vc2VydmVyJyk7XG5cbnZhciBfc2VydmVyMiA9IF9pbnRlcm9wUmVxdWlyZURlZmF1bHQoX3NlcnZlcik7XG5cbnZhciBfc29ja2V0SW8gPSByZXF1aXJlKCcuL3NvY2tldC1pbycpO1xuXG52YXIgX3NvY2tldElvMiA9IF9pbnRlcm9wUmVxdWlyZURlZmF1bHQoX3NvY2tldElvKTtcblxudmFyIF93ZWJzb2NrZXQgPSByZXF1aXJlKCcuL3dlYnNvY2tldCcpO1xuXG52YXIgX3dlYnNvY2tldDIgPSBfaW50ZXJvcFJlcXVpcmVEZWZhdWx0KF93ZWJzb2NrZXQpO1xuXG5pZiAodHlwZW9mIHdpbmRvdyAhPT0gJ3VuZGVmaW5lZCcpIHtcbiAgd2luZG93Lk1vY2tTZXJ2ZXIgPSBfc2VydmVyMlsnZGVmYXVsdCddO1xuICB3aW5kb3cuTW9ja1dlYlNvY2tldCA9IF93ZWJzb2NrZXQyWydkZWZhdWx0J107XG4gIHdpbmRvdy5Nb2NrU29ja2V0SU8gPSBfc29ja2V0SW8yWydkZWZhdWx0J107XG59XG5cbnZhciBTZXJ2ZXIgPSBfc2VydmVyMlsnZGVmYXVsdCddO1xuZXhwb3J0cy5TZXJ2ZXIgPSBTZXJ2ZXI7XG52YXIgV2ViU29ja2V0ID0gX3dlYnNvY2tldDJbJ2RlZmF1bHQnXTtcbmV4cG9ydHMuV2ViU29ja2V0ID0gV2ViU29ja2V0O1xudmFyIFNvY2tldElPID0gX3NvY2tldElvMlsnZGVmYXVsdCddO1xuZXhwb3J0cy5Tb2NrZXRJTyA9IFNvY2tldElPOyIsIid1c2Ugc3RyaWN0JztcblxuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsICdfX2VzTW9kdWxlJywge1xuICB2YWx1ZTogdHJ1ZVxufSk7XG5cbnZhciBfY3JlYXRlQ2xhc3MgPSAoZnVuY3Rpb24gKCkgeyBmdW5jdGlvbiBkZWZpbmVQcm9wZXJ0aWVzKHRhcmdldCwgcHJvcHMpIHsgZm9yICh2YXIgaSA9IDA7IGkgPCBwcm9wcy5sZW5ndGg7IGkrKykgeyB2YXIgZGVzY3JpcHRvciA9IHByb3BzW2ldOyBkZXNjcmlwdG9yLmVudW1lcmFibGUgPSBkZXNjcmlwdG9yLmVudW1lcmFibGUgfHwgZmFsc2U7IGRlc2NyaXB0b3IuY29uZmlndXJhYmxlID0gdHJ1ZTsgaWYgKCd2YWx1ZScgaW4gZGVzY3JpcHRvcikgZGVzY3JpcHRvci53cml0YWJsZSA9IHRydWU7IE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0YXJnZXQsIGRlc2NyaXB0b3Iua2V5LCBkZXNjcmlwdG9yKTsgfSB9IHJldHVybiBmdW5jdGlvbiAoQ29uc3RydWN0b3IsIHByb3RvUHJvcHMsIHN0YXRpY1Byb3BzKSB7IGlmIChwcm90b1Byb3BzKSBkZWZpbmVQcm9wZXJ0aWVzKENvbnN0cnVjdG9yLnByb3RvdHlwZSwgcHJvdG9Qcm9wcyk7IGlmIChzdGF0aWNQcm9wcykgZGVmaW5lUHJvcGVydGllcyhDb25zdHJ1Y3Rvciwgc3RhdGljUHJvcHMpOyByZXR1cm4gQ29uc3RydWN0b3I7IH07IH0pKCk7XG5cbmZ1bmN0aW9uIF9jbGFzc0NhbGxDaGVjayhpbnN0YW5jZSwgQ29uc3RydWN0b3IpIHsgaWYgKCEoaW5zdGFuY2UgaW5zdGFuY2VvZiBDb25zdHJ1Y3RvcikpIHsgdGhyb3cgbmV3IFR5cGVFcnJvcignQ2Fubm90IGNhbGwgYSBjbGFzcyBhcyBhIGZ1bmN0aW9uJyk7IH0gfVxuXG52YXIgX2hlbHBlcnNBcnJheUhlbHBlcnMgPSByZXF1aXJlKCcuL2hlbHBlcnMvYXJyYXktaGVscGVycycpO1xuXG4vKlxuKiBUaGUgbmV0d29yayBicmlkZ2UgaXMgYSB3YXkgZm9yIHRoZSBtb2NrIHdlYnNvY2tldCBvYmplY3QgdG8gJ2NvbW11bmljYXRlJyB3aXRoXG4qIGFsbCBhdmFsaWJsZSBzZXJ2ZXJzLiBUaGlzIGlzIGEgc2luZ2xldG9uIG9iamVjdCBzbyBpdCBpcyBpbXBvcnRhbnQgdGhhdCB5b3VcbiogY2xlYW4gdXAgdXJsTWFwIHdoZW5ldmVyIHlvdSBhcmUgZmluaXNoZWQuXG4qL1xuXG52YXIgTmV0d29ya0JyaWRnZSA9IChmdW5jdGlvbiAoKSB7XG4gIGZ1bmN0aW9uIE5ldHdvcmtCcmlkZ2UoKSB7XG4gICAgX2NsYXNzQ2FsbENoZWNrKHRoaXMsIE5ldHdvcmtCcmlkZ2UpO1xuXG4gICAgdGhpcy51cmxNYXAgPSB7fTtcbiAgfVxuXG4gIC8qXG4gICogQXR0YWNoZXMgYSB3ZWJzb2NrZXQgb2JqZWN0IHRvIHRoZSB1cmxNYXAgaGFzaCBzbyB0aGF0IGl0IGNhbiBmaW5kIHRoZSBzZXJ2ZXJcbiAgKiBpdCBpcyBjb25uZWN0ZWQgdG8gYW5kIHRoZSBzZXJ2ZXIgaW4gdHVybiBjYW4gZmluZCBpdC5cbiAgKlxuICAqIEBwYXJhbSB7b2JqZWN0fSB3ZWJzb2NrZXQgLSB3ZWJzb2NrZXQgb2JqZWN0IHRvIGFkZCB0byB0aGUgdXJsTWFwIGhhc2hcbiAgKiBAcGFyYW0ge3N0cmluZ30gdXJsXG4gICovXG5cbiAgX2NyZWF0ZUNsYXNzKE5ldHdvcmtCcmlkZ2UsIFt7XG4gICAga2V5OiAnYXR0YWNoV2ViU29ja2V0JyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gYXR0YWNoV2ViU29ja2V0KHdlYnNvY2tldCwgdXJsKSB7XG4gICAgICB2YXIgY29ubmVjdGlvbkxvb2t1cCA9IHRoaXMudXJsTWFwW3VybF07XG5cbiAgICAgIGlmIChjb25uZWN0aW9uTG9va3VwICYmIGNvbm5lY3Rpb25Mb29rdXAuc2VydmVyICYmIGNvbm5lY3Rpb25Mb29rdXAud2Vic29ja2V0cy5pbmRleE9mKHdlYnNvY2tldCkgPT09IC0xKSB7XG4gICAgICAgIGNvbm5lY3Rpb25Mb29rdXAud2Vic29ja2V0cy5wdXNoKHdlYnNvY2tldCk7XG4gICAgICAgIHJldHVybiBjb25uZWN0aW9uTG9va3VwLnNlcnZlcjtcbiAgICAgIH1cbiAgICB9XG5cbiAgICAvKlxuICAgICogQXR0YWNoZXMgYSB3ZWJzb2NrZXQgdG8gYSByb29tXG4gICAgKi9cbiAgfSwge1xuICAgIGtleTogJ2FkZE1lbWJlcnNoaXBUb1Jvb20nLFxuICAgIHZhbHVlOiBmdW5jdGlvbiBhZGRNZW1iZXJzaGlwVG9Sb29tKHdlYnNvY2tldCwgcm9vbSkge1xuICAgICAgdmFyIGNvbm5lY3Rpb25Mb29rdXAgPSB0aGlzLnVybE1hcFt3ZWJzb2NrZXQudXJsXTtcblxuICAgICAgaWYgKGNvbm5lY3Rpb25Mb29rdXAgJiYgY29ubmVjdGlvbkxvb2t1cC5zZXJ2ZXIgJiYgY29ubmVjdGlvbkxvb2t1cC53ZWJzb2NrZXRzLmluZGV4T2Yod2Vic29ja2V0KSAhPT0gLTEpIHtcbiAgICAgICAgaWYgKCFjb25uZWN0aW9uTG9va3VwLnJvb21NZW1iZXJzaGlwc1tyb29tXSkge1xuICAgICAgICAgIGNvbm5lY3Rpb25Mb29rdXAucm9vbU1lbWJlcnNoaXBzW3Jvb21dID0gW107XG4gICAgICAgIH1cblxuICAgICAgICBjb25uZWN0aW9uTG9va3VwLnJvb21NZW1iZXJzaGlwc1tyb29tXS5wdXNoKHdlYnNvY2tldCk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgLypcbiAgICAqIEF0dGFjaGVzIGEgc2VydmVyIG9iamVjdCB0byB0aGUgdXJsTWFwIGhhc2ggc28gdGhhdCBpdCBjYW4gZmluZCBhIHdlYnNvY2tldHNcbiAgICAqIHdoaWNoIGFyZSBjb25uZWN0ZWQgdG8gaXQgYW5kIHNvIHRoYXQgd2Vic29ja2V0cyBjYW4gaW4gdHVybiBjYW4gZmluZCBpdC5cbiAgICAqXG4gICAgKiBAcGFyYW0ge29iamVjdH0gc2VydmVyIC0gc2VydmVyIG9iamVjdCB0byBhZGQgdG8gdGhlIHVybE1hcCBoYXNoXG4gICAgKiBAcGFyYW0ge3N0cmluZ30gdXJsXG4gICAgKi9cbiAgfSwge1xuICAgIGtleTogJ2F0dGFjaFNlcnZlcicsXG4gICAgdmFsdWU6IGZ1bmN0aW9uIGF0dGFjaFNlcnZlcihzZXJ2ZXIsIHVybCkge1xuICAgICAgdmFyIGNvbm5lY3Rpb25Mb29rdXAgPSB0aGlzLnVybE1hcFt1cmxdO1xuXG4gICAgICBpZiAoIWNvbm5lY3Rpb25Mb29rdXApIHtcbiAgICAgICAgdGhpcy51cmxNYXBbdXJsXSA9IHtcbiAgICAgICAgICBzZXJ2ZXI6IHNlcnZlcixcbiAgICAgICAgICB3ZWJzb2NrZXRzOiBbXSxcbiAgICAgICAgICByb29tTWVtYmVyc2hpcHM6IHt9XG4gICAgICAgIH07XG5cbiAgICAgICAgcmV0dXJuIHNlcnZlcjtcbiAgICAgIH1cbiAgICB9XG5cbiAgICAvKlxuICAgICogRmluZHMgdGhlIHNlcnZlciB3aGljaCBpcyAncnVubmluZycgb24gdGhlIGdpdmVuIHVybC5cbiAgICAqXG4gICAgKiBAcGFyYW0ge3N0cmluZ30gdXJsIC0gdGhlIHVybCB0byB1c2UgdG8gZmluZCB3aGljaCBzZXJ2ZXIgaXMgcnVubmluZyBvbiBpdFxuICAgICovXG4gIH0sIHtcbiAgICBrZXk6ICdzZXJ2ZXJMb29rdXAnLFxuICAgIHZhbHVlOiBmdW5jdGlvbiBzZXJ2ZXJMb29rdXAodXJsKSB7XG4gICAgICB2YXIgY29ubmVjdGlvbkxvb2t1cCA9IHRoaXMudXJsTWFwW3VybF07XG5cbiAgICAgIGlmIChjb25uZWN0aW9uTG9va3VwKSB7XG4gICAgICAgIHJldHVybiBjb25uZWN0aW9uTG9va3VwLnNlcnZlcjtcbiAgICAgIH1cbiAgICB9XG5cbiAgICAvKlxuICAgICogRmluZHMgYWxsIHdlYnNvY2tldHMgd2hpY2ggaXMgJ2xpc3RlbmluZycgb24gdGhlIGdpdmVuIHVybC5cbiAgICAqXG4gICAgKiBAcGFyYW0ge3N0cmluZ30gdXJsIC0gdGhlIHVybCB0byB1c2UgdG8gZmluZCBhbGwgd2Vic29ja2V0cyB3aGljaCBhcmUgYXNzb2NpYXRlZCB3aXRoIGl0XG4gICAgKiBAcGFyYW0ge3N0cmluZ30gcm9vbSAtIGlmIGEgcm9vbSBpcyBwcm92aWRlZCwgd2lsbCBvbmx5IHJldHVybiBzb2NrZXRzIGluIHRoaXMgcm9vbVxuICAgICovXG4gIH0sIHtcbiAgICBrZXk6ICd3ZWJzb2NrZXRzTG9va3VwJyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gd2Vic29ja2V0c0xvb2t1cCh1cmwsIHJvb20pIHtcbiAgICAgIHZhciBjb25uZWN0aW9uTG9va3VwID0gdGhpcy51cmxNYXBbdXJsXTtcblxuICAgICAgaWYgKCFjb25uZWN0aW9uTG9va3VwKSB7XG4gICAgICAgIHJldHVybiBbXTtcbiAgICAgIH1cblxuICAgICAgaWYgKHJvb20pIHtcbiAgICAgICAgdmFyIG1lbWJlcnMgPSBjb25uZWN0aW9uTG9va3VwLnJvb21NZW1iZXJzaGlwc1tyb29tXTtcbiAgICAgICAgcmV0dXJuIG1lbWJlcnMgPyBtZW1iZXJzIDogW107XG4gICAgICB9XG5cbiAgICAgIHJldHVybiBjb25uZWN0aW9uTG9va3VwLndlYnNvY2tldHM7XG4gICAgfVxuXG4gICAgLypcbiAgICAqIFJlbW92ZXMgdGhlIGVudHJ5IGFzc29jaWF0ZWQgd2l0aCB0aGUgdXJsLlxuICAgICpcbiAgICAqIEBwYXJhbSB7c3RyaW5nfSB1cmxcbiAgICAqL1xuICB9LCB7XG4gICAga2V5OiAncmVtb3ZlU2VydmVyJyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gcmVtb3ZlU2VydmVyKHVybCkge1xuICAgICAgZGVsZXRlIHRoaXMudXJsTWFwW3VybF07XG4gICAgfVxuXG4gICAgLypcbiAgICAqIFJlbW92ZXMgdGhlIGluZGl2aWR1YWwgd2Vic29ja2V0IGZyb20gdGhlIG1hcCBvZiBhc3NvY2lhdGVkIHdlYnNvY2tldHMuXG4gICAgKlxuICAgICogQHBhcmFtIHtvYmplY3R9IHdlYnNvY2tldCAtIHdlYnNvY2tldCBvYmplY3QgdG8gcmVtb3ZlIGZyb20gdGhlIHVybCBtYXBcbiAgICAqIEBwYXJhbSB7c3RyaW5nfSB1cmxcbiAgICAqL1xuICB9LCB7XG4gICAga2V5OiAncmVtb3ZlV2ViU29ja2V0JyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gcmVtb3ZlV2ViU29ja2V0KHdlYnNvY2tldCwgdXJsKSB7XG4gICAgICB2YXIgY29ubmVjdGlvbkxvb2t1cCA9IHRoaXMudXJsTWFwW3VybF07XG5cbiAgICAgIGlmIChjb25uZWN0aW9uTG9va3VwKSB7XG4gICAgICAgIGNvbm5lY3Rpb25Mb29rdXAud2Vic29ja2V0cyA9ICgwLCBfaGVscGVyc0FycmF5SGVscGVycy5yZWplY3QpKGNvbm5lY3Rpb25Mb29rdXAud2Vic29ja2V0cywgZnVuY3Rpb24gKHNvY2tldCkge1xuICAgICAgICAgIHJldHVybiBzb2NrZXQgPT09IHdlYnNvY2tldDtcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgLypcbiAgICAqIFJlbW92ZXMgYSB3ZWJzb2NrZXQgZnJvbSBhIHJvb21cbiAgICAqL1xuICB9LCB7XG4gICAga2V5OiAncmVtb3ZlTWVtYmVyc2hpcEZyb21Sb29tJyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gcmVtb3ZlTWVtYmVyc2hpcEZyb21Sb29tKHdlYnNvY2tldCwgcm9vbSkge1xuICAgICAgdmFyIGNvbm5lY3Rpb25Mb29rdXAgPSB0aGlzLnVybE1hcFt3ZWJzb2NrZXQudXJsXTtcbiAgICAgIHZhciBtZW1iZXJzaGlwcyA9IGNvbm5lY3Rpb25Mb29rdXAucm9vbU1lbWJlcnNoaXBzW3Jvb21dO1xuXG4gICAgICBpZiAoY29ubmVjdGlvbkxvb2t1cCAmJiBtZW1iZXJzaGlwcyAhPT0gbnVsbCkge1xuICAgICAgICBjb25uZWN0aW9uTG9va3VwLnJvb21NZW1iZXJzaGlwc1tyb29tXSA9ICgwLCBfaGVscGVyc0FycmF5SGVscGVycy5yZWplY3QpKG1lbWJlcnNoaXBzLCBmdW5jdGlvbiAoc29ja2V0KSB7XG4gICAgICAgICAgcmV0dXJuIHNvY2tldCA9PT0gd2Vic29ja2V0O1xuICAgICAgICB9KTtcbiAgICAgIH1cbiAgICB9XG4gIH1dKTtcblxuICByZXR1cm4gTmV0d29ya0JyaWRnZTtcbn0pKCk7XG5cbmV4cG9ydHNbJ2RlZmF1bHQnXSA9IG5ldyBOZXR3b3JrQnJpZGdlKCk7XG4vLyBOb3RlOiB0aGlzIGlzIGEgc2luZ2xldG9uXG5tb2R1bGUuZXhwb3J0cyA9IGV4cG9ydHNbJ2RlZmF1bHQnXTsiLCIndXNlIHN0cmljdCc7XG5cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCAnX19lc01vZHVsZScsIHtcbiAgdmFsdWU6IHRydWVcbn0pO1xuXG52YXIgX2NyZWF0ZUNsYXNzID0gKGZ1bmN0aW9uICgpIHsgZnVuY3Rpb24gZGVmaW5lUHJvcGVydGllcyh0YXJnZXQsIHByb3BzKSB7IGZvciAodmFyIGkgPSAwOyBpIDwgcHJvcHMubGVuZ3RoOyBpKyspIHsgdmFyIGRlc2NyaXB0b3IgPSBwcm9wc1tpXTsgZGVzY3JpcHRvci5lbnVtZXJhYmxlID0gZGVzY3JpcHRvci5lbnVtZXJhYmxlIHx8IGZhbHNlOyBkZXNjcmlwdG9yLmNvbmZpZ3VyYWJsZSA9IHRydWU7IGlmICgndmFsdWUnIGluIGRlc2NyaXB0b3IpIGRlc2NyaXB0b3Iud3JpdGFibGUgPSB0cnVlOyBPYmplY3QuZGVmaW5lUHJvcGVydHkodGFyZ2V0LCBkZXNjcmlwdG9yLmtleSwgZGVzY3JpcHRvcik7IH0gfSByZXR1cm4gZnVuY3Rpb24gKENvbnN0cnVjdG9yLCBwcm90b1Byb3BzLCBzdGF0aWNQcm9wcykgeyBpZiAocHJvdG9Qcm9wcykgZGVmaW5lUHJvcGVydGllcyhDb25zdHJ1Y3Rvci5wcm90b3R5cGUsIHByb3RvUHJvcHMpOyBpZiAoc3RhdGljUHJvcHMpIGRlZmluZVByb3BlcnRpZXMoQ29uc3RydWN0b3IsIHN0YXRpY1Byb3BzKTsgcmV0dXJuIENvbnN0cnVjdG9yOyB9OyB9KSgpO1xuXG52YXIgX2dldCA9IGZ1bmN0aW9uIGdldChfeDQsIF94NSwgX3g2KSB7IHZhciBfYWdhaW4gPSB0cnVlOyBfZnVuY3Rpb246IHdoaWxlIChfYWdhaW4pIHsgdmFyIG9iamVjdCA9IF94NCwgcHJvcGVydHkgPSBfeDUsIHJlY2VpdmVyID0gX3g2OyBfYWdhaW4gPSBmYWxzZTsgaWYgKG9iamVjdCA9PT0gbnVsbCkgb2JqZWN0ID0gRnVuY3Rpb24ucHJvdG90eXBlOyB2YXIgZGVzYyA9IE9iamVjdC5nZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3Iob2JqZWN0LCBwcm9wZXJ0eSk7IGlmIChkZXNjID09PSB1bmRlZmluZWQpIHsgdmFyIHBhcmVudCA9IE9iamVjdC5nZXRQcm90b3R5cGVPZihvYmplY3QpOyBpZiAocGFyZW50ID09PSBudWxsKSB7IHJldHVybiB1bmRlZmluZWQ7IH0gZWxzZSB7IF94NCA9IHBhcmVudDsgX3g1ID0gcHJvcGVydHk7IF94NiA9IHJlY2VpdmVyOyBfYWdhaW4gPSB0cnVlOyBkZXNjID0gcGFyZW50ID0gdW5kZWZpbmVkOyBjb250aW51ZSBfZnVuY3Rpb247IH0gfSBlbHNlIGlmICgndmFsdWUnIGluIGRlc2MpIHsgcmV0dXJuIGRlc2MudmFsdWU7IH0gZWxzZSB7IHZhciBnZXR0ZXIgPSBkZXNjLmdldDsgaWYgKGdldHRlciA9PT0gdW5kZWZpbmVkKSB7IHJldHVybiB1bmRlZmluZWQ7IH0gcmV0dXJuIGdldHRlci5jYWxsKHJlY2VpdmVyKTsgfSB9IH07XG5cbmZ1bmN0aW9uIF9pbnRlcm9wUmVxdWlyZURlZmF1bHQob2JqKSB7IHJldHVybiBvYmogJiYgb2JqLl9fZXNNb2R1bGUgPyBvYmogOiB7ICdkZWZhdWx0Jzogb2JqIH07IH1cblxuZnVuY3Rpb24gX2NsYXNzQ2FsbENoZWNrKGluc3RhbmNlLCBDb25zdHJ1Y3RvcikgeyBpZiAoIShpbnN0YW5jZSBpbnN0YW5jZW9mIENvbnN0cnVjdG9yKSkgeyB0aHJvdyBuZXcgVHlwZUVycm9yKCdDYW5ub3QgY2FsbCBhIGNsYXNzIGFzIGEgZnVuY3Rpb24nKTsgfSB9XG5cbmZ1bmN0aW9uIF9pbmhlcml0cyhzdWJDbGFzcywgc3VwZXJDbGFzcykgeyBpZiAodHlwZW9mIHN1cGVyQ2xhc3MgIT09ICdmdW5jdGlvbicgJiYgc3VwZXJDbGFzcyAhPT0gbnVsbCkgeyB0aHJvdyBuZXcgVHlwZUVycm9yKCdTdXBlciBleHByZXNzaW9uIG11c3QgZWl0aGVyIGJlIG51bGwgb3IgYSBmdW5jdGlvbiwgbm90ICcgKyB0eXBlb2Ygc3VwZXJDbGFzcyk7IH0gc3ViQ2xhc3MucHJvdG90eXBlID0gT2JqZWN0LmNyZWF0ZShzdXBlckNsYXNzICYmIHN1cGVyQ2xhc3MucHJvdG90eXBlLCB7IGNvbnN0cnVjdG9yOiB7IHZhbHVlOiBzdWJDbGFzcywgZW51bWVyYWJsZTogZmFsc2UsIHdyaXRhYmxlOiB0cnVlLCBjb25maWd1cmFibGU6IHRydWUgfSB9KTsgaWYgKHN1cGVyQ2xhc3MpIE9iamVjdC5zZXRQcm90b3R5cGVPZiA/IE9iamVjdC5zZXRQcm90b3R5cGVPZihzdWJDbGFzcywgc3VwZXJDbGFzcykgOiBzdWJDbGFzcy5fX3Byb3RvX18gPSBzdXBlckNsYXNzOyB9XG5cbnZhciBfdXJpanMgPSByZXF1aXJlKCd1cmlqcycpO1xuXG52YXIgX3VyaWpzMiA9IF9pbnRlcm9wUmVxdWlyZURlZmF1bHQoX3VyaWpzKTtcblxudmFyIF93ZWJzb2NrZXQgPSByZXF1aXJlKCcuL3dlYnNvY2tldCcpO1xuXG52YXIgX3dlYnNvY2tldDIgPSBfaW50ZXJvcFJlcXVpcmVEZWZhdWx0KF93ZWJzb2NrZXQpO1xuXG52YXIgX2V2ZW50VGFyZ2V0ID0gcmVxdWlyZSgnLi9ldmVudC10YXJnZXQnKTtcblxudmFyIF9ldmVudFRhcmdldDIgPSBfaW50ZXJvcFJlcXVpcmVEZWZhdWx0KF9ldmVudFRhcmdldCk7XG5cbnZhciBfbmV0d29ya0JyaWRnZSA9IHJlcXVpcmUoJy4vbmV0d29yay1icmlkZ2UnKTtcblxudmFyIF9uZXR3b3JrQnJpZGdlMiA9IF9pbnRlcm9wUmVxdWlyZURlZmF1bHQoX25ldHdvcmtCcmlkZ2UpO1xuXG52YXIgX2hlbHBlcnNDbG9zZUNvZGVzID0gcmVxdWlyZSgnLi9oZWxwZXJzL2Nsb3NlLWNvZGVzJyk7XG5cbnZhciBfaGVscGVyc0Nsb3NlQ29kZXMyID0gX2ludGVyb3BSZXF1aXJlRGVmYXVsdChfaGVscGVyc0Nsb3NlQ29kZXMpO1xuXG52YXIgX2V2ZW50RmFjdG9yeSA9IHJlcXVpcmUoJy4vZXZlbnQtZmFjdG9yeScpO1xuXG4vKlxuKiBodHRwczovL2dpdGh1Yi5jb20vd2Vic29ja2V0cy93cyNzZXJ2ZXItZXhhbXBsZVxuKi9cblxudmFyIFNlcnZlciA9IChmdW5jdGlvbiAoX0V2ZW50VGFyZ2V0KSB7XG4gIF9pbmhlcml0cyhTZXJ2ZXIsIF9FdmVudFRhcmdldCk7XG5cbiAgLypcbiAgKiBAcGFyYW0ge3N0cmluZ30gdXJsXG4gICovXG5cbiAgZnVuY3Rpb24gU2VydmVyKHVybCkge1xuICAgIF9jbGFzc0NhbGxDaGVjayh0aGlzLCBTZXJ2ZXIpO1xuXG4gICAgX2dldChPYmplY3QuZ2V0UHJvdG90eXBlT2YoU2VydmVyLnByb3RvdHlwZSksICdjb25zdHJ1Y3RvcicsIHRoaXMpLmNhbGwodGhpcyk7XG4gICAgdGhpcy51cmwgPSAoMCwgX3VyaWpzMlsnZGVmYXVsdCddKSh1cmwpLnRvU3RyaW5nKCk7XG4gICAgdmFyIHNlcnZlciA9IF9uZXR3b3JrQnJpZGdlMlsnZGVmYXVsdCddLmF0dGFjaFNlcnZlcih0aGlzLCB0aGlzLnVybCk7XG5cbiAgICBpZiAoIXNlcnZlcikge1xuICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KCgwLCBfZXZlbnRGYWN0b3J5LmNyZWF0ZUV2ZW50KSh7IHR5cGU6ICdlcnJvcicgfSkpO1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdBIG1vY2sgc2VydmVyIGlzIGFscmVhZHkgbGlzdGVuaW5nIG9uIHRoaXMgdXJsJyk7XG4gICAgfVxuICB9XG5cbiAgLypcbiAgICogQWx0ZXJuYXRpdmUgY29uc3RydWN0b3IgdG8gc3VwcG9ydCBuYW1lc3BhY2VzIGluIHNvY2tldC5pb1xuICAgKlxuICAgKiBodHRwOi8vc29ja2V0LmlvL2RvY3Mvcm9vbXMtYW5kLW5hbWVzcGFjZXMvI2N1c3RvbS1uYW1lc3BhY2VzXG4gICAqL1xuXG4gIC8qXG4gICogVGhpcyBpcyB0aGUgbWFpbiBmdW5jdGlvbiBmb3IgdGhlIG1vY2sgc2VydmVyIHRvIHN1YnNjcmliZSB0byB0aGUgb24gZXZlbnRzLlxuICAqXG4gICogaWU6IG1vY2tTZXJ2ZXIub24oJ2Nvbm5lY3Rpb24nLCBmdW5jdGlvbigpIHsgY29uc29sZS5sb2coJ2EgbW9jayBjbGllbnQgY29ubmVjdGVkJyk7IH0pO1xuICAqXG4gICogQHBhcmFtIHtzdHJpbmd9IHR5cGUgLSBUaGUgZXZlbnQga2V5IHRvIHN1YnNjcmliZSB0by4gVmFsaWQga2V5cyBhcmU6IGNvbm5lY3Rpb24sIG1lc3NhZ2UsIGFuZCBjbG9zZS5cbiAgKiBAcGFyYW0ge2Z1bmN0aW9ufSBjYWxsYmFjayAtIFRoZSBjYWxsYmFjayB3aGljaCBzaG91bGQgYmUgY2FsbGVkIHdoZW4gYSBjZXJ0YWluIGV2ZW50IGlzIGZpcmVkLlxuICAqL1xuXG4gIF9jcmVhdGVDbGFzcyhTZXJ2ZXIsIFt7XG4gICAga2V5OiAnb24nLFxuICAgIHZhbHVlOiBmdW5jdGlvbiBvbih0eXBlLCBjYWxsYmFjaykge1xuICAgICAgdGhpcy5hZGRFdmVudExpc3RlbmVyKHR5cGUsIGNhbGxiYWNrKTtcbiAgICB9XG5cbiAgICAvKlxuICAgICogVGhpcyBzZW5kIGZ1bmN0aW9uIHdpbGwgbm90aWZ5IGFsbCBtb2NrIGNsaWVudHMgdmlhIHRoZWlyIG9ubWVzc2FnZSBjYWxsYmFja3MgdGhhdCB0aGUgc2VydmVyXG4gICAgKiBoYXMgYSBtZXNzYWdlIGZvciB0aGVtLlxuICAgICpcbiAgICAqIEBwYXJhbSB7Kn0gZGF0YSAtIEFueSBqYXZhc2NyaXB0IG9iamVjdCB3aGljaCB3aWxsIGJlIGNyYWZ0ZWQgaW50byBhIE1lc3NhZ2VPYmplY3QuXG4gICAgKi9cbiAgfSwge1xuICAgIGtleTogJ3NlbmQnLFxuICAgIHZhbHVlOiBmdW5jdGlvbiBzZW5kKGRhdGEpIHtcbiAgICAgIHZhciBvcHRpb25zID0gYXJndW1lbnRzLmxlbmd0aCA8PSAxIHx8IGFyZ3VtZW50c1sxXSA9PT0gdW5kZWZpbmVkID8ge30gOiBhcmd1bWVudHNbMV07XG5cbiAgICAgIHRoaXMuZW1pdCgnbWVzc2FnZScsIGRhdGEsIG9wdGlvbnMpO1xuICAgIH1cblxuICAgIC8qXG4gICAgKiBTZW5kcyBhIGdlbmVyaWMgbWVzc2FnZSBldmVudCB0byBhbGwgbW9jayBjbGllbnRzLlxuICAgICovXG4gIH0sIHtcbiAgICBrZXk6ICdlbWl0JyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gZW1pdChldmVudCwgZGF0YSkge1xuICAgICAgdmFyIF90aGlzMiA9IHRoaXM7XG5cbiAgICAgIHZhciBvcHRpb25zID0gYXJndW1lbnRzLmxlbmd0aCA8PSAyIHx8IGFyZ3VtZW50c1syXSA9PT0gdW5kZWZpbmVkID8ge30gOiBhcmd1bWVudHNbMl07XG4gICAgICB2YXIgd2Vic29ja2V0cyA9IG9wdGlvbnMud2Vic29ja2V0cztcblxuICAgICAgaWYgKCF3ZWJzb2NrZXRzKSB7XG4gICAgICAgIHdlYnNvY2tldHMgPSBfbmV0d29ya0JyaWRnZTJbJ2RlZmF1bHQnXS53ZWJzb2NrZXRzTG9va3VwKHRoaXMudXJsKTtcbiAgICAgIH1cblxuICAgICAgd2Vic29ja2V0cy5mb3JFYWNoKGZ1bmN0aW9uIChzb2NrZXQpIHtcbiAgICAgICAgc29ja2V0LmRpc3BhdGNoRXZlbnQoKDAsIF9ldmVudEZhY3RvcnkuY3JlYXRlTWVzc2FnZUV2ZW50KSh7XG4gICAgICAgICAgdHlwZTogZXZlbnQsXG4gICAgICAgICAgZGF0YTogZGF0YSxcbiAgICAgICAgICBvcmlnaW46IF90aGlzMi51cmwsXG4gICAgICAgICAgdGFyZ2V0OiBzb2NrZXRcbiAgICAgICAgfSkpO1xuICAgICAgfSk7XG4gICAgfVxuXG4gICAgLypcbiAgICAqIENsb3NlcyB0aGUgY29ubmVjdGlvbiBhbmQgdHJpZ2dlcnMgdGhlIG9uY2xvc2UgbWV0aG9kIG9mIGFsbCBsaXN0ZW5pbmdcbiAgICAqIHdlYnNvY2tldHMuIEFmdGVyIHRoYXQgaXQgcmVtb3ZlcyBpdHNlbGYgZnJvbSB0aGUgdXJsTWFwIHNvIGFub3RoZXIgc2VydmVyXG4gICAgKiBjb3VsZCBhZGQgaXRzZWxmIHRvIHRoZSB1cmwuXG4gICAgKlxuICAgICogQHBhcmFtIHtvYmplY3R9IG9wdGlvbnNcbiAgICAqL1xuICB9LCB7XG4gICAga2V5OiAnY2xvc2UnLFxuICAgIHZhbHVlOiBmdW5jdGlvbiBjbG9zZSgpIHtcbiAgICAgIHZhciBvcHRpb25zID0gYXJndW1lbnRzLmxlbmd0aCA8PSAwIHx8IGFyZ3VtZW50c1swXSA9PT0gdW5kZWZpbmVkID8ge30gOiBhcmd1bWVudHNbMF07XG4gICAgICB2YXIgY29kZSA9IG9wdGlvbnMuY29kZTtcbiAgICAgIHZhciByZWFzb24gPSBvcHRpb25zLnJlYXNvbjtcbiAgICAgIHZhciB3YXNDbGVhbiA9IG9wdGlvbnMud2FzQ2xlYW47XG5cbiAgICAgIHZhciBsaXN0ZW5lcnMgPSBfbmV0d29ya0JyaWRnZTJbJ2RlZmF1bHQnXS53ZWJzb2NrZXRzTG9va3VwKHRoaXMudXJsKTtcblxuICAgICAgbGlzdGVuZXJzLmZvckVhY2goZnVuY3Rpb24gKHNvY2tldCkge1xuICAgICAgICBzb2NrZXQucmVhZHlTdGF0ZSA9IF93ZWJzb2NrZXQyWydkZWZhdWx0J10uQ0xPU0U7XG4gICAgICAgIHNvY2tldC5kaXNwYXRjaEV2ZW50KCgwLCBfZXZlbnRGYWN0b3J5LmNyZWF0ZUNsb3NlRXZlbnQpKHtcbiAgICAgICAgICB0eXBlOiAnY2xvc2UnLFxuICAgICAgICAgIHRhcmdldDogc29ja2V0LFxuICAgICAgICAgIGNvZGU6IGNvZGUgfHwgX2hlbHBlcnNDbG9zZUNvZGVzMlsnZGVmYXVsdCddLkNMT1NFX05PUk1BTCxcbiAgICAgICAgICByZWFzb246IHJlYXNvbiB8fCAnJyxcbiAgICAgICAgICB3YXNDbGVhbjogd2FzQ2xlYW5cbiAgICAgICAgfSkpO1xuICAgICAgfSk7XG5cbiAgICAgIHRoaXMuZGlzcGF0Y2hFdmVudCgoMCwgX2V2ZW50RmFjdG9yeS5jcmVhdGVDbG9zZUV2ZW50KSh7IHR5cGU6ICdjbG9zZScgfSksIHRoaXMpO1xuICAgICAgX25ldHdvcmtCcmlkZ2UyWydkZWZhdWx0J10ucmVtb3ZlU2VydmVyKHRoaXMudXJsKTtcbiAgICB9XG5cbiAgICAvKlxuICAgICogUmV0dXJucyBhbiBhcnJheSBvZiB3ZWJzb2NrZXRzIHdoaWNoIGFyZSBsaXN0ZW5pbmcgdG8gdGhpcyBzZXJ2ZXJcbiAgICAqL1xuICB9LCB7XG4gICAga2V5OiAnY2xpZW50cycsXG4gICAgdmFsdWU6IGZ1bmN0aW9uIGNsaWVudHMoKSB7XG4gICAgICByZXR1cm4gX25ldHdvcmtCcmlkZ2UyWydkZWZhdWx0J10ud2Vic29ja2V0c0xvb2t1cCh0aGlzLnVybCk7XG4gICAgfVxuXG4gICAgLypcbiAgICAqIFByZXBhcmVzIGEgbWV0aG9kIHRvIHN1Ym1pdCBhbiBldmVudCB0byBtZW1iZXJzIG9mIHRoZSByb29tXG4gICAgKlxuICAgICogZS5nLiBzZXJ2ZXIudG8oJ215LXJvb20nKS5lbWl0KCdoaSEnKTtcbiAgICAqL1xuICB9LCB7XG4gICAga2V5OiAndG8nLFxuICAgIHZhbHVlOiBmdW5jdGlvbiB0byhyb29tKSB7XG4gICAgICB2YXIgX3RoaXMgPSB0aGlzO1xuICAgICAgdmFyIHdlYnNvY2tldHMgPSBfbmV0d29ya0JyaWRnZTJbJ2RlZmF1bHQnXS53ZWJzb2NrZXRzTG9va3VwKHRoaXMudXJsLCByb29tKTtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIGVtaXQ6IGZ1bmN0aW9uIGVtaXQoZXZlbnQsIGRhdGEpIHtcbiAgICAgICAgICBfdGhpcy5lbWl0KGV2ZW50LCBkYXRhLCB7IHdlYnNvY2tldHM6IHdlYnNvY2tldHMgfSk7XG4gICAgICAgIH1cbiAgICAgIH07XG4gICAgfVxuICB9XSk7XG5cbiAgcmV0dXJuIFNlcnZlcjtcbn0pKF9ldmVudFRhcmdldDJbJ2RlZmF1bHQnXSk7XG5cblNlcnZlci5vZiA9IGZ1bmN0aW9uIG9mKHVybCkge1xuICByZXR1cm4gbmV3IFNlcnZlcih1cmwpO1xufTtcblxuZXhwb3J0c1snZGVmYXVsdCddID0gU2VydmVyO1xubW9kdWxlLmV4cG9ydHMgPSBleHBvcnRzWydkZWZhdWx0J107IiwiJ3VzZSBzdHJpY3QnO1xuXG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgJ19fZXNNb2R1bGUnLCB7XG4gIHZhbHVlOiB0cnVlXG59KTtcblxudmFyIF9jcmVhdGVDbGFzcyA9IChmdW5jdGlvbiAoKSB7IGZ1bmN0aW9uIGRlZmluZVByb3BlcnRpZXModGFyZ2V0LCBwcm9wcykgeyBmb3IgKHZhciBpID0gMDsgaSA8IHByb3BzLmxlbmd0aDsgaSsrKSB7IHZhciBkZXNjcmlwdG9yID0gcHJvcHNbaV07IGRlc2NyaXB0b3IuZW51bWVyYWJsZSA9IGRlc2NyaXB0b3IuZW51bWVyYWJsZSB8fCBmYWxzZTsgZGVzY3JpcHRvci5jb25maWd1cmFibGUgPSB0cnVlOyBpZiAoJ3ZhbHVlJyBpbiBkZXNjcmlwdG9yKSBkZXNjcmlwdG9yLndyaXRhYmxlID0gdHJ1ZTsgT2JqZWN0LmRlZmluZVByb3BlcnR5KHRhcmdldCwgZGVzY3JpcHRvci5rZXksIGRlc2NyaXB0b3IpOyB9IH0gcmV0dXJuIGZ1bmN0aW9uIChDb25zdHJ1Y3RvciwgcHJvdG9Qcm9wcywgc3RhdGljUHJvcHMpIHsgaWYgKHByb3RvUHJvcHMpIGRlZmluZVByb3BlcnRpZXMoQ29uc3RydWN0b3IucHJvdG90eXBlLCBwcm90b1Byb3BzKTsgaWYgKHN0YXRpY1Byb3BzKSBkZWZpbmVQcm9wZXJ0aWVzKENvbnN0cnVjdG9yLCBzdGF0aWNQcm9wcyk7IHJldHVybiBDb25zdHJ1Y3RvcjsgfTsgfSkoKTtcblxudmFyIF9nZXQgPSBmdW5jdGlvbiBnZXQoX3gzLCBfeDQsIF94NSkgeyB2YXIgX2FnYWluID0gdHJ1ZTsgX2Z1bmN0aW9uOiB3aGlsZSAoX2FnYWluKSB7IHZhciBvYmplY3QgPSBfeDMsIHByb3BlcnR5ID0gX3g0LCByZWNlaXZlciA9IF94NTsgX2FnYWluID0gZmFsc2U7IGlmIChvYmplY3QgPT09IG51bGwpIG9iamVjdCA9IEZ1bmN0aW9uLnByb3RvdHlwZTsgdmFyIGRlc2MgPSBPYmplY3QuZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yKG9iamVjdCwgcHJvcGVydHkpOyBpZiAoZGVzYyA9PT0gdW5kZWZpbmVkKSB7IHZhciBwYXJlbnQgPSBPYmplY3QuZ2V0UHJvdG90eXBlT2Yob2JqZWN0KTsgaWYgKHBhcmVudCA9PT0gbnVsbCkgeyByZXR1cm4gdW5kZWZpbmVkOyB9IGVsc2UgeyBfeDMgPSBwYXJlbnQ7IF94NCA9IHByb3BlcnR5OyBfeDUgPSByZWNlaXZlcjsgX2FnYWluID0gdHJ1ZTsgZGVzYyA9IHBhcmVudCA9IHVuZGVmaW5lZDsgY29udGludWUgX2Z1bmN0aW9uOyB9IH0gZWxzZSBpZiAoJ3ZhbHVlJyBpbiBkZXNjKSB7IHJldHVybiBkZXNjLnZhbHVlOyB9IGVsc2UgeyB2YXIgZ2V0dGVyID0gZGVzYy5nZXQ7IGlmIChnZXR0ZXIgPT09IHVuZGVmaW5lZCkgeyByZXR1cm4gdW5kZWZpbmVkOyB9IHJldHVybiBnZXR0ZXIuY2FsbChyZWNlaXZlcik7IH0gfSB9O1xuXG5mdW5jdGlvbiBfaW50ZXJvcFJlcXVpcmVEZWZhdWx0KG9iaikgeyByZXR1cm4gb2JqICYmIG9iai5fX2VzTW9kdWxlID8gb2JqIDogeyAnZGVmYXVsdCc6IG9iaiB9OyB9XG5cbmZ1bmN0aW9uIF9jbGFzc0NhbGxDaGVjayhpbnN0YW5jZSwgQ29uc3RydWN0b3IpIHsgaWYgKCEoaW5zdGFuY2UgaW5zdGFuY2VvZiBDb25zdHJ1Y3RvcikpIHsgdGhyb3cgbmV3IFR5cGVFcnJvcignQ2Fubm90IGNhbGwgYSBjbGFzcyBhcyBhIGZ1bmN0aW9uJyk7IH0gfVxuXG5mdW5jdGlvbiBfaW5oZXJpdHMoc3ViQ2xhc3MsIHN1cGVyQ2xhc3MpIHsgaWYgKHR5cGVvZiBzdXBlckNsYXNzICE9PSAnZnVuY3Rpb24nICYmIHN1cGVyQ2xhc3MgIT09IG51bGwpIHsgdGhyb3cgbmV3IFR5cGVFcnJvcignU3VwZXIgZXhwcmVzc2lvbiBtdXN0IGVpdGhlciBiZSBudWxsIG9yIGEgZnVuY3Rpb24sIG5vdCAnICsgdHlwZW9mIHN1cGVyQ2xhc3MpOyB9IHN1YkNsYXNzLnByb3RvdHlwZSA9IE9iamVjdC5jcmVhdGUoc3VwZXJDbGFzcyAmJiBzdXBlckNsYXNzLnByb3RvdHlwZSwgeyBjb25zdHJ1Y3RvcjogeyB2YWx1ZTogc3ViQ2xhc3MsIGVudW1lcmFibGU6IGZhbHNlLCB3cml0YWJsZTogdHJ1ZSwgY29uZmlndXJhYmxlOiB0cnVlIH0gfSk7IGlmIChzdXBlckNsYXNzKSBPYmplY3Quc2V0UHJvdG90eXBlT2YgPyBPYmplY3Quc2V0UHJvdG90eXBlT2Yoc3ViQ2xhc3MsIHN1cGVyQ2xhc3MpIDogc3ViQ2xhc3MuX19wcm90b19fID0gc3VwZXJDbGFzczsgfVxuXG52YXIgX3VyaWpzID0gcmVxdWlyZSgndXJpanMnKTtcblxudmFyIF91cmlqczIgPSBfaW50ZXJvcFJlcXVpcmVEZWZhdWx0KF91cmlqcyk7XG5cbnZhciBfaGVscGVyc0RlbGF5ID0gcmVxdWlyZSgnLi9oZWxwZXJzL2RlbGF5Jyk7XG5cbnZhciBfaGVscGVyc0RlbGF5MiA9IF9pbnRlcm9wUmVxdWlyZURlZmF1bHQoX2hlbHBlcnNEZWxheSk7XG5cbnZhciBfZXZlbnRUYXJnZXQgPSByZXF1aXJlKCcuL2V2ZW50LXRhcmdldCcpO1xuXG52YXIgX2V2ZW50VGFyZ2V0MiA9IF9pbnRlcm9wUmVxdWlyZURlZmF1bHQoX2V2ZW50VGFyZ2V0KTtcblxudmFyIF9uZXR3b3JrQnJpZGdlID0gcmVxdWlyZSgnLi9uZXR3b3JrLWJyaWRnZScpO1xuXG52YXIgX25ldHdvcmtCcmlkZ2UyID0gX2ludGVyb3BSZXF1aXJlRGVmYXVsdChfbmV0d29ya0JyaWRnZSk7XG5cbnZhciBfaGVscGVyc0Nsb3NlQ29kZXMgPSByZXF1aXJlKCcuL2hlbHBlcnMvY2xvc2UtY29kZXMnKTtcblxudmFyIF9oZWxwZXJzQ2xvc2VDb2RlczIgPSBfaW50ZXJvcFJlcXVpcmVEZWZhdWx0KF9oZWxwZXJzQ2xvc2VDb2Rlcyk7XG5cbnZhciBfZXZlbnRGYWN0b3J5ID0gcmVxdWlyZSgnLi9ldmVudC1mYWN0b3J5Jyk7XG5cbi8qXG4qIFRoZSBzb2NrZXQtaW8gY2xhc3MgaXMgZGVzaWduZWQgdG8gbWltaWNrIHRoZSByZWFsIEFQSSBhcyBjbG9zZWx5IGFzIHBvc3NpYmxlLlxuKlxuKiBodHRwOi8vc29ja2V0LmlvL2RvY3MvXG4qL1xuXG52YXIgU29ja2V0SU8gPSAoZnVuY3Rpb24gKF9FdmVudFRhcmdldCkge1xuICBfaW5oZXJpdHMoU29ja2V0SU8sIF9FdmVudFRhcmdldCk7XG5cbiAgLypcbiAgKiBAcGFyYW0ge3N0cmluZ30gdXJsXG4gICovXG5cbiAgZnVuY3Rpb24gU29ja2V0SU8oKSB7XG4gICAgdmFyIF90aGlzID0gdGhpcztcblxuICAgIHZhciB1cmwgPSBhcmd1bWVudHMubGVuZ3RoIDw9IDAgfHwgYXJndW1lbnRzWzBdID09PSB1bmRlZmluZWQgPyAnc29ja2V0LmlvJyA6IGFyZ3VtZW50c1swXTtcbiAgICB2YXIgcHJvdG9jb2wgPSBhcmd1bWVudHMubGVuZ3RoIDw9IDEgfHwgYXJndW1lbnRzWzFdID09PSB1bmRlZmluZWQgPyAnJyA6IGFyZ3VtZW50c1sxXTtcblxuICAgIF9jbGFzc0NhbGxDaGVjayh0aGlzLCBTb2NrZXRJTyk7XG5cbiAgICBfZ2V0KE9iamVjdC5nZXRQcm90b3R5cGVPZihTb2NrZXRJTy5wcm90b3R5cGUpLCAnY29uc3RydWN0b3InLCB0aGlzKS5jYWxsKHRoaXMpO1xuXG4gICAgdGhpcy5iaW5hcnlUeXBlID0gJ2Jsb2InO1xuICAgIHRoaXMudXJsID0gKDAsIF91cmlqczJbJ2RlZmF1bHQnXSkodXJsKS50b1N0cmluZygpO1xuICAgIHRoaXMucmVhZHlTdGF0ZSA9IFNvY2tldElPLkNPTk5FQ1RJTkc7XG4gICAgdGhpcy5wcm90b2NvbCA9ICcnO1xuXG4gICAgaWYgKHR5cGVvZiBwcm90b2NvbCA9PT0gJ3N0cmluZycpIHtcbiAgICAgIHRoaXMucHJvdG9jb2wgPSBwcm90b2NvbDtcbiAgICB9IGVsc2UgaWYgKEFycmF5LmlzQXJyYXkocHJvdG9jb2wpICYmIHByb3RvY29sLmxlbmd0aCA+IDApIHtcbiAgICAgIHRoaXMucHJvdG9jb2wgPSBwcm90b2NvbFswXTtcbiAgICB9XG5cbiAgICB2YXIgc2VydmVyID0gX25ldHdvcmtCcmlkZ2UyWydkZWZhdWx0J10uYXR0YWNoV2ViU29ja2V0KHRoaXMsIHRoaXMudXJsKTtcblxuICAgIC8qXG4gICAgKiBEZWxheSB0cmlnZ2VyaW5nIHRoZSBjb25uZWN0aW9uIGV2ZW50cyBzbyB0aGV5IGNhbiBiZSBkZWZpbmVkIGluIHRpbWUuXG4gICAgKi9cbiAgICAoMCwgX2hlbHBlcnNEZWxheTJbJ2RlZmF1bHQnXSkoZnVuY3Rpb24gZGVsYXlDYWxsYmFjaygpIHtcbiAgICAgIGlmIChzZXJ2ZXIpIHtcbiAgICAgICAgdGhpcy5yZWFkeVN0YXRlID0gU29ja2V0SU8uT1BFTjtcbiAgICAgICAgc2VydmVyLmRpc3BhdGNoRXZlbnQoKDAsIF9ldmVudEZhY3RvcnkuY3JlYXRlRXZlbnQpKHsgdHlwZTogJ2Nvbm5lY3Rpb24nIH0pLCBzZXJ2ZXIsIHRoaXMpO1xuICAgICAgICBzZXJ2ZXIuZGlzcGF0Y2hFdmVudCgoMCwgX2V2ZW50RmFjdG9yeS5jcmVhdGVFdmVudCkoeyB0eXBlOiAnY29ubmVjdCcgfSksIHNlcnZlciwgdGhpcyk7IC8vIGFsaWFzXG4gICAgICAgIHRoaXMuZGlzcGF0Y2hFdmVudCgoMCwgX2V2ZW50RmFjdG9yeS5jcmVhdGVFdmVudCkoeyB0eXBlOiAnY29ubmVjdCcsIHRhcmdldDogdGhpcyB9KSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0aGlzLnJlYWR5U3RhdGUgPSBTb2NrZXRJTy5DTE9TRUQ7XG4gICAgICAgIHRoaXMuZGlzcGF0Y2hFdmVudCgoMCwgX2V2ZW50RmFjdG9yeS5jcmVhdGVFdmVudCkoeyB0eXBlOiAnZXJyb3InLCB0YXJnZXQ6IHRoaXMgfSkpO1xuICAgICAgICB0aGlzLmRpc3BhdGNoRXZlbnQoKDAsIF9ldmVudEZhY3RvcnkuY3JlYXRlQ2xvc2VFdmVudCkoe1xuICAgICAgICAgIHR5cGU6ICdjbG9zZScsXG4gICAgICAgICAgdGFyZ2V0OiB0aGlzLFxuICAgICAgICAgIGNvZGU6IF9oZWxwZXJzQ2xvc2VDb2RlczJbJ2RlZmF1bHQnXS5DTE9TRV9OT1JNQUxcbiAgICAgICAgfSkpO1xuXG4gICAgICAgIGNvbnNvbGUuZXJyb3IoJ1NvY2tldC5pbyBjb25uZWN0aW9uIHRvIFxcJycgKyB0aGlzLnVybCArICdcXCcgZmFpbGVkJyk7XG4gICAgICB9XG4gICAgfSwgdGhpcyk7XG5cbiAgICAvKipcbiAgICAgIEFkZCBhbiBhbGlhc2VkIGV2ZW50IGxpc3RlbmVyIGZvciBjbG9zZSAvIGRpc2Nvbm5lY3RcbiAgICAgKi9cbiAgICB0aGlzLmFkZEV2ZW50TGlzdGVuZXIoJ2Nsb3NlJywgZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICBfdGhpcy5kaXNwYXRjaEV2ZW50KCgwLCBfZXZlbnRGYWN0b3J5LmNyZWF0ZUNsb3NlRXZlbnQpKHtcbiAgICAgICAgdHlwZTogJ2Rpc2Nvbm5lY3QnLFxuICAgICAgICB0YXJnZXQ6IGV2ZW50LnRhcmdldCxcbiAgICAgICAgY29kZTogZXZlbnQuY29kZVxuICAgICAgfSkpO1xuICAgIH0pO1xuICB9XG5cbiAgLypcbiAgKiBDbG9zZXMgdGhlIFNvY2tldElPIGNvbm5lY3Rpb24gb3IgY29ubmVjdGlvbiBhdHRlbXB0LCBpZiBhbnkuXG4gICogSWYgdGhlIGNvbm5lY3Rpb24gaXMgYWxyZWFkeSBDTE9TRUQsIHRoaXMgbWV0aG9kIGRvZXMgbm90aGluZy5cbiAgKi9cblxuICBfY3JlYXRlQ2xhc3MoU29ja2V0SU8sIFt7XG4gICAga2V5OiAnY2xvc2UnLFxuICAgIHZhbHVlOiBmdW5jdGlvbiBjbG9zZSgpIHtcbiAgICAgIGlmICh0aGlzLnJlYWR5U3RhdGUgIT09IFNvY2tldElPLk9QRU4pIHtcbiAgICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICAgIH1cblxuICAgICAgdmFyIHNlcnZlciA9IF9uZXR3b3JrQnJpZGdlMlsnZGVmYXVsdCddLnNlcnZlckxvb2t1cCh0aGlzLnVybCk7XG4gICAgICBfbmV0d29ya0JyaWRnZTJbJ2RlZmF1bHQnXS5yZW1vdmVXZWJTb2NrZXQodGhpcywgdGhpcy51cmwpO1xuXG4gICAgICB0aGlzLnJlYWR5U3RhdGUgPSBTb2NrZXRJTy5DTE9TRUQ7XG4gICAgICB0aGlzLmRpc3BhdGNoRXZlbnQoKDAsIF9ldmVudEZhY3RvcnkuY3JlYXRlQ2xvc2VFdmVudCkoe1xuICAgICAgICB0eXBlOiAnY2xvc2UnLFxuICAgICAgICB0YXJnZXQ6IHRoaXMsXG4gICAgICAgIGNvZGU6IF9oZWxwZXJzQ2xvc2VDb2RlczJbJ2RlZmF1bHQnXS5DTE9TRV9OT1JNQUxcbiAgICAgIH0pKTtcblxuICAgICAgaWYgKHNlcnZlcikge1xuICAgICAgICBzZXJ2ZXIuZGlzcGF0Y2hFdmVudCgoMCwgX2V2ZW50RmFjdG9yeS5jcmVhdGVDbG9zZUV2ZW50KSh7XG4gICAgICAgICAgdHlwZTogJ2Rpc2Nvbm5lY3QnLFxuICAgICAgICAgIHRhcmdldDogdGhpcyxcbiAgICAgICAgICBjb2RlOiBfaGVscGVyc0Nsb3NlQ29kZXMyWydkZWZhdWx0J10uQ0xPU0VfTk9STUFMXG4gICAgICAgIH0pLCBzZXJ2ZXIpO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8qXG4gICAgKiBBbGlhcyBmb3IgU29ja2V0I2Nsb3NlXG4gICAgKlxuICAgICogaHR0cHM6Ly9naXRodWIuY29tL3NvY2tldGlvL3NvY2tldC5pby1jbGllbnQvYmxvYi9tYXN0ZXIvbGliL3NvY2tldC5qcyNMMzgzXG4gICAgKi9cbiAgfSwge1xuICAgIGtleTogJ2Rpc2Nvbm5lY3QnLFxuICAgIHZhbHVlOiBmdW5jdGlvbiBkaXNjb25uZWN0KCkge1xuICAgICAgdGhpcy5jbG9zZSgpO1xuICAgIH1cblxuICAgIC8qXG4gICAgKiBTdWJtaXRzIGFuIGV2ZW50IHRvIHRoZSBzZXJ2ZXIgd2l0aCBhIHBheWxvYWRcbiAgICAqL1xuICB9LCB7XG4gICAga2V5OiAnZW1pdCcsXG4gICAgdmFsdWU6IGZ1bmN0aW9uIGVtaXQoZXZlbnQsIGRhdGEpIHtcbiAgICAgIGlmICh0aGlzLnJlYWR5U3RhdGUgIT09IFNvY2tldElPLk9QRU4pIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdTb2NrZXRJTyBpcyBhbHJlYWR5IGluIENMT1NJTkcgb3IgQ0xPU0VEIHN0YXRlJyk7XG4gICAgICB9XG5cbiAgICAgIHZhciBtZXNzYWdlRXZlbnQgPSAoMCwgX2V2ZW50RmFjdG9yeS5jcmVhdGVNZXNzYWdlRXZlbnQpKHtcbiAgICAgICAgdHlwZTogZXZlbnQsXG4gICAgICAgIG9yaWdpbjogdGhpcy51cmwsXG4gICAgICAgIGRhdGE6IGRhdGFcbiAgICAgIH0pO1xuXG4gICAgICB2YXIgc2VydmVyID0gX25ldHdvcmtCcmlkZ2UyWydkZWZhdWx0J10uc2VydmVyTG9va3VwKHRoaXMudXJsKTtcblxuICAgICAgaWYgKHNlcnZlcikge1xuICAgICAgICBzZXJ2ZXIuZGlzcGF0Y2hFdmVudChtZXNzYWdlRXZlbnQsIGRhdGEpO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8qXG4gICAgKiBTdWJtaXRzIGEgJ21lc3NhZ2UnIGV2ZW50IHRvIHRoZSBzZXJ2ZXIuXG4gICAgKlxuICAgICogU2hvdWxkIGJlaGF2ZSBleGFjdGx5IGxpa2UgV2ViU29ja2V0I3NlbmRcbiAgICAqXG4gICAgKiBodHRwczovL2dpdGh1Yi5jb20vc29ja2V0aW8vc29ja2V0LmlvLWNsaWVudC9ibG9iL21hc3Rlci9saWIvc29ja2V0LmpzI0wxMTNcbiAgICAqL1xuICB9LCB7XG4gICAga2V5OiAnc2VuZCcsXG4gICAgdmFsdWU6IGZ1bmN0aW9uIHNlbmQoZGF0YSkge1xuICAgICAgdGhpcy5lbWl0KCdtZXNzYWdlJywgZGF0YSk7XG4gICAgfVxuXG4gICAgLypcbiAgICAqIEZvciByZWdpc3RlcmluZyBldmVudHMgdG8gYmUgcmVjZWl2ZWQgZnJvbSB0aGUgc2VydmVyXG4gICAgKi9cbiAgfSwge1xuICAgIGtleTogJ29uJyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gb24odHlwZSwgY2FsbGJhY2spIHtcbiAgICAgIHRoaXMuYWRkRXZlbnRMaXN0ZW5lcih0eXBlLCBjYWxsYmFjayk7XG4gICAgfVxuXG4gICAgLypcbiAgICAgKiBKb2luIGEgcm9vbSBvbiBhIHNlcnZlclxuICAgICAqXG4gICAgICogaHR0cDovL3NvY2tldC5pby9kb2NzL3Jvb21zLWFuZC1uYW1lc3BhY2VzLyNqb2luaW5nLWFuZC1sZWF2aW5nXG4gICAgICovXG4gIH0sIHtcbiAgICBrZXk6ICdqb2luJyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gam9pbihyb29tKSB7XG4gICAgICBfbmV0d29ya0JyaWRnZTJbJ2RlZmF1bHQnXS5hZGRNZW1iZXJzaGlwVG9Sb29tKHRoaXMsIHJvb20pO1xuICAgIH1cblxuICAgIC8qXG4gICAgICogR2V0IHRoZSB3ZWJzb2NrZXQgdG8gbGVhdmUgdGhlIHJvb21cbiAgICAgKlxuICAgICAqIGh0dHA6Ly9zb2NrZXQuaW8vZG9jcy9yb29tcy1hbmQtbmFtZXNwYWNlcy8jam9pbmluZy1hbmQtbGVhdmluZ1xuICAgICAqL1xuICB9LCB7XG4gICAga2V5OiAnbGVhdmUnLFxuICAgIHZhbHVlOiBmdW5jdGlvbiBsZWF2ZShyb29tKSB7XG4gICAgICBfbmV0d29ya0JyaWRnZTJbJ2RlZmF1bHQnXS5yZW1vdmVNZW1iZXJzaGlwRnJvbVJvb20odGhpcywgcm9vbSk7XG4gICAgfVxuXG4gICAgLypcbiAgICAgKiBJbnZva2VzIGFsbCBsaXN0ZW5lciBmdW5jdGlvbnMgdGhhdCBhcmUgbGlzdGVuaW5nIHRvIHRoZSBnaXZlbiBldmVudC50eXBlIHByb3BlcnR5LiBFYWNoXG4gICAgICogbGlzdGVuZXIgd2lsbCBiZSBwYXNzZWQgdGhlIGV2ZW50IGFzIHRoZSBmaXJzdCBhcmd1bWVudC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSB7b2JqZWN0fSBldmVudCAtIGV2ZW50IG9iamVjdCB3aGljaCB3aWxsIGJlIHBhc3NlZCB0byBhbGwgbGlzdGVuZXJzIG9mIHRoZSBldmVudC50eXBlIHByb3BlcnR5XG4gICAgICovXG4gIH0sIHtcbiAgICBrZXk6ICdkaXNwYXRjaEV2ZW50JyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gZGlzcGF0Y2hFdmVudChldmVudCkge1xuICAgICAgdmFyIF90aGlzMiA9IHRoaXM7XG5cbiAgICAgIGZvciAodmFyIF9sZW4gPSBhcmd1bWVudHMubGVuZ3RoLCBjdXN0b21Bcmd1bWVudHMgPSBBcnJheShfbGVuID4gMSA/IF9sZW4gLSAxIDogMCksIF9rZXkgPSAxOyBfa2V5IDwgX2xlbjsgX2tleSsrKSB7XG4gICAgICAgIGN1c3RvbUFyZ3VtZW50c1tfa2V5IC0gMV0gPSBhcmd1bWVudHNbX2tleV07XG4gICAgICB9XG5cbiAgICAgIHZhciBldmVudE5hbWUgPSBldmVudC50eXBlO1xuICAgICAgdmFyIGxpc3RlbmVycyA9IHRoaXMubGlzdGVuZXJzW2V2ZW50TmFtZV07XG5cbiAgICAgIGlmICghQXJyYXkuaXNBcnJheShsaXN0ZW5lcnMpKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgIH1cblxuICAgICAgbGlzdGVuZXJzLmZvckVhY2goZnVuY3Rpb24gKGxpc3RlbmVyKSB7XG4gICAgICAgIGlmIChjdXN0b21Bcmd1bWVudHMubGVuZ3RoID4gMCkge1xuICAgICAgICAgIGxpc3RlbmVyLmFwcGx5KF90aGlzMiwgY3VzdG9tQXJndW1lbnRzKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAvLyBSZWd1bGFyIFdlYlNvY2tldHMgZXhwZWN0IGEgTWVzc2FnZUV2ZW50IGJ1dCBTb2NrZXRpby5pbyBqdXN0IHdhbnRzIHJhdyBkYXRhXG4gICAgICAgICAgLy8gIHBheWxvYWQgaW5zdGFuY2VvZiBNZXNzYWdlRXZlbnQgd29ya3MsIGJ1dCB5b3UgY2FuJ3QgaXNudGFuY2Ugb2YgTm9kZUV2ZW50XG4gICAgICAgICAgLy8gIGZvciBub3cgd2UgZGV0ZWN0IGlmIHRoZSBvdXRwdXQgaGFzIGRhdGEgZGVmaW5lZCBvbiBpdFxuICAgICAgICAgIGxpc3RlbmVyLmNhbGwoX3RoaXMyLCBldmVudC5kYXRhID8gZXZlbnQuZGF0YSA6IGV2ZW50KTtcbiAgICAgICAgfVxuICAgICAgfSk7XG4gICAgfVxuICB9XSk7XG5cbiAgcmV0dXJuIFNvY2tldElPO1xufSkoX2V2ZW50VGFyZ2V0MlsnZGVmYXVsdCddKTtcblxuU29ja2V0SU8uQ09OTkVDVElORyA9IDA7XG5Tb2NrZXRJTy5PUEVOID0gMTtcblNvY2tldElPLkNMT1NJTkcgPSAyO1xuU29ja2V0SU8uQ0xPU0VEID0gMztcblxuLypcbiogU3RhdGljIGNvbnN0cnVjdG9yIG1ldGhvZHMgZm9yIHRoZSBJTyBTb2NrZXRcbiovXG52YXIgSU8gPSBmdW5jdGlvbiBpb0NvbnN0cnVjdG9yKHVybCkge1xuICByZXR1cm4gbmV3IFNvY2tldElPKHVybCk7XG59O1xuXG4vKlxuKiBBbGlhcyB0aGUgcmF3IElPKCkgY29uc3RydWN0b3JcbiovXG5JTy5jb25uZWN0ID0gZnVuY3Rpb24gaW9Db25uZWN0KHVybCkge1xuICAvKiBlc2xpbnQtZGlzYWJsZSBuZXctY2FwICovXG4gIHJldHVybiBJTyh1cmwpO1xuICAvKiBlc2xpbnQtZW5hYmxlIG5ldy1jYXAgKi9cbn07XG5cbmV4cG9ydHNbJ2RlZmF1bHQnXSA9IElPO1xubW9kdWxlLmV4cG9ydHMgPSBleHBvcnRzWydkZWZhdWx0J107IiwiJ3VzZSBzdHJpY3QnO1xuXG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgJ19fZXNNb2R1bGUnLCB7XG4gIHZhbHVlOiB0cnVlXG59KTtcblxudmFyIF9jcmVhdGVDbGFzcyA9IChmdW5jdGlvbiAoKSB7IGZ1bmN0aW9uIGRlZmluZVByb3BlcnRpZXModGFyZ2V0LCBwcm9wcykgeyBmb3IgKHZhciBpID0gMDsgaSA8IHByb3BzLmxlbmd0aDsgaSsrKSB7IHZhciBkZXNjcmlwdG9yID0gcHJvcHNbaV07IGRlc2NyaXB0b3IuZW51bWVyYWJsZSA9IGRlc2NyaXB0b3IuZW51bWVyYWJsZSB8fCBmYWxzZTsgZGVzY3JpcHRvci5jb25maWd1cmFibGUgPSB0cnVlOyBpZiAoJ3ZhbHVlJyBpbiBkZXNjcmlwdG9yKSBkZXNjcmlwdG9yLndyaXRhYmxlID0gdHJ1ZTsgT2JqZWN0LmRlZmluZVByb3BlcnR5KHRhcmdldCwgZGVzY3JpcHRvci5rZXksIGRlc2NyaXB0b3IpOyB9IH0gcmV0dXJuIGZ1bmN0aW9uIChDb25zdHJ1Y3RvciwgcHJvdG9Qcm9wcywgc3RhdGljUHJvcHMpIHsgaWYgKHByb3RvUHJvcHMpIGRlZmluZVByb3BlcnRpZXMoQ29uc3RydWN0b3IucHJvdG90eXBlLCBwcm90b1Byb3BzKTsgaWYgKHN0YXRpY1Byb3BzKSBkZWZpbmVQcm9wZXJ0aWVzKENvbnN0cnVjdG9yLCBzdGF0aWNQcm9wcyk7IHJldHVybiBDb25zdHJ1Y3RvcjsgfTsgfSkoKTtcblxudmFyIF9nZXQgPSBmdW5jdGlvbiBnZXQoX3gyLCBfeDMsIF94NCkgeyB2YXIgX2FnYWluID0gdHJ1ZTsgX2Z1bmN0aW9uOiB3aGlsZSAoX2FnYWluKSB7IHZhciBvYmplY3QgPSBfeDIsIHByb3BlcnR5ID0gX3gzLCByZWNlaXZlciA9IF94NDsgX2FnYWluID0gZmFsc2U7IGlmIChvYmplY3QgPT09IG51bGwpIG9iamVjdCA9IEZ1bmN0aW9uLnByb3RvdHlwZTsgdmFyIGRlc2MgPSBPYmplY3QuZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yKG9iamVjdCwgcHJvcGVydHkpOyBpZiAoZGVzYyA9PT0gdW5kZWZpbmVkKSB7IHZhciBwYXJlbnQgPSBPYmplY3QuZ2V0UHJvdG90eXBlT2Yob2JqZWN0KTsgaWYgKHBhcmVudCA9PT0gbnVsbCkgeyByZXR1cm4gdW5kZWZpbmVkOyB9IGVsc2UgeyBfeDIgPSBwYXJlbnQ7IF94MyA9IHByb3BlcnR5OyBfeDQgPSByZWNlaXZlcjsgX2FnYWluID0gdHJ1ZTsgZGVzYyA9IHBhcmVudCA9IHVuZGVmaW5lZDsgY29udGludWUgX2Z1bmN0aW9uOyB9IH0gZWxzZSBpZiAoJ3ZhbHVlJyBpbiBkZXNjKSB7IHJldHVybiBkZXNjLnZhbHVlOyB9IGVsc2UgeyB2YXIgZ2V0dGVyID0gZGVzYy5nZXQ7IGlmIChnZXR0ZXIgPT09IHVuZGVmaW5lZCkgeyByZXR1cm4gdW5kZWZpbmVkOyB9IHJldHVybiBnZXR0ZXIuY2FsbChyZWNlaXZlcik7IH0gfSB9O1xuXG5mdW5jdGlvbiBfaW50ZXJvcFJlcXVpcmVEZWZhdWx0KG9iaikgeyByZXR1cm4gb2JqICYmIG9iai5fX2VzTW9kdWxlID8gb2JqIDogeyAnZGVmYXVsdCc6IG9iaiB9OyB9XG5cbmZ1bmN0aW9uIF9jbGFzc0NhbGxDaGVjayhpbnN0YW5jZSwgQ29uc3RydWN0b3IpIHsgaWYgKCEoaW5zdGFuY2UgaW5zdGFuY2VvZiBDb25zdHJ1Y3RvcikpIHsgdGhyb3cgbmV3IFR5cGVFcnJvcignQ2Fubm90IGNhbGwgYSBjbGFzcyBhcyBhIGZ1bmN0aW9uJyk7IH0gfVxuXG5mdW5jdGlvbiBfaW5oZXJpdHMoc3ViQ2xhc3MsIHN1cGVyQ2xhc3MpIHsgaWYgKHR5cGVvZiBzdXBlckNsYXNzICE9PSAnZnVuY3Rpb24nICYmIHN1cGVyQ2xhc3MgIT09IG51bGwpIHsgdGhyb3cgbmV3IFR5cGVFcnJvcignU3VwZXIgZXhwcmVzc2lvbiBtdXN0IGVpdGhlciBiZSBudWxsIG9yIGEgZnVuY3Rpb24sIG5vdCAnICsgdHlwZW9mIHN1cGVyQ2xhc3MpOyB9IHN1YkNsYXNzLnByb3RvdHlwZSA9IE9iamVjdC5jcmVhdGUoc3VwZXJDbGFzcyAmJiBzdXBlckNsYXNzLnByb3RvdHlwZSwgeyBjb25zdHJ1Y3RvcjogeyB2YWx1ZTogc3ViQ2xhc3MsIGVudW1lcmFibGU6IGZhbHNlLCB3cml0YWJsZTogdHJ1ZSwgY29uZmlndXJhYmxlOiB0cnVlIH0gfSk7IGlmIChzdXBlckNsYXNzKSBPYmplY3Quc2V0UHJvdG90eXBlT2YgPyBPYmplY3Quc2V0UHJvdG90eXBlT2Yoc3ViQ2xhc3MsIHN1cGVyQ2xhc3MpIDogc3ViQ2xhc3MuX19wcm90b19fID0gc3VwZXJDbGFzczsgfVxuXG52YXIgX3VyaWpzID0gcmVxdWlyZSgndXJpanMnKTtcblxudmFyIF91cmlqczIgPSBfaW50ZXJvcFJlcXVpcmVEZWZhdWx0KF91cmlqcyk7XG5cbnZhciBfaGVscGVyc0RlbGF5ID0gcmVxdWlyZSgnLi9oZWxwZXJzL2RlbGF5Jyk7XG5cbnZhciBfaGVscGVyc0RlbGF5MiA9IF9pbnRlcm9wUmVxdWlyZURlZmF1bHQoX2hlbHBlcnNEZWxheSk7XG5cbnZhciBfZXZlbnRUYXJnZXQgPSByZXF1aXJlKCcuL2V2ZW50LXRhcmdldCcpO1xuXG52YXIgX2V2ZW50VGFyZ2V0MiA9IF9pbnRlcm9wUmVxdWlyZURlZmF1bHQoX2V2ZW50VGFyZ2V0KTtcblxudmFyIF9uZXR3b3JrQnJpZGdlID0gcmVxdWlyZSgnLi9uZXR3b3JrLWJyaWRnZScpO1xuXG52YXIgX25ldHdvcmtCcmlkZ2UyID0gX2ludGVyb3BSZXF1aXJlRGVmYXVsdChfbmV0d29ya0JyaWRnZSk7XG5cbnZhciBfaGVscGVyc0Nsb3NlQ29kZXMgPSByZXF1aXJlKCcuL2hlbHBlcnMvY2xvc2UtY29kZXMnKTtcblxudmFyIF9oZWxwZXJzQ2xvc2VDb2RlczIgPSBfaW50ZXJvcFJlcXVpcmVEZWZhdWx0KF9oZWxwZXJzQ2xvc2VDb2Rlcyk7XG5cbnZhciBfZXZlbnRGYWN0b3J5ID0gcmVxdWlyZSgnLi9ldmVudC1mYWN0b3J5Jyk7XG5cbi8qXG4qIFRoZSBtYWluIHdlYnNvY2tldCBjbGFzcyB3aGljaCBpcyBkZXNpZ25lZCB0byBtaW1pY2sgdGhlIG5hdGl2ZSBXZWJTb2NrZXQgY2xhc3MgYXMgY2xvc2VcbiogYXMgcG9zc2libGUuXG4qXG4qIGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTL2RvY3MvV2ViL0FQSS9XZWJTb2NrZXRcbiovXG5cbnZhciBXZWJTb2NrZXQgPSAoZnVuY3Rpb24gKF9FdmVudFRhcmdldCkge1xuICBfaW5oZXJpdHMoV2ViU29ja2V0LCBfRXZlbnRUYXJnZXQpO1xuXG4gIC8qXG4gICogQHBhcmFtIHtzdHJpbmd9IHVybFxuICAqL1xuXG4gIGZ1bmN0aW9uIFdlYlNvY2tldCh1cmwpIHtcbiAgICB2YXIgcHJvdG9jb2wgPSBhcmd1bWVudHMubGVuZ3RoIDw9IDEgfHwgYXJndW1lbnRzWzFdID09PSB1bmRlZmluZWQgPyAnJyA6IGFyZ3VtZW50c1sxXTtcblxuICAgIF9jbGFzc0NhbGxDaGVjayh0aGlzLCBXZWJTb2NrZXQpO1xuXG4gICAgX2dldChPYmplY3QuZ2V0UHJvdG90eXBlT2YoV2ViU29ja2V0LnByb3RvdHlwZSksICdjb25zdHJ1Y3RvcicsIHRoaXMpLmNhbGwodGhpcyk7XG5cbiAgICBpZiAoIXVybCkge1xuICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignRmFpbGVkIHRvIGNvbnN0cnVjdCBcXCdXZWJTb2NrZXRcXCc6IDEgYXJndW1lbnQgcmVxdWlyZWQsIGJ1dCBvbmx5IDAgcHJlc2VudC4nKTtcbiAgICB9XG5cbiAgICB0aGlzLmJpbmFyeVR5cGUgPSAnYmxvYic7XG4gICAgdGhpcy51cmwgPSAoMCwgX3VyaWpzMlsnZGVmYXVsdCddKSh1cmwpLnRvU3RyaW5nKCk7XG4gICAgdGhpcy5yZWFkeVN0YXRlID0gV2ViU29ja2V0LkNPTk5FQ1RJTkc7XG4gICAgdGhpcy5wcm90b2NvbCA9ICcnO1xuXG4gICAgaWYgKHR5cGVvZiBwcm90b2NvbCA9PT0gJ3N0cmluZycpIHtcbiAgICAgIHRoaXMucHJvdG9jb2wgPSBwcm90b2NvbDtcbiAgICB9IGVsc2UgaWYgKEFycmF5LmlzQXJyYXkocHJvdG9jb2wpICYmIHByb3RvY29sLmxlbmd0aCA+IDApIHtcbiAgICAgIHRoaXMucHJvdG9jb2wgPSBwcm90b2NvbFswXTtcbiAgICB9XG5cbiAgICAvKlxuICAgICogSW4gb3JkZXIgdG8gY2FwdHVyZSB0aGUgY2FsbGJhY2sgZnVuY3Rpb24gd2UgbmVlZCB0byBkZWZpbmUgY3VzdG9tIHNldHRlcnMuXG4gICAgKiBUbyBpbGx1c3RyYXRlOlxuICAgICogICBteVNvY2tldC5vbm9wZW4gPSBmdW5jdGlvbigpIHsgYWxlcnQodHJ1ZSkgfTtcbiAgICAqXG4gICAgKiBUaGUgb25seSB3YXkgdG8gY2FwdHVyZSB0aGF0IGZ1bmN0aW9uIGFuZCBob2xkIG9udG8gaXQgZm9yIGxhdGVyIGlzIHdpdGggdGhlXG4gICAgKiBiZWxvdyBjb2RlOlxuICAgICovXG4gICAgT2JqZWN0LmRlZmluZVByb3BlcnRpZXModGhpcywge1xuICAgICAgb25vcGVuOiB7XG4gICAgICAgIGNvbmZpZ3VyYWJsZTogdHJ1ZSxcbiAgICAgICAgZW51bWVyYWJsZTogdHJ1ZSxcbiAgICAgICAgZ2V0OiBmdW5jdGlvbiBnZXQoKSB7XG4gICAgICAgICAgcmV0dXJuIHRoaXMubGlzdGVuZXJzLm9wZW47XG4gICAgICAgIH0sXG4gICAgICAgIHNldDogZnVuY3Rpb24gc2V0KGxpc3RlbmVyKSB7XG4gICAgICAgICAgdGhpcy5hZGRFdmVudExpc3RlbmVyKCdvcGVuJywgbGlzdGVuZXIpO1xuICAgICAgICB9XG4gICAgICB9LFxuICAgICAgb25tZXNzYWdlOiB7XG4gICAgICAgIGNvbmZpZ3VyYWJsZTogdHJ1ZSxcbiAgICAgICAgZW51bWVyYWJsZTogdHJ1ZSxcbiAgICAgICAgZ2V0OiBmdW5jdGlvbiBnZXQoKSB7XG4gICAgICAgICAgcmV0dXJuIHRoaXMubGlzdGVuZXJzLm1lc3NhZ2U7XG4gICAgICAgIH0sXG4gICAgICAgIHNldDogZnVuY3Rpb24gc2V0KGxpc3RlbmVyKSB7XG4gICAgICAgICAgdGhpcy5hZGRFdmVudExpc3RlbmVyKCdtZXNzYWdlJywgbGlzdGVuZXIpO1xuICAgICAgICB9XG4gICAgICB9LFxuICAgICAgb25jbG9zZToge1xuICAgICAgICBjb25maWd1cmFibGU6IHRydWUsXG4gICAgICAgIGVudW1lcmFibGU6IHRydWUsXG4gICAgICAgIGdldDogZnVuY3Rpb24gZ2V0KCkge1xuICAgICAgICAgIHJldHVybiB0aGlzLmxpc3RlbmVycy5jbG9zZTtcbiAgICAgICAgfSxcbiAgICAgICAgc2V0OiBmdW5jdGlvbiBzZXQobGlzdGVuZXIpIHtcbiAgICAgICAgICB0aGlzLmFkZEV2ZW50TGlzdGVuZXIoJ2Nsb3NlJywgbGlzdGVuZXIpO1xuICAgICAgICB9XG4gICAgICB9LFxuICAgICAgb25lcnJvcjoge1xuICAgICAgICBjb25maWd1cmFibGU6IHRydWUsXG4gICAgICAgIGVudW1lcmFibGU6IHRydWUsXG4gICAgICAgIGdldDogZnVuY3Rpb24gZ2V0KCkge1xuICAgICAgICAgIHJldHVybiB0aGlzLmxpc3RlbmVycy5lcnJvcjtcbiAgICAgICAgfSxcbiAgICAgICAgc2V0OiBmdW5jdGlvbiBzZXQobGlzdGVuZXIpIHtcbiAgICAgICAgICB0aGlzLmFkZEV2ZW50TGlzdGVuZXIoJ2Vycm9yJywgbGlzdGVuZXIpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSk7XG5cbiAgICB2YXIgc2VydmVyID0gX25ldHdvcmtCcmlkZ2UyWydkZWZhdWx0J10uYXR0YWNoV2ViU29ja2V0KHRoaXMsIHRoaXMudXJsKTtcblxuICAgIC8qXG4gICAgKiBUaGlzIGRlbGF5IGlzIG5lZWRlZCBzbyB0aGF0IHdlIGRvbnQgdHJpZ2dlciBhbiBldmVudCBiZWZvcmUgdGhlIGNhbGxiYWNrcyBoYXZlIGJlZW5cbiAgICAqIHNldHVwLiBGb3IgZXhhbXBsZTpcbiAgICAqXG4gICAgKiB2YXIgc29ja2V0ID0gbmV3IFdlYlNvY2tldCgnd3M6Ly9sb2NhbGhvc3QnKTtcbiAgICAqXG4gICAgKiAvLyBJZiB3ZSBkb250IGhhdmUgdGhlIGRlbGF5IHRoZW4gdGhlIGV2ZW50IHdvdWxkIGJlIHRyaWdnZXJlZCByaWdodCBoZXJlIGFuZCB0aGlzIGlzXG4gICAgKiAvLyBiZWZvcmUgdGhlIG9ub3BlbiBoYWQgYSBjaGFuY2UgdG8gcmVnaXN0ZXIgaXRzZWxmLlxuICAgICpcbiAgICAqIHNvY2tldC5vbm9wZW4gPSAoKSA9PiB7IC8vIHRoaXMgd291bGQgbmV2ZXIgYmUgY2FsbGVkIH07XG4gICAgKlxuICAgICogLy8gYW5kIHdpdGggdGhlIGRlbGF5IHRoZSBldmVudCBnZXRzIHRyaWdnZXJlZCBoZXJlIGFmdGVyIGFsbCBvZiB0aGUgY2FsbGJhY2tzIGhhdmUgYmVlblxuICAgICogLy8gcmVnaXN0ZXJlZCA6LSlcbiAgICAqL1xuICAgICgwLCBfaGVscGVyc0RlbGF5MlsnZGVmYXVsdCddKShmdW5jdGlvbiBkZWxheUNhbGxiYWNrKCkge1xuICAgICAgaWYgKHNlcnZlcikge1xuICAgICAgICB0aGlzLnJlYWR5U3RhdGUgPSBXZWJTb2NrZXQuT1BFTjtcbiAgICAgICAgc2VydmVyLmRpc3BhdGNoRXZlbnQoKDAsIF9ldmVudEZhY3RvcnkuY3JlYXRlRXZlbnQpKHsgdHlwZTogJ2Nvbm5lY3Rpb24nIH0pLCBzZXJ2ZXIsIHRoaXMpO1xuICAgICAgICB0aGlzLmRpc3BhdGNoRXZlbnQoKDAsIF9ldmVudEZhY3RvcnkuY3JlYXRlRXZlbnQpKHsgdHlwZTogJ29wZW4nLCB0YXJnZXQ6IHRoaXMgfSkpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhpcy5yZWFkeVN0YXRlID0gV2ViU29ja2V0LkNMT1NFRDtcbiAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KCgwLCBfZXZlbnRGYWN0b3J5LmNyZWF0ZUV2ZW50KSh7IHR5cGU6ICdlcnJvcicsIHRhcmdldDogdGhpcyB9KSk7XG4gICAgICAgIHRoaXMuZGlzcGF0Y2hFdmVudCgoMCwgX2V2ZW50RmFjdG9yeS5jcmVhdGVDbG9zZUV2ZW50KSh7IHR5cGU6ICdjbG9zZScsIHRhcmdldDogdGhpcywgY29kZTogX2hlbHBlcnNDbG9zZUNvZGVzMlsnZGVmYXVsdCddLkNMT1NFX05PUk1BTCB9KSk7XG5cbiAgICAgICAgY29uc29sZS5lcnJvcignV2ViU29ja2V0IGNvbm5lY3Rpb24gdG8gXFwnJyArIHRoaXMudXJsICsgJ1xcJyBmYWlsZWQnKTtcbiAgICAgIH1cbiAgICB9LCB0aGlzKTtcbiAgfVxuXG4gIC8qXG4gICogVHJhbnNtaXRzIGRhdGEgdG8gdGhlIHNlcnZlciBvdmVyIHRoZSBXZWJTb2NrZXQgY29ubmVjdGlvbi5cbiAgKlxuICAqIGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTL2RvY3MvV2ViL0FQSS9XZWJTb2NrZXQjc2VuZCgpXG4gICovXG5cbiAgX2NyZWF0ZUNsYXNzKFdlYlNvY2tldCwgW3tcbiAgICBrZXk6ICdzZW5kJyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gc2VuZChkYXRhKSB7XG4gICAgICBpZiAodGhpcy5yZWFkeVN0YXRlID09PSBXZWJTb2NrZXQuQ0xPU0lORyB8fCB0aGlzLnJlYWR5U3RhdGUgPT09IFdlYlNvY2tldC5DTE9TRUQpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdXZWJTb2NrZXQgaXMgYWxyZWFkeSBpbiBDTE9TSU5HIG9yIENMT1NFRCBzdGF0ZScpO1xuICAgICAgfVxuXG4gICAgICB2YXIgbWVzc2FnZUV2ZW50ID0gKDAsIF9ldmVudEZhY3RvcnkuY3JlYXRlTWVzc2FnZUV2ZW50KSh7XG4gICAgICAgIHR5cGU6ICdtZXNzYWdlJyxcbiAgICAgICAgb3JpZ2luOiB0aGlzLnVybCxcbiAgICAgICAgZGF0YTogZGF0YVxuICAgICAgfSk7XG5cbiAgICAgIHZhciBzZXJ2ZXIgPSBfbmV0d29ya0JyaWRnZTJbJ2RlZmF1bHQnXS5zZXJ2ZXJMb29rdXAodGhpcy51cmwpO1xuXG4gICAgICBpZiAoc2VydmVyKSB7XG4gICAgICAgIHNlcnZlci5kaXNwYXRjaEV2ZW50KG1lc3NhZ2VFdmVudCwgZGF0YSk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgLypcbiAgICAqIENsb3NlcyB0aGUgV2ViU29ja2V0IGNvbm5lY3Rpb24gb3IgY29ubmVjdGlvbiBhdHRlbXB0LCBpZiBhbnkuXG4gICAgKiBJZiB0aGUgY29ubmVjdGlvbiBpcyBhbHJlYWR5IENMT1NFRCwgdGhpcyBtZXRob2QgZG9lcyBub3RoaW5nLlxuICAgICpcbiAgICAqIGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTL2RvY3MvV2ViL0FQSS9XZWJTb2NrZXQjY2xvc2UoKVxuICAgICovXG4gIH0sIHtcbiAgICBrZXk6ICdjbG9zZScsXG4gICAgdmFsdWU6IGZ1bmN0aW9uIGNsb3NlKCkge1xuICAgICAgaWYgKHRoaXMucmVhZHlTdGF0ZSAhPT0gV2ViU29ja2V0Lk9QRU4pIHtcbiAgICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICAgIH1cblxuICAgICAgdmFyIHNlcnZlciA9IF9uZXR3b3JrQnJpZGdlMlsnZGVmYXVsdCddLnNlcnZlckxvb2t1cCh0aGlzLnVybCk7XG4gICAgICB2YXIgY2xvc2VFdmVudCA9ICgwLCBfZXZlbnRGYWN0b3J5LmNyZWF0ZUNsb3NlRXZlbnQpKHtcbiAgICAgICAgdHlwZTogJ2Nsb3NlJyxcbiAgICAgICAgdGFyZ2V0OiB0aGlzLFxuICAgICAgICBjb2RlOiBfaGVscGVyc0Nsb3NlQ29kZXMyWydkZWZhdWx0J10uQ0xPU0VfTk9STUFMXG4gICAgICB9KTtcblxuICAgICAgX25ldHdvcmtCcmlkZ2UyWydkZWZhdWx0J10ucmVtb3ZlV2ViU29ja2V0KHRoaXMsIHRoaXMudXJsKTtcblxuICAgICAgdGhpcy5yZWFkeVN0YXRlID0gV2ViU29ja2V0LkNMT1NFRDtcbiAgICAgIHRoaXMuZGlzcGF0Y2hFdmVudChjbG9zZUV2ZW50KTtcblxuICAgICAgaWYgKHNlcnZlcikge1xuICAgICAgICBzZXJ2ZXIuZGlzcGF0Y2hFdmVudChjbG9zZUV2ZW50LCBzZXJ2ZXIpO1xuICAgICAgfVxuICAgIH1cbiAgfV0pO1xuXG4gIHJldHVybiBXZWJTb2NrZXQ7XG59KShfZXZlbnRUYXJnZXQyWydkZWZhdWx0J10pO1xuXG5XZWJTb2NrZXQuQ09OTkVDVElORyA9IDA7XG5XZWJTb2NrZXQuT1BFTiA9IDE7XG5XZWJTb2NrZXQuQ0xPU0lORyA9IDI7XG5XZWJTb2NrZXQuQ0xPU0VEID0gMztcblxuZXhwb3J0c1snZGVmYXVsdCddID0gV2ViU29ja2V0O1xubW9kdWxlLmV4cG9ydHMgPSBleHBvcnRzWydkZWZhdWx0J107Il19 diff --git a/actioncable/test/server/base_test.rb b/actioncable/test/server/base_test.rb new file mode 100644 index 0000000000..f0a51c5a7d --- /dev/null +++ b/actioncable/test/server/base_test.rb @@ -0,0 +1,33 @@ +require "test_helper" +require "stubs/test_server" +require "active_support/core_ext/hash/indifferent_access" + +class BaseTest < ActiveSupport::TestCase + def setup + @server = ActionCable::Server::Base.new + @server.config.cable = { adapter: "async" }.with_indifferent_access + end + + class FakeConnection + def close + end + end + + test "#restart closes all open connections" do + conn = FakeConnection.new + @server.add_connection(conn) + + conn.expects(:close) + @server.restart + end + + test "#restart shuts down worker pool" do + @server.worker_pool.expects(:halt) + @server.restart + end + + test "#restart shuts down pub/sub adapter" do + @server.pubsub.expects(:shutdown) + @server.restart + end +end diff --git a/actioncable/test/server/broadcasting_test.rb b/actioncable/test/server/broadcasting_test.rb new file mode 100644 index 0000000000..ed377b7d5d --- /dev/null +++ b/actioncable/test/server/broadcasting_test.rb @@ -0,0 +1,60 @@ +require "test_helper" +require "stubs/test_server" + +class BroadcastingTest < ActiveSupport::TestCase + test "fetching a broadcaster converts the broadcasting queue to a string" do + broadcasting = :test_queue + server = TestServer.new + broadcaster = server.broadcaster_for(broadcasting) + + assert_equal "test_queue", broadcaster.broadcasting + end + + test "broadcast generates notification" do + begin + server = TestServer.new + + events = [] + ActiveSupport::Notifications.subscribe "broadcast.action_cable" do |*args| + events << ActiveSupport::Notifications::Event.new(*args) + end + + broadcasting = "test_queue" + message = { body: "test message" } + server.broadcast(broadcasting, message) + + assert_equal 1, events.length + assert_equal "broadcast.action_cable", events[0].name + assert_equal broadcasting, events[0].payload[:broadcasting] + assert_equal message, events[0].payload[:message] + assert_equal ActiveSupport::JSON, events[0].payload[:coder] + ensure + ActiveSupport::Notifications.unsubscribe "broadcast.action_cable" + end + end + + test "broadcaster from broadcaster_for generates notification" do + begin + server = TestServer.new + + events = [] + ActiveSupport::Notifications.subscribe "broadcast.action_cable" do |*args| + events << ActiveSupport::Notifications::Event.new(*args) + end + + broadcasting = "test_queue" + message = { body: "test message" } + + broadcaster = server.broadcaster_for(broadcasting) + broadcaster.broadcast(message) + + assert_equal 1, events.length + assert_equal "broadcast.action_cable", events[0].name + assert_equal broadcasting, events[0].payload[:broadcasting] + assert_equal message, events[0].payload[:message] + assert_equal ActiveSupport::JSON, events[0].payload[:coder] + ensure + ActiveSupport::Notifications.unsubscribe "broadcast.action_cable" + end + end +end diff --git a/actioncable/test/stubs/room.rb b/actioncable/test/stubs/room.rb index cd66a0b687..9e521cf3a6 100644 --- a/actioncable/test/stubs/room.rb +++ b/actioncable/test/stubs/room.rb @@ -1,7 +1,7 @@ class Room attr_reader :id, :name - def initialize(id, name='Campfire') + def initialize(id, name="Campfire") @id = id @name = name end diff --git a/actioncable/test/stubs/test_connection.rb b/actioncable/test/stubs/test_connection.rb index da98201900..cd2e219d88 100644 --- a/actioncable/test/stubs/test_connection.rb +++ b/actioncable/test/stubs/test_connection.rb @@ -1,25 +1,33 @@ -require 'stubs/user' +require "stubs/user" class TestConnection - attr_reader :identifiers, :logger, :current_user, :transmissions + attr_reader :identifiers, :logger, :current_user, :server, :transmissions - def initialize(user = User.new("lifo")) + delegate :pubsub, to: :server + + def initialize(user = User.new("lifo"), coder: ActiveSupport::JSON, subscription_adapter: SuccessAdapter) + @coder = coder @identifiers = [ :current_user ] @current_user = user @logger = ActiveSupport::TaggedLogging.new ActiveSupport::Logger.new(StringIO.new) + @server = TestServer.new(subscription_adapter: subscription_adapter) @transmissions = [] end - def pubsub - SuccessAdapter.new(TestServer.new) + def transmit(cable_message) + @transmissions << encode(cable_message) end - def transmit(data) - @transmissions << data + def last_transmission + decode @transmissions.last if @transmissions.any? end - def last_transmission - @transmissions.last + def decode(websocket_message) + @coder.decode websocket_message + end + + def encode(cable_message) + @coder.encode cable_message end end diff --git a/actioncable/test/stubs/test_server.rb b/actioncable/test/stubs/test_server.rb index 56d132b30a..5bf2a151dc 100644 --- a/actioncable/test/stubs/test_server.rb +++ b/actioncable/test/stubs/test_server.rb @@ -1,20 +1,30 @@ -require 'ostruct' +require "ostruct" class TestServer include ActionCable::Server::Connections + include ActionCable::Server::Broadcasting - attr_reader :logger, :config + attr_reader :logger, :config, :mutex - def initialize + def initialize(subscription_adapter: SuccessAdapter) @logger = ActiveSupport::TaggedLogging.new ActiveSupport::Logger.new(StringIO.new) - @config = OpenStruct.new(log_tags: [], subscription_adapter: SuccessAdapter) + + @config = OpenStruct.new(log_tags: [], subscription_adapter: subscription_adapter) + + @mutex = Monitor.new end def pubsub - @config.subscription_adapter.new(self) + @pubsub ||= @config.subscription_adapter.new(self) + end + + def event_loop + @event_loop ||= ActionCable::Connection::StreamEventLoop.new.tap do |loop| + loop.instance_variable_set(:@executor, Concurrent.global_io_executor) + end end - def stream_event_loop - @stream_event_loop ||= ActionCable::Connection::StreamEventLoop.new + def worker_pool + @worker_pool ||= ActionCable::Server::Worker.new(max_size: 5) end end diff --git a/actioncable/test/subscription_adapter/async_test.rb b/actioncable/test/subscription_adapter/async_test.rb index 8f413f14c2..7bc2e55d40 100644 --- a/actioncable/test/subscription_adapter/async_test.rb +++ b/actioncable/test/subscription_adapter/async_test.rb @@ -1,5 +1,5 @@ -require 'test_helper' -require_relative './common' +require "test_helper" +require_relative "./common" class AsyncAdapterTest < ActionCable::TestCase include CommonSubscriptionAdapterTest @@ -12,6 +12,6 @@ class AsyncAdapterTest < ActionCable::TestCase end def cable_config - { adapter: 'async' } + { adapter: "async" } end end diff --git a/actioncable/test/subscription_adapter/base_test.rb b/actioncable/test/subscription_adapter/base_test.rb index 7a7ae131e6..212ea49d2f 100644 --- a/actioncable/test/subscription_adapter/base_test.rb +++ b/actioncable/test/subscription_adapter/base_test.rb @@ -1,5 +1,5 @@ -require 'test_helper' -require 'stubs/test_server' +require "test_helper" +require "stubs/test_server" class ActionCable::SubscriptionAdapter::BaseTest < ActionCable::TestCase ## TEST THAT ERRORS ARE RETURNED FOR INHERITORS THAT DON'T OVERRIDE METHODS @@ -15,58 +15,58 @@ class ActionCable::SubscriptionAdapter::BaseTest < ActionCable::TestCase test "#broadcast returns NotImplementedError by default" do assert_raises NotImplementedError do - BrokenAdapter.new(@server).broadcast('channel', 'payload') + BrokenAdapter.new(@server).broadcast("channel", "payload") end end test "#subscribe returns NotImplementedError by default" do - callback = lambda { puts 'callback' } - success_callback = lambda { puts 'success' } + callback = lambda { puts "callback" } + success_callback = lambda { puts "success" } assert_raises NotImplementedError do - BrokenAdapter.new(@server).subscribe('channel', callback, success_callback) + BrokenAdapter.new(@server).subscribe("channel", callback, success_callback) end end test "#unsubscribe returns NotImplementedError by default" do - callback = lambda { puts 'callback' } + callback = lambda { puts "callback" } assert_raises NotImplementedError do - BrokenAdapter.new(@server).unsubscribe('channel', callback) + BrokenAdapter.new(@server).unsubscribe("channel", callback) end end # TEST METHODS THAT ARE REQUIRED OF THE ADAPTER'S BACKEND STORAGE OBJECT test "#broadcast is implemented" do - broadcast = SuccessAdapter.new(@server).broadcast('channel', 'payload') + broadcast = SuccessAdapter.new(@server).broadcast("channel", "payload") assert_respond_to(SuccessAdapter.new(@server), :broadcast) - assert_nothing_raised NotImplementedError do + assert_nothing_raised do broadcast end end test "#subscribe is implemented" do - callback = lambda { puts 'callback' } - success_callback = lambda { puts 'success' } - subscribe = SuccessAdapter.new(@server).subscribe('channel', callback, success_callback) + callback = lambda { puts "callback" } + success_callback = lambda { puts "success" } + subscribe = SuccessAdapter.new(@server).subscribe("channel", callback, success_callback) assert_respond_to(SuccessAdapter.new(@server), :subscribe) - assert_nothing_raised NotImplementedError do + assert_nothing_raised do subscribe end end test "#unsubscribe is implemented" do - callback = lambda { puts 'callback' } - unsubscribe = SuccessAdapter.new(@server).unsubscribe('channel', callback) + callback = lambda { puts "callback" } + unsubscribe = SuccessAdapter.new(@server).unsubscribe("channel", callback) assert_respond_to(SuccessAdapter.new(@server), :unsubscribe) - assert_nothing_raised NotImplementedError do + assert_nothing_raised do unsubscribe end end diff --git a/actioncable/test/subscription_adapter/common.rb b/actioncable/test/subscription_adapter/common.rb index 361858784e..3aa88c2caa 100644 --- a/actioncable/test/subscription_adapter/common.rb +++ b/actioncable/test/subscription_adapter/common.rb @@ -1,29 +1,17 @@ -require 'test_helper' -require 'concurrent' +require "test_helper" +require "concurrent" -require 'active_support/core_ext/hash/indifferent_access' -require 'pathname' +require "active_support/core_ext/hash/indifferent_access" +require "pathname" module CommonSubscriptionAdapterTest WAIT_WHEN_EXPECTING_EVENT = 3 WAIT_WHEN_NOT_EXPECTING_EVENT = 0.2 def setup - # TODO: ActionCable requires a *lot* of setup at the moment... - ::Object.const_set(:ApplicationCable, Module.new) - ::ApplicationCable.const_set(:Connection, Class.new(ActionCable::Connection::Base)) - - ::Object.const_set(:Rails, Module.new) - ::Rails.singleton_class.send(:define_method, :root) { Pathname.new(__dir__) } - server = ActionCable::Server::Base.new - server.config = ActionCable::Server::Configuration.new - inner_logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN } - server.config.logger = ActionCable::Connection::TaggedLoggerProxy.new(inner_logger, tags: []) - - - # and now the "real" setup for our test: server.config.cable = cable_config.with_indifferent_access + server.config.logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN } adapter_klass = server.config.pubsub_adapter @@ -32,20 +20,9 @@ module CommonSubscriptionAdapterTest end def teardown - @tx_adapter.shutdown if @tx_adapter && @tx_adapter != @rx_adapter - @rx_adapter.shutdown if @rx_adapter - - begin - ::Object.send(:remove_const, :ApplicationCable) - rescue NameError - end - begin - ::Object.send(:remove_const, :Rails) - rescue NameError - end + [@rx_adapter, @tx_adapter].uniq.compact.each(&:shutdown) end - def subscribe_as_queue(channel, adapter = @rx_adapter) queue = Queue.new @@ -63,77 +40,76 @@ module CommonSubscriptionAdapterTest adapter.unsubscribe(channel, callback) if subscribed.set? end - def test_subscribe_and_unsubscribe - subscribe_as_queue('channel') do |queue| + subscribe_as_queue("channel") do |queue| end end def test_basic_broadcast - subscribe_as_queue('channel') do |queue| - @tx_adapter.broadcast('channel', 'hello world') + subscribe_as_queue("channel") do |queue| + @tx_adapter.broadcast("channel", "hello world") - assert_equal 'hello world', queue.pop + assert_equal "hello world", queue.pop end end def test_broadcast_after_unsubscribe keep_queue = nil - subscribe_as_queue('channel') do |queue| + subscribe_as_queue("channel") do |queue| keep_queue = queue - @tx_adapter.broadcast('channel', 'hello world') + @tx_adapter.broadcast("channel", "hello world") - assert_equal 'hello world', queue.pop + assert_equal "hello world", queue.pop end - @tx_adapter.broadcast('channel', 'hello void') + @tx_adapter.broadcast("channel", "hello void") sleep WAIT_WHEN_NOT_EXPECTING_EVENT assert_empty keep_queue end def test_multiple_broadcast - subscribe_as_queue('channel') do |queue| - @tx_adapter.broadcast('channel', 'bananas') - @tx_adapter.broadcast('channel', 'apples') + subscribe_as_queue("channel") do |queue| + @tx_adapter.broadcast("channel", "bananas") + @tx_adapter.broadcast("channel", "apples") received = [] 2.times { received << queue.pop } - assert_equal ['apples', 'bananas'], received.sort + assert_equal ["apples", "bananas"], received.sort end end def test_identical_subscriptions - subscribe_as_queue('channel') do |queue| - subscribe_as_queue('channel') do |queue_2| - @tx_adapter.broadcast('channel', 'hello') + subscribe_as_queue("channel") do |queue| + subscribe_as_queue("channel") do |queue_2| + @tx_adapter.broadcast("channel", "hello") - assert_equal 'hello', queue_2.pop + assert_equal "hello", queue_2.pop end - assert_equal 'hello', queue.pop + assert_equal "hello", queue.pop end end def test_simultaneous_subscriptions - subscribe_as_queue('channel') do |queue| - subscribe_as_queue('other channel') do |queue_2| - @tx_adapter.broadcast('channel', 'apples') - @tx_adapter.broadcast('other channel', 'oranges') + subscribe_as_queue("channel") do |queue| + subscribe_as_queue("other channel") do |queue_2| + @tx_adapter.broadcast("channel", "apples") + @tx_adapter.broadcast("other channel", "oranges") - assert_equal 'apples', queue.pop - assert_equal 'oranges', queue_2.pop + assert_equal "apples", queue.pop + assert_equal "oranges", queue_2.pop end end end def test_channel_filtered_broadcast - subscribe_as_queue('channel') do |queue| - @tx_adapter.broadcast('other channel', 'one') - @tx_adapter.broadcast('channel', 'two') + subscribe_as_queue("channel") do |queue| + @tx_adapter.broadcast("other channel", "one") + @tx_adapter.broadcast("channel", "two") - assert_equal 'two', queue.pop + assert_equal "two", queue.pop end end end diff --git a/actioncable/test/subscription_adapter/evented_redis_test.rb b/actioncable/test/subscription_adapter/evented_redis_test.rb index 70333e51bd..f316bc46ef 100644 --- a/actioncable/test/subscription_adapter/evented_redis_test.rb +++ b/actioncable/test/subscription_adapter/evented_redis_test.rb @@ -1,10 +1,29 @@ -require 'test_helper' -require_relative './common' +require "test_helper" +require_relative "./common" class EventedRedisAdapterTest < ActionCable::TestCase include CommonSubscriptionAdapterTest + def setup + super + + # em-hiredis is warning-rich + @previous_verbose, $VERBOSE = $VERBOSE, nil + end + + def teardown + super + + # Ensure EM is shut down before we re-enable warnings + EventMachine.reactor_thread.tap do |thread| + EventMachine.stop + thread.join + end + + $VERBOSE = @previous_verbose + end + def cable_config - { adapter: 'evented_redis', url: 'redis://127.0.0.1:6379/12' } + { adapter: "evented_redis", url: "redis://127.0.0.1:6379/12" } end end diff --git a/actioncable/test/subscription_adapter/inline_test.rb b/actioncable/test/subscription_adapter/inline_test.rb index 75ea51e6b3..52bfa00aba 100644 --- a/actioncable/test/subscription_adapter/inline_test.rb +++ b/actioncable/test/subscription_adapter/inline_test.rb @@ -1,5 +1,5 @@ -require 'test_helper' -require_relative './common' +require "test_helper" +require_relative "./common" class InlineAdapterTest < ActionCable::TestCase include CommonSubscriptionAdapterTest @@ -12,6 +12,6 @@ class InlineAdapterTest < ActionCable::TestCase end def cable_config - { adapter: 'inline' } + { adapter: "inline" } end end diff --git a/actioncable/test/subscription_adapter/postgresql_test.rb b/actioncable/test/subscription_adapter/postgresql_test.rb index 64c632b0cd..beb6efab28 100644 --- a/actioncable/test/subscription_adapter/postgresql_test.rb +++ b/actioncable/test/subscription_adapter/postgresql_test.rb @@ -1,22 +1,30 @@ -require 'test_helper' -require_relative './common' +require "test_helper" +require_relative "./common" -require 'active_record' +require "active_record" class PostgresqlAdapterTest < ActionCable::TestCase include CommonSubscriptionAdapterTest def setup - database_config = { 'adapter' => 'postgresql', 'database' => 'activerecord_unittest' } - ar_tests = File.expand_path('../../../activerecord/test', __dir__) + database_config = { "adapter" => "postgresql", "database" => "activerecord_unittest" } + ar_tests = File.expand_path("../../../activerecord/test", __dir__) if Dir.exist?(ar_tests) - require File.join(ar_tests, 'config') - require File.join(ar_tests, 'support/config') - local_config = ARTest.config['arunit'] + require File.join(ar_tests, "config") + require File.join(ar_tests, "support/config") + local_config = ARTest.config["arunit"] database_config.update local_config if local_config end + ActiveRecord::Base.establish_connection database_config + begin + ActiveRecord::Base.connection + rescue + @rx_adapter = @tx_adapter = nil + skip "Couldn't connect to PostgreSQL: #{database_config.inspect}" + end + super end @@ -27,6 +35,6 @@ class PostgresqlAdapterTest < ActionCable::TestCase end def cable_config - { adapter: 'postgresql' } + { adapter: "postgresql" } end end diff --git a/actioncable/test/subscription_adapter/redis_test.rb b/actioncable/test/subscription_adapter/redis_test.rb index 4f34dd86c9..2ba5636656 100644 --- a/actioncable/test/subscription_adapter/redis_test.rb +++ b/actioncable/test/subscription_adapter/redis_test.rb @@ -1,16 +1,16 @@ -require 'test_helper' -require_relative './common' +require "test_helper" +require_relative "./common" class RedisAdapterTest < ActionCable::TestCase include CommonSubscriptionAdapterTest def cable_config - { adapter: 'redis', driver: 'ruby', url: 'redis://127.0.0.1:6379/12' } + { adapter: "redis", driver: "ruby", url: "redis://127.0.0.1:6379/12" } end end class RedisAdapterTest::Hiredis < RedisAdapterTest def cable_config - super.merge(driver: 'hiredis') + super.merge(driver: "hiredis") end end diff --git a/actioncable/test/subscription_adapter/subscriber_map_test.rb b/actioncable/test/subscription_adapter/subscriber_map_test.rb new file mode 100644 index 0000000000..76b984c849 --- /dev/null +++ b/actioncable/test/subscription_adapter/subscriber_map_test.rb @@ -0,0 +1,17 @@ +require "test_helper" + +class SubscriberMapTest < ActionCable::TestCase + test "broadcast should not change subscribers" do + setup_subscription_map + origin = @subscription_map.instance_variable_get(:@subscribers).dup + + @subscription_map.broadcast("not_exist_channel", "") + + assert_equal origin, @subscription_map.instance_variable_get(:@subscribers) + end + + private + def setup_subscription_map + @subscription_map = ActionCable::SubscriptionAdapter::SubscriberMap.new + end +end diff --git a/actioncable/test/test_helper.rb b/actioncable/test/test_helper.rb index 8ddbd4e764..a47032753b 100644 --- a/actioncable/test/test_helper.rb +++ b/actioncable/test/test_helper.rb @@ -1,28 +1,35 @@ -require File.expand_path('../../../load_paths', __FILE__) +require "action_cable" +require "active_support/testing/autorun" -require 'action_cable' -require 'active_support/testing/autorun' +require "puma" +require "mocha/setup" +require "rack/mock" - -require 'puma' - -require 'mocha/setup' - -require 'rack/mock' +begin + require "byebug" +rescue LoadError +end # Require all the stubs and models -Dir[File.dirname(__FILE__) + '/stubs/*.rb'].each {|file| require file } +Dir[File.dirname(__FILE__) + "/stubs/*.rb"].each { |file| require file } class ActionCable::TestCase < ActiveSupport::TestCase def wait_for_async - e = Concurrent.global_io_executor - until e.completed_task_count == e.scheduled_task_count - sleep 0.1 - end + wait_for_executor Concurrent.global_io_executor end def run_in_eventmachine yield wait_for_async end + + def wait_for_executor(executor) + # do not wait forever, wait 2s + timeout = 2 + until executor.completed_task_count == executor.scheduled_task_count + sleep 0.1 + timeout -= 0.1 + raise "Executor could not complete all tasks in 2 seconds" unless timeout > 0 + end + end end diff --git a/actioncable/test/worker_test.rb b/actioncable/test/worker_test.rb index 4a699cde27..63dc453840 100644 --- a/actioncable/test/worker_test.rb +++ b/actioncable/test/worker_test.rb @@ -1,4 +1,4 @@ -require 'test_helper' +require "test_helper" class WorkerTest < ActiveSupport::TestCase class Receiver @@ -17,7 +17,9 @@ class WorkerTest < ActiveSupport::TestCase end def logger - ActionCable.server.logger + # Impersonating a connection requires a TaggedLoggerProxy'ied logger. + inner_logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN } + ActionCable::Connection::TaggedLoggerProxy.new(inner_logger, tags: []) end end @@ -31,22 +33,12 @@ class WorkerTest < ActiveSupport::TestCase end test "invoke" do - @worker.invoke @receiver, :run + @worker.invoke @receiver, :run, connection: @receiver.connection assert_equal :run, @receiver.last_action end test "invoke with arguments" do - @worker.invoke @receiver, :process, "Hello" + @worker.invoke @receiver, :process, "Hello", connection: @receiver.connection assert_equal [ :process, "Hello" ], @receiver.last_action end - - test "running periodic timers with a proc" do - @worker.run_periodic_timer @receiver, @receiver.method(:run) - assert_equal :run, @receiver.last_action - end - - test "running periodic timers with a method" do - @worker.run_periodic_timer @receiver, :run - assert_equal :run, @receiver.last_action - end end diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index 22e9bd12a1..3b9f503a0b 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,99 +1,6 @@ -## Rails 5.0.0.beta2 (February 01, 2016) ## +* Exception handling: use `rescue_from` to handle exceptions raised by + mailer actions, by message delivery, and by deferred delivery jobs. -* No changes. + *Jeremy Daer* - -## Rails 5.0.0.beta1 (December 18, 2015) ## - -* `config.force_ssl = true` will set - `config.action_mailer.default_url_options = { protocol: 'https' }`. - - *Andrew Kampjes* - -* Add `config.action_mailer.deliver_later_queue_name` configuration to set the - mailer queue name. - - *Chris McGrath* - -* `assert_emails` in block form use the given number as expected value. - This makes the error message much easier to understand. - - *Yuji Yaginuma* - -* Add support for inline images in mailer previews by using an interceptor - class to convert cid: urls in image src attributes to data urls. - - *Andrew White* - -* Mailer preview now uses `url_for` to fix links to emails for apps running on - a subdirectory. - - *Remo Mueller* - -* Mailer previews no longer crash when the `mail` method wasn't called - (`NullMail`). - - Fixes #19849. - - *Yves Senn* - -* Make sure labels and values line up in mailer previews. - - *Yves Senn* - -* Add `assert_enqueued_emails` and `assert_no_enqueued_emails`. - - Example: - - def test_emails - assert_enqueued_emails 2 do - ContactMailer.welcome.deliver_later - ContactMailer.welcome.deliver_later - end - end - - def test_no_emails - assert_no_enqueued_emails do - # No emails enqueued here - end - end - - *George Claghorn* - -* Add `_mailer` suffix to mailers created via generator, following the same - naming convention used in controllers and jobs. - - *Carlos Souza* - -* Remove deprecated `*_path` helpers in email views. - - *Rafael Mendonça França* - -* Remove deprecated `deliver` and `deliver!` methods. - - *claudiob* - -* Template lookup now respects default locale and I18n fallbacks. - - Given the following templates: - - mailer/demo.html.erb - mailer/demo.en.html.erb - mailer/demo.pt.html.erb - - Before this change, for a locale that doesn't have its associated file, the - `mailer/demo.html.erb` would be rendered even if `en` was the default locale. - - Now `mailer/demo.en.html.erb` has precedence over the file without locale. - - Also, it is possible to give a fallback. - - mailer/demo.pt.html.erb - mailer/demo.pt-BR.html.erb - - So if the locale is `pt-PT`, `mailer/demo.pt.html.erb` will be rendered given - the right I18n fallback configuration. - - *Rafael Mendonça França* - -Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/actionmailer/CHANGELOG.md) for previous changes. +Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/actionmailer/CHANGELOG.md) for previous changes. diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile index 54e6cff48d..6f05d236d9 100644 --- a/actionmailer/Rakefile +++ b/actionmailer/Rakefile @@ -1,15 +1,14 @@ -require 'rake/testtask' +require "rake/testtask" desc "Default Task" task default: [ :test ] task :package -task "package:clean" # Run the unit tests Rake::TestTask.new { |t| t.libs << "test" - t.pattern = 'test/**/*_test.rb' + t.pattern = "test/**/*_test.rb" t.warning = true t.verbose = true t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) @@ -18,7 +17,7 @@ Rake::TestTask.new { |t| namespace :test do task :isolated do Dir.glob("test/**/*_test.rb").all? do |file| - sh(Gem.ruby, '-w', '-Ilib:test', file) - end or raise "Failures" + sh(Gem.ruby, "-w", "-Ilib:test", file) + end || raise("Failures") end end diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec index fa6043fdd7..e75dae6cf9 100644 --- a/actionmailer/actionmailer.gemspec +++ b/actionmailer/actionmailer.gemspec @@ -1,28 +1,28 @@ -version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip +version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY - s.name = 'actionmailer' + s.name = "actionmailer" s.version = version - s.summary = 'Email composition, delivery, and receiving framework (part of Rails).' - s.description = 'Email on Rails. Compose, deliver, receive, and test emails using the familiar controller/view pattern. First-class support for multipart email and attachments.' + s.summary = "Email composition, delivery, and receiving framework (part of Rails)." + s.description = "Email on Rails. Compose, deliver, receive, and test emails using the familiar controller/view pattern. First-class support for multipart email and attachments." - s.required_ruby_version = '>= 2.2.2' + s.required_ruby_version = ">= 2.2.2" - s.license = 'MIT' + s.license = "MIT" - s.author = 'David Heinemeier Hansson' - s.email = 'david@loudthinking.com' - s.homepage = 'http://rubyonrails.org' + s.author = "David Heinemeier Hansson" + s.email = "david@loudthinking.com" + s.homepage = "http://rubyonrails.org" - s.files = Dir['CHANGELOG.md', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*'] - s.require_path = 'lib' - s.requirements << 'none' + s.files = Dir["CHANGELOG.md", "README.rdoc", "MIT-LICENSE", "lib/**/*"] + s.require_path = "lib" + s.requirements << "none" - s.add_dependency 'actionpack', version - s.add_dependency 'actionview', version - s.add_dependency 'activejob', version + s.add_dependency "actionpack", version + s.add_dependency "actionview", version + s.add_dependency "activejob", version - s.add_dependency 'mail', ['~> 2.5', '>= 2.5.4'] - s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.5' + s.add_dependency "mail", ["~> 2.5", ">= 2.5.4"] + s.add_dependency "rails-dom-testing", "~> 2.0" end diff --git a/actionmailer/bin/test b/actionmailer/bin/test index 404cabba51..84a05bba08 100755 --- a/actionmailer/bin/test +++ b/actionmailer/bin/test @@ -1,4 +1,6 @@ #!/usr/bin/env ruby -COMPONENT_ROOT = File.expand_path("../../", __FILE__) + +COMPONENT_ROOT = File.expand_path("..", __dir__) require File.expand_path("../tools/test", COMPONENT_ROOT) + exit Minitest.run(ARGV) diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb index 55c017e338..668bc99435 100644 --- a/actionmailer/lib/action_mailer.rb +++ b/actionmailer/lib/action_mailer.rb @@ -21,15 +21,15 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ -require 'abstract_controller' -require 'action_mailer/version' +require "abstract_controller" +require "action_mailer/version" # Common Active Support usage in Action Mailer -require 'active_support/rails' -require 'active_support/core_ext/class' -require 'active_support/core_ext/module/attr_internal' -require 'active_support/core_ext/string/inflections' -require 'active_support/lazy_load_hooks' +require "active_support/rails" +require "active_support/core_ext/class" +require "active_support/core_ext/module/attr_internal" +require "active_support/core_ext/string/inflections" +require "active_support/lazy_load_hooks" module ActionMailer extend ::ActiveSupport::Autoload @@ -43,14 +43,14 @@ module ActionMailer autoload :InlinePreviewInterceptor autoload :MailHelper autoload :Preview - autoload :Previews, 'action_mailer/preview' + autoload :Previews, "action_mailer/preview" autoload :TestCase autoload :TestHelper autoload :MessageDelivery autoload :DeliveryJob end -autoload :Mime, 'action_dispatch/http/mime_type' +autoload :Mime, "action_dispatch/http/mime_type" ActiveSupport.on_load(:action_view) do ActionView::Base.default_formats ||= Mime::SET.symbols diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index bb3cb1be45..e9966c7ff5 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -1,10 +1,11 @@ -require 'mail' -require 'action_mailer/collector' -require 'active_support/core_ext/string/inflections' -require 'active_support/core_ext/hash/except' -require 'active_support/core_ext/module/anonymous' +require "mail" +require "action_mailer/collector" +require "active_support/core_ext/string/inflections" +require "active_support/core_ext/hash/except" +require "active_support/core_ext/module/anonymous" -require 'action_mailer/log_subscriber' +require "action_mailer/log_subscriber" +require "action_mailer/rescuable" module ActionMailer # Action Mailer allows you to send email from your application using a mailer model and views. @@ -86,7 +87,7 @@ module ActionMailer # Like Action Controller, each mailer class has a corresponding view directory in which each # method of the class looks for a template with its name. # - # To define a template to be used with a mailing, create an <tt>.erb</tt> file with the same + # To define a template to be used with a mailer, create an <tt>.erb</tt> file with the same # name as the method in your mailer model. For example, in the mailer defined above, the template at # <tt>app/views/notifier_mailer/welcome.text.erb</tt> would be used to generate the email. # @@ -144,7 +145,7 @@ module ActionMailer # mail.deliver_now # generates and sends the email now # # The <tt>ActionMailer::MessageDelivery</tt> class is a wrapper around a delegate that will call - # your method to generate the mail. If you want direct access to delegator, or <tt>Mail::Message</tt>, + # your method to generate the mail. If you want direct access to the delegator, or <tt>Mail::Message</tt>, # you can call the <tt>message</tt> method on the <tt>ActionMailer::MessageDelivery</tt> object. # # NotifierMailer.welcome(User.first).message # => a Mail::Message object @@ -163,7 +164,7 @@ module ActionMailer # # Multipart messages can also be used implicitly because Action Mailer will automatically detect and use # multipart templates, where each template is named after the name of the action, followed by the content - # type. Each such detected template will be added as a separate part to the message. + # type. Each such detected template will be added to the message, as a separate part. # # For example, if the following templates exist: # * signup_notification.text.erb @@ -288,8 +289,8 @@ module ActionMailer # end # # Note that the proc is evaluated right at the start of the mail message generation, so if you - # set something in the default using a proc, and then set the same thing inside of your - # mailer method, it will get over written by the mailer method. + # 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. # # It is also possible to set these default options that will be used in all mailers through # the <tt>default_options=</tt> configuration in <tt>config/application.rb</tt>: @@ -389,13 +390,13 @@ module ActionMailer # to use it. Defaults to <tt>true</tt>. # * <tt>:openssl_verify_mode</tt> - When using TLS, you can set how OpenSSL checks the certificate. This is # really useful if you need to validate a self-signed and/or a wildcard certificate. You can use the name - # of an OpenSSL verify constant (<tt>'none'</tt>, <tt>'peer'</tt>, <tt>'client_once'</tt>, - # <tt>'fail_if_no_peer_cert'</tt>) or directly the constant (<tt>OpenSSL::SSL::VERIFY_NONE</tt>, - # <tt>OpenSSL::SSL::VERIFY_PEER</tt>, ...). + # of an OpenSSL verify constant (<tt>'none'</tt> or <tt>'peer'</tt>) or directly the constant + # (<tt>OpenSSL::SSL::VERIFY_NONE</tt> or <tt>OpenSSL::SSL::VERIFY_PEER</tt>). + # <tt>:ssl/:tls</tt> Enables the SMTP connection to use SMTP/TLS (SMTPS: SMTP over direct TLS connection) # # * <tt>sendmail_settings</tt> - Allows you to override options for the <tt>:sendmail</tt> delivery method. # * <tt>:location</tt> - The location of the sendmail executable. Defaults to <tt>/usr/sbin/sendmail</tt>. - # * <tt>:arguments</tt> - The command line arguments. Defaults to <tt>-i -t</tt> with <tt>-f sender@address</tt> + # * <tt>:arguments</tt> - The command line arguments. Defaults to <tt>-i</tt> with <tt>-f sender@address</tt> # added automatically before the message is sent. # # * <tt>file_settings</tt> - Allows you to override options for the <tt>:file</tt> delivery method. @@ -419,6 +420,7 @@ module ActionMailer # * <tt>deliver_later_queue_name</tt> - The name of the queue used with <tt>deliver_later</tt>. class Base < AbstractController::Base include DeliveryMethods + include Rescuable include Previews abstract! @@ -430,6 +432,7 @@ module ActionMailer include AbstractController::Translation include AbstractController::AssetPaths include AbstractController::Callbacks + include AbstractController::Caching include ActionView::Layouts @@ -484,7 +487,7 @@ module ActionMailer end private :observer_class_for - # Returns the name of current mailer. This method is also being used as a path for a view lookup. + # Returns the name of the current mailer. This method is also being used as a path for a view lookup. # If this is an anonymous mailer, this method will return +anonymous+ instead. def mailer_name @mailer_name ||= anonymous? ? "anonymous" : name.underscore @@ -595,7 +598,7 @@ module ActionMailer end class NullMail #:nodoc: - def body; '' end + def body; "" end def header; {} end def respond_to?(string, include_all=false) @@ -664,7 +667,7 @@ module ActionMailer # # You can also specify overrides if you want by passing a hash instead of a string: # - # mail.attachments['filename.jpg'] = {mime_type: 'application/x-gzip', + # mail.attachments['filename.jpg'] = {mime_type: 'application/gzip', # content: File.read('/path/to/filename.jpg')} # # If you want to use encoding other than Base64 then you will need to pass encoding @@ -672,7 +675,7 @@ module ActionMailer # data: # # file_content = SpecialEncode(File.read('/path/to/filename.jpg')) - # mail.attachments['filename.jpg'] = {mime_type: 'application/x-gzip', + # mail.attachments['filename.jpg'] = {mime_type: 'application/gzip', # encoding: 'SpecialEncoding', # content: file_content } # @@ -829,124 +832,136 @@ module ActionMailer protected - # Used by #mail to set the content type of the message. - # - # It will use the given +user_content_type+, or multipart if the mail - # message has any attachments. If the attachments are inline, the content - # type will be "multipart/related", otherwise "multipart/mixed". - # - # 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) - params = m.content_type_parameters || {} - case - when user_content_type.present? - user_content_type - when m.has_attachments? - if m.attachments.detect(&:inline?) - ["multipart", "related", params] + # Used by #mail to set the content type of the message. + # + # It will use the given +user_content_type+, or multipart if the mail + # message has any attachments. If the attachments are inline, the content + # type will be "multipart/related", otherwise "multipart/mixed". + # + # 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) + params = m.content_type_parameters || {} + case + when user_content_type.present? + user_content_type + when m.has_attachments? + if m.attachments.detect(&:inline?) + ["multipart", "related", params] + else + ["multipart", "mixed", params] + end + when m.multipart? + ["multipart", "alternative", params] else - ["multipart", "mixed", params] + m.content_type || class_default end - when m.multipart? - ["multipart", "alternative", params] - else - m.content_type || class_default end - end - # Translates the +subject+ using Rails I18n class under <tt>[mailer_scope, action_name]</tt> scope. - # 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 = {}) - mailer_scope = self.class.mailer_name.tr('/', '.') - I18n.t(:subject, interpolations.merge(scope: [mailer_scope, action_name], default: action_name.humanize)) - end + # Translates the +subject+ using Rails I18n class under <tt>[mailer_scope, action_name]</tt> scope. + # 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 = {}) + 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? - false - end + # Emails do not support relative path links. + def self.supports_path? + 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 - ] - end.to_h - - headers_with_defaults = headers.reverse_merge(default_values) - headers_with_defaults[:subject] ||= default_i18n_subject - headers_with_defaults - end + def apply_defaults(headers) + default_values = self.class.default.map do |key, value| + [ + key, + value.is_a?(Proc) ? instance_eval(&value) : value + ] + end.to_h + + headers_with_defaults = headers.reverse_merge(default_values) + headers_with_defaults[:subject] ||= default_i18n_subject + headers_with_defaults + end - def assign_headers_to_message(message, headers) - assignable = headers.except(:parts_order, :content_type, :body, :template_name, - :template_path, :delivery_method, :delivery_method_options) - assignable.each { |k, v| message[k] = v } - end + def assign_headers_to_message(message, headers) + assignable = headers.except(:parts_order, :content_type, :body, :template_name, + :template_path, :delivery_method, :delivery_method_options) + assignable.each { |k, v| message[k] = v } + end - def collect_responses(headers) - if block_given? - collector = ActionMailer::Collector.new(lookup_context) { render(action_name) } - yield(collector) - collector.responses - elsif headers[:body] - [{ - body: headers.delete(:body), - content_type: self.class.default[:content_type] || "text/plain" - }] - else - collect_responses_from_templates(headers) + def collect_responses(headers) + if block_given? + collector = ActionMailer::Collector.new(lookup_context) { render(action_name) } + yield(collector) + collector.responses + elsif headers[:body] + [{ + body: headers.delete(:body), + content_type: self.class.default[:content_type] || "text/plain" + }] + else + collect_responses_from_templates(headers) + end end - end - def collect_responses_from_templates(headers) - templates_path = headers[:template_path] || self.class.mailer_name - templates_name = headers[:template_name] || action_name + def collect_responses_from_templates(headers) + templates_path = headers[:template_path] || self.class.mailer_name + templates_name = headers[:template_name] || action_name - each_template(Array(templates_path), templates_name).map do |template| - self.formats = template.formats - { - body: render(template: template), - content_type: template.type.to_s - } + each_template(Array(templates_path), templates_name).map do |template| + self.formats = template.formats + { + body: render(template: template), + content_type: template.type.to_s + } + end end - end - def each_template(paths, name, &block) - templates = lookup_context.find_all(name, paths) - if templates.empty? - raise ActionView::MissingTemplate.new(paths, name, paths, false, 'mailer') - else - templates.uniq(&:formats).each(&block) + def each_template(paths, name, &block) + templates = lookup_context.find_all(name, paths) + if templates.empty? + raise ActionView::MissingTemplate.new(paths, name, paths, false, "mailer") + else + templates.uniq(&:formats).each(&block) + end end - end - def create_parts_from_responses(m, responses) - if responses.size == 1 && !m.has_attachments? - responses[0].each { |k,v| m[k] = v } - elsif responses.size > 1 && m.has_attachments? - container = Mail::Part.new - container.content_type = "multipart/alternative" - responses.each { |r| insert_part(container, r, m.charset) } - m.add_part(container) - else - responses.each { |r| insert_part(m, r, m.charset) } + def create_parts_from_responses(m, responses) + if responses.size == 1 && !m.has_attachments? + responses[0].each { |k,v| m[k] = v } + elsif responses.size > 1 && m.has_attachments? + container = Mail::Part.new + container.content_type = "multipart/alternative" + responses.each { |r| insert_part(container, r, m.charset) } + m.add_part(container) + else + responses.each { |r| insert_part(m, r, m.charset) } + end end - end - def insert_part(container, response, charset) - response[:charset] ||= charset - part = Mail::Part.new(response) - container.add_part(part) - end + def insert_part(container, response, charset) + response[:charset] ||= charset + part = Mail::Part.new(response) + container.add_part(part) + end + + # This and #instrument_name is for caching instrument + def instrument_payload(key) + { + mailer: mailer_name, + key: key + } + end + + def instrument_name + "action_mailer" + end - ActiveSupport.run_load_hooks(:action_mailer, self) + ActiveSupport.run_load_hooks(:action_mailer, self) end end diff --git a/actionmailer/lib/action_mailer/collector.rb b/actionmailer/lib/action_mailer/collector.rb index e8883a8235..d97a73d65a 100644 --- a/actionmailer/lib/action_mailer/collector.rb +++ b/actionmailer/lib/action_mailer/collector.rb @@ -1,6 +1,6 @@ -require 'abstract_controller/collector' -require 'active_support/core_ext/hash/reverse_merge' -require 'active_support/core_ext/array/extract_options' +require "abstract_controller/collector" +require "active_support/core_ext/hash/reverse_merge" +require "active_support/core_ext/array/extract_options" module ActionMailer class Collector diff --git a/actionmailer/lib/action_mailer/delivery_job.rb b/actionmailer/lib/action_mailer/delivery_job.rb index 52772af2d3..a617daa87e 100644 --- a/actionmailer/lib/action_mailer/delivery_job.rb +++ b/actionmailer/lib/action_mailer/delivery_job.rb @@ -1,13 +1,34 @@ -require 'active_job' +require "active_job" module ActionMailer # The <tt>ActionMailer::DeliveryJob</tt> class is used when you # want to send emails outside of the request-response cycle. + # + # Exceptions are rescued and handled by the mailer class. class DeliveryJob < ActiveJob::Base # :nodoc: queue_as { ActionMailer::Base.deliver_later_queue_name } + rescue_from StandardError, with: :handle_exception_with_mailer_class + def perform(mailer, mail_method, delivery_method, *args) #:nodoc: mailer.constantize.public_send(mail_method, *args).send(delivery_method) end + + private + # "Deserialize" the mailer class name by hand in case another argument + # (like a Global ID reference) raised DeserializationError. + def mailer_class + if mailer = Array(@serialized_arguments).first || Array(arguments).first + mailer.constantize + end + end + + def handle_exception_with_mailer_class(exception) + if klass = mailer_class + klass.handle_exception exception + else + raise exception + end + end end end diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb index 4758b55a2a..6be2c91da6 100644 --- a/actionmailer/lib/action_mailer/delivery_methods.rb +++ b/actionmailer/lib/action_mailer/delivery_methods.rb @@ -1,4 +1,4 @@ -require 'tmpdir' +require "tmpdir" module ActionMailer # This module handles everything related to mail delivery, from registering @@ -25,7 +25,7 @@ module ActionMailer add_delivery_method :smtp, Mail::SMTP, address: "localhost", port: 25, - domain: 'localhost.localdomain', + domain: "localhost.localdomain", user_name: nil, password: nil, authentication: nil, @@ -35,8 +35,8 @@ module ActionMailer location: defined?(Rails.root) ? "#{Rails.root}/tmp/mails" : "#{Dir.tmpdir}/mails" add_delivery_method :sendmail, Mail::Sendmail, - location: '/usr/sbin/sendmail', - arguments: '-i -t' + location: "/usr/sbin/sendmail", + arguments: "-i" add_delivery_method :test, Mail::TestMailer end @@ -51,7 +51,7 @@ module ActionMailer # # add_delivery_method :sendmail, Mail::Sendmail, # location: '/usr/sbin/sendmail', - # arguments: '-i -t' + # arguments: '-i' def add_delivery_method(symbol, klass, default_options={}) class_attribute(:"#{symbol}_settings") unless respond_to?(:"#{symbol}_settings") send(:"#{symbol}_settings=", default_options) diff --git a/actionmailer/lib/action_mailer/gem_version.rb b/actionmailer/lib/action_mailer/gem_version.rb index a1ee5fb238..7dafceef2b 100644 --- a/actionmailer/lib/action_mailer/gem_version.rb +++ b/actionmailer/lib/action_mailer/gem_version.rb @@ -6,9 +6,9 @@ module ActionMailer module VERSION MAJOR = 5 - MINOR = 0 + MINOR = 1 TINY = 0 - PRE = "beta2" + PRE = "alpha" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actionmailer/lib/action_mailer/inline_preview_interceptor.rb b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb index 6d02b39225..b7318f0092 100644 --- a/actionmailer/lib/action_mailer/inline_preview_interceptor.rb +++ b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb @@ -1,9 +1,9 @@ -require 'base64' +require "base64" module ActionMailer # Implements a mailer preview interceptor that converts image tag src attributes # that use inline cid: style urls to data: style urls so that they are visible - # when previewing a HTML email in a web browser. + # when previewing an HTML email in a web browser. # # This interceptor is enabled by default. To disable it, delete it from the # <tt>ActionMailer::Base.preview_interceptors</tt> array: @@ -55,7 +55,7 @@ module ActionMailer end def find_part(cid) - message.all_parts.find{ |p| p.attachment? && p.cid == cid } + message.all_parts.find { |p| p.attachment? && p.cid == cid } end end end diff --git a/actionmailer/lib/action_mailer/log_subscriber.rb b/actionmailer/lib/action_mailer/log_subscriber.rb index 2867bf90fb..2c058ccf66 100644 --- a/actionmailer/lib/action_mailer/log_subscriber.rb +++ b/actionmailer/lib/action_mailer/log_subscriber.rb @@ -1,4 +1,4 @@ -require 'active_support/log_subscriber' +require "active_support/log_subscriber" module ActionMailer # Implements the ActiveSupport::LogSubscriber for logging notifications when @@ -7,7 +7,7 @@ module ActionMailer # An email was delivered. def deliver(event) info do - recipients = Array(event.payload[:to]).join(', ') + recipients = Array(event.payload[:to]).join(", ") "Sent mail to #{recipients} (#{event.duration.round(1)}ms)" end diff --git a/actionmailer/lib/action_mailer/mail_helper.rb b/actionmailer/lib/action_mailer/mail_helper.rb index 239974e7b1..e04fc08866 100644 --- a/actionmailer/lib/action_mailer/mail_helper.rb +++ b/actionmailer/lib/action_mailer/mail_helper.rb @@ -54,7 +54,7 @@ module ActionMailer sentences = [[]] text.split.each do |word| - if sentences.first.present? && (sentences.last + [word]).join(' ').length > len + if sentences.first.present? && (sentences.last + [word]).join(" ").length > len sentences << [word] else sentences.last << word diff --git a/actionmailer/lib/action_mailer/message_delivery.rb b/actionmailer/lib/action_mailer/message_delivery.rb index 5fcb5a0c88..994d297768 100644 --- a/actionmailer/lib/action_mailer/message_delivery.rb +++ b/actionmailer/lib/action_mailer/message_delivery.rb @@ -1,7 +1,6 @@ -require 'delegate' +require "delegate" module ActionMailer - # The <tt>ActionMailer::MessageDelivery</tt> class is used by # <tt>ActionMailer::Base</tt> when creating a new mailer. # <tt>MessageDelivery</tt> is a wrapper (+Delegator+ subclass) around a lazy @@ -14,29 +13,35 @@ module ActionMailer # Notifier.welcome(User.first).deliver_later # enqueue email delivery as a job through Active Job # Notifier.welcome(User.first).message # a Mail::Message object class MessageDelivery < Delegator - def initialize(mailer, mail_method, *args) #:nodoc: - @mailer = mailer - @mail_method = mail_method - @args = args + def initialize(mailer_class, action, *args) #:nodoc: + @mailer_class, @action, @args = mailer_class, action, args + + # The mail is only processed if we try to call any methods on it. + # Typical usage will leave it unloaded and call deliver_later. + @processed_mailer = nil + @mail_message = nil end + # Method calls are delegated to the Mail::Message that's ready to deliver. def __getobj__ #:nodoc: - @obj ||= begin - mailer = @mailer.new - mailer.process @mail_method, *@args - mailer.message - end + @mail_message ||= processed_mailer.message end - def __setobj__(obj) #:nodoc: - @obj = obj + # Unused except for delegator internals (dup, marshaling). + def __setobj__(mail_message) #:nodoc: + @mail_message = mail_message end - # Returns the Mail::Message object + # Returns the resulting Mail::Message def message __getobj__ end + # Was the delegate loaded, causing the mailer action to be processed? + def processed? + @processed_mailer || @mail_message + end + # Enqueues the email to be delivered through Active Job. When the # job runs it will send the email using +deliver_now!+. That means # that the message will be sent bypassing checking +perform_deliveries+ @@ -77,7 +82,9 @@ module ActionMailer # Notifier.welcome(User.first).deliver_now! # def deliver_now! - message.deliver! + processed_mailer.handle_exceptions do + message.deliver! + end end # Delivers an email: @@ -85,14 +92,34 @@ module ActionMailer # Notifier.welcome(User.first).deliver_now # def deliver_now - message.deliver + processed_mailer.handle_exceptions do + message.deliver + end end private + # Returns the processed Mailer instance. We keep this instance + # on hand so we can delegate exception handling to it. + def processed_mailer + @processed_mailer ||= @mailer_class.new.tap do |mailer| + mailer.process @action, *@args + end + end def enqueue_delivery(delivery_method, options={}) - args = @mailer.name, @mail_method.to_s, delivery_method.to_s, *@args - ActionMailer::DeliveryJob.set(options).perform_later(*args) + if processed? + ::Kernel.raise "You've accessed the message before asking to " \ + "deliver it later, so you may have made local changes that would " \ + "be silently lost if we enqueued a job to deliver it. Why? Only " \ + "the mailer method *arguments* are passed with the delivery job! " \ + "Do not access the message in any way if you mean to deliver it " \ + "later. Workarounds: 1. don't touch the message before calling " \ + "#deliver_later, 2. only touch the message *within your mailer " \ + "method*, or 3. use a custom Active Job instead of #deliver_later." + else + args = @mailer_class.name, @action.to_s, delivery_method.to_s, *@args + ::ActionMailer::DeliveryJob.set(options).perform_later(*args) + end end end end diff --git a/actionmailer/lib/action_mailer/preview.rb b/actionmailer/lib/action_mailer/preview.rb index aab92fe8db..93a11453bf 100644 --- a/actionmailer/lib/action_mailer/preview.rb +++ b/actionmailer/lib/action_mailer/preview.rb @@ -1,4 +1,4 @@ -require 'active_support/descendants_tracker' +require "active_support/descendants_tracker" module ActionMailer module Previews #:nodoc: @@ -34,7 +34,8 @@ module ActionMailer # Either a class or a string can be passed in as the Interceptor. If a # string is passed in it will be <tt>constantize</tt>d. def register_preview_interceptor(interceptor) - preview_interceptor = case interceptor + preview_interceptor = \ + case interceptor when String, Symbol interceptor.to_s.camelize.constantize else @@ -80,23 +81,23 @@ module ActionMailer # Returns true if the preview exists. def exists?(preview) - all.any?{ |p| p.preview_name == preview } + all.any? { |p| p.preview_name == preview } end # Find a mailer preview by its underscored class name. def find(preview) - all.find{ |p| p.preview_name == preview } + all.find { |p| p.preview_name == preview } end # Returns the underscored name of the mailer preview without the suffix. def preview_name - name.sub(/Preview$/, '').underscore + name.sub(/Preview$/, "").underscore end protected def load_previews #:nodoc: if preview_path - Dir["#{preview_path}/**/*_preview.rb"].each{ |file| require_dependency file } + Dir["#{preview_path}/**/*_preview.rb"].each { |file| require_dependency file } end end diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb index fa707021c7..c47d7781cc 100644 --- a/actionmailer/lib/action_mailer/railtie.rb +++ b/actionmailer/lib/action_mailer/railtie.rb @@ -1,4 +1,4 @@ -require 'active_job/railtie' +require "active_job/railtie" require "action_mailer" require "rails" require "abstract_controller/railties/routes_helpers" @@ -18,13 +18,14 @@ module ActionMailer if app.config.force_ssl options.default_url_options ||= {} - options.default_url_options[:protocol] ||= 'https' + options.default_url_options[:protocol] ||= "https" end options.assets_dir ||= paths["public"].first options.javascripts_dir ||= paths["public/javascripts"].first options.stylesheets_dir ||= paths["public/stylesheets"].first options.show_previews = Rails.env.development? if options.show_previews.nil? + options.cache_store ||= Rails.cache if options.show_previews options.preview_path ||= defined?(Rails.root) ? "#{Rails.root}/test/mailers/previews" : nil @@ -44,14 +45,9 @@ module ActionMailer register_observers(options.delete(:observers)) options.each { |k,v| send("#{k}=", v) } - - if options.show_previews - app.routes.prepend do - get '/rails/mailers' => "rails/mailers#index" - get '/rails/mailers/*path' => "rails/mailers#preview" - end - end end + + ActiveSupport.on_load(:action_dispatch_integration_test) { include ActionMailer::TestCase::ClearTestDeliveries } end initializer "action_mailer.compile_config_methods" do @@ -60,9 +56,18 @@ module ActionMailer end end - config.after_initialize do - if ActionMailer::Base.preview_path - ActiveSupport::Dependencies.autoload_paths << ActionMailer::Base.preview_path + config.after_initialize do |app| + options = app.config.action_mailer + + if options.show_previews + app.routes.prepend do + get "/rails/mailers" => "rails/mailers#index", internal: true + get "/rails/mailers/*path" => "rails/mailers#preview", internal: true + end + + if options.preview_path + ActiveSupport::Dependencies.autoload_paths << options.preview_path + end end end end diff --git a/actionmailer/lib/action_mailer/rescuable.rb b/actionmailer/lib/action_mailer/rescuable.rb new file mode 100644 index 0000000000..f2eabfa057 --- /dev/null +++ b/actionmailer/lib/action_mailer/rescuable.rb @@ -0,0 +1,27 @@ +module ActionMailer #:nodoc: + # Provides `rescue_from` for mailers. Wraps mailer action processing, + # mail job processing, and mail delivery. + module Rescuable + extend ActiveSupport::Concern + include ActiveSupport::Rescuable + + class_methods do + def handle_exception(exception) #:nodoc: + rescue_with_handler(exception) || raise(exception) + end + end + + def handle_exceptions #:nodoc: + yield + rescue => exception + rescue_with_handler(exception) || raise + end + + private + def process(*) + handle_exceptions do + super + end + end + end +end diff --git a/actionmailer/lib/action_mailer/test_case.rb b/actionmailer/lib/action_mailer/test_case.rb index 0aa15e31ba..1d09a4ee96 100644 --- a/actionmailer/lib/action_mailer/test_case.rb +++ b/actionmailer/lib/action_mailer/test_case.rb @@ -1,5 +1,5 @@ -require 'active_support/test_case' -require 'rails-dom-testing' +require "active_support/test_case" +require "rails-dom-testing" module ActionMailer class NonInferrableMailerError < ::StandardError @@ -11,6 +11,23 @@ module ActionMailer end class TestCase < ActiveSupport::TestCase + module ClearTestDeliveries + extend ActiveSupport::Concern + + included do + setup :clear_test_deliveries + teardown :clear_test_deliveries + end + + private + + def clear_test_deliveries + if ActionMailer::Base.delivery_method == :test + ActionMailer::Base.deliveries.clear + end + end + end + module Behavior extend ActiveSupport::Concern @@ -24,6 +41,7 @@ module ActionMailer setup :initialize_test_deliveries setup :set_expected_mail teardown :restore_test_deliveries + ActiveSupport.run_load_hooks(:action_mailer_test_case, self) end module ClassMethods @@ -61,12 +79,12 @@ module ActionMailer 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: restore_delivery_method ActionMailer::Base.perform_deliveries = @old_perform_deliveries - ActionMailer::Base.deliveries.clear end def set_delivery_method(method) # :nodoc: @@ -75,13 +93,14 @@ module ActionMailer end def restore_delivery_method # :nodoc: + ActionMailer::Base.deliveries.clear ActionMailer::Base.delivery_method = @old_delivery_method end def set_expected_mail # :nodoc: @expected = Mail.new @expected.content_type ["text", "plain", { "charset" => charset }] - @expected.mime_version = '1.0' + @expected.mime_version = "1.0" end private @@ -95,7 +114,7 @@ module ActionMailer end def read_fixture(action) - IO.readlines(File.join(Rails.root, 'test', 'fixtures', self.class.mailer_class.name.underscore, action)) + IO.readlines(File.join(Rails.root, "test", "fixtures", self.class.mailer_class.name.underscore, action)) end end diff --git a/actionmailer/lib/action_mailer/test_helper.rb b/actionmailer/lib/action_mailer/test_helper.rb index e423aac389..c17ecad4c6 100644 --- a/actionmailer/lib/action_mailer/test_helper.rb +++ b/actionmailer/lib/action_mailer/test_helper.rb @@ -1,4 +1,4 @@ -require 'active_job' +require "active_job" module ActionMailer # Provides helper methods for testing Action Mailer, including #assert_emails diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb index 06f80a8fdc..8452d6370e 100644 --- a/actionmailer/lib/action_mailer/version.rb +++ b/actionmailer/lib/action_mailer/version.rb @@ -1,4 +1,4 @@ -require_relative 'gem_version' +require_relative "gem_version" module ActionMailer # Returns the version of the currently loaded Action Mailer as a diff --git a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb index 5a5c9d32bb..9dd7ee7a27 100644 --- a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb +++ b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb @@ -8,14 +8,29 @@ module Rails check_class_collision suffix: "Mailer" def create_mailer_file - template "mailer.rb", File.join('app/mailers', class_path, "#{file_name}_mailer.rb") + 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) + template "application_mailer.rb", application_mailer_file_name + end + end end hook_for :template_engine, :test_framework protected def file_name - @_file_name ||= super.gsub(/\_mailer/i, '') + @_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" + else + "app/mailers/application_mailer.rb" + end end end end diff --git a/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb b/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb new file mode 100644 index 0000000000..00fb9bd48f --- /dev/null +++ b/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb @@ -0,0 +1,6 @@ +<% module_namespacing do -%> +class ApplicationMailer < ActionMailer::Base + default from: 'from@example.com' + layout 'mailer' +end +<% end %> diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb index 285b2cfcb5..82f03f5cba 100644 --- a/actionmailer/test/abstract_unit.rb +++ b/actionmailer/test/abstract_unit.rb @@ -1,5 +1,4 @@ -require File.expand_path('../../../load_paths', __FILE__) -require 'active_support/core_ext/kernel/reporting' +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 @@ -10,17 +9,17 @@ end module Rails def self.root - File.expand_path('../', File.dirname(__FILE__)) + File.expand_path("../", File.dirname(__FILE__)) end end -require 'active_support/testing/autorun' -require 'active_support/testing/method_call_assertions' -require 'action_mailer' -require 'action_mailer/test_case' +require "active_support/testing/autorun" +require "active_support/testing/method_call_assertions" +require "action_mailer" +require "action_mailer/test_case" # Emulate AV railtie -require 'action_view' +require "action_view" ActionMailer::Base.include(ActionView::Layouts) # Show backtraces for deprecated behavior for quicker cleanup. @@ -29,15 +28,15 @@ ActiveSupport::Deprecation.debug = true # Disable available locale checks to avoid warnings running the test suite. I18n.enforce_available_locales = false -FIXTURE_LOAD_PATH = File.expand_path('fixtures', File.dirname(__FILE__)) +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' +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 = '') +def jruby_skip(message = "") skip message if defined?(JRUBY_VERSION) end diff --git a/actionmailer/test/assert_select_email_test.rb b/actionmailer/test/assert_select_email_test.rb index cae2e20abd..bf14fe0853 100644 --- a/actionmailer/test/assert_select_email_test.rb +++ b/actionmailer/test/assert_select_email_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class AssertSelectEmailTest < ActionMailer::TestCase class AssertSelectMailer < ActionMailer::Base @@ -11,8 +11,8 @@ class AssertSelectEmailTest < ActionMailer::TestCase class AssertMultipartSelectMailer < ActionMailer::Base def test(options) mail subject: "Test e-mail", from: "test@test.host", to: "test <test@test.host>" do |format| - format.text { render text: options[:text] } - format.html { render text: options[:html] } + format.text { render plain: options[:text] } + format.html { render plain: options[:html] } end end end @@ -36,7 +36,7 @@ class AssertSelectEmailTest < ActionMailer::TestCase end def test_assert_select_email_multipart - AssertMultipartSelectMailer.test(html: "<div><p>foo</p><p>bar</p></div>", text: 'foo bar').deliver_now + AssertMultipartSelectMailer.test(html: "<div><p>foo</p><p>bar</p></div>", text: "foo bar").deliver_now assert_select_email do assert_select "div:root" do assert_select "p:first-child", "foo" diff --git a/actionmailer/test/asset_host_test.rb b/actionmailer/test/asset_host_test.rb index 10cfdcf693..812df01a34 100644 --- a/actionmailer/test/asset_host_test.rb +++ b/actionmailer/test/asset_host_test.rb @@ -1,11 +1,11 @@ -require 'abstract_unit' -require 'action_controller' +require "abstract_unit" +require "action_controller" class AssetHostMailer < ActionMailer::Base def email_with_asset - mail to: 'test@localhost', - subject: 'testing email containing asset path while asset_host is set', - from: 'tester@example.com' + mail to: "test@localhost", + subject: "testing email containing asset path while asset_host is set", + from: "tester@example.com" end end @@ -22,16 +22,16 @@ class AssetHostTest < ActionMailer::TestCase def test_asset_host_as_string mail = AssetHostMailer.email_with_asset - assert_dom_equal %Q{<img alt="Somelogo" src="http://www.example.com/images/somelogo.png" />}, mail.body.to_s.strip + assert_dom_equal '<img alt="Somelogo" src="http://www.example.com/images/somelogo.png" />', mail.body.to_s.strip end def test_asset_host_as_one_argument_proc AssetHostMailer.config.asset_host = Proc.new { |source| - if source.starts_with?('/images') - 'http://images.example.com' + if source.starts_with?("/images") + "http://images.example.com" end } mail = AssetHostMailer.email_with_asset - assert_dom_equal %Q{<img alt="Somelogo" src="http://images.example.com/images/somelogo.png" />}, mail.body.to_s.strip + assert_dom_equal '<img alt="Somelogo" src="http://images.example.com/images/somelogo.png" />', mail.body.to_s.strip end end diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index 50f2c71737..3bca69890d 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -1,12 +1,12 @@ -require 'abstract_unit' -require 'set' +require "abstract_unit" +require "set" -require 'action_dispatch' -require 'active_support/time' +require "action_dispatch" +require "active_support/time" -require 'mailers/base_mailer' -require 'mailers/proc_mailer' -require 'mailers/asset_mailer' +require "mailers/base_mailer" +require "mailers/proc_mailer" +require "mailers/asset_mailer" class BaseTest < ActiveSupport::TestCase include Rails::Dom::Testing::Assertions::DomAssertions @@ -32,33 +32,33 @@ class BaseTest < ActiveSupport::TestCase # Basic mail usage without block test "mail() should set the headers of the mail message" do email = BaseMailer.welcome - assert_equal(['system@test.lindsaar.net'], email.to) - assert_equal(['jose@test.plataformatec.com'], email.from) - assert_equal('The first email on new API!', email.subject) + assert_equal(["system@test.lindsaar.net"], email.to) + assert_equal(["jose@test.plataformatec.com"], email.from) + assert_equal("The first email on new API!", email.subject) end test "mail() with from overwrites the class level default" do - email = BaseMailer.welcome(from: 'someone@example.com', - to: 'another@example.org') - assert_equal(['someone@example.com'], email.from) - assert_equal(['another@example.org'], email.to) + email = BaseMailer.welcome(from: "someone@example.com", + to: "another@example.org") + assert_equal(["someone@example.com"], email.from) + assert_equal(["another@example.org"], email.to) end test "mail() with bcc, cc, content_type, charset, mime_version, reply_to and date" do time = Time.now.beginning_of_day.to_datetime - email = BaseMailer.welcome(bcc: 'bcc@test.lindsaar.net', - cc: 'cc@test.lindsaar.net', - content_type: 'multipart/mixed', - charset: 'iso-8559-1', - mime_version: '2.0', - reply_to: 'reply-to@test.lindsaar.net', + email = BaseMailer.welcome(bcc: "bcc@test.lindsaar.net", + cc: "cc@test.lindsaar.net", + content_type: "multipart/mixed", + charset: "iso-8559-1", + mime_version: "2.0", + reply_to: "reply-to@test.lindsaar.net", date: time) - assert_equal(['bcc@test.lindsaar.net'], email.bcc) - assert_equal(['cc@test.lindsaar.net'], email.cc) - assert_equal('multipart/mixed; charset=iso-8559-1', email.content_type) - assert_equal('iso-8559-1', email.charset) - assert_equal('2.0', email.mime_version) - assert_equal(['reply-to@test.lindsaar.net'], email.reply_to) + assert_equal(["bcc@test.lindsaar.net"], email.bcc) + assert_equal(["cc@test.lindsaar.net"], email.cc) + assert_equal("multipart/mixed; charset=iso-8559-1", email.content_type) + assert_equal("iso-8559-1", email.charset) + assert_equal("2.0", email.mime_version) + assert_equal(["reply-to@test.lindsaar.net"], email.reply_to) assert_equal(time, email.date) end @@ -75,63 +75,63 @@ class BaseTest < ActiveSupport::TestCase test "should set template content type if mail has only one part" do mail = BaseMailer.html_only - assert_equal('text/html', mail.mime_type) + assert_equal("text/html", mail.mime_type) mail = BaseMailer.plain_text_only - assert_equal('text/plain', mail.mime_type) + assert_equal("text/plain", mail.mime_type) end # Custom headers test "custom headers" do email = BaseMailer.welcome - assert_equal("Not SPAM", email['X-SPAM'].decoded) + assert_equal("Not SPAM", email["X-SPAM"].decoded) end test "can pass random headers in as a hash to mail" do - hash = {'X-Special-Domain-Specific-Header' => "SecretValue", - 'In-Reply-To' => '1234@mikel.me.com' } + hash = { "X-Special-Domain-Specific-Header" => "SecretValue", + "In-Reply-To" => "1234@mikel.me.com" } mail = BaseMailer.welcome(hash) - assert_equal('SecretValue', mail['X-Special-Domain-Specific-Header'].decoded) - assert_equal('1234@mikel.me.com', mail['In-Reply-To'].decoded) + assert_equal("SecretValue", mail["X-Special-Domain-Specific-Header"].decoded) + assert_equal("1234@mikel.me.com", mail["In-Reply-To"].decoded) end test "can pass random headers in as a hash to headers" do - hash = {'X-Special-Domain-Specific-Header' => "SecretValue", - 'In-Reply-To' => '1234@mikel.me.com' } + hash = { "X-Special-Domain-Specific-Header" => "SecretValue", + "In-Reply-To" => "1234@mikel.me.com" } mail = BaseMailer.welcome_with_headers(hash) - assert_equal('SecretValue', mail['X-Special-Domain-Specific-Header'].decoded) - assert_equal('1234@mikel.me.com', mail['In-Reply-To'].decoded) + assert_equal("SecretValue", mail["X-Special-Domain-Specific-Header"].decoded) + assert_equal("1234@mikel.me.com", mail["In-Reply-To"].decoded) end # Attachments test "attachment with content" do email = BaseMailer.attachment_with_content assert_equal(1, email.attachments.length) - assert_equal('invoice.pdf', email.attachments[0].filename) - assert_equal('This is test File content', email.attachments['invoice.pdf'].decoded) + assert_equal("invoice.pdf", email.attachments[0].filename) + assert_equal("This is test File content", email.attachments["invoice.pdf"].decoded) end test "attachment gets content type from filename" do email = BaseMailer.attachment_with_content - assert_equal('invoice.pdf', email.attachments[0].filename) - assert_equal('application/pdf', email.attachments[0].mime_type) + assert_equal("invoice.pdf", email.attachments[0].filename) + assert_equal("application/pdf", email.attachments[0].mime_type) end test "attachment with hash" do email = BaseMailer.attachment_with_hash assert_equal(1, email.attachments.length) - assert_equal('invoice.jpg', email.attachments[0].filename) + assert_equal("invoice.jpg", email.attachments[0].filename) expected = "\312\213\254\232)b" expected.force_encoding(Encoding::BINARY) - assert_equal expected, email.attachments['invoice.jpg'].decoded + assert_equal expected, email.attachments["invoice.jpg"].decoded end test "attachment with hash using default mail encoding" do email = BaseMailer.attachment_with_hash_default_encoding assert_equal(1, email.attachments.length) - assert_equal('invoice.jpg', email.attachments[0].filename) + assert_equal("invoice.jpg", email.attachments[0].filename) expected = "\312\213\254\232)b" expected.force_encoding(Encoding::BINARY) - assert_equal expected, email.attachments['invoice.jpg'].decoded + assert_equal expected, email.attachments["invoice.jpg"].decoded end test "sets mime type to multipart/mixed when attachment is included" do @@ -215,24 +215,24 @@ class BaseTest < ActiveSupport::TestCase email = BaseMailer.welcome(subject: nil) assert_equal "Welcome", email.subject - with_translation 'en', base_mailer: {welcome: {subject: "New Subject!"}} do + with_translation "en", base_mailer: { welcome: { subject: "New Subject!" } } do email = BaseMailer.welcome(subject: nil) assert_equal "New Subject!", email.subject end end end - test 'default subject can have interpolations' do - with_translation 'en', base_mailer: {with_subject_interpolations: {subject: 'Will the real %{rapper_or_impersonator} please stand up?'}} do + test "default subject can have interpolations" do + with_translation "en", base_mailer: { with_subject_interpolations: { subject: "Will the real %{rapper_or_impersonator} please stand up?" } } do email = BaseMailer.with_subject_interpolations - assert_equal 'Will the real Slim Shady please stand up?', email.subject + assert_equal "Will the real Slim Shady please stand up?", email.subject end end test "translations are scoped properly" do - with_translation 'en', base_mailer: {email_with_translations: {greet_user: "Hello %{name}!"}} do + with_translation "en", base_mailer: { email_with_translations: { greet_user: "Hello %{name}!" } } do email = BaseMailer.email_with_translations - assert_equal 'Hello lifo!', email.body.encoded + assert_equal "Hello lifo!", email.body.encoded end end @@ -240,7 +240,7 @@ class BaseTest < ActiveSupport::TestCase class LateAttachmentMailer < ActionMailer::Base def welcome mail body: "yay", from: "welcome@example.com", to: "to@example.com" - attachments['invoice.pdf'] = 'This is test File content' + attachments["invoice.pdf"] = "This is test File content" end end @@ -252,7 +252,7 @@ class BaseTest < ActiveSupport::TestCase class LateInlineAttachmentMailer < ActionMailer::Base def welcome mail body: "yay", from: "welcome@example.com", to: "to@example.com" - attachments.inline['invoice.pdf'] = 'This is test File content' + attachments.inline["invoice.pdf"] = "This is test File content" end end @@ -271,13 +271,13 @@ class BaseTest < ActiveSupport::TestCase assert_nothing_raised { mail.message } assert_equal ["image/jpeg; filename=controller_attachments.jpg", - "image/jpeg; filename=attachments.jpg"], mail.attachments.inline.map {|a| a['Content-Type'].to_s } + "image/jpeg; filename=attachments.jpg"], mail.attachments.inline.map { |a| a["Content-Type"].to_s } end test "accessing attachments works after mail was called" do class LateAttachmentAccessorMailer < ActionMailer::Base def welcome - attachments['invoice.pdf'] = 'This is test File content' + attachments["invoice.pdf"] = "This is test File content" mail body: "yay", from: "welcome@example.com", to: "to@example.com" unless attachments.map(&:filename) == ["invoice.pdf"] @@ -366,7 +366,7 @@ class BaseTest < ActiveSupport::TestCase I18n.backend = fallback_backend.new I18n.fallbacks[:"de-AT"] = [:de] - swap I18n, locale: 'de-AT' do + swap I18n, locale: "de-AT" do email = BaseMailer.implicit_with_locale assert_equal(2, email.parts.size) assert_equal("multipart/alternative", email.mime_type) @@ -380,7 +380,6 @@ class BaseTest < ActiveSupport::TestCase end end - test "implicit multipart with several view paths uses the first one with template" do old = BaseMailer.view_paths begin @@ -449,7 +448,7 @@ class BaseTest < ActiveSupport::TestCase assert_equal("Format with any!", email.parts[1].body.encoded) end - test 'explicit without specifying format with format.any' do + test "explicit without specifying format with format.any" do error = assert_raises(ArgumentError) do BaseMailer.explicit_without_specifying_format_with_any.parts end @@ -508,13 +507,13 @@ class BaseTest < ActiveSupport::TestCase test "calling just the action should return the generated mail object" do email = BaseMailer.welcome assert_equal(0, BaseMailer.deliveries.length) - assert_equal('The first email on new API!', email.subject) + assert_equal("The first email on new API!", email.subject) end test "calling deliver on the action should deliver the mail object" do assert_called(BaseMailer, :deliver_mail) do mail = BaseMailer.welcome.deliver_now - assert_equal 'The first email on new API!', mail.subject + assert_equal "The first email on new API!", mail.subject end end @@ -534,35 +533,35 @@ class BaseTest < ActiveSupport::TestCase # Rendering test "you can specify a different template for implicit render" do - mail = BaseMailer.implicit_different_template('implicit_multipart').deliver_now + mail = BaseMailer.implicit_different_template("implicit_multipart").deliver_now assert_equal("HTML Implicit Multipart", mail.html_part.body.decoded) assert_equal("TEXT Implicit Multipart", mail.text_part.body.decoded) end test "should raise if missing template in implicit render" do assert_raises ActionView::MissingTemplate do - BaseMailer.implicit_different_template('missing_template').deliver_now + BaseMailer.implicit_different_template("missing_template").deliver_now end assert_equal(0, BaseMailer.deliveries.length) end test "you can specify a different template for explicit render" do - mail = BaseMailer.explicit_different_template('explicit_multipart_templates').deliver_now + mail = BaseMailer.explicit_different_template("explicit_multipart_templates").deliver_now assert_equal("HTML Explicit Multipart Templates", mail.html_part.body.decoded) assert_equal("TEXT Explicit Multipart Templates", mail.text_part.body.decoded) end test "you can specify a different layout" do - mail = BaseMailer.different_layout('different_layout').deliver_now + mail = BaseMailer.different_layout("different_layout").deliver_now assert_equal("HTML -- HTML", mail.html_part.body.decoded) assert_equal("PLAIN -- PLAIN", mail.text_part.body.decoded) end test "you can specify the template path for implicit lookup" do - mail = BaseMailer.welcome_from_another_path('another.path/base_mailer').deliver_now + mail = BaseMailer.welcome_from_another_path("another.path/base_mailer").deliver_now assert_equal("Welcome from another path", mail.body.encoded) - mail = BaseMailer.welcome_from_another_path(['unknown/invalid', 'another.path/base_mailer']).deliver_now + mail = BaseMailer.welcome_from_another_path(["unknown/invalid", "another.path/base_mailer"]).deliver_now assert_equal("Welcome from another path", mail.body.encoded) end @@ -589,15 +588,15 @@ class BaseTest < ActiveSupport::TestCase assert_dom_equal(%{<img alt="Dummy" src="http://local.com/images/dummy.png" />}, mail.body.to_s.strip) end - test 'the view is not rendered when mail was never called' do + test "the view is not rendered when mail was never called" do mail = BaseMailer.without_mail_call - assert_equal('', mail.body.to_s.strip) + assert_equal("", mail.body.to_s.strip) mail.deliver_now end - test 'the return value of mailer methods is not relevant' do + test "the return value of mailer methods is not relevant" do mail = BaseMailer.with_nil_as_return_value - assert_equal('Welcome', mail.body.to_s.strip) + assert_equal("Welcome", mail.body.to_s.strip) mail.deliver_now end @@ -708,16 +707,16 @@ class BaseTest < ActiveSupport::TestCase end test "being able to put proc's into the defaults hash and they get evaluated on mail sending" do - mail1 = ProcMailer.welcome['X-Proc-Method'] + mail1 = ProcMailer.welcome["X-Proc-Method"] yesterday = 1.day.ago Time.stub(:now, yesterday) do - mail2 = ProcMailer.welcome['X-Proc-Method'] + mail2 = ProcMailer.welcome["X-Proc-Method"] assert(mail1.to_s.to_i > mail2.to_s.to_i) end end - test 'default values which have to_proc (e.g. symbols) should not be considered procs' do - assert(ProcMailer.welcome['x-has-to-proc'].to_s == 'symbol') + test "default values which have to_proc (e.g. symbols) should not be considered procs" do + assert(ProcMailer.welcome["x-has-to-proc"].to_s == "symbol") end test "we can call other defined methods on the class as needed" do @@ -732,12 +731,12 @@ class BaseTest < ActiveSupport::TestCase def welcome ; mail ; end private - def add_special_header! - headers('X-Special-Header' => 'Wow, so special') - end + def add_special_header! + headers("X-Special-Header" => "Wow, so special") + end end - assert_equal('Wow, so special', BeforeActionMailer.welcome['X-Special-Header'].to_s) + assert_equal("Wow, so special", BeforeActionMailer.welcome["X-Special-Header"].to_s) end test "modifying the mail message with an after_action" do @@ -747,12 +746,12 @@ class BaseTest < ActiveSupport::TestCase def welcome ; mail ; end private - def add_special_header! - headers('X-Special-Header' => 'Testing') - end + def add_special_header! + headers("X-Special-Header" => "Testing") + end end - assert_equal('Testing', AfterActionMailer.welcome['X-Special-Header'].to_s) + assert_equal("Testing", AfterActionMailer.welcome["X-Special-Header"].to_s) end test "adding an inline attachment using a before_action" do @@ -762,19 +761,19 @@ class BaseTest < ActiveSupport::TestCase def welcome ; mail ; end private - def add_inline_attachment! - attachments.inline["footer.jpg"] = 'hey there' - end + def add_inline_attachment! + attachments.inline["footer.jpg"] = "hey there" + end end mail = DefaultInlineAttachmentMailer.welcome - assert_equal('image/jpeg; filename=footer.jpg', mail.attachments.inline.first['Content-Type'].to_s) + assert_equal("image/jpeg; filename=footer.jpg", mail.attachments.inline.first["Content-Type"].to_s) end test "action methods should be refreshed after defining new method" do class FooMailer < ActionMailer::Base - # this triggers action_methods - self.respond_to?(:foo) + # This triggers action_methods. + respond_to?(:foo) def notify end @@ -798,8 +797,8 @@ class BaseTest < ActiveSupport::TestCase test "default_from can be set" do class DefaultFromMailer < ActionMailer::Base - default to: 'system@test.lindsaar.net' - self.default_options = {from: "robert.pankowecki@gmail.com"} + default to: "system@test.lindsaar.net" + self.default_options = { from: "robert.pankowecki@gmail.com" } def welcome mail(subject: "subject", body: "hello world") @@ -814,7 +813,7 @@ class BaseTest < ActiveSupport::TestCase after_action :a_callback def welcome - headers('X-Special-Header' => 'special indeed!') + headers("X-Special-Header" => "special indeed!") mail subject: "subject", body: "hello world", to: ["joe@example.com"] end diff --git a/actionmailer/test/caching_test.rb b/actionmailer/test/caching_test.rb new file mode 100644 index 0000000000..cff49c8894 --- /dev/null +++ b/actionmailer/test/caching_test.rb @@ -0,0 +1,273 @@ +require "fileutils" +require "abstract_unit" +require "mailers/base_mailer" +require "mailers/caching_mailer" + +CACHE_DIR = "test_cache" +# Don't change '/../temp/' cavalierly or you might hose something you don't want hosed +FILE_STORE_PATH = File.join(File.dirname(__FILE__), "/../temp/", CACHE_DIR) + +class FragmentCachingMailer < ActionMailer::Base + abstract! + + def some_action; end +end + +class BaseCachingTest < ActiveSupport::TestCase + def setup + super + @store = ActiveSupport::Cache::MemoryStore.new + @mailer = FragmentCachingMailer.new + @mailer.perform_caching = true + @mailer.cache_store = @store + end + + def test_fragment_cache_key + assert_equal "views/what a key", @mailer.fragment_cache_key("what a key") + end +end + +class FragmentCachingTest < BaseCachingTest + def test_read_fragment_with_caching_enabled + @store.write("views/name", "value") + assert_equal "value", @mailer.read_fragment("name") + end + + def test_read_fragment_with_caching_disabled + @mailer.perform_caching = false + @store.write("views/name", "value") + assert_nil @mailer.read_fragment("name") + end + + def test_fragment_exist_with_caching_enabled + @store.write("views/name", "value") + assert @mailer.fragment_exist?("name") + assert !@mailer.fragment_exist?("other_name") + end + + def test_fragment_exist_with_caching_disabled + @mailer.perform_caching = false + @store.write("views/name", "value") + assert !@mailer.fragment_exist?("name") + assert !@mailer.fragment_exist?("other_name") + end + + def test_write_fragment_with_caching_enabled + assert_nil @store.read("views/name") + assert_equal "value", @mailer.write_fragment("name", "value") + assert_equal "value", @store.read("views/name") + end + + def test_write_fragment_with_caching_disabled + assert_nil @store.read("views/name") + @mailer.perform_caching = false + assert_equal "value", @mailer.write_fragment("name", "value") + assert_nil @store.read("views/name") + end + + def test_expire_fragment_with_simple_key + @store.write("views/name", "value") + @mailer.expire_fragment "name" + assert_nil @store.read("views/name") + end + + def test_expire_fragment_with_regexp + @store.write("views/name", "value") + @store.write("views/another_name", "another_value") + @store.write("views/primalgrasp", "will not expire ;-)") + + @mailer.expire_fragment(/name/) + + assert_nil @store.read("views/name") + assert_nil @store.read("views/another_name") + assert_equal "will not expire ;-)", @store.read("views/primalgrasp") + end + + def test_fragment_for + @store.write("views/expensive", "fragment content") + fragment_computed = false + + view_context = @mailer.view_context + + buffer = "generated till now -> ".html_safe + buffer << view_context.send(:fragment_for, "expensive") { fragment_computed = true } + + assert !fragment_computed + assert_equal "generated till now -> fragment content", buffer + end + + def test_html_safety + assert_nil @store.read("views/name") + content = "value".html_safe + assert_equal content, @mailer.write_fragment("name", content) + + cached = @store.read("views/name") + assert_equal content, cached + assert_equal String, cached.class + + html_safe = @mailer.read_fragment("name") + assert_equal content, html_safe + assert html_safe.html_safe? + end +end + +class FunctionalFragmentCachingTest < BaseCachingTest + def setup + super + @store = ActiveSupport::Cache::MemoryStore.new + @mailer = CachingMailer.new + @mailer.perform_caching = true + @mailer.cache_store = @store + end + + def test_fragment_caching + email = @mailer.fragment_cache + expected_body = "\"Welcome\"" + + assert_match expected_body, email.body.encoded + assert_match expected_body, + @store.read("views/caching/#{template_digest("caching_mailer/fragment_cache")}") + end + + def test_fragment_caching_in_partials + email = @mailer.fragment_cache_in_partials + expected_body = "Old fragment caching in a partial" + assert_match(expected_body, email.body.encoded) + + assert_match(expected_body, + @store.read("views/caching/#{template_digest("caching_mailer/_partial")}")) + end + + def test_skip_fragment_cache_digesting + email = @mailer.skip_fragment_cache_digesting + expected_body = "No Digest" + + assert_match expected_body, email.body.encoded + assert_match expected_body, @store.read("views/no_digest") + end + + def test_fragment_caching_options + time = Time.now + email = @mailer.fragment_caching_options + expected_body = "No Digest" + + assert_match expected_body, email.body.encoded + Time.stub(:now, time + 11) do + assert_nil @store.read("views/no_digest") + end + end + + def test_multipart_fragment_caching + email = @mailer.multipart_cache + + expected_text_body = "\"Welcome text\"" + expected_html_body = "\"Welcome html\"" + encoded_body = email.body.encoded + assert_match expected_text_body, encoded_body + assert_match expected_html_body, encoded_body + assert_match expected_text_body, + @store.read("views/text_caching") + assert_match expected_html_body, + @store.read("views/html_caching") + end + + def test_fragment_cache_instrumentation + @mailer.enable_fragment_cache_logging = true + payload = nil + + subscriber = proc do |*args| + event = ActiveSupport::Notifications::Event.new(*args) + payload = event.payload + end + + ActiveSupport::Notifications.subscribed(subscriber, "read_fragment.action_mailer") do + @mailer.fragment_cache + end + + assert_equal "caching_mailer", payload[:mailer] + assert_equal "views/caching/#{template_digest("caching_mailer/fragment_cache")}", payload[:key] + ensure + @mailer.enable_fragment_cache_logging = true + end + + private + + def template_digest(name) + ActionView::Digestor.digest(name: name, finder: @mailer.lookup_context) + end +end + +class CacheHelperOutputBufferTest < BaseCachingTest + class MockController + def read_fragment(name, options) + return false + end + + def write_fragment(name, fragment, options) + fragment + end + end + + def setup + super + end + + def test_output_buffer + output_buffer = ActionView::OutputBuffer.new + controller = MockController.new + cache_helper = Class.new do + def self.controller; end; + def self.output_buffer; end; + def self.output_buffer=; end; + end + cache_helper.extend(ActionView::Helpers::CacheHelper) + + cache_helper.stub :controller, controller do + cache_helper.stub :output_buffer, output_buffer do + assert_called_with cache_helper, :output_buffer=, [output_buffer.class.new(output_buffer)] do + assert_nothing_raised do + cache_helper.send :fragment_for, "Test fragment name", "Test fragment", &Proc.new { nil } + end + end + end + end + end + + def test_safe_buffer + output_buffer = ActiveSupport::SafeBuffer.new + controller = MockController.new + cache_helper = Class.new do + def self.controller; end; + def self.output_buffer; end; + def self.output_buffer=; end; + end + cache_helper.extend(ActionView::Helpers::CacheHelper) + + cache_helper.stub :controller, controller do + cache_helper.stub :output_buffer, output_buffer do + assert_called_with cache_helper, :output_buffer=, [output_buffer.class.new(output_buffer)] do + assert_nothing_raised do + cache_helper.send :fragment_for, "Test fragment name", "Test fragment", &Proc.new { nil } + end + end + end + end + end +end + +class ViewCacheDependencyTest < BaseCachingTest + class NoDependenciesMailer < ActionMailer::Base + end + class HasDependenciesMailer < ActionMailer::Base + view_cache_dependency { "trombone" } + view_cache_dependency { "flute" } + end + + def test_view_cache_dependencies_are_empty_by_default + assert NoDependenciesMailer.new.view_cache_dependencies.empty? + end + + def test_view_cache_dependencies_are_listed_in_declaration_order + assert_equal %w(trombone flute), HasDependenciesMailer.new.view_cache_dependencies + end +end diff --git a/actionmailer/test/delivery_methods_test.rb b/actionmailer/test/delivery_methods_test.rb index 2786fe0d07..898d32c1e2 100644 --- a/actionmailer/test/delivery_methods_test.rb +++ b/actionmailer/test/delivery_methods_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class MyCustomDelivery end @@ -23,7 +23,7 @@ class DefaultsDeliveryMethodsTest < ActiveSupport::TestCase test "default smtp settings" do settings = { address: "localhost", port: 25, - domain: 'localhost.localdomain', + domain: "localhost.localdomain", user_name: nil, password: nil, authentication: nil, @@ -32,14 +32,14 @@ class DefaultsDeliveryMethodsTest < ActiveSupport::TestCase end test "default file delivery settings (with Rails.root)" do - settings = {location: "#{Rails.root}/tmp/mails"} + settings = { location: "#{Rails.root}/tmp/mails" } assert_equal settings, ActionMailer::Base.file_settings end test "default sendmail settings" do settings = { - location: '/usr/sbin/sendmail', - arguments: '-i -t' + location: "/usr/sbin/sendmail", + arguments: "-i" } assert_equal settings, ActionMailer::Base.sendmail_settings end @@ -83,8 +83,8 @@ end class MailDeliveryTest < ActiveSupport::TestCase class DeliveryMailer < ActionMailer::Base DEFAULT_HEADERS = { - to: 'mikel@test.lindsaar.net', - from: 'jose@test.plataformatec.com' + to: "mikel@test.lindsaar.net", + from: "jose@test.plataformatec.com" } def welcome(hash={}) @@ -127,16 +127,16 @@ class MailDeliveryTest < ActiveSupport::TestCase end test "delivery method options default to class level options" do - default_options = {a: "b"} + default_options = { a: "b" } ActionMailer::Base.add_delivery_method :optioned, MyOptionedDelivery, default_options mail_instance = DeliveryMailer.welcome(delivery_method: :optioned) assert_equal default_options, mail_instance.delivery_method.options end test "delivery method options can be overridden per mail instance" do - default_options = {a: "b"} + default_options = { a: "b" } ActionMailer::Base.add_delivery_method :optioned, MyOptionedDelivery, default_options - overridden_options = {a: "a"} + overridden_options = { a: "a" } mail_instance = DeliveryMailer.welcome(delivery_method: :optioned, delivery_method_options: overridden_options) assert_equal overridden_options, mail_instance.delivery_method.options end @@ -145,14 +145,14 @@ class MailDeliveryTest < ActiveSupport::TestCase settings = { address: "localhost", port: 25, - domain: 'localhost.localdomain', + domain: "localhost.localdomain", user_name: nil, password: nil, authentication: nil, enable_starttls_auto: true } assert_equal settings, ActionMailer::Base.smtp_settings - overridden_options = {user_name: "overridden", password: "somethingobtuse"} + overridden_options = { user_name: "overridden", password: "somethingobtuse" } mail_instance = DeliveryMailer.welcome(delivery_method_options: overridden_options) delivery_method_instance = mail_instance.delivery_method assert_equal "overridden", delivery_method_instance.settings[:user_name] diff --git a/actionmailer/test/fixtures/caching_mailer/_partial.html.erb b/actionmailer/test/fixtures/caching_mailer/_partial.html.erb new file mode 100644 index 0000000000..8e965f52b4 --- /dev/null +++ b/actionmailer/test/fixtures/caching_mailer/_partial.html.erb @@ -0,0 +1,3 @@ +<% cache :caching do %> + Old fragment caching in a partial +<% end %> diff --git a/actionmailer/test/fixtures/caching_mailer/fragment_cache.html.erb b/actionmailer/test/fixtures/caching_mailer/fragment_cache.html.erb new file mode 100644 index 0000000000..90189627da --- /dev/null +++ b/actionmailer/test/fixtures/caching_mailer/fragment_cache.html.erb @@ -0,0 +1,3 @@ +<% cache :caching do %> +"Welcome" +<% end %> diff --git a/actionmailer/test/fixtures/caching_mailer/fragment_cache_in_partials.html.erb b/actionmailer/test/fixtures/caching_mailer/fragment_cache_in_partials.html.erb new file mode 100644 index 0000000000..2957d083e8 --- /dev/null +++ b/actionmailer/test/fixtures/caching_mailer/fragment_cache_in_partials.html.erb @@ -0,0 +1 @@ +<%= render "partial" %> diff --git a/actionmailer/test/fixtures/caching_mailer/fragment_caching_options.html.erb b/actionmailer/test/fixtures/caching_mailer/fragment_caching_options.html.erb new file mode 100644 index 0000000000..0541ac321b --- /dev/null +++ b/actionmailer/test/fixtures/caching_mailer/fragment_caching_options.html.erb @@ -0,0 +1,3 @@ +<%= cache :no_digest, skip_digest: true, expires_in: 0 do %> + No Digest +<% end %> diff --git a/actionmailer/test/fixtures/caching_mailer/multipart_cache.html.erb b/actionmailer/test/fixtures/caching_mailer/multipart_cache.html.erb new file mode 100644 index 0000000000..0d26baa2d7 --- /dev/null +++ b/actionmailer/test/fixtures/caching_mailer/multipart_cache.html.erb @@ -0,0 +1,3 @@ +<% cache :html_caching, skip_digest: true do %> + "Welcome html" +<% end %> diff --git a/actionmailer/test/fixtures/caching_mailer/multipart_cache.text.erb b/actionmailer/test/fixtures/caching_mailer/multipart_cache.text.erb new file mode 100644 index 0000000000..ef97326e03 --- /dev/null +++ b/actionmailer/test/fixtures/caching_mailer/multipart_cache.text.erb @@ -0,0 +1,3 @@ +<% cache :text_caching, skip_digest: true do %> + "Welcome text" +<% end %> diff --git a/actionmailer/test/fixtures/caching_mailer/skip_fragment_cache_digesting.html.erb b/actionmailer/test/fixtures/caching_mailer/skip_fragment_cache_digesting.html.erb new file mode 100644 index 0000000000..0d52429a81 --- /dev/null +++ b/actionmailer/test/fixtures/caching_mailer/skip_fragment_cache_digesting.html.erb @@ -0,0 +1,3 @@ +<%= cache :no_digest, skip_digest: true do %> + No Digest +<% end %> diff --git a/actionmailer/test/i18n_with_controller_test.rb b/actionmailer/test/i18n_with_controller_test.rb index 6124ffeb52..039685ffe5 100644 --- a/actionmailer/test/i18n_with_controller_test.rb +++ b/actionmailer/test/i18n_with_controller_test.rb @@ -1,10 +1,10 @@ -require 'abstract_unit' -require 'action_view' -require 'action_controller' +require "abstract_unit" +require "action_view" +require "action_controller" class I18nTestMailer < ActionMailer::Base configure do |c| - c.assets_dir = '' + c.assets_dir = "" end def mail_with_i18n_subject(recipient) @@ -18,14 +18,16 @@ end class TestController < ActionController::Base def send_mail email = I18nTestMailer.mail_with_i18n_subject("test@localhost").deliver_now - render text: "Mail sent - Subject: #{email.subject}" + render plain: "Mail sent - Subject: #{email.subject}" end end class ActionMailerI18nWithControllerTest < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new Routes.draw do - get ':controller(/:action(/:id))' + ActiveSupport::Deprecation.silence do + get ":controller(/:action(/:id))" + end end class RoutedRackApp @@ -54,9 +56,9 @@ class ActionMailerI18nWithControllerTest < ActionDispatch::IntegrationTest def test_send_mail stub_any_instance(Mail::SMTP, instance: Mail::SMTP.new({})) do |instance| assert_called(instance, :deliver!) do - with_translation 'de', email_subject: '[Anmeldung] Willkommen' do + with_translation "de", email_subject: "[Anmeldung] Willkommen" do ActiveSupport::Deprecation.silence do - get '/test/send_mail' + get "/test/send_mail" end assert_equal "Mail sent - Subject: [Anmeldung] Willkommen", @response.body end @@ -66,10 +68,10 @@ class ActionMailerI18nWithControllerTest < ActionDispatch::IntegrationTest protected - def with_translation(locale, data) - I18n.backend.store_translations(locale, data) - yield - ensure - I18n.backend.reload! - end + def with_translation(locale, data) + I18n.backend.store_translations(locale, data) + yield + ensure + I18n.backend.reload! + end end diff --git a/actionmailer/test/log_subscriber_test.rb b/actionmailer/test/log_subscriber_test.rb index 3871b16840..d864c3acca 100644 --- a/actionmailer/test/log_subscriber_test.rb +++ b/actionmailer/test/log_subscriber_test.rb @@ -1,7 +1,7 @@ -require 'abstract_unit' -require 'mailers/base_mailer' -require 'active_support/log_subscriber/test_helper' -require 'action_mailer/log_subscriber' +require "abstract_unit" +require "mailers/base_mailer" +require "active_support/log_subscriber/test_helper" +require "action_mailer/log_subscriber" class AMLogSubscriberTest < ActionMailer::TestCase include ActiveSupport::LogSubscriber::TestHelper diff --git a/actionmailer/test/mail_helper_test.rb b/actionmailer/test/mail_helper_test.rb index ff6b25b0c7..972629e477 100644 --- a/actionmailer/test/mail_helper_test.rb +++ b/actionmailer/test/mail_helper_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class HelperMailer < ActionMailer::Base def use_mail_helper @@ -67,10 +67,10 @@ The second protected - def mail_with_defaults(&block) - mail(to: "test@localhost", from: "tester@example.com", - subject: "using helpers", &block) - end + def mail_with_defaults(&block) + mail(to: "test@localhost", from: "tester@example.com", + subject: "using helpers", &block) + end end class MailerHelperTest < ActionMailer::TestCase diff --git a/actionmailer/test/mail_layout_test.rb b/actionmailer/test/mail_layout_test.rb index 166dd096d4..73059d782d 100644 --- a/actionmailer/test/mail_layout_test.rb +++ b/actionmailer/test/mail_layout_test.rb @@ -1,7 +1,7 @@ -require 'abstract_unit' +require "abstract_unit" class AutoLayoutMailer < ActionMailer::Base - default to: 'test@localhost', + default to: "test@localhost", subject: "You have a mail", from: "tester@example.com" @@ -11,7 +11,7 @@ class AutoLayoutMailer < ActionMailer::Base def spam @world = "Earth" - mail(body: render(inline: "Hello, <%= @world %>", layout: 'spam')) + mail(body: render(inline: "Hello, <%= @world %>", layout: "spam")) end def nolayout @@ -28,9 +28,9 @@ class AutoLayoutMailer < ActionMailer::Base end class ExplicitLayoutMailer < ActionMailer::Base - layout 'spam', except: [:logout] + layout "spam", except: [:logout] - default to: 'test@localhost', + default to: "test@localhost", subject: "You have a mail", from: "tester@example.com" @@ -54,10 +54,10 @@ class LayoutMailerTest < ActiveSupport::TestCase assert_equal "multipart/alternative", mail.mime_type assert_equal 2, mail.parts.size - assert_equal 'text/plain', mail.parts.first.mime_type + assert_equal "text/plain", mail.parts.first.mime_type assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body.to_s - assert_equal 'text/html', mail.parts.last.mime_type + assert_equal "text/html", mail.parts.last.mime_type assert_equal "Hello from layout text/html multipart", mail.parts.last.body.to_s end @@ -66,10 +66,10 @@ class LayoutMailerTest < ActiveSupport::TestCase assert_equal "multipart/mixed", mail.mime_type assert_equal 2, mail.parts.size - assert_equal 'text/plain', mail.parts.first.mime_type + assert_equal "text/plain", mail.parts.first.mime_type assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body.to_s - assert_equal 'text/html', mail.parts.last.mime_type + assert_equal "text/html", mail.parts.last.mime_type assert_equal "Hello from layout text/html multipart", mail.parts.last.body.to_s end diff --git a/actionmailer/test/mailers/asset_mailer.rb b/actionmailer/test/mailers/asset_mailer.rb index f54a50d00d..1cf15128d2 100644 --- a/actionmailer/test/mailers/asset_mailer.rb +++ b/actionmailer/test/mailers/asset_mailer.rb @@ -2,6 +2,6 @@ class AssetMailer < ActionMailer::Base self.mailer_name = "asset_mailer" def welcome - mail + mail end end diff --git a/actionmailer/test/mailers/base_mailer.rb b/actionmailer/test/mailers/base_mailer.rb index 8c2225ce60..0d45376070 100644 --- a/actionmailer/test/mailers/base_mailer.rb +++ b/actionmailer/test/mailers/base_mailer.rb @@ -1,13 +1,13 @@ class BaseMailer < ActionMailer::Base self.mailer_name = "base_mailer" - default to: 'system@test.lindsaar.net', - from: 'jose@test.plataformatec.com', - reply_to: 'mikel@test.lindsaar.net' + default to: "system@test.lindsaar.net", + from: "jose@test.plataformatec.com", + reply_to: "mikel@test.lindsaar.net" def welcome(hash = {}) - headers['X-SPAM'] = "Not SPAM" - mail({subject: "The first email on new API!"}.merge!(hash)) + headers["X-SPAM"] = "Not SPAM" + mail({ subject: "The first email on new API!" }.merge!(hash)) end def welcome_with_headers(hash = {}) @@ -28,30 +28,30 @@ class BaseMailer < ActionMailer::Base end def inline_attachment - attachments.inline['logo.png'] = "\312\213\254\232" + attachments.inline["logo.png"] = "\312\213\254\232" mail end def attachment_with_content(hash = {}) - attachments['invoice.pdf'] = 'This is test File content' + attachments["invoice.pdf"] = "This is test File content" mail(hash) end def attachment_with_hash - attachments['invoice.jpg'] = { data: ::Base64.encode64("\312\213\254\232)b"), + attachments["invoice.jpg"] = { data: ::Base64.encode64("\312\213\254\232)b"), mime_type: "image/x-jpg", transfer_encoding: "base64" } mail end def attachment_with_hash_default_encoding - attachments['invoice.jpg'] = { data: "\312\213\254\232)b", + attachments["invoice.jpg"] = { data: "\312\213\254\232)b", mime_type: "image/x-jpg" } mail end def implicit_multipart(hash = {}) - attachments['invoice.pdf'] = 'This is test File content' if hash.delete(:attachments) + attachments["invoice.pdf"] = "This is test File content" if hash.delete(:attachments) mail(hash) end @@ -60,10 +60,10 @@ class BaseMailer < ActionMailer::Base end def explicit_multipart(hash = {}) - attachments['invoice.pdf'] = 'This is test File content' if hash.delete(:attachments) + attachments["invoice.pdf"] = "This is test File content" if hash.delete(:attachments) mail(hash) do |format| - format.text { render text: "TEXT Explicit Multipart" } - format.html { render text: "HTML Explicit Multipart" } + format.text { render plain: "TEXT Explicit Multipart" } + format.html { render plain: "HTML Explicit Multipart" } end end @@ -76,7 +76,7 @@ class BaseMailer < ActionMailer::Base def explicit_multipart_with_any(hash = {}) mail(hash) do |format| - format.any(:text, :html){ render text: "Format with any!" } + format.any(:text, :html) { render plain: "Format with any!" } end end @@ -88,8 +88,8 @@ class BaseMailer < ActionMailer::Base def explicit_multipart_with_options(include_html = false) mail do |format| - format.text(content_transfer_encoding: "base64"){ render "welcome" } - format.html{ render "welcome" } if include_html + format.text(content_transfer_encoding: "base64") { render "welcome" } + format.html { render "welcome" } if include_html end end @@ -100,18 +100,18 @@ class BaseMailer < ActionMailer::Base end end - def implicit_different_template(template_name='') + def implicit_different_template(template_name="") mail(template_name: template_name) end - def explicit_different_template(template_name='') + def explicit_different_template(template_name="") mail do |format| format.text { render template: "#{mailer_name}/#{template_name}" } format.html { render template: "#{mailer_name}/#{template_name}" } end end - def different_layout(layout_name='') + def different_layout(layout_name="") mail do |format| format.text { render layout: layout_name } format.html { render layout: layout_name } @@ -131,6 +131,6 @@ class BaseMailer < ActionMailer::Base end def with_subject_interpolations - mail(subject: default_i18n_subject(rapper_or_impersonator: 'Slim Shady'), body: '') + mail(subject: default_i18n_subject(rapper_or_impersonator: "Slim Shady"), body: "") end end diff --git a/actionmailer/test/mailers/caching_mailer.rb b/actionmailer/test/mailers/caching_mailer.rb new file mode 100644 index 0000000000..92d3cff7c9 --- /dev/null +++ b/actionmailer/test/mailers/caching_mailer.rb @@ -0,0 +1,23 @@ +class CachingMailer < ActionMailer::Base + self.mailer_name = "caching_mailer" + + def fragment_cache + mail(subject: "welcome", template_name: "fragment_cache") + end + + def fragment_cache_in_partials + mail(subject: "welcome", template_name: "fragment_cache_in_partials") + end + + def skip_fragment_cache_digesting + mail(subject: "welcome", template_name: "skip_fragment_cache_digesting") + end + + def fragment_caching_options + mail(subject: "welcome", template_name: "fragment_caching_options") + end + + def multipart_cache + mail(subject: "welcome", template_name: "multipart_cache") + end +end diff --git a/actionmailer/test/mailers/delayed_mailer.rb b/actionmailer/test/mailers/delayed_mailer.rb index 62d4baa434..cae4ec2f48 100644 --- a/actionmailer/test/mailers/delayed_mailer.rb +++ b/actionmailer/test/mailers/delayed_mailer.rb @@ -1,6 +1,26 @@ +require "active_job/arguments" + +class DelayedMailerError < StandardError; end + class DelayedMailer < ActionMailer::Base + cattr_accessor :last_error + cattr_accessor :last_rescue_from_instance + + rescue_from DelayedMailerError do |error| + @@last_error = error + @@last_rescue_from_instance = self + end + + rescue_from ActiveJob::DeserializationError do |error| + @@last_error = error + @@last_rescue_from_instance = self + end def test_message(*) - mail(from: 'test-sender@test.com', to: 'test-receiver@test.com', subject: 'Test Subject', body: 'Test Body') + mail(from: "test-sender@test.com", to: "test-receiver@test.com", subject: "Test Subject", body: "Test Body") + end + + def test_raise(klass_name) + raise klass_name.constantize, "boom" end end diff --git a/actionmailer/test/mailers/proc_mailer.rb b/actionmailer/test/mailers/proc_mailer.rb index 7e189d861f..2487db9eb9 100644 --- a/actionmailer/test/mailers/proc_mailer.rb +++ b/actionmailer/test/mailers/proc_mailer.rb @@ -1,8 +1,8 @@ class ProcMailer < ActionMailer::Base - default to: 'system@test.lindsaar.net', - 'X-Proc-Method' => Proc.new { Time.now.to_i.to_s }, + default to: "system@test.lindsaar.net", + "X-Proc-Method" => Proc.new { Time.now.to_i.to_s }, subject: Proc.new { give_a_greeting }, - 'x-has-to-proc' => :symbol + "x-has-to-proc" => :symbol def welcome mail @@ -10,8 +10,7 @@ class ProcMailer < ActionMailer::Base private - def give_a_greeting - "Thanks for signing up this afternoon" - end - + def give_a_greeting + "Thanks for signing up this afternoon" + end end diff --git a/actionmailer/test/message_delivery_test.rb b/actionmailer/test/message_delivery_test.rb index b834cdd08c..0eb81a8496 100644 --- a/actionmailer/test/message_delivery_test.rb +++ b/actionmailer/test/message_delivery_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'active_job' -require 'mailers/delayed_mailer' +require "abstract_unit" +require "active_job" +require "mailers/delayed_mailer" class MessageDeliveryTest < ActiveSupport::TestCase include ActiveJob::TestHelper @@ -12,39 +12,46 @@ class MessageDeliveryTest < ActiveSupport::TestCase ActionMailer::Base.deliver_later_queue_name = :test_queue ActionMailer::Base.delivery_method = :test ActiveJob::Base.logger = Logger.new(nil) - @mail = DelayedMailer.test_message(1, 2, 3) ActionMailer::Base.deliveries.clear ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true + + DelayedMailer.last_error = nil + DelayedMailer.last_rescue_from_instance = nil + + @mail = DelayedMailer.test_message(1, 2, 3) end teardown do ActiveJob::Base.logger = @previous_logger ActionMailer::Base.delivery_method = @previous_delivery_method ActionMailer::Base.deliver_later_queue_name = @previous_deliver_later_queue_name + + DelayedMailer.last_error = nil + DelayedMailer.last_rescue_from_instance = nil end - test 'should have a message' do + test "should have a message" do assert @mail.message end - test 'its message should be a Mail::Message' do + test "its message should be a Mail::Message" do assert_equal Mail::Message , @mail.message.class end - test 'should respond to .deliver_later' do + test "should respond to .deliver_later" do assert_respond_to @mail, :deliver_later end - test 'should respond to .deliver_later!' do + test "should respond to .deliver_later!" do assert_respond_to @mail, :deliver_later! end - test 'should respond to .deliver_now' do + test "should respond to .deliver_now" do assert_respond_to @mail, :deliver_now end - test 'should respond to .deliver_now!' do + test "should respond to .deliver_now!" do assert_respond_to @mail, :deliver_now! end @@ -55,42 +62,92 @@ class MessageDeliveryTest < ActiveSupport::TestCase ActionMailer::Base.deliveries.clear end - test 'should enqueue the email with :deliver_now delivery method' do - assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3]) do + test "should enqueue the email with :deliver_now delivery method" do + assert_performed_with(job: ActionMailer::DeliveryJob, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3]) do @mail.deliver_later end end - test 'should enqueue the email with :deliver_now! delivery method' do - assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now!', 1, 2, 3]) do + test "should enqueue the email with :deliver_now! delivery method" do + assert_performed_with(job: ActionMailer::DeliveryJob, args: ["DelayedMailer", "test_message", "deliver_now!", 1, 2, 3]) do @mail.deliver_later! end end - test 'should enqueue a delivery with a delay' do + test "should enqueue a delivery with a delay" do travel_to Time.new(2004, 11, 24, 01, 04, 44) do - assert_performed_with(job: ActionMailer::DeliveryJob, at: Time.current.to_f+600.seconds, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3]) do + assert_performed_with(job: ActionMailer::DeliveryJob, at: Time.current.to_f+600.seconds, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3]) do @mail.deliver_later wait: 600.seconds end end end - test 'should enqueue a delivery at a specific time' do + test "should enqueue a delivery at a specific time" do later_time = Time.now.to_f + 3600 - assert_performed_with(job: ActionMailer::DeliveryJob, at: later_time, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3]) do + assert_performed_with(job: ActionMailer::DeliveryJob, at: later_time, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3]) do @mail.deliver_later wait_until: later_time end end - test 'should enqueue the job on the correct queue' do - assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3], queue: "test_queue") do + test "should enqueue the job on the correct queue" do + assert_performed_with(job: ActionMailer::DeliveryJob, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3], queue: "test_queue") do @mail.deliver_later end end - test 'can override the queue when enqueuing mail' do - assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3], queue: "another_queue") do + test "can override the queue when enqueuing mail" do + assert_performed_with(job: ActionMailer::DeliveryJob, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3], queue: "another_queue") do @mail.deliver_later(queue: :another_queue) end end + + test "deliver_later after accessing the message is disallowed" do + @mail.message # Load the message, which calls the mailer method. + + assert_raise RuntimeError do + @mail.deliver_later + end + end + + test "job delegates error handling to mailer" do + # Superclass not rescued by mailer's rescue_from RuntimeError + message = DelayedMailer.test_raise("StandardError") + assert_raise(StandardError) { message.deliver_later } + assert_nil DelayedMailer.last_error + assert_nil DelayedMailer.last_rescue_from_instance + + # Rescued by mailer's rescue_from RuntimeError + message = DelayedMailer.test_raise("DelayedMailerError") + assert_nothing_raised { message.deliver_later } + assert_equal "boom", DelayedMailer.last_error.message + assert_kind_of DelayedMailer, DelayedMailer.last_rescue_from_instance + end + + class DeserializationErrorFixture + include GlobalID::Identification + + def self.find(id) + raise "boom, missing find" + end + + attr_reader :id + def initialize(id = 1) + @id = id + end + + def to_global_id(options = {}) + super app: "foo" + end + end + + test "job delegates deserialization errors to mailer class" do + # Inject an argument that can't be deserialized. + message = DelayedMailer.test_message(DeserializationErrorFixture.new) + + # DeserializationError is raised, rescued, and delegated to the handler + # on the mailer class. + assert_nothing_raised { message.deliver_later } + assert_equal DelayedMailer, DelayedMailer.last_rescue_from_instance + assert_equal "Error while trying to deserialize arguments: boom, missing find", DelayedMailer.last_error.message + end end diff --git a/actionmailer/test/test_case_test.rb b/actionmailer/test/test_case_test.rb index 86fd37bea6..193d107b0a 100644 --- a/actionmailer/test/test_case_test.rb +++ b/actionmailer/test/test_case_test.rb @@ -1,8 +1,46 @@ -require 'abstract_unit' +require "abstract_unit" class TestTestMailer < ActionMailer::Base end +class ClearTestDeliveriesMixinTest < ActiveSupport::TestCase + include ActionMailer::TestCase::ClearTestDeliveries + + def before_setup + ActionMailer::Base.delivery_method, @original_delivery_method = :test, ActionMailer::Base.delivery_method + ActionMailer::Base.deliveries << "better clear me, setup" + super + end + + def after_teardown + super + assert_equal [], ActionMailer::Base.deliveries + ActionMailer::Base.delivery_method = @original_delivery_method + end + + def test_deliveries_are_cleared_on_setup_and_teardown + assert_equal [], ActionMailer::Base.deliveries + ActionMailer::Base.deliveries << "better clear me, teardown" + end +end + +class MailerDeliveriesClearingTest < ActionMailer::TestCase + def before_setup + ActionMailer::Base.deliveries << "better clear me, setup" + super + end + + def after_teardown + super + assert_equal [], ActionMailer::Base.deliveries + end + + def test_deliveries_are_cleared_on_setup_and_teardown + assert_equal [], ActionMailer::Base.deliveries + ActionMailer::Base.deliveries << "better clear me, teardown" + end +end + class CrazyNameMailerTest < ActionMailer::TestCase tests TestTestMailer @@ -20,7 +58,7 @@ class CrazySymbolNameMailerTest < ActionMailer::TestCase end class CrazyStringNameMailerTest < ActionMailer::TestCase - tests 'test_test_mailer' + tests "test_test_mailer" def test_set_mailer_class_manual_using_string assert_equal TestTestMailer, self.class.mailer_class diff --git a/actionmailer/test/test_helper_test.rb b/actionmailer/test/test_helper_test.rb index 0a4bc75d3e..31ac5a5211 100644 --- a/actionmailer/test/test_helper_test.rb +++ b/actionmailer/test/test_helper_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/testing/stream' +require "abstract_unit" +require "active_support/testing/stream" class TestHelperMailer < ActionMailer::Base def test @@ -40,11 +40,11 @@ class TestHelperMailerTest < ActionMailer::TestCase end def test_encode - assert_equal '=?UTF-8?Q?This_is_=E3=81=82_string?=', encode('This is ã‚ string') + assert_equal "=?UTF-8?Q?This_is_=E3=81=82_string?=", encode("This is ã‚ string") end def test_read_fixture - assert_equal ['Welcome!'], read_fixture('welcome') + assert_equal ["Welcome!"], read_fixture("welcome") end def test_assert_emails @@ -198,6 +198,6 @@ class AnotherTestHelperMailerTest < ActionMailer::TestCase def test_setup_shouldnt_conflict_with_mailer_setup assert_kind_of Mail::Message, @expected - assert_equal 'a value', @test_var + assert_equal "a value", @test_var end end diff --git a/actionmailer/test/url_test.rb b/actionmailer/test/url_test.rb index 7928fe9542..27f6e8a491 100644 --- a/actionmailer/test/url_test.rb +++ b/actionmailer/test/url_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'action_controller' +require "abstract_unit" +require "action_controller" class WelcomeController < ActionController::Base end @@ -11,10 +11,10 @@ class ActionMailer::Base end class UrlTestMailer < ActionMailer::Base - default_url_options[:host] = 'www.basecamphq.com' + default_url_options[:host] = "www.basecamphq.com" configure do |c| - c.assets_dir = '' # To get the tests to pass + c.assets_dir = "" # To get the tests to pass end def signed_up_with_url(recipient) @@ -27,14 +27,14 @@ class UrlTestMailer < ActionMailer::Base def exercise_url_for(options) @options = options @url = url_for(@options) - mail(from: 'from@example.com', to: 'to@example.com', subject: 'subject') + mail(from: "from@example.com", to: "to@example.com", subject: "subject") end end class ActionMailerUrlTest < ActionMailer::TestCase class DummyModel def self.model_name - OpenStruct.new(route_key: 'dummy_model') + OpenStruct.new(route_key: "dummy_model") end def persisted? @@ -64,7 +64,7 @@ class ActionMailerUrlTest < ActionMailer::TestCase end def assert_url_for(expected, options, relative = false) - expected = "http://www.basecamphq.com#{expected}" if expected.start_with?('/') && !relative + expected = "http://www.basecamphq.com#{expected}" if expected.start_with?("/") && !relative urls = UrlTestMailer.exercise_url_for(options).body.to_s.chomp.split assert_equal expected, urls.first @@ -72,44 +72,48 @@ class ActionMailerUrlTest < ActionMailer::TestCase end def setup - @recipient = 'test@localhost' + @recipient = "test@localhost" end def test_url_for UrlTestMailer.delivery_method = :test AppRoutes.draw do - get ':controller(/:action(/:id))' - get '/welcome' => 'foo#bar', as: 'welcome' - get '/dummy_model' => 'foo#baz', as: 'dummy_model' + ActiveSupport::Deprecation.silence do + get ":controller(/:action(/:id))" + get "/welcome" => "foo#bar", as: "welcome" + get "/dummy_model" => "foo#baz", as: "dummy_model" + end end # string - assert_url_for 'http://foo/', 'http://foo/' + assert_url_for "http://foo/", "http://foo/" # symbol - assert_url_for '/welcome', :welcome + assert_url_for "/welcome", :welcome # hash - assert_url_for '/a/b/c', controller: 'a', action: 'b', id: 'c' - assert_url_for '/a/b/c', {controller: 'a', action: 'b', id: 'c', only_path: true}, true + assert_url_for "/a/b/c", controller: "a", action: "b", id: "c" + assert_url_for "/a/b/c", { controller: "a", action: "b", id: "c", only_path: true }, true # model - assert_url_for '/dummy_model', DummyModel.new + assert_url_for "/dummy_model", DummyModel.new # class - assert_url_for '/dummy_model', DummyModel + assert_url_for "/dummy_model", DummyModel # array - assert_url_for '/dummy_model' , [DummyModel] + assert_url_for "/dummy_model" , [DummyModel] end def test_signed_up_with_url UrlTestMailer.delivery_method = :test AppRoutes.draw do - get ':controller(/:action(/:id))' - get '/welcome' => "foo#bar", as: "welcome" + ActiveSupport::Deprecation.silence do + get ":controller(/:action(/:id))" + get "/welcome" => "foo#bar", as: "welcome" + end end expected = new_mail @@ -124,15 +128,15 @@ class ActionMailerUrlTest < ActionMailer::TestCase assert_nothing_raised { created = UrlTestMailer.signed_up_with_url(@recipient) } assert_not_nil created - expected.message_id = '<123@456>' - created.message_id = '<123@456>' + expected.message_id = "<123@456>" + created.message_id = "<123@456>" assert_dom_equal expected.encoded, created.encoded assert_nothing_raised { UrlTestMailer.signed_up_with_url(@recipient).deliver_now } assert_not_nil ActionMailer::Base.deliveries.first delivered = ActionMailer::Base.deliveries.first - delivered.message_id = '<123@456>' + delivered.message_id = "<123@456>" assert_dom_equal expected.encoded, delivered.encoded end end diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 931313612c..c9c347ea26 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,715 +1,265 @@ -* Add request encoding and response parsing to integration tests. +* Allow keys not found in RACK_KEY_TRANSLATION for setting the environment when rendering + arbitrary templates. - What previously was: + *Sammy Larbi* - ```ruby - require 'test_helper' +* Remove deprecated support to non-keyword arguments in `ActionDispatch::IntegrationTest#process`, + `#get`, `#post`, `#patch`, `#put`, `#delete`, and `#head`. - class ApiTest < ActionDispatch::IntegrationTest - test 'creates articles' do - assert_difference -> { Article.count } do - post articles_path(format: :json), - params: { article: { title: 'Ahoy!' } }.to_json, - headers: { 'Content-Type' => 'application/json' } - end - - assert_equal({ 'id' => Article.last.id, 'title' => 'Ahoy!' }, JSON.parse(response.body)) - end - end - ``` - - Can now be written as: - - ```ruby - require 'test_helper' - - class ApiTest < ActionDispatch::IntegrationTest - test 'creates articles' do - assert_difference -> { Article.count } do - post articles_path, params: { article: { title: 'Ahoy!' } }, as: :json - end - - assert_equal({ 'id' => Article.last.id, 'title' => 'Ahoy!' }, response.parsed_body) - end - end - ``` - - Passing `as: :json` to integration test request helpers will set the format, - content type and encode the parameters as JSON. - - Then on the response side, `parsed_body` will parse the body according to the - content type the response has. - - Currently JSON is the only supported MIME type. Add your own with - `ActionDispatch::IntegrationTest.register_encoder`. - - *Kasper Timm Hansen* - -* Add image/svg+xml as a default mime type. - - *DHH* - -## Rails 5.0.0.beta2 (February 01, 2016) ## - -* Add `-g` and `-c` options to `bin/rails routes`. These options return the url `name`, `verb` and - `path` field that match the pattern or match a specific controller. - - Deprecate `CONTROLLER` env variable in `bin/rails routes`. - - See #18902. - - *Anton Davydov* & *Vipul A M* - -* Response etags to always be weak: Prefixes 'W/' to value returned by - `ActionDispatch::Http::Cache::Response#etag=`, such that etags set in - `fresh_when` and `stale?` are weak. - - Fixes #17556. - - *Abhishek Yadav* - -* Provide the name of HTTP Status code in assertions. - - *Sean Collins* - -* More explicit error message when running `rake routes`. `CONTROLLER` argument - can now be supplied in different ways: - `Rails::WelcomeController`, `Rails::Welcome`, `rails/welcome`. - - Fixes #22918. - - *Edouard Chin* - -* Allow `ActionController::Parameters` instances as an argument to URL - helper methods. An `ArgumentError` will be raised if the passed parameters - are not secure. - - Fixes #22832. - - *Prathamesh Sonpatki* - -* Add option for per-form CSRF tokens. - - *Greg Ose & Ben Toews* - -* Add tests and documentation for `ActionController::Renderers::use_renderers`. - - *Benjamin Fleischer* - -* Fix `ActionController::Parameters#convert_parameters_to_hashes` to return filtered - or unfiltered values based on from where it is called, `to_h` or `to_unsafe_h` - respectively. - - Fixes #22841. - - *Prathamesh Sonpatki* - -* Add `ActionController::Parameters#include?` - - *Justin Coyne* - -## Rails 5.0.0.beta1 (December 18, 2015) ## - -* Deprecate `redirect_to :back` in favor of `redirect_back`, which accepts a - required `fallback_location` argument, thus eliminating the possibility of a - `RedirectBackError`. - - *Derek Prior* - -* Add `redirect_back` method to `ActionController::Redirecting` to provide a - way to safely redirect to the `HTTP_REFERER` if it is present, falling back - to a provided redirect otherwise. - - *Derek Prior* - -* `ActionController::TestCase` will be moved to it's own gem in Rails 5.1 - - With the speed improvements made to `ActionDispatch::IntegrationTest` we no - longer need to keep two separate code bases for testing controllers. In - Rails 5.1 `ActionController::TestCase` will be deprecated and moved into a - gem outside of Rails source. - - This is a documentation deprecation so that going forward so new tests will use - `ActionDispatch::IntegrationTest` instead of `ActionController::TestCase`. - - *Eileen M. Uchitelle* - -* Add a `response_format` option to `ActionDispatch::DebugExceptions` - to configure the format of the response when errors occur in - development mode. - - If `response_format` is `:default` the debug info will be rendered - in an HTML page. In the other hand, if the provided value is `:api` - the debug info will be rendered in the original response format. - - *Jorge Bejar* - -* Change the `protect_from_forgery` prepend default to `false`. - - Per this comment - https://github.com/rails/rails/pull/18334#issuecomment-69234050 we want - `protect_from_forgery` to default to `prepend: false`. - - `protect_from_forgery` will now be inserted into the callback chain at the - point it is called in your application. This is useful for cases where you - want to `protect_from_forgery` after you perform required authentication - callbacks or other callbacks that are required to run after forgery protection. - - If you want `protect_from_forgery` callbacks to always run first, regardless of - position they are called in your application then you can add `prepend: true` - to your `protect_from_forgery` call. - - Example: - - ```ruby - protect_from_forgery prepend: true - ``` - - *Eileen M. Uchitelle* - -* In url_for, never append a question mark to the URL when the query string - is empty anyway. (It used to do that when called like `url_for(controller: - 'x', action: 'y', q: {})`.) - - *Paul Grayson* - -* Catch invalid UTF-8 querystring values and respond with BadRequest - - Check querystring params for invalid UTF-8 characters, and raise an - ActionController::BadRequest error if present. Previously these strings - would typically trigger errors further down the stack. - - *Grey Baker* - -* Parse RSS/ATOM responses as XML, not HTML. - - *Alexander Kaupanin* - -* Show helpful message in `BadRequest` exceptions due to invalid path - parameter encodings. - - Fixes #21923. - - *Agis Anastasopoulos* - -* Add the ability of returning arbitrary headers to `ActionDispatch::Static`. - - Now ActionDispatch::Static can accept HTTP headers so that developers - will have control of returning arbitrary headers like - 'Access-Control-Allow-Origin' when a response is delivered. They can be - configured with `#config`: - - Example: - - config.public_file_server.headers = { - "Cache-Control" => "public, max-age=60", - "Access-Control-Allow-Origin" => "http://rubyonrails.org" - } - - *Yuki Nishijima* - -* Allow multiple `root` routes in same scope level. Example: - - Example: - - root 'blog#show', constraints: ->(req) { Hostname.blog_site?(req.host) } - root 'landing#show' - - *Rafael Sales* - -* Fix regression in mounted engine named routes generation for app deployed to - a subdirectory. `relative_url_root` was prepended to the path twice (e.g. - "/subdir/subdir/engine_path" instead of "/subdir/engine_path") - - Fixes #20920. Fixes #21459. - - *Matthew Erhard* - -* `ActionDispatch::Response#new` no longer applies default headers. If you want - default headers applied to the response object, then call - `ActionDispatch::Response.create`. This change only impacts people who are - directly constructing an `ActionDispatch::Response` object. - -* Accessing mime types via constants like `Mime::HTML` is deprecated. Please - change code like this: - - Mime::HTML - - To this: - - Mime[:html] - - This change is so that Rails will not manage a list of constants, and fixes - an issue where if a type isn't registered you could possibly get the wrong - object. - - `Mime[:html]` is available in older versions of Rails, too, so you can - safely change libraries and plugins and maintain compatibility with - multiple versions of Rails. - -* `url_for` does not modify its arguments when generating polymorphic URLs. - - *Bernerd Schaefer* - -* Make it easier to opt in to `config.force_ssl` and `config.ssl_options` by - making them less dangerous to try and easier to disable. - - SSL redirect: - * Move `:host` and `:port` options within `redirect: { … }`. Deprecate. - * Introduce `:status` and `:body` to customize the redirect response. - The 301 permanent default makes it difficult to test the redirect and - back out of it since browsers remember the 301. Test with a 302 or 307 - instead, then switch to 301 once you're confident that all is well. - - HTTP Strict Transport Security (HSTS): - * Shorter max-age. Shorten the default max-age from 1 year to 180 days, - the low end for https://www.ssllabs.com/ssltest/ grading and greater - than the 18-week minimum to qualify for browser preload lists. - * Disabling HSTS. Setting `hsts: false` now sets `hsts { expires: 0 }` - instead of omitting the header. Omitting does nothing to disable HSTS - since browsers hang on to your previous settings until they expire. - Sending `{ hsts: { expires: 0 }}` flushes out old browser settings and - actually disables HSTS: - http://tools.ietf.org/html/rfc6797#section-6.1.1 - * HSTS Preload. Introduce `preload: true` to set the `preload` flag, - indicating that your site may be included in browser preload lists, - including Chrome, Firefox, Safari, IE11, and Edge. Submit your site: - https://hstspreload.appspot.com - - *Jeremy Daer* - -* Update `ActionController::TestSession#fetch` to behave more like - `ActionDispatch::Request::Session#fetch` when using non-string keys. - - *Jeremy Friesen* - -* Using strings or symbols for middleware class names is deprecated. Convert - things like this: - - middleware.use "Foo::Bar" - - to this: - - middleware.use Foo::Bar - -* `ActionController::TestSession` now accepts a default value as well as - a block for generating a default value based off the key provided. - - This fixes calls to `session#fetch` in `ApplicationController` instances that - take more two arguments or a block from raising `ArgumentError: wrong - number of arguments (2 for 1)` when performing controller tests. - - *Matthew Gerrior* - -* Fix `ActionController::Parameters#fetch` overwriting `KeyError` returned by - default block. - - *Jonas Schuber Erlandsson*, *Roque Pinel* - -* `ActionController::Parameters` no longer inherits from - `HashWithIndifferentAccess` + *Rafael Mendonça França* - Inheriting from `HashWithIndifferentAccess` allowed users to call any - enumerable methods on `Parameters` object, resulting in a risk of losing the - `permitted?` status or even getting back a pure `Hash` object instead of - a `Parameters` object with proper sanitization. +* Remove deprecated `ActionDispatch::IntegrationTest#*_via_redirect`. - By not inheriting from `HashWithIndifferentAccess`, we are able to make - sure that all methods that are defined in `Parameters` object will return - a proper `Parameters` object with a correct `permitted?` flag. + *Rafael Mendonça França* - *Prem Sichanugrist* +* Remove deprecated `ActionDispatch::IntegrationTest#xml_http_request`. -* Replaced `ActiveSupport::Concurrency::Latch` with `Concurrent::CountDownLatch` - from the concurrent-ruby gem. + *Rafael Mendonça França* - *Jerry D'Antonio* +* Remove deprecated support for passing `:path` and route path as strings in `ActionDispatch::Routing::Mapper#match`. -* Add ability to filter parameters based on parent keys. + *Rafael Mendonça França* - # matches {credit_card: {code: "xxxx"}} - # doesn't match {file: { code: "xxxx"}} - config.filter_parameters += [ "credit_card.code" ] +* Remove deprecated support for passing path as `nil` in `ActionDispatch::Routing::Mapper#match`. - See #13897. + *Rafael Mendonça França* - *Guillaume Malette* +* Remove deprecated `cache_control` argument from `ActionDispatch::Static#initialize`. -* Deprecate passing first parameter as `Hash` and default status code for `head` method. + *Rafael Mendonça França* - *Mehmet Emin İNAÇ* +* Remove deprecated support to passing strings or symbols to the middleware stack. -* Adds`Rack::Utils::ParameterTypeError` and `Rack::Utils::InvalidParameterError` - to the rescue_responses hash in `ExceptionWrapper` (Rack recommends - integrators serve 400s for both of these). + *Rafael Mendonça França* - *Grey Baker* +* Change HSTS subdomain to true. -* Add support for API only apps. - `ActionController::API` is added as a replacement of - `ActionController::Base` for this kind of applications. + *Rafael Mendonça França* - *Santiago Pastorino*, *Jorge Bejar* +* Remove deprecated `host` and `port` ssl options. -* Remove `assigns` and `assert_template`. Both methods have been extracted - into a gem at https://github.com/rails/rails-controller-testing. + *Rafael Mendonça França* - See #18950. +* Remove deprecated `const_error` argument in + `ActionDispatch::Session::SessionRestoreError#initialize`. - *Alan Guo Xiang Tan* + *Rafael Mendonça França* -* `FileHandler` and `Static` middleware initializers accept `index` argument - to configure the directory index file name. Defaults to `index` (as in - `index.html`). +* Remove deprecated `#original_exception` in `ActionDispatch::Session::SessionRestoreError`. - See #20017. + *Rafael Mendonça França* - *Eliot Sykes* +* Deprecate `ActionDispatch::ParamsParser::ParseError` in favor of + `ActionDispatch::Http::Parameters::ParseError`. -* Deprecate `:nothing` option for `render` method. + *Rafael Mendonça França* - *Mehmet Emin İNAÇ* +* Remove deprecated `ActionDispatch::ParamsParser`. -* Fix `rake routes` not showing the right format when - nesting multiple routes. + *Rafael Mendonça França* - See #18373. +* Remove deprecated `original_exception` and `message` arguments in + `ActionDispatch::ParamsParser::ParseError#initialize`. - *Ravil Bayramgalin* + *Rafael Mendonça França* -* Add ability to override default form builder for a controller. +* Remove deprecated `#original_exception` in `ActionDispatch::ParamsParser::ParseError`. - class AdminController < ApplicationController - default_form_builder AdminFormBuilder - end + *Rafael Mendonça França* - *Kevin McPhillips* +* Remove deprecated access to mime types through constants. -* For actions with no corresponding templates, render `head :no_content` - instead of raising an error. This allows for slimmer API controller - methods that simply work, without needing further instructions. + *Rafael Mendonça França* - See #19036. +* Remove deprecated support to non-keyword arguments in `ActionController::TestCase#process`, + `#get`, `#post`, `#patch`, `#put`, `#delete`, and `#head`. - *Stephen Bussey* + *Rafael Mendonça França* -* Provide friendlier access to request variants. +* Remove deprecated `xml_http_request` and `xhr` methods in `ActionController::TestCase`. - request.variant = :phone - request.variant.phone? # true - request.variant.tablet? # false + *Rafael Mendonça França* - request.variant = [:phone, :tablet] - request.variant.phone? # true - request.variant.desktop? # false - request.variant.any?(:phone, :desktop) # true - request.variant.any?(:desktop, :watch) # false +* Remove deprecated methods in `ActionController::Parameters`. - *George Claghorn* + *Rafael Mendonça França* -* Fix regression where a gzip file response would have a Content-type, - even when it was a 304 status code. +* Remove deprecated support to comparing a `ActionController::Parameters` + with a `Hash`. - See #19271. + *Rafael Mendonça França* - *Kohei Suzuki* +* Remove deprecated support to `:text` in `render`. -* Fix handling of empty `X_FORWARDED_HOST` header in `raw_host_with_port`. + *Rafael Mendonça França* - Previously, an empty `X_FORWARDED_HOST` header would cause - `Actiondispatch::Http:URL.raw_host_with_port` to return `nil`, causing - `Actiondispatch::Http:URL.host` to raise a `NoMethodError`. +* Remove deprecated support to `:nothing` in `render`. - *Adam Forsyth* + *Rafael Mendonça França* -* Allow `Bearer` as token-keyword in `Authorization-Header`. +* Remove deprecated support to `:back` in `redirect_to`. - Additionally to `Token`, the keyword `Bearer` is acceptable as a keyword - for the auth-token. The `Bearer` keyword is described in the original - OAuth RFC and used in libraries like Angular-JWT. + *Rafael Mendonça França* - See #19094. +* Remove deprecated support to passing status as option `head`. - *Peter Schröder* + *Rafael Mendonça França* -* Drop request class from `RouteSet` constructor. +* Remove deprecated support to passing original exception to `ActionController::BadRequest` + and the `ActionController::BadRequest#original_exception` method. - If you would like to use a custom request class, please subclass and implement - the `request_class` method. + *Rafael Mendonça França* - *tenderlove@ruby-lang.org* +* Remove deprecated methods `skip_action_callback`, `skip_filter`, `before_filter`, + `prepend_before_filter`, `skip_before_filter`, `append_before_filter`, `around_filter` + `prepend_around_filter`, `skip_around_filter`, `append_around_filter`, `after_filter`, + `prepend_after_filter`, `skip_after_filter` and `append_after_filter`. -* Fallback to `ENV['RAILS_RELATIVE_URL_ROOT']` in `url_for`. + *Rafael Mendonça França* - Fixed an issue where the `RAILS_RELATIVE_URL_ROOT` environment variable is not - prepended to the path when `url_for` is called. If `SCRIPT_NAME` (used by Rack) - is set, it takes precedence. +* Show an "unmatched constraints" error when params fail to match constraints + on a matched route, rather than a "missing keys" error. - Fixes #5122. + Fixes #26470. - *Yasyf Mohamedali* + *Chris Carter* -* Partitioning of routes is now done when the routes are being drawn. This - helps to decrease the time spent filtering the routes during the first request. +* Fix adding implicitly rendered template digests to ETags. - *Guo Xiang Tan* + Fixes a case when modifying an implicitly rendered template for a + controller action using `fresh_when` or `stale?` would not result in a new + `ETag` value. -* Fix regression in functional tests. Responses should have default headers - assigned. + *Javan Makhmali* - See #18423. +* Make `fixture_file_upload` work in integration tests. - *Jeremy Kemper*, *Yves Senn* + *Yuji Yaginuma* -* Deprecate `AbstractController#skip_action_callback` in favor of individual skip_callback methods - (which can be made to raise an error if no callback was removed). +* Add `to_param` to `ActionController::Parameters` deprecations. - *Iain Beeston* + In the future `ActionController::Parameters` are discouraged from being used + in URLs without explicit whitelisting. Go through `to_h` to use `to_param`. -* Alias the `ActionDispatch::Request#uuid` method to `ActionDispatch::Request#request_id`. - Due to implementation, `config.log_tags = [:request_id]` also works in substitute - for `config.log_tags = [:uuid]`. + *Kir Shatrov* - *David Ilizarov* +* Fix nested multiple roots -* Change filter on /rails/info/routes to use an actual path regexp from rails - and not approximate javascript version. Oniguruma supports much more - extensive list of features than javascript regexp engine. + The PR #20940 enabled the use of multiple roots with different constraints + at the top level but unfortunately didn't work when those roots were inside + a namespace and also broke the use of root inside a namespace after a top + level root was defined because the check for the existence of the named route + used the global :root name and not the namespaced name. - Fixes #18402. + This is fixed by using the name_for_action method to expand the :root name to + the full namespaced name. We can pass nil for the second argument as we're not + dealing with resource definitions so don't need to handle the cases for edit + and new routes. - *Ravil Bayramgalin* + Fixes #26148. -* Non-string authenticity tokens do not raise NoMethodError when decoding - the masked token. + *Ryo Hashimoto*, *Andrew White* - *Ville Lautanala* +* Include the content of the flash in the auto-generated etag. This solves the following problem: -* Add `http_cache_forever` to Action Controller, so we can cache a response - that never gets expired. + 1. POST /messages + 2. redirect_to messages_url, notice: 'Message was created' + 3. GET /messages/1 + 4. GET /messages - *arthurnn* + Step 4 would before still include the flash message, even though it's no longer relevant, + because the etag cache was recorded with the flash in place and didn't change when it was gone. -* `ActionController#translate` supports symbols as shortcuts. - When a shortcut is given it also performs the lookup without the action - name. + *DHH* - *Max Melentiev* +* SSL: Changes redirect behavior for all non-GET and non-HEAD requests + (like POST/PUT/PATCH etc) to `http://` resources to redirect to `https://` + with a [307 status code](http://tools.ietf.org/html/rfc7231#section-6.4.7) instead of [301 status code](http://tools.ietf.org/html/rfc7231#section-6.4.2). -* Expand `ActionController::ConditionalGet#fresh_when` and `stale?` to also - accept a collection of records as the first argument, so that the - following code can be written in a shorter form. + 307 status code instructs the HTTP clients to preserve the original + request method while redirecting. It has been part of HTTP RFC since + 1999 and is implemented/recognized by most (if not all) user agents. # Before - def index - @articles = Article.all - fresh_when(etag: @articles, last_modified: @articles.maximum(:updated_at)) - end + POST http://example.com/articles (i.e. ArticlesContoller#create) + redirects to + GET https://example.com/articles (i.e. ArticlesContoller#index) # After - def index - @articles = Article.all - fresh_when(@articles) - end - - *claudiob* - -* Explicitly ignored wildcard verbs when searching for HEAD routes before fallback - - Fixes an issue where a mounted rack app at root would intercept the HEAD - request causing an incorrect behavior during the fall back to GET requests. - - Example: - - draw do - get '/home' => 'test#index' - mount rack_app, at: '/' - end - head '/home' - assert_response :success - - In this case, a HEAD request runs through the routes the first time and fails - to match anything. Then, it runs through the list with the fallback and matches - `get '/home'`. The original behavior would match the rack app in the first pass. - - *Terence Sun* - -* Migrating xhr methods to keyword arguments syntax - in `ActionController::TestCase` and `ActionDispatch::Integration` - - Old syntax: - - xhr :get, :create, params: { id: 1 } - - New syntax example: - - get :create, params: { id: 1 }, xhr: true - - *Kir Shatrov* - -* Migrating to keyword arguments syntax in `ActionController::TestCase` and - `ActionDispatch::Integration` HTTP request methods. - - Example: - - post :create, params: { y: x }, session: { a: 'b' } - get :view, params: { id: 1 } - get :view, params: { id: 1 }, format: :json - - *Kir Shatrov* - -* Preserve default url options when generating URLs. - - Fixes an issue that would cause `default_url_options` to be lost when - generating URLs with fewer positional arguments than parameters in the - route definition. - - *Tekin Suleyman* + POST http://example.com/articles (i.e. ArticlesContoller#create) + redirects to + POST https://example.com/articles (i.e. ArticlesContoller#create) -* Deprecate `*_via_redirect` integration test methods. + *Chirag Singhal* - Use `follow_redirect!` manually after the request call for the same behavior. +* Add `:as` option to `ActionController:TestCase#process` and related methods. - *Aditya Kapoor* + Specifying `as: mime_type` allows the `CONTENT_TYPE` header to be specified + in controller tests without manually doing this through `@request.headers['CONTENT_TYPE']`. -* Add `ActionController::Renderer` to render arbitrary templates - outside controller actions. + *Everest Stefan Munro-Zeisberger* - Its functionality is accessible through class methods `render` and - `renderer` of `ActionController::Base`. +* Show cache hits and misses when rendering partials. - *Ravil Bayramgalin* + Partials using the `cache` helper will show whether a render hit or missed + the cache: -* Support `:assigns` option when rendering with controllers/mailers. - - *Ravil Bayramgalin* - -* Default headers, removed in controller actions, are no longer reapplied on - the test response. - - *Jonas Baumann* - -* Deprecate all `*_filter` callbacks in favor of `*_action` callbacks. - - *Rafael Mendonça França* - -* Allow you to pass `prepend: false` to `protect_from_forgery` to have the - verification callback appended instead of prepended to the chain. - This allows you to let the verification step depend on prior callbacks. - - Example: - - class ApplicationController < ActionController::Base - before_action :authenticate - protect_from_forgery prepend: false, unless: -> { @authenticated_by.oauth? } - - private - def authenticate - if oauth_request? - # authenticate with oauth - @authenticated_by = 'oauth'.inquiry - else - # authenticate with cookies - @authenticated_by = 'cookie'.inquiry - end - end - end - - *Josef Å imánek* - -* Remove `ActionController::HideActions`. - - *Ravil Bayramgalin* - -* Remove `respond_to`/`respond_with` placeholder methods, this functionality - has been extracted to the `responders` gem. - - *Carlos Antonio da Silva* - -* Remove deprecated assertion files. - - *Rafael Mendonça França* - -* Remove deprecated usage of string keys in URL helpers. - - *Rafael Mendonça França* - -* Remove deprecated `only_path` option on `*_path` helpers. - - *Rafael Mendonça França* - -* Remove deprecated `NamedRouteCollection#helpers`. - - *Rafael Mendonça França* - -* Remove deprecated support to define routes with `:to` option that doesn't contain `#`. - - *Rafael Mendonça França* - -* Remove deprecated `ActionDispatch::Response#to_ary`. - - *Rafael Mendonça França* - -* Remove deprecated `ActionDispatch::Request#deep_munge`. - - *Rafael Mendonça França* - -* Remove deprecated `ActionDispatch::Http::Parameters#symbolized_path_parameters`. - - *Rafael Mendonça França* - -* Remove deprecated option `use_route` in controller tests. - - *Rafael Mendonça França* - -* Ensure `append_info_to_payload` is called even if an exception is raised. - - Fixes an issue where when an exception is raised in the request the additional - payload data is not available. + ``` + Rendered messages/_message.html.erb in 1.2 ms [cache hit] + Rendered recordings/threads/_thread.html.erb in 1.5 ms [cache miss] + ``` - See #14903. + This removes the need for the old fragment cache logging: - *Dieter Komendera*, *Margus Pärt* + ``` + Read fragment views/v1/2914079/v1/2914079/recordings/70182313-20160225015037000000/d0bdf2974e1ef6d31685c3b392ad0b74 (0.6ms) + Rendered messages/_message.html.erb in 1.2 ms [cache hit] + Write fragment views/v1/2914079/v1/2914079/recordings/70182313-20160225015037000000/3b4e249ac9d168c617e32e84b99218b5 (1.1ms) + Rendered recordings/threads/_thread.html.erb in 1.5 ms [cache miss] + ``` -* Correctly rely on the response's status code to handle calls to `head`. + Though that full output can be reenabled with + `config.action_controller.enable_fragment_cache_logging = true`. - *Robin Dupret* + *Stan Lo* -* Using `head` method returns empty response_body instead - of returning a single space " ". +* Don't override the `Accept` header in integration tests when called with `xhr: true`. - The old behavior was added as a workaround for a bug in an early - version of Safari, where the HTTP headers are not returned correctly - if the response body has a 0-length. This is been fixed since and - the workaround is no longer necessary. + Fixes #25859. - Fixes #18253. + *David Chen* - *Prathamesh Sonpatki* +* Fix `defaults` option for root route. -* Fix how polymorphic routes works with objects that implement `to_model`. + A regression from some refactoring for the 5.0 release, this change + fixes the use of `defaults` (default parameters) in the `root` routing method. - *Travis Grathwell* + *Chris Arcand* -* Stop converting empty arrays in `params` to `nil`. +* Check `request.path_parameters` encoding at the point they're set. - This behavior was introduced in response to CVE-2012-2660, CVE-2012-2694 - and CVE-2013-0155 + Check for any non-UTF8 characters in path parameters at the point they're + set in `env`. Previously they were checked for when used to get a controller + class, but this meant routes that went directly to a Rack app, or skipped + controller instantiation for some other reason, had to defend against + non-UTF8 characters themselves. - ActiveRecord now issues a safe query when passing an empty array into - a where clause, so there is no longer a need to defend against this type - of input (any nils are still stripped from the array). + *Grey Baker* - *Chris Sinjakli* +* Don't raise `ActionController::UnknownHttpMethod` from `ActionDispatch::Static`. -* Fixed usage of optional scopes in url helpers. + Pass `Rack::Request` objects to `ActionDispatch::FileHandler` to avoid it + raising `ActionController::UnknownHttpMethod`. If an unknown method is + passed, it should pass exception higher in the stack instead, once we've had a + chance to define exception handling behaviour. - *Alex Robbin* + *Grey Baker* -* Fixed handling of positional url helper arguments when `format: false`. +* Handle `Rack::QueryParser` errors in `ActionDispatch::ExceptionWrapper`. - Fixes #17819. + Updated `ActionDispatch::ExceptionWrapper` to handle the Rack 2.0 namespace + for `ParameterTypeError` and `InvalidParameterError` errors. - *Andrew White*, *Tatiana Soukiassian* + *Grey Baker* -Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/actionpack/CHANGELOG.md) for previous changes. +Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/actionpack/CHANGELOG.md) for previous changes. diff --git a/actionpack/Rakefile b/actionpack/Rakefile index 37a269cffd..31dd1865f9 100644 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -1,16 +1,15 @@ -require 'rake/testtask' +require "rake/testtask" -test_files = Dir.glob('test/**/*_test.rb') +test_files = Dir.glob("test/**/*_test.rb") desc "Default Task" -task :default => :test +task default: :test task :package -task "package:clean" # Run the unit tests Rake::TestTask.new do |t| - t.libs << 'test' + t.libs << "test" t.test_files = test_files t.warning = true @@ -21,19 +20,19 @@ end namespace :test do task :isolated do test_files.all? do |file| - sh(Gem.ruby, '-w', '-Ilib:test', file) - end or raise "Failures" + sh(Gem.ruby, "-w", "-Ilib:test", file) + end || raise("Failures") end end task :lines do - load File.expand_path('..', File.dirname(__FILE__)) + '/tools/line_statistics' + load File.expand_path("..", File.dirname(__FILE__)) + "/tools/line_statistics" files = FileList["lib/**/*.rb"] CodeTools::LineStatistics.new(files).print_loc end -rule '.rb' => '.y' do |t| +rule ".rb" => ".y" do |t| sh "racc -l -o #{t.name} #{t.source}" end -task compile: 'lib/action_dispatch/journey/parser.rb' +task compile: "lib/action_dispatch/journey/parser.rb" diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 28d8bc3091..2c24a54305 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -1,31 +1,31 @@ -version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip +version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY - s.name = 'actionpack' + s.name = "actionpack" s.version = version - s.summary = 'Web-flow and rendering framework putting the VC in MVC (part of Rails).' - s.description = 'Web apps on Rails. Simple, battle-tested conventions for building and testing MVC web applications. Works with any Rack-compatible server.' + s.summary = "Web-flow and rendering framework putting the VC in MVC (part of Rails)." + s.description = "Web apps on Rails. Simple, battle-tested conventions for building and testing MVC web applications. Works with any Rack-compatible server." - s.required_ruby_version = '>= 2.2.2' + s.required_ruby_version = ">= 2.2.2" - s.license = 'MIT' + s.license = "MIT" - s.author = 'David Heinemeier Hansson' - s.email = 'david@loudthinking.com' - s.homepage = 'http://www.rubyonrails.org' + s.author = "David Heinemeier Hansson" + s.email = "david@loudthinking.com" + s.homepage = "http://rubyonrails.org" - s.files = Dir['CHANGELOG.md', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*'] - s.require_path = 'lib' - s.requirements << 'none' + s.files = Dir["CHANGELOG.md", "README.rdoc", "MIT-LICENSE", "lib/**/*"] + s.require_path = "lib" + s.requirements << "none" - s.add_dependency 'activesupport', version + s.add_dependency "activesupport", version - s.add_dependency 'rack', '~> 2.x' - s.add_dependency 'rack-test', '~> 0.6.3' - s.add_dependency 'rails-html-sanitizer', '~> 1.0', '>= 1.0.2' - s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.5' - s.add_dependency 'actionview', version + s.add_dependency "rack", "~> 2.0" + s.add_dependency "rack-test", "~> 0.6.3" + s.add_dependency "rails-html-sanitizer", "~> 1.0", ">= 1.0.2" + s.add_dependency "rails-dom-testing", "~> 2.0" + s.add_dependency "actionview", version - s.add_development_dependency 'activemodel', version + s.add_development_dependency "activemodel", version end diff --git a/actionpack/bin/test b/actionpack/bin/test index 404cabba51..84a05bba08 100755 --- a/actionpack/bin/test +++ b/actionpack/bin/test @@ -1,4 +1,6 @@ #!/usr/bin/env ruby -COMPONENT_ROOT = File.expand_path("../../", __FILE__) + +COMPONENT_ROOT = File.expand_path("..", __dir__) require File.expand_path("../tools/test", COMPONENT_ROOT) + exit Minitest.run(ARGV) diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb index 56c4033387..8bd965b198 100644 --- a/actionpack/lib/abstract_controller.rb +++ b/actionpack/lib/abstract_controller.rb @@ -1,11 +1,12 @@ -require 'action_pack' -require 'active_support/rails' -require 'active_support/i18n' +require "action_pack" +require "active_support/rails" +require "active_support/i18n" module AbstractController extend ActiveSupport::Autoload autoload :Base + autoload :Caching autoload :Callbacks autoload :Collector autoload :DoubleRenderError, "abstract_controller/rendering" @@ -15,4 +16,9 @@ module AbstractController autoload :Translation autoload :AssetPaths autoload :UrlFor + + def self.eager_load! + super + AbstractController::Caching.eager_load! + end end diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index 8edea0f52b..8e588812f8 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -1,13 +1,11 @@ -require 'erubis' -require 'active_support/configurable' -require 'active_support/descendants_tracker' -require 'active_support/core_ext/module/anonymous' -require 'active_support/core_ext/module/attr_internal' +require "erubis" +require "abstract_controller/error" +require "active_support/configurable" +require "active_support/descendants_tracker" +require "active_support/core_ext/module/anonymous" +require "active_support/core_ext/module/attr_internal" module AbstractController - class Error < StandardError #:nodoc: - end - # Raised when a non-existing controller action is triggered. class ActionNotFound < StandardError end @@ -78,7 +76,7 @@ module AbstractController end end - # action_methods are cached and there is sometimes need to refresh + # action_methods are cached and there is sometimes a 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! @@ -96,7 +94,7 @@ module AbstractController # ==== Returns # * <tt>String</tt> def controller_path - @controller_path ||= name.sub(/Controller$/, ''.freeze).underscore unless anonymous? + @controller_path ||= name.sub(/Controller$/, "".freeze).underscore unless anonymous? end # Refresh the cached action_methods when a new action_method is added. @@ -152,6 +150,13 @@ module AbstractController _find_action_name(action_name) end + # Tests if a response body is set. Used to determine if the + # +process_action+ callback needs to be terminated in + # +AbstractController::Callbacks+. + def performed? + response_body + end + # Returns true if the given controller is capable of rendering # a path. A subclass of +AbstractController::Base+ # may return false. An Email controller for example does not diff --git a/actionpack/lib/abstract_controller/caching.rb b/actionpack/lib/abstract_controller/caching.rb new file mode 100644 index 0000000000..d222880922 --- /dev/null +++ b/actionpack/lib/abstract_controller/caching.rb @@ -0,0 +1,65 @@ +module AbstractController + module Caching + extend ActiveSupport::Concern + extend ActiveSupport::Autoload + + eager_autoload do + autoload :Fragments + end + + module ConfigMethods + def cache_store + config.cache_store + end + + def cache_store=(store) + config.cache_store = ActiveSupport::Cache.lookup_store(store) + end + + private + def cache_configured? + perform_caching && cache_store + end + end + + include ConfigMethods + include AbstractController::Caching::Fragments + + included do + extend ConfigMethods + + config_accessor :default_static_extension + self.default_static_extension ||= ".html" + + config_accessor :perform_caching + self.perform_caching = true if perform_caching.nil? + + config_accessor :enable_fragment_cache_logging + self.enable_fragment_cache_logging = false + + class_attribute :_view_cache_dependencies + self._view_cache_dependencies = [] + helper_method :view_cache_dependencies if respond_to?(:helper_method) + end + + module ClassMethods + def view_cache_dependency(&dependency) + self._view_cache_dependencies += [dependency] + end + end + + def view_cache_dependencies + self.class._view_cache_dependencies.map { |dep| instance_exec(&dep) }.compact + end + + protected + # Convenience accessor. + def cache(key, options = {}, &block) + if cache_configured? + cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block) + else + yield + end + end + end +end diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/abstract_controller/caching/fragments.rb index b9ad51a9cf..c85b4adba1 100644 --- a/actionpack/lib/action_controller/caching/fragments.rb +++ b/actionpack/lib/abstract_controller/caching/fragments.rb @@ -1,4 +1,4 @@ -module ActionController +module AbstractController module Caching # Fragment caching is used for caching various blocks within # views without caching the entire action as a whole. This is @@ -51,7 +51,7 @@ module ActionController # end # end def fragment_cache_key(value = nil, &key) - self.fragment_cache_keys += [key || ->{ value }] + self.fragment_cache_keys += [key || -> { value }] end end @@ -135,13 +135,8 @@ module ActionController end def instrument_fragment_cache(name, key) # :nodoc: - payload = { - controller: controller_name, - action: action_name, - key: key - } - - ActiveSupport::Notifications.instrument("#{name}.action_controller", payload) { yield } + payload = instrument_payload(key) + ActiveSupport::Notifications.instrument("#{name}.#{instrument_name}", payload) { yield } end end end diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index d63ce9c1c3..ce4ecf17cc 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -9,7 +9,7 @@ module AbstractController included do define_callbacks :process_action, - terminator: ->(controller, result_lambda) { result_lambda.call if result_lambda.is_a?(Proc); controller.response_body }, + terminator: ->(controller, result_lambda) { result_lambda.call if result_lambda.is_a?(Proc); controller.performed? }, skip_after_callbacks_if_terminated: true end @@ -49,30 +49,11 @@ module AbstractController def _normalize_callback_option(options, from, to) # :nodoc: if from = options[from] _from = Array(from).map(&:to_s).to_set - from = proc {|c| _from.include? c.action_name } + from = proc { |c| _from.include? c.action_name } options[to] = Array(options[to]).unshift(from) end end - # Skip before, after, and around action callbacks matching any of the names. - # - # ==== Parameters - # * <tt>names</tt> - A list of valid names that could be used for - # callbacks. Note that skipping uses Ruby equality, so it's - # impossible to skip a callback defined using an anonymous proc - # using #skip_action_callback. - def skip_action_callback(*names) - ActiveSupport::Deprecation.warn('`skip_action_callback` is deprecated and will be removed in Rails 5.1. Please use skip_before_action, skip_after_action or skip_around_action instead.') - skip_before_action(*names, raise: false) - skip_after_action(*names, raise: false) - skip_around_action(*names, raise: false) - end - - def skip_filter(*names) - ActiveSupport::Deprecation.warn("`skip_filter` is deprecated and will be removed in Rails 5.1. Use skip_before_action, skip_after_action or skip_around_action instead.") - skip_action_callback(*names) - end - # Take callback names and an optional callback proc, normalize them, # then call the block with each callback. This allows us to abstract # the normalization across several methods that use it. @@ -187,22 +168,12 @@ module AbstractController end end - define_method "#{callback}_filter" do |*names, &blk| - ActiveSupport::Deprecation.warn("#{callback}_filter is deprecated and will be removed in Rails 5.1. Use #{callback}_action instead.") - send("#{callback}_action", *names, &blk) - end - define_method "prepend_#{callback}_action" do |*names, &blk| _insert_callbacks(names, blk) do |name, options| - set_callback(:process_action, callback, name, options.merge(:prepend => true)) + set_callback(:process_action, callback, name, options.merge(prepend: true)) end end - define_method "prepend_#{callback}_filter" do |*names, &blk| - ActiveSupport::Deprecation.warn("prepend_#{callback}_filter is deprecated and will be removed in Rails 5.1. Use prepend_#{callback}_action instead.") - send("prepend_#{callback}_action", *names, &blk) - end - # Skip a before, after or around callback. See _insert_callbacks # for details on the allowed parameters. define_method "skip_#{callback}_action" do |*names| @@ -211,18 +182,8 @@ module AbstractController end end - define_method "skip_#{callback}_filter" do |*names, &blk| - ActiveSupport::Deprecation.warn("skip_#{callback}_filter is deprecated and will be removed in Rails 5.1. Use skip_#{callback}_action instead.") - send("skip_#{callback}_action", *names, &blk) - end - # *_action is the same as append_*_action alias_method :"append_#{callback}_action", :"#{callback}_action" - - define_method "append_#{callback}_filter" do |*names, &blk| - ActiveSupport::Deprecation.warn("append_#{callback}_filter is deprecated and will be removed in Rails 5.1. Use append_#{callback}_action instead.") - send("append_#{callback}_action", *names, &blk) - end end end end diff --git a/actionpack/lib/abstract_controller/collector.rb b/actionpack/lib/abstract_controller/collector.rb index 55654be224..57714b0588 100644 --- a/actionpack/lib/abstract_controller/collector.rb +++ b/actionpack/lib/abstract_controller/collector.rb @@ -16,7 +16,7 @@ module AbstractController end Mime::Type.register_callback do |mime| - generate_method_for_mime(mime) unless self.instance_methods.include?(mime.to_sym) + generate_method_for_mime(mime) unless instance_methods.include?(mime.to_sym) end protected diff --git a/actionpack/lib/abstract_controller/error.rb b/actionpack/lib/abstract_controller/error.rb new file mode 100644 index 0000000000..7fafce4dd4 --- /dev/null +++ b/actionpack/lib/abstract_controller/error.rb @@ -0,0 +1,4 @@ +module AbstractController + class Error < StandardError #:nodoc: + end +end diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index d84c238a62..ef3be7af83 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -1,4 +1,4 @@ -require 'active_support/dependencies' +require "active_support/dependencies" module AbstractController module Helpers @@ -38,7 +38,8 @@ module AbstractController end # Declare a controller method as a helper. For example, the following - # makes the +current_user+ controller method available to the view: + # makes the +current_user+ and +logged_in?+ controller methods available + # to the view: # class ApplicationController < ActionController::Base # helper_method :current_user, :logged_in? # @@ -170,25 +171,25 @@ module AbstractController end private - # Makes all the (instance) methods in the helper module available to templates - # rendered through this controller. - # - # ==== Parameters - # * <tt>module</tt> - The module to include into the current helper module - # for the class - def add_template_helper(mod) - _helpers.module_eval { include mod } - end + # Makes all the (instance) methods in the helper module available to templates + # rendered through this controller. + # + # ==== Parameters + # * <tt>module</tt> - The module to include into the current helper module + # for the class + def add_template_helper(mod) + _helpers.module_eval { include mod } + end - def default_helper_module! - module_name = name.sub(/Controller$/, ''.freeze) - module_path = module_name.underscore - helper module_path - rescue LoadError => e - raise e unless e.is_missing? "helpers/#{module_path}_helper" - rescue NameError => e - raise e unless e.missing_name? "#{module_name}Helper" - end + def default_helper_module! + module_name = name.sub(/Controller$/, "".freeze) + module_path = module_name.underscore + helper module_path + rescue LoadError => e + raise e unless e.is_missing? "helpers/#{module_path}_helper" + rescue NameError => e + raise e unless e.missing_name? "#{module_name}Helper" + end end end end diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index e765d73ce4..a0560a8748 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -1,8 +1,7 @@ -require 'active_support/concern' -require 'active_support/core_ext/class/attribute' -require 'action_view' -require 'action_view/view_paths' -require 'set' +require "abstract_controller/error" +require "action_view" +require "action_view/view_paths" +require "set" module AbstractController class DoubleRenderError < Error @@ -59,9 +58,7 @@ module AbstractController end DEFAULT_PROTECTED_INSTANCE_VARIABLES = Set.new %i( - @_action_name @_response_body @_formats @_prefixes @_config - @_view_context_class @_view_renderer @_lookup_context - @_routes @_db_runtime + @_action_name @_response_body @_formats @_prefixes ) # This method should return a hash with assigns. @@ -123,7 +120,7 @@ module AbstractController def _normalize_render(*args, &block) options = _normalize_args(*args, &block) #TODO: remove defined? when we restore AP <=> AV dependency - if defined?(request) && request.variant.present? + if defined?(request) && !request.nil? && request.variant.present? options[:variant] = request.variant end _normalize_options(options) diff --git a/actionpack/lib/abstract_controller/translation.rb b/actionpack/lib/abstract_controller/translation.rb index 56b8ce895e..9e3858802a 100644 --- a/actionpack/lib/abstract_controller/translation.rb +++ b/actionpack/lib/abstract_controller/translation.rb @@ -9,8 +9,8 @@ module AbstractController # to translate many keys within the same controller / action and gives you a # simple framework for scoping them consistently. def translate(key, options = {}) - if key.to_s.first == '.' - path = controller_path.tr('/', '.') + if key.to_s.first == "." + path = controller_path.tr("/", ".") defaults = [:"#{path}#{key}"] defaults << options[:default] if options[:default] options[:default] = defaults diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 40f33a9de0..50f20aa789 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -1,25 +1,29 @@ -require 'active_support/rails' -require 'abstract_controller' -require 'action_dispatch' -require 'action_controller/metal/live' -require 'action_controller/metal/strong_parameters' +require "active_support/rails" +require "abstract_controller" +require "action_dispatch" +require "action_controller/metal/live" +require "action_controller/metal/strong_parameters" module ActionController extend ActiveSupport::Autoload autoload :API autoload :Base - autoload :Caching autoload :Metal autoload :Middleware autoload :Renderer autoload :FormBuilder + eager_autoload do + autoload :Caching + end + autoload_under "metal" do autoload :ConditionalGet autoload :Cookies autoload :DataStreaming autoload :EtagWithTemplateDigest + autoload :EtagWithFlash autoload :Flash autoload :ForceSSL autoload :Head @@ -37,6 +41,7 @@ module ActionController autoload :Rescue autoload :Streaming autoload :StrongParameters + autoload :ParameterEncoding autoload :Testing autoload :UrlFor end @@ -45,19 +50,14 @@ module ActionController autoload :ApiRendering end - autoload :TestCase, 'action_controller/test_case' - autoload :TemplateAssertions, 'action_controller/test_case' - - def self.eager_load! - super - ActionController::Caching.eager_load! - end + autoload :TestCase, "action_controller/test_case" + autoload :TemplateAssertions, "action_controller/test_case" end # Common Active Support usage in Action Controller -require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/core_ext/load_error' -require 'active_support/core_ext/module/attr_internal' -require 'active_support/core_ext/name_error' -require 'active_support/core_ext/uri' -require 'active_support/inflector' +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/load_error" +require "active_support/core_ext/module/attr_internal" +require "active_support/core_ext/name_error" +require "active_support/core_ext/uri" +require "active_support/inflector" diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb index ff12705abe..5cd8d77ddb 100644 --- a/actionpack/lib/action_controller/api.rb +++ b/actionpack/lib/action_controller/api.rb @@ -1,6 +1,6 @@ -require 'action_view' -require 'action_controller' -require 'action_controller/log_subscriber' +require "action_view" +require "action_controller" +require "action_controller/log_subscriber" module ActionController # API Controller is a lightweight version of <tt>ActionController::Base</tt>, @@ -14,22 +14,22 @@ module ActionController # flash, assets, and so on. This makes the entire controller stack thinner, # suitable for API applications. It doesn't mean you won't have such # features if you need them: they're all available for you to include in - # your application, they're just not part of the default API Controller stack. + # your application, they're just not part of the default API controller stack. # - # By default, only the ApplicationController in a \Rails application inherits - # from <tt>ActionController::API</tt>. All other controllers in turn inherit - # from ApplicationController. + # Normally, +ApplicationController+ is the only controller that inherits from + # <tt>ActionController::API</tt>. All other controllers in turn inherit from + # +ApplicationController+. # # A sample controller could look like this: # # class PostsController < ApplicationController # def index - # @posts = Post.all - # render json: @posts + # posts = Post.all + # render json: posts # end # end # - # Request, response and parameters objects all work the exact same way as + # Request, response, and parameters objects all work the exact same way as # <tt>ActionController::Base</tt>. # # == Renders @@ -37,18 +37,18 @@ module ActionController # The default API Controller stack includes all renderers, which means you # can use <tt>render :json</tt> and brothers freely in your controllers. Keep # in mind that templates are not going to be rendered, so you need to ensure - # your controller is calling either <tt>render</tt> or <tt>redirect</tt> in - # all actions, otherwise it will return 204 No Content response. + # your controller is calling either <tt>render</tt> or <tt>redirect_to</tt> in + # all actions, otherwise it will return 204 No Content. # # def show - # @post = Post.find(params[:id]) - # render json: @post + # post = Post.find(params[:id]) + # render json: post # end # # == Redirects # # Redirects are used to move from one action to another. You can use the - # <tt>redirect</tt> method in your controllers in the same way as + # <tt>redirect_to</tt> method in your controllers in the same way as in # <tt>ActionController::Base</tt>. For example: # # def create @@ -56,7 +56,7 @@ module ActionController # # do stuff here # end # - # == Adding new behavior + # == Adding New Behavior # # In some scenarios you may want to add back some functionality provided by # <tt>ActionController::Base</tt> that is not present by default in @@ -72,18 +72,19 @@ module ActionController # # class PostsController < ApplicationController # def index - # @posts = Post.all + # posts = Post.all # # respond_to do |format| - # format.json { render json: @posts } - # format.xml { render xml: @posts } + # format.json { render json: posts } + # format.xml { render xml: posts } # end # end # end # - # Quite straightforward. Make sure to check <tt>ActionController::Base</tt> - # available modules if you want to include any other functionality that is - # not provided by <tt>ActionController::API</tt> out of the box. + # Quite straightforward. Make sure to check the modules included in + # <tt>ActionController::Base</tt> if you want to use any other + # functionality that is not provided by <tt>ActionController::API</tt> + # out of the box. class API < Metal abstract! diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 04e5922ce8..ca8066cd82 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -1,4 +1,4 @@ -require 'action_view' +require "action_view" require "action_controller/log_subscriber" require "action_controller/metal/params_wrapper" @@ -32,7 +32,7 @@ module ActionController # new post), it initiates a redirect instead. This redirect works by returning an external # "302 Moved" HTTP response that takes the user to the index action. # - # These two methods represent the two basic action archetypes used in Action Controllers. Get-and-show and do-and-redirect. + # These two methods represent the two basic action archetypes used in Action Controllers: Get-and-show and do-and-redirect. # Most actions are variations on these themes. # # == Requests @@ -51,8 +51,8 @@ module ActionController # == Parameters # # All request parameters, whether they come from a query string in the URL or form data submitted through a POST request are - # available through the params method which returns a hash. For example, an action that was performed through - # <tt>/posts?category=All&limit=5</tt> will include <tt>{ "category" => "All", "limit" => "5" }</tt> in params. + # available through the <tt>params</tt> method which returns a hash. For example, an action that was performed through + # <tt>/posts?category=All&limit=5</tt> will include <tt>{ "category" => "All", "limit" => "5" }</tt> in <tt>params</tt>. # # It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as: # @@ -60,7 +60,7 @@ module ActionController # <input type="text" name="post[address]" value="hyacintvej"> # # A request stemming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>. - # If the address input had been named <tt>post[address][street]</tt>, the params would have included + # If the address input had been named <tt>post[address][street]</tt>, the <tt>params</tt> would have included # <tt>{ "post" => { "address" => { "street" => "hyacintvej" } } }</tt>. There's no limit to the depth of the nesting. # # == Sessions @@ -213,11 +213,12 @@ module ActionController Renderers::All, ConditionalGet, EtagWithTemplateDigest, + EtagWithFlash, Caching, MimeResponds, ImplicitRender, StrongParameters, - + ParameterEncoding, Cookies, Flash, FormBuilder, @@ -229,7 +230,7 @@ module ActionController HttpAuthentication::Digest::ControllerMethods, HttpAuthentication::Token::ControllerMethods, - # Before callbacks should also be executed the earliest as possible, so + # Before callbacks should also be executed as early as possible, so # also include them at the bottom. AbstractController::Callbacks, @@ -251,9 +252,10 @@ module ActionController setup_renderer! # Define some internal variables that should not be propagated to the view. - PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [ - :@_params, :@_response, :@_request, - :@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout ] + PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + %i( + @_params @_response @_request @_config @_url_options @_action_has_layout @_view_context_class + @_view_renderer @_lookup_context @_routes @_view_runtime @_db_runtime @_helper_proxy + ) def _protected_ivars # :nodoc: PROTECTED_IVARS diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index 0b8fa2ea09..a9a8508abc 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -1,6 +1,3 @@ -require 'fileutils' -require 'uri' - module ActionController # \Caching is a cheap way of speeding up slow applications by keeping the result of # calculations, renderings, and database calls around for subsequent requests. @@ -23,65 +20,25 @@ module ActionController # config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new('localhost:11211') # config.action_controller.cache_store = MyOwnStore.new('parameter') module Caching - extend ActiveSupport::Concern extend ActiveSupport::Autoload - - eager_autoload do - autoload :Fragments - end - - module ConfigMethods - def cache_store - config.cache_store - end - - def cache_store=(store) - config.cache_store = ActiveSupport::Cache.lookup_store(store) - end - - private - def cache_configured? - perform_caching && cache_store - end - end - - include AbstractController::Callbacks - - include ConfigMethods - include Fragments + extend ActiveSupport::Concern included do - extend ConfigMethods - - config_accessor :default_static_extension - self.default_static_extension ||= '.html' - - config_accessor :perform_caching - self.perform_caching = true if perform_caching.nil? - - class_attribute :_view_cache_dependencies - self._view_cache_dependencies = [] - helper_method :view_cache_dependencies if respond_to?(:helper_method) + include AbstractController::Caching end - module ClassMethods - def view_cache_dependency(&dependency) - self._view_cache_dependencies += [dependency] - end - end + private - def view_cache_dependencies - self.class._view_cache_dependencies.map { |dep| instance_exec(&dep) }.compact - end + def instrument_payload(key) + { + controller: controller_name, + action: action_name, + key: key + } + end - protected - # Convenience accessor. - def cache(key, options = {}, &block) - if cache_configured? - cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block) - else - yield - end + def instrument_name + "action_controller" end end end diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index a0917b4fdb..d29a5fe68f 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -51,7 +51,7 @@ module ActionController def unpermitted_parameters(event) debug do unpermitted_keys = event.payload[:keys] - "Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{unpermitted_keys.join(", ")}" + "Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{unpermitted_keys.map { |e| ":#{e}" }.join(", ")}" end end @@ -59,7 +59,7 @@ module ActionController expire_fragment expire_page write_page).each do |method| class_eval <<-METHOD, __FILE__, __LINE__ + 1 def #{method}(event) - return unless logger.info? + return unless logger.info? && ActionController::Base.enable_fragment_cache_logging key_or_path = event.payload[:key] || event.payload[:path] human_name = #{method.to_s.humanize.inspect} info("\#{human_name} \#{key_or_path} (\#{event.duration.round(1)}ms)") diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 1641d01c30..ed93a2f09c 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -1,7 +1,7 @@ -require 'active_support/core_ext/array/extract_options' -require 'action_dispatch/middleware/stack' -require 'action_dispatch/http/request' -require 'action_dispatch/http/response' +require "active_support/core_ext/array/extract_options" +require "action_dispatch/middleware/stack" +require "action_dispatch/http/request" +require "action_dispatch/http/response" module ActionController # Extend ActionDispatch middleware stack to make it aware of options @@ -34,29 +34,29 @@ module ActionController private - INCLUDE = ->(list, action) { list.include? action } - EXCLUDE = ->(list, action) { !list.include? action } - NULL = ->(list, action) { true } - - def build_middleware(klass, args, block) - options = args.extract_options! - only = Array(options.delete(:only)).map(&:to_s) - except = Array(options.delete(:except)).map(&:to_s) - args << options unless options.empty? - - strategy = NULL - list = nil - - if only.any? - strategy = INCLUDE - list = only - elsif except.any? - strategy = EXCLUDE - list = except - end + INCLUDE = ->(list, action) { list.include? action } + EXCLUDE = ->(list, action) { !list.include? action } + NULL = ->(list, action) { true } + + def build_middleware(klass, args, block) + options = args.extract_options! + only = Array(options.delete(:only)).map(&:to_s) + except = Array(options.delete(:except)).map(&:to_s) + args << options unless options.empty? + + strategy = NULL + list = nil + + if only.any? + strategy = INCLUDE + list = only + elsif except.any? + strategy = EXCLUDE + list = except + end - Middleware.new(get_class(klass), args, list, strategy, block) - end + Middleware.new(klass, args, list, strategy, block) + end end # <tt>ActionController::Metal</tt> is the simplest possible controller, providing a @@ -130,7 +130,7 @@ module ActionController # ==== Returns # * <tt>string</tt> def self.controller_name - @controller_name ||= name.demodulize.sub(/Controller$/, '').underscore + @controller_name ||= name.demodulize.sub(/Controller$/, "").underscore end def self.make_response!(request) @@ -139,15 +139,19 @@ module ActionController end end + def self.encoding_for_param(action, param) # :nodoc: + ::Encoding::UTF_8 + end + # Delegates to the class' <tt>controller_name</tt> def controller_name self.class.controller_name end attr_internal :response, :request - delegate :session, :to => "@_request" + delegate :session, to: "@_request" delegate :headers, :status=, :location=, :content_type=, - :status, :location, :content_type, :to => "@_response" + :status, :location, :content_type, to: "@_response" def initialize @_request = nil @@ -175,10 +179,7 @@ module ActionController body = [body] unless body.nil? || body.respond_to?(:each) response.reset_body! return unless body - body.each { |part| - next if part.empty? - response.write part - } + response.body = body super end diff --git a/actionpack/lib/action_controller/metal/basic_implicit_render.rb b/actionpack/lib/action_controller/metal/basic_implicit_render.rb index 6c6f8381ff..cef65a362c 100644 --- a/actionpack/lib/action_controller/metal/basic_implicit_render.rb +++ b/actionpack/lib/action_controller/metal/basic_implicit_render.rb @@ -1,5 +1,5 @@ module ActionController - module BasicImplicitRender + module BasicImplicitRender # :nodoc: def send_action(method, *args) super.tap { default_render unless performed? } end diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index f8e0d9cf6c..89bf60a0bb 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/hash/keys' +require "active_support/core_ext/hash/keys" module ActionController module ConditionalGet @@ -36,8 +36,23 @@ module ActionController # # === Parameters: # - # * <tt>:etag</tt>. - # * <tt>:last_modified</tt>. + # * <tt>:etag</tt> Sets a "weak" ETag validator on the response. See the + # +:weak_etag+ option. + # * <tt>:weak_etag</tt> Sets a "weak" ETag validator on the response. + # Requests that set If-None-Match header may return a 304 Not Modified + # response if it matches the ETag exactly. A weak ETag indicates semantic + # equivalence, not byte-for-byte equality, so they're good for caching + # HTML pages in browser caches. They can't be used for responses that + # must be byte-identical, like serving Range requests within a PDF file. + # * <tt>:strong_etag</tt> Sets a "strong" ETag validator on the response. + # Requests that set If-None-Match header may return a 304 Not Modified + # response if it matches the ETag exactly. A strong ETag implies exact + # equality: the response must match byte for byte. This is necessary for + # doing Range requests within a large video or PDF file, for example, or + # for compatibility with some CDNs that don't support weak ETags. + # * <tt>:last_modified</tt> Sets a "weak" last-update validator on the + # response. Subsequent requests that set If-Modified-Since may return a + # 304 Not Modified response if last_modified <= If-Modified-Since. # * <tt>:public</tt> By default the Cache-Control header is private, set this to # +true+ if you want your application to be cacheable by other devices (proxy caches). # * <tt>:template</tt> By default, the template digest for the current @@ -86,12 +101,16 @@ module ActionController # # before_action { fresh_when @article, template: 'widgets/show' } # - def fresh_when(object = nil, etag: object, last_modified: nil, public: false, template: nil) + def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_modified: nil, public: false, template: nil) + weak_etag ||= etag || object unless strong_etag last_modified ||= object.try(:updated_at) || object.try(:maximum, :updated_at) - if etag || template - response.etag = combine_etags(etag: etag, last_modified: last_modified, - public: public, template: template) + if strong_etag + response.strong_etag = combine_etags strong_etag, + last_modified: last_modified, public: public, template: template + elsif weak_etag || template + response.weak_etag = combine_etags weak_etag, + last_modified: last_modified, public: public, template: template end response.last_modified = last_modified if last_modified @@ -107,8 +126,23 @@ module ActionController # # === Parameters: # - # * <tt>:etag</tt>. - # * <tt>:last_modified</tt>. + # * <tt>:etag</tt> Sets a "weak" ETag validator on the response. See the + # +:weak_etag+ option. + # * <tt>:weak_etag</tt> Sets a "weak" ETag validator on the response. + # Requests that set If-None-Match header may return a 304 Not Modified + # response if it matches the ETag exactly. A weak ETag indicates semantic + # equivalence, not byte-for-byte equality, so they're good for caching + # HTML pages in browser caches. They can't be used for responses that + # must be byte-identical, like serving Range requests within a PDF file. + # * <tt>:strong_etag</tt> Sets a "strong" ETag validator on the response. + # Requests that set If-None-Match header may return a 304 Not Modified + # response if it matches the ETag exactly. A strong ETag implies exact + # equality: the response must match byte for byte. This is necessary for + # doing Range requests within a large video or PDF file, for example, or + # for compatibility with some CDNs that don't support weak ETags. + # * <tt>:last_modified</tt> Sets a "weak" last-update validator on the + # response. Subsequent requests that set If-Modified-Since may return a + # 304 Not Modified response if last_modified <= If-Modified-Since. # * <tt>:public</tt> By default the Cache-Control header is private, set this to # +true+ if you want your application to be cacheable by other devices (proxy caches). # * <tt>:template</tt> By default, the template digest for the current @@ -180,12 +214,12 @@ module ActionController # super if stale? @article, template: 'widgets/show' # end # - def stale?(object = nil, etag: object, last_modified: nil, public: nil, template: nil) - fresh_when(object, etag: etag, last_modified: last_modified, public: public, template: template) + def stale?(object = nil, **freshness_kwargs) + fresh_when(object, **freshness_kwargs) !request.fresh?(response) end - # Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a +private+ + # Sets an HTTP 1.1 Cache-Control header. Defaults to issuing a +private+ # instruction, so that intermediate caches must not cache the response. # # expires_in 20.minutes @@ -195,47 +229,45 @@ module ActionController # This method will overwrite an existing Cache-Control header. # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities. # - # The method will also ensure a HTTP Date header for client compatibility. + # The method will also ensure an HTTP Date header for client compatibility. def expires_in(seconds, options = {}) response.cache_control.merge!( - :max_age => seconds, - :public => options.delete(:public), - :must_revalidate => options.delete(:must_revalidate) + max_age: seconds, + public: options.delete(:public), + must_revalidate: options.delete(:must_revalidate) ) options.delete(:private) - response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"} + response.cache_control[:extras] = options.map { |k,v| "#{k}=#{v}" } response.date = Time.now unless response.date? end - # Sets a HTTP 1.1 Cache-Control header of <tt>no-cache</tt> so no caching should - # occur by the browser or intermediate caches (like caching proxy servers). + # Sets an HTTP 1.1 Cache-Control header of <tt>no-cache</tt>. This means the + # resource will be marked as stale, so clients must always revalidate. + # Intermediate/browser caches may still store the asset. def expires_now - response.cache_control.replace(:no_cache => true) + response.cache_control.replace(no_cache: true) end # Cache or yield the block. The cache is supposed to never expire. # - # You can use this method when you have a HTTP response that never changes, + # You can use this method when you have an HTTP response that never changes, # and the browser and proxies should cache it indefinitely. # # * +public+: By default, HTTP responses are private, cached only on the # user's web browser. To allow proxies to cache the response, set +true+ to # indicate that they can serve the cached response to all users. - # - # * +version+: the version passed as a key for the cache. - def http_cache_forever(public: false, version: 'v1') + def http_cache_forever(public: false) expires_in 100.years, public: public - yield if stale?(etag: "#{version}-#{request.fullpath}", + yield if stale?(etag: request.fullpath, last_modified: Time.new(2011, 1, 1).utc, public: public) end private - def combine_etags(options) - etags = etaggers.map { |etagger| instance_exec(options, &etagger) }.compact - etags.unshift options[:etag] + def combine_etags(validator, options) + [validator, *etaggers.map { |etagger| instance_exec(options, &etagger) }].compact end end end diff --git a/actionpack/lib/action_controller/metal/cookies.rb b/actionpack/lib/action_controller/metal/cookies.rb index f8efb2b076..44925641a1 100644 --- a/actionpack/lib/action_controller/metal/cookies.rb +++ b/actionpack/lib/action_controller/metal/cookies.rb @@ -3,7 +3,7 @@ module ActionController #:nodoc: extend ActiveSupport::Concern included do - helper_method :cookies + helper_method :cookies if defined?(helper_method) end private diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb index 957e7a3019..f089c8423b 100644 --- a/actionpack/lib/action_controller/metal/data_streaming.rb +++ b/actionpack/lib/action_controller/metal/data_streaming.rb @@ -1,4 +1,4 @@ -require 'action_controller/metal/exceptions' +require "action_controller/metal/exceptions" module ActionController #:nodoc: # Methods for sending arbitrary data and for streaming files to the browser, @@ -8,8 +8,8 @@ module ActionController #:nodoc: include ActionController::Rendering - DEFAULT_SEND_FILE_TYPE = 'application/octet-stream'.freeze #:nodoc: - DEFAULT_SEND_FILE_DISPOSITION = 'attachment'.freeze #:nodoc: + DEFAULT_SEND_FILE_TYPE = "application/octet-stream".freeze #:nodoc: + DEFAULT_SEND_FILE_DISPOSITION = "attachment".freeze #:nodoc: protected # Sends the file. This uses a server-appropriate method (such as X-Sendfile) @@ -25,14 +25,13 @@ module ActionController #:nodoc: # * <tt>:filename</tt> - suggests a filename for the browser to use. # Defaults to <tt>File.basename(path)</tt>. # * <tt>:type</tt> - specifies an HTTP content type. - # You can specify either a string or a symbol for a registered type register with - # <tt>Mime::Type.register</tt>, for example :json - # If omitted, type will be guessed from the file extension specified in <tt>:filename</tt>. - # If no content type is registered for the extension, default type 'application/octet-stream' will be used. + # You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example :json. + # If omitted, the type will be inferred from the file extension specified in <tt>:filename</tt>. + # If no content type is registered for the extension, the default type 'application/octet-stream' will be used. # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded. # Valid values are 'inline' and 'attachment' (default). # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to 200. - # * <tt>:url_based_filename</tt> - set to +true+ if you want the browser guess the filename from + # * <tt>:url_based_filename</tt> - set to +true+ if you want the browser to guess the filename from # the URL, which is necessary for i18n filenames on certain browsers # (setting <tt>:filename</tt> overrides this option). # @@ -65,12 +64,13 @@ module ActionController #:nodoc: # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 # for the Cache-Control header spec. def send_file(path, options = {}) #:doc: - raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path) + raise MissingFile, "Cannot read file #{path}" unless File.file?(path) && File.readable?(path) options[:filename] ||= File.basename(path) unless options[:url_based_filename] 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 @@ -79,14 +79,14 @@ module ActionController #:nodoc: # <tt>render plain: data</tt>, but also allows you to specify whether # the browser should display the response as a file attachment (i.e. in a # download dialog) or as inline data. You may also set the content type, - # the apparent file name, and other things. + # the file name, and other things. # # Options: # * <tt>:filename</tt> - suggests a filename for the browser to use. - # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify - # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json - # If omitted, type will be guessed from the file extension specified in <tt>:filename</tt>. - # If no content type is registered for the extension, default type 'application/octet-stream' will be used. + # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. + # You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example :json. + # If omitted, type will be inferred from the file extension specified in <tt>:filename</tt>. + # If no content type is registered for the extension, the default type 'application/octet-stream' will be used. # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded. # Valid values are 'inline' and 'attachment' (default). # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to 200. @@ -123,7 +123,7 @@ module ActionController #:nodoc: else if !type_provided && options[:filename] # If type wasn't provided, try guessing from file extension. - content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.delete('.')) || content_type + content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.delete(".")) || content_type end self.content_type = content_type end @@ -132,10 +132,10 @@ module ActionController #:nodoc: unless disposition.nil? disposition = disposition.to_s disposition += %(; filename="#{options[:filename]}") if options[:filename] - headers['Content-Disposition'] = disposition + headers["Content-Disposition"] = disposition end - headers['Content-Transfer-Encoding'] = 'binary' + headers["Content-Transfer-Encoding"] = "binary" response.sending_file = true diff --git a/actionpack/lib/action_controller/metal/etag_with_flash.rb b/actionpack/lib/action_controller/metal/etag_with_flash.rb new file mode 100644 index 0000000000..474d75f02e --- /dev/null +++ b/actionpack/lib/action_controller/metal/etag_with_flash.rb @@ -0,0 +1,16 @@ +module ActionController + # When you're using the flash, it's generally used as a conditional on the view. + # This means the content of the view depends on the flash. Which in turn means + # that the etag for a response should be computed with the content of the flash + # in mind. This does that by including the content of the flash as a component + # in the etag that's generated for a response. + module EtagWithFlash + extend ActiveSupport::Concern + + include ActionController::ConditionalGet + + included do + etag { flash unless flash.empty? } + end + end +end diff --git a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb index 669cf55bca..6c103bb042 100644 --- a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb +++ b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb @@ -33,18 +33,24 @@ module ActionController end private - def determine_template_etag(options) - if template = pick_template_for_etag(options) - lookup_and_digest_template(template) + def determine_template_etag(options) + if template = pick_template_for_etag(options) + lookup_and_digest_template(template) + end end - end - def pick_template_for_etag(options) - options.fetch(:template) { "#{controller_name}/#{action_name}" } - end + # Pick the template digest to include in the ETag. If the +:template+ option + # is present, use the named template. If +:template+ is nil or absent, use + # the default controller/action template. If +:template+ is false, omit the + # template digest from the ETag. + def pick_template_for_etag(options) + unless options[:template] == false + options[:template] || "#{controller_path}/#{action_name}" + end + end - def lookup_and_digest_template(template) - ActionView::Digestor.digest name: template, finder: lookup_context - end + def lookup_and_digest_template(template) + ActionView::Digestor.digest name: template, finder: lookup_context + end end end diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb index 5c0ada37be..3761e6172b 100644 --- a/actionpack/lib/action_controller/metal/exceptions.rb +++ b/actionpack/lib/action_controller/metal/exceptions.rb @@ -3,20 +3,10 @@ module ActionController end class BadRequest < ActionControllerError #:nodoc: - def initialize(msg = nil, e = nil) - if e - ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \ - "Exceptions will automatically capture the original exception.", caller) - end - + def initialize(msg = nil) super(msg) set_backtrace $!.backtrace if $! end - - def original_exception - ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller) - cause - end end class RenderError < ActionControllerError #:nodoc: @@ -35,7 +25,7 @@ module ActionController class MethodNotAllowed < ActionControllerError #:nodoc: def initialize(*allowed_methods) - super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.") + super("Only #{allowed_methods.to_sentence(locale: :en)} requests are allowed.") end end @@ -49,7 +39,7 @@ module ActionController end class SessionOverflowError < ActionControllerError #:nodoc: - DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.' + DEFAULT_MESSAGE = "Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data." def initialize(message = nil) super(message || DEFAULT_MESSAGE) diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb index e31d65aac2..b8976497a4 100644 --- a/actionpack/lib/action_controller/metal/force_ssl.rb +++ b/actionpack/lib/action_controller/metal/force_ssl.rb @@ -1,18 +1,18 @@ -require 'active_support/core_ext/hash/except' -require 'active_support/core_ext/hash/slice' +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/slice" module ActionController - # This module provides a method which will redirect browser to use HTTPS + # This module provides a method which will redirect the browser to use HTTPS # protocol. This will ensure that user's sensitive information will be - # transferred safely over the internet. You _should_ always force browser + # transferred safely over the internet. You _should_ always force the browser # to use HTTPS when you're transferring sensitive information such as # user authentication, account information, or credit card information. # # Note that if you are really concerned about your application security, # you might consider using +config.force_ssl+ in your config file instead. # That will ensure all the data transferred via HTTPS protocol and prevent - # user from getting session hijacked when accessing the site under unsecured - # HTTP protocol. + # the user from getting their session hijacked when accessing the site over + # unsecured HTTP protocol. module ForceSSL extend ActiveSupport::Concern include AbstractController::Callbacks @@ -76,10 +76,10 @@ module ActionController def force_ssl_redirect(host_or_options = nil) unless request.ssl? options = { - :protocol => 'https://', - :host => request.host, - :path => request.fullpath, - :status => :moved_permanently + protocol: "https://", + host: request.host, + path: request.fullpath, + status: :moved_permanently } if host_or_options.is_a?(Hash) diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb index 5e9832fd4e..4dff23dd85 100644 --- a/actionpack/lib/action_controller/metal/head.rb +++ b/actionpack/lib/action_controller/metal/head.rb @@ -18,13 +18,7 @@ module ActionController # See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list of valid +status+ symbols. def head(status, options = {}) if status.is_a?(Hash) - msg = status[:status] ? 'The :status option' : 'The implicit :ok status' - options, status = status, status.delete(:status) - - ActiveSupport::Deprecation.warn(<<-MSG.squish) - #{msg} on `head` has been deprecated and will be removed in Rails 5.1. - Please pass the status as a separate parameter before the options, instead. - MSG + raise ArgumentError, "#{status.inspect} is not a valid value for `status`." end status ||= :ok @@ -33,7 +27,7 @@ module ActionController content_type = options.delete(:content_type) options.each do |key, value| - headers[key.to_s.dasherize.split('-').each { |v| v[0] = v[0].chr.upcase }.join('-')] = value.to_s + headers[key.to_s.dasherize.split("-").each { |v| v[0] = v[0].chr.upcase }.join("-")] = value.to_s end self.status = status @@ -41,7 +35,7 @@ module ActionController self.response_body = "" - if include_content?(self.response_code) + if include_content?(response_code) self.content_type = content_type || (Mime[formats.first] if formats) self.response.charset = false end @@ -50,15 +44,15 @@ module ActionController end private - def include_content?(status) - case status - when 100..199 - false - when 204, 205, 304 - false - else - true + def include_content?(status) + case status + when 100..199 + false + when 204, 205, 304 + false + else + true + end end - end end end diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index d3853e2e83..476d081239 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -5,7 +5,7 @@ module ActionController # # In addition to using the standard template helpers provided, creating custom helpers to # extract complicated logic or reusable functionality is strongly encouraged. By default, each controller - # will include all helpers. These helpers are only accessible on the controller through <tt>.helpers</tt> + # will include all helpers. These helpers are only accessible on the controller through <tt>#helpers</tt> # # In previous versions of \Rails the controller will include a helper which # matches the name of the controller, e.g., <tt>MyController</tt> will automatically @@ -71,7 +71,7 @@ module ActionController attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") } end - # Provides a proxy to access helpers methods from outside the view. + # Provides a proxy to access helper methods from outside the view. def helpers @helper_proxy ||= begin proxy = ActionView::Base.new @@ -108,10 +108,15 @@ module ActionController end private - # Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt> - def all_application_helpers - all_helpers_from_path(helpers_path) - end + # Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt> + def all_application_helpers + all_helpers_from_path(helpers_path) + end + end + + # Provides a proxy to access helper methods from outside the view. + def helpers + @_helper_proxy ||= view_context end end end diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 35be6d9300..a335bf109e 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -1,5 +1,5 @@ -require 'base64' -require 'active_support/security_utils' +require "base64" +require "active_support/security_utils" module ActionController # Makes it dead easy to do HTTP Basic, Digest and Token authentication. @@ -99,23 +99,23 @@ module ActionController end def has_basic_credentials?(request) - request.authorization.present? && (auth_scheme(request).downcase == 'basic') + request.authorization.present? && (auth_scheme(request).downcase == "basic") end def user_name_and_password(request) - decode_credentials(request).split(':', 2) + decode_credentials(request).split(":", 2) end def decode_credentials(request) - ::Base64.decode64(auth_param(request) || '') + ::Base64.decode64(auth_param(request) || "") end def auth_scheme(request) - request.authorization.to_s.split(' ', 2).first + request.authorization.to_s.split(" ", 2).first end def auth_param(request) - request.authorization.to_s.split(' ', 2).second + request.authorization.to_s.split(" ", 2).second end def encode_credentials(user_name, password) @@ -208,7 +208,7 @@ module ActionController password = password_procedure.call(credentials[:username]) return false unless password - method = request.get_header('rack.methodoverride.original_method') || request.get_header('REQUEST_METHOD') + method = request.get_header("rack.methodoverride.original_method") || request.get_header("REQUEST_METHOD") uri = credentials[:uri] [true, false].any? do |trailing_question_mark| @@ -226,17 +226,17 @@ module ActionController # of a plain-text password. def expected_response(http_method, uri, credentials, password, password_is_ha1=true) ha1 = password_is_ha1 ? password : ha1(credentials, password) - ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(':')) - ::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(':')) + ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":")) + ::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(":")) end def ha1(credentials, password) - ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':')) + ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(":")) end def encode_credentials(http_method, credentials, password, password_is_ha1) credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password, password_is_ha1) - "Digest " + credentials.sort_by {|x| x[0].to_s }.map {|v| "#{v[0]}='#{v[1]}'" }.join(', ') + "Digest " + credentials.sort_by { |x| x[0].to_s }.map { |v| "#{v[0]}='#{v[1]}'" }.join(", ") end def decode_credentials_header(request) @@ -244,9 +244,9 @@ module ActionController end def decode_credentials(header) - ActiveSupport::HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/, '').split(',').map do |pair| - key, value = pair.split('=', 2) - [key.strip, value.to_s.gsub(/^"|"$/,'').delete('\'')] + ActiveSupport::HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/, "").split(",").map do |pair| + key, value = pair.split("=", 2) + [key.strip, value.to_s.gsub(/^"|"$/,"").delete('\'')] end] end @@ -310,9 +310,9 @@ module ActionController end # Might want a shorter timeout depending on whether the request - # is a PATCH, PUT, or POST, and if client is browser or web service. + # is a PATCH, PUT, or POST, and if the client is a browser or web service. # Can be much shorter if the Stale directive is implemented. This would - # allow a user to use new nonce without prompting user again for their + # allow a user to use new nonce without prompting the user again for their # username and password. def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60) return false if value.nil? @@ -324,7 +324,6 @@ module ActionController def opaque(secret_key) ::Digest::MD5.hexdigest(secret_key) end - end # Makes it dead easy to do HTTP Token authentication. @@ -347,7 +346,12 @@ module ActionController # private # def authenticate # authenticate_or_request_with_http_token do |token, options| - # token == TOKEN + # # Compare the tokens in a time-constant manner, to mitigate + # # timing attacks. + # ActiveSupport::SecurityUtils.secure_compare( + # ::Digest::SHA256.hexdigest(token), + # ::Digest::SHA256.hexdigest(TOKEN) + # ) # end # end # end @@ -401,7 +405,7 @@ module ActionController # # RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L] module Token - TOKEN_KEY = 'token=' + TOKEN_KEY = "token=" TOKEN_REGEX = /^(Token|Bearer)\s+/ AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/ extend self @@ -471,14 +475,14 @@ module ActionController # This removes the <tt>"</tt> characters wrapping the value. def rewrite_param_values(array_params) - array_params.each { |param| (param[1] || "").gsub! %r/^"|"$/, '' } + array_params.each { |param| (param[1] || "").gsub! %r/^"|"$/, "" } end # This method takes an authorization body and splits up the key-value # pairs by the standardized <tt>:</tt>, <tt>;</tt>, or <tt>\t</tt> # delimiters defined in +AUTHN_PAIR_DELIMITERS+. def raw_params(auth) - _raw_params = auth.sub(TOKEN_REGEX, '').split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/) + _raw_params = auth.sub(TOKEN_REGEX, "").split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/) if !(_raw_params.first =~ %r{\A#{TOKEN_KEY}}) _raw_params[0] = "#{TOKEN_KEY}#{_raw_params.first}" diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb index 17fcc2fa02..8615c16c6f 100644 --- a/actionpack/lib/action_controller/metal/implicit_render.rb +++ b/actionpack/lib/action_controller/metal/implicit_render.rb @@ -1,36 +1,73 @@ +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+. + # + # For API controllers, the implicit response is always 204 No Content. + # + # For all other controllers, we use these heuristics to decide whether to + # render a template, raise an error for a missing template, or respond with + # 204 No Content: + # + # First, if we DO find a template, it's rendered. Template lookup accounts + # for the action name, locales, format, variant, template handlers, and more + # (see +render+ for details). + # + # Second, if we DON'T find a template but the controller action does have + # templates for other formats, variants, etc., then we trust that you meant + # to provide a template for this response, too, and we raise + # <tt>ActionController::UnknownFormat</tt> with an explanation. + # + # Third, if we DON'T find a template AND the request is a page load in a web + # browser (technically, a non-XHR GET request for an HTML response) where + # you reasonably expect to have rendered a template, then we raise + # <tt>ActionView::UnknownFormat</tt> with an explanation. + # + # Finally, if we DON'T find a template AND the request isn't a browser page + # load, then we implicitly respond with 204 No Content. module ImplicitRender - + # :stopdoc: include BasicImplicitRender - # Renders the template corresponding to the controller action, if it exists. - # The action name, format, and variant are all taken into account. - # For example, the "new" action with an HTML format and variant "phone" - # would try to render the <tt>new.html+phone.erb</tt> template. - # - # If no template is found <tt>ActionController::BasicImplicitRender</tt>'s implementation is called, unless - # a block is passed. In that case, it will override the super implementation. - # - # default_render do - # head 404 # No template was found - # end def default_render(*args) if template_exists?(action_name.to_s, _prefixes, variants: request.variant) render(*args) + elsif any_templates?(action_name.to_s, _prefixes) + message = "#{self.class.name}\##{action_name} is missing a template " \ + "for this request format and variant.\n" \ + "\nrequest.formats: #{request.formats.map(&:to_s).inspect}" \ + "\nrequest.variant: #{request.variant.inspect}" + + raise ActionController::UnknownFormat, message + elsif interactive_browser_request? + message = "#{self.class.name}\##{action_name} is missing a template " \ + "for this request format and variant.\n\n" \ + "request.formats: #{request.formats.map(&:to_s).inspect}\n" \ + "request.variant: #{request.variant.inspect}\n\n" \ + "NOTE! For XHR/Ajax or API requests, this action would normally " \ + "respond with 204 No Content: an empty white screen. Since you're " \ + "loading it in a web browser, we assume that you expected to " \ + "actually render a template, not nothing, so we're showing an " \ + "error to be extra-clear. If you expect 204 No Content, carry on. " \ + "That's what you'll get from an XHR or API request. Give it a shot." + + raise ActionController::UnknownFormat, message else - if block_given? - yield(*args) - else - logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger - super - end + logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger + super end end def method_for_action(action_name) super || if template_exists?(action_name.to_s, _prefixes) - "default_render" - end + "default_render" + end end + + private + def interactive_browser_request? + request.get? && request.format == Mime[:html] && !request.xhr? + end end end diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index bf74b39ac4..2ede96c667 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -1,5 +1,5 @@ -require 'benchmark' -require 'abstract_controller/logger' +require "benchmark" +require "abstract_controller/logger" module ActionController # Adds instrumentation to several ends in ActionController::Base. It also provides @@ -16,12 +16,13 @@ module ActionController def process_action(*args) raw_payload = { - :controller => self.class.name, - :action => self.action_name, - :params => request.filtered_parameters, - :format => request.format.ref, - :method => request.request_method, - :path => request.fullpath + controller: self.class.name, + action: action_name, + params: request.filtered_parameters, + headers: request.headers, + format: request.format.ref, + method: request.request_method, + path: request.fullpath } ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup) @@ -47,7 +48,7 @@ module ActionController def send_file(path, options={}) ActiveSupport::Notifications.instrument("send_file.action_controller", - options.merge(:path => path)) do + options.merge(path: path)) do super end end @@ -71,11 +72,11 @@ module ActionController # A hook invoked every time a before callback is halted. def halted_callback_hook(filter) - ActiveSupport::Notifications.instrument("halted_callback.action_controller", :filter => filter) + ActiveSupport::Notifications.instrument("halted_callback.action_controller", filter: filter) end - # A hook which allows you to clean up any time taken into account in - # views wrongly, like database querying time. + # A hook which allows you to clean up any time, wrongly taken into account in + # views, like database querying time. # # def cleanup_view_runtime # super - time_taken_in_something_expensive diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb index fc20e7a421..26a16104db 100644 --- a/actionpack/lib/action_controller/metal/live.rb +++ b/actionpack/lib/action_controller/metal/live.rb @@ -1,9 +1,9 @@ -require 'action_dispatch/http/response' -require 'delegate' -require 'active_support/json' +require "action_dispatch/http/response" +require "delegate" +require "active_support/json" module ActionController - # Mix this module in to your controller, and all actions in that controller + # Mix this module into your controller, and all actions in that controller # will be able to stream data to the client as it's written. # # class MyController < ActionController::Base @@ -20,7 +20,7 @@ module ActionController # end # end # - # There are a few caveats with this use. You *cannot* write headers after the + # There are a few caveats with this module. You *cannot* write headers after the # response has been committed (Response#committed? will return truthy). # Calling +write+ or +close+ on the response stream will cause the response # object to be committed. Make sure all headers are set before calling write @@ -84,7 +84,6 @@ module ActionController # Note: SSEs are not currently supported by IE. However, they are supported # by Chrome, Firefox, Opera, and Safari. class SSE - WHITELISTED_OPTIONS = %w( retry event id ) def initialize(stream, options = {}) @@ -163,14 +162,6 @@ module ActionController end end - def each - @response.sending! - while str = @buf.pop - yield str - end - @response.sent! - end - # Write a 'close' event to the buffer; the producer/writing thread # uses this to notify us that it's finished supplying content. # @@ -210,23 +201,36 @@ module ActionController def call_on_error @error_callback.call end + + private + + def each_chunk(&block) + loop do + str = nil + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + str = @buf.pop + end + break unless str + yield str + end + end end class Response < ActionDispatch::Response #:nodoc: all private - def before_committed - super - jar = request.cookie_jar - # The response can be committed multiple times - jar.write self unless committed? - end + def before_committed + super + jar = request.cookie_jar + # The response can be committed multiple times + jar.write self unless committed? + end - def build_buffer(response, body) - buf = Live::Buffer.new response - body.each { |part| buf.write part } - buf - end + def build_buffer(response, body) + buf = Live::Buffer.new response + body.each { |part| buf.write part } + buf + end end def process(name) diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 173a14a1d2..f6aabcb102 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -1,4 +1,4 @@ -require 'abstract_controller/collector' +require "abstract_controller/collector" module ActionController #:nodoc: module MimeResponds @@ -198,7 +198,7 @@ module ActionController #:nodoc: _process_format(format) _set_rendered_content_type format response = collector.response - response ? response.call : render({}) + response.call if response else raise ActionController::UnknownFormat end @@ -280,8 +280,8 @@ module ActionController #:nodoc: def any(*args, &block) if block_given? - if args.any? && args.none?{ |a| a == @variant } - args.each{ |v| @variants[v] = block } + if args.any? && args.none? { |a| a == @variant } + args.each { |v| @variants[v] = block } else @variants[:any] = block end diff --git a/actionpack/lib/action_controller/metal/parameter_encoding.rb b/actionpack/lib/action_controller/metal/parameter_encoding.rb new file mode 100644 index 0000000000..c457fd0d06 --- /dev/null +++ b/actionpack/lib/action_controller/metal/parameter_encoding.rb @@ -0,0 +1,30 @@ +module ActionController + # Allows encoding to be specified per parameter per action. + module ParameterEncoding + extend ActiveSupport::Concern + + module ClassMethods + def inherited(klass) # :nodoc: + super + klass.setup_param_encode + end + + def setup_param_encode # :nodoc: + @_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 + end + + def parameter_encoding(action, param_name, encoding) + @_parameter_encodings[action.to_s] ||= {} + @_parameter_encodings[action.to_s][param_name.to_s] = encoding + end + end + end +end diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index c38fc40b81..9d1b740025 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -1,7 +1,7 @@ -require 'active_support/core_ext/hash/slice' -require 'active_support/core_ext/hash/except' -require 'active_support/core_ext/module/anonymous' -require 'action_dispatch/http/mime_type' +require "active_support/core_ext/hash/slice" +require "active_support/core_ext/hash/except" +require "active_support/core_ext/module/anonymous" +require "action_dispatch/http/mime_type" module ActionController # Wraps the parameters hash into a nested hash. This will allow clients to @@ -71,7 +71,7 @@ module ActionController EXCLUDE_PARAMETERS = %w(authenticity_token _method utf8) - require 'mutex_m' + require "mutex_m" class Options < Struct.new(:name, :format, :include, :exclude, :klass, :model) # :nodoc: include Mutex_m @@ -128,30 +128,30 @@ module ActionController end private - # Determine the wrapper model from the controller's name. By convention, - # this could be done by trying to find the defined model that has the - # same singular name as the controller. For example, +UsersController+ - # will try to find if the +User+ model exists. - # - # 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: - return nil if klass.anonymous? - model_name = klass.name.sub(/Controller$/, '').classify - - begin - if model_klass = model_name.safe_constantize - model_klass - else - namespaces = model_name.split("::") - namespaces.delete_at(-2) - break if namespaces.last == model_name - model_name = namespaces.join("::") - end - end until model_klass + # Determine the wrapper model from the controller's name. By convention, + # this could be done by trying to find the defined model that has the + # same singular name as the controller. For example, +UsersController+ + # will try to find if the +User+ model exists. + # + # 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: + return nil if klass.anonymous? + model_name = klass.name.sub(/Controller$/, "").classify + + begin + if model_klass = model_name.safe_constantize + model_klass + else + namespaces = model_name.split("::") + namespaces.delete_at(-2) + break if namespaces.last == model_name + model_name = namespaces.join("::") + end + end until model_klass - model_klass - end + model_klass + end end included do @@ -198,9 +198,9 @@ module ActionController when Hash options = name_or_model_or_options when false - options = options.merge(:format => []) + options = options.merge(format: []) when Symbol, String - options = options.merge(:name => name_or_model_or_options) + options = options.merge(name: name_or_model_or_options) else model = name_or_model_or_options end diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index b13ba06962..30798c1d99 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -1,12 +1,4 @@ module ActionController - class RedirectBackError < AbstractController::Error #:nodoc: - DEFAULT_MESSAGE = 'No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].' - - def initialize(message = nil) - super(message || DEFAULT_MESSAGE) - end - end - module Redirecting extend ActiveSupport::Concern @@ -24,10 +16,10 @@ module ActionController # === Examples: # # redirect_to action: "show", id: 5 - # redirect_to post + # redirect_to @post # redirect_to "http://www.rubyonrails.org" # redirect_to "/images/screenshot.jpg" - # redirect_to articles_url + # redirect_to posts_url # redirect_to proc { edit_post_url(@post) } # # The redirection happens as a "302 Found" header unless otherwise specified using the <tt>:status</tt> option: @@ -77,14 +69,14 @@ module ActionController # is missing this header, the <tt>fallback_location</tt> will be used. # # redirect_back fallback_location: { action: "show", id: 5 } - # redirect_back fallback_location: post + # redirect_back fallback_location: @post # redirect_back fallback_location: "http://www.rubyonrails.org" - # redirect_back fallback_location: "/images/screenshot.jpg" - # redirect_back fallback_location: articles_url - # redirect_back fallback_location: proc { edit_post_url(@post) } + # redirect_back fallback_location: "/images/screenshot.jpg" + # redirect_back fallback_location: posts_url + # redirect_back fallback_location: proc { edit_post_url(@post) } # # All options that can be passed to <tt>redirect_to</tt> are accepted as - # options and the behavior is indetical. + # options and the behavior is identical. def redirect_back(fallback_location:, **args) if referer = request.headers["Referer"] redirect_to referer, **args @@ -104,14 +96,6 @@ module ActionController options when String request.protocol + request.host_with_port + options - when :back - ActiveSupport::Deprecation.warn(<<-MESSAGE.squish) - `redirect_to :back` is deprecated and will be removed from Rails 5.1. - Please use `redirect_back(fallback_location: fallback_location)` where - `fallback_location` represents the location to use if the request has - no HTTP referer information. - MESSAGE - request.headers["Referer"] or raise RedirectBackError when Proc _compute_redirect_to_location request, options.call else diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index 90fb34e386..f8a037189c 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -1,4 +1,4 @@ -require 'set' +require "set" module ActionController # See <tt>Renderers.add</tt> @@ -71,8 +71,6 @@ module ActionController # format.csv { render csv: @csvable, filename: @csvable.name } # end # end - # To use renderers and their mime types in more concise ways, see - # <tt>ActionController::MimeResponds::ClassMethods.respond_to</tt> def self.add(key, &block) define_method(_render_with_renderer_method_name(key), &block) RENDERERS << key.to_sym @@ -94,7 +92,6 @@ module ActionController end module ClassMethods - # Adds, by name, a renderer or renderers to the +_renderers+ available # to call within controller actions. # @@ -103,7 +100,7 @@ module ActionController # # Both <tt>ActionController::Base</tt> and <tt>ActionController::API</tt> # include <tt>ActionController::Renderers::All</tt>, making all renderers - # avaialable in the controller. See <tt>Renderers::RENDERERS</tt> and <tt>Renderers.add</tt>. + # available in the controller. See <tt>Renderers::RENDERERS</tt> and <tt>Renderers.add</tt>. # # Since <tt>ActionController::Metal</tt> controllers cannot render, the controller # must include <tt>AbstractController::Rendering</tt>, <tt>ActionController::Rendering</tt>, diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index cce6fe7787..67365a143e 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -1,10 +1,10 @@ -require 'active_support/core_ext/string/filters' +require "active_support/core_ext/string/filters" module ActionController module Rendering extend ActiveSupport::Concern - RENDER_FORMATS_IN_PRIORITY = [:body, :text, :plain, :html] + RENDER_FORMATS_IN_PRIORITY = [:body, :plain, :html] module ClassMethods # Documentation at ActionController::Renderer#render @@ -32,7 +32,7 @@ module ActionController # Check for double render errors and set the content_type after rendering. def render(*args) #:nodoc: - raise ::AbstractController::DoubleRenderError if self.response_body + raise ::AbstractController::DoubleRenderError if response_body super end @@ -49,84 +49,68 @@ module ActionController end def render_to_body(options = {}) - super || _render_in_priorities(options) || ' ' + super || _render_in_priorities(options) || " " end private - def _render_in_priorities(options) - RENDER_FORMATS_IN_PRIORITY.each do |format| - return options[format] if options.key?(format) - end - - nil - end - - def _set_html_content_type - self.content_type = Mime[:html].to_s - end + def _render_in_priorities(options) + RENDER_FORMATS_IN_PRIORITY.each do |format| + return options[format] if options.key?(format) + end - def _set_rendered_content_type(format) - unless response.content_type - self.content_type = format.to_s + nil end - end - - # Normalize arguments by catching blocks and setting them on :update. - def _normalize_args(action=nil, options={}, &blk) #:nodoc: - options = super - options[:update] = blk if block_given? - options - end - # Normalize both text and status options. - def _normalize_options(options) #:nodoc: - _normalize_text(options) - - if options[:text] - ActiveSupport::Deprecation.warn <<-WARNING.squish - `render :text` is deprecated because it does not actually render a - `text/plain` response. Switch to `render plain: 'plain text'` to - render as `text/plain`, `render html: '<strong>HTML</strong>'` to - render as `text/html`, or `render body: 'raw'` to match the deprecated - behavior and render with the default Content-Type, which is - `text/plain`. - WARNING + def _set_html_content_type + self.content_type = Mime[:html].to_s end - if options[:html] - options[:html] = ERB::Util.html_escape(options[:html]) + def _set_rendered_content_type(format) + unless response.content_type + self.content_type = format.to_s + end end - if options.delete(:nothing) - ActiveSupport::Deprecation.warn("`:nothing` option is deprecated and will be removed in Rails 5.1. Use `head` method to respond with empty response body.") - options[:body] = nil + # Normalize arguments by catching blocks and setting them on :update. + def _normalize_args(action=nil, options={}, &blk) #:nodoc: + options = super + options[:update] = blk if block_given? + options end - if options[:status] - options[:status] = Rack::Utils.status_code(options[:status]) - end + # Normalize both text and status options. + def _normalize_options(options) #:nodoc: + _normalize_text(options) - super - end + if options[:html] + options[:html] = ERB::Util.html_escape(options[:html]) + end - def _normalize_text(options) - RENDER_FORMATS_IN_PRIORITY.each do |format| - if options.key?(format) && options[format].respond_to?(:to_text) - options[format] = options[format].to_text + if options[:status] + options[:status] = Rack::Utils.status_code(options[:status]) + end + + super + end + + def _normalize_text(options) + RENDER_FORMATS_IN_PRIORITY.each do |format| + if options.key?(format) && options[format].respond_to?(:to_text) + options[format] = options[format].to_text + end end end - end - # Process controller specific options, as status, content-type and location. - def _process_options(options) #:nodoc: - status, content_type, location = options.values_at(:status, :content_type, :location) + # Process controller specific options, as status, content-type and location. + def _process_options(options) #:nodoc: + 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 + self.status = status if status + self.content_type = content_type if content_type + self.headers["Location"] = url_for(location) if location - super - end + super + end end end diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 6586985ff5..3d3c121280 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -1,6 +1,6 @@ -require 'rack/session/abstract/id' -require 'action_controller/metal/exceptions' -require 'active_support/security_utils' +require "rack/session/abstract/id" +require "action_controller/metal/exceptions" +require "active_support/security_utils" module ActionController #:nodoc: class InvalidAuthenticityToken < ActionControllerError #:nodoc: @@ -109,10 +109,10 @@ module ActionController #:nodoc: # * <tt>:only/:except</tt> - Only apply forgery protection to a subset of actions. For example <tt>only: [ :create, :create_all ]</tt>. # * <tt>:if/:unless</tt> - Turn off the forgery protection entirely depending on the passed Proc or method reference. # * <tt>:prepend</tt> - By default, the verification of the authentication token will be added at the position of the - # protect_from_forgery call in your application. This means any callbacks added before are run first. This is useful - # when you want your forgery protection to depend on other callbacks, like authentication methods (Oauth vs Cookie auth). + # protect_from_forgery call in your application. This means any callbacks added before are run first. This is useful + # when you want your forgery protection to depend on other callbacks, like authentication methods (Oauth vs Cookie auth). # - # If you need to add verification to the beginning of the callback chain, use <tt>prepend: true</tt>. + # If you need to add verification to the beginning of the callback chain, use <tt>prepend: true</tt>. # * <tt>:with</tt> - Set the method to handle unverified request. # # Valid unverified request handling methods are: @@ -130,11 +130,11 @@ module ActionController #:nodoc: private - def protection_method_class(name) - ActionController::RequestForgeryProtection::ProtectionMethods.const_get(name.to_s.classify) - rescue NameError - raise ArgumentError, 'Invalid request forgery protection method, use :null_session, :exception, or :reset_session' - end + def protection_method_class(name) + ActionController::RequestForgeryProtection::ProtectionMethods.const_get(name.to_s.classify) + rescue NameError + raise ArgumentError, "Invalid request forgery protection method, use :null_session, :exception, or :reset_session" + end end module ProtectionMethods @@ -154,26 +154,26 @@ module ActionController #:nodoc: protected - class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc: - def initialize(req) - super(nil, req) - @data = {} - @loaded = true - end + class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc: + def initialize(req) + super(nil, req) + @data = {} + @loaded = true + end - # no-op - def destroy; end + # no-op + def destroy; end - def exists? - true + def exists? + true + end end - end - class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc: - def write(*) - # nothing + class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc: + def write(*) + # nothing + end end - end end class ResetSession @@ -213,7 +213,7 @@ module ActionController #:nodoc: if !verified_request? if logger && log_warning_on_csrf_failure - logger.warn "Can't verify CSRF token authenticity" + logger.warn "Can't verify CSRF token authenticity." end handle_unverified_request end @@ -235,7 +235,9 @@ module ActionController #:nodoc: # we aren't serving an unauthorized cross-origin response. def verify_same_origin_request if marked_for_same_origin_verification? && non_xhr_javascript_response? - logger.warn CROSS_ORIGIN_JAVASCRIPT_WARNING if logger + if logger && log_warning_on_csrf_failure + logger.warn CROSS_ORIGIN_JAVASCRIPT_WARNING + end raise ActionController::InvalidCrossOriginRequest, CROSS_ORIGIN_JAVASCRIPT_WARNING end end @@ -379,7 +381,8 @@ module ActionController #:nodoc: def xor_byte_strings(s1, s2) s2_bytes = s2.bytes - s1.bytes.map.with_index { |c1, i| c1 ^ s2_bytes[i] }.pack('c*') + 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. @@ -404,7 +407,8 @@ module ActionController #:nodoc: end def normalize_action_path(action_path) - action_path.split('?').first.to_s.chomp('/') + uri = URI.parse(action_path) + uri.path.chomp("/") end end end diff --git a/actionpack/lib/action_controller/metal/rescue.rb b/actionpack/lib/action_controller/metal/rescue.rb index 81b9a7b9ed..2d99e4045b 100644 --- a/actionpack/lib/action_controller/metal/rescue.rb +++ b/actionpack/lib/action_controller/metal/rescue.rb @@ -1,18 +1,11 @@ module ActionController #:nodoc: - # This module is responsible to provide `rescue_from` helpers - # to controllers and configure when detailed exceptions must be + # This module is responsible for providing `rescue_from` helpers + # to controllers and configuring when detailed exceptions must be # shown. module Rescue extend ActiveSupport::Concern include ActiveSupport::Rescuable - def rescue_with_handler(exception) - if exception.cause && handler_for_rescue(exception.cause) - exception = exception.cause - end - super(exception) - end - # Override this method if you want to customize when detailed # exceptions must be shown. This method is only called when # consider_all_requests_local is false. By default, it returns @@ -26,8 +19,8 @@ module ActionController #:nodoc: def process_action(*args) super rescue Exception => exception - request.env['action_dispatch.show_detailed_exceptions'] ||= show_detailed_exceptions? - rescue_with_handler(exception) || raise(exception) + request.env["action_dispatch.show_detailed_exceptions"] ||= show_detailed_exceptions? + rescue_with_handler(exception) || raise end end end diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index a6115674aa..481f19f1ef 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -1,4 +1,4 @@ -require 'rack/chunked' +require "rack/chunked" module ActionController #:nodoc: # Allows views to be streamed back to the client as they are rendered. diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index d3382ef296..b64617d084 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -1,12 +1,14 @@ -require 'active_support/core_ext/hash/indifferent_access' -require 'active_support/core_ext/hash/transform_values' -require 'active_support/core_ext/array/wrap' -require 'active_support/core_ext/string/filters' -require 'active_support/rescuable' -require 'action_dispatch/http/upload' -require 'rack/test' -require 'stringio' -require 'set' +require "active_support/core_ext/hash/indifferent_access" +require "active_support/core_ext/hash/transform_values" +require "active_support/core_ext/array/wrap" +require "active_support/core_ext/string/filters" +require "active_support/core_ext/object/to_query" +require "active_support/rescuable" +require "action_dispatch/http/upload" +require "rack/test" +require "stringio" +require "set" +require "yaml" module ActionController # Raised when a required parameter is missing. @@ -31,19 +33,19 @@ module ActionController # # params = ActionController::Parameters.new(a: "123", b: "456") # params.permit(:c) - # # => ActionController::UnpermittedParameters: found unpermitted parameters: a, b + # # => ActionController::UnpermittedParameters: found unpermitted parameters: :a, :b class UnpermittedParameters < IndexError attr_reader :params # :nodoc: def initialize(params) # :nodoc: @params = params - super("found unpermitted parameter#{'s' if params.size > 1 }: #{params.join(", ")}") + super("found unpermitted parameter#{'s' if params.size > 1 }: #{params.map { |e| ":#{e}" }.join(", ")}") end end # == Action Controller \Parameters # - # Allows to choose which attributes should be whitelisted for mass updating + # Allows you to choose which attributes should be whitelisted for mass updating # and thus prevent accidentally exposing that which shouldn't be exposed. # Provides two methods for this purpose: #require and #permit. The former is # used to mark parameters as required. The latter is used to set the parameter @@ -58,8 +60,7 @@ module ActionController # }) # # permitted = params.require(:person).permit(:name, :age) - # permitted # => {"name"=>"Francesco", "age"=>22} - # permitted.class # => ActionController::Parameters + # permitted # => <ActionController::Parameters {"name"=>"Francesco", "age"=>22} permitted: true> # permitted.permitted? # => true # # Person.first.update!(permitted) @@ -87,7 +88,7 @@ module ActionController # # params = ActionController::Parameters.new(a: "123", b: "456") # params.permit(:c) - # # => {} + # # => <ActionController::Parameters {} permitted: true> # # ActionController::Parameters.action_on_unpermitted_parameters = :raise # @@ -107,9 +108,11 @@ module ActionController # params["key"] # => "value" class Parameters cattr_accessor :permit_all_parameters, instance_accessor: false + self.permit_all_parameters = false + cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false - delegate :keys, :key?, :has_key?, :values, :has_value?, :value?, :empty?, :include?, :inspect, + delegate :keys, :key?, :has_key?, :values, :has_value?, :value?, :empty?, :include?, :as_json, to: :@parameters # By default, never raise an UnpermittedParameters exception if these @@ -122,16 +125,6 @@ module ActionController cattr_accessor :always_permitted_parameters self.always_permitted_parameters = %w( controller action ) - def self.const_missing(const_name) - return super unless const_name == :NEVER_UNPERMITTED_PARAMS - ActiveSupport::Deprecation.warn(<<-MSG.squish) - `ActionController::Parameters::NEVER_UNPERMITTED_PARAMS` has been deprecated. - Use `ActionController::Parameters.always_permitted_parameters` instead. - MSG - - always_permitted_parameters - end - # Returns a new instance of <tt>ActionController::Parameters</tt>. # Also, sets the +permitted+ attribute to the default value of # <tt>ActionController::Parameters.permit_all_parameters</tt>. @@ -154,17 +147,12 @@ module ActionController end # Returns true if another +Parameters+ object contains the same content and - # permitted flag, or other Hash-like object contains the same content. This - # override is in place so you can perform a comparison with `Hash`. - def ==(other_hash) - if other_hash.respond_to?(:permitted?) - super + # permitted flag. + def ==(other) + if other.respond_to?(:permitted?) + self.permitted? == other.permitted? && self.parameters == other.parameters else - if other_hash.is_a?(Hash) - @parameters == other_hash.with_indifferent_access - else - @parameters == other_hash - end + @parameters == other end end @@ -190,12 +178,19 @@ module ActionController # Returns an unsafe, unfiltered # <tt>ActiveSupport::HashWithIndifferentAccess</tt> representation of this # parameter. + # + # params = ActionController::Parameters.new({ + # name: 'Senjougahara Hitagi', + # oddity: 'Heavy stone crab' + # }) + # params.to_unsafe_h + # # => {"name"=>"Senjougahara Hitagi", "oddity" => "Heavy stone crab"} def to_unsafe_h convert_parameters_to_hashes(@parameters, :to_unsafe_h) end alias_method :to_unsafe_hash, :to_unsafe_h - # Convert all hashes in values into parameters, then yield each pair like + # Convert all hashes in values into parameters, then yield each pair in # the same way as <tt>Hash#each_pair</tt> def each_pair(&block) @parameters.each_pair do |key, value| @@ -254,7 +249,7 @@ module ActionController # either present or the singleton +false+, returns said value: # # ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person) - # # => {"name"=>"Francesco"} + # # => <ActionController::Parameters {"name"=>"Francesco"} permitted: false> # # Otherwise raises <tt>ActionController::ParameterMissing</tt>: # @@ -275,12 +270,12 @@ module ActionController # returned: # # params = ActionController::Parameters.new(user: { ... }, profile: { ... }) - # user_params, profile_params = params.require(:user, :profile) + # user_params, profile_params = params.require([:user, :profile]) # - # Otherwise, the method reraises the first exception found: + # Otherwise, the method re-raises the first exception found: # # params = ActionController::Parameters.new(user: {}, profile: {}) - # user_params, profile_params = params.require(:user, :profile) + # user_params, profile_params = params.require([:user, :profile]) # # ActionController::ParameterMissing: param is missing or the value is empty: user # # Technically this method can be used to fetch terminal values: @@ -373,13 +368,13 @@ module ActionController # }) # # params.require(:person).permit(:contact) - # # => {} + # # => <ActionController::Parameters {} permitted: true> # # params.require(:person).permit(contact: :phone) - # # => {"contact"=>{"phone"=>"555-1234"}} + # # => <ActionController::Parameters {"contact"=><ActionController::Parameters {"phone"=>"555-1234"} permitted: true>} permitted: true> # # params.require(:person).permit(contact: [ :email, :phone ]) - # # => {"contact"=>{"email"=>"none@test.com", "phone"=>"555-1234"}} + # # => <ActionController::Parameters {"contact"=><ActionController::Parameters {"email"=>"none@test.com", "phone"=>"555-1234"} permitted: true>} permitted: true> def permit(*filters) params = self.class.new @@ -401,7 +396,7 @@ module ActionController # returns +nil+. # # params = ActionController::Parameters.new(person: { name: 'Francesco' }) - # params[:person] # => {"name"=>"Francesco"} + # params[:person] # => <ActionController::Parameters {"name"=>"Francesco"} permitted: false> # params[:none] # => nil def [](key) convert_hashes_to_parameters(key, @parameters[key]) @@ -420,7 +415,7 @@ module ActionController # is given, then that will be run and its result returned. # # params = ActionController::Parameters.new(person: { name: 'Francesco' }) - # params.fetch(:person) # => {"name"=>"Francesco"} + # params.fetch(:person) # => <ActionController::Parameters {"name"=>"Francesco"} permitted: false> # params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none # params.fetch(:none, 'Francesco') # => "Francesco" # params.fetch(:none) { 'Francesco' } # => "Francesco" @@ -436,13 +431,28 @@ module ActionController ) end + if Hash.method_defined?(:dig) + # Extracts the nested parameter from the given +keys+ by calling +dig+ + # at each step. Returns +nil+ if any intermediate step is +nil+. + # + # params = ActionController::Parameters.new(foo: { bar: { baz: 1 } }) + # params.dig(:foo, :bar, :baz) # => 1 + # params.dig(:foo, :zot, :xyz) # => nil + # + # params2 = ActionController::Parameters.new(foo: [10, 11, 12]) + # params2.dig(:foo, 1) # => 11 + def dig(*keys) + convert_value_to_parameters(@parameters.dig(*keys)) + end + end + # Returns a new <tt>ActionController::Parameters</tt> instance that # includes only the given +keys+. If the given +keys+ # don't exist, returns an empty hash. # # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) - # params.slice(:a, :b) # => {"a"=>1, "b"=>2} - # params.slice(:d) # => {} + # params.slice(:a, :b) # => <ActionController::Parameters {"a"=>1, "b"=>2} permitted: false> + # params.slice(:d) # => <ActionController::Parameters {} permitted: false> def slice(*keys) new_instance_with_inherited_permitted_status(@parameters.slice(*keys)) end @@ -458,8 +468,8 @@ module ActionController # filters out the given +keys+. # # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) - # params.except(:a, :b) # => {"c"=>3} - # params.except(:d) # => {"a"=>1,"b"=>2,"c"=>3} + # params.except(:a, :b) # => <ActionController::Parameters {"c"=>3} permitted: false> + # params.except(:d) # => <ActionController::Parameters {"a"=>1, "b"=>2, "c"=>3} permitted: false> def except(*keys) new_instance_with_inherited_permitted_status(@parameters.except(*keys)) end @@ -467,8 +477,8 @@ module ActionController # Removes and returns the key/value pairs matching the given keys. # # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) - # params.extract!(:a, :b) # => {"a"=>1, "b"=>2} - # params # => {"c"=>3} + # params.extract!(:a, :b) # => <ActionController::Parameters {"a"=>1, "b"=>2} permitted: false> + # params # => <ActionController::Parameters {"c"=>3} permitted: false> def extract!(*keys) new_instance_with_inherited_permitted_status(@parameters.extract!(*keys)) end @@ -478,7 +488,7 @@ module ActionController # # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) # params.transform_values { |x| x * 2 } - # # => {"a"=>2, "b"=>4, "c"=>6} + # # => <ActionController::Parameters {"a"=>2, "b"=>4, "c"=>6} permitted: false> def transform_values(&block) if block new_instance_with_inherited_permitted_status( @@ -555,25 +565,11 @@ module ActionController convert_value_to_parameters(@parameters.values_at(*keys)) end - # Returns an exact copy of the <tt>ActionController::Parameters</tt> - # instance. +permitted+ state is kept on the duped object. - # - # params = ActionController::Parameters.new(a: 1) - # params.permit! - # params.permitted? # => true - # copy_params = params.dup # => {"a"=>1} - # copy_params.permitted? # => true - def dup - super.tap do |duplicate| - duplicate.permitted = @permitted - end - end - # Returns a new <tt>ActionController::Parameters</tt> with all keys from # +other_hash+ merges into current hash. def merge(other_hash) new_instance_with_inherited_permitted_status( - @parameters.merge(other_hash) + @parameters.merge(other_hash.to_h) ) end @@ -584,31 +580,55 @@ module ActionController dup end - def method_missing(method_sym, *args, &block) - if @parameters.respond_to?(method_sym) - message = <<-DEPRECATE.squish - Method #{method_sym} is deprecated and will be removed in Rails 5.1, - as `ActionController::Parameters` no longer inherits from - hash. Using this deprecated behavior exposes potential security - problems. If you continue to use this method you may be creating - a security vulnerability in your app that can be exploited. Instead, - consider using one of these documented methods which are not - deprecated: http://api.rubyonrails.org/v#{ActionPack.version}/classes/ActionController/Parameters.html - DEPRECATE - ActiveSupport::Deprecation.warn(message) - @parameters.public_send(method_sym, *args, &block) - else - super + def inspect + "<#{self.class} #{@parameters} permitted: #{@permitted}>" + end + + def self.hook_into_yaml_loading # :nodoc: + # Wire up YAML format compatibility with Rails 4.2 and Psych 2.0.8 and 2.0.9+. + # Makes the YAML parser call `init_with` when it encounters the keys below + # instead of trying its own parsing routines. + YAML.load_tags["!ruby/hash-with-ivars:ActionController::Parameters"] = name + YAML.load_tags["!ruby/hash:ActionController::Parameters"] = name + end + hook_into_yaml_loading + + def init_with(coder) # :nodoc: + case coder.tag + when "!ruby/hash:ActionController::Parameters" + # YAML 2.0.8's format where hash instance variables weren't stored. + @parameters = coder.map.with_indifferent_access + @permitted = false + when "!ruby/hash-with-ivars:ActionController::Parameters" + # YAML 2.0.9's Hash subclass format where keys and values + # were stored under an elements hash and `permitted` within an ivars hash. + @parameters = coder.map["elements"].with_indifferent_access + @permitted = coder.map["ivars"][:@permitted] + when "!ruby/object:ActionController::Parameters" + # YAML's Object format. Only needed because of the format + # backwardscompability above, otherwise equivalent to YAML's initialization. + @parameters, @permitted = coder.map["parameters"], coder.map["permitted"] + end + end + + undef_method :to_param + + # Returns duplicate of object including all parameters + def deep_dup + self.class.new(@parameters.deep_dup).tap do |duplicate| + duplicate.permitted = @permitted end end protected + attr_reader :parameters + def permitted=(new_permitted) @permitted = new_permitted end def fields_for_style? - @parameters.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) } + @parameters.all? { |k, v| k =~ /\A-?\d+\z/ && (v.is_a?(Hash) || v.is_a?(Parameters)) } end private @@ -682,7 +702,7 @@ module ActionController end def unpermitted_keys(params) - self.keys - params.keys - self.always_permitted_parameters + keys - params.keys - always_permitted_parameters end # @@ -713,7 +733,7 @@ module ActionController ] def permitted_scalar?(value) - PERMITTED_SCALAR_TYPES.any? {|type| value.is_a?(type)} + PERMITTED_SCALAR_TYPES.any? { |type| value.is_a?(type) } end def permitted_scalar_filter(params, key) @@ -729,11 +749,15 @@ module ActionController end def array_of_permitted_scalars?(value) - if value.is_a?(Array) && value.all? {|element| permitted_scalar?(element)} + if value.is_a?(Array) && value.all? { |element| permitted_scalar?(element) } yield value end end + def non_scalar?(value) + value.is_a?(Array) || value.is_a?(Parameters) + end + EMPTY_ARRAY = [] def hash_filter(params, filter) filter = filter.with_indifferent_access @@ -748,7 +772,7 @@ module ActionController array_of_permitted_scalars?(self[key]) do |val| params[key] = val end - else + elsif non_scalar?(value) # Declaration { user: :name } or { user: [:name, :age, { address: ... }] }. params[key] = each_element(value) do |element| element.permit(*Array.wrap(filter[key])) @@ -756,6 +780,11 @@ module ActionController end end end + + def initialize_copy(source) + super + @parameters = @parameters.dup + end end # == Strong \Parameters @@ -771,7 +800,7 @@ module ActionController # # class PeopleController < ActionController::Base # # Using "Person.create(params[:person])" would raise an - # # ActiveModel::ForbiddenAttributes exception because it'd + # # ActiveModel::ForbiddenAttributesError exception because it'd # # be using mass assignment without an explicit permit step. # # This is the recommended form: # def create @@ -799,7 +828,8 @@ module ActionController # end # # In order to use <tt>accepts_nested_attributes_for</tt> with Strong \Parameters, you - # will need to specify which nested attributes should be whitelisted. + # will need to specify which nested attributes should be whitelisted. You might want + # to allow +:id+ and +:_destroy+, see ActiveRecord::NestedAttributes for more information. # # class Person # has_many :pets @@ -819,7 +849,7 @@ module ActionController # # It's mandatory to specify the nested attributes that should be whitelisted. # # If you use `permit` with just the key that points to the nested attributes hash, # # it will return an empty hash. - # params.require(:person).permit(:name, :age, pets_attributes: [ :name, :category ]) + # params.require(:person).permit(:name, :age, pets_attributes: [ :id, :name, :category ]) # end # end # diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb index ac37b00010..9bb416178a 100644 --- a/actionpack/lib/action_controller/metal/testing.rb +++ b/actionpack/lib/action_controller/metal/testing.rb @@ -13,7 +13,7 @@ module ActionController module ClassMethods def before_filters - _process_action_callbacks.find_all{|x| x.kind == :before}.map(&:name) + _process_action_callbacks.find_all { |x| x.kind == :before }.map(&:name) end end end diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index dbf7241a14..9f3cc099d6 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -27,10 +27,10 @@ module ActionController def url_options @_url_options ||= { - :host => request.host, - :port => request.optional_port, - :protocol => request.protocol, - :_recall => request.path_parameters + host: request.host, + port: request.optional_port, + protocol: request.protocol, + _recall: request.path_parameters }.merge!(super).freeze if (same_origin = _routes.equal?(request.routes)) || diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 28b20052b5..6513a556ee 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -11,7 +11,7 @@ module ActionController config.eager_load_namespaces << ActionController - initializer "action_controller.assets_config", :group => :all do |app| + initializer "action_controller.assets_config", group: :all do |app| app.config.action_controller.assets_dir ||= app.config.paths["public"].first end diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb index e4d19e9dba..243a54330e 100644 --- a/actionpack/lib/action_controller/renderer.rb +++ b/actionpack/lib/action_controller/renderer.rb @@ -1,7 +1,7 @@ -require 'active_support/core_ext/hash/keys' +require "active_support/core_ext/hash/keys" module ActionController - # ActionController::Renderer allows to render arbitrary templates + # ActionController::Renderer allows you to render arbitrary templates # without requirement of being in controller actions. # # You get a concrete renderer class by invoking ActionController::Base#renderer. @@ -13,11 +13,11 @@ module ActionController # # ApplicationController.renderer.render template: '...' # - # You can use a shortcut on controller to replace previous example with: + # You can use this shortcut in a controller, instead of the previous example: # # ApplicationController.render template: '...' # - # #render method allows you to use any options as when rendering in controller. + # #render allows you to use the same options that you can use when rendering in a controller. # For example, # # FooController.render :action, locals: { ... }, assigns: { ... } @@ -37,15 +37,15 @@ module ActionController attr_reader :defaults, :controller DEFAULTS = { - http_host: 'example.org', + http_host: "example.org", https: false, - method: 'get', - script_name: '', - input: '' + method: "get", + script_name: "", + input: "" }.freeze # Create a new renderer instance for a specific controller class. - def self.for(controller, env = {}, defaults = DEFAULTS) + def self.for(controller, env = {}, defaults = DEFAULTS.dup) new(controller, env, defaults) end @@ -69,7 +69,7 @@ module ActionController # Render templates with any options from ActionController::Base#render_to_string. def render(*args) - raise 'missing controller' unless controller + raise "missing controller" unless controller request = ActionDispatch::Request.new @env request.routes = controller._routes @@ -88,21 +88,23 @@ module ActionController end RACK_KEY_TRANSLATION = { - http_host: 'HTTP_HOST', - https: 'HTTPS', - method: 'REQUEST_METHOD', - script_name: 'SCRIPT_NAME', - input: 'rack.input' + http_host: "HTTP_HOST", + https: "HTTPS", + method: "REQUEST_METHOD", + script_name: "SCRIPT_NAME", + input: "rack.input" } IDENTITY = ->(_) { _ } RACK_VALUE_TRANSLATION = { - https: ->(v) { v ? 'on' : 'off' }, + https: ->(v) { v ? "on" : "off" }, method: ->(v) { v.upcase }, } - def rack_key_for(key); RACK_KEY_TRANSLATION[key]; end + def rack_key_for(key) + RACK_KEY_TRANSLATION.fetch(key, key.to_s) + end def rack_value_for(key, value) RACK_VALUE_TRANSLATION.fetch(key, IDENTITY).call value diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 0c4b661214..33c0201951 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -1,13 +1,12 @@ -require 'rack/session/abstract/id' -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 'action_controller/template_assertions' -require 'rails-dom-testing' +require "rack/session/abstract/id" +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 "action_controller/template_assertions" +require "rails-dom-testing" module ActionController - # :stopdoc: class Metal include Testing::Functional end @@ -27,18 +26,20 @@ module ActionController # Please use ActionDispatch::IntegrationTest going forward. class TestRequest < ActionDispatch::TestRequest #:nodoc: DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup - DEFAULT_ENV.delete 'PATH_INFO' + DEFAULT_ENV.delete "PATH_INFO" def self.new_session TestSession.new end + attr_reader :controller_class + # Create a new test request with default `env` values - def self.create + def self.create(controller_class) env = {} env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application env["rack.request.cookie_hash"] = {}.with_indifferent_access - new(default_env.merge(env), new_session) + new(default_env.merge(env), new_session, controller_class) end def self.default_env @@ -46,13 +47,14 @@ module ActionController end private_class_method :default_env - def initialize(env, session) + def initialize(env, session, controller_class) super(env) self.session = session - self.session_options = TestSession::DEFAULT_OPTIONS + self.session_options = TestSession::DEFAULT_OPTIONS.dup + @controller_class = controller_class @custom_param_parsers = { - Mime[:xml] => lambda { |raw_post| Hash.from_xml(raw_post)['hash'] } + xml: lambda { |raw_post| Hash.from_xml(raw_post)["hash"] } } end @@ -61,7 +63,7 @@ module ActionController end def content_type=(type) - set_header 'CONTENT_TYPE', type + set_header "CONTENT_TYPE", type end def assign_parameters(routes, controller_path, action, parameters, generated_path, query_string_keys) @@ -83,7 +85,7 @@ module ActionController end if get? - if self.query_string.blank? + if query_string.blank? self.query_string = non_path_parameters.to_query end else @@ -91,8 +93,8 @@ module ActionController self.content_type = ENCODER.content_type data = ENCODER.build_multipart non_path_parameters else - fetch_header('CONTENT_TYPE') do |k| - set_header k, 'application/x-www-form-urlencoded' + fetch_header("CONTENT_TYPE") do |k| + set_header k, "application/x-www-form-urlencoded" end case content_mime_type.to_sym @@ -105,13 +107,13 @@ module ActionController when :url_encoded_form data = non_path_parameters.to_query else - @custom_param_parsers[content_mime_type] = ->(_) { non_path_parameters } + @custom_param_parsers[content_mime_type.symbol] = ->(_) { non_path_parameters } data = non_path_parameters.to_query end end - set_header 'CONTENT_LENGTH', data.length.to_s - set_header 'rack.input', StringIO.new(data) + set_header "CONTENT_LENGTH", data.length.to_s + set_header "rack.input", StringIO.new(data) end fetch_header("PATH_INFO") do |k| @@ -152,9 +154,9 @@ module ActionController private - def params_parsers - super.merge @custom_param_parsers - end + def params_parsers + super.merge @custom_param_parsers + end end class LiveTestResponse < Live::Response @@ -208,10 +210,18 @@ module ActionController end # Superclass for ActionController functional tests. Functional tests allow you to - # test a single controller action per test method. This should not be confused with - # integration tests (see ActionDispatch::IntegrationTest), which are more like - # "stories" that can involve multiple controllers and multiple actions (i.e. multiple - # different HTTP requests). + # test a single controller action per test method. + # + # == Use integration style controller tests over functional style controller tests. + # + # Rails discourages the use of functional tests in favor of integration tests + # (use ActionDispatch::IntegrationTest). + # + # New Rails applications no longer generate functional style controller tests and they should + # only be used for backward compatibility. Integration style controller tests perform actual + # requests, whereas functional style controller tests merely simulate a request. Besides, + # integration tests are as fast as functional tests and provide lot of helpers such as +as+, + # +parsed_body+ for effective testing of controller actions including even API endpoints. # # == Basic example # @@ -320,7 +330,6 @@ module ActionController attr_reader :response, :request module ClassMethods - # Sets the controller class name. Useful if the name can't be inferred from test class. # Normalizes +controller_class+ before using. # @@ -377,58 +386,43 @@ module ActionController # # Note that the request method is not verified. The different methods are # available to make the tests more expressive. - def get(action, *args) - res = process_with_kwargs("GET", action, *args) + def get(action, **args) + res = process(action, method: "GET", **args) cookies.update res.cookies res end # Simulate a POST request with the given parameters and set/volley the response. # See +get+ for more details. - def post(action, *args) - process_with_kwargs("POST", action, *args) + def post(action, **args) + process(action, method: "POST", **args) end # Simulate a PATCH request with the given parameters and set/volley the response. # See +get+ for more details. - def patch(action, *args) - process_with_kwargs("PATCH", action, *args) + def patch(action, **args) + process(action, method: "PATCH", **args) end # Simulate a PUT request with the given parameters and set/volley the response. # See +get+ for more details. - def put(action, *args) - process_with_kwargs("PUT", action, *args) + def put(action, **args) + process(action, method: "PUT", **args) end # Simulate a DELETE request with the given parameters and set/volley the response. # See +get+ for more details. - def delete(action, *args) - process_with_kwargs("DELETE", action, *args) + def delete(action, **args) + process(action, method: "DELETE", **args) end # Simulate a HEAD request with the given parameters and set/volley the response. # See +get+ for more details. - def head(action, *args) - process_with_kwargs("HEAD", action, *args) + def head(action, **args) + process(action, method: "HEAD", **args) end - def xml_http_request(*args) - ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc) - xhr and xml_http_request methods are deprecated in favor of - `get :index, xhr: true` and `post :create, xhr: true` - MSG - - @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' - @request.env['HTTP_ACCEPT'] ||= [Mime[:js], Mime[:html], Mime[:xml], 'text/xml', '*/*'].join(', ') - __send__(*args).tap do - @request.env.delete 'HTTP_X_REQUESTED_WITH' - @request.env.delete 'HTTP_ACCEPT' - end - end - alias xhr :xml_http_request - - # Simulate a HTTP request to +action+ by specifying request method, + # Simulate an HTTP request to +action+ by specifying request method, # parameters and set/volley the response. # # - +action+: The controller action to call. @@ -440,6 +434,8 @@ module ActionController # - +session+: A hash of parameters to store in the session. This may be +nil+. # - +flash+: A hash of parameters to store in the flash. This may be +nil+. # - +format+: Request format. Defaults to +nil+. Can be string or symbol. + # - +as+: Content type. Defaults to +nil+. Must be a symbol that corresponds + # to a mime type. # # Example calling +create+ action and sending two params: # @@ -456,56 +452,39 @@ module ActionController # respectively which will make tests more expressive. # # Note that the request method is not verified. - def process(action, *args) + def process(action, method: "GET", params: {}, session: nil, body: nil, flash: {}, format: nil, xhr: false, as: nil) check_required_ivars - if kwarg_request?(args) - parameters, session, body, flash, http_method, format, xhr = args[0].values_at(:params, :session, :body, :flash, :method, :format, :xhr) - else - http_method, parameters, session, flash = args - format = nil - - if parameters.is_a?(String) && http_method != 'HEAD' - body = parameters - parameters = nil - end - - if parameters || session || flash - non_kwarg_request_warning - end - end - if body - @request.set_header 'RAW_POST_DATA', body + @request.set_header "RAW_POST_DATA", body end - if http_method - http_method = http_method.to_s.upcase - else - http_method = "GET" - end - - parameters ||= {} - - if format - parameters[:format] = format - end + http_method = method.to_s.upcase @html_document = nil - self.cookies.update @request.cookies - self.cookies.update_cookies_from_jar - @request.set_header 'HTTP_COOKIE', cookies.to_header - @request.delete_header 'action_dispatch.cookies' + cookies.update(@request.cookies) + cookies.update_cookies_from_jar + @request.set_header "HTTP_COOKIE", cookies.to_header + @request.delete_header "action_dispatch.cookies" - @request = TestRequest.new scrub_env!(@request.env), @request.session + @request = TestRequest.new scrub_env!(@request.env), @request.session, @controller.class @response = build_response @response_klass @response.request = @request @controller.recycle! - @request.set_header 'REQUEST_METHOD', http_method + @request.set_header "REQUEST_METHOD", http_method + + if as + @request.content_type = Mime[as].to_s + format ||= as + end - parameters = parameters.symbolize_keys + parameters = params.symbolize_keys + + if format + parameters[:format] = format + end generated_extras = @routes.generate_extras(parameters.merge(controller: controller_class_name, action: action.to_s)) generated_path = generated_path(generated_extras) @@ -517,9 +496,9 @@ module ActionController @request.flash.update(flash || {}) if xhr - @request.set_header 'HTTP_X_REQUESTED_WITH', 'XMLHttpRequest' - @request.fetch_header('HTTP_ACCEPT') do |k| - @request.set_header k, [Mime[:js], Mime[:html], Mime[:xml], 'text/xml', '*/*'].join(', ') + @request.set_header "HTTP_X_REQUESTED_WITH", "XMLHttpRequest" + @request.fetch_header("HTTP_ACCEPT") do |k| + @request.set_header k, [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ") end end @@ -527,32 +506,37 @@ module ActionController @request.set_header k, @controller.config.relative_url_root end - @controller.recycle! - @controller.dispatch(action, @request, @response) - @request = @controller.request - @response = @controller.response + begin + @controller.recycle! + @controller.dispatch(action, @request, @response) + ensure + @request = @controller.request + @response = @controller.response - @request.delete_header 'HTTP_COOKIE' + @request.delete_header "HTTP_COOKIE" - if @request.have_cookie_jar? - unless @request.cookie_jar.committed? - @request.cookie_jar.write(@response) - self.cookies.update(@request.cookie_jar.instance_variable_get(:@cookies)) + if @request.have_cookie_jar? + unless @request.cookie_jar.committed? + @request.cookie_jar.write(@response) + cookies.update(@request.cookie_jar.instance_variable_get(:@cookies)) + end end - end - @response.prepare! + @response.prepare! - if flash_value = @request.flash.to_session_value - @request.session['flash'] = flash_value - else - @request.session.delete('flash') - end + if flash_value = @request.flash.to_session_value + @request.session["flash"] = flash_value + else + @request.session.delete("flash") + end - if xhr - @request.delete_header 'HTTP_X_REQUESTED_WITH' - @request.delete_header 'HTTP_ACCEPT' + if xhr + @request.delete_header "HTTP_X_REQUESTED_WITH" + @request.delete_header "HTTP_ACCEPT" + end + @request.query_string = "" + + @response.sent! end - @request.query_string = '' @response end @@ -587,7 +571,7 @@ module ActionController end end - @request = TestRequest.create + @request = TestRequest.create(@controller.class) @response = build_response @response_klass @response.request = @request @@ -606,71 +590,35 @@ module ActionController include ActionDispatch::Assertions class_attribute :_controller_class setup :setup_controller_request_and_response + ActiveSupport.run_load_hooks(:action_controller_test_case, self) end private - def scrub_env!(env) - env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ } - env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ } - env.delete 'action_dispatch.request.query_parameters' - env.delete 'action_dispatch.request.request_parameters' - env - end - - def process_with_kwargs(http_method, action, *args) - if kwarg_request?(args) - args.first.merge!(method: http_method) - process(action, *args) - else - non_kwarg_request_warning if args.any? - - args = args.unshift(http_method) - process(action, *args) + def scrub_env!(env) + env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ } + env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ } + env.delete "action_dispatch.request.query_parameters" + env.delete "action_dispatch.request.request_parameters" + env["rack.input"] = StringIO.new + env end - end - - REQUEST_KWARGS = %i(params session flash method body xhr) - def kwarg_request?(args) - args[0].respond_to?(:keys) && ( - (args[0].key?(:format) && args[0].keys.size == 1) || - args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) } - ) - end - - def non_kwarg_request_warning - ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc) - ActionController::TestCase HTTP request methods will accept only - keyword arguments in future Rails versions. - - Examples: - - get :show, params: { id: 1 }, session: { user_id: 1 } - process :update, method: :post, params: { id: 1 } - MSG - end - def document_root_element - html_document.root - end + def document_root_element + html_document.root + end - def check_required_ivars - # Sanity check for required instance variables so we can give an - # understandable error message. - [:@routes, :@controller, :@request, :@response].each do |iv_name| - if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil? - raise "#{iv_name} is nil: make sure you set it in your test's setup method." + def check_required_ivars + # Sanity check for required instance variables so we can give an + # understandable error message. + [:@routes, :@controller, :@request, :@response].each do |iv_name| + if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil? + raise "#{iv_name} is nil: make sure you set it in your test's setup method." + end end end - end - - def html_format?(parameters) - return true unless parameters.key?(:format) - Mime.fetch(parameters[:format]) { Mime['html'] }.html? - end end include Behavior end - # :startdoc: end diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 1e4df07d6e..7931e53c3e 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -21,15 +21,15 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ -require 'active_support' -require 'active_support/rails' -require 'active_support/core_ext/module/attribute_accessors' +require "active_support" +require "active_support/rails" +require "active_support/core_ext/module/attribute_accessors" -require 'action_pack' -require 'rack' +require "action_pack" +require "rack" module Rack - autoload :Test, 'rack/test' + autoload :Test, "rack/test" end module ActionDispatch @@ -39,21 +39,21 @@ module ActionDispatch end eager_autoload do - autoload_under 'http' do + autoload_under "http" do autoload :Request autoload :Response end end - autoload_under 'middleware' do + autoload_under "middleware" do autoload :RequestId autoload :Callbacks autoload :Cookies autoload :DebugExceptions + autoload :DebugLocks autoload :ExceptionWrapper + autoload :Executor autoload :Flash - autoload :LoadInterlock - autoload :ParamsParser autoload :PublicExceptions autoload :Reloader autoload :RemoteIp @@ -63,7 +63,7 @@ module ActionDispatch end autoload :Journey - autoload :MiddlewareStack, 'action_dispatch/middleware/stack' + autoload :MiddlewareStack, "action_dispatch/middleware/stack" autoload :Routing module Http @@ -75,23 +75,23 @@ module ActionDispatch autoload :Parameters autoload :ParameterFilter autoload :Upload - autoload :UploadedFile, 'action_dispatch/http/upload' + autoload :UploadedFile, "action_dispatch/http/upload" autoload :URL end module Session - autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store' - autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store' - autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store' - autoload :CacheStore, 'action_dispatch/middleware/session/cache_store' + autoload :AbstractStore, "action_dispatch/middleware/session/abstract_store" + autoload :CookieStore, "action_dispatch/middleware/session/cookie_store" + autoload :MemCacheStore, "action_dispatch/middleware/session/mem_cache_store" + autoload :CacheStore, "action_dispatch/middleware/session/cache_store" end mattr_accessor :test_app - autoload_under 'testing' do + autoload_under "testing" do autoload :Assertions autoload :Integration - autoload :IntegrationTest, 'action_dispatch/testing/integration' + autoload :IntegrationTest, "action_dispatch/testing/integration" autoload :TestProcess autoload :TestRequest autoload :TestResponse @@ -99,7 +99,7 @@ module ActionDispatch end end -autoload :Mime, 'action_dispatch/http/mime_type' +autoload :Mime, "action_dispatch/http/mime_type" ActiveSupport.on_load(:action_view) do ActionView::Base.default_formats ||= Mime::SET.symbols diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index 4bd727c14e..985e0fb972 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -2,9 +2,8 @@ module ActionDispatch module Http module Cache module Request - - HTTP_IF_MODIFIED_SINCE = 'HTTP_IF_MODIFIED_SINCE'.freeze - HTTP_IF_NONE_MATCH = 'HTTP_IF_NONE_MATCH'.freeze + HTTP_IF_MODIFIED_SINCE = "HTTP_IF_MODIFIED_SINCE".freeze + HTTP_IF_NONE_MATCH = "HTTP_IF_NONE_MATCH".freeze def if_modified_since if since = get_header(HTTP_IF_MODIFIED_SINCE) @@ -17,9 +16,7 @@ module ActionDispatch end def if_none_match_etags - (if_none_match ? if_none_match.split(/\s*,\s*/) : []).collect do |etag| - etag.gsub(/^\"|\"$/, "") - end + if_none_match ? if_none_match.split(/\s*,\s*/) : [] end def not_modified?(modified_at) @@ -28,8 +25,8 @@ module ActionDispatch def etag_matches?(etag) if etag - etag = etag.gsub(/^\"|\"$/, "") - if_none_match_etags.include?(etag) + validators = if_none_match_etags + validators.include?(etag) || validators.include?("*") end end @@ -80,30 +77,66 @@ module ActionDispatch set_header DATE, utc_time.httpdate end - # This method allows you to set the ETag for cached content, which - # will be returned to the end user. + # This method sets a weak ETag validator on the response so browsers + # and proxies may cache the response, keyed on the ETag. On subsequent + # requests, the If-None-Match header is set to the cached ETag. If it + # matches the current ETag, we can return a 304 Not Modified response + # with no body, letting the browser or proxy know that their cache is + # current. Big savings in request time and network bandwidth. + # + # Weak ETags are considered to be semantically equivalent but not + # byte-for-byte identical. This is perfect for browser caching of HTML + # pages where we don't care about exact equality, just what the user + # is viewing. + # + # Strong ETags are considered byte-for-byte identical. They allow a + # browser or proxy cache to support Range requests, useful for paging + # through a PDF file or scrubbing through a video. Some CDNs only + # support strong ETags and will ignore weak ETags entirely. # - # By default, Action Dispatch sets all ETags to be weak. - # This ensures that if the content changes only semantically, - # the whole page doesn't have to be regenerated from scratch - # by the web server. With strong ETags, pages are compared - # byte by byte, and are regenerated only if they are not exactly equal. - def etag=(etag) - key = ActiveSupport::Cache.expand_cache_key(etag) - super %(W/"#{Digest::MD5.hexdigest(key)}") + # Weak ETags are what we almost always need, so they're the default. + # Check out `#strong_etag=` to provide a strong ETag validator. + def etag=(weak_validators) + self.weak_etag = weak_validators + end + + def weak_etag=(weak_validators) + set_header "ETag", generate_weak_etag(weak_validators) + end + + def strong_etag=(strong_validators) + set_header "ETag", generate_strong_etag(strong_validators) end def etag?; etag; end + # True if an ETag is set and it's a weak validator (preceded with W/) + def weak_etag? + etag? && etag.starts_with?('W/"') + end + + # True if an ETag is set and it isn't a weak validator (not preceded with W/) + def strong_etag? + etag? && !weak_etag? + end + private - DATE = 'Date'.freeze + DATE = "Date".freeze LAST_MODIFIED = "Last-Modified".freeze SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public private must-revalidate]) + def generate_weak_etag(validators) + "W/#{generate_strong_etag(validators)}" + end + + def generate_strong_etag(validators) + %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(validators))}") + end + def cache_control_segments if cache_control = _cache_control - cache_control.delete(' ').split(',') + cache_control.delete(" ").split(",") else [] end @@ -113,10 +146,10 @@ module ActionDispatch cache_control = {} cache_control_segments.each do |segment| - directive, argument = segment.split('=', 2) + directive, argument = segment.split("=", 2) if SPECIAL_KEYS.include? directive - key = directive.tr('-', '_') + key = directive.tr("-", "_") cache_control[key.to_sym] = argument || true else cache_control[:extras] ||= [] diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index 9dcab79c3a..e5874a39f6 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -1,12 +1,14 @@ -require 'action_dispatch/http/parameter_filter' +require "action_dispatch/http/parameter_filter" module ActionDispatch module Http # Allows you to specify sensitive parameters which will be replaced from # the request log by looking in the query string of the request and all - # sub-hashes of the params hash to filter. If a block is given, each key and - # value of the params hash and all sub-hashes is passed to it, the value - # or key can be replaced using String#replace or similar method. + # sub-hashes of the params hash to filter. Filtering only certain sub-keys + # from a hash is possible by using the dot notation: 'credit_card.number'. + # If a block is given, each key and value of the params hash and all + # sub-hashes is passed to it, the value or key can be replaced using + # String#replace or similar method. # # env["action_dispatch.parameter_filter"] = [:password] # => replaces the value to all keys matching /password/i with "[FILTERED]" @@ -14,6 +16,10 @@ module ActionDispatch # env["action_dispatch.parameter_filter"] = [:foo, "bar"] # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]" # + # env["action_dispatch.parameter_filter"] = [ "credit_card.code" ] + # => replaces { credit_card: {code: "xxxx"} } with "[FILTERED]", does not + # change { file: { code: "xxxx"} } + # # env["action_dispatch.parameter_filter"] = -> (k, v) do # v.reverse! if k =~ /secret/i # end @@ -64,7 +70,7 @@ module ActionDispatch ParameterFilter.new(filters) end - KV_RE = '[^&;=]+' + KV_RE = "[^&;=]+" PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})} def filtered_query_string query_string.gsub(PAIR_RE) do |_| diff --git a/actionpack/lib/action_dispatch/http/filter_redirect.rb b/actionpack/lib/action_dispatch/http/filter_redirect.rb index f4b806b8b5..fc3c44582a 100644 --- a/actionpack/lib/action_dispatch/http/filter_redirect.rb +++ b/actionpack/lib/action_dispatch/http/filter_redirect.rb @@ -1,8 +1,7 @@ module ActionDispatch module Http module FilterRedirect - - FILTERED = '[FILTERED]'.freeze # :nodoc: + FILTERED = "[FILTERED]".freeze # :nodoc: def filtered_location # :nodoc: if location_filter_match? @@ -16,7 +15,7 @@ module ActionDispatch def location_filters if request - request.get_header('action_dispatch.redirect_filter') || [] + request.get_header("action_dispatch.redirect_filter") || [] else [] end @@ -31,7 +30,6 @@ module ActionDispatch end end end - end end end diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb index 8e899174c6..3c03976f03 100644 --- a/actionpack/lib/action_dispatch/http/headers.rb +++ b/actionpack/lib/action_dispatch/http/headers.rb @@ -3,9 +3,9 @@ module ActionDispatch # Provides access to the request's HTTP headers from the environment. # # env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" } - # headers = ActionDispatch::Http::Headers.new(env) + # headers = ActionDispatch::Http::Headers.from_hash(env) # headers["Content-Type"] # => "text/plain" - # headers["User-Agent"] # => "curl/7/43/0" + # headers["User-Agent"] # => "curl/7.43.0" # # Also note that when headers are mapped to CGI-like variables by the Rack # server, both dashes and underscores are converted to underscores. This @@ -86,7 +86,7 @@ module ActionDispatch @req.fetch_header(env_name(key)) do return default unless default == DEFAULT return yield if block_given? - raise NameError, key + raise KeyError, key end end @@ -115,16 +115,16 @@ module ActionDispatch private - # Converts a HTTP header name to an environment variable name if it is - # not contained within the headers hash. - def env_name(key) - key = key.to_s - if key =~ HTTP_HEADER - key = key.upcase.tr('-', '_') - key = "HTTP_" + key unless CGI_VARIABLES.include?(key) + # Converts an HTTP header name to an environment variable name if it is + # not contained within the headers hash. + def env_name(key) + key = key.to_s + if key =~ HTTP_HEADER + key = key.upcase.tr("-", "_") + key = "HTTP_" + key unless CGI_VARIABLES.include?(key) + end + key end - key - end end end end diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index e9b25339dc..d0c9413efa 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/module/attribute_accessors' +require "active_support/core_ext/module/attribute_accessors" module ActionDispatch module Http @@ -16,7 +16,7 @@ module ActionDispatch # X-Post-Data-Format HTTP header if present. def content_mime_type fetch_header("action_dispatch.request.content_type") do |k| - v = if get_header('CONTENT_TYPE') =~ /^([^,\;]*)/ + v = if get_header("CONTENT_TYPE") =~ /^([^,\;]*)/ Mime::Type.lookup($1.strip.downcase) else nil @@ -30,13 +30,13 @@ module ActionDispatch end def has_content_type? - has_header? 'CONTENT_TYPE' + has_header? "CONTENT_TYPE" end # Returns the accepted MIME type for the request. def accepts fetch_header("action_dispatch.request.accepts") do |k| - header = get_header('HTTP_ACCEPT').to_s.strip + header = get_header("HTTP_ACCEPT").to_s.strip v = if header.empty? [content_mime_type] @@ -152,23 +152,23 @@ module ActionDispatch protected - BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/ + BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/ - def valid_accept_header - (xhr? && (accept.present? || content_mime_type)) || - (accept.present? && accept !~ BROWSER_LIKE_ACCEPTS) - end + def valid_accept_header + (xhr? && (accept.present? || content_mime_type)) || + (accept.present? && accept !~ BROWSER_LIKE_ACCEPTS) + end - def use_accept_header - !self.class.ignore_accept_header - end + def use_accept_header + !self.class.ignore_accept_header + end - def format_from_path_extension - path = @env['action_dispatch.original_path'] || @env['PATH_INFO'] - if match = path && path.match(/\.(\w+)\z/) - Mime[match.captures.first] + def format_from_path_extension + path = get_header("action_dispatch.original_path") || get_header("PATH_INFO") + if match = path && path.match(/\.(\w+)\z/) + Mime[match.captures.first] + end end - end end end end diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 4672ea7199..58eb8d0baf 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -1,8 +1,8 @@ # -*- frozen-string-literal: true -*- -require 'singleton' -require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/core_ext/string/starts_ends_with' +require "singleton" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/string/starts_ends_with" module Mime class Mimes @@ -45,32 +45,6 @@ module Mime return type if type.is_a?(Type) EXTENSION_LOOKUP.fetch(type.to_s) { |k| yield k } end - - def const_missing(sym) - ext = sym.downcase - if Mime[ext] - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Accessing mime types via constants is deprecated. - Please change `Mime::#{sym}` to `Mime[:#{ext}]`. - MSG - Mime[ext] - else - super - end - end - - def const_defined?(sym, inherit = true) - ext = sym.downcase - if Mime[ext] - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Accessing mime types via constants is deprecated. - Please change `Mime.const_defined?(#{sym})` to `Mime[:#{ext}]`. - MSG - true - else - super - end - end end # Encapsulates the notion of a mime type. Can be used at render time, for example, with: @@ -99,7 +73,7 @@ module Mime def initialize(index, name, q = nil) @index = index @name = name - q ||= 0.0 if @name == '*/*'.freeze # default wildcard match to end of list + q ||= 0.0 if @name == "*/*".freeze # default wildcard match to end of list @q = ((q || 1.0).to_f * 100).to_i end @@ -114,7 +88,7 @@ module Mime def self.sort!(list) list.sort! - text_xml_idx = find_item_by_name list, 'text/xml' + text_xml_idx = find_item_by_name list, "text/xml" app_xml_idx = find_item_by_name list, Mime[:xml].to_s # Take care of the broken text/xml entry by renaming or deleting it @@ -141,7 +115,7 @@ module Mime type = list[idx] break if type.q < app_xml.q - if type.name.ends_with? '+xml' + if type.name.ends_with? "+xml" list[app_xml_idx], list[idx] = list[idx], app_xml app_xml_idx = idx end @@ -195,12 +169,12 @@ module Mime end def parse(accept_header) - if !accept_header.include?(',') + if !accept_header.include?(",") accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)].compact else list, index = [], 0 - accept_header.split(',').each do |header| + accept_header.split(",").each do |header| params, q = header.split(PARAMETER_SEPARATOR_REGEXP) next unless params @@ -306,31 +280,31 @@ module Mime protected - attr_reader :string, :synonyms + attr_reader :string, :synonyms private - def to_ary; end - def to_a; end + def to_ary; end + def to_a; end - def method_missing(method, *args) - if method.to_s.ends_with? '?' - method[0..-2].downcase.to_sym == to_sym - else - super + def method_missing(method, *args) + if method.to_s.ends_with? "?" + method[0..-2].downcase.to_sym == to_sym + else + super + end end - end - def respond_to_missing?(method, include_private = false) #:nodoc: - method.to_s.ends_with? '?' - end + def respond_to_missing?(method, include_private = false) #:nodoc: + method.to_s.ends_with? "?" + end end class AllType < Type include Singleton def initialize - super '*/*', :all + super "*/*", :all end def all?; true; end @@ -352,14 +326,14 @@ module Mime def ref; end def respond_to_missing?(method, include_private = false) - method.to_s.ends_with? '?' + method.to_s.ends_with? "?" end private - def method_missing(method, *args) - false if method.to_s.ends_with? '?' - end + def method_missing(method, *args) + false if method.to_s.ends_with? "?" + end end end -require 'action_dispatch/http/mime_types' +require "action_dispatch/http/mime_types" diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb index 8356d1a238..8b04174f1f 100644 --- a/actionpack/lib/action_dispatch/http/mime_types.rb +++ b/actionpack/lib/action_dispatch/http/mime_types.rb @@ -21,14 +21,15 @@ Mime::Type.register "video/mpeg", :mpeg, [], %w(mpg mpeg mpe) Mime::Type.register "application/xml", :xml, %w( text/xml application/x-xml ) Mime::Type.register "application/rss+xml", :rss Mime::Type.register "application/atom+xml", :atom -Mime::Type.register "application/x-yaml", :yaml, %w( text/yaml ) +Mime::Type.register "application/x-yaml", :yaml, %w( text/yaml ), %w(yml yaml) Mime::Type.register "multipart/form-data", :multipart_form Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form # http://www.ietf.org/rfc/rfc4627.txt # http://www.json.org/JSONRequest.html -Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest application/vnd.api+json ) +Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest ) Mime::Type.register "application/pdf", :pdf, [], %w(pdf) Mime::Type.register "application/zip", :zip, [], %w(zip) +Mime::Type.register "application/gzip", :gzip, %w(application/x-gzip), %w(gz) diff --git a/actionpack/lib/action_dispatch/http/parameter_filter.rb b/actionpack/lib/action_dispatch/http/parameter_filter.rb index e826551f4b..01fe35f5c6 100644 --- a/actionpack/lib/action_dispatch/http/parameter_filter.rb +++ b/actionpack/lib/action_dispatch/http/parameter_filter.rb @@ -1,7 +1,9 @@ +require "active_support/core_ext/object/duplicable" + module ActionDispatch module Http class ParameterFilter - FILTERED = '[FILTERED]'.freeze # :nodoc: + FILTERED = "[FILTERED]".freeze # :nodoc: def initialize(filters = []) @filters = filters @@ -37,8 +39,8 @@ module ActionDispatch deep_regexps, regexps = regexps.partition { |r| r.to_s.include?("\\.".freeze) } deep_strings, strings = strings.partition { |s| s.include?("\\.".freeze) } - regexps << Regexp.new(strings.join('|'.freeze), true) unless strings.empty? - deep_regexps << Regexp.new(deep_strings.join('|'.freeze), true) unless deep_strings.empty? + regexps << Regexp.new(strings.join("|".freeze), true) unless strings.empty? + deep_regexps << Regexp.new(deep_strings.join("|".freeze), true) unless deep_strings.empty? new regexps, deep_regexps, blocks end @@ -58,7 +60,7 @@ module ActionDispatch parents.push(key) if deep_regexps if regexps.any? { |r| key =~ r } value = FILTERED - elsif deep_regexps && (joined = parents.join('.')) && deep_regexps.any? { |r| joined =~ r } + elsif deep_regexps && (joined = parents.join(".")) && deep_regexps.any? { |r| joined =~ r } value = FILTERED elsif value.is_a?(Hash) value = call(value, parents) diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index cca7376ffa..ddd15b748b 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -1,22 +1,39 @@ module ActionDispatch module Http module Parameters - PARAMETERS_KEY = 'action_dispatch.request.path_parameters' + extend ActiveSupport::Concern + + PARAMETERS_KEY = "action_dispatch.request.path_parameters" DEFAULT_PARSERS = { - Mime[:json] => lambda { |raw_post| + Mime[:json].symbol => -> (raw_post) { data = ActiveSupport::JSON.decode(raw_post) - data.is_a?(Hash) ? data : {:_json => data} + data.is_a?(Hash) ? data : { _json: data } } } - def self.included(klass) - class << klass - attr_accessor :parameter_parsers + # Raised when raw data from the request cannot be parsed by the parser + # defined for request's content mime type. + class ParseError < StandardError + def initialize + super($!.message) end + end + + included do + class << self + attr_reader :parameter_parsers + end + + self.parameter_parsers = DEFAULT_PARSERS + end - klass.parameter_parsers = DEFAULT_PARSERS + module ClassMethods + def parameter_parsers=(parsers) # :nodoc: + @parameter_parsers = parsers.transform_keys { |key| key.respond_to?(:symbol) ? key.symbol : key } + end end + # Returns both GET and POST \parameters in a single hash. def parameters params = get_header("action_dispatch.request.parameters") @@ -28,14 +45,22 @@ module ActionDispatch query_parameters.dup end params.merge!(path_parameters) + params = set_custom_encoding(params) set_header("action_dispatch.request.parameters", params) params end alias :params :parameters def path_parameters=(parameters) #:nodoc: - delete_header('action_dispatch.request.parameters') + delete_header("action_dispatch.request.parameters") + + # If any of the path parameters has an invalid encoding then + # raise since it's likely to trigger errors further on. + Request::Utils.check_param_encoding(parameters) + set_header PARAMETERS_KEY, parameters + rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e + raise ActionController::BadRequest.new("Invalid path parameters: #{e.message}") end # Returns a hash with the \parameters used to form the \path of the request. @@ -48,24 +73,43 @@ module ActionDispatch private - def parse_formatted_parameters(parsers) - return yield if content_length.zero? + def set_custom_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)) + end + end - strategy = parsers.fetch(content_mime_type) { return yield } + 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? + + strategy = parsers.fetch(content_mime_type.symbol) { return yield } - begin - strategy.call(raw_post) - rescue # JSON or Ruby code block errors - my_logger = logger || ActiveSupport::Logger.new($stderr) - my_logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{raw_post}" + begin + strategy.call(raw_post) + rescue # JSON or Ruby code block errors + my_logger = logger || ActiveSupport::Logger.new($stderr) + my_logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{raw_post}" - raise ParamsParser::ParseError + raise ParseError + end end - end - def params_parsers - ActionDispatch::Request.parameter_parsers - end + def params_parsers + ActionDispatch::Request.parameter_parsers + end end end + + module ParamsParser + ParseError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("ActionDispatch::ParamsParser::ParseError", "ActionDispatch::Http::Parameters::ParseError") + end end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 5427425ef7..9986d6e1e9 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -1,16 +1,16 @@ -require 'stringio' - -require 'active_support/inflector' -require 'action_dispatch/http/headers' -require 'action_controller/metal/exceptions' -require 'rack/request' -require 'action_dispatch/http/cache' -require 'action_dispatch/http/mime_negotiation' -require 'action_dispatch/http/parameters' -require 'action_dispatch/http/filter_parameters' -require 'action_dispatch/http/upload' -require 'action_dispatch/http/url' -require 'active_support/core_ext/array/conversions' +require "stringio" + +require "active_support/inflector" +require "action_dispatch/http/headers" +require "action_controller/metal/exceptions" +require "rack/request" +require "action_dispatch/http/cache" +require "action_dispatch/http/mime_negotiation" +require "action_dispatch/http/parameters" +require "action_dispatch/http/filter_parameters" +require "action_dispatch/http/upload" +require "action_dispatch/http/url" +require "active_support/core_ext/array/conversions" module ActionDispatch class Request @@ -22,8 +22,8 @@ module ActionDispatch include ActionDispatch::Http::URL include Rack::Request::Env - autoload :Session, 'action_dispatch/request/session' - autoload :Utils, 'action_dispatch/request/utils' + autoload :Session, "action_dispatch/request/session" + autoload :Utils, "action_dispatch/request/utils" LOCALHOST = Regexp.union [/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/] @@ -66,29 +66,18 @@ module ActionDispatch def commit_cookie_jar! # :nodoc: end - def check_path_parameters! - # If any of the path parameters has an invalid encoding then - # raise since it's likely to trigger errors further on. - path_parameters.each do |key, value| - next unless value.respond_to?(:valid_encoding?) - unless value.valid_encoding? - raise ActionController::BadRequest, "Invalid parameter encoding: #{key} => #{value.inspect}" - end - end - end - PASS_NOT_FOUND = Class.new { # :nodoc: def self.action(_); self; end - def self.call(_); [404, {'X-Cascade' => 'pass'}, []]; end + def self.call(_); [404, { "X-Cascade" => "pass" }, []]; end + def self.encoding_for_param(action, param); ::Encoding::UTF_8; end } def controller_class - check_path_parameters! params = path_parameters if params.key?(:controller) controller_param = params[:controller].underscore - params[:action] ||= 'index' + params[:action] ||= "index" const_name = "#{controller_param.camelize}Controller" ActiveSupport::Dependencies.constantize(const_name) else @@ -160,11 +149,11 @@ module ActionDispatch end def controller_instance # :nodoc: - get_header('action_controller.instance'.freeze) + get_header("action_controller.instance".freeze) end def controller_instance=(controller) # :nodoc: - set_header('action_controller.instance'.freeze, controller) + set_header("action_controller.instance".freeze, controller) end def http_auth_salt @@ -175,7 +164,7 @@ module ActionDispatch # We're treating `nil` as "unset", and we want the default setting to be # `true`. This logic should be extracted to `env_config` and calculated # once. - !(get_header('action_dispatch.show_exceptions'.freeze) == false) + !(get_header("action_dispatch.show_exceptions".freeze) == false) end # Returns a symbol form of the #request_method @@ -187,7 +176,7 @@ module ActionDispatch # even if it was overridden by middleware. See #request_method for # more information. def method - @method ||= check_method(get_header("rack.methodoverride.original_method") || get_header('REQUEST_METHOD')) + @method ||= check_method(get_header("rack.methodoverride.original_method") || get_header("REQUEST_METHOD")) end # Returns a symbol form of the #method @@ -249,7 +238,7 @@ module ActionDispatch # (case-insensitive), which may need to be manually added depending on the # choice of JavaScript libraries and frameworks. def xml_http_request? - get_header('HTTP_X_REQUESTED_WITH') =~ /XMLHttpRequest/i + get_header("HTTP_X_REQUESTED_WITH") =~ /XMLHttpRequest/i end alias :xhr? :xml_http_request? @@ -288,24 +277,24 @@ module ActionDispatch # Returns the lowercase name of the HTTP server software. def server_software - (get_header('SERVER_SOFTWARE') && /^([a-zA-Z]+)/ =~ get_header('SERVER_SOFTWARE')) ? $1.downcase : nil + (get_header("SERVER_SOFTWARE") && /^([a-zA-Z]+)/ =~ get_header("SERVER_SOFTWARE")) ? $1.downcase : nil end # Read the request \body. This is useful for web services that need to # work with raw requests directly. def raw_post - unless has_header? 'RAW_POST_DATA' + unless has_header? "RAW_POST_DATA" raw_post_body = body - set_header('RAW_POST_DATA', raw_post_body.read(content_length)) + set_header("RAW_POST_DATA", raw_post_body.read(content_length)) raw_post_body.rewind if raw_post_body.respond_to?(:rewind) end - get_header 'RAW_POST_DATA' + get_header "RAW_POST_DATA" end # The request body is an IO input stream. If the RAW_POST_DATA environment # variable is already set, wrap it in a StringIO. def body - if raw_post = get_header('RAW_POST_DATA') + if raw_post = get_header("RAW_POST_DATA") raw_post.force_encoding(Encoding::BINARY) StringIO.new(raw_post) else @@ -326,7 +315,7 @@ module ActionDispatch end def body_stream #:nodoc: - get_header('rack.input') + get_header("rack.input") end # TODO This should be broken apart into AD::Request::Session and probably @@ -337,7 +326,6 @@ module ActionDispatch else self.session = {} end - self.flash = nil end def session=(session) #:nodoc: @@ -369,7 +357,7 @@ module ActionDispatch end self.request_parameters = Request::Utils.normalize_encode_params(pr) end - rescue ParamsParser::ParseError # one of the parse strategies blew up + rescue Http::Parameters::ParseError # one of the parse strategies blew up self.request_parameters = Request::Utils.normalize_encode_params(super || {}) raise rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e @@ -380,10 +368,10 @@ module ActionDispatch # Returns the authorization header regardless of whether it was specified directly or through one of the # proxy alternatives. def authorization - get_header('HTTP_AUTHORIZATION') || - get_header('X-HTTP_AUTHORIZATION') || - get_header('X_HTTP_AUTHORIZATION') || - get_header('REDIRECT_X_HTTP_AUTHORIZATION') + get_header("HTTP_AUTHORIZATION") || + get_header("X-HTTP_AUTHORIZATION") || + get_header("X_HTTP_AUTHORIZATION") || + get_header("REDIRECT_X_HTTP_AUTHORIZATION") end # True if the request came from localhost, 127.0.0.1, or ::1. @@ -403,6 +391,10 @@ module ActionDispatch def commit_flash end + def ssl? + super || scheme == "wss".freeze + end + private def check_method(name) HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS[0...-1].join(', ')}, and #{HTTP_METHODS[-1]}") diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index fa4c54701a..e8173e2a99 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -1,7 +1,7 @@ -require 'active_support/core_ext/module/attribute_accessors' -require 'action_dispatch/http/filter_redirect' -require 'action_dispatch/http/cache' -require 'monitor' +require "active_support/core_ext/module/attribute_accessors" +require "action_dispatch/http/filter_redirect" +require "action_dispatch/http/cache" +require "monitor" module ActionDispatch # :nodoc: # Represents an HTTP response generated by a controller action. Use it to @@ -41,7 +41,7 @@ module ActionDispatch # :nodoc: def []=(k,v) if @response.sending? || @response.sent? - raise ActionDispatch::IllegalStateError, 'header already sent' + raise ActionDispatch::IllegalStateError, "header already sent" end super @@ -67,8 +67,14 @@ module ActionDispatch # :nodoc: alias_method :headers, :header - delegate :[], :[]=, :to => :@header - delegate :each, :to => :@stream + delegate :[], :[]=, to: :@header + + def each(&block) + sending! + x = @stream.each(&block) + sent! + x + end CONTENT_TYPE = "Content-Type".freeze SET_COOKIE = "Set-Cookie".freeze @@ -97,10 +103,10 @@ module ActionDispatch # :nodoc: def body @str_body ||= begin - buf = '' - each { |chunk| buf << chunk } - buf - end + buf = "" + each { |chunk| buf << chunk } + buf + end end def write(string) @@ -112,10 +118,13 @@ module ActionDispatch # :nodoc: end def each(&block) - @response.sending! - x = @buf.each(&block) - @response.sent! - x + if @str_body + return enum_for(:each) unless block_given? + + yield @str_body + else + each_chunk(&block) + end end def abort @@ -129,6 +138,12 @@ module ActionDispatch # :nodoc: def closed? @closed end + + private + + def each_chunk(&block) + @buf.each(&block) # extract into own method + end end def self.create(status = 200, header = {}, body = [], default_headers: self.default_headers) @@ -209,8 +224,10 @@ module ActionDispatch # :nodoc: # Sets the HTTP content type. def content_type=(content_type) - header_info = parse_content_type - set_content_type content_type.to_s, header_info.charset || self.class.default_charset + 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 end # Sets the HTTP response's content MIME type. For example, in the controller @@ -223,7 +240,7 @@ module ActionDispatch # :nodoc: # information. def content_type - parse_content_type.mime_type + parsed_content_type_header.mime_type end def sending_file=(v) @@ -238,7 +255,7 @@ module ActionDispatch # :nodoc: # response.charset = 'utf-16' # => 'utf-16' # response.charset = nil # => 'utf-8' def charset=(charset) - header_info = parse_content_type + header_info = parsed_content_type_header if false == charset set_header CONTENT_TYPE, header_info.mime_type else @@ -250,7 +267,7 @@ module ActionDispatch # :nodoc: # The charset of the response. HTML wants to know the encoding of the # content you're giving them, so we need to send that along. def charset - header_info = parse_content_type + header_info = parsed_content_type_header header_info.charset || self.class.default_charset end @@ -314,7 +331,7 @@ module ActionDispatch # :nodoc: # Stream the file's contents if Rack::Sendfile isn't present. def each - File.open(to_path, 'rb') do |file| + File.open(to_path, "rb") do |file| while chunk = file.read(16384) yield chunk end @@ -374,7 +391,7 @@ module ActionDispatch # :nodoc: if header = get_header(SET_COOKIE) header = header.split("\n") if header.respond_to?(:to_str) header.each do |cookie| - if pair = cookie.split(';').first + if pair = cookie.split(";").first key, value = pair.split("=").map { |v| Rack::Utils.unescape(v) } cookies[key] = value end @@ -388,8 +405,7 @@ module ActionDispatch # :nodoc: ContentTypeHeader = Struct.new :mime_type, :charset NullContentTypeHeader = ContentTypeHeader.new nil, nil - def parse_content_type - content_type = get_header CONTENT_TYPE + def parse_content_type(content_type) if content_type type, charset = content_type.split(/;\s*charset=/) type = nil if type.empty? @@ -399,8 +415,14 @@ module ActionDispatch # :nodoc: end end + # Small internal convenience method to get the parsed version of the current + # content type header. + def parsed_content_type_header + parse_content_type(get_header(CONTENT_TYPE)) + end + def set_content_type(content_type, charset) - type = (content_type || '').dup + type = (content_type || "").dup type << "; charset=#{charset}" if charset set_header CONTENT_TYPE, type end @@ -435,7 +457,7 @@ module ActionDispatch # :nodoc: def assign_default_content_type_and_charset! return if content_type - ct = parse_content_type + ct = parsed_content_type_header set_content_type(ct.mime_type || Mime[:html].to_s, ct.charset || self.class.default_charset) end @@ -460,7 +482,7 @@ module ActionDispatch # :nodoc: end def respond_to?(method, include_private = false) - if method.to_s == 'to_path' + if method.to_s == "to_path" @response.stream.respond_to?(method) else super @@ -479,7 +501,7 @@ module ActionDispatch # :nodoc: def handle_no_content! if NO_CONTENT_CODES.include?(@status) @header.delete CONTENT_TYPE - @header.delete 'Content-Length' + @header.delete "Content-Length" end end diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index a221f4c5af..9aa73c862b 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -25,7 +25,7 @@ module ActionDispatch def initialize(hash) # :nodoc: @tempfile = hash[:tempfile] - raise(ArgumentError, ':tempfile is required') unless @tempfile + raise(ArgumentError, ":tempfile is required") unless @tempfile @original_filename = hash[:filename] if @original_filename diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 37f41ae988..06ffa983d1 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/module/attribute_accessors' +require "active_support/core_ext/module/attribute_accessors" module ActionDispatch module Http @@ -42,7 +42,7 @@ module ActionDispatch # # Second-level domain example # extract_subdomain('dev.www.example.co.uk', 2) # => "dev.www" def extract_subdomain(host, tld_length) - extract_subdomains(host, tld_length).join('.') + extract_subdomains(host, tld_length).join(".") end def url_for(options) @@ -59,7 +59,7 @@ module ActionDispatch port = options[:port] unless host - raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true' + raise ArgumentError, "Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true" end build_host_url(host, port, protocol, options, path_for(options)) @@ -92,17 +92,17 @@ module ActionDispatch end def extract_domain_from(host, tld_length) - host.split('.').last(1 + tld_length).join('.') + host.split(".").last(1 + tld_length).join(".") end def extract_subdomains_from(host, tld_length) - parts = host.split('.') + parts = host.split(".") parts[0..-(tld_length + 2)] end def add_trailing_slash(path) # includes querysting - if path.include?('?') + if path.include?("?") path.sub!(/\?/, '/\&') # does not have a .format elsif !path.include?(".") @@ -162,7 +162,7 @@ module ActionDispatch if subdomain == true return _host if domain.nil? - host << extract_subdomains_from(_host, tld_length).join('.') + host << extract_subdomains_from(_host, tld_length).join(".") elsif subdomain host << subdomain.to_param end @@ -192,11 +192,7 @@ module ActionDispatch # Returns the complete URL used for this request. # - # class Request < Rack::Request - # include ActionDispatch::Http::URL - # end - # - # req = Request.new 'HTTP_HOST' => 'example.com' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' # req.url # => "http://example.com" def url protocol + host_with_port + fullpath @@ -204,61 +200,52 @@ module ActionDispatch # Returns 'https://' if this is an SSL request and 'http://' otherwise. # - # class Request < Rack::Request - # include ActionDispatch::Http::URL - # end - # - # req = Request.new 'HTTP_HOST' => 'example.com' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' # req.protocol # => "http://" # - # req = Request.new 'HTTP_HOST' => 'example.com', 'HTTPS' => 'on' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com', 'HTTPS' => 'on' # req.protocol # => "https://" def protocol - @protocol ||= ssl? ? 'https://' : 'http://' + @protocol ||= ssl? ? "https://" : "http://" end - # Returns the \host for this request, such as "example.com". + # Returns the \host and port for this request, such as "example.com:8080". # - # class Request < Rack::Request - # include ActionDispatch::Http::URL - # end - # - # req = Request.new 'HTTP_HOST' => 'example.com' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' # req.raw_host_with_port # => "example.com" # - # req = Request.new 'HTTP_HOST' => 'example.com:8080' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' + # req.raw_host_with_port # => "example.com:80" + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' # req.raw_host_with_port # => "example.com:8080" def raw_host_with_port if forwarded = x_forwarded_host.presence forwarded.split(/,\s?/).last else - get_header('HTTP_HOST') || "#{server_name || server_addr}:#{get_header('SERVER_PORT')}" + get_header("HTTP_HOST") || "#{server_name || server_addr}:#{get_header('SERVER_PORT')}" end end - # Returns the host for this request, such as example.com. + # Returns the host for this request, such as "example.com". # - # class Request < Rack::Request - # include ActionDispatch::Http::URL - # end - # - # req = Request.new 'HTTP_HOST' => 'example.com:8080' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' # req.host # => "example.com" def host - raw_host_with_port.sub(/:\d+$/, ''.freeze) + raw_host_with_port.sub(/:\d+$/, "".freeze) end # Returns a \host:\port string for this request, such as "example.com" or - # "example.com:8080". + # "example.com:8080". Port is only included if it is not a default port + # (80 or 443) # - # class Request < Rack::Request - # include ActionDispatch::Http::URL - # end + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' + # req.host_with_port # => "example.com" # - # req = Request.new 'HTTP_HOST' => 'example.com:80' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' # req.host_with_port # => "example.com" # - # req = Request.new 'HTTP_HOST' => 'example.com:8080' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' # req.host_with_port # => "example.com:8080" def host_with_port "#{host}#{port_string}" @@ -266,14 +253,10 @@ module ActionDispatch # Returns the port number of this request as an integer. # - # class Request < Rack::Request - # include ActionDispatch::Http::URL - # end - # - # req = Request.new 'HTTP_HOST' => 'example.com' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' # req.port # => 80 # - # req = Request.new 'HTTP_HOST' => 'example.com:8080' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' # req.port # => 8080 def port @port ||= begin @@ -287,29 +270,21 @@ module ActionDispatch # Returns the standard \port number for this request's protocol. # - # class Request < Rack::Request - # include ActionDispatch::Http::URL - # end - # - # req = Request.new 'HTTP_HOST' => 'example.com:8080' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' # req.standard_port # => 80 def standard_port case protocol - when 'https://' then 443 + when "https://" then 443 else 80 end end # Returns whether this request is using the standard port # - # class Request < Rack::Request - # include ActionDispatch::Http::URL - # end - # - # req = Request.new 'HTTP_HOST' => 'example.com:80' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' # req.standard_port? # => true # - # req = Request.new 'HTTP_HOST' => 'example.com:8080' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' # req.standard_port? # => false def standard_port? port == standard_port @@ -318,14 +293,10 @@ module ActionDispatch # Returns a number \port suffix like 8080 if the \port number of this request # is not the default HTTP \port 80 or HTTPS \port 443. # - # class Request < Rack::Request - # include ActionDispatch::Http::URL - # end - # - # req = Request.new 'HTTP_HOST' => 'example.com:80' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' # req.optional_port # => nil # - # req = Request.new 'HTTP_HOST' => 'example.com:8080' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' # req.optional_port # => 8080 def optional_port standard_port? ? nil : port @@ -334,21 +305,24 @@ module ActionDispatch # Returns a string \port suffix, including colon, like ":8080" if the \port # number of this request is not the default HTTP \port 80 or HTTPS \port 443. # - # class Request < Rack::Request - # include ActionDispatch::Http::URL - # end - # - # req = Request.new 'HTTP_HOST' => 'example.com:80' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' # req.port_string # => "" # - # req = Request.new 'HTTP_HOST' => 'example.com:8080' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' # req.port_string # => ":8080" def port_string - standard_port? ? '' : ":#{port}" + standard_port? ? "" : ":#{port}" end + # Returns the requested port, such as 8080, based on SERVER_PORT + # + # req = ActionDispatch::Request.new 'SERVER_PORT' => '80' + # req.server_port # => 80 + # + # req = ActionDispatch::Request.new 'SERVER_PORT' => '8080' + # req.server_port # => 8080 def server_port - get_header('SERVER_PORT').to_i + get_header("SERVER_PORT").to_i end # Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify diff --git a/actionpack/lib/action_dispatch/journey.rb b/actionpack/lib/action_dispatch/journey.rb index ad42713482..d1cfc51f3e 100644 --- a/actionpack/lib/action_dispatch/journey.rb +++ b/actionpack/lib/action_dispatch/journey.rb @@ -1,5 +1,5 @@ -require 'action_dispatch/journey/router' -require 'action_dispatch/journey/gtg/builder' -require 'action_dispatch/journey/gtg/simulator' -require 'action_dispatch/journey/nfa/builder' -require 'action_dispatch/journey/nfa/simulator' +require "action_dispatch/journey/router" +require "action_dispatch/journey/gtg/builder" +require "action_dispatch/journey/gtg/simulator" +require "action_dispatch/journey/nfa/builder" +require "action_dispatch/journey/nfa/simulator" diff --git a/actionpack/lib/action_dispatch/journey/backwards.rb b/actionpack/lib/action_dispatch/journey/backwards.rb deleted file mode 100644 index 3bd20fdf81..0000000000 --- a/actionpack/lib/action_dispatch/journey/backwards.rb +++ /dev/null @@ -1,5 +0,0 @@ -module Rack # :nodoc: - Mount = ActionDispatch::Journey::Router - Mount::RouteSet = ActionDispatch::Journey::Router - Mount::RegexpWithNamedGroups = ActionDispatch::Journey::Path::Pattern -end diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb index 0323360faa..b5a8ca7262 100644 --- a/actionpack/lib/action_dispatch/journey/formatter.rb +++ b/actionpack/lib/action_dispatch/journey/formatter.rb @@ -1,10 +1,11 @@ -require 'action_controller/metal/exceptions' +require "action_controller/metal/exceptions" module ActionDispatch + # :stopdoc: module Journey # The Formatter class is used for formatting URLs. For example, parameters # passed to +url_for+ in Rails will eventually call Formatter#generate. - class Formatter # :nodoc: + class Formatter attr_reader :routes def initialize(routes) @@ -32,15 +33,24 @@ module ActionDispatch defaults = route.defaults required_parts = route.required_parts - parameterized_parts.keep_if do |key, value| - (defaults[key].nil? && value.present?) || value.to_s != defaults[key].to_s || required_parts.include?(key) + + 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 + break if required_parts.include?(key) + + parameterized_parts.delete(key) end return [route.format(parameterized_parts), params] end - message = "No route matches #{Hash[constraints.sort_by{|k,v| k.to_s}].inspect}" - message << " missing required keys: #{missing_keys.sort.inspect}" if missing_keys && !missing_keys.empty? + unmatched_keys = (missing_keys || []) & constraints.keys + missing_keys = (missing_keys || []) - unmatched_keys + + message = "No route matches #{Hash[constraints.sort_by { |k,v| k.to_s }].inspect}" + message << ", missing required keys: #{missing_keys.sort.inspect}" if missing_keys && !missing_keys.empty? + message << ", possible unmatched constraints: #{unmatched_keys.sort.inspect}" if unmatched_keys && !unmatched_keys.empty? raise ActionController::UrlGenerationError, message end @@ -169,4 +179,5 @@ module ActionDispatch end end end + # :startdoc: end diff --git a/actionpack/lib/action_dispatch/journey/gtg/builder.rb b/actionpack/lib/action_dispatch/journey/gtg/builder.rb index 450588cda6..9990c66627 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/builder.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/builder.rb @@ -1,4 +1,4 @@ -require 'action_dispatch/journey/gtg/transition_table' +require "action_dispatch/journey/gtg/transition_table" module ActionDispatch module Journey # :nodoc: @@ -75,7 +75,7 @@ module ActionDispatch when Nodes::Unary nullable?(node.left) else - raise ArgumentError, 'unknown nullable: %s' % node.class.name + raise ArgumentError, "unknown nullable: %s" % node.class.name end end @@ -96,7 +96,7 @@ module ActionDispatch when Nodes::Terminal nullable?(node) ? [] : [node] else - raise ArgumentError, 'unknown firstpos: %s' % node.class.name + raise ArgumentError, "unknown firstpos: %s" % node.class.name end end @@ -117,7 +117,7 @@ module ActionDispatch when Nodes::Unary lastpos(node.left) else - raise ArgumentError, 'unknown lastpos: %s' % node.class.name + raise ArgumentError, "unknown lastpos: %s" % node.class.name end end diff --git a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb index 94b0a24344..d692f6415c 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb @@ -1,4 +1,4 @@ -require 'strscan' +require "strscan" module ActionDispatch module Journey # :nodoc: diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb index d7ce6042c2..0be18dc26f 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb @@ -1,4 +1,4 @@ -require 'action_dispatch/journey/nfa/dot' +require "action_dispatch/journey/nfa/dot" module ActionDispatch module Journey # :nodoc: @@ -72,20 +72,20 @@ module ActionDispatch end def to_svg - svg = IO.popen('dot -Tsvg', 'w+') { |f| + svg = IO.popen("dot -Tsvg", "w+") { |f| f.write(to_dot) f.close_write f.readlines } 3.times { svg.shift } - svg.join.sub(/width="[^"]*"/, '').sub(/height="[^"]*"/, '') + svg.join.sub(/width="[^"]*"/, "").sub(/height="[^"]*"/, "") end - def visualizer(paths, title = 'FSM') - viz_dir = File.join File.dirname(__FILE__), '..', 'visualizer' - fsm_js = File.read File.join(viz_dir, 'fsm.js') - fsm_css = File.read File.join(viz_dir, 'fsm.css') - erb = File.read File.join(viz_dir, 'index.html.erb') + def visualizer(paths, title = "FSM") + viz_dir = File.join File.dirname(__FILE__), "..", "visualizer" + fsm_js = File.read File.join(viz_dir, "fsm.js") + fsm_css = File.read File.join(viz_dir, "fsm.css") + erb = File.read File.join(viz_dir, "index.html.erb") states = "function tt() { return #{to_json}; }" fun_routes = paths.sample(3).map do |ast| @@ -93,10 +93,10 @@ module ActionDispatch case n when Nodes::Symbol case n.left - when ':id' then rand(100).to_s - when ':format' then %w{ xml json }.sample + when ":id" then rand(100).to_s + when ":format" then %w{ xml json }.sample else - 'omg' + "omg" end when Nodes::Terminal then n.symbol else @@ -115,7 +115,7 @@ module ActionDispatch svg = svg javascripts = javascripts - require 'erb' + require "erb" template = ERB.new erb template.result(binding) end @@ -148,7 +148,7 @@ module ActionDispatch when Regexp @regexp_states else - raise ArgumentError, 'unknown symbol: %s' % sym.class + raise ArgumentError, "unknown symbol: %s" % sym.class end end end diff --git a/actionpack/lib/action_dispatch/journey/nfa/builder.rb b/actionpack/lib/action_dispatch/journey/nfa/builder.rb index ee6494c3e4..19e5752ae5 100644 --- a/actionpack/lib/action_dispatch/journey/nfa/builder.rb +++ b/actionpack/lib/action_dispatch/journey/nfa/builder.rb @@ -1,5 +1,5 @@ -require 'action_dispatch/journey/nfa/transition_table' -require 'action_dispatch/journey/gtg/transition_table' +require "action_dispatch/journey/nfa/transition_table" +require "action_dispatch/journey/gtg/transition_table" module ActionDispatch module Journey # :nodoc: diff --git a/actionpack/lib/action_dispatch/journey/nfa/dot.rb b/actionpack/lib/action_dispatch/journey/nfa/dot.rb index 7063b44bb5..8119e5d9da 100644 --- a/actionpack/lib/action_dispatch/journey/nfa/dot.rb +++ b/actionpack/lib/action_dispatch/journey/nfa/dot.rb @@ -18,7 +18,7 @@ module ActionDispatch # (memos || []).map { |v| " #{k} -> #{v.object_id};" } #}.uniq - <<-eodot + <<-eodot digraph nfa { rankdir=LR; node [shape = doublecircle]; @@ -26,7 +26,7 @@ digraph nfa { node [shape = circle]; #{edges.join "\n"} } - eodot + eodot end end end diff --git a/actionpack/lib/action_dispatch/journey/nfa/simulator.rb b/actionpack/lib/action_dispatch/journey/nfa/simulator.rb index b23270db3c..324d0eed15 100644 --- a/actionpack/lib/action_dispatch/journey/nfa/simulator.rb +++ b/actionpack/lib/action_dispatch/journey/nfa/simulator.rb @@ -1,4 +1,4 @@ -require 'strscan' +require "strscan" module ActionDispatch module Journey # :nodoc: diff --git a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb index 0ccab21801..4737adc724 100644 --- a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb +++ b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb @@ -1,4 +1,4 @@ -require 'action_dispatch/journey/nfa/dot' +require "action_dispatch/journey/nfa/dot" module ActionDispatch module Journey # :nodoc: diff --git a/actionpack/lib/action_dispatch/journey/nodes/node.rb b/actionpack/lib/action_dispatch/journey/nodes/node.rb index 2793c5668d..0d874a84c9 100644 --- a/actionpack/lib/action_dispatch/journey/nodes/node.rb +++ b/actionpack/lib/action_dispatch/journey/nodes/node.rb @@ -1,4 +1,4 @@ -require 'action_dispatch/journey/visitors' +require "action_dispatch/journey/visitors" module ActionDispatch module Journey # :nodoc: @@ -18,7 +18,7 @@ module ActionDispatch end def to_s - Visitors::String::INSTANCE.accept(self, '') + Visitors::String::INSTANCE.accept(self, "") end def to_dot @@ -30,7 +30,7 @@ module ActionDispatch end def name - left.tr '*:'.freeze, ''.freeze + left.tr "*:".freeze, "".freeze end def type @@ -80,7 +80,7 @@ module ActionDispatch def initialize(left) super @regexp = DEFAULT_EXP - @name = left.tr '*:'.freeze, ''.freeze + @name = left.tr "*:".freeze, "".freeze end def default_regexp? @@ -104,7 +104,7 @@ module ActionDispatch def type; :STAR; end def name - left.name.tr '*:', '' + left.name.tr "*:", "" end end diff --git a/actionpack/lib/action_dispatch/journey/parser.rb b/actionpack/lib/action_dispatch/journey/parser.rb index 9012297400..ee91b11b42 100644 --- a/actionpack/lib/action_dispatch/journey/parser.rb +++ b/actionpack/lib/action_dispatch/journey/parser.rb @@ -4,195 +4,195 @@ # from Racc grammer file "". # -require 'racc/parser.rb' +require "racc/parser.rb" - -require 'action_dispatch/journey/parser_extras' +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, 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 end # class Parser - end # module Journey - end # module ActionDispatch + end # module Journey + # :startdoc: +end # module ActionDispatch diff --git a/actionpack/lib/action_dispatch/journey/parser_extras.rb b/actionpack/lib/action_dispatch/journey/parser_extras.rb index fff0299812..4c7e82d93c 100644 --- a/actionpack/lib/action_dispatch/journey/parser_extras.rb +++ b/actionpack/lib/action_dispatch/journey/parser_extras.rb @@ -1,9 +1,10 @@ -require 'action_dispatch/journey/scanner' -require 'action_dispatch/journey/nodes/node' +require "action_dispatch/journey/scanner" +require "action_dispatch/journey/nodes/node" module ActionDispatch - module Journey # :nodoc: - class Parser < Racc::Parser # :nodoc: + # :stopdoc: + module Journey + class Parser < Racc::Parser include Journey::Nodes def self.parse(string) @@ -24,4 +25,5 @@ module ActionDispatch end end end + # :startdoc: end diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb index 018b89a2b7..0902b9233e 100644 --- a/actionpack/lib/action_dispatch/journey/path/pattern.rb +++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb @@ -4,7 +4,7 @@ module ActionDispatch class Pattern # :nodoc: attr_reader :spec, :requirements, :anchored - def self.from_string string + def self.from_string(string) build(string, {}, "/.?", true) end @@ -98,7 +98,7 @@ module ActionDispatch end def visit_STAR(node) - re = @matchers[node.left.to_sym] || '.+' + re = @matchers[node.left.to_sym] || ".+" "(#{re})" end @@ -175,7 +175,7 @@ module ActionDispatch if @requirements.key?(node) re = /#{@requirements[node]}|/ - @offsets.push((re.match('').length - 1) + @offsets.last) + @offsets.push((re.match("").length - 1) + @offsets.last) else @offsets << @offsets.last end diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb index 35c2b1b86e..79cd010d64 100644 --- a/actionpack/lib/action_dispatch/journey/route.rb +++ b/actionpack/lib/action_dispatch/journey/route.rb @@ -1,9 +1,10 @@ module ActionDispatch - module Journey # :nodoc: - class Route # :nodoc: + # :stopdoc: + module Journey + class Route attr_reader :app, :path, :defaults, :name, :precedence - attr_reader :constraints + attr_reader :constraints, :internal alias :conditions :constraints module VerbMatchers @@ -29,16 +30,15 @@ module ActionDispatch class All def self.call(_); true; end - def self.verb; ''; end + def self.verb; ""; end end - VERB_TO_CLASS = VERBS.each_with_object({ :all => All }) do |verb, hash| + VERB_TO_CLASS = VERBS.each_with_object(all: All) do |verb, hash| klass = const_get verb hash[verb] = klass hash[verb.downcase] = klass hash[verb.downcase.to_sym] = klass end - end def self.verb_matcher(verb) @@ -55,7 +55,7 @@ module ActionDispatch ## # +path+ is a path constraint. # +constraints+ is a hash of constraints to be applied to this route. - def initialize(name, app, path, constraints, required_defaults, defaults, request_method_match, precedence) + def initialize(name, app, path, constraints, required_defaults, defaults, request_method_match, precedence, internal = false) @name = name @app = app @path = path @@ -70,6 +70,7 @@ module ActionDispatch @decorated_ast = nil @precedence = precedence @path_formatter = @path.build_formatter + @internal = internal end def ast @@ -80,8 +81,8 @@ module ActionDispatch end end - def requirements # :nodoc: - # needed for rails `rake routes` + def requirements + # needed for rails `rails routes` @defaults.merge(path.requirements).delete_if { |_,v| /.+?/ == v } @@ -163,17 +164,18 @@ module ActionDispatch end def verb - verbs.join('|') + verbs.join("|") end private - def verbs - @request_method_match.map(&:verb) - end + def verbs + @request_method_match.map(&:verb) + end - def match_verb(request) - @request_method_match.any? { |m| m.call request } - end + def match_verb(request) + @request_method_match.any? { |m| m.call request } + end end end + # :startdoc: end diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb index f649588520..d0ef549335 100644 --- a/actionpack/lib/action_dispatch/journey/router.rb +++ b/actionpack/lib/action_dispatch/journey/router.rb @@ -1,14 +1,14 @@ -require 'action_dispatch/journey/router/utils' -require 'action_dispatch/journey/routes' -require 'action_dispatch/journey/formatter' +require "action_dispatch/journey/router/utils" +require "action_dispatch/journey/routes" +require "action_dispatch/journey/formatter" before = $-w $-w = false -require 'action_dispatch/journey/parser' +require "action_dispatch/journey/parser" $-w = before -require 'action_dispatch/journey/route' -require 'action_dispatch/journey/path/pattern' +require "action_dispatch/journey/route" +require "action_dispatch/journey/path/pattern" module ActionDispatch module Journey # :nodoc: @@ -16,9 +16,6 @@ module ActionDispatch class RoutingError < ::StandardError # :nodoc: end - # :nodoc: - VERSION = '2.0.0' - attr_accessor :routes def initialize(routes) @@ -32,7 +29,7 @@ module ActionDispatch script_name = req.script_name unless route.path.anchored - req.script_name = (script_name.to_s + match.to_s).chomp('/') + req.script_name = (script_name.to_s + match.to_s).chomp("/") req.path_info = match.post_match req.path_info = "/" + req.path_info unless req.path_info.start_with? "/" end @@ -41,7 +38,7 @@ module ActionDispatch status, headers, body = route.app.serve(req) - if 'pass' == headers['X-Cascade'] + if "pass" == headers["X-Cascade"] req.script_name = script_name req.path_info = path_info req.path_parameters = set_params @@ -51,7 +48,7 @@ module ActionDispatch return [status, headers, body] end - return [404, {'X-Cascade' => 'pass'}, ['Not Found']] + return [404, { "X-Cascade" => "pass" }, ["Not Found"]] end def recognize(rails_req) @@ -75,7 +72,9 @@ module ActionDispatch private def partitioned_routes - routes.partitioned_routes + routes.partition { |r| + r.path.anchored && r.ast.grep(Nodes::Symbol).all? { |n| n.default_regexp? } + } end def ast @@ -95,7 +94,7 @@ module ActionDispatch simulator.memos(path) { [] } end - def find_routes req + def find_routes(req) routes = filter_routes(req.path_info).concat custom_routes.find_all { |r| r.path.match(req.path_info) } diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb index 9793ca1c7a..ce5d350763 100644 --- a/actionpack/lib/action_dispatch/journey/router/utils.rb +++ b/actionpack/lib/action_dispatch/journey/router/utils.rb @@ -14,10 +14,10 @@ module ActionDispatch # normalize_path("/%ab") # => "/%AB" def self.normalize_path(path) path = "/#{path}" - path.squeeze!('/'.freeze) - path.sub!(%r{/+\Z}, ''.freeze) + path.squeeze!("/".freeze) + path.sub!(%r{/+\Z}, "".freeze) path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase } - path = '/' if path == ''.freeze + path = "/" if path == "".freeze path end @@ -28,7 +28,7 @@ module ActionDispatch US_ASCII = Encoding::US_ASCII UTF_8 = Encoding::UTF_8 EMPTY = "".force_encoding(US_ASCII).freeze - DEC2HEX = (0..255).to_a.map{ |i| ENCODE % i }.map{ |s| s.force_encoding(US_ASCII) } + DEC2HEX = (0..255).to_a.map { |i| ENCODE % i }.map { |s| s.force_encoding(US_ASCII) } ALPHA = "a-zA-Z".freeze DIGIT = "0-9".freeze @@ -55,12 +55,12 @@ module ActionDispatch def unescape_uri(uri) encoding = uri.encoding == US_ASCII ? UTF_8 : uri.encoding - uri.gsub(ESCAPED) { |match| [match[1, 2].hex].pack('C') }.force_encoding(encoding) + uri.gsub(ESCAPED) { |match| [match[1, 2].hex].pack("C") }.force_encoding(encoding) end protected def escape(component, pattern) - component.gsub(pattern){ |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII) + component.gsub(pattern) { |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII) end def percent_encode(unsafe) diff --git a/actionpack/lib/action_dispatch/journey/scanner.rb b/actionpack/lib/action_dispatch/journey/scanner.rb index 19e0bc03d6..4b8c8ab063 100644 --- a/actionpack/lib/action_dispatch/journey/scanner.rb +++ b/actionpack/lib/action_dispatch/journey/scanner.rb @@ -1,4 +1,4 @@ -require 'strscan' +require "strscan" module ActionDispatch module Journey # :nodoc: @@ -50,7 +50,7 @@ module ActionDispatch when text = @ss.scan(/(?<!\\):\w+/) [:SYMBOL, text] when text = @ss.scan(/(?:[\w%\-~!$&'*+,;=@]|\\:|\\\(|\\\))+/) - [:LITERAL, text.tr('\\', '')] + [:LITERAL, text.tr('\\', "")] # 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 306d2e674a..74f3c0617a 100644 --- a/actionpack/lib/action_dispatch/journey/visitors.rb +++ b/actionpack/lib/action_dispatch/journey/visitors.rb @@ -1,5 +1,6 @@ module ActionDispatch - module Journey # :nodoc: + # :stopdoc: + module Journey class Format ESCAPE_PATH = ->(value) { Router::Utils.escape_path(value) } ESCAPE_SEGMENT = ->(value) { Router::Utils.escape_segment(value) } @@ -37,7 +38,7 @@ module ActionDispatch @parameters.each do |index| param = parts[index] value = hash[param.name] - return ''.freeze unless value + return "".freeze unless value parts[index] = param.escape value end @@ -57,7 +58,7 @@ module ActionDispatch private - def visit node + def visit(node) send(DISPATCH_CACHE[node.type], node) end @@ -97,7 +98,7 @@ module ActionDispatch visit(node, seed) end - def visit node, seed + def visit(node, seed) send(DISPATCH_CACHE[node.type], node, seed) end @@ -166,28 +167,28 @@ module ActionDispatch class String < FunctionalVisitor # :nodoc: private - def binary(node, seed) - visit(node.right, visit(node.left, seed)) - end + def binary(node, seed) + visit(node.right, visit(node.left, seed)) + end - def nary(node, seed) - last_child = node.children.last - node.children.inject(seed) { |s, c| - string = visit(c, s) - string << "|".freeze unless last_child == c - string - } - end + def nary(node, seed) + last_child = node.children.last + node.children.inject(seed) { |s, c| + string = visit(c, s) + string << "|".freeze unless last_child == c + string + } + end - def terminal(node, seed) - seed + node.left - end + def terminal(node, seed) + seed + node.left + end - def visit_GROUP(node, seed) - visit(node.left, seed << "(".freeze) << ")".freeze - end + def visit_GROUP(node, seed) + visit(node.left, seed << "(".freeze) << ")".freeze + end - INSTANCE = new + INSTANCE = new end class Dot < FunctionalVisitor # :nodoc: @@ -261,4 +262,5 @@ module ActionDispatch end end end + # :startdoc: end diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb index f80df78582..fef246532b 100644 --- a/actionpack/lib/action_dispatch/middleware/callbacks.rb +++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb @@ -7,7 +7,16 @@ module ActionDispatch define_callbacks :call class << self - delegate :to_prepare, :to_cleanup, :to => "ActionDispatch::Reloader" + 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) diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 3477aa8b29..6f4fab396a 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -1,12 +1,13 @@ -require 'active_support/core_ext/hash/keys' -require 'active_support/key_generator' -require 'active_support/message_verifier' -require 'active_support/json' +require "active_support/core_ext/hash/keys" +require "active_support/key_generator" +require "active_support/message_verifier" +require "active_support/json" +require "rack/utils" module ActionDispatch class Request def cookie_jar - fetch_header('action_dispatch.cookies'.freeze) do + fetch_header("action_dispatch.cookies".freeze) do self.cookie_jar = Cookies::CookieJar.build(self, cookies) end end @@ -19,11 +20,11 @@ module ActionDispatch } def have_cookie_jar? - has_header? 'action_dispatch.cookies'.freeze + has_header? "action_dispatch.cookies".freeze end def cookie_jar=(jar) - set_header 'action_dispatch.cookies'.freeze, jar + set_header "action_dispatch.cookies".freeze, jar end def key_generator @@ -236,9 +237,9 @@ module ActionDispatch private - def upgrade_legacy_signed_cookies? - request.secret_token.present? && request.secret_key_base.present? - end + def upgrade_legacy_signed_cookies? + request.secret_token.present? && request.secret_key_base.present? + end end # Passing the ActiveSupport::MessageEncryptor::NullSerializer downstream @@ -337,13 +338,13 @@ module ActionDispatch end def to_header - @cookies.map { |k,v| "#{k}=#{v}" }.join ';' + @cookies.map { |k,v| "#{escape(k)}=#{escape(v)}" }.join "; " end def handle_options(options) #:nodoc: options[:path] ||= "/" - if options[:domain] == :all || options[:domain] == 'all' + if options[:domain] == :all || options[:domain] == "all" # if there is a provided tld length then we use it otherwise default domain regexp domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP @@ -354,7 +355,7 @@ module ActionDispatch end elsif options[:domain].is_a? Array # if host matches one of the supplied domains without a dot in front of it - options[:domain] = options[:domain].find {|domain| request.host.include? domain.sub(/^\./, '') } + options[:domain] = options[:domain].find { |domain| request.host.include? domain.sub(/^\./, "") } end end @@ -366,12 +367,12 @@ module ActionDispatch value = options[:value] else value = options - options = { :value => value } + options = { value: value } end handle_options(options) - if @cookies[name.to_s] != value or options[:expires] + if @cookies[name.to_s] != value || options[:expires] @cookies[name.to_s] = value @set_cookies[name.to_s] = options @delete_cookies.delete(name.to_s) @@ -405,7 +406,7 @@ module ActionDispatch # Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie def clear(options = {}) - @cookies.each_key{ |k| delete(k, options) } + @cookies.each_key { |k| delete(k, options) } end def write(headers) @@ -419,22 +420,26 @@ module ActionDispatch private - def make_set_cookie_header(header) - header = @set_cookies.inject(header) { |m, (k, v)| - if write_cookie?(v) - ::Rack::Utils.add_cookie_to_header(m, k, v) - else - m - end - } - @delete_cookies.inject(header) { |m, (k, v)| - ::Rack::Utils.add_remove_cookie_to_header(m, k, v) - } - end + def escape(string) + ::Rack::Utils.escape(string) + end - def write_cookie?(cookie) - request.ssl? || !cookie[:secure] || always_write_cookie - end + def make_set_cookie_header(header) + header = @set_cookies.inject(header) { |m, (k, v)| + if write_cookie?(v) + ::Rack::Utils.add_cookie_to_header(m, k, v) + else + m + end + } + @delete_cookies.inject(header) { |m, (k, v)| + ::Rack::Utils.add_remove_cookie_to_header(m, k, v) + } + end + + def write_cookie?(cookie) + request.ssl? || !cookie[:secure] || always_write_cookie + end end class AbstractCookieJar # :nodoc: @@ -523,7 +528,7 @@ module ActionDispatch end def digest - request.cookies_digest || 'SHA1' + request.cookies_digest || "SHA1" end def key_generator @@ -571,8 +576,8 @@ module ActionDispatch "Read the upgrade documentation to learn more about this new config option." end - secret = key_generator.generate_key(request.encrypted_cookie_salt || '') - sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt || '') + secret = key_generator.generate_key(request.encrypted_cookie_salt || "")[0, ActiveSupport::MessageEncryptor.key_len] + sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt || "") @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer) end diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb index b55c937e0c..ee644f41c8 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb @@ -1,16 +1,16 @@ -require 'action_dispatch/http/request' -require 'action_dispatch/middleware/exception_wrapper' -require 'action_dispatch/routing/inspector' -require 'action_view' -require 'action_view/base' +require "action_dispatch/http/request" +require "action_dispatch/middleware/exception_wrapper" +require "action_dispatch/routing/inspector" +require "action_view" +require "action_view/base" -require 'pp' +require "pp" module ActionDispatch # This middleware is responsible for logging exceptions and # showing a debugging page in case the request is local. class DebugExceptions - RESCUES_TEMPLATE_PATH = File.expand_path('../templates', __FILE__) + RESCUES_TEMPLATE_PATH = File.expand_path("../templates", __FILE__) class DebugView < ActionView::Base def debug_params(params) @@ -19,7 +19,7 @@ module ActionDispatch clean_params.delete("controller") if clean_params.empty? - 'None' + "None" else PP.pp(clean_params, "", 200) end @@ -27,15 +27,23 @@ module ActionDispatch def debug_headers(headers) if headers.present? - headers.inspect.gsub(',', ",\n") + headers.inspect.gsub(",", ",\n") else - 'None' + "None" end end def debug_hash(object) object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n") end + + def render(*) + if logger = ActionView::Base.logger + logger.silence { super } + else + super + end + end end def initialize(app, routes_app = nil, response_format = :default) @@ -48,7 +56,7 @@ module ActionDispatch request = ActionDispatch::Request.new env _, headers, body = response = @app.call(env) - if headers['X-Cascade'] == 'pass' + if headers["X-Cascade"] == "pass" body.close if body.respond_to?(:close) raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}" end @@ -61,120 +69,129 @@ module ActionDispatch private - def render_exception(request, exception) - backtrace_cleaner = request.get_header('action_dispatch.backtrace_cleaner') - wrapper = ExceptionWrapper.new(backtrace_cleaner, exception) - log_error(request, wrapper) - - if request.get_header('action_dispatch.show_detailed_exceptions') - case @response_format - when :api - render_for_api_application(request, wrapper) - when :default - render_for_default_application(request, wrapper) + def render_exception(request, exception) + backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner") + wrapper = ExceptionWrapper.new(backtrace_cleaner, exception) + log_error(request, wrapper) + + if request.get_header("action_dispatch.show_detailed_exceptions") + content_type = request.formats.first + + if api_request?(content_type) + render_for_api_request(content_type, wrapper) + else + render_for_browser_request(request, wrapper) + end + else + raise exception end - else - raise exception end - end - def render_for_default_application(request, wrapper) - template = create_template(request, wrapper) - file = "rescues/#{wrapper.rescue_template}" + def render_for_browser_request(request, wrapper) + template = create_template(request, wrapper) + file = "rescues/#{wrapper.rescue_template}" - if request.xhr? - body = template.render(template: file, layout: false, formats: [:text]) - format = "text/plain" - else - body = template.render(template: file, layout: 'rescues/layout') - format = "text/html" + if request.xhr? + body = template.render(template: file, layout: false, formats: [:text]) + format = "text/plain" + else + body = template.render(template: file, layout: "rescues/layout") + format = "text/html" + end + render(wrapper.status_code, body, format) end - render(wrapper.status_code, body, format) - end - def render_for_api_application(request, wrapper) - body = { - status: wrapper.status_code, - error: Rack::Utils::HTTP_STATUS_CODES.fetch( - wrapper.status_code, - Rack::Utils::HTTP_STATUS_CODES[500] - ), - exception: wrapper.exception.inspect, - traces: wrapper.traces - } - - content_type = request.formats.first - to_format = "to_#{content_type.to_sym}" - - if content_type && body.respond_to?(to_format) - formatted_body = body.public_send(to_format) - format = content_type - else - formatted_body = body.to_json - format = Mime[:json] - end - - render(wrapper.status_code, formatted_body, format) - end + def render_for_api_request(content_type, wrapper) + body = { + status: wrapper.status_code, + error: Rack::Utils::HTTP_STATUS_CODES.fetch( + wrapper.status_code, + Rack::Utils::HTTP_STATUS_CODES[500] + ), + exception: wrapper.exception.inspect, + traces: wrapper.traces + } + + to_format = "to_#{content_type.to_sym}" + + if content_type && body.respond_to?(to_format) + formatted_body = body.public_send(to_format) + format = content_type + else + formatted_body = body.to_json + format = Mime[:json] + end + + render(wrapper.status_code, formatted_body, format) + end - def create_template(request, wrapper) - traces = wrapper.traces + def create_template(request, wrapper) + traces = wrapper.traces - trace_to_show = 'Application Trace' - if traces[trace_to_show].empty? && wrapper.rescue_template != 'routing_error' - trace_to_show = 'Full Trace' + trace_to_show = "Application Trace" + if traces[trace_to_show].empty? && wrapper.rescue_template != "routing_error" + trace_to_show = "Full Trace" + end + + if source_to_show = traces[trace_to_show].first + source_to_show_id = source_to_show[:id] + end + + DebugView.new([RESCUES_TEMPLATE_PATH], + request: request, + exception: wrapper.exception, + traces: traces, + show_source_idx: source_to_show_id, + trace_to_show: trace_to_show, + routes_inspector: routes_inspector(wrapper.exception), + source_extracts: wrapper.source_extracts, + line_number: wrapper.line_number, + file: wrapper.file + ) end - if source_to_show = traces[trace_to_show].first - source_to_show_id = source_to_show[:id] + def render(status, body, format) + [status, { "Content-Type" => "#{format}; charset=#{Response.default_charset}", "Content-Length" => body.bytesize.to_s }, [body]] end - DebugView.new([RESCUES_TEMPLATE_PATH], - request: request, - exception: wrapper.exception, - traces: traces, - show_source_idx: source_to_show_id, - trace_to_show: trace_to_show, - routes_inspector: routes_inspector(wrapper.exception), - source_extracts: wrapper.source_extracts, - line_number: wrapper.line_number, - file: wrapper.file - ) - end + def log_error(request, wrapper) + logger = logger(request) + return unless logger - def render(status, body, format) - [status, {'Content-Type' => "#{format}; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]] - end + exception = wrapper.exception - def log_error(request, wrapper) - logger = logger(request) - return unless logger + trace = wrapper.application_trace + trace = wrapper.framework_trace if trace.empty? - exception = wrapper.exception + ActiveSupport::Deprecation.silence do + logger.fatal " " + logger.fatal "#{exception.class} (#{exception.message}):" + log_array logger, exception.annoted_source_code if exception.respond_to?(:annoted_source_code) + logger.fatal " " + log_array logger, trace + end + end - trace = wrapper.application_trace - trace = wrapper.framework_trace if trace.empty? + def log_array(logger, array) + array.map { |line| logger.fatal line } + end - ActiveSupport::Deprecation.silence do - message = "\n#{exception.class} (#{exception.message}):\n" - message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) - message << " " << trace.join("\n ") - logger.fatal("#{message}\n\n") + def logger(request) + request.logger || ActionView::Base.logger || stderr_logger end - end - def logger(request) - request.logger || stderr_logger - end + def stderr_logger + @stderr_logger ||= ActiveSupport::Logger.new($stderr) + end - def stderr_logger - @stderr_logger ||= ActiveSupport::Logger.new($stderr) - end + def routes_inspector(exception) + if @routes_app.respond_to?(:routes) && (exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error)) + ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes) + end + end - def routes_inspector(exception) - if @routes_app.respond_to?(:routes) && (exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error)) - ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes) + def api_request?(content_type) + @response_format == :api && !content_type.html? end - end end end diff --git a/actionpack/lib/action_dispatch/middleware/debug_locks.rb b/actionpack/lib/action_dispatch/middleware/debug_locks.rb new file mode 100644 index 0000000000..74b952528e --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/debug_locks.rb @@ -0,0 +1,122 @@ +module ActionDispatch + # This middleware can be used to diagnose deadlocks in the autoload interlock. + # + # To use it, insert it near the top of the middleware stack, using + # <tt>config/application.rb</tt>: + # + # config.middleware.insert_before Rack::Sendfile, ActionDispatch::DebugLocks + # + # After restarting the application and re-triggering the deadlock condition, + # <tt>/rails/locks</tt> will show a summary of all threads currently known to + # the interlock, which lock level they are holding or awaiting, and their + # current backtrace. + # + # Generally a deadlock will be caused by the interlock conflicting with some + # other external lock or blocking I/O call. These cannot be automatically + # identified, but should be visible in the displayed backtraces. + # + # NOTE: The formatting and content of this middleware's output is intended for + # human consumption, and should be expected to change between releases. + # + # This middleware exposes operational details of the server, with no access + # control. It should only be enabled when in use, and removed thereafter. + class DebugLocks + def initialize(app, path = "/rails/locks") + @app = app + @path = path + end + + def call(env) + req = ActionDispatch::Request.new env + + if req.get? + path = req.path_info.chomp("/".freeze) + if path == @path + return render_details(req) + end + end + + @app.call(env) + end + + private + def render_details(req) + threads = ActiveSupport::Dependencies.interlock.raw_state do |threads| + # The Interlock itself comes to a complete halt as long as this block + # is executing. That gives us a more consistent picture of everything, + # but creates a pretty strong Observer Effect. + # + # Most directly, that means we need to do as little as possible in + # this block. More widely, it means this middleware should remain a + # strictly diagnostic tool (to be used when something has gone wrong), + # and not for any sort of general monitoring. + + threads.each.with_index do |(thread, info), idx| + info[:index] = idx + info[:backtrace] = thread.backtrace + end + + threads + end + + str = threads.map do |thread, info| + if info[:exclusive] + lock_state = "Exclusive" + elsif info[:sharing] > 0 + lock_state = "Sharing" + lock_state << " x#{info[:sharing]}" if info[:sharing] > 1 + else + lock_state = "No lock" + end + + if info[:waiting] + lock_state << " (yielded share)" + end + + msg = "Thread #{info[:index]} [0x#{thread.__id__.to_s(16)} #{thread.status || 'dead'}] #{lock_state}\n" + + if info[:sleeper] + msg << " Waiting in #{info[:sleeper]}" + msg << " to #{info[:purpose].to_s.inspect}" unless info[:purpose].nil? + msg << "\n" + + if info[:compatible] + compat = info[:compatible].map { |c| c == false ? "share" : c.to_s.inspect } + msg << " may be pre-empted for: #{compat.join(', ')}\n" + end + + blockers = threads.values.select { |binfo| blocked_by?(info, binfo, threads.values) } + msg << " blocked by: #{blockers.map { |i| i[:index] }.join(', ')}\n" if blockers.any? + end + + blockees = threads.values.select { |binfo| blocked_by?(binfo, info, threads.values) } + msg << " blocking: #{blockees.map { |i| i[:index] }.join(', ')}\n" if blockees.any? + + msg << "\n#{info[:backtrace].join("\n")}\n" if info[:backtrace] + end.join("\n\n---\n\n\n") + + [200, { "Content-Type" => "text/plain", "Content-Length" => str.size }, [str]] + end + + def blocked_by?(victim, blocker, all_threads) + return false if victim.equal?(blocker) + + case victim[:sleeper] + when :start_sharing + blocker[:exclusive] || + (!victim[:waiting] && blocker[:compatible] && !blocker[:compatible].include?(false)) + when :start_exclusive + blocker[:sharing] > 0 || + blocker[:exclusive] || + (blocker[:compatible] && !blocker[:compatible].include?(victim[:purpose])) + when :yield_shares + blocker[:exclusive] + when :stop_exclusive + blocker[:exclusive] || + victim[:compatible] && + victim[:compatible].include?(blocker[:purpose]) && + all_threads.all? { |other| !other[:compatible] || blocker.equal?(other) || other[:compatible].include?(blocker[:purpose]) } + end + end + end +end diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb index 3b61824cc9..99dc37c568 100644 --- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb +++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb @@ -1,34 +1,33 @@ -require 'action_controller/metal/exceptions' -require 'active_support/core_ext/module/attribute_accessors' -require 'rack/utils' +require "active_support/core_ext/module/attribute_accessors" +require "rack/utils" module ActionDispatch class ExceptionWrapper cattr_accessor :rescue_responses @@rescue_responses = Hash.new(:internal_server_error) @@rescue_responses.merge!( - 'ActionController::RoutingError' => :not_found, - 'AbstractController::ActionNotFound' => :not_found, - 'ActionController::MethodNotAllowed' => :method_not_allowed, - 'ActionController::UnknownHttpMethod' => :method_not_allowed, - 'ActionController::NotImplemented' => :not_implemented, - 'ActionController::UnknownFormat' => :not_acceptable, - 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity, - 'ActionController::InvalidCrossOriginRequest' => :unprocessable_entity, - 'ActionDispatch::ParamsParser::ParseError' => :bad_request, - 'ActionController::BadRequest' => :bad_request, - 'ActionController::ParameterMissing' => :bad_request, - 'Rack::Utils::ParameterTypeError' => :bad_request, - 'Rack::Utils::InvalidParameterError' => :bad_request + "ActionController::RoutingError" => :not_found, + "AbstractController::ActionNotFound" => :not_found, + "ActionController::MethodNotAllowed" => :method_not_allowed, + "ActionController::UnknownHttpMethod" => :method_not_allowed, + "ActionController::NotImplemented" => :not_implemented, + "ActionController::UnknownFormat" => :not_acceptable, + "ActionController::InvalidAuthenticityToken" => :unprocessable_entity, + "ActionController::InvalidCrossOriginRequest" => :unprocessable_entity, + "ActionDispatch::Http::Parameters::ParseError" => :bad_request, + "ActionController::BadRequest" => :bad_request, + "ActionController::ParameterMissing" => :bad_request, + "Rack::QueryParser::ParameterTypeError" => :bad_request, + "Rack::QueryParser::InvalidParameterError" => :bad_request ) cattr_accessor :rescue_templates - @@rescue_templates = Hash.new('diagnostics') + @@rescue_templates = Hash.new("diagnostics") @@rescue_templates.merge!( - 'ActionView::MissingTemplate' => 'missing_template', - 'ActionController::RoutingError' => 'routing_error', - 'AbstractController::ActionNotFound' => 'unknown_action', - 'ActionView::Template::Error' => 'template_error' + "ActionView::MissingTemplate" => "missing_template", + "ActionController::RoutingError" => "routing_error", + "AbstractController::ActionNotFound" => "unknown_action", + "ActionView::Template::Error" => "template_error" ) attr_reader :backtrace_cleaner, :exception, :line_number, :file @@ -101,49 +100,49 @@ module ActionDispatch private - def backtrace - Array(@exception.backtrace) - end + def backtrace + Array(@exception.backtrace) + end - def original_exception(exception) - if @@rescue_responses.has_key?(exception.cause.class.name) - exception.cause - else - exception + def original_exception(exception) + if @@rescue_responses.has_key?(exception.cause.class.name) + exception.cause + else + exception + end end - end - def clean_backtrace(*args) - if backtrace_cleaner - backtrace_cleaner.clean(backtrace, *args) - else - backtrace + def clean_backtrace(*args) + if backtrace_cleaner + backtrace_cleaner.clean(backtrace, *args) + else + backtrace + end end - end - def source_fragment(path, line) - return unless Rails.respond_to?(:root) && Rails.root - full_path = Rails.root.join(path) - if File.exist?(full_path) - File.open(full_path, "r") do |file| - start = [line - 3, 0].max - lines = file.each_line.drop(start).take(6) - Hash[*(start+1..(lines.count+start)).zip(lines).flatten] + def source_fragment(path, line) + return unless Rails.respond_to?(:root) && Rails.root + full_path = Rails.root.join(path) + if File.exist?(full_path) + File.open(full_path, "r") do |file| + start = [line - 3, 0].max + lines = file.each_line.drop(start).take(6) + Hash[*(start+1..(lines.count+start)).zip(lines).flatten] + end end end - end - def extract_file_and_line_number(trace) - # Split by the first colon followed by some digits, which works for both - # Windows and Unix path styles. - file, line = trace.match(/^(.+?):(\d+).*$/, &:captures) || trace - [file, line.to_i] - end + def extract_file_and_line_number(trace) + # Split by the first colon followed by some digits, which works for both + # Windows and Unix path styles. + file, line = trace.match(/^(.+?):(\d+).*$/, &:captures) || trace + [file, line.to_i] + end - def expand_backtrace - @exception.backtrace.unshift( - @exception.to_s.split("\n") - ).flatten! - end + def expand_backtrace + @exception.backtrace.unshift( + @exception.to_s.split("\n") + ).flatten! + end end end diff --git a/actionpack/lib/action_dispatch/middleware/executor.rb b/actionpack/lib/action_dispatch/middleware/executor.rb new file mode 100644 index 0000000000..3d43f97a2b --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/executor.rb @@ -0,0 +1,19 @@ +require "rack/body_proxy" + +module ActionDispatch + class Executor + def initialize(app, executor) + @app, @executor = app, executor + end + + def call(env) + state = @executor.run! + begin + response = @app.call(env) + returned = response << ::Rack::BodyProxy.new(response.pop) { state.complete! } + ensure + state.complete! unless returned + end + end + end +end diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index c51dcd542a..6900934712 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/hash/keys' +require "active_support/core_ext/hash/keys" module ActionDispatch # The flash provides a way to pass temporary primitive-types (String, Array, Hash) between actions. Anything you place in the flash will be exposed @@ -36,7 +36,7 @@ module ActionDispatch # # See docs on the FlashHash class for more details about the flash. class Flash - KEY = 'action_dispatch.request.flash_hash'.freeze + KEY = "action_dispatch.request.flash_hash".freeze module RequestMethods # Access the contents of the flash. Use <tt>flash["notice"]</tt> to @@ -60,16 +60,21 @@ module ActionDispatch session = self.session || {} flash_hash = self.flash_hash - if flash_hash && (flash_hash.present? || session.key?('flash')) + if flash_hash && (flash_hash.present? || session.key?("flash")) session["flash"] = flash_hash.to_session_value self.flash = flash_hash.dup end if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?) - session.key?('flash') && session['flash'].nil? - session.delete('flash') + session.key?("flash") && session["flash"].nil? + session.delete("flash") end end + + def reset_session # :nodoc + super + self.flash = nil + end end class FlashNow #:nodoc: @@ -113,8 +118,8 @@ module ActionDispatch end new(flashes, flashes.keys) when Hash # Rails 4.0 - flashes = value['flashes'] - if discard = value['discard'] + flashes = value["flashes"] + if discard = value["discard"] flashes.except!(*discard) end new(flashes, flashes.keys) @@ -128,7 +133,7 @@ module ActionDispatch def to_session_value #:nodoc: flashes_to_keep = @flashes.except(*@discard) return nil if flashes_to_keep.empty? - {'flashes' => flashes_to_keep} + { "discard" => [], "flashes" => flashes_to_keep } end def initialize(flashes = {}, discard = []) #:nodoc: @@ -272,15 +277,15 @@ module ActionDispatch end protected - def now_is_loaded? - @now - end + def now_is_loaded? + @now + end - def stringify_array(array) - array.map do |item| - item.kind_of?(Symbol) ? item.to_s : item + def stringify_array(array) + array.map do |item| + item.kind_of?(Symbol) ? item.to_s : item + end end - end end def self.new(app) app; end diff --git a/actionpack/lib/action_dispatch/middleware/load_interlock.rb b/actionpack/lib/action_dispatch/middleware/load_interlock.rb deleted file mode 100644 index 07f498319c..0000000000 --- a/actionpack/lib/action_dispatch/middleware/load_interlock.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'active_support/dependencies' -require 'rack/body_proxy' - -module ActionDispatch - class LoadInterlock - def initialize(app) - @app = app - end - - def call(env) - interlock = ActiveSupport::Dependencies.interlock - interlock.start_running - response = @app.call(env) - body = Rack::BodyProxy.new(response[2]) { interlock.done_running } - response[2] = body - response - ensure - interlock.done_running unless body - end - end -end diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb deleted file mode 100644 index c2a4f46e67..0000000000 --- a/actionpack/lib/action_dispatch/middleware/params_parser.rb +++ /dev/null @@ -1,44 +0,0 @@ -require 'action_dispatch/http/request' - -module ActionDispatch - # ActionDispatch::ParamsParser works for all the requests having any Content-Length - # (like POST). It takes raw data from the request and puts it through the parser - # that is picked based on Content-Type header. - # - # In case of any error while parsing data ParamsParser::ParseError is raised. - class ParamsParser - # Raised when raw data from the request cannot be parsed by the parser - # defined for request's content mime type. - class ParseError < StandardError - - def initialize(message = nil, original_exception = nil) - if message - ActiveSupport::Deprecation.warn("Passing #message is deprecated and has no effect. " \ - "#{self.class} will automatically capture the message " \ - "of the original exception.", caller) - end - - if original_exception - ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \ - "Exceptions will automatically capture the original exception.", caller) - end - - super($!.message) - end - - def original_exception - ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller) - cause - end - end - - # Create a new +ParamsParser+ middleware instance. - # - # The +parsers+ argument can take Hash of parsers where key is identifying - # content mime type, and value is a lambda that is going to process data. - def self.new(app, parsers = {}) - ActionDispatch::Request.parameter_parsers = ActionDispatch::Request::DEFAULT_PARSERS.merge(parsers) - app - end - end -end diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb index 0f27984550..46f0f675b9 100644 --- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb @@ -20,36 +20,36 @@ module ActionDispatch request = ActionDispatch::Request.new(env) status = request.path_info[1..-1].to_i content_type = request.formats.first - body = { :status => status, :error => Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) } + body = { status: status, error: Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) } render(status, content_type, body) end private - def render(status, content_type, body) - format = "to_#{content_type.to_sym}" if content_type - if format && body.respond_to?(format) - render_format(status, content_type, body.public_send(format)) - else - render_html(status) + def render(status, content_type, body) + format = "to_#{content_type.to_sym}" if content_type + if format && body.respond_to?(format) + render_format(status, content_type, body.public_send(format)) + else + render_html(status) + end end - end - def render_format(status, content_type, body) - [status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}", - 'Content-Length' => body.bytesize.to_s}, [body]] - end + def render_format(status, content_type, body) + [status, { "Content-Type" => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}", + "Content-Length" => body.bytesize.to_s }, [body]] + end - def render_html(status) - path = "#{public_path}/#{status}.#{I18n.locale}.html" - path = "#{public_path}/#{status}.html" unless (found = File.exist?(path)) + def render_html(status) + path = "#{public_path}/#{status}.#{I18n.locale}.html" + path = "#{public_path}/#{status}.html" unless (found = File.exist?(path)) - if found || File.exist?(path) - render_format(status, 'text/html', File.read(path)) - else - [404, { "X-Cascade" => "pass" }, []] + if found || File.exist?(path) + render_format(status, "text/html", File.read(path)) + else + [404, { "X-Cascade" => "pass" }, []] + end end - end end end diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb index af9a29eb07..90c64037aa 100644 --- a/actionpack/lib/action_dispatch/middleware/reloader.rb +++ b/actionpack/lib/action_dispatch/middleware/reloader.rb @@ -23,74 +23,32 @@ module ActionDispatch # middleware stack, but are executed only when <tt>ActionDispatch::Reloader.prepare!</tt> # or <tt>ActionDispatch::Reloader.cleanup!</tt> are called manually. # - class Reloader - include ActiveSupport::Callbacks - include ActiveSupport::Deprecation::Reporting - - define_callbacks :prepare - define_callbacks :cleanup - - # Add a prepare callback. Prepare callbacks are run before each request, prior - # to ActionDispatch::Callback's before callbacks. + class Reloader < Executor def self.to_prepare(*args, &block) - unless block_given? - warn "to_prepare without a block is deprecated. Please use a block" - end - set_callback(:prepare, *args, &block) + ActiveSupport::Reloader.to_prepare(*args, &block) end - # Add a cleanup callback. Cleanup callbacks are run after each request is - # complete (after #close is called on the response body). def self.to_cleanup(*args, &block) - unless block_given? - warn "to_cleanup without a block is deprecated. Please use a block" - end - set_callback(:cleanup, *args, &block) + ActiveSupport::Reloader.to_complete(*args, &block) end - # Execute all prepare callbacks. def self.prepare! - new(nil).prepare! + default_reloader.prepare! end - # Execute all cleanup callbacks. def self.cleanup! - new(nil).cleanup! - end - - def initialize(app, condition=nil) - @app = app - @condition = condition || lambda { true } - @validated = true + default_reloader.reload! end - def call(env) - @validated = @condition.call - prepare! - - response = @app.call(env) - response[2] = ::Rack::BodyProxy.new(response[2]) { cleanup! } + class << self + attr_accessor :default_reloader # :nodoc: - response - rescue Exception - cleanup! - raise + 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 - def prepare! #:nodoc: - run_callbacks :prepare if validated? - end - - def cleanup! #:nodoc: - run_callbacks :cleanup if validated? - ensure - @validated = true - end - - private - - def validated? #:nodoc: - @validated - 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 31b75498b6..523eeb5b05 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -1,4 +1,4 @@ -require 'ipaddr' +require "ipaddr" module ActionDispatch # This middleware calculates the IP address of the remote client that is @@ -176,8 +176,6 @@ module ActionDispatch @proxies.any? { |proxy| proxy === ip } end end - end - end end diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb index 1555ff72af..1925ffd9dd 100644 --- a/actionpack/lib/action_dispatch/middleware/request_id.rb +++ b/actionpack/lib/action_dispatch/middleware/request_id.rb @@ -1,9 +1,10 @@ -require 'securerandom' -require 'active_support/core_ext/string/access' +require "securerandom" +require "active_support/core_ext/string/access" module ActionDispatch - # Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through - # ActionDispatch::Request#uuid or the alias ActionDispatch::Request#request_id) and sends the same id to the client via the X-Request-Id header. + # Makes a unique request id available to the +action_dispatch.request_id+ env variable (which is then accessible + # through <tt>ActionDispatch::Request#request_id</tt> or the alias <tt>ActionDispatch::Request#uuid</tt>) and sends + # the same id to the client via the X-Request-Id header. # # The unique request id is either based on the X-Request-Id header in the request, which would typically be generated # by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the @@ -12,7 +13,7 @@ module ActionDispatch # The unique request id can be used to trace a request end-to-end and would typically end up being part of log files # from multiple pieces of the stack. class RequestId - X_REQUEST_ID = "X-Request-Id".freeze # :nodoc: + X_REQUEST_ID = "X-Request-Id".freeze #:nodoc: def initialize(app) @app = app diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index 5fb5953811..49b82e7128 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -1,34 +1,23 @@ -require 'rack/utils' -require 'rack/request' -require 'rack/session/abstract/id' -require 'action_dispatch/middleware/cookies' -require 'action_dispatch/request/session' +require "rack/utils" +require "rack/request" +require "rack/session/abstract/id" +require "action_dispatch/middleware/cookies" +require "action_dispatch/request/session" module ActionDispatch module Session class SessionRestoreError < StandardError #:nodoc: - - def initialize(const_error = nil) - if const_error - ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \ - "Exceptions will automatically capture the original exception.", caller) - end - + 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" + "(Original exception: #{$!.message} [#{$!.class}])\n") set_backtrace $!.backtrace end - - def original_exception - ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller) - cause - end end module Compatibility def initialize(app, options = {}) - options[:key] ||= '_session_id' + options[:key] ||= "_session_id" super end @@ -46,9 +35,9 @@ module ActionDispatch end private - def make_request(env) - ActionDispatch::Request.new env - end + def make_request(env) + ActionDispatch::Request.new env + end end module StaleSessionCheck @@ -94,9 +83,9 @@ module ActionDispatch private - def set_cookie(request, session_id, cookie) - request.cookie_jar[key] = cookie - end + def set_cookie(request, session_id, cookie) + request.cookie_jar[key] = cookie + end end end end diff --git a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb index 589ae46e38..71274bc13a 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb @@ -1,4 +1,4 @@ -require 'action_dispatch/middleware/session/abstract_store' +require "action_dispatch/middleware/session/abstract_store" module ActionDispatch module Session @@ -19,7 +19,7 @@ module ActionDispatch # Get a session from the cache. def find_session(env, sid) - unless sid and session = @cache.read(cache_key(sid)) + unless sid && (session = @cache.read(cache_key(sid))) sid, session = generate_sid, {} end [sid, session] @@ -29,7 +29,7 @@ module ActionDispatch def write_session(env, sid, session, options) key = cache_key(sid) if session - @cache.write(key, session, :expires_in => options[:expire_after]) + @cache.write(key, session, expires_in: options[:expire_after]) else @cache.delete(key) end diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 429a98f236..8409109ede 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -1,6 +1,6 @@ -require 'active_support/core_ext/hash/keys' -require 'action_dispatch/middleware/session/abstract_store' -require 'rack/session/cookie' +require "active_support/core_ext/hash/keys" +require "action_dispatch/middleware/session/abstract_store" +require "rack/session/cookie" module ActionDispatch module Session @@ -23,7 +23,7 @@ module ActionDispatch # goes a step further than signed cookies in that encrypted cookies cannot # be altered or read by users. This is the default starting in Rails 4. # - # If you have both secret_token and secret_key base set, your cookies will + # If you have both secret_token and secret_key_base set, your cookies will # be encrypted, and signed cookies generated by Rails 3 will be # transparently read and encrypted to provide a smooth upgrade path. # @@ -64,7 +64,7 @@ module ActionDispatch # <tt>:httponly</tt>. class CookieStore < AbstractStore def initialize(app, options={}) - super(app, options.merge!(:cookie_only => true)) + super(app, options.merge!(cookie_only: true)) end def delete_session(req, session_id, options) @@ -84,46 +84,46 @@ module ActionDispatch private - def extract_session_id(req) - stale_session_check! do - unpacked_cookie_data(req)["session_id"] + def extract_session_id(req) + stale_session_check! do + unpacked_cookie_data(req)["session_id"] + end end - end - def unpacked_cookie_data(req) - req.fetch_header("action_dispatch.request.unsigned_session_cookie") do |k| - v = stale_session_check! do - if data = get_cookie(req) - data.stringify_keys! + def unpacked_cookie_data(req) + req.fetch_header("action_dispatch.request.unsigned_session_cookie") do |k| + v = stale_session_check! do + if data = get_cookie(req) + data.stringify_keys! + end + data || {} end - data || {} + req.set_header k, v end - req.set_header k, v end - end - def persistent_session_id!(data, sid=nil) - data ||= {} - data["session_id"] ||= sid || generate_sid - data - end + def persistent_session_id!(data, sid=nil) + data ||= {} + data["session_id"] ||= sid || generate_sid + data + end - def write_session(req, sid, session_data, options) - session_data["session_id"] = sid - session_data - end + def write_session(req, sid, session_data, options) + session_data["session_id"] = sid + session_data + end - def set_cookie(request, session_id, cookie) - cookie_jar(request)[@key] = cookie - end + def set_cookie(request, session_id, cookie) + cookie_jar(request)[@key] = cookie + end - def get_cookie(req) - cookie_jar(req)[@key] - end + def get_cookie(req) + cookie_jar(req)[@key] + end - def cookie_jar(request) - request.cookie_jar.signed_or_encrypted - end + def cookie_jar(request) + request.cookie_jar.signed_or_encrypted + end end end end diff --git a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb index cb19786f0b..ee2b1f26ad 100644 --- a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb @@ -1,6 +1,6 @@ -require 'action_dispatch/middleware/session/abstract_store' +require "action_dispatch/middleware/session/abstract_store" begin - require 'rack/session/dalli' + require "rack/session/dalli" rescue LoadError => e $stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install" raise e diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 64695f9738..90f26a1c33 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -1,5 +1,5 @@ -require 'action_dispatch/http/request' -require 'action_dispatch/middleware/exception_wrapper' +require "action_dispatch/http/request" +require "action_dispatch/middleware/exception_wrapper" module ActionDispatch # This middleware rescues any exception returned by the application @@ -15,7 +15,7 @@ module ActionDispatch # If any exception happens inside the exceptions app, this middleware # catches the exceptions and returns a FAILSAFE_RESPONSE. class ShowExceptions - FAILSAFE_RESPONSE = [500, { 'Content-Type' => 'text/plain' }, + FAILSAFE_RESPONSE = [500, { "Content-Type" => "text/plain" }, ["500 Internal Server Error\n" \ "If you are the administrator of this website, then please read this web " \ "application's log file and/or the web server's log file to find out what " \ @@ -39,22 +39,22 @@ module ActionDispatch private - def render_exception(request, exception) - backtrace_cleaner = request.get_header 'action_dispatch.backtrace_cleaner' - wrapper = ExceptionWrapper.new(backtrace_cleaner, exception) - status = wrapper.status_code - request.set_header "action_dispatch.exception", wrapper.exception - request.set_header "action_dispatch.original_path", request.path_info - request.path_info = "/#{status}" - response = @exceptions_app.call(request.env) - response[1]['X-Cascade'] == 'pass' ? pass_response(status) : response - rescue Exception => failsafe_error - $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}" - FAILSAFE_RESPONSE - end + def render_exception(request, exception) + backtrace_cleaner = request.get_header "action_dispatch.backtrace_cleaner" + wrapper = ExceptionWrapper.new(backtrace_cleaner, exception) + status = wrapper.status_code + request.set_header "action_dispatch.exception", wrapper.exception + request.set_header "action_dispatch.original_path", request.path_info + request.path_info = "/#{status}" + response = @exceptions_app.call(request.env) + response[1]["X-Cascade"] == "pass" ? pass_response(status) : response + rescue Exception => failsafe_error + $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}" + FAILSAFE_RESPONSE + end - def pass_response(status) - [status, {"Content-Type" => "text/html; charset=#{Response.default_charset}", "Content-Length" => "0"}, []] - end + def pass_response(status) + [status, { "Content-Type" => "text/html; charset=#{Response.default_charset}", "Content-Length" => "0" }, []] + end end end diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb index 735b5939dd..557721c301 100644 --- a/actionpack/lib/action_dispatch/middleware/ssl.rb +++ b/actionpack/lib/action_dispatch/middleware/ssl.rb @@ -18,45 +18,44 @@ module ActionDispatch # Enabled by default. Configure `config.ssl_options` with `hsts: false` to disable. # # Set `config.ssl_options` with `hsts: { … }` to configure HSTS: - # * `expires`: How long, in seconds, these settings will stick. Defaults to - # `180.days` (recommended). The minimum required to qualify for browser - # preload lists is `18.weeks`. + # * `expires`: How long, in seconds, these settings will stick. The minimum + # required to qualify for browser preload lists is `18.weeks`. Defaults to + # `180.days` (recommended). # * `subdomains`: Set to `true` to tell the browser to apply these settings # to all subdomains. This protects your cookies from interception by a - # vulnerable site on a subdomain. Defaults to `false`. + # vulnerable site on a subdomain. Defaults to `true`. # * `preload`: Advertise that this site may be included in browsers' # preloaded HSTS lists. HSTS protects your site on every visit *except the # first visit* since it hasn't seen your HSTS header yet. To close this # gap, browser vendors include a baked-in list of HSTS-enabled sites. # Go to https://hstspreload.appspot.com to submit your site for inclusion. + # Defaults to `false`. # # To turn off HSTS, omitting the header is not enough. Browsers will remember the # original HSTS directive until it expires. Instead, use the header to tell browsers to # expire HSTS immediately. Setting `hsts: false` is a shortcut for # `hsts: { expires: 0 }`. + # + # Requests can opt-out of redirection with `exclude`: + # + # config.ssl_options = { redirect: { exclude: -> request { request.path =~ /healthcheck/ } } } class SSL # Default to 180 days, the low end for https://www.ssllabs.com/ssltest/ # and greater than the 18-week requirement for browser preload lists. HSTS_EXPIRES_IN = 15552000 def self.default_hsts_options - { expires: HSTS_EXPIRES_IN, subdomains: false, preload: false } + { expires: HSTS_EXPIRES_IN, subdomains: true, preload: false } end - def initialize(app, redirect: {}, hsts: {}, secure_cookies: true, **options) + def initialize(app, redirect: {}, hsts: {}, secure_cookies: true) @app = app - if options[:host] || options[:port] - ActiveSupport::Deprecation.warn <<-end_warning.strip_heredoc - The `:host` and `:port` options are moving within `:redirect`: - `config.ssl_options = { redirect: { host: …, port: … }}`. - end_warning - @redirect = options.slice(:host, :port) - else - @redirect = redirect - end + @redirect = redirect + @exclude = @redirect && @redirect[:exclude] || proc { !@redirect } @secure_cookies = secure_cookies + @hsts_header = build_hsts_header(normalize_hsts_options(hsts)) end @@ -69,14 +68,14 @@ module ActionDispatch flag_cookies_as_secure! headers if @secure_cookies end else - return redirect_to_https request if @redirect + return redirect_to_https request unless @exclude.call(request) @app.call(env) end end private def set_hsts_header!(headers) - headers['Strict-Transport-Security'.freeze] ||= @hsts_header + headers["Strict-Transport-Security".freeze] ||= @hsts_header end def normalize_hsts_options(options) @@ -102,10 +101,10 @@ module ActionDispatch end def flag_cookies_as_secure!(headers) - if cookies = headers['Set-Cookie'.freeze] + if cookies = headers["Set-Cookie".freeze] cookies = cookies.split("\n".freeze) - headers['Set-Cookie'.freeze] = cookies.map { |cookie| + headers["Set-Cookie".freeze] = cookies.map { |cookie| if cookie !~ /;\s*secure\s*(;|$)/i "#{cookie}; secure" else @@ -116,12 +115,20 @@ module ActionDispatch end def redirect_to_https(request) - [ @redirect.fetch(:status, 301), - { 'Content-Type' => 'text/html', - 'Location' => https_location_for(request) }, + [ @redirect.fetch(:status, redirection_status(request)), + { "Content-Type" => "text/html", + "Location" => https_location_for(request) }, @redirect.fetch(:body, []) ] end + def redirection_status(request) + if request.get? || request.head? + 301 # Issue a permanent redirect via a GET request. + else + 307 # Issue a fresh request redirect to preserve the HTTP method. + end + end + def https_location_for(request) host = @redirect[:host] || request.host port = @redirect[:port] || request.port diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb index 0b4bee5462..6949b31e75 100644 --- a/actionpack/lib/action_dispatch/middleware/stack.rb +++ b/actionpack/lib/action_dispatch/middleware/stack.rb @@ -88,7 +88,6 @@ module ActionDispatch end def delete(target) - target = get_class target middlewares.delete_if { |m| m.klass == target } end @@ -102,32 +101,14 @@ module ActionDispatch private - def assert_index(index, where) - index = get_class index - i = index.is_a?(Integer) ? index : middlewares.index { |m| m.klass == index } - raise "No such middleware to insert #{where}: #{index.inspect}" unless i - i - end - - def get_class(klass) - if klass.is_a?(String) || klass.is_a?(Symbol) - classcache = ActiveSupport::Dependencies::Reference - converted_klass = classcache[klass.to_s] - ActiveSupport::Deprecation.warn <<-eowarn -Passing strings or symbols to the middleware builder is deprecated, please change -them to actual class references. For example: - - "#{klass}" => #{converted_klass} - - eowarn - converted_klass - else - klass + def assert_index(index, where) + i = index.is_a?(Integer) ? index : middlewares.index { |m| m.klass == index } + raise "No such middleware to insert #{where}: #{index.inspect}" unless i + i end - end - def build_middleware(klass, args, block) - Middleware.new(get_class(klass), args, block) - end + def build_middleware(klass, args, block) + Middleware.new(klass, args, block) + end end end diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index 41c220236a..5c71f0fc48 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -1,5 +1,5 @@ -require 'rack/utils' -require 'active_support/core_ext/uri' +require "rack/utils" +require "active_support/core_ext/uri" module ActionDispatch # This middleware returns a file's contents from disk in the body response. @@ -13,8 +13,8 @@ module ActionDispatch # located at `public/assets/application.js` if the file exists. If the file # does not exist, a 404 "File not Found" response will be returned. class FileHandler - def initialize(root, index: 'index', headers: {}) - @root = root.chomp('/') + def initialize(root, index: "index", headers: {}) + @root = root.chomp("/") @file_server = ::Rack::File.new(@root, headers) @index = index end @@ -27,13 +27,13 @@ module ActionDispatch # in the server's `public/` directory (see Static#call). def match?(path) path = ::Rack::Utils.unescape_path path - return false unless valid_path?(path) - path = Rack::Utils.clean_path_info path + return false unless ::Rack::Utils.valid_path? path + path = ::Rack::Utils.clean_path_info path 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("UTF-8".freeze)) begin File.file?(path) && File.readable?(path) rescue SystemCallError @@ -46,7 +46,7 @@ module ActionDispatch end def call(env) - serve ActionDispatch::Request.new env + serve(Rack::Request.new(env)) end def serve(request) @@ -59,13 +59,13 @@ module ActionDispatch if status == 304 return [status, headers, body] end - headers['Content-Encoding'] = 'gzip' - headers['Content-Type'] = content_type(path) + headers["Content-Encoding"] = "gzip" + headers["Content-Type"] = content_type(path) else status, headers, body = @file_server.call(request.env) end - headers['Vary'] = 'Accept-Encoding' if gzip_path + headers["Vary"] = "Accept-Encoding" if gzip_path return [status, headers, body] ensure @@ -78,11 +78,11 @@ module ActionDispatch end def content_type(path) - ::Rack::Mime.mime_type(::File.extname(path), 'text/plain'.freeze) + ::Rack::Mime.mime_type(::File.extname(path), "text/plain".freeze) end def gzip_encoding_accepted?(request) - request.accept_encoding =~ /\bgzip\b/i + request.accept_encoding.any? { |enc, quality| enc =~ /\bgzip\b/i } end def gzip_file_path(path) @@ -94,10 +94,6 @@ module ActionDispatch false end end - - def valid_path?(path) - path.valid_encoding? && !path.include?("\0") - end end # This middleware will attempt to return the contents of a file's body from @@ -110,23 +106,16 @@ module ActionDispatch # produce a directory traversal using this middleware. Only 'GET' and 'HEAD' # requests will result in a file being returned. class Static - def initialize(app, path, deprecated_cache_control = :not_set, index: 'index', headers: {}) - if deprecated_cache_control != :not_set - ActiveSupport::Deprecation.warn("The `cache_control` argument is deprecated," \ - "replaced by `headers: { 'Cache-Control' => #{deprecated_cache_control} }`, " \ - " and will be removed in Rails 5.1.") - headers['Cache-Control'.freeze] = deprecated_cache_control - end - + def initialize(app, path, index: "index", headers: {}) @app = app @file_handler = FileHandler.new(path, index: index, headers: headers) end def call(env) - req = ActionDispatch::Request.new env + req = Rack::Request.new env if req.get? || req.head? - path = req.path_info.chomp('/'.freeze) + path = req.path_info.chomp("/".freeze) if match = @file_handler.match?(path) req.path_info = match return @file_handler.serve(req) diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index ddeea24bb3..48cc91bbfa 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -8,20 +8,20 @@ module ActionDispatch config.action_dispatch.show_exceptions = true config.action_dispatch.tld_length = 1 config.action_dispatch.ignore_accept_header = false - config.action_dispatch.rescue_templates = { } - config.action_dispatch.rescue_responses = { } + config.action_dispatch.rescue_templates = {} + config.action_dispatch.rescue_responses = {} config.action_dispatch.default_charset = nil config.action_dispatch.rack_cache = false - config.action_dispatch.http_auth_salt = 'http authentication' - config.action_dispatch.signed_cookie_salt = 'signed cookie' - config.action_dispatch.encrypted_cookie_salt = 'encrypted cookie' - config.action_dispatch.encrypted_signed_cookie_salt = 'signed encrypted cookie' + config.action_dispatch.http_auth_salt = "http authentication" + config.action_dispatch.signed_cookie_salt = "signed cookie" + config.action_dispatch.encrypted_cookie_salt = "encrypted cookie" + config.action_dispatch.encrypted_signed_cookie_salt = "signed encrypted cookie" config.action_dispatch.perform_deep_munge = true config.action_dispatch.default_headers = { - 'X-Frame-Options' => 'SAMEORIGIN', - 'X-XSS-Protection' => '1; mode=block', - 'X-Content-Type-Options' => 'nosniff' + "X-Frame-Options" => "SAMEORIGIN", + "X-XSS-Protection" => "1; mode=block", + "X-Content-Type-Options" => "nosniff" } config.eager_load_namespaces << ActionDispatch @@ -39,6 +39,8 @@ 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/session.rb b/actionpack/lib/action_dispatch/request/session.rb index 42890225fa..b883ca0f61 100644 --- a/actionpack/lib/action_dispatch/request/session.rb +++ b/actionpack/lib/action_dispatch/request/session.rb @@ -1,4 +1,4 @@ -require 'rack/session/abstract/id' +require "rack/session/abstract/id" module ActionDispatch class Request @@ -9,7 +9,7 @@ module ActionDispatch # Singleton object used to determine if an optional param wasn't specified Unspecified = Object.new - + # Creates a session hash, merging the properties of the previous session if any def self.create(store, req, default_options) session_was = find req @@ -198,28 +198,32 @@ module ActionDispatch @delegate.merge!(other) end + def each(&block) + to_hash.each(&block) + end + private - def load_for_read! - load! if !loaded? && exists? - end + def load_for_read! + load! if !loaded? && exists? + end - def load_for_write! - load! unless loaded? - end + def load_for_write! + load! unless loaded? + end - def load! - id, session = @by.load_session @req - options[:id] = id - @delegate.replace(stringify_keys(session)) - @loaded = true - end + def load! + id, session = @by.load_session @req + options[:id] = id + @delegate.replace(stringify_keys(session)) + @loaded = true + end - def stringify_keys(other) - other.each_with_object({}) { |(key, value), hash| - hash[key.to_s] = value - } - end + def stringify_keys(other) + other.each_with_object({}) { |(key, value), hash| + hash[key.to_s] = value + } + end end end end diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb index bb3df3c311..282bdbd2be 100644 --- a/actionpack/lib/action_dispatch/request/utils.rb +++ b/actionpack/lib/action_dispatch/request/utils.rb @@ -1,7 +1,6 @@ module ActionDispatch class Request class Utils # :nodoc: - mattr_accessor :perform_deep_munge self.perform_deep_munge = true @@ -64,4 +63,3 @@ module ActionDispatch end end end - diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index 79f9283f83..61ebd0b8db 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -73,14 +73,14 @@ module ActionDispatch # get 'post/:id' => 'posts#show' # post 'post/:id' => 'posts#create_comment' # + # Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same + # URL will route to the <tt>show</tt> action. + # # If your route needs to respond to more than one HTTP method (or all methods) then using the # <tt>:via</tt> option on <tt>match</tt> is preferable. # # match 'post/:id' => 'posts#show', via: [:get, :post] # - # Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same - # URL will route to the <tt>show</tt> action. - # # == Named routes # # Routes can be named by passing an <tt>:as</tt> option, @@ -89,7 +89,7 @@ module ActionDispatch # # Example: # - # # In routes.rb + # # In config/routes.rb # get '/login' => 'accounts#login', as: 'login' # # # With render, redirect_to, tests, etc. @@ -101,7 +101,7 @@ module ActionDispatch # # Use <tt>root</tt> as a shorthand to name a route for the root path "/". # - # # In routes.rb + # # In config/routes.rb # root to: 'blogs#index' # # # would recognize http://www.example.com/ as @@ -114,15 +114,15 @@ module ActionDispatch # Note: when using +controller+, the route is simply named after the # method you call on the block parameter rather than map. # - # # In routes.rb + # # In config/routes.rb # controller :blog do # get 'blog/show' => :list # get 'blog/delete' => :delete - # get 'blog/edit/:id' => :edit + # get 'blog/edit' => :edit # end # # # provides named routes for show, delete, and edit - # link_to @article.title, show_path(id: @article.id) + # link_to @article.title, blog_show_path(id: @article.id) # # == Pretty URLs # @@ -159,7 +159,7 @@ module ActionDispatch # # controller 'geocode' do # get 'geocode/:postalcode' => :show, constraints: { - # postalcode: /# Postcode format + # postalcode: /# Postalcode format # \d{5} #Prefix # (-\d{4})? #Suffix # /x @@ -196,7 +196,7 @@ module ActionDispatch # # Rails.application.reload_routes! # - # This will clear all named routes and reload routes.rb if the file has been modified from + # This will clear all named routes and reload config/routes.rb if the file has been modified from # last load. To absolutely force reloading, use <tt>reload!</tt>. # # == Testing Routes @@ -252,5 +252,14 @@ module ActionDispatch SEPARATORS = %w( / . ? ) #:nodoc: HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc: + + #:stopdoc: + INSECURE_URL_PARAMETERS_MESSAGE = <<-MSG.squish + Attempting to generate a URL from non-sanitized request parameters! + + An attacker can inject malicious data into the generated URL, such as + changing the host. Whitelist and sanitize passed parameters to be secure. + MSG + #:startdoc: end end diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index 983f1daeb3..b91ffb8419 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -1,5 +1,5 @@ -require 'delegate' -require 'active_support/core_ext/string/strip' +require "delegate" +require "active_support/core_ext/string/strip" module ActionDispatch module Routing @@ -33,15 +33,15 @@ module ActionDispatch end def controller - requirements[:controller] || ':controller' + parts.include?(:controller) ? ":controller" : requirements[:controller] end def action - requirements[:action] || ':action' + parts.include?(:action) ? ":action" : requirements[:action] end def internal? - controller.to_s =~ %r{\Arails/(info|mailers|welcome)} + internal end def engine? @@ -51,7 +51,7 @@ module ActionDispatch ## # This class is just used for displaying route information when someone - # executes `rake routes` or looks at the RoutingError page. + # executes `rails routes` or looks at the RoutingError page. # People should not use this class. class RoutesInspector # :nodoc: def initialize(routes) @@ -80,48 +80,48 @@ module ActionDispatch private - def normalize_filter(filter) - if filter.is_a?(Hash) && filter[:controller] - { controller: /#{filter[:controller].downcase.sub(/_?controller\z/, '').sub('::', '/')}/ } - elsif filter - { controller: /#{filter}/, action: /#{filter}/, verb: /#{filter}/, name: /#{filter}/, path: /#{filter}/ } + def normalize_filter(filter) + if filter.is_a?(Hash) && filter[:controller] + { controller: /#{filter[:controller].downcase.sub(/_?controller\z/, '').sub('::', '/')}/ } + elsif filter + { controller: /#{filter}/, action: /#{filter}/, verb: /#{filter}/, name: /#{filter}/, path: /#{filter}/ } + end end - end - def filter_routes(filter) - if filter - @routes.select do |route| - route_wrapper = RouteWrapper.new(route) - filter.any? { |default, value| route_wrapper.send(default) =~ value } + def filter_routes(filter) + if filter + @routes.select do |route| + route_wrapper = RouteWrapper.new(route) + filter.any? { |default, value| route_wrapper.send(default) =~ value } + end + else + @routes end - else - @routes end - end - def collect_routes(routes) - routes.collect do |route| - RouteWrapper.new(route) - end.reject(&:internal?).collect do |route| - collect_engine_routes(route) + def collect_routes(routes) + routes.collect do |route| + RouteWrapper.new(route) + end.reject(&:internal?).collect do |route| + collect_engine_routes(route) - { name: route.name, - verb: route.verb, - path: route.path, - reqs: route.reqs } + { name: route.name, + verb: route.verb, + path: route.path, + reqs: route.reqs } + end end - end - def collect_engine_routes(route) - name = route.endpoint - return unless route.engine? - return if @engines[name] + def collect_engine_routes(route) + name = route.endpoint + return unless route.engine? + return if @engines[name] - routes = route.rack_app.routes - if routes.is_a?(ActionDispatch::Routing::RouteSet) - @engines[name] = collect_routes(routes.routes) + routes = route.rack_app.routes + if routes.is_a?(ActionDispatch::Routing::RouteSet) + @engines[name] = collect_routes(routes.routes) + end end - end end class ConsoleFormatter @@ -161,7 +161,7 @@ module ActionDispatch private def draw_section(routes) - header_lengths = ['Prefix', 'Verb', 'URI Pattern'].map(&:length) + header_lengths = ["Prefix", "Verb", "URI Pattern"].map(&:length) name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max) routes.map do |r| diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index afbaa45d20..c412484895 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1,10 +1,8 @@ -require 'active_support/core_ext/hash/reverse_merge' -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' +require "active_support/core_ext/hash/slice" +require "active_support/core_ext/enumerable" +require "active_support/core_ext/array/extract_options" +require "action_dispatch/routing/redirection" +require "action_dispatch/routing/endpoint" module ActionDispatch module Routing @@ -42,7 +40,7 @@ module ActionDispatch end def serve(req) - return [ 404, {'X-Cascade' => 'pass'}, [] ] unless matches?(req) + return [ 404, { "X-Cascade" => "pass" }, [] ] unless matches?(req) @strategy.call @app, req end @@ -94,7 +92,7 @@ module ActionDispatch end def self.optional_format?(path, format) - format != false && !path.include?(':format') && !path.end_with?('/') + format != false && !path.include?(":format") && !path.end_with?("/") end def initialize(set, ast, defaults, controller, default_action, modyoule, to, formatted, scope_constraints, blocks, via, options_constraints, anchor, options) @@ -107,6 +105,7 @@ module ActionDispatch @ast = ast @anchor = anchor @via = via + @internal = options.delete(:internal) path_params = ast.find_all(&:symbol?).map(&:to_sym) @@ -120,7 +119,7 @@ module ActionDispatch if options_constraints.is_a?(Hash) @defaults = Hash[options_constraints.find_all { |key, default| - URL_OPTIONS.include?(key) && (String === default || Fixnum === default) + URL_OPTIONS.include?(key) && (String === default || Integer === default) }].merge @defaults @blocks = blocks constraints.merge! options_constraints @@ -137,6 +136,10 @@ module ActionDispatch @conditions = Hash[conditions] @defaults = formats[:defaults].merge(@defaults).merge(normalize_defaults(options)) + if path_params.include?(:action) && !@requirements.key?(:action) + @defaults[:action] ||= "index" + end + @required_defaults = (split_options[:required_defaults] || []).map(&:first) end @@ -148,7 +151,8 @@ module ActionDispatch required_defaults, defaults, request_method, - precedence) + precedence, + @internal) route end @@ -265,7 +269,7 @@ module ActionDispatch { requirements: { format: Regexp.compile(formatted) }, defaults: { format: formatted } } else - { requirements: { }, defaults: { } } + { requirements: {}, defaults: {} } end end @@ -328,7 +332,7 @@ module ActionDispatch def split_to(to) if to =~ /#/ - to.split('#') + to.split("#") else [] end @@ -609,7 +613,7 @@ module ActionDispatch target_as = name_for_action(options[:as], path) options[:via] ||= :all - match(path, options.merge(:to => app, :anchor => false, :format => false)) + match(path, options.merge(to: app, anchor: false, format: false)) define_generate_prefix(app, target_as) if rails_app self @@ -656,7 +660,7 @@ module ActionDispatch super(options) else prefix_options = options.slice(*_route.segment_keys) - prefix_options[:relative_url_root] = ''.freeze + prefix_options[:relative_url_root] = "".freeze # we must actually delete prefix segment keys to avoid passing them to next url_for _route.segment_keys.each { |k| options.delete(k) } _routes.url_helpers.send("#{name}_path", prefix_options) @@ -711,11 +715,7 @@ module ActionDispatch def map_method(method, args, &block) options = args.extract_options! options[:via] = method - if options.key?(:defaults) - defaults(options.delete(:defaults)) { match(*args, options, &block) } - else - match(*args, options, &block) - end + match(*args, options, &block) self end end @@ -809,7 +809,7 @@ module ActionDispatch options = args.extract_options!.dup scope = {} - options[:path] = args.flatten.join('/') if args.any? + options[:path] = args.flatten.join("/") if args.any? options[:constraints] ||= {} unless nested_scope? @@ -819,10 +819,10 @@ module ActionDispatch if options[:constraints].is_a?(Hash) defaults = options[:constraints].select do |k, v| - URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum)) + URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Integer)) end - (options[:defaults] ||= {}).reverse_merge!(defaults) + options[:defaults] = defaults.merge(options[:defaults] || {}) else block, options[:constraints] = options[:constraints], {} end @@ -833,7 +833,7 @@ module ActionDispatch end if options.key? :anchor - raise ArgumentError, 'anchor is ignored unless passed to `match`' + raise ArgumentError, "anchor is ignored unless passed to `match`" end @scope.options.each do |option| @@ -980,7 +980,7 @@ module ActionDispatch # resources :iphones # end def constraints(constraints = {}) - scope(:constraints => constraints) { yield } + scope(constraints: constraints) { yield } end # Allows you to set default parameters for a route, such as this: @@ -1057,6 +1057,10 @@ module ActionDispatch def merge_shallow_scope(parent, child) #:nodoc: child ? true : false end + + def merge_to_scope(parent, child) + child + end end # Resource routing allows you to quickly declare all of the common routes @@ -1548,11 +1552,13 @@ module ActionDispatch # match 'path' => 'controller#action', via: patch # match 'path', to: 'controller#action', via: :post # match 'path', 'otherpath', on: :member, via: :get - def match(path, *rest) + def match(path, *rest, &block) if rest.empty? && Hash === path options = path path, to = options.find { |name, _value| name.is_a?(String) } + raise ArgumentError, "Route path not specified" if path.nil? + case to when Symbol options[:action] = to @@ -1573,110 +1579,13 @@ module ActionDispatch paths = [path] + rest end - if options[:on] && !VALID_ON_OPTIONS.include?(options[:on]) - raise ArgumentError, "Unknown scope #{on.inspect} given to :on" - end - - if @scope[:controller] && @scope[:action] - options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}" - end - - controller = options.delete(:controller) || @scope[:controller] - option_path = options.delete :path - to = options.delete :to - via = Mapping.check_via Array(options.delete(:via) { - @scope[:via] - }) - formatted = options.delete(:format) { @scope[:format] } - anchor = options.delete(:anchor) { true } - options_constraints = options.delete(:constraints) || {} - - path_types = paths.group_by(&:class) - path_types.fetch(String, []).each do |_path| - route_options = options.dup - if _path && option_path - ActiveSupport::Deprecation.warn <<-eowarn -Specifying strings for both :path and the route path is deprecated. Change things like this: - - match #{_path.inspect}, :path => #{option_path.inspect} - -to this: - - match #{option_path.inspect}, :as => #{_path.inspect}, :action => #{path.inspect} - eowarn - route_options[:action] = _path - route_options[:as] = _path - _path = option_path - end - to = get_to_from_path(_path, to, route_options[:action]) - decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints) - end - - path_types.fetch(Symbol, []).each do |action| - route_options = options.dup - decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints) - end - - self - end - - def get_to_from_path(path, to, action) - return to if to || action - - path_without_format = path.sub(/\(\.:format\)$/, '') - if using_match_shorthand?(path_without_format) - path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1').tr("-", "_") - else - nil - end - end - - def using_match_shorthand?(path) - path =~ %r{^/?[-\w]+/[-\w/]+$} - end - - def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc: - if on = options.delete(:on) - send(on) { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) } + if options.key?(:defaults) + defaults(options.delete(:defaults)) { map_match(paths, options, &block) } else - case @scope.scope_level - when :resources - nested { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) } - when :resource - member { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) } - else - add_route(path, controller, options, _path, to, via, formatted, anchor, options_constraints) - end + map_match(paths, options, &block) end end - def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc: - path = path_for_action(action, _path) - raise ArgumentError, "path is required" if path.blank? - - action = action.to_s - - default_action = options.delete(:action) || @scope[:action] - - if action =~ /^[\w\-\/]+$/ - default_action ||= action.tr('-', '_') unless action.include?("/") - else - action = nil - end - - as = if !options.fetch(:as, true) # if it's set to nil or false - options.delete(:as) - else - name_for_action(options.delete(:as), action) - end - - path = Mapping.normalize_path URI.parser.escape(path), formatted - ast = Journey::Parser.parse path - - mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options) - @set.add_route(mapping, ast, as, anchor) - end - # You can specify what Rails should route "/" to with the root method: # # root to: 'pages#main' @@ -1693,7 +1602,7 @@ to this: def root(path, options = {}) if path.is_a?(String) options[:to] = path - elsif path.is_a?(Hash) and options.empty? + elsif path.is_a?(Hash) && options.empty? options = path else raise ArgumentError, "must be called with a path and/or options" @@ -1782,7 +1691,7 @@ to this: end def resource_scope(resource) #:nodoc: - @scope = @scope.new(:scope_level_resource => resource) + @scope = @scope.new(scope_level_resource: resource) controller(resource.resource_scope) { yield } ensure @@ -1790,7 +1699,7 @@ to this: end def nested_options #:nodoc: - options = { :as => parent_resource.member_name } + options = { as: parent_resource.member_name } options[:constraints] = { parent_resource.nested_param => param_constraint } if param_constraint? @@ -1817,8 +1726,8 @@ to this: end def shallow_scope #:nodoc: - scope = { :as => @scope[:shallow_prefix], - :path => @scope[:shallow_path] } + scope = { as: @scope[:shallow_prefix], + path: @scope[:shallow_path] } @scope = @scope.new scope yield @@ -1847,8 +1756,8 @@ to this: prefix = action end - if prefix && prefix != '/' && !prefix.empty? - Mapper.normalize_name prefix.to_s.tr('-', '_') + if prefix && prefix != "/" && !prefix.empty? + Mapper.normalize_name prefix.to_s.tr("-", "_") end end @@ -1864,7 +1773,7 @@ to this: end action_name = @scope.action_name(name_prefix, prefix, collection_name, member_name) - candidate = action_name.select(&:present?).join('_') + candidate = action_name.select(&:present?).join("_") unless candidate.empty? # If a name was not explicitly given, we check if it is valid @@ -1893,19 +1802,120 @@ to this: def api_only? @set.api_only? end + private - def path_scope(path) - @scope = @scope.new(path: merge_path_scope(@scope[:path], path)) - yield - ensure - @scope = @scope.parent - end + def path_scope(path) + @scope = @scope.new(path: merge_path_scope(@scope[:path], path)) + yield + ensure + @scope = @scope.parent + end - def match_root_route(options) - name = has_named_route?(:root) ? nil : :root - match '/', { :as => name, :via => :get }.merge!(options) - end + def map_match(paths, options) + if options[:on] && !VALID_ON_OPTIONS.include?(options[:on]) + raise ArgumentError, "Unknown scope #{on.inspect} given to :on" + end + + if @scope[:to] + options[:to] ||= @scope[:to] + end + + if @scope[:controller] && @scope[:action] + options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}" + end + + controller = options.delete(:controller) || @scope[:controller] + option_path = options.delete :path + to = options.delete :to + via = Mapping.check_via Array(options.delete(:via) { + @scope[:via] + }) + formatted = options.delete(:format) { @scope[:format] } + anchor = options.delete(:anchor) { true } + options_constraints = options.delete(:constraints) || {} + + path_types = paths.group_by(&:class) + path_types.fetch(String, []).each do |_path| + route_options = options.dup + if _path && option_path + raise ArgumentError, "Ambigous route definition. Both :path and the route path where specified as strings." + end + to = get_to_from_path(_path, to, route_options[:action]) + decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints) + end + + path_types.fetch(Symbol, []).each do |action| + route_options = options.dup + decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints) + end + + self + end + + def get_to_from_path(path, to, action) + return to if to || action + + path_without_format = path.sub(/\(\.:format\)$/, "") + if using_match_shorthand?(path_without_format) + path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1').tr("-", "_") + else + nil + end + end + + def using_match_shorthand?(path) + path =~ %r{^/?[-\w]+/[-\w/]+$} + end + + def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc: + if on = options.delete(:on) + send(on) { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) } + else + case @scope.scope_level + when :resources + nested { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) } + when :resource + member { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) } + else + add_route(path, controller, options, _path, to, via, formatted, anchor, options_constraints) + end + end + end + + def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc: + path = path_for_action(action, _path) + raise ArgumentError, "path is required" if path.blank? + + action = action.to_s + + default_action = options.delete(:action) || @scope[:action] + + if action =~ /^[\w\-\/]+$/ + default_action ||= action.tr("-", "_") unless action.include?("/") + else + action = nil + end + + as = if !options.fetch(:as, true) # if it's set to nil or false + options.delete(:as) + else + name_for_action(options.delete(:as), action) + end + + path = Mapping.normalize_path URI.parser.escape(path), formatted + ast = Journey::Parser.parse path + + mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options) + @set.add_route(mapping, ast, as, anchor) + end + + def match_root_route(options) + name = has_named_route?(name_for_action(:root, nil)) ? nil : :root + args = ["/", { as: name, via: :get }.merge!(options)] + + match(*args) + end end # Routing Concerns allow you to declare common routes that can be reused @@ -2016,7 +2026,7 @@ to this: class Scope # :nodoc: OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module, :controller, :action, :path_names, :constraints, - :shallow, :blocks, :defaults, :via, :format, :options] + :shallow, :blocks, :defaults, :via, :format, :options, :to] RESOURCE_SCOPES = [:resource, :resources] RESOURCE_METHOD_SCOPES = [:collection, :member, :new] @@ -2083,8 +2093,7 @@ to this: def each node = self - loop do - break if node.equal? NULL + until node.equal? NULL yield node node = node.parent end @@ -2097,7 +2106,7 @@ to this: def initialize(set) #:nodoc: @set = set - @scope = Scope.new({ :path_names => @set.resources_path_names }) + @scope = Scope.new(path_names: @set.resources_path_names) @concerns = {} end diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index 9934f5547a..4f1aaeefc8 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -4,7 +4,7 @@ module ActionDispatch # given an Active Record model instance. They are to be used in combination with # ActionController::Resources. # - # These methods are useful when you want to generate correct URL or path to a RESTful + # These methods are useful when you want to generate the correct URL or path to a RESTful # resource without having to know the exact type of the record in question. # # Nested resources and/or namespaces are also supported, as illustrated in the example: @@ -79,7 +79,7 @@ module ActionDispatch # polymorphic_url([blog, post], anchor: 'my_anchor', script_name: "/my_app") # # => "http://example.com/my_app/blogs/1/posts/1#my_anchor" # - # For all of these options, see the documentation for <tt>url_for</tt>. + # For all of these options, see the documentation for {url_for}[rdoc-ref:ActionDispatch::Routing::UrlFor]. # # ==== Functionality # @@ -134,7 +134,6 @@ module ActionDispatch opts end - %w(edit new).each do |action| module_eval <<-EOT, __FILE__, __LINE__ + 1 def #{action}_polymorphic_url(record_or_hash, options = {}) @@ -149,176 +148,175 @@ module ActionDispatch private - def polymorphic_url_for_action(action, record_or_hash, options) - polymorphic_url(record_or_hash, options.merge(:action => action)) - end + def polymorphic_url_for_action(action, record_or_hash, options) + polymorphic_url(record_or_hash, options.merge(action: action)) + end - def polymorphic_path_for_action(action, record_or_hash, options) - polymorphic_path(record_or_hash, options.merge(:action => action)) - end + def polymorphic_path_for_action(action, record_or_hash, options) + polymorphic_path(record_or_hash, options.merge(action: action)) + end - class HelperMethodBuilder # :nodoc: - CACHE = { 'path' => {}, 'url' => {} } + class HelperMethodBuilder # :nodoc: + CACHE = { "path" => {}, "url" => {} } - def self.get(action, type) - type = type.to_s - CACHE[type].fetch(action) { build action, type } - end + def self.get(action, type) + type = type.to_s + CACHE[type].fetch(action) { build action, type } + end - def self.url; CACHE['url'.freeze][nil]; end - def self.path; CACHE['path'.freeze][nil]; end + def self.url; CACHE["url".freeze][nil]; end + def self.path; CACHE["path".freeze][nil]; end - def self.build(action, type) - prefix = action ? "#{action}_" : "" - suffix = type - if action.to_s == 'new' - HelperMethodBuilder.singular prefix, suffix - else - HelperMethodBuilder.plural prefix, suffix + def self.build(action, type) + prefix = action ? "#{action}_" : "" + suffix = type + if action.to_s == "new" + HelperMethodBuilder.singular prefix, suffix + else + HelperMethodBuilder.plural prefix, suffix + end end - end - def self.singular(prefix, suffix) - new(->(name) { name.singular_route_key }, prefix, suffix) - end + def self.singular(prefix, suffix) + new(->(name) { name.singular_route_key }, prefix, suffix) + end - def self.plural(prefix, suffix) - new(->(name) { name.route_key }, prefix, suffix) - end + def self.plural(prefix, suffix) + new(->(name) { name.route_key }, prefix, suffix) + end - def self.polymorphic_method(recipient, record_or_hash_or_array, action, type, options) - builder = get action, type + def self.polymorphic_method(recipient, record_or_hash_or_array, action, type, options) + builder = get action, type + + case record_or_hash_or_array + when Array + record_or_hash_or_array = record_or_hash_or_array.compact + if record_or_hash_or_array.empty? + raise ArgumentError, "Nil location provided. Can't build URI." + end + if record_or_hash_or_array.first.is_a?(ActionDispatch::Routing::RoutesProxy) + recipient = record_or_hash_or_array.shift + end + + method, args = builder.handle_list record_or_hash_or_array + when String, Symbol + method, args = builder.handle_string record_or_hash_or_array + when Class + method, args = builder.handle_class record_or_hash_or_array - case record_or_hash_or_array - when Array - record_or_hash_or_array = record_or_hash_or_array.compact - if record_or_hash_or_array.empty? + when nil raise ArgumentError, "Nil location provided. Can't build URI." + else + method, args = builder.handle_model record_or_hash_or_array end - if record_or_hash_or_array.first.is_a?(ActionDispatch::Routing::RoutesProxy) - recipient = record_or_hash_or_array.shift - end - - method, args = builder.handle_list record_or_hash_or_array - when String, Symbol - method, args = builder.handle_string record_or_hash_or_array - when Class - method, args = builder.handle_class record_or_hash_or_array - when nil - raise ArgumentError, "Nil location provided. Can't build URI." - else - method, args = builder.handle_model record_or_hash_or_array + if options.empty? + recipient.send(method, *args) + else + recipient.send(method, *args, options) + end end + attr_reader :suffix, :prefix - if options.empty? - recipient.send(method, *args) - else - recipient.send(method, *args, options) + def initialize(key_strategy, prefix, suffix) + @key_strategy = key_strategy + @prefix = prefix + @suffix = suffix end - end - - attr_reader :suffix, :prefix - - def initialize(key_strategy, prefix, suffix) - @key_strategy = key_strategy - @prefix = prefix - @suffix = suffix - end - - def handle_string(record) - [get_method_for_string(record), []] - end - - def handle_string_call(target, str) - target.send get_method_for_string str - end - def handle_class(klass) - [get_method_for_class(klass), []] - end + def handle_string(record) + [get_method_for_string(record), []] + end - def handle_class_call(target, klass) - target.send get_method_for_class klass - end + def handle_string_call(target, str) + target.send get_method_for_string str + end - def handle_model(record) - args = [] + def handle_class(klass) + [get_method_for_class(klass), []] + end - model = record.to_model - named_route = if model.persisted? - args << model - get_method_for_string model.model_name.singular_route_key - else - get_method_for_class model - end + def handle_class_call(target, klass) + target.send get_method_for_class klass + end - [named_route, args] - end + def handle_model(record) + args = [] - def handle_model_call(target, model) - method, args = handle_model model - target.send(method, *args) - end + model = record.to_model + named_route = if model.persisted? + args << model + get_method_for_string model.model_name.singular_route_key + else + get_method_for_class model + end - def handle_list(list) - record_list = list.dup - record = record_list.pop + [named_route, args] + end - args = [] + def handle_model_call(target, model) + method, args = handle_model model + target.send(method, *args) + end - route = record_list.map { |parent| - case parent + def handle_list(list) + record_list = list.dup + record = record_list.pop + + args = [] + + route = record_list.map { |parent| + case parent + when Symbol, String + parent.to_s + when Class + args << parent + parent.model_name.singular_route_key + else + args << parent.to_model + parent.to_model.model_name.singular_route_key + end + } + + route << + case record when Symbol, String - parent.to_s + record.to_s when Class - args << parent - parent.model_name.singular_route_key + @key_strategy.call record.model_name else - args << parent.to_model - parent.to_model.model_name.singular_route_key + model = record.to_model + if model.persisted? + args << model + model.model_name.singular_route_key + else + @key_strategy.call model.model_name + end end - } - - route << - case record - when Symbol, String - record.to_s - when Class - @key_strategy.call record.model_name - else - model = record.to_model - if model.persisted? - args << model - model.model_name.singular_route_key - else - @key_strategy.call model.model_name - end - end - route << suffix + route << suffix - named_route = prefix + route.join("_") - [named_route, args] - end + named_route = prefix + route.join("_") + [named_route, args] + end - private + private - def get_method_for_class(klass) - name = @key_strategy.call klass.model_name - get_method_for_string name - end + def get_method_for_class(klass) + name = @key_strategy.call klass.model_name + get_method_for_string name + end - def get_method_for_string(str) - "#{prefix}#{str}_#{suffix}" - end + def get_method_for_string(str) + "#{prefix}#{str}_#{suffix}" + end - [nil, 'new', 'edit'].each do |action| - CACHE['url'][action] = build action, 'url' - CACHE['path'][action] = build action, 'path' + [nil, "new", "edit"].each do |action| + CACHE["url"][action] = build action, "url" + CACHE["path"][action] = build action, "path" + end end - end end end end diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb index d6987f4d09..87bcceccc0 100644 --- a/actionpack/lib/action_dispatch/routing/redirection.rb +++ b/actionpack/lib/action_dispatch/routing/redirection.rb @@ -1,9 +1,9 @@ -require 'action_dispatch/http/request' -require 'active_support/core_ext/uri' -require 'active_support/core_ext/array/extract_options' -require 'rack/utils' -require 'action_controller/metal/exceptions' -require 'action_dispatch/routing/endpoint' +require "action_dispatch/http/request" +require "active_support/core_ext/uri" +require "active_support/core_ext/array/extract_options" +require "rack/utils" +require "action_controller/metal/exceptions" +require "action_dispatch/routing/endpoint" module ActionDispatch module Routing @@ -22,7 +22,6 @@ module ActionDispatch end def serve(req) - req.check_path_parameters! uri = URI.parse(path(req.path_parameters, req)) unless uri.host @@ -40,9 +39,9 @@ module ActionDispatch body = %(<html><body>You are being <a href="#{ERB::Util.unwrapped_html_escape(uri.to_s)}">redirected</a>.</body></html>) headers = { - 'Location' => uri.to_s, - 'Content-Type' => 'text/html', - 'Content-Length' => body.length.to_s + "Location" => uri.to_s, + "Content-Type" => "text/html", + "Content-Length" => body.length.to_s } [ status, headers, [body] ] @@ -58,19 +57,19 @@ module ActionDispatch private def relative_path?(path) - path && !path.empty? && path[0] != '/' + path && !path.empty? && path[0] != "/" end def escape(params) - Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }] + Hash[params.map { |k,v| [k, Rack::Utils.escape(v)] }] end def escape_fragment(params) - Hash[params.map{ |k,v| [k, Journey::Router::Utils.escape_fragment(v)] }] + Hash[params.map { |k,v| [k, Journey::Router::Utils.escape_fragment(v)] }] end def escape_path(params) - Hash[params.map{ |k,v| [k, Journey::Router::Utils.escape_path(v)] }] + Hash[params.map { |k,v| [k, Journey::Router::Utils.escape_path(v)] }] end end @@ -104,11 +103,11 @@ module ActionDispatch def path(params, request) url_options = { - :protocol => request.protocol, - :host => request.host, - :port => request.optional_port, - :path => request.path, - :params => request.query_parameters + protocol: request.protocol, + host: request.host, + port: request.optional_port, + path: request.path, + params: request.query_parameters }.merge! options if !params.empty? && url_options[:path].match(/%\{\w*\}/) @@ -129,12 +128,11 @@ module ActionDispatch end def inspect - "redirect(#{status}, #{options.map{ |k,v| "#{k}: #{v}" }.join(', ')})" + "redirect(#{status}, #{options.map { |k,v| "#{k}: #{v}" }.join(', ')})" end end module Redirection - # Redirect any path to another path: # # get "/stories" => redirect("/posts") diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 846b5fa1fc..326329c65f 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -1,12 +1,11 @@ -require 'action_dispatch/journey' -require 'active_support/concern' -require 'active_support/core_ext/object/to_query' -require 'active_support/core_ext/hash/slice' -require 'active_support/core_ext/module/remove_method' -require 'active_support/core_ext/array/extract_options' -require 'action_controller/metal/exceptions' -require 'action_dispatch/http/request' -require 'action_dispatch/routing/endpoint' +require "action_dispatch/journey" +require "active_support/core_ext/object/to_query" +require "active_support/core_ext/hash/slice" +require "active_support/core_ext/module/remove_method" +require "active_support/core_ext/array/extract_options" +require "action_controller/metal/exceptions" +require "action_dispatch/http/request" +require "action_dispatch/routing/endpoint" module ActionDispatch module Routing @@ -34,7 +33,7 @@ module ActionDispatch if @raise_on_name_error raise else - return [404, {'X-Cascade' => 'pass'}, []] + return [404, { "X-Cascade" => "pass" }, []] end end @@ -59,7 +58,7 @@ module ActionDispatch private - def controller(_); @controller_class; end + def controller(_); @controller_class; end end # A NamedRouteCollection instance is a collection of named routes, and also @@ -180,40 +179,40 @@ module ActionDispatch private - def optimized_helper(args) - params = parameterize_args(args) do - raise_generation_error(args) - end + def optimized_helper(args) + params = parameterize_args(args) do + raise_generation_error(args) + end - @route.format params - end + @route.format params + end - def optimize_routes_generation?(t) - t.send(:optimize_routes_generation?) - end + def optimize_routes_generation?(t) + t.send(:optimize_routes_generation?) + end - def parameterize_args(args) - params = {} - @arg_size.times { |i| - key = @required_parts[i] - value = args[i].to_param - yield key if value.nil? || value.empty? - params[key] = value - } - params - end + def parameterize_args(args) + params = {} + @arg_size.times { |i| + key = @required_parts[i] + value = args[i].to_param + yield key if value.nil? || value.empty? + params[key] = value + } + params + end - def raise_generation_error(args) - missing_keys = [] - params = parameterize_args(args) { |missing_key| - missing_keys << missing_key - } - constraints = Hash[@route.requirements.merge(params).sort_by{|k,v| k.to_s}] - message = "No route matches #{constraints.inspect}" - message << " missing required keys: #{missing_keys.sort.inspect}" + def raise_generation_error(args) + missing_keys = [] + params = parameterize_args(args) { |missing_key| + missing_keys << missing_key + } + constraints = Hash[@route.requirements.merge(params).sort_by { |k,v| k.to_s }] + message = "No route matches #{constraints.inspect}" + message << ", missing required keys: #{missing_keys.sort.inspect}" - raise ActionController::UrlGenerationError, message - end + raise ActionController::UrlGenerationError, message + end end def initialize(route, options, route_name, url_strategy) @@ -264,38 +263,39 @@ module ActionDispatch end private - # Create a url helper allowing ordered parameters to be associated - # with corresponding dynamic segments, so you can do: - # - # foo_url(bar, baz, bang) - # - # Instead of: - # - # foo_url(bar: bar, baz: baz, bang: bang) - # - # Also allow options hash, so you can do: - # - # foo_url(bar, baz, bang, sort_by: 'baz') - # - def define_url_helper(mod, route, name, opts, route_key, url_strategy) - helper = UrlHelper.create(route, opts, route_key, url_strategy) - mod.module_eval do - define_method(name) do |*args| - last = args.last - options = case last - when Hash - args.pop - when ActionController::Parameters - if last.permitted? - args.pop.to_h - else - raise ArgumentError, "Generating an URL from non sanitized request parameters is insecure!" - end - end - helper.call self, args, options + # Create a url helper allowing ordered parameters to be associated + # with corresponding dynamic segments, so you can do: + # + # foo_url(bar, baz, bang) + # + # Instead of: + # + # foo_url(bar: bar, baz: baz, bang: bang) + # + # Also allow options hash, so you can do: + # + # foo_url(bar, baz, bang, sort_by: 'baz') + # + def define_url_helper(mod, route, name, opts, route_key, url_strategy) + helper = UrlHelper.create(route, opts, route_key, url_strategy) + mod.module_eval do + define_method(name) do |*args| + last = args.last + options = \ + case last + when Hash + args.pop + when ActionController::Parameters + if last.permitted? + args.pop.to_h + else + raise ArgumentError, ActionDispatch::Routing::INSECURE_URL_PARAMETERS_MESSAGE + end + end + helper.call self, args, options + end end end - end end # strategy for building urls to send to the client @@ -310,7 +310,7 @@ module ActionDispatch alias :routes :set def self.default_resources_path_names - { :new => 'new', :edit => 'edit' } + { new: "new", edit: "edit" } end def self.new_with_config(config) @@ -513,6 +513,21 @@ module ActionDispatch route = @set.add_route(name, mapping) named_routes[name] = route if name + + if route.segment_keys.include?(:controller) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using a dynamic :controller segment in a route is deprecated and + will be removed in Rails 5.1. + MSG + end + + if route.segment_keys.include?(:action) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using a dynamic :action segment in a route is deprecated and + will be removed in Rails 5.1. + MSG + end + route end @@ -533,12 +548,10 @@ module ActionDispatch @recall = recall @set = set - normalize_recall! normalize_options! normalize_controller_action_id! use_relative_controller! normalize_controller! - normalize_action! end def controller @@ -557,11 +570,6 @@ module ActionDispatch end end - # Set 'index' as default action for recall - def normalize_recall! - @recall[:action] ||= 'index' - end - def normalize_options! # If an explicit :controller was given, always make :action explicit # too, so that action expiry works as expected for things like @@ -573,12 +581,12 @@ module ActionDispatch # be "index", not the recalled action of "show". if options[:controller] - options[:action] ||= 'index' + options[:action] ||= "index" options[:controller] = options[:controller].to_s end if options.key?(:action) - options[:action] = (options[:action] || 'index').to_s + options[:action] = (options[:action] || "index").to_s end end @@ -588,8 +596,8 @@ module ActionDispatch # :controller, :action or :id is not found, don't pull any # more keys from the recall. def normalize_controller_action_id! - use_recall_for(:controller) or return - use_recall_for(:action) or return + use_recall_for(:controller) || return + use_recall_for(:action) || return use_recall_for(:id) end @@ -597,7 +605,7 @@ module ActionDispatch # is specified, the controller becomes "foo/baz/bat" def use_relative_controller! if !named_route && different_controller? && !controller.start_with?("/") - old_parts = current_controller.split('/') + old_parts = current_controller.split("/") size = controller.count("/") + 1 parts = old_parts[0...-size] << controller @options[:controller] = parts.join("/") @@ -615,13 +623,6 @@ module ActionDispatch end end - # Move 'index' action from options to recall - def normalize_action! - if @options[:action] == 'index'.freeze - @recall[:action] = @options.delete(:action) - end - end - # Generates a path from routes, returns [path, params]. # If no route is generated the formatter will raise ActionController::UrlGenerationError def generate @@ -669,7 +670,7 @@ module ActionDispatch end def find_script_name(options) - options.delete(:script_name) || find_relative_url_root(options) || '' + options.delete(:script_name) || find_relative_url_root(options) || "" end def find_relative_url_root(options) @@ -730,7 +731,7 @@ module ActionDispatch extras = environment[:extras] || {} begin - env = Rack::MockRequest.env_for(path, {:method => method}) + env = Rack::MockRequest.env_for(path, method: method) rescue URI::InvalidURIError => e raise ActionController::RoutingError, e.message end diff --git a/actionpack/lib/action_dispatch/routing/routes_proxy.rb b/actionpack/lib/action_dispatch/routing/routes_proxy.rb index 040ea04046..ee847eaeed 100644 --- a/actionpack/lib/action_dispatch/routing/routes_proxy.rb +++ b/actionpack/lib/action_dispatch/routing/routes_proxy.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/array/extract_options' +require "active_support/core_ext/array/extract_options" module ActionDispatch module Routing @@ -19,7 +19,7 @@ module ActionDispatch end end - def respond_to?(method, include_private = false) + def respond_to_missing?(method, include_private = false) super || @helpers.respond_to?(method) end diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index f91679593e..a1ac5a2b6c 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -173,7 +173,7 @@ module ActionDispatch route_name) when ActionController::Parameters unless options.permitted? - raise ArgumentError.new("Generating an URL from non sanitized request parameters is insecure!") + raise ArgumentError.new(ActionDispatch::Routing::INSECURE_URL_PARAMETERS_MESSAGE) end route_name = options.delete :use_route _routes.url_for(options.to_h.symbolize_keys. @@ -194,20 +194,20 @@ module ActionDispatch protected - def optimize_routes_generation? - _routes.optimize_routes_generation? && default_url_options.empty? - end + def optimize_routes_generation? + _routes.optimize_routes_generation? && default_url_options.empty? + end - def _with_routes(routes) - old_routes, @_routes = @_routes, routes - yield - ensure - @_routes = old_routes - end + def _with_routes(routes) + old_routes, @_routes = @_routes, routes + yield + ensure + @_routes = old_routes + end - def _routes_context - self - end + def _routes_context + self + end end end end diff --git a/actionpack/lib/action_dispatch/testing/assertion_response.rb b/actionpack/lib/action_dispatch/testing/assertion_response.rb index 3fb81ff083..c37726957e 100644 --- a/actionpack/lib/action_dispatch/testing/assertion_response.rb +++ b/actionpack/lib/action_dispatch/testing/assertion_response.rb @@ -1,14 +1,7 @@ module ActionDispatch - # This is a class that abstracts away an asserted response. - # It purposely does not inherit from Response, because it doesn't need it. - # That means it does not have headers or a body. - # - # As an input to the initializer, we take a Fixnum, a String, or a Symbol. - # If it's a Fixnum or String, we figure out what its symbolized name. - # If it's a Symbol, we figure out what its corresponding code is. - # The resulting code will be a Fixnum, for real HTTP codes, and it will - # be a String for the pseudo-HTTP codes, such as: - # :success, :missing, :redirect and :error + # This is a class that abstracts away an asserted response. It purposely + # does not inherit from Response because it doesn't need it. That means it + # does not have headers or a body. class AssertionResponse attr_reader :code, :name @@ -19,6 +12,9 @@ module ActionDispatch error: "5XX" } + # Accepts a specific response status code as an Integer (404) or String + # ('404') or a response status range as a Symbol pseudo-code (:success, + # indicating any 200-299 status code). def initialize(code_or_name) if code_or_name.is_a?(Symbol) @name = code_or_name @@ -38,12 +34,12 @@ module ActionDispatch private - def code_from_name(name) - GENERIC_RESPONSE_CODES[name] || Rack::Utils::SYMBOL_TO_STATUS_CODE[name] - end + def code_from_name(name) + GENERIC_RESPONSE_CODES[name] || Rack::Utils::SYMBOL_TO_STATUS_CODE[name] + end - def name_from_code(code) - GENERIC_RESPONSE_CODES.invert[code] || Rack::Utils::HTTP_STATUS_CODES[code] - end + def name_from_code(code) + GENERIC_RESPONSE_CODES.invert[code] || Rack::Utils::HTTP_STATUS_CODES[code] + end end end diff --git a/actionpack/lib/action_dispatch/testing/assertions.rb b/actionpack/lib/action_dispatch/testing/assertions.rb index fae266273e..b362931ef7 100644 --- a/actionpack/lib/action_dispatch/testing/assertions.rb +++ b/actionpack/lib/action_dispatch/testing/assertions.rb @@ -1,9 +1,9 @@ -require 'rails-dom-testing' +require "rails-dom-testing" module ActionDispatch module Assertions - autoload :ResponseAssertions, 'action_dispatch/testing/assertions/response' - autoload :RoutingAssertions, 'action_dispatch/testing/assertions/routing' + autoload :ResponseAssertions, "action_dispatch/testing/assertions/response" + autoload :RoutingAssertions, "action_dispatch/testing/assertions/routing" extend ActiveSupport::Concern diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index cd55b7d975..a2eaccd9ef 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -79,11 +79,16 @@ module ActionDispatch def generate_response_message(expected, actual = @response.response_code) "Expected response to be a <#{code_with_name(expected)}>,"\ " but was a <#{code_with_name(actual)}>" - .concat location_if_redirected + .concat(location_if_redirected).concat(response_body_if_short) + end + + def response_body_if_short + return "" if @response.body.size > 500 + "\nResponse body: #{@response.body}" end def location_if_redirected - return '' unless @response.redirection? && @response.location.present? + return "" unless @response.redirection? && @response.location.present? location = normalize_argument_to_redirection(@response.location) " redirect to <#{location}>" end diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index e7af27463c..e53bc6af12 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -1,7 +1,7 @@ -require 'uri' -require 'active_support/core_ext/hash/indifferent_access' -require 'active_support/core_ext/string/access' -require 'action_controller/metal/exceptions' +require "uri" +require "active_support/core_ext/hash/indifferent_access" +require "active_support/core_ext/string/access" +require "action_controller/metal/exceptions" module ActionDispatch module Assertions @@ -82,7 +82,7 @@ module ActionDispatch expected_path = uri.path.to_s.empty? ? "/" : uri.path end else - expected_path = "/#{expected_path}" unless expected_path.first == '/' + expected_path = "/#{expected_path}" unless expected_path.first == "/" end # Load routes.rb if it hasn't been loaded. @@ -117,7 +117,7 @@ module ActionDispatch # # Tests a route, providing a defaults hash # assert_routing 'controller/action/9', {id: "9", item: "square"}, {controller: "controller", action: "action"}, {}, {item: "square"} # - # # Tests a route with a HTTP method + # # Tests a route with an HTTP method # assert_routing({ method: 'put', path: '/product/321' }, { controller: "product", action: "update", id: "321" }) def assert_routing(path, options, defaults={}, extras={}, message=nil) assert_recognizes(options, path, extras, message) @@ -127,7 +127,7 @@ module ActionDispatch options[:controller] = "/#{controller}" end - generate_options = options.dup.delete_if{ |k, _| defaults.key?(k) } + generate_options = options.dup.delete_if { |k, _| defaults.key?(k) } assert_generates(path.is_a?(Hash) ? path[:path] : path, generate_options, defaults, extras, message) end @@ -184,7 +184,7 @@ module ActionDispatch end # Assume given controller - request = ActionController::TestRequest.create + request = ActionController::TestRequest.create @controller.class if path =~ %r{://} fail_on(URI::InvalidURIError, msg) do @@ -202,7 +202,7 @@ module ActionDispatch request.request_method = method if method params = fail_on(ActionController::RoutingError, msg) do - @routes.recognize_path(path, { :method => method, :extras => extras }) + @routes.recognize_path(path, method: method, extras: extras) end request.path_parameters = params.with_indifferent_access diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index f4534b4173..e2ea96513c 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -1,106 +1,51 @@ -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' +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" + +require "action_dispatch/testing/request_encoder" module ActionDispatch module Integration #:nodoc: module RequestHelpers - # Performs a GET request with the given parameters. - # - # - +path+: The URI (as a String) on which you want to perform a GET - # request. - # - +params+: The HTTP parameters that you want to pass. This may - # be +nil+, - # a Hash, or a String that is appropriately encoded - # (<tt>application/x-www-form-urlencoded</tt> or - # <tt>multipart/form-data</tt>). - # - +headers+: Additional headers to pass, as a Hash. The headers will be - # merged into the Rack env hash. - # - +env+: Additional env to pass, as a Hash. The headers will be - # merged into the Rack env hash. - # - # This method returns a Response object, which one can use to - # inspect the details of the response. Furthermore, if this method was - # called from an ActionDispatch::IntegrationTest object, then that - # object's <tt>@response</tt> instance variable will point to the same - # response object. - # - # You can also perform POST, PATCH, PUT, DELETE, and HEAD requests with - # +#post+, +#patch+, +#put+, +#delete+, and +#head+. - # - # Example: - # - # get '/feed', params: { since: 201501011400 } - # post '/profile', headers: { "X-Test-Header" => "testvalue" } - def get(path, *args) - process_with_kwargs(:get, path, *args) + # Performs a GET request with the given parameters. See +#process+ for more + # details. + def get(path, **args) + process(:get, path, **args) end - # Performs a POST request with the given parameters. See +#get+ for more + # Performs a POST request with the given parameters. See +#process+ for more # details. - def post(path, *args) - process_with_kwargs(:post, path, *args) + def post(path, **args) + process(:post, path, **args) end - # Performs a PATCH request with the given parameters. See +#get+ for more + # Performs a PATCH request with the given parameters. See +#process+ for more # details. - def patch(path, *args) - process_with_kwargs(:patch, path, *args) + def patch(path, **args) + process(:patch, path, **args) end - # Performs a PUT request with the given parameters. See +#get+ for more + # Performs a PUT request with the given parameters. See +#process+ for more # details. - def put(path, *args) - process_with_kwargs(:put, path, *args) + def put(path, **args) + process(:put, path, **args) end - # Performs a DELETE request with the given parameters. See +#get+ for + # Performs a DELETE request with the given parameters. See +#process+ for # more details. - def delete(path, *args) - process_with_kwargs(:delete, path, *args) + def delete(path, **args) + process(:delete, path, **args) end - # Performs a HEAD request with the given parameters. See +#get+ for more + # Performs a HEAD request with the given parameters. See +#process+ for more # details. def head(path, *args) - process_with_kwargs(:head, path, *args) - end - - # Performs an XMLHttpRequest request with the given parameters, mirroring - # an AJAX request made from JavaScript. - # - # The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or - # +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart - # string; the headers are a hash. - # - # Example: - # - # xhr :get, '/feed', params: { since: 201501011400 } - def xml_http_request(request_method, path, *args) - if kwarg_request?(args) - params, headers, env = args.first.values_at(:params, :headers, :env) - else - params = args[0] - headers = args[1] - env = {} - - if params.present? || headers.present? - non_kwarg_request_warning - end - end - - ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc) - xhr and xml_http_request methods are deprecated in favor of - `get "/posts", xhr: true` and `post "/posts/1", xhr: true` - MSG - - process(request_method, path, params: params, headers: headers, xhr: true) + process(:head, path, *args) end - alias xhr :xml_http_request # Follow a single redirect response. If the last response was not a # redirect, an exception will be raised. Otherwise, the redirect is @@ -110,58 +55,6 @@ module ActionDispatch get(response.location) status end - - # Performs a request using the specified method, following any subsequent - # redirect. Note that the redirects are followed until the response is - # not a redirect--this means you may run into an infinite loop if your - # redirect loops back to itself. - # - # Example: - # - # request_via_redirect :post, '/welcome', - # params: { ref_id: 14 }, - # headers: { "X-Test-Header" => "testvalue" } - def request_via_redirect(http_method, path, *args) - process_with_kwargs(http_method, path, *args) - - follow_redirect! while redirect? - status - end - - # Performs a GET request, following any subsequent redirect. - # See +request_via_redirect+ for more information. - def get_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`get_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') - request_via_redirect(:get, path, *args) - end - - # Performs a POST request, following any subsequent redirect. - # See +request_via_redirect+ for more information. - def post_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`post_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') - request_via_redirect(:post, path, *args) - end - - # Performs a PATCH request, following any subsequent redirect. - # See +request_via_redirect+ for more information. - def patch_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`patch_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') - request_via_redirect(:patch, path, *args) - end - - # Performs a PUT request, following any subsequent redirect. - # See +request_via_redirect+ for more information. - def put_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`put_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') - request_via_redirect(:put, path, *args) - end - - # Performs a DELETE request, following any subsequent redirect. - # See +request_via_redirect+ for more information. - def delete_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`delete_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') - request_via_redirect(:delete, path, *args) - end end # An instance of this class represents a set of requests and responses @@ -176,14 +69,14 @@ module ActionDispatch DEFAULT_HOST = "www.example.com" include Minitest::Assertions - include TestProcess, RequestHelpers, Assertions + include RequestHelpers, Assertions %w( status status_message headers body redirect? ).each do |method| - delegate method, :to => :response, :allow_nil => true + delegate method, to: :response, allow_nil: true end %w( path ).each do |method| - delegate method, :to => :request, :allow_nil => true + delegate method, to: :request, allow_nil: true end # The hostname used in the last request. @@ -234,7 +127,7 @@ module ActionDispatch url_options.reverse_merge!(@app.routes.default_url_options) end - url_options.reverse_merge!(:host => host, :protocol => https? ? "https" : "http") + url_options.reverse_merge!(host: host, protocol: https? ? "https" : "http") end end @@ -280,168 +173,129 @@ module ActionDispatch @https end - # Set the host name to use in the next request. + # Performs the actual request. # - # session.host! "www.example.com" - alias :host! :host= - - private - def _mock_session - @_mock_session ||= Rack::MockSession.new(@app, host) - end - - def process_with_kwargs(http_method, path, *args) - if kwarg_request?(args) - process(http_method, path, *args) - else - non_kwarg_request_warning if args.any? - process(http_method, path, { params: args[0], headers: args[1] }) - end - end - - REQUEST_KWARGS = %i(params headers env xhr) - def kwarg_request?(args) - args[0].respond_to?(:keys) && args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) } - end - - def non_kwarg_request_warning - ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc) - ActionDispatch::IntegrationTest HTTP request methods will accept only - the following keyword arguments in future Rails versions: - #{REQUEST_KWARGS.join(', ')} - - Examples: - - get '/profile', - params: { id: 1 }, - headers: { 'X-Extra-Header' => '123' }, - env: { 'action_dispatch.custom' => 'custom' }, - xhr: true - MSG + # - +method+: The HTTP method (GET, POST, PATCH, PUT, DELETE, HEAD, OPTIONS) + # as a symbol. + # - +path+: The URI (as a String) on which you want to perform the + # request. + # - +params+: The HTTP parameters that you want to pass. This may + # be +nil+, + # a Hash, or a String that is appropriately encoded + # (<tt>application/x-www-form-urlencoded</tt> or + # <tt>multipart/form-data</tt>). + # - +headers+: Additional headers to pass, as a Hash. The headers will be + # merged into the Rack env hash. + # - +env+: Additional env to pass, as a Hash. The headers will be + # merged into the Rack env hash. + # + # This method is rarely used directly. Use +#get+, +#post+, or other standard + # HTTP methods in integration tests. +#process+ is only required when using a + # request method that doesn't have a method defined in the integration tests. + # + # This method returns a Response object, which one can use to + # inspect the details of the response. Furthermore, if this method was + # called from an ActionDispatch::IntegrationTest object, then that + # object's <tt>@response</tt> instance variable will point to the same + # response object. + # + # Example: + # process :get, '/author', params: { since: 201501011400 } + def process(method, path, params: nil, headers: nil, env: nil, xhr: false, as: nil) + request_encoder = RequestEncoder.encoder(as) + headers ||= {} + + if method == :get && as == :json && params + headers["X-Http-Method-Override"] = "GET" + method = :post end - # Performs the actual request. - def process(method, path, params: nil, headers: nil, env: nil, xhr: false, as: nil) - request_encoder = RequestEncoder.encoder(as) - - if path =~ %r{://} - location = URI.parse(path) + if path =~ %r{://} + path = build_expanded_path(path, request_encoder) do |location| https! URI::HTTPS === location if location.scheme + if url_host = location.host default = Rack::Request::DEFAULT_PORTS[location.scheme] url_host += ":#{location.port}" if default != location.port host! url_host end - path = request_encoder.append_format_to location.path - path = location.query ? "#{path}?#{location.query}" : path - else - path = request_encoder.append_format_to path end + elsif as + path = build_expanded_path(path, request_encoder) + end - hostname, port = host.split(':') - - request_env = { - :method => method, - :params => request_encoder.encode_params(params), - - "SERVER_NAME" => hostname, - "SERVER_PORT" => port || (https? ? "443" : "80"), - "HTTPS" => https? ? "on" : "off", - "rack.url_scheme" => https? ? "https" : "http", - - "REQUEST_URI" => path, - "HTTP_HOST" => host, - "REMOTE_ADDR" => remote_addr, - "CONTENT_TYPE" => request_encoder.content_type, - "HTTP_ACCEPT" => accept - } - - if xhr - headers ||= {} - headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' - headers['HTTP_ACCEPT'] ||= [Mime[:js], Mime[:html], Mime[:xml], 'text/xml', '*/*'].join(', ') - end + hostname, port = host.split(":") - # this modifies the passed request_env directly - if headers.present? - Http::Headers.from_hash(request_env).merge!(headers) - end - if env.present? - Http::Headers.from_hash(request_env).merge!(env) - end + request_env = { + :method => method, + :params => request_encoder.encode_params(params), - session = Rack::Test::Session.new(_mock_session) + "SERVER_NAME" => hostname, + "SERVER_PORT" => port || (https? ? "443" : "80"), + "HTTPS" => https? ? "on" : "off", + "rack.url_scheme" => https? ? "https" : "http", - # NOTE: rack-test v0.5 doesn't build a default uri correctly - # Make sure requested path is always a full uri - session.request(build_full_uri(path, request_env), request_env) - - @request_count += 1 - @request = ActionDispatch::Request.new(session.last_request.env) - response = _mock_session.last_response - @response = ActionDispatch::TestResponse.from_response(response) - @response.request = @request - @response.response_parser = RequestEncoder.parser(@response.content_type) - @html_document = nil - @url_options = nil + "REQUEST_URI" => path, + "HTTP_HOST" => host, + "REMOTE_ADDR" => remote_addr, + "CONTENT_TYPE" => request_encoder.content_type, + "HTTP_ACCEPT" => accept + } - @controller = @request.controller_instance + wrapped_headers = Http::Headers.from_hash({}) + wrapped_headers.merge!(headers) if headers - response.status + if xhr + wrapped_headers["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest" + wrapped_headers["HTTP_ACCEPT"] ||= [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ") end - def build_full_uri(path, env) - "#{env['rack.url_scheme']}://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{path}" + # this modifies the passed request_env directly + if wrapped_headers.present? + Http::Headers.from_hash(request_env).merge!(wrapped_headers) + end + if env.present? + Http::Headers.from_hash(request_env).merge!(env) end - class RequestEncoder # :nodoc: - @encoders = {} - - attr_reader :response_parser - - def initialize(mime_name, param_encoder, response_parser, url_encoded_form = false) - @mime = Mime[mime_name] + session = Rack::Test::Session.new(_mock_session) - unless @mime - raise ArgumentError, "Can't register a request encoder for " \ - "unregistered MIME Type: #{mime_name}. See `Mime::Type.register`." - end - - @url_encoded_form = url_encoded_form - @path_format = ".#{@mime.symbol}" unless @url_encoded_form - @response_parser = response_parser || -> body { body } - @param_encoder = param_encoder || :"to_#{@mime.symbol}".to_proc - end + # NOTE: rack-test v0.5 doesn't build a default uri correctly + # Make sure requested path is always a full uri + session.request(build_full_uri(path, request_env), request_env) - def append_format_to(path) - path << @path_format unless @url_encoded_form - path - end - - def content_type - @mime.to_s - end + @request_count += 1 + @request = ActionDispatch::Request.new(session.last_request.env) + response = _mock_session.last_response + @response = ActionDispatch::TestResponse.from_response(response) + @response.request = @request + @html_document = nil + @url_options = nil - def encode_params(params) - @param_encoder.call(params) - end + @controller = @request.controller_instance - def self.parser(content_type) - mime = Mime::Type.lookup(content_type) - encoder(mime ? mime.ref : nil).response_parser - end + response.status + end - def self.encoder(name) - @encoders[name] || WWWFormEncoder - end + # Set the host name to use in the next request. + # + # session.host! "www.example.com" + alias :host! :host= - def self.register_encoder(mime_name, param_encoder: nil, response_parser: nil) - @encoders[mime_name] = new(mime_name, param_encoder, response_parser) - end + private + def _mock_session + @_mock_session ||= Rack::MockSession.new(@app, host) + end - register_encoder :json, response_parser: -> body { JSON.parse(body) } + def build_full_uri(path, env) + "#{env['rack.url_scheme']}://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{path}" + end - WWWFormEncoder = new(:url_encoded_form, -> params { params }, nil, true) + def build_expanded_path(path, request_encoder) + location = URI.parse(path) + yield location if block_given? + path = request_encoder.append_format_to location.path + location.query ? "#{path}?#{location.query}" : path end end @@ -452,9 +306,13 @@ module ActionDispatch attr_reader :app + def initialize(*args, &blk) + super(*args, &blk) + @integration_session = nil + end + def before_setup # :nodoc: @app = nil - @integration_session = nil super end @@ -488,7 +346,7 @@ module ActionDispatch xml_http_request xhr get_via_redirect post_via_redirect).each do |method| define_method(method) do |*args| # reset the html_document variable, except for cookies/assigns calls - unless method == 'cookies' || method == 'assigns' + unless method == "cookies" || method == "assigns" @html_document = nil end @@ -530,7 +388,7 @@ module ActionDispatch integration_session.default_url_options = options end - def respond_to?(method, include_private = false) + def respond_to_missing?(method, include_private = false) integration_session.respond_to?(method, include_private) || super end @@ -699,70 +557,95 @@ module ActionDispatch # end # end # + # See the {request helpers documentation}[rdoc-ref:ActionDispatch::Integration::RequestHelpers] for help on how to + # use +get+, etc. + # + # === Changing the request encoding + # # You can also test your JSON API easily by setting what the request should # be encoded as: # - # require 'test_helper' + # require "test_helper" # # class ApiTest < ActionDispatch::IntegrationTest - # test 'creates articles' do + # test "creates articles" do # assert_difference -> { Article.count } do - # post articles_path, params: { article: { title: 'Ahoy!' } }, as: :json + # post articles_path, params: { article: { title: "Ahoy!" } }, as: :json # end # # assert_response :success - # assert_equal({ id: Arcticle.last.id, title: 'Ahoy!' }, response.parsed_body) + # assert_equal({ id: Arcticle.last.id, title: "Ahoy!" }, response.parsed_body) # end # end # - # The `as` option sets the format to JSON, sets the content type to - # 'application/json' and encodes the parameters as JSON. + # The +as+ option sets the format to JSON, sets the content type to + # "application/json" and encodes the parameters as JSON. # - # Calling `parsed_body` on the response parses the response body as what - # the last request was encoded as. If the request wasn't encoded `as` something, - # it's the same as calling `body`. + # Calling +parsed_body+ on the response parses the response body based on the + # last response MIME type. # - # For any custom MIME Types you've registered, you can even add your own encoders with: + # For any custom MIME types you've registered, you can even add your own encoders with: # # ActionDispatch::IntegrationTest.register_encoder :wibble, # param_encoder: -> params { params.to_wibble }, # response_parser: -> body { body } # - # Where `param_encoder` defines how the params should be encoded and - # `response_parser` defines how the response body should be parsed through - # `parsed_body`. + # Where +param_encoder+ defines how the params should be encoded and + # +response_parser+ defines how the response body should be parsed through + # +parsed_body+. # # Consult the Rails Testing Guide for more. class IntegrationTest < ActiveSupport::TestCase - include Integration::Runner - include ActionController::TemplateAssertions - include ActionDispatch::Routing::UrlFor - - @@app = nil + include TestProcess - def self.app - @@app || ActionDispatch.test_app + module UrlOptions + extend ActiveSupport::Concern + def url_options + integration_session.url_options + end end - def self.app=(app) - @@app = app - end + module Behavior + extend ActiveSupport::Concern - def app - super || self.class.app - end + include Integration::Runner + include ActionController::TemplateAssertions - def url_options - integration_session.url_options - end + included do + include ActionDispatch::Routing::UrlFor + include UrlOptions # don't let UrlFor override the url_options method + ActiveSupport.run_load_hooks(:action_dispatch_integration_test, self) + @@app = nil + end - def document_root_element - html_document.root - end + module ClassMethods + def app + if defined?(@@app) && @@app + @@app + else + ActionDispatch.test_app + end + end - def self.register_encoder(*args) - Integration::Session::RequestEncoder.register_encoder(*args) + def app=(app) + @@app = app + end + + def register_encoder(*args) + RequestEncoder.register_encoder(*args) + end + end + + def app + super || self.class.app + end + + def document_root_element + html_document.root + end end + + include Behavior end end diff --git a/actionpack/lib/action_dispatch/testing/request_encoder.rb b/actionpack/lib/action_dispatch/testing/request_encoder.rb new file mode 100644 index 0000000000..b0b994b2d0 --- /dev/null +++ b/actionpack/lib/action_dispatch/testing/request_encoder.rb @@ -0,0 +1,54 @@ +module ActionDispatch + class RequestEncoder # :nodoc: + @encoders = {} + + attr_reader :response_parser + + def initialize(mime_name, param_encoder, response_parser, url_encoded_form = false) + @mime = Mime[mime_name] + + unless @mime + raise ArgumentError, "Can't register a request encoder for " \ + "unregistered MIME Type: #{mime_name}. See `Mime::Type.register`." + end + + @url_encoded_form = url_encoded_form + @path_format = ".#{@mime.symbol}" unless @url_encoded_form + @response_parser = response_parser || -> body { body } + @param_encoder = param_encoder || :"to_#{@mime.symbol}".to_proc + end + + def append_format_to(path) + if @url_encoded_form + path + else + path + @path_format + end + end + + def content_type + @mime.to_s + end + + def encode_params(params) + @param_encoder.call(params) + end + + def self.parser(content_type) + mime = Mime::Type.lookup(content_type) + encoder(mime ? mime.ref : nil).response_parser + end + + def self.encoder(name) + @encoders[name] || WWWFormEncoder + end + + def self.register_encoder(mime_name, param_encoder: nil, response_parser: nil) + @encoders[mime_name] = new(mime_name, param_encoder, response_parser) + end + + register_encoder :json, response_parser: -> body { JSON.parse(body) } + + WWWFormEncoder = new(:url_encoded_form, -> params { params }, nil, true) + end +end diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb index 1ecd7d14a7..8b03b776fa 100644 --- a/actionpack/lib/action_dispatch/testing/test_process.rb +++ b/actionpack/lib/action_dispatch/testing/test_process.rb @@ -1,5 +1,5 @@ -require 'action_dispatch/middleware/cookies' -require 'action_dispatch/middleware/flash' +require "action_dispatch/middleware/cookies" +require "action_dispatch/middleware/flash" module ActionDispatch module TestProcess @@ -34,7 +34,8 @@ module ActionDispatch # # 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 + 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) diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb index ad1a7f7109..91b25ec155 100644 --- a/actionpack/lib/action_dispatch/testing/test_request.rb +++ b/actionpack/lib/action_dispatch/testing/test_request.rb @@ -1,12 +1,12 @@ -require 'active_support/core_ext/hash/indifferent_access' -require 'rack/utils' +require "active_support/core_ext/hash/indifferent_access" +require "rack/utils" module ActionDispatch class TestRequest < Request - DEFAULT_ENV = Rack::MockRequest.env_for('/', - 'HTTP_HOST' => 'test.host', - 'REMOTE_ADDR' => '0.0.0.0', - 'HTTP_USER_AGENT' => 'Rails Testing', + DEFAULT_ENV = Rack::MockRequest.env_for("/", + "HTTP_HOST" => "test.host", + "REMOTE_ADDR" => "0.0.0.0", + "HTTP_USER_AGENT" => "Rails Testing", ) # Create a new test request with default `env` values @@ -22,23 +22,23 @@ module ActionDispatch private_class_method :default_env def request_method=(method) - @env['REQUEST_METHOD'] = method.to_s.upcase + super(method.to_s.upcase) end def host=(host) - @env['HTTP_HOST'] = host + set_header("HTTP_HOST", host) end def port=(number) - @env['SERVER_PORT'] = number.to_i + set_header("SERVER_PORT", number.to_i) end def request_uri=(uri) - @env['REQUEST_URI'] = uri + set_header("REQUEST_URI", uri) end def path=(path) - @env['PATH_INFO'] = path + set_header("PATH_INFO", path) end def action=(action_name) @@ -46,24 +46,24 @@ module ActionDispatch end def if_modified_since=(last_modified) - @env['HTTP_IF_MODIFIED_SINCE'] = last_modified + set_header("HTTP_IF_MODIFIED_SINCE", last_modified) end def if_none_match=(etag) - @env['HTTP_IF_NONE_MATCH'] = etag + set_header("HTTP_IF_NONE_MATCH", etag) end def remote_addr=(addr) - @env['REMOTE_ADDR'] = addr + set_header("REMOTE_ADDR", addr) end def user_agent=(user_agent) - @env['HTTP_USER_AGENT'] = user_agent + set_header("HTTP_USER_AGENT", user_agent) end def accept=(mime_types) - @env.delete('action_dispatch.request.accepts') - @env['HTTP_ACCEPT'] = Array(mime_types).collect(&:to_s).join(",") + delete_header("action_dispatch.request.accepts") + set_header("HTTP_ACCEPT", Array(mime_types).collect(&:to_s).join(",")) end end end diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb index 9d4b73a43d..5c89f9c75e 100644 --- a/actionpack/lib/action_dispatch/testing/test_response.rb +++ b/actionpack/lib/action_dispatch/testing/test_response.rb @@ -1,3 +1,5 @@ +require "action_dispatch/testing/request_encoder" + module ActionDispatch # Integration test methods such as ActionDispatch::Integration::Session#get # and ActionDispatch::Integration::Session#post return objects of class @@ -10,6 +12,11 @@ module ActionDispatch new response.status, response.headers, response.body end + def initialize(*) # :nodoc: + super + @response_parser = RequestEncoder.parser(content_type) + end + # Was the response successful? alias_method :success?, :successful? @@ -19,8 +26,6 @@ module ActionDispatch # Was there a server-side error? alias_method :error?, :server_error? - attr_writer :response_parser # :nodoc: - def parsed_body @parsed_body ||= @response_parser.call(body) end diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb index 941877d10d..ee6dabd133 100644 --- a/actionpack/lib/action_pack.rb +++ b/actionpack/lib/action_pack.rb @@ -21,4 +21,4 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ -require 'action_pack/version' +require "action_pack/version" diff --git a/actionpack/lib/action_pack/gem_version.rb b/actionpack/lib/action_pack/gem_version.rb index 778c5482d3..d8f86630b1 100644 --- a/actionpack/lib/action_pack/gem_version.rb +++ b/actionpack/lib/action_pack/gem_version.rb @@ -6,9 +6,9 @@ module ActionPack module VERSION MAJOR = 5 - MINOR = 0 + MINOR = 1 TINY = 0 - PRE = "beta2" + PRE = "alpha" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb index 7088cd2760..3d96158431 100644 --- a/actionpack/lib/action_pack/version.rb +++ b/actionpack/lib/action_pack/version.rb @@ -1,4 +1,4 @@ -require_relative 'gem_version' +require_relative "gem_version" module ActionPack # Returns the version of the currently loaded ActionPack as a <tt>Gem::Version</tt> diff --git a/actionpack/test/abstract/callbacks_test.rb b/actionpack/test/abstract/callbacks_test.rb index 07571602e4..9c2261bf76 100644 --- a/actionpack/test/abstract/callbacks_test.rb +++ b/actionpack/test/abstract/callbacks_test.rb @@ -1,8 +1,7 @@ -require 'abstract_unit' +require "abstract_unit" module AbstractController module Testing - class ControllerWithCallbacks < AbstractController::Base include AbstractController::Callbacks end @@ -114,8 +113,8 @@ module AbstractController end class CallbacksWithConditions < ControllerWithCallbacks - before_action :list, :only => :index - before_action :authenticate, :except => :index + before_action :list, only: :index + before_action :authenticate, except: :index def index self.response_body = @list.join(", ") @@ -126,14 +125,14 @@ module AbstractController end private - def list - @list = ["Hello", "World"] - end - - def authenticate - @list ||= [] - @authenticated = "true" - end + def list + @list = ["Hello", "World"] + end + + def authenticate + @list ||= [] + @authenticated = "true" + end end class TestCallbacksWithConditions < ActiveSupport::TestCase @@ -170,14 +169,14 @@ module AbstractController end private - def list - @list = ["Hello", "World"] - end - - def authenticate - @list = [] - @authenticated = "true" - end + def list + @list = ["Hello", "World"] + end + + def authenticate + @list = [] + @authenticated = "true" + end end class TestCallbacksWithArrayConditions < ActiveSupport::TestCase @@ -202,7 +201,7 @@ module AbstractController end class ChangedConditions < Callback2 - before_action :first, :only => :index + before_action :first, only: :index def not_index @text ||= nil @@ -265,53 +264,5 @@ module AbstractController assert_equal "Hello world Howdy!", controller.response_body end end - - class AliasedCallbacks < ControllerWithCallbacks - ActiveSupport::Deprecation.silence do - before_filter :first - after_filter :second - around_filter :aroundz - end - - def first - @text = "Hello world" - end - - def second - @second = "Goodbye" - end - - def aroundz - @aroundz = "FIRST" - yield - @aroundz << "SECOND" - end - - def index - @text ||= nil - self.response_body = @text.to_s - end - end - - class TestAliasedCallbacks < ActiveSupport::TestCase - def setup - @controller = AliasedCallbacks.new - end - - test "before_filter works" do - @controller.process(:index) - assert_equal "Hello world", @controller.response_body - end - - test "after_filter works" do - @controller.process(:index) - assert_equal "Goodbye", @controller.instance_variable_get("@second") - end - - test "around_filter works" do - @controller.process(:index) - assert_equal "FIRSTSECOND", @controller.instance_variable_get("@aroundz") - end - end end end diff --git a/actionpack/test/abstract/collector_test.rb b/actionpack/test/abstract/collector_test.rb index edbb84d462..7fe19e6b10 100644 --- a/actionpack/test/abstract/collector_test.rb +++ b/actionpack/test/abstract/collector_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module AbstractController module Testing diff --git a/actionpack/test/abstract/translation_test.rb b/actionpack/test/abstract/translation_test.rb index 1435928578..0c4071df8d 100644 --- a/actionpack/test/abstract/translation_test.rb +++ b/actionpack/test/abstract/translation_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module AbstractController module Testing @@ -9,21 +9,20 @@ module AbstractController class TranslationControllerTest < ActiveSupport::TestCase def setup @controller = TranslationController.new - I18n.backend.store_translations(:en, { + I18n.backend.store_translations(:en, one: { - two: 'bar', + two: "bar", }, abstract_controller: { testing: { translation: { index: { - foo: 'bar', + foo: "bar", }, - no_action: 'no_action_tr', + no_action: "no_action_tr", }, }, - }, - }) + }) end def test_action_controller_base_responds_to_translate @@ -44,30 +43,30 @@ module AbstractController def test_lazy_lookup @controller.stub :action_name, :index do - assert_equal 'bar', @controller.t('.foo') + assert_equal "bar", @controller.t(".foo") end end def test_lazy_lookup_with_symbol @controller.stub :action_name, :index do - assert_equal 'bar', @controller.t(:'.foo') + assert_equal "bar", @controller.t(:'.foo') end end def test_lazy_lookup_fallback @controller.stub :action_name, :index do - assert_equal 'no_action_tr', @controller.t(:'.no_action') + assert_equal "no_action_tr", @controller.t(:'.no_action') end end def test_default_translation @controller.stub :action_name, :index do - assert_equal 'bar', @controller.t('one.two') + assert_equal "bar", @controller.t("one.two") end end def test_localize - time, expected = Time.gm(2000), 'Sat, 01 Jan 2000 00:00:00 +0000' + time, expected = Time.gm(2000), "Sat, 01 Jan 2000 00:00:00 +0000" I18n.stub :localize, expected do assert_equal expected, @controller.l(time) end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 1ef10ff956..6d28753947 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -1,10 +1,8 @@ -require File.expand_path('../../../load_paths', __FILE__) +$:.unshift(File.dirname(__FILE__) + "/lib") +$:.unshift(File.dirname(__FILE__) + "/fixtures/helpers") +$:.unshift(File.dirname(__FILE__) + "/fixtures/alternate_helpers") -$:.unshift(File.dirname(__FILE__) + '/lib') -$:.unshift(File.dirname(__FILE__) + '/fixtures/helpers') -$:.unshift(File.dirname(__FILE__) + '/fixtures/alternate_helpers') - -require 'active_support/core_ext/kernel/reporting' +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 @@ -13,32 +11,30 @@ silence_warnings do Encoding.default_external = "UTF-8" end -require 'drb' +require "drb" begin - require 'drb/unix' + require "drb/unix" rescue LoadError puts "'drb/unix' is not available" end -if ENV['TRAVIS'] +if ENV["TRAVIS"] PROCESS_COUNT = 0 else - PROCESS_COUNT = (ENV['N'] || 4).to_i + PROCESS_COUNT = (ENV["N"] || 4).to_i end -require 'active_support/testing/autorun' -require 'abstract_controller' -require 'abstract_controller/railties/routes_helpers' -require 'action_controller' -require 'action_view' -require 'action_view/testing/resolvers' -require 'action_dispatch' -require 'active_support/dependencies' -require 'active_model' -require 'active_record' -require 'action_controller/caching' +require "active_support/testing/autorun" +require "abstract_controller" +require "abstract_controller/railties/routes_helpers" +require "action_controller" +require "action_view" +require "action_view/testing/resolvers" +require "action_dispatch" +require "active_support/dependencies" +require "active_model" -require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late +require "pp" # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late module Rails class << self @@ -60,16 +56,14 @@ ActiveSupport::Deprecation.debug = true # Disable available locale checks to avoid warnings running the test suite. I18n.enforce_available_locales = false -# Register danish language for testing -I18n.backend.store_translations 'da', {} -I18n.backend.store_translations 'pt-BR', {} - -FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures') +FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), "fixtures") SharedTestRoutes = ActionDispatch::Routing::RouteSet.new SharedTestRoutes.draw do - get ':controller(/:action)' + ActiveSupport::Deprecation.silence do + get ":controller(/:action)" + end end module ActionDispatch @@ -110,6 +104,7 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase middleware.use ActionDispatch::Callbacks middleware.use ActionDispatch::Cookies middleware.use ActionDispatch::Flash + middleware.use Rack::MethodOverride middleware.use Rack::Head yield(middleware) if block_given? end @@ -118,34 +113,28 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase self.app = build_app app.routes.draw do - get ':controller(/:action)' + ActiveSupport::Deprecation.silence do + get ":controller(/:action)" + end end class DeadEndRoutes < ActionDispatch::Routing::RouteSet # Stub Rails dispatcher so it does not get controller references and # simply return the controller#action as Rack::Body. class NullController < ::ActionController::Metal - def initialize(controller_name) - @controller = controller_name - end - - def make_response!(request) - self.class.make_response! request - end - - def dispatch(action, req, res) - [200, {'Content-Type' => 'text/html'}, ["#{@controller}##{action}"]] + def self.dispatch(action, req, res) + [200, { "Content-Type" => "text/html" }, ["#{req.params[:controller]}##{action}"]] end end - class NullControllerRequest < DelegateClass(ActionDispatch::Request) + class NullControllerRequest < ActionDispatch::Request def controller_class - NullController.new params[:controller] + NullController end end - def make_request env - NullControllerRequest.new super + def make_request(env) + NullControllerRequest.new env end end @@ -162,7 +151,7 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase yield temporary_routes ensure self.class.app = old_app - self.remove! + remove! silence_warnings { Object.const_set(:SharedTestRoutes, old_routes) } end @@ -175,7 +164,7 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase ActiveSupport::Dependencies.autoload_paths << path yield ensure - ActiveSupport::Dependencies.autoload_paths.reject! {|p| p == path} + ActiveSupport::Dependencies.autoload_paths.reject! { |p| p == path } ActiveSupport::Dependencies.clear end end @@ -186,7 +175,7 @@ end class Rack::TestCase < ActionDispatch::IntegrationTest def self.testing(klass = nil) if klass - @testing = "/#{klass.name.underscore}".sub!(/_controller$/, '') + @testing = "/#{klass.name.underscore}".sub!(/_controller$/, "") else @testing end @@ -250,36 +239,17 @@ module ActionController end end - class ::ApplicationController < ActionController::Base end -class Workshop - extend ActiveModel::Naming - include ActiveModel::Conversion - attr_accessor :id - - def initialize(id) - @id = id - end - - def persisted? - id.present? - end - - def to_s - id.to_s - end -end - module ActionDispatch class DebugExceptions private - remove_method :stderr_logger - # Silence logger - def stderr_logger - nil - end + remove_method :stderr_logger + # Silence logger + def stderr_logger + nil + end end end @@ -289,16 +259,16 @@ 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 def request_path_params(path, options = {}) - method = options[:method] || 'GET' - resp = send_request URI('http://localhost' + path), method.to_s.upcase, nil + method = options[:method] || "GET" + resp = send_request URI("http://localhost" + path), method.to_s.upcase, nil status = resp.first if status == 404 raise ActionController::RoutingError, "No route matches #{path.inspect}" @@ -307,23 +277,23 @@ module ActionDispatch end def get(uri_or_host, path = nil) - send_request(uri_or_host, 'GET', path)[2].join + send_request(uri_or_host, "GET", path)[2].join end def post(uri_or_host, path = nil) - send_request(uri_or_host, 'POST', path)[2].join + send_request(uri_or_host, "POST", path)[2].join end def put(uri_or_host, path = nil) - send_request(uri_or_host, 'PUT', path)[2].join + send_request(uri_or_host, "PUT", path)[2].join end def delete(uri_or_host, path = nil) - send_request(uri_or_host, 'DELETE', path)[2].join + send_request(uri_or_host, "DELETE", path)[2].join end def patch(uri_or_host, path = nil) - send_request(uri_or_host, 'PATCH', path)[2].join + send_request(uri_or_host, "PATCH", path)[2].join end end end @@ -331,7 +301,7 @@ end module RoutingTestHelpers def url_for(set, options) route_name = options.delete :use_route - set.url_for options.merge(:only_path => true), route_name + set.url_for options.merge(only_path: true), route_name end def make_set(strict = true) @@ -369,53 +339,31 @@ module RoutingTestHelpers private - def make_request(env) - Request.new super, url_helpers, @block, strict - end + def make_request(env) + Request.new super, url_helpers, @block, strict + end end end -class MetalRenderingController < ActionController::Metal - include AbstractController::Rendering - include ActionController::Rendering - include ActionController::Renderers -end - class ResourcesController < ActionController::Base def index() head :ok end alias_method :show, :index end -class ThreadsController < ResourcesController; end -class MessagesController < ResourcesController; end class CommentsController < ResourcesController; end -class ReviewsController < ResourcesController; end - class AccountsController < ResourcesController; end -class AdminController < ResourcesController; end -class ProductsController < ResourcesController; end class ImagesController < ResourcesController; end -module Backoffice - class ProductsController < ResourcesController; end - class ImagesController < ResourcesController; end - - module Admin - class ProductsController < ResourcesController; end - class ImagesController < ResourcesController; end - end -end - # Skips the current run on Rubinius using Minitest::Assertions#skip -def rubinius_skip(message = '') - skip message if RUBY_ENGINE == 'rbx' +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 = '') +def jruby_skip(message = "") skip message if defined?(JRUBY_VERSION) end -require 'active_support/testing/method_call_assertions' +require "active_support/testing/method_call_assertions" class ForkingExecutor class Server @@ -425,27 +373,27 @@ class ForkingExecutor @queue = Queue.new end - def record reporter, result + def record(reporter, result) reporter.record result end - def << o + def <<(o) o[2] = DRbObject.new(o[2]) if o @queue << o end def pop; @queue.pop; end end - def initialize size + def initialize(size) @size = size @queue = Server.new - file = File.join Dir.tmpdir, Dir::Tmpname.make_tmpname('rails-tests', 'fd') + file = File.join Dir.tmpdir, Dir::Tmpname.make_tmpname("rails-tests", "fd") @url = "drbunix://#{file}" @pool = nil DRb.start_service @url, @queue end - def << work; @queue << work; end + def <<(work); @queue << work; end def shutdown pool = @size.times.map { @@ -469,18 +417,18 @@ class ForkingExecutor end private - def translate_exceptions(result) - result.failures.map! { |e| - begin - Marshal.dump e - e - rescue TypeError - ex = Exception.new e.message - ex.set_backtrace e.backtrace - Minitest::UnexpectedError.new ex - end - } - end + def translate_exceptions(result) + result.failures.map! { |e| + begin + Marshal.dump e + e + rescue TypeError + ex = Exception.new e.message + ex.set_backtrace e.backtrace + Minitest::UnexpectedError.new ex + end + } + end end if RUBY_ENGINE == "ruby" && PROCESS_COUNT > 0 diff --git a/actionpack/test/assertions/response_assertions_test.rb b/actionpack/test/assertions/response_assertions_test.rb index 579ce0ed29..14a04ccdb1 100644 --- a/actionpack/test/assertions/response_assertions_test.rb +++ b/actionpack/test/assertions/response_assertions_test.rb @@ -1,15 +1,16 @@ -require 'abstract_unit' -require 'action_dispatch/testing/assertions/response' +require "abstract_unit" +require "action_dispatch/testing/assertions/response" module ActionDispatch module Assertions class ResponseAssertionsTest < ActiveSupport::TestCase include ResponseAssertions - FakeResponse = Struct.new(:response_code, :location) do + FakeResponse = Struct.new(:response_code, :location, :body) do def initialize(*) super self.location ||= "http://test.example.com/posts" + self.body ||= "" end [:successful, :not_found, :redirection, :server_error].each do |sym| @@ -26,7 +27,7 @@ module ActionDispatch def test_assert_response_predicate_methods [:success, :missing, :redirect, :error].each do |sym| - @response = FakeResponse.new RESPONSE_PREDICATES[sym].to_s.sub(/\?/, '').to_sym + @response = FakeResponse.new RESPONSE_PREDICATES[sym].to_s.sub(/\?/, "").to_sym assert_response sym assert_raises(Minitest::Assertion) { @@ -35,7 +36,7 @@ module ActionDispatch end end - def test_assert_response_fixnum + def test_assert_response_integer @response = FakeResponse.new 400 assert_response 400 @@ -92,7 +93,7 @@ module ActionDispatch def test_error_message_shows_302_redirect_when_302_asserted_for_success @response = ActionDispatch::Response.new @response.status = 302 - @response.location = 'http://test.host/posts/redirect/1' + @response.location = "http://test.host/posts/redirect/1" error = assert_raises(Minitest::Assertion) { assert_response :success } expected = "Expected response to be a <2XX: success>,"\ @@ -104,7 +105,7 @@ module ActionDispatch def test_error_message_shows_302_redirect_when_302_asserted_for_301 @response = ActionDispatch::Response.new @response.status = 302 - @response.location = 'http://test.host/posts/redirect/2' + @response.location = "http://test.host/posts/redirect/2" error = assert_raises(Minitest::Assertion) { assert_response 301 } expected = "Expected response to be a <301: Moved Permanently>,"\ @@ -112,6 +113,27 @@ module ActionDispatch " redirect to <http://test.host/posts/redirect/2>" assert_match expected, error.message end + + def test_error_message_shows_short_response_body + @response = ActionDispatch::Response.new + @response.status = 400 + @response.body = "not too long" + error = assert_raises(Minitest::Assertion) { assert_response 200 } + expected = "Expected response to be a <200: OK>,"\ + " but was a <400: Bad Request>" \ + "\nResponse body: not too long" + assert_match expected, error.message + end + + def test_error_message_does_not_show_long_response_body + @response = ActionDispatch::Response.new + @response.status = 400 + @response.body = "not too long" * 50 + error = assert_raises(Minitest::Assertion) { assert_response 200 } + expected = "Expected response to be a <200: OK>,"\ + " but was a <400: Bad Request>" + assert_match expected, error.message + end end end end diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index 899d92f815..b08f1f1707 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -1,33 +1,32 @@ -require 'abstract_unit' -require 'controller/fake_controllers' +require "abstract_unit" +require "controller/fake_controllers" class ActionPackAssertionsController < ActionController::Base - def nothing() head :ok end - def hello_xml_world() render :template => "test/hello_xml_world"; end + def hello_xml_world() render template: "test/hello_xml_world"; end def hello_xml_world_pdf self.content_type = "application/pdf" - render :template => "test/hello_xml_world" + render template: "test/hello_xml_world" end def hello_xml_world_pdf_header response.headers["Content-Type"] = "application/pdf; charset=utf-8" - render :template => "test/hello_xml_world" + render template: "test/hello_xml_world" end def redirect_internal() redirect_to "/nothing"; end - def redirect_to_action() redirect_to :action => "flash_me", :id => 1, :params => { "panda" => "fun" }; end + def redirect_to_action() redirect_to action: "flash_me", id: 1, params: { "panda" => "fun" }; end - def redirect_to_controller() redirect_to :controller => "elsewhere", :action => "flash_me"; end + def redirect_to_controller() redirect_to controller: "elsewhere", action: "flash_me"; end - def redirect_to_controller_with_symbol() redirect_to :controller => :elsewhere, :action => :flash_me; end + def redirect_to_controller_with_symbol() redirect_to controller: :elsewhere, action: :flash_me; end - def redirect_to_path() redirect_to '/some/path' end + def redirect_to_path() redirect_to "/some/path" end - def redirect_invalid_external_route() redirect_to 'ht_tp://www.rubyonrails.org' end + def redirect_invalid_external_route() redirect_to "ht_tp://www.rubyonrails.org" end def redirect_to_named_route() redirect_to route_one_url end @@ -35,14 +34,14 @@ class ActionPackAssertionsController < ActionController::Base def redirect_external_protocol_relative() redirect_to "//www.rubyonrails.org"; end - def response404() head '404 AWOL' end + def response404() head "404 AWOL" end - def response500() head '500 Sorry' end + def response500() head "500 Sorry" end - def response599() head '599 Whoah!' end + def response599() head "599 Whoah!" end def flash_me - flash['hello'] = 'my name is inigo montoya...' + flash["hello"] = "my name is inigo montoya..." render plain: "Inconceivable!" end @@ -53,7 +52,7 @@ class ActionPackAssertionsController < ActionController::Base def assign_this @howdy = "ho" - render :inline => "Mr. Henke" + render inline: "Mr. Henke" end def render_based_on_parameters @@ -69,7 +68,7 @@ class ActionPackAssertionsController < ActionController::Base end def session_stuffing - session['xmas'] = 'turkey' + session["xmas"] = "turkey" render plain: "ho ho ho" end @@ -84,11 +83,11 @@ class ActionPackAssertionsController < ActionController::Base end def render_file_absolute_path - render :file => File.expand_path('../../../README.rdoc', __FILE__) + render file: File.expand_path("../../../README.rdoc", __FILE__) end def render_file_relative_path - render :file => 'README.rdoc' + render file: "README.rdoc" end end @@ -97,7 +96,7 @@ end # is expecting something other than an error. class AssertResponseWithUnexpectedErrorController < ActionController::Base def index - raise 'FAIL' + raise "FAIL" end def show @@ -116,21 +115,20 @@ module Admin end def redirect_to_absolute_controller - redirect_to :controller => '/content' + redirect_to controller: "/content" end def redirect_to_fellow_controller - redirect_to :controller => 'user' + redirect_to controller: "user" end def redirect_to_top_level_named_route - redirect_to top_level_url(:id => "foo") + redirect_to top_level_url(id: "foo") end end end class ActionPackAssertionsControllerTest < ActionController::TestCase - def test_render_file_absolute_path get :render_file_absolute_path assert_match(/\A= Action Pack/, @response.body) @@ -144,30 +142,30 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase def test_get_request assert_raise(RuntimeError) { get :raise_exception_on_get } get :raise_exception_on_post - assert_equal 'request method: GET', @response.body + assert_equal "request method: GET", @response.body end def test_post_request assert_raise(RuntimeError) { post :raise_exception_on_post } post :raise_exception_on_get - assert_equal 'request method: POST', @response.body + assert_equal "request method: POST", @response.body end def test_get_post_request_switch post :raise_exception_on_get - assert_equal 'request method: POST', @response.body + assert_equal "request method: POST", @response.body get :raise_exception_on_post - assert_equal 'request method: GET', @response.body + assert_equal "request method: GET", @response.body post :raise_exception_on_get - assert_equal 'request method: POST', @response.body + assert_equal "request method: POST", @response.body get :raise_exception_on_post - assert_equal 'request method: GET', @response.body + assert_equal "request method: GET", @response.body end def test_string_constraint with_routing do |set| set.draw do - get "photos", :to => 'action_pack_assertions#nothing', :constraints => {:subdomain => "admin"} + get "photos", to: "action_pack_assertions#nothing", constraints: { subdomain: "admin" } end end end @@ -175,19 +173,22 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase def test_assert_redirect_to_named_route_failure with_routing do |set| set.draw do - get 'route_one', :to => 'action_pack_assertions#nothing', :as => :route_one - get 'route_two', :to => 'action_pack_assertions#nothing', :id => 'two', :as => :route_two - get ':controller/:action' + get "route_one", to: "action_pack_assertions#nothing", as: :route_one + get "route_two", to: "action_pack_assertions#nothing", id: "two", as: :route_two + + ActiveSupport::Deprecation.silence do + get ":controller/:action" + end end process :redirect_to_named_route assert_raise(ActiveSupport::TestCase::Assertion) do - assert_redirected_to 'http://test.host/route_two' + assert_redirected_to "http://test.host/route_two" end assert_raise(ActiveSupport::TestCase::Assertion) do assert_redirected_to %r(^http://test.host/route_two) end assert_raise(ActiveSupport::TestCase::Assertion) do - assert_redirected_to :controller => 'action_pack_assertions', :action => 'nothing', :id => 'two' + assert_redirected_to controller: "action_pack_assertions", action: "nothing", id: "two" end assert_raise(ActiveSupport::TestCase::Assertion) do assert_redirected_to route_two_url @@ -200,8 +201,11 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase with_routing do |set| set.draw do - get 'admin/inner_module', :to => 'admin/inner_module#index', :as => :admin_inner_module - get ':controller/:action' + get "admin/inner_module", to: "admin/inner_module#index", as: :admin_inner_module + + ActiveSupport::Deprecation.silence do + get ":controller/:action" + end end process :redirect_to_index # redirection is <{"action"=>"index", "controller"=>"admin/admin/inner_module"}> @@ -214,8 +218,11 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase with_routing do |set| set.draw do - get '/action_pack_assertions/:id', :to => 'action_pack_assertions#index', :as => :top_level - get ':controller/:action' + get "/action_pack_assertions/:id", to: "action_pack_assertions#index", as: :top_level + + ActiveSupport::Deprecation.silence do + get ":controller/:action" + end end process :redirect_to_top_level_named_route # assert_redirected_to "http://test.host/action_pack_assertions/foo" would pass because of exact match early return @@ -230,12 +237,15 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase with_routing do |set| set.draw do # this controller exists in the admin namespace as well which is the only difference from previous test - get '/user/:id', :to => 'user#index', :as => :top_level - get ':controller/:action' + get "/user/:id", to: "user#index", as: :top_level + + ActiveSupport::Deprecation.silence do + get ":controller/:action" + end end process :redirect_to_top_level_named_route # assert_redirected_to top_level_url('foo') would pass because of exact match early return - assert_redirected_to top_level_path('foo') + assert_redirected_to top_level_path("foo") end end @@ -247,7 +257,7 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase assert_no_match( /#{request.protocol}#{request.host}\/\/www.rubyonrails.org/, ex.message, - 'protocol relative url was incorrectly normalized' + "protocol relative url was incorrectly normalized" ) end end @@ -271,7 +281,7 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase def test_flash_exist process :flash_me assert flash.any? - assert flash['hello'].present? + assert flash["hello"].present? end def test_flash_does_not_exist @@ -281,7 +291,7 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase def test_session_exist process :session_stuffing - assert_equal 'turkey', session['xmas'] + assert_equal "turkey", session["xmas"] end def session_does_not_exist @@ -291,13 +301,13 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase def test_redirection_location process :redirect_internal - assert_equal 'http://test.host/nothing', @response.redirect_url + assert_equal "http://test.host/nothing", @response.redirect_url process :redirect_external - assert_equal 'http://www.rubyonrails.org', @response.redirect_url + assert_equal "http://www.rubyonrails.org", @response.redirect_url process :redirect_external_protocol_relative - assert_equal '//www.rubyonrails.org', @response.redirect_url + assert_equal "//www.rubyonrails.org", @response.redirect_url end def test_no_redirect_url @@ -364,24 +374,24 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase def test_assert_redirection_fails_with_incorrect_controller process :redirect_to_controller assert_raise(ActiveSupport::TestCase::Assertion) do - assert_redirected_to :controller => "action_pack_assertions", :action => "flash_me" + assert_redirected_to controller: "action_pack_assertions", action: "flash_me" end end def test_assert_redirection_with_extra_controller_option get :redirect_to_action - assert_redirected_to :controller => 'action_pack_assertions', :action => "flash_me", :id => 1, :params => { :panda => 'fun' } + assert_redirected_to controller: "action_pack_assertions", action: "flash_me", id: 1, params: { panda: "fun" } end def test_redirected_to_url_leading_slash process :redirect_to_path - assert_redirected_to '/some/path' + assert_redirected_to "/some/path" end def test_redirected_to_url_no_leading_slash_fails process :redirect_to_path assert_raise ActiveSupport::TestCase::Assertion do - assert_redirected_to 'some/path' + assert_redirected_to "some/path" end end @@ -392,43 +402,43 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase def test_redirected_to_url_full_url process :redirect_to_path - assert_redirected_to 'http://test.host/some/path' + assert_redirected_to "http://test.host/some/path" end def test_assert_redirection_with_symbol process :redirect_to_controller_with_symbol assert_nothing_raised { - assert_redirected_to :controller => "elsewhere", :action => "flash_me" + assert_redirected_to controller: "elsewhere", action: "flash_me" } process :redirect_to_controller_with_symbol assert_nothing_raised { - assert_redirected_to :controller => :elsewhere, :action => :flash_me + assert_redirected_to controller: :elsewhere, action: :flash_me } end def test_redirected_to_with_nested_controller @controller = Admin::InnerModuleController.new get :redirect_to_absolute_controller - assert_redirected_to :controller => '/content' + assert_redirected_to controller: "/content" get :redirect_to_fellow_controller - assert_redirected_to :controller => 'admin/user' + assert_redirected_to controller: "admin/user" end def test_assert_response_uses_exception_message @controller = AssertResponseWithUnexpectedErrorController.new - e = assert_raise RuntimeError, 'Expected non-success response' do + e = assert_raise RuntimeError, "Expected non-success response" do get :index end assert_response :success - assert_includes 'FAIL', e.message + assert_includes "FAIL", e.message end def test_assert_response_failure_response_with_no_exception @controller = AssertResponseWithUnexpectedErrorController.new get :show assert_response 500 - assert_equal 'Boom', response.body + assert_equal "Boom", response.body end end @@ -437,21 +447,21 @@ class ActionPackHeaderTest < ActionController::TestCase def test_rendering_xml_sets_content_type process :hello_xml_world - assert_equal('application/xml; charset=utf-8', @response.headers['Content-Type']) + assert_equal("application/xml; charset=utf-8", @response.headers["Content-Type"]) end def test_rendering_xml_respects_content_type process :hello_xml_world_pdf - assert_equal('application/pdf; charset=utf-8', @response.headers['Content-Type']) + assert_equal("application/pdf; charset=utf-8", @response.headers["Content-Type"]) end def test_rendering_xml_respects_content_type_when_set_in_the_header process :hello_xml_world_pdf_header - assert_equal('application/pdf; charset=utf-8', @response.headers['Content-Type']) + assert_equal("application/pdf; charset=utf-8", @response.headers["Content-Type"]) end def test_render_text_with_custom_content_type get :render_text_with_custom_content_type - assert_equal 'application/rss+xml; charset=utf-8', @response.headers['Content-Type'] + assert_equal "application/rss+xml; charset=utf-8", @response.headers["Content-Type"] end end diff --git a/actionpack/test/controller/api/conditional_get_test.rb b/actionpack/test/controller/api/conditional_get_test.rb index b4f1673be0..7b70829101 100644 --- a/actionpack/test/controller/api/conditional_get_test.rb +++ b/actionpack/test/controller/api/conditional_get_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'active_support/core_ext/integer/time' -require 'active_support/core_ext/numeric/time' +require "abstract_unit" +require "active_support/core_ext/integer/time" +require "active_support/core_ext/numeric/time" class ConditionalGetApiController < ActionController::API before_action :handle_last_modified_and_etags, only: :two @@ -17,9 +17,9 @@ class ConditionalGetApiController < ActionController::API private - def handle_last_modified_and_etags - fresh_when(last_modified: Time.now.utc.beginning_of_day, etag: [ :foo, 123 ]) - end + def handle_last_modified_and_etags + fresh_when(last_modified: Time.now.utc.beginning_of_day, etag: [ :foo, 123 ]) + end end class ConditionalGetApiTest < ActionController::TestCase @@ -31,7 +31,7 @@ class ConditionalGetApiTest < ActionController::TestCase def test_request_gets_last_modified get :two - assert_equal @last_modified, @response.headers['Last-Modified'] + assert_equal @last_modified, @response.headers["Last-Modified"] assert_response :success end @@ -52,6 +52,6 @@ class ConditionalGetApiTest < ActionController::TestCase get :one assert_equal 304, @response.status.to_i assert @response.body.blank? - assert_equal @last_modified, @response.headers['Last-Modified'] + assert_equal @last_modified, @response.headers["Last-Modified"] end end diff --git a/actionpack/test/controller/api/data_streaming_test.rb b/actionpack/test/controller/api/data_streaming_test.rb index 0e7d97d1f4..f15b78d102 100644 --- a/actionpack/test/controller/api/data_streaming_test.rb +++ b/actionpack/test/controller/api/data_streaming_test.rb @@ -1,8 +1,8 @@ -require 'abstract_unit' +require "abstract_unit" module TestApiFileUtils def file_path() File.expand_path(__FILE__) end - def file_data() @data ||= File.open(file_path, 'rb') { |f| f.read } end + def file_data() @data ||= File.open(file_path, "rb") { |f| f.read } end end class DataStreamingApiController < ActionController::API @@ -19,7 +19,7 @@ class DataStreamingApiTest < ActionController::TestCase tests DataStreamingApiController def test_data - response = process('two') + response = process("two") assert_kind_of String, response.body assert_equal file_data, response.body end diff --git a/actionpack/test/controller/api/force_ssl_test.rb b/actionpack/test/controller/api/force_ssl_test.rb index 8578340d82..d239964e4a 100644 --- a/actionpack/test/controller/api/force_ssl_test.rb +++ b/actionpack/test/controller/api/force_ssl_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class ForceSSLApiController < ActionController::API force_ssl diff --git a/actionpack/test/controller/api/implicit_render_test.rb b/actionpack/test/controller/api/implicit_render_test.rb index 26f9cd8f78..b51ee0cf42 100644 --- a/actionpack/test/controller/api/implicit_render_test.rb +++ b/actionpack/test/controller/api/implicit_render_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class ImplicitRenderAPITestController < ActionController::API def empty_action diff --git a/actionpack/test/controller/api/params_wrapper_test.rb b/actionpack/test/controller/api/params_wrapper_test.rb index 53b3a0c3cc..a1da852040 100644 --- a/actionpack/test/controller/api/params_wrapper_test.rb +++ b/actionpack/test/controller/api/params_wrapper_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class ParamsWrapperForApiTest < ActionController::TestCase class UsersController < ActionController::API @@ -17,10 +17,10 @@ class ParamsWrapperForApiTest < ActionController::TestCase tests UsersController def test_specify_wrapper_name - @request.env['CONTENT_TYPE'] = 'application/json' - post :test, params: { 'username' => 'sikachu' } + @request.env["CONTENT_TYPE"] = "application/json" + post :test, params: { "username" => "sikachu" } - expected = { 'username' => 'sikachu', 'person' => { 'username' => 'sikachu' }} + expected = { "username" => "sikachu", "person" => { "username" => "sikachu" } } assert_equal expected, @controller.last_parameters end end diff --git a/actionpack/test/controller/api/redirect_to_test.rb b/actionpack/test/controller/api/redirect_to_test.rb index 18877c4b3a..ab14409f40 100644 --- a/actionpack/test/controller/api/redirect_to_test.rb +++ b/actionpack/test/controller/api/redirect_to_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class RedirectToApiController < ActionController::API def one diff --git a/actionpack/test/controller/api/renderers_test.rb b/actionpack/test/controller/api/renderers_test.rb index 911a8144b2..04e34a1f8f 100644 --- a/actionpack/test/controller/api/renderers_test.rb +++ b/actionpack/test/controller/api/renderers_test.rb @@ -1,14 +1,14 @@ -require 'abstract_unit' -require 'active_support/core_ext/hash/conversions' +require "abstract_unit" +require "active_support/core_ext/hash/conversions" class RenderersApiController < ActionController::API class Model def to_json(options = {}) - { a: 'b' }.to_json(options) + { a: "b" }.to_json(options) end def to_xml(options = {}) - { a: 'b' }.to_xml(options) + { a: "b" }.to_xml(options) end end @@ -21,11 +21,7 @@ class RenderersApiController < ActionController::API end def plain - render plain: 'Hi from plain', status: 500 - end - - def text - render text: 'Hi from text', status: 500 + render plain: "Hi from plain", status: 500 end end @@ -35,26 +31,18 @@ class RenderersApiTest < ActionController::TestCase def test_render_json get :one assert_response :success - assert_equal({ a: 'b' }.to_json, @response.body) + assert_equal({ a: "b" }.to_json, @response.body) end def test_render_xml get :two assert_response :success - assert_equal({ a: 'b' }.to_xml, @response.body) + assert_equal({ a: "b" }.to_xml, @response.body) end def test_render_plain get :plain assert_response :internal_server_error - assert_equal('Hi from plain', @response.body) - end - - def test_render_text - assert_deprecated do - get :text - end - assert_response :internal_server_error - assert_equal('Hi from text', @response.body) + assert_equal("Hi from plain", @response.body) end end diff --git a/actionpack/test/controller/api/url_for_test.rb b/actionpack/test/controller/api/url_for_test.rb index 0d8691a091..cb4ae7a88a 100644 --- a/actionpack/test/controller/api/url_for_test.rb +++ b/actionpack/test/controller/api/url_for_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class UrlForApiController < ActionController::API def one; end @@ -10,7 +10,7 @@ class UrlForApiTest < ActionController::TestCase def setup super - @request.host = 'www.example.com' + @request.host = "www.example.com" end def test_url_for diff --git a/actionpack/test/controller/api/with_cookies_test.rb b/actionpack/test/controller/api/with_cookies_test.rb new file mode 100644 index 0000000000..8928237dfd --- /dev/null +++ b/actionpack/test/controller/api/with_cookies_test.rb @@ -0,0 +1,21 @@ +require "abstract_unit" + +class WithCookiesController < ActionController::API + include ActionController::Cookies + + def with_cookies + render plain: cookies[:foobar] + end +end + +class WithCookiesTest < ActionController::TestCase + tests WithCookiesController + + def test_with_cookies + request.cookies[:foobar] = "bazbang" + + get :with_cookies + + assert_equal "bazbang", response.body + end +end diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index e3f669dbb5..42a5157010 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'active_support/logger' -require 'controller/fake_models' +require "abstract_unit" +require "active_support/logger" +require "controller/fake_models" # Provide some controller to run the tests on. module Submodule @@ -19,11 +19,11 @@ end class DefaultUrlOptionsController < ActionController::Base def from_view - render :inline => "<%= #{params[:route]} %>" + render inline: "<%= #{params[:route]} %>" end def default_url_options - { :host => 'www.override.com', :action => 'new', :locale => 'en' } + { host: "www.override.com", action: "new", locale: "en" } end end @@ -33,17 +33,17 @@ class OptionalDefaultUrlOptionsController < ActionController::Base end def default_url_options - { format: 'atom', id: 'default-id' } + { format: "atom", id: "default-id" } end end class UrlOptionsController < ActionController::Base def from_view - render :inline => "<%= #{params[:route]} %>" + render inline: "<%= #{params[:route]} %>" end def url_options - super.merge(:host => 'www.override.com') + super.merge(host: "www.override.com") end end @@ -58,17 +58,16 @@ class ActionMissingController < ActionController::Base end class ControllerClassTests < ActiveSupport::TestCase - def test_controller_path - assert_equal 'empty', EmptyController.controller_path + assert_equal "empty", EmptyController.controller_path assert_equal EmptyController.controller_path, EmptyController.new.controller_path - assert_equal 'submodule/contained_empty', Submodule::ContainedEmptyController.controller_path + assert_equal "submodule/contained_empty", Submodule::ContainedEmptyController.controller_path assert_equal Submodule::ContainedEmptyController.controller_path, Submodule::ContainedEmptyController.new.controller_path end def test_controller_name - assert_equal 'empty', EmptyController.controller_name - assert_equal 'contained_empty', Submodule::ContainedEmptyController.controller_name + assert_equal "empty", EmptyController.controller_name + assert_equal "contained_empty", Submodule::ContainedEmptyController.controller_name end def test_no_deprecation_when_action_view_record_identifier_is_included @@ -80,13 +79,13 @@ class ControllerClassTests < ActiveSupport::TestCase dom_id = RecordIdentifierIncludedController.new.dom_id(record) end - assert_equal 'comment_1', dom_id + assert_equal "comment_1", dom_id dom_class = nil assert_not_deprecated do dom_class = RecordIdentifierIncludedController.new.dom_class(record) end - assert_equal 'comment', dom_class + assert_equal "comment", dom_class end end @@ -112,7 +111,7 @@ class ControllerInstanceTests < ActiveSupport::TestCase end def test_temporary_anonymous_controllers - name = 'ExamplesController' + name = "ExamplesController" klass = Class.new(ActionController::Base) Object.const_set(name, klass) @@ -152,37 +151,40 @@ class UrlOptionsTest < ActionController::TestCase def setup super - @request.host = 'www.example.com' + @request.host = "www.example.com" end def test_url_for_query_params_included rs = ActionDispatch::Routing::RouteSet.new rs.draw do - get 'home' => 'pages#home' + get "home" => "pages#home" end options = { - :action => "home", - :controller => "pages", - :only_path => true, - :params => { "token" => "secret" } + action: "home", + controller: "pages", + only_path: true, + params: { "token" => "secret" } } - assert_equal '/home?token=secret', rs.url_for(options) + assert_equal "/home?token=secret", rs.url_for(options) end def test_url_options_override with_routing do |set| set.draw do - get 'from_view', :to => 'url_options#from_view', :as => :from_view - get ':controller/:action' + get "from_view", to: "url_options#from_view", as: :from_view + + ActiveSupport::Deprecation.silence do + get ":controller/:action" + end end get :from_view, params: { route: "from_view_url" } - assert_equal 'http://www.override.com/from_view', @response.body - assert_equal 'http://www.override.com/from_view', @controller.send(:from_view_url) - assert_equal 'http://www.override.com/default_url_options/index', @controller.url_for(:controller => 'default_url_options') + assert_equal "http://www.override.com/from_view", @response.body + assert_equal "http://www.override.com/from_view", @controller.send(:from_view_url) + assert_equal "http://www.override.com/default_url_options/index", @controller.url_for(controller: "default_url_options") end end @@ -192,7 +194,7 @@ class UrlOptionsTest < ActionController::TestCase get "account/overview" end - assert !@controller.class.action_methods.include?("account_overview_path") + assert_not_includes @controller.class.action_methods, "account_overview_path" end end end @@ -202,21 +204,24 @@ class DefaultUrlOptionsTest < ActionController::TestCase def setup super - @request.host = 'www.example.com' + @request.host = "www.example.com" end def test_default_url_options_override with_routing do |set| set.draw do - get 'from_view', :to => 'default_url_options#from_view', :as => :from_view - get ':controller/:action' + get "from_view", to: "default_url_options#from_view", as: :from_view + + ActiveSupport::Deprecation.silence do + get ":controller/:action" + end end get :from_view, params: { route: "from_view_url" } - assert_equal 'http://www.override.com/from_view?locale=en', @response.body - assert_equal 'http://www.override.com/from_view?locale=en', @controller.send(:from_view_url) - assert_equal 'http://www.override.com/default_url_options/new?locale=en', @controller.url_for(:controller => 'default_url_options') + assert_equal "http://www.override.com/from_view?locale=en", @response.body + assert_equal "http://www.override.com/from_view?locale=en", @controller.send(:from_view_url) + assert_equal "http://www.override.com/default_url_options/new?locale=en", @controller.url_for(controller: "default_url_options") end end @@ -226,22 +231,25 @@ class DefaultUrlOptionsTest < ActionController::TestCase scope("/:locale") do resources :descriptions end - get ':controller/:action' + + ActiveSupport::Deprecation.silence do + get ":controller/:action" + end end get :from_view, params: { route: "description_path(1)" } - assert_equal '/en/descriptions/1', @response.body - assert_equal '/en/descriptions', @controller.send(:descriptions_path) - assert_equal '/pl/descriptions', @controller.send(:descriptions_path, "pl") - assert_equal '/pl/descriptions', @controller.send(:descriptions_path, :locale => "pl") - assert_equal '/pl/descriptions.xml', @controller.send(:descriptions_path, "pl", "xml") - assert_equal '/en/descriptions.xml', @controller.send(:descriptions_path, :format => "xml") - assert_equal '/en/descriptions/1', @controller.send(:description_path, 1) - assert_equal '/pl/descriptions/1', @controller.send(:description_path, "pl", 1) - assert_equal '/pl/descriptions/1', @controller.send(:description_path, 1, :locale => "pl") - assert_equal '/pl/descriptions/1.xml', @controller.send(:description_path, "pl", 1, "xml") - assert_equal '/en/descriptions/1.xml', @controller.send(:description_path, 1, :format => "xml") + assert_equal "/en/descriptions/1", @response.body + assert_equal "/en/descriptions", @controller.send(:descriptions_path) + assert_equal "/pl/descriptions", @controller.send(:descriptions_path, "pl") + assert_equal "/pl/descriptions", @controller.send(:descriptions_path, locale: "pl") + assert_equal "/pl/descriptions.xml", @controller.send(:descriptions_path, "pl", "xml") + assert_equal "/en/descriptions.xml", @controller.send(:descriptions_path, format: "xml") + assert_equal "/en/descriptions/1", @controller.send(:description_path, 1) + assert_equal "/pl/descriptions/1", @controller.send(:description_path, "pl", 1) + assert_equal "/pl/descriptions/1", @controller.send(:description_path, 1, locale: "pl") + assert_equal "/pl/descriptions/1.xml", @controller.send(:description_path, "pl", 1, "xml") + assert_equal "/en/descriptions/1.xml", @controller.send(:description_path, 1, format: "xml") end end end @@ -250,10 +258,10 @@ class OptionalDefaultUrlOptionsControllerTest < ActionController::TestCase def test_default_url_options_override_missing_positional_arguments with_routing do |set| set.draw do - get "/things/:id(.:format)" => 'things#show', :as => :thing + get "/things/:id(.:format)" => "things#show", :as => :thing end - assert_equal '/things/1.atom', thing_path('1') - assert_equal '/things/default-id.atom', thing_path + assert_equal "/things/1.atom", thing_path("1") + assert_equal "/things/default-id.atom", thing_path end end end @@ -263,7 +271,7 @@ class EmptyUrlOptionsTest < ActionController::TestCase def setup super - @request.host = 'www.example.com' + @request.host = "www.example.com" end def test_ensure_url_for_works_as_expected_when_called_with_no_options_if_default_url_options_is_not_set @@ -280,7 +288,7 @@ class EmptyUrlOptionsTest < ActionController::TestCase resources :things end - assert_equal '/things', @controller.send(:things_path) + assert_equal "/things", @controller.send(:things_path) end end end diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index 7556f984f2..18490c7d73 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -1,10 +1,10 @@ -require 'fileutils' -require 'abstract_unit' -require 'lib/controller/fake_models' +require "fileutils" +require "abstract_unit" +require "lib/controller/fake_models" -CACHE_DIR = 'test_cache' +CACHE_DIR = "test_cache" # Don't change '/../temp/' cavalierly or you might hose something you don't want hosed -FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR) +FILE_STORE_PATH = File.join(File.dirname(__FILE__), "/../temp/", CACHE_DIR) class FragmentCachingMetalTestController < ActionController::Metal abstract! @@ -21,14 +21,14 @@ class FragmentCachingMetalTest < ActionController::TestCase @controller = FragmentCachingMetalTestController.new @controller.perform_caching = true @controller.cache_store = @store - @params = { controller: 'posts', action: 'index' } + @params = { controller: "posts", action: "index" } @controller.params = @params @controller.request = @request @controller.response = @response end def test_fragment_cache_key - assert_equal 'views/what a key', @controller.fragment_cache_key('what a key') + assert_equal "views/what a key", @controller.fragment_cache_key("what a key") end end @@ -49,96 +49,96 @@ class FragmentCachingTest < ActionController::TestCase @controller = FragmentCachingTestController.new @controller.perform_caching = true @controller.cache_store = @store - @params = {:controller => 'posts', :action => 'index'} + @params = { controller: "posts", action: "index" } @controller.params = @params @controller.request = @request @controller.response = @response end def test_fragment_cache_key - assert_equal 'views/what a key', @controller.fragment_cache_key('what a key') + assert_equal "views/what a key", @controller.fragment_cache_key("what a key") assert_equal "views/test.host/fragment_caching_test/some_action", - @controller.fragment_cache_key(:controller => 'fragment_caching_test',:action => 'some_action') + @controller.fragment_cache_key(controller: "fragment_caching_test",action: "some_action") end def test_read_fragment_with_caching_enabled - @store.write('views/name', 'value') - assert_equal 'value', @controller.read_fragment('name') + @store.write("views/name", "value") + assert_equal "value", @controller.read_fragment("name") end def test_read_fragment_with_caching_disabled @controller.perform_caching = false - @store.write('views/name', 'value') - assert_nil @controller.read_fragment('name') + @store.write("views/name", "value") + assert_nil @controller.read_fragment("name") end def test_fragment_exist_with_caching_enabled - @store.write('views/name', 'value') - assert @controller.fragment_exist?('name') - assert !@controller.fragment_exist?('other_name') + @store.write("views/name", "value") + assert @controller.fragment_exist?("name") + assert !@controller.fragment_exist?("other_name") end def test_fragment_exist_with_caching_disabled @controller.perform_caching = false - @store.write('views/name', 'value') - assert !@controller.fragment_exist?('name') - assert !@controller.fragment_exist?('other_name') + @store.write("views/name", "value") + assert !@controller.fragment_exist?("name") + assert !@controller.fragment_exist?("other_name") end def test_write_fragment_with_caching_enabled - assert_nil @store.read('views/name') - assert_equal 'value', @controller.write_fragment('name', 'value') - assert_equal 'value', @store.read('views/name') + assert_nil @store.read("views/name") + assert_equal "value", @controller.write_fragment("name", "value") + assert_equal "value", @store.read("views/name") end def test_write_fragment_with_caching_disabled - assert_nil @store.read('views/name') + assert_nil @store.read("views/name") @controller.perform_caching = false - assert_equal 'value', @controller.write_fragment('name', 'value') - assert_nil @store.read('views/name') + assert_equal "value", @controller.write_fragment("name", "value") + assert_nil @store.read("views/name") end def test_expire_fragment_with_simple_key - @store.write('views/name', 'value') - @controller.expire_fragment 'name' - assert_nil @store.read('views/name') + @store.write("views/name", "value") + @controller.expire_fragment "name" + assert_nil @store.read("views/name") end def test_expire_fragment_with_regexp - @store.write('views/name', 'value') - @store.write('views/another_name', 'another_value') - @store.write('views/primalgrasp', 'will not expire ;-)') + @store.write("views/name", "value") + @store.write("views/another_name", "another_value") + @store.write("views/primalgrasp", "will not expire ;-)") @controller.expire_fragment(/name/) - assert_nil @store.read('views/name') - assert_nil @store.read('views/another_name') - assert_equal 'will not expire ;-)', @store.read('views/primalgrasp') + assert_nil @store.read("views/name") + assert_nil @store.read("views/another_name") + assert_equal "will not expire ;-)", @store.read("views/primalgrasp") end def test_fragment_for - @store.write('views/expensive', 'fragment content') + @store.write("views/expensive", "fragment content") fragment_computed = false view_context = @controller.view_context - buffer = 'generated till now -> '.html_safe - buffer << view_context.send(:fragment_for, 'expensive') { fragment_computed = true } + buffer = "generated till now -> ".html_safe + buffer << view_context.send(:fragment_for, "expensive") { fragment_computed = true } assert !fragment_computed - assert_equal 'generated till now -> fragment content', buffer + assert_equal "generated till now -> fragment content", buffer end def test_html_safety - assert_nil @store.read('views/name') - content = 'value'.html_safe - assert_equal content, @controller.write_fragment('name', content) + assert_nil @store.read("views/name") + content = "value".html_safe + assert_equal content, @controller.write_fragment("name", content) - cached = @store.read('views/name') + cached = @store.read("views/name") assert_equal content, cached assert_equal String, cached.class - html_safe = @controller.read_fragment('name') + html_safe = @controller.read_fragment("name") assert_equal content, html_safe assert html_safe.html_safe? end @@ -184,6 +184,7 @@ class FunctionalFragmentCachingTest < ActionController::TestCase @controller = FunctionalCachingController.new @controller.perform_caching = true @controller.cache_store = @store + @controller.enable_fragment_cache_logging = true end def test_fragment_caching @@ -219,12 +220,15 @@ CACHED end def test_fragment_caching_with_options + time = Time.now get :fragment_cached_with_options assert_response :success expected_body = "<body>\n<p>ERB</p>\n</body>\n" assert_equal expected_body, @response.body - assert_equal "<p>ERB</p>", @store.read("views/with_options") + Time.stub(:now, time + 11) do + assert_nil @store.read("views/with_options") + end end def test_render_inline_before_fragment_caching @@ -274,7 +278,6 @@ CACHED @store.read("views/test.host/functional_caching/formatted_fragment_cached/#{template_digest("functional_caching/formatted_fragment_cached")}") end - def test_fragment_caching_with_variant get :formatted_fragment_cached_with_variant, format: "html", params: { v: :phone } assert_response :success @@ -293,7 +296,6 @@ CACHED end class CacheHelperOutputBufferTest < ActionController::TestCase - class MockController def read_fragment(name, options) return false @@ -322,7 +324,7 @@ class CacheHelperOutputBufferTest < ActionController::TestCase cache_helper.stub :output_buffer, output_buffer do assert_called_with cache_helper, :output_buffer=, [output_buffer.class.new(output_buffer)] do assert_nothing_raised do - cache_helper.send :fragment_for, 'Test fragment name', 'Test fragment', &Proc.new{ nil } + cache_helper.send :fragment_for, "Test fragment name", "Test fragment", &Proc.new { nil } end end end @@ -343,7 +345,7 @@ class CacheHelperOutputBufferTest < ActionController::TestCase cache_helper.stub :output_buffer, output_buffer do assert_called_with cache_helper, :output_buffer=, [output_buffer.class.new(output_buffer)] do assert_nothing_raised do - cache_helper.send :fragment_for, 'Test fragment name', 'Test fragment', &Proc.new{ nil } + cache_helper.send :fragment_for, "Test fragment name", "Test fragment", &Proc.new { nil } end end end @@ -373,44 +375,44 @@ class CollectionCacheController < ActionController::Base attr_accessor :partial_rendered_times def index - @customers = [Customer.new('david', params[:id] || 1)] + @customers = [Customer.new("david", params[:id] || 1)] end def index_ordered - @customers = [Customer.new('david', 1), Customer.new('david', 2), Customer.new('david', 3)] - render 'index' + @customers = [Customer.new("david", 1), Customer.new("david", 2), Customer.new("david", 3)] + render "index" end - def index_explicit_render - @customers = [Customer.new('david', 1)] - render partial: 'customers/customer', collection: @customers + def index_explicit_render_in_controller + @customers = [Customer.new("david", 1)] + render partial: "customers/customer", collection: @customers, cached: true end def index_with_comment - @customers = [Customer.new('david', 1)] - render partial: 'customers/commented_customer', collection: @customers, as: :customer + @customers = [Customer.new("david", 1)] + render partial: "customers/commented_customer", collection: @customers, as: :customer, cached: true end def index_with_callable_cache_key - @customers = [Customer.new('david', 1)] - render @customers, cache: -> customer { 'cached_david' } + @customers = [Customer.new("david", 1)] + render partial: "customers/customer", collection: @customers, cached: -> customer { "cached_david" } end end -class AutomaticCollectionCacheTest < ActionController::TestCase +class CollectionCacheTest < ActionController::TestCase def setup super @controller = CollectionCacheController.new @controller.perform_caching = true @controller.partial_rendered_times = 0 @controller.cache_store = ActiveSupport::Cache::MemoryStore.new - ActionView::PartialRenderer.collection_cache = @controller.cache_store + ActionView::PartialRenderer.collection_cache = ActiveSupport::Cache::MemoryStore.new end def test_collection_fetches_cached_views get :index assert_equal 1, @controller.partial_rendered_times - assert_customer_cached 'david/1', 'david, 1' + assert_customer_cached "david/1", "david, 1" get :index assert_equal 1, @controller.partial_rendered_times @@ -419,17 +421,17 @@ class AutomaticCollectionCacheTest < ActionController::TestCase def test_preserves_order_when_reading_from_cache_plus_rendering get :index, params: { id: 2 } assert_equal 1, @controller.partial_rendered_times - assert_select ':root', 'david, 2' + assert_select ":root", "david, 2" get :index_ordered assert_equal 3, @controller.partial_rendered_times - assert_select ':root', "david, 1\n david, 2\n david, 3" + assert_select ":root", "david, 1\n david, 2\n david, 3" end def test_explicit_render_call_with_options - get :index_explicit_render + get :index_explicit_render_in_controller - assert_select ':root', "david, 1" + assert_select ":root", "david, 1" end def test_caching_works_with_beginning_comment @@ -442,8 +444,7 @@ class AutomaticCollectionCacheTest < ActionController::TestCase def test_caching_with_callable_cache_key get :index_with_callable_cache_key - assert_customer_cached 'cached_david', 'david, 1' - assert_customer_cached 'david/1', 'david, 1' + assert_customer_cached "cached_david", "david, 1" end private @@ -471,9 +472,9 @@ class FragmentCacheKeyTest < ActionController::TestCase def test_fragment_cache_key @controller.account_id = "123" - assert_equal 'views/v1/123/what a key', @controller.fragment_cache_key('what a key') + assert_equal "views/v1/123/what a key", @controller.fragment_cache_key("what a key") @controller.account_id = nil - assert_equal 'views/v1//what a key', @controller.fragment_cache_key('what a key') + assert_equal "views/v1//what a key", @controller.fragment_cache_key("what a key") end end diff --git a/actionpack/test/controller/content_type_test.rb b/actionpack/test/controller/content_type_test.rb index c02607b55e..fcb2632b80 100644 --- a/actionpack/test/controller/content_type_test.rb +++ b/actionpack/test/controller/content_type_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class OldContentTypeController < ActionController::Base # :ported: @@ -14,7 +14,7 @@ class OldContentTypeController < ActionController::Base # :ported: def render_content_type_from_render - render body: "hello world!", :content_type => Mime[:rss] + render body: "hello world!", content_type: Mime[:rss] end # :ported: @@ -37,7 +37,7 @@ class OldContentTypeController < ActionController::Base def render_change_for_builder response.content_type = Mime[:html] - render :action => "render_default_for_builder" + render action: "render_default_for_builder" end def render_default_content_types_for_respond_to @@ -131,13 +131,13 @@ class ContentTypeTest < ActionController::TestCase private - def with_default_charset(charset) - old_default_charset = ActionDispatch::Response.default_charset - ActionDispatch::Response.default_charset = charset - yield - ensure - ActionDispatch::Response.default_charset = old_default_charset - end + def with_default_charset(charset) + old_default_charset = ActionDispatch::Response.default_charset + ActionDispatch::Response.default_charset = charset + yield + ensure + ActionDispatch::Response.default_charset = old_default_charset + end end class AcceptBasedContentTypeTest < ActionController::TestCase diff --git a/actionpack/test/controller/default_url_options_with_before_action_test.rb b/actionpack/test/controller/default_url_options_with_before_action_test.rb index 12fbe0424e..e3fe7a6495 100644 --- a/actionpack/test/controller/default_url_options_with_before_action_test.rb +++ b/actionpack/test/controller/default_url_options_with_before_action_test.rb @@ -1,7 +1,6 @@ -require 'abstract_unit' +require "abstract_unit" class ControllerWithBeforeActionAndDefaultUrlOptions < ActionController::Base - before_action { I18n.locale = params[:locale] } after_action { I18n.locale = "en" } @@ -10,16 +9,15 @@ class ControllerWithBeforeActionAndDefaultUrlOptions < ActionController::Base end def redirect - redirect_to :action => "target" + redirect_to action: "target" end def default_url_options - {:locale => "de"} + { locale: "de" } end end class ControllerWithBeforeActionAndDefaultUrlOptionsTest < ActionController::TestCase - # This test has its roots in issue #1872 test "should redirect with correct locale :de" do get :redirect, params: { locale: "de" } diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index 08271012e9..a34878cee8 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -1,8 +1,8 @@ -require 'abstract_unit' +require "abstract_unit" class ActionController::Base class << self - %w(append_around_action prepend_after_action prepend_around_action prepend_before_action skip_after_action skip_before_action skip_action_callback).each do |pending| + %w(append_around_action prepend_after_action prepend_around_action prepend_before_action skip_after_action skip_before_action).each do |pending| define_method(pending) do |*args| $stderr.puts "#{pending} unimplemented: #{args.inspect}" end unless method_defined?(pending) @@ -21,7 +21,7 @@ class FilterTest < ActionController::TestCase after_action :clean_up def show - render :inline => "ran action" + render inline: "ran action" end private @@ -37,7 +37,7 @@ class FilterTest < ActionController::TestCase end class ChangingTheRequirementsController < TestController - before_action :ensure_login, :except => [:go_wild] + before_action :ensure_login, except: [:go_wild] def go_wild render plain: "gobble" @@ -56,14 +56,14 @@ class FilterTest < ActionController::TestCase end protected - (1..3).each do |i| - define_method "try_#{i}" do - instance_variable_set :@try, i - if action_name == "fail_#{i}" - head(404) + (1..3).each do |i| + define_method "try_#{i}" do + instance_variable_set :@try, i + if action_name == "fail_#{i}" + head(404) + end end end - end end class RenderingController < ActionController::Base @@ -72,14 +72,14 @@ class FilterTest < ActionController::TestCase def show @ran_action = true - render :inline => "ran action" + render inline: "ran action" end private def before_action_rendering @ran_filter ||= [] @ran_filter << "before_action_rendering" - render :inline => "something else" + render inline: "something else" end def unreached_after_action @@ -102,19 +102,19 @@ class FilterTest < ActionController::TestCase def show @ran_action = true - render :inline => "ran show action" + render inline: "ran show action" end def target_of_redirection @ran_target_of_redirection = true - render :inline => "ran target_of_redirection action" + render inline: "ran target_of_redirection action" end private def before_action_redirects @ran_filter ||= [] @ran_filter << "before_action_redirects" - redirect_to(:action => 'target_of_redirection') + redirect_to(action: "target_of_redirection") end def unreached_after_action @@ -133,15 +133,15 @@ class FilterTest < ActionController::TestCase class ConditionalFilterController < ActionController::Base def show - render :inline => "ran action" + render inline: "ran action" end def another_action - render :inline => "ran action" + render inline: "ran action" end def show_without_action - render :inline => "ran action without action" + render inline: "ran action without action" end private @@ -157,28 +157,28 @@ class FilterTest < ActionController::TestCase end class ConditionalCollectionFilterController < ConditionalFilterController - before_action :ensure_login, :except => [ :show_without_action, :another_action ] + before_action :ensure_login, except: [ :show_without_action, :another_action ] end class OnlyConditionSymController < ConditionalFilterController - before_action :ensure_login, :only => :show + before_action :ensure_login, only: :show end class ExceptConditionSymController < ConditionalFilterController - before_action :ensure_login, :except => :show_without_action + before_action :ensure_login, except: :show_without_action end class BeforeAndAfterConditionController < ConditionalFilterController - before_action :ensure_login, :only => :show - after_action :clean_up_tmp, :only => :show + before_action :ensure_login, only: :show + after_action :clean_up_tmp, only: :show end class OnlyConditionProcController < ConditionalFilterController - before_action(:only => :show) {|c| c.instance_variable_set(:"@ran_proc_action", true) } + before_action(only: :show) { |c| c.instance_variable_set(:"@ran_proc_action", true) } end class ExceptConditionProcController < ConditionalFilterController - before_action(:except => :show_without_action) {|c| c.instance_variable_set(:"@ran_proc_action", true) } + before_action(except: :show_without_action) { |c| c.instance_variable_set(:"@ran_proc_action", true) } end class ConditionalClassFilter @@ -186,24 +186,24 @@ class FilterTest < ActionController::TestCase end class OnlyConditionClassController < ConditionalFilterController - before_action ConditionalClassFilter, :only => :show + before_action ConditionalClassFilter, only: :show end class ExceptConditionClassController < ConditionalFilterController - before_action ConditionalClassFilter, :except => :show_without_action + before_action ConditionalClassFilter, except: :show_without_action end class AnomolousYetValidConditionController < ConditionalFilterController - before_action(ConditionalClassFilter, :ensure_login, Proc.new {|c| c.instance_variable_set(:"@ran_proc_action1", true)}, :except => :show_without_action) { |c| c.instance_variable_set(:"@ran_proc_action2", true)} + before_action(ConditionalClassFilter, :ensure_login, Proc.new { |c| c.instance_variable_set(:"@ran_proc_action1", true) }, except: :show_without_action) { |c| c.instance_variable_set(:"@ran_proc_action2", true) } end class OnlyConditionalOptionsFilter < ConditionalFilterController - before_action :ensure_login, :only => :index, :if => Proc.new {|c| c.instance_variable_set(:"@ran_conditional_index_proc", true) } + before_action :ensure_login, only: :index, if: Proc.new { |c| c.instance_variable_set(:"@ran_conditional_index_proc", true) } end class ConditionalOptionsFilter < ConditionalFilterController - before_action :ensure_login, :if => Proc.new { |c| true } - before_action :clean_up_tmp, :if => Proc.new { |c| false } + before_action :ensure_login, if: Proc.new { |c| true } + before_action :clean_up_tmp, if: Proc.new { |c| false } end class ConditionalOptionsSkipFilter < ConditionalFilterController @@ -222,7 +222,7 @@ class FilterTest < ActionController::TestCase skip_before_action :clean_up_tmp, only: :login, if: -> { true } def login - render plain: 'ok' + render plain: "ok" end end @@ -234,7 +234,7 @@ class FilterTest < ActionController::TestCase skip_before_action :clean_up_tmp, if: -> { true }, except: :login def login - render plain: 'ok' + render plain: "ok" end end @@ -255,14 +255,14 @@ class FilterTest < ActionController::TestCase class SkippingAndLimitedController < TestController skip_before_action :ensure_login - before_action :ensure_login, :only => :index + before_action :ensure_login, only: :index def index - render plain: 'ok' + render plain: "ok" end def public - render plain: 'ok' + render plain: "ok" end end @@ -272,7 +272,7 @@ class FilterTest < ActionController::TestCase before_action :ensure_login def index - render plain: 'ok' + render plain: "ok" end private @@ -283,17 +283,17 @@ class FilterTest < ActionController::TestCase end class ConditionalSkippingController < TestController - skip_before_action :ensure_login, :only => [ :login ] - skip_after_action :clean_up, :only => [ :login ] + skip_before_action :ensure_login, only: [ :login ] + skip_after_action :clean_up, only: [ :login ] - before_action :find_user, :only => [ :change_password ] + before_action :find_user, only: [ :change_password ] def login - render :inline => "ran action" + render inline: "ran action" end def change_password - render :inline => "ran action" + render inline: "ran action" end protected @@ -304,29 +304,29 @@ class FilterTest < ActionController::TestCase end class ConditionalParentOfConditionalSkippingController < ConditionalFilterController - before_action :conditional_in_parent_before, :only => [:show, :another_action] - after_action :conditional_in_parent_after, :only => [:show, :another_action] + before_action :conditional_in_parent_before, only: [:show, :another_action] + after_action :conditional_in_parent_after, only: [:show, :another_action] private def conditional_in_parent_before @ran_filter ||= [] - @ran_filter << 'conditional_in_parent_before' + @ran_filter << "conditional_in_parent_before" end def conditional_in_parent_after @ran_filter ||= [] - @ran_filter << 'conditional_in_parent_after' + @ran_filter << "conditional_in_parent_after" end end class ChildOfConditionalParentController < ConditionalParentOfConditionalSkippingController - skip_before_action :conditional_in_parent_before, :only => :another_action - skip_after_action :conditional_in_parent_after, :only => :another_action + skip_before_action :conditional_in_parent_before, only: :another_action + skip_after_action :conditional_in_parent_after, only: :another_action end class AnotherChildOfConditionalParentController < ConditionalParentOfConditionalSkippingController - skip_before_action :conditional_in_parent_before, :only => :show + skip_before_action :conditional_in_parent_before, only: :show end class ProcController < PrependingController @@ -418,14 +418,14 @@ class FilterTest < ActionController::TestCase class OutOfOrder < StandardError; end before_action :first - before_action :second, :only => :foo + before_action :second, only: :foo def foo - render plain: 'foo' + render plain: "foo" end def bar - render plain: 'bar' + render plain: "bar" end protected @@ -458,20 +458,20 @@ class FilterTest < ActionController::TestCase def before_all @ran_filter ||= [] - @ran_filter << 'before_all' + @ran_filter << "before_all" end def after_all @ran_filter ||= [] - @ran_filter << 'after_all' + @ran_filter << "after_all" end def between_before_all_and_after_all @ran_filter ||= [] - @ran_filter << 'between_before_all_and_after_all' + @ran_filter << "between_before_all_and_after_all" end def show - render plain: 'hello' + render plain: "hello" end end @@ -494,14 +494,13 @@ class FilterTest < ActionController::TestCase end class NonYieldingAroundFilterController < ActionController::Base - before_action :filter_one around_action :non_yielding_action before_action :action_two after_action :action_three def index - render :inline => "index" + render inline: "index" end private @@ -522,22 +521,21 @@ class FilterTest < ActionController::TestCase def action_three @filters << "action_three" end - end class ImplicitActionsController < ActionController::Base - before_action :find_only, :only => :edit - before_action :find_except, :except => :edit + before_action :find_only, only: :edit + before_action :find_except, except: :edit private - def find_only - @only = 'Only' - end + def find_only + @only = "Only" + end - def find_except - @except = 'Except' - end + def find_except + @except = "Except" + end end def test_non_yielding_around_actions_do_not_raise @@ -611,12 +609,12 @@ class FilterTest < ActionController::TestCase end def test_if_is_ignored_when_used_with_only - test_process(SkipFilterUsingOnlyAndIf, 'login') + test_process(SkipFilterUsingOnlyAndIf, "login") assert_not @controller.instance_variable_defined?(:@ran_filter) end def test_except_is_ignored_when_used_with_if - test_process(SkipFilterUsingIfAndExcept, 'login') + test_process(SkipFilterUsingIfAndExcept, "login") assert_equal %w(ensure_login), @controller.instance_variable_get(:@ran_filter) end @@ -745,13 +743,13 @@ class FilterTest < ActionController::TestCase def test_actions_with_mixed_specialization_run_in_order assert_nothing_raised do - response = test_process(MixedSpecializationController, 'bar') - assert_equal 'bar', response.body + response = test_process(MixedSpecializationController, "bar") + assert_equal "bar", response.body end assert_nothing_raised do - response = test_process(MixedSpecializationController, 'foo') - assert_equal 'foo', response.body + response = test_process(MixedSpecializationController, "foo") + assert_equal "foo", response.body end end @@ -795,7 +793,7 @@ class FilterTest < ActionController::TestCase def test_conditional_skipping_of_actions_when_parent_action_is_also_conditional test_process(ChildOfConditionalParentController) assert_equal %w( conditional_in_parent_before conditional_in_parent_after ), @controller.instance_variable_get(:@ran_filter) - test_process(ChildOfConditionalParentController, 'another_action') + test_process(ChildOfConditionalParentController, "another_action") assert_not @controller.instance_variable_defined?(:@ran_filter) end @@ -824,15 +822,15 @@ class FilterTest < ActionController::TestCase end def test_actions_obey_only_and_except_for_implicit_actions - test_process(ImplicitActionsController, 'show') - assert_equal 'Except', @controller.instance_variable_get(:@except) + test_process(ImplicitActionsController, "show") + assert_equal "Except", @controller.instance_variable_get(:@except) assert_not @controller.instance_variable_defined?(:@only) - assert_equal 'show', response.body + assert_equal "show", response.body - test_process(ImplicitActionsController, 'edit') - assert_equal 'Only', @controller.instance_variable_get(:@only) + test_process(ImplicitActionsController, "edit") + assert_equal "Only", @controller.instance_variable_get(:@only) assert_not @controller.instance_variable_defined?(:@except) - assert_equal 'edit', response.body + assert_equal "edit", response.body end private @@ -859,14 +857,14 @@ class PostsController < ActionController::Base private def default_action - render :inline => "#{action_name} called" + render inline: "#{action_name} called" end end class ControllerWithSymbolAsFilter < PostsController - around_action :raise_before, :only => :raises_before - around_action :raise_after, :only => :raises_after - around_action :without_exception, :only => :no_raise + around_action :raise_before, only: :raises_before + around_action :raise_after, only: :raises_after + around_action :without_exception, only: :no_raise private def raise_before @@ -898,7 +896,7 @@ class ControllerWithFilterClass < PostsController end end - around_action YieldingFilter, :only => :raises_after + around_action YieldingFilter, only: :raises_after end class ControllerWithFilterInstance < PostsController @@ -909,11 +907,11 @@ class ControllerWithFilterInstance < PostsController end end - around_action YieldingFilter.new, :only => :raises_after + around_action YieldingFilter.new, only: :raises_after end class ControllerWithProcFilter < PostsController - around_action(:only => :no_raise) do |c,b| + around_action(only: :no_raise) do |c,b| c.instance_variable_set(:"@before", true) b.call c.instance_variable_set(:"@after", true) @@ -921,7 +919,7 @@ class ControllerWithProcFilter < PostsController end class ControllerWithNestedFilters < ControllerWithSymbolAsFilter - around_action :raise_before, :raise_after, :without_exception, :only => :raises_both + around_action :raise_before, :raise_after, :without_exception, only: :raises_both end class ControllerWithAllTypesOfFilters < PostsController @@ -931,26 +929,26 @@ class ControllerWithAllTypesOfFilters < PostsController around_action :around_again private - def before - @ran_filter ||= [] - @ran_filter << 'before' - end + def before + @ran_filter ||= [] + @ran_filter << "before" + end - def around - @ran_filter << 'around (before yield)' - yield - @ran_filter << 'around (after yield)' - end + def around + @ran_filter << "around (before yield)" + yield + @ran_filter << "around (after yield)" + end - def after - @ran_filter << 'after' - end + def after + @ran_filter << "after" + end - def around_again - @ran_filter << 'around_again (before yield)' - yield - @ran_filter << 'around_again (after yield)' - end + def around_again + @ran_filter << "around_again (before yield)" + yield + @ran_filter << "around_again (after yield)" + end end class ControllerWithTwoLessFilters < ControllerWithAllTypesOfFilters @@ -958,46 +956,39 @@ class ControllerWithTwoLessFilters < ControllerWithAllTypesOfFilters skip_after_action :after end -class SkipFilterUsingSkipActionCallback < ControllerWithAllTypesOfFilters - ActiveSupport::Deprecation.silence do - skip_action_callback :around_again - skip_action_callback :after - end -end - class YieldingAroundFiltersTest < ActionController::TestCase include PostsController::AroundExceptions def test_base controller = PostsController - assert_nothing_raised { test_process(controller,'no_raise') } - assert_nothing_raised { test_process(controller,'raises_before') } - assert_nothing_raised { test_process(controller,'raises_after') } - assert_nothing_raised { test_process(controller,'no_action') } + assert_nothing_raised { test_process(controller,"no_raise") } + assert_nothing_raised { test_process(controller,"raises_before") } + assert_nothing_raised { test_process(controller,"raises_after") } + assert_nothing_raised { test_process(controller,"no_action") } end def test_with_symbol controller = ControllerWithSymbolAsFilter - assert_nothing_raised { test_process(controller,'no_raise') } - assert_raise(Before) { test_process(controller,'raises_before') } - assert_raise(After) { test_process(controller,'raises_after') } - assert_nothing_raised { test_process(controller,'no_raise') } + assert_nothing_raised { test_process(controller,"no_raise") } + assert_raise(Before) { test_process(controller,"raises_before") } + assert_raise(After) { test_process(controller,"raises_after") } + assert_nothing_raised { test_process(controller,"no_raise") } end def test_with_class controller = ControllerWithFilterClass - assert_nothing_raised { test_process(controller,'no_raise') } - assert_raise(After) { test_process(controller,'raises_after') } + assert_nothing_raised { test_process(controller,"no_raise") } + assert_raise(After) { test_process(controller,"raises_after") } end def test_with_instance controller = ControllerWithFilterInstance - assert_nothing_raised { test_process(controller,'no_raise') } - assert_raise(After) { test_process(controller,'raises_after') } + assert_nothing_raised { test_process(controller,"no_raise") } + assert_raise(After) { test_process(controller,"raises_after") } end def test_with_proc - test_process(ControllerWithProcFilter,'no_raise') + test_process(ControllerWithProcFilter,"no_raise") assert @controller.instance_variable_get(:@before) assert @controller.instance_variable_get(:@after) end @@ -1006,70 +997,49 @@ class YieldingAroundFiltersTest < ActionController::TestCase controller = ControllerWithNestedFilters assert_nothing_raised do begin - test_process(controller,'raises_both') + test_process(controller,"raises_both") rescue Before, After end end assert_raise Before do begin - test_process(controller,'raises_both') + test_process(controller,"raises_both") rescue After end end end def test_action_order_with_all_action_types - test_process(ControllerWithAllTypesOfFilters,'no_raise') - assert_equal 'before around (before yield) around_again (before yield) around_again (after yield) after around (after yield)', @controller.instance_variable_get(:@ran_filter).join(' ') + test_process(ControllerWithAllTypesOfFilters,"no_raise") + assert_equal "before around (before yield) around_again (before yield) around_again (after yield) after around (after yield)", @controller.instance_variable_get(:@ran_filter).join(" ") end def test_action_order_with_skip_action_method - test_process(ControllerWithTwoLessFilters,'no_raise') - assert_equal 'before around (before yield) around (after yield)', @controller.instance_variable_get(:@ran_filter).join(' ') + test_process(ControllerWithTwoLessFilters,"no_raise") + assert_equal "before around (before yield) around (after yield)", @controller.instance_variable_get(:@ran_filter).join(" ") end def test_first_action_in_multiple_before_action_chain_halts controller = ::FilterTest::TestMultipleFiltersController.new - response = test_process(controller, 'fail_1') - assert_equal '', response.body + response = test_process(controller, "fail_1") + assert_equal "", response.body assert_equal 1, controller.instance_variable_get(:@try) end def test_second_action_in_multiple_before_action_chain_halts controller = ::FilterTest::TestMultipleFiltersController.new - response = test_process(controller, 'fail_2') - assert_equal '', response.body + response = test_process(controller, "fail_2") + assert_equal "", response.body assert_equal 2, controller.instance_variable_get(:@try) end def test_last_action_in_multiple_before_action_chain_halts controller = ::FilterTest::TestMultipleFiltersController.new - response = test_process(controller, 'fail_3') - assert_equal '', response.body + response = test_process(controller, "fail_3") + assert_equal "", response.body assert_equal 3, controller.instance_variable_get(:@try) end - def test_skipping_with_skip_action_callback - test_process(SkipFilterUsingSkipActionCallback,'no_raise') - assert_equal 'before around (before yield) around (after yield)', @controller.instance_variable_get(:@ran_filter).join(' ') - end - - def test_deprecated_skip_action_callback - assert_deprecated do - Class.new(PostsController) do - skip_action_callback :clean_up - end - end - end - - def test_deprecated_skip_filter - assert_deprecated do - Class.new(PostsController) do - skip_filter :clean_up - end - end - end - protected def test_process(controller, action = "show") @controller = controller.is_a?(Class) ? controller.new : controller diff --git a/actionpack/test/controller/flash_hash_test.rb b/actionpack/test/controller/flash_hash_test.rb index 081288ef21..32f0db71f5 100644 --- a/actionpack/test/controller/flash_hash_test.rb +++ b/actionpack/test/controller/flash_hash_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module ActionDispatch class FlashHashTest < ActiveSupport::TestCase @@ -7,62 +7,62 @@ module ActionDispatch end def test_set_get - @hash[:foo] = 'zomg' - assert_equal 'zomg', @hash[:foo] + @hash[:foo] = "zomg" + assert_equal "zomg", @hash[:foo] end def test_keys assert_equal [], @hash.keys - @hash['foo'] = 'zomg' - assert_equal ['foo'], @hash.keys + @hash["foo"] = "zomg" + assert_equal ["foo"], @hash.keys - @hash['bar'] = 'zomg' - assert_equal ['foo', 'bar'].sort, @hash.keys.sort + @hash["bar"] = "zomg" + assert_equal ["foo", "bar"].sort, @hash.keys.sort end def test_update - @hash['foo'] = 'bar' - @hash.update('foo' => 'baz', 'hello' => 'world') + @hash["foo"] = "bar" + @hash.update("foo" => "baz", "hello" => "world") - assert_equal 'baz', @hash['foo'] - assert_equal 'world', @hash['hello'] + assert_equal "baz", @hash["foo"] + assert_equal "world", @hash["hello"] end def test_key - @hash['foo'] = 'bar' + @hash["foo"] = "bar" - assert @hash.key?('foo') + assert @hash.key?("foo") assert @hash.key?(:foo) - assert_not @hash.key?('bar') + assert_not @hash.key?("bar") assert_not @hash.key?(:bar) end def test_delete - @hash['foo'] = 'bar' - @hash.delete 'foo' + @hash["foo"] = "bar" + @hash.delete "foo" - assert !@hash.key?('foo') - assert_nil @hash['foo'] + assert !@hash.key?("foo") + assert_nil @hash["foo"] end def test_to_hash - @hash['foo'] = 'bar' - assert_equal({'foo' => 'bar'}, @hash.to_hash) + @hash["foo"] = "bar" + assert_equal({ "foo" => "bar" }, @hash.to_hash) - @hash.to_hash['zomg'] = 'aaron' - assert !@hash.key?('zomg') - assert_equal({'foo' => 'bar'}, @hash.to_hash) + @hash.to_hash["zomg"] = "aaron" + assert !@hash.key?("zomg") + assert_equal({ "foo" => "bar" }, @hash.to_hash) end def test_to_session_value - @hash['foo'] = 'bar' - assert_equal({'flashes' => {'foo' => 'bar'}}, @hash.to_session_value) + @hash["foo"] = "bar" + assert_equal({ "discard" => [], "flashes" => { "foo" => "bar" } }, @hash.to_session_value) - @hash.now['qux'] = 1 - assert_equal({'flashes' => {'foo' => 'bar'}}, @hash.to_session_value) + @hash.now["qux"] = 1 + assert_equal({ "flashes" => { "foo" => "bar" }, "discard" => [] }, @hash.to_session_value) - @hash.discard('foo') + @hash.discard("foo") assert_equal(nil, @hash.to_session_value) @hash.sweep @@ -71,19 +71,19 @@ module ActionDispatch def test_from_session_value # {"session_id"=>"f8e1b8152ba7609c28bbb17ec9263ba7", "flash"=>#<ActionDispatch::Flash::FlashHash:0x00000000000000 @used=#<Set: {"farewell"}>, @closed=false, @flashes={"greeting"=>"Hello", "farewell"=>"Goodbye"}, @now=nil>} - rails_3_2_cookie = 'BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJWY4ZTFiODE1MmJhNzYwOWMyOGJiYjE3ZWM5MjYzYmE3BjsAVEkiCmZsYXNoBjsARm86JUFjdGlvbkRpc3BhdGNoOjpGbGFzaDo6Rmxhc2hIYXNoCToKQHVzZWRvOghTZXQGOgpAaGFzaHsGSSINZmFyZXdlbGwGOwBUVDoMQGNsb3NlZEY6DUBmbGFzaGVzewdJIg1ncmVldGluZwY7AFRJIgpIZWxsbwY7AFRJIg1mYXJld2VsbAY7AFRJIgxHb29kYnllBjsAVDoJQG5vdzA=' + rails_3_2_cookie = "BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJWY4ZTFiODE1MmJhNzYwOWMyOGJiYjE3ZWM5MjYzYmE3BjsAVEkiCmZsYXNoBjsARm86JUFjdGlvbkRpc3BhdGNoOjpGbGFzaDo6Rmxhc2hIYXNoCToKQHVzZWRvOghTZXQGOgpAaGFzaHsGSSINZmFyZXdlbGwGOwBUVDoMQGNsb3NlZEY6DUBmbGFzaGVzewdJIg1ncmVldGluZwY7AFRJIgpIZWxsbwY7AFRJIg1mYXJld2VsbAY7AFRJIgxHb29kYnllBjsAVDoJQG5vdzA=" session = Marshal.load(Base64.decode64(rails_3_2_cookie)) - hash = Flash::FlashHash.from_session_value(session['flash']) - assert_equal({'greeting' => 'Hello'}, hash.to_hash) + hash = Flash::FlashHash.from_session_value(session["flash"]) + assert_equal({ "greeting" => "Hello" }, hash.to_hash) assert_equal(nil, hash.to_session_value) end def test_from_session_value_on_json_serializer decrypted_data = "{ \"session_id\":\"d98bdf6d129618fc2548c354c161cfb5\", \"flash\":{\"discard\":[\"farewell\"], \"flashes\":{\"greeting\":\"Hello\",\"farewell\":\"Goodbye\"}} }" session = ActionDispatch::Cookies::JsonSerializer.load(decrypted_data) - hash = Flash::FlashHash.from_session_value(session['flash']) + hash = Flash::FlashHash.from_session_value(session["flash"]) - assert_equal({'greeting' => 'Hello'}, hash.to_hash) + assert_equal({ "greeting" => "Hello" }, hash.to_hash) assert_equal(nil, hash.to_session_value) assert_equal "Hello", hash[:greeting] assert_equal "Hello", hash["greeting"] @@ -91,15 +91,15 @@ module ActionDispatch def test_empty? assert @hash.empty? - @hash['zomg'] = 'bears' + @hash["zomg"] = "bears" assert !@hash.empty? @hash.clear assert @hash.empty? end def test_each - @hash['hello'] = 'world' - @hash['foo'] = 'bar' + @hash["hello"] = "world" + @hash["foo"] = "bar" things = [] @hash.each do |k,v| @@ -110,13 +110,13 @@ module ActionDispatch end def test_replace - @hash['hello'] = 'world' - @hash.replace('omg' => 'aaron') - assert_equal({'omg' => 'aaron'}, @hash.to_hash) + @hash["hello"] = "world" + @hash.replace("omg" => "aaron") + assert_equal({ "omg" => "aaron" }, @hash.to_hash) end def test_discard_no_args - @hash['hello'] = 'world' + @hash["hello"] = "world" @hash.discard @hash.sweep @@ -124,49 +124,49 @@ module ActionDispatch end def test_discard_one_arg - @hash['hello'] = 'world' - @hash['omg'] = 'world' - @hash.discard 'hello' + @hash["hello"] = "world" + @hash["omg"] = "world" + @hash.discard "hello" @hash.sweep - assert_equal({'omg' => 'world'}, @hash.to_hash) + assert_equal({ "omg" => "world" }, @hash.to_hash) end def test_keep_sweep - @hash['hello'] = 'world' + @hash["hello"] = "world" @hash.sweep - assert_equal({'hello' => 'world'}, @hash.to_hash) + assert_equal({ "hello" => "world" }, @hash.to_hash) end def test_update_sweep - @hash['hello'] = 'world' - @hash.update({'hi' => 'mom'}) + @hash["hello"] = "world" + @hash.update("hi" => "mom") @hash.sweep - assert_equal({'hello' => 'world', 'hi' => 'mom'}, @hash.to_hash) + assert_equal({ "hello" => "world", "hi" => "mom" }, @hash.to_hash) end def test_update_delete_sweep - @hash['hello'] = 'world' - @hash.delete 'hello' - @hash.update({'hello' => 'mom'}) + @hash["hello"] = "world" + @hash.delete "hello" + @hash.update("hello" => "mom") @hash.sweep - assert_equal({'hello' => 'mom'}, @hash.to_hash) + assert_equal({ "hello" => "mom" }, @hash.to_hash) end def test_delete_sweep - @hash['hello'] = 'world' - @hash['hi'] = 'mom' - @hash.delete 'hi' + @hash["hello"] = "world" + @hash["hi"] = "mom" + @hash.delete "hi" @hash.sweep - assert_equal({'hello' => 'world'}, @hash.to_hash) + assert_equal({ "hello" => "world" }, @hash.to_hash) end def test_clear_sweep - @hash['hello'] = 'world' + @hash["hello"] = "world" @hash.clear @hash.sweep @@ -174,38 +174,38 @@ module ActionDispatch end def test_replace_sweep - @hash['hello'] = 'world' - @hash.replace({'hi' => 'mom'}) + @hash["hello"] = "world" + @hash.replace("hi" => "mom") @hash.sweep - assert_equal({'hi' => 'mom'}, @hash.to_hash) + assert_equal({ "hi" => "mom" }, @hash.to_hash) end def test_discard_then_add - @hash['hello'] = 'world' - @hash['omg'] = 'world' - @hash.discard 'hello' - @hash['hello'] = 'world' + @hash["hello"] = "world" + @hash["omg"] = "world" + @hash.discard "hello" + @hash["hello"] = "world" @hash.sweep - assert_equal({'omg' => 'world', 'hello' => 'world'}, @hash.to_hash) + assert_equal({ "omg" => "world", "hello" => "world" }, @hash.to_hash) end def test_keep_all_sweep - @hash['hello'] = 'world' - @hash['omg'] = 'world' - @hash.discard 'hello' + @hash["hello"] = "world" + @hash["omg"] = "world" + @hash.discard "hello" @hash.keep @hash.sweep - assert_equal({'omg' => 'world', 'hello' => 'world'}, @hash.to_hash) + assert_equal({ "omg" => "world", "hello" => "world" }, @hash.to_hash) end def test_double_sweep - @hash['hello'] = 'world' + @hash["hello"] = "world" @hash.sweep - assert_equal({'hello' => 'world'}, @hash.to_hash) + assert_equal({ "hello" => "world" }, @hash.to_hash) @hash.sweep assert_equal({}, @hash.to_hash) diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index b063d769a4..dc641c19ab 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -1,11 +1,11 @@ -require 'abstract_unit' -require 'active_support/key_generator' +require "abstract_unit" +require "active_support/key_generator" class FlashTest < ActionController::TestCase class TestController < ActionController::Base def set_flash flash["that"] = "hello" - render :inline => "hello" + render inline: "hello" end def set_flash_now @@ -14,32 +14,32 @@ class FlashTest < ActionController::TestCase flash.now["foo"] ||= "err" @flashy = flash.now["that"] @flash_copy = {}.update flash - render :inline => "hello" + render inline: "hello" end def attempt_to_use_flash_now @flash_copy = {}.update flash @flashy = flash["that"] - render :inline => "hello" + render inline: "hello" end def use_flash @flash_copy = {}.update flash @flashy = flash["that"] - render :inline => "hello" + render inline: "hello" end def use_flash_and_keep_it @flash_copy = {}.update flash @flashy = flash["that"] flash.keep - render :inline => "hello" + render inline: "hello" end def use_flash_and_update_it flash.update("this" => "hello again") @flash_copy = {}.update flash - render :inline => "hello" + render inline: "hello" end def use_flash_after_reset_session @@ -49,11 +49,11 @@ class FlashTest < ActionController::TestCase @flashy_that_reset = flash["that"] flash["this"] = "good-bye" @flashy_this = flash["this"] - render :inline => "hello" + render inline: "hello" end # methods for test_sweep_after_halted_action_chain - before_action :halt_and_redir, only: 'filter_halting_action' + before_action :halt_and_redir, only: "filter_halting_action" def std_action @flash_copy = {}.update(flash) @@ -66,34 +66,34 @@ class FlashTest < ActionController::TestCase def halt_and_redir flash["foo"] = "bar" - redirect_to :action => "std_action" + redirect_to action: "std_action" @flash_copy = {}.update(flash) end def redirect_with_alert - redirect_to '/nowhere', :alert => "Beware the nowheres!" + redirect_to "/nowhere", alert: "Beware the nowheres!" end def redirect_with_notice - redirect_to '/somewhere', :notice => "Good luck in the somewheres!" + redirect_to "/somewhere", notice: "Good luck in the somewheres!" end def render_with_flash_now_alert flash.now.alert = "Beware the nowheres now!" - render :inline => "hello" + render inline: "hello" end def render_with_flash_now_notice flash.now.notice = "Good luck in the somewheres now!" - render :inline => "hello" + render inline: "hello" end def redirect_with_other_flashes - redirect_to '/wonderland', :flash => { :joyride => "Horses!" } + redirect_to "/wonderland", flash: { joyride: "Horses!" } end def redirect_with_foo_flash - redirect_to "/wonderland", :foo => 'for great justice' + redirect_to "/wonderland", foo: "for great justice" end end @@ -172,17 +172,17 @@ class FlashTest < ActionController::TestCase def test_keep_and_discard_return_values flash = ActionDispatch::Flash::FlashHash.new - flash.update(:foo => :foo_indeed, :bar => :bar_indeed) + flash.update(foo: :foo_indeed, bar: :bar_indeed) assert_equal(:foo_indeed, flash.discard(:foo)) # valid key passed assert_nil flash.discard(:unknown) # non existent key passed - assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.discard().to_hash) # nothing passed - assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.discard(nil).to_hash) # nothing passed + assert_equal({ "foo" => :foo_indeed, "bar" => :bar_indeed }, flash.discard().to_hash) # nothing passed + assert_equal({ "foo" => :foo_indeed, "bar" => :bar_indeed }, flash.discard(nil).to_hash) # nothing passed assert_equal(:foo_indeed, flash.keep(:foo)) # valid key passed assert_nil flash.keep(:unknown) # non existent key passed - assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.keep().to_hash) # nothing passed - assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.keep(nil).to_hash) # nothing passed + assert_equal({ "foo" => :foo_indeed, "bar" => :bar_indeed }, flash.keep().to_hash) # nothing passed + assert_equal({ "foo" => :foo_indeed, "bar" => :bar_indeed }, flash.keep(nil).to_hash) # nothing passed end def test_redirect_to_with_alert @@ -227,7 +227,7 @@ class FlashTest < ActionController::TestCase add_flash_types :foo end subclass_controller_with_no_flash_type = Class.new(test_controller_with_flash_type_foo) - assert subclass_controller_with_no_flash_type._flash_types.include?(:foo) + assert_includes subclass_controller_with_no_flash_type._flash_types, :foo end def test_does_not_add_flash_type_to_parent_class @@ -239,8 +239,8 @@ class FlashTest < ActionController::TestCase end class FlashIntegrationTest < ActionDispatch::IntegrationTest - SessionKey = '_myapp_session' - Generator = ActiveSupport::LegacyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33') + SessionKey = "_myapp_session" + Generator = ActiveSupport::LegacyKeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33") class TestController < ActionController::Base add_flash_types :bar @@ -256,22 +256,29 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest end def use_flash - render :inline => "flash: #{flash["that"]}" + render inline: "flash: #{flash["that"]}" end def set_bar flash[:bar] = "for great justice" head :ok end + + def set_flash_optionally + flash.now.notice = params[:flash] + if stale? etag: "abe" + render inline: "maybe flash" + end + end end def test_flash with_test_route_set do - get '/set_flash' + get "/set_flash" assert_response :success assert_equal "hello", @request.flash["that"] - get '/use_flash' + get "/use_flash" assert_response :success assert_equal "flash: hello", @response.body end @@ -279,7 +286,7 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest def test_just_using_flash_does_not_stream_a_cookie_back with_test_route_set do - get '/use_flash' + get "/use_flash" assert_response :success assert_nil @response.headers["Set-Cookie"] assert_equal "flash: ", @response.body @@ -288,25 +295,47 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest def test_setting_flash_does_not_raise_in_following_requests with_test_route_set do - env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new } - get '/set_flash', env: env - get '/set_flash', env: env + env = { "action_dispatch.request.flash_hash" => ActionDispatch::Flash::FlashHash.new } + get "/set_flash", env: env + get "/set_flash", env: env end end def test_setting_flash_now_does_not_raise_in_following_requests with_test_route_set do - env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new } - get '/set_flash_now', env: env - get '/set_flash_now', env: env + env = { "action_dispatch.request.flash_hash" => ActionDispatch::Flash::FlashHash.new } + get "/set_flash_now", env: env + get "/set_flash_now", env: env end end def test_added_flash_types_method with_test_route_set do - get '/set_bar' + get "/set_bar" assert_response :success - assert_equal 'for great justice', @controller.bar + assert_equal "for great justice", @controller.bar + end + end + + def test_flash_factored_into_etag + with_test_route_set do + get "/set_flash_optionally" + no_flash_etag = response.etag + + get "/set_flash_optionally", params: { flash: "hello!" } + hello_flash_etag = response.etag + + assert_not_equal no_flash_etag, hello_flash_etag + + get "/set_flash_optionally", params: { flash: "hello!" } + another_hello_flash_etag = response.etag + + assert_equal another_hello_flash_etag, hello_flash_etag + + get "/set_flash_optionally", params: { flash: "goodbye!" } + goodbye_flash_etag = response.etag + + assert_not_equal another_hello_flash_etag, goodbye_flash_etag end end @@ -323,11 +352,13 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest def with_test_route_set with_routing do |set| set.draw do - get ':action', :to => FlashIntegrationTest::TestController + ActiveSupport::Deprecation.silence do + get ":action", to: FlashIntegrationTest::TestController + end end @app = self.class.build_app(set) do |middleware| - middleware.use ActionDispatch::Session::CookieStore, :key => SessionKey + middleware.use ActionDispatch::Session::CookieStore, key: SessionKey middleware.use ActionDispatch::Flash middleware.delete ActionDispatch::ShowExceptions end diff --git a/actionpack/test/controller/force_ssl_test.rb b/actionpack/test/controller/force_ssl_test.rb index 22f1cc7c22..2b3859aa57 100644 --- a/actionpack/test/controller/force_ssl_test.rb +++ b/actionpack/test/controller/force_ssl_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class ForceSSLController < ActionController::Base def banana @@ -15,15 +15,15 @@ class ForceSSLControllerLevel < ForceSSLController end class ForceSSLCustomOptions < ForceSSLController - force_ssl :host => "secure.example.com", :only => :redirect_host - force_ssl :port => 8443, :only => :redirect_port - force_ssl :subdomain => 'secure', :only => :redirect_subdomain - force_ssl :domain => 'secure.com', :only => :redirect_domain - force_ssl :path => '/foo', :only => :redirect_path - force_ssl :status => :found, :only => :redirect_status - force_ssl :flash => { :message => 'Foo, Bar!' }, :only => :redirect_flash - force_ssl :alert => 'Foo, Bar!', :only => :redirect_alert - force_ssl :notice => 'Foo, Bar!', :only => :redirect_notice + force_ssl host: "secure.example.com", only: :redirect_host + force_ssl port: 8443, only: :redirect_port + force_ssl subdomain: "secure", only: :redirect_subdomain + force_ssl domain: "secure.com", only: :redirect_domain + force_ssl path: "/foo", only: :redirect_path + force_ssl status: :found, only: :redirect_status + force_ssl flash: { message: "Foo, Bar!" }, only: :redirect_flash + force_ssl alert: "Foo, Bar!", only: :redirect_alert + force_ssl notice: "Foo, Bar!", only: :redirect_notice def force_ssl_action render plain: action_name @@ -53,42 +53,42 @@ class ForceSSLCustomOptions < ForceSSLController end class ForceSSLOnlyAction < ForceSSLController - force_ssl :only => :cheeseburger + force_ssl only: :cheeseburger end class ForceSSLExceptAction < ForceSSLController - force_ssl :except => :banana + force_ssl except: :banana end class ForceSSLIfCondition < ForceSSLController - force_ssl :if => :use_force_ssl? + force_ssl if: :use_force_ssl? def use_force_ssl? - action_name == 'cheeseburger' + action_name == "cheeseburger" end end class ForceSSLFlash < ForceSSLController - force_ssl :except => [:banana, :set_flash, :use_flash] + force_ssl except: [:banana, :set_flash, :use_flash] def set_flash flash["that"] = "hello" - redirect_to '/force_ssl_flash/cheeseburger' + redirect_to "/force_ssl_flash/cheeseburger" end def use_flash @flash_copy = {}.update flash @flashy = flash["that"] - render :inline => "hello" + render inline: "hello" end end class RedirectToSSL < ForceSSLController def banana - force_ssl_redirect || render(plain: 'monkey') + force_ssl_redirect || render(plain: "monkey") end def cheeseburger - force_ssl_redirect('secure.cheeseburger.host') || render(plain: 'ihaz') + force_ssl_redirect("secure.cheeseburger.host") || render(plain: "ihaz") end end @@ -114,7 +114,7 @@ end class ForceSSLCustomOptionsTest < ActionController::TestCase def setup - @request.env['HTTP_HOST'] = 'www.example.com:80' + @request.env["HTTP_HOST"] = "www.example.com:80" end def test_redirect_to_custom_host @@ -229,15 +229,13 @@ class ForceSSLFlashTest < ActionController::TestCase assert_response 302 assert_equal "http://test.host/force_ssl_flash/cheeseburger", redirect_to_url - # FIXME: AC::TestCase#build_request_uri doesn't build a new uri if PATH_INFO exists - @request.env.delete('PATH_INFO') + @request.env.delete("PATH_INFO") get :cheeseburger assert_response 301 assert_equal "https://test.host/force_ssl_flash/cheeseburger", redirect_to_url - # FIXME: AC::TestCase#build_request_uri doesn't build a new uri if PATH_INFO exists - @request.env.delete('PATH_INFO') + @request.env.delete("PATH_INFO") get :use_flash assert_equal "hello", @controller.instance_variable_get("@flash_copy")["that"] @@ -251,15 +249,15 @@ class ForceSSLDuplicateRoutesTest < ActionController::TestCase def test_force_ssl_redirects_to_same_path with_routing do |set| set.draw do - get '/foo', :to => 'force_ssl_controller_level#banana' - get '/bar', :to => 'force_ssl_controller_level#banana' + get "/foo", to: "force_ssl_controller_level#banana" + get "/bar", to: "force_ssl_controller_level#banana" end - @request.env['PATH_INFO'] = '/bar' + @request.env["PATH_INFO"] = "/bar" get :banana assert_response 301 - assert_equal 'https://test.host/bar', redirect_to_url + assert_equal "https://test.host/bar", redirect_to_url end end end @@ -270,12 +268,12 @@ class ForceSSLFormatTest < ActionController::TestCase def test_force_ssl_redirects_to_same_format with_routing do |set| set.draw do - get '/foo', :to => 'force_ssl_controller_level#banana' + get "/foo", to: "force_ssl_controller_level#banana" end get :banana, format: :json assert_response 301 - assert_equal 'https://test.host/foo.json', redirect_to_url + assert_equal "https://test.host/foo.json", redirect_to_url end end end @@ -286,18 +284,18 @@ class ForceSSLOptionalSegmentsTest < ActionController::TestCase def test_force_ssl_redirects_to_same_format with_routing do |set| set.draw do - scope '(:locale)' do - defaults :locale => 'en' do - get '/foo', :to => 'force_ssl_controller_level#banana' + scope "(:locale)" do + defaults locale: "en" do + get "/foo", to: "force_ssl_controller_level#banana" end end end - @request.env['PATH_INFO'] = '/en/foo' - get :banana, params: { locale: 'en' } - assert_equal 'en', @controller.params[:locale] + @request.env["PATH_INFO"] = "/en/foo" + get :banana, params: { locale: "en" } + assert_equal "en", @controller.params[:locale] assert_response 301 - assert_equal 'https://test.host/en/foo', redirect_to_url + assert_equal "https://test.host/en/foo", redirect_to_url end end end @@ -316,9 +314,18 @@ class RedirectToSSLTest < ActionController::TestCase end def test_cheeseburgers_does_not_redirect_if_already_https - request.env['HTTPS'] = 'on' + request.env["HTTPS"] = "on" + get :cheeseburger + assert_response 200 + assert_equal "ihaz", response.body + end +end + +class ForceSSLControllerLevelTest < ActionController::TestCase + def test_no_redirect_websocket_ssl_request + request.env["rack.url_scheme"] = "wss" + request.env["Upgrade"] = "websocket" get :cheeseburger assert_response 200 - assert_equal 'ihaz', response.body end end diff --git a/actionpack/test/controller/form_builder_test.rb b/actionpack/test/controller/form_builder_test.rb index 99eeaf9ab6..5a3dc2ee03 100644 --- a/actionpack/test/controller/form_builder_test.rb +++ b/actionpack/test/controller/form_builder_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class FormBuilderController < ActionController::Base class SpecializedFormBuilder < ActionView::Helpers::FormBuilder ; end diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb index feb882a2b3..4c6a772062 100644 --- a/actionpack/test/controller/helper_test.rb +++ b/actionpack/test/controller/helper_test.rb @@ -1,17 +1,17 @@ -require 'abstract_unit' +require "abstract_unit" -ActionController::Base.helpers_path = File.expand_path('../../fixtures/helpers', __FILE__) +ActionController::Base.helpers_path = File.expand_path("../../fixtures/helpers", __FILE__) module Fun class GamesController < ActionController::Base def render_hello_world - render :inline => "hello: <%= stratego %>" + render inline: "hello: <%= stratego %>" end end class PdfController < ActionController::Base def test - render :inline => "test: <%= foobar %>" + render inline: "test: <%= foobar %>" end end end @@ -35,11 +35,11 @@ class JustMeController < ActionController::Base clear_helpers def flash - render :inline => "<h1><%= notice %></h1>" + render inline: "<h1><%= notice %></h1>" end def lib - render :inline => '<%= useful_function %>' + render inline: "<%= useful_function %>" end end @@ -48,7 +48,7 @@ end class HelpersPathsController < ActionController::Base paths = ["helpers2_pack", "helpers1_pack"].map do |path| - File.join(File.expand_path('../../fixtures', __FILE__), path) + File.join(File.expand_path("../../fixtures", __FILE__), path) end $:.unshift(*paths) @@ -56,12 +56,12 @@ class HelpersPathsController < ActionController::Base helper :all def index - render :inline => "<%= conflicting_helper %>" + render inline: "<%= conflicting_helper %>" end end class HelpersTypoController < ActionController::Base - path = File.expand_path('../../fixtures/helpers_typo', __FILE__) + path = File.expand_path("../../fixtures/helpers_typo", __FILE__) $:.unshift(path) self.helpers_path = path end @@ -89,7 +89,7 @@ class HelpersTypoControllerTest < ActiveSupport::TestCase end def test_helper_typo_error_message - e = assert_raise(NameError) { HelpersTypoController.helper 'admin/users' } + e = assert_raise(NameError) { HelpersTypoController.helper "admin/users" } assert_equal "Couldn't find Admin::UsersHelper, expected it to be defined in helpers/admin/users_helper.rb", e.message end @@ -106,7 +106,7 @@ class HelperTest < ActiveSupport::TestCase def setup # Increment symbol counter. - @symbol = (@@counter ||= 'A0').succ!.dup + @symbol = (@@counter ||= "A0").succ!.dup # Generate new controller class. controller_class_name = "Helper#{@symbol}Controller" @@ -125,13 +125,13 @@ class HelperTest < ActiveSupport::TestCase def test_helper_method assert_nothing_raised { @controller_class.helper_method :delegate_method } - assert master_helper_methods.include?(:delegate_method) + assert_includes master_helper_methods, :delegate_method end def test_helper_attr assert_nothing_raised { @controller_class.helper_attr :delegate_attr } - assert master_helper_methods.include?(:delegate_attr) - assert master_helper_methods.include?(:delegate_attr=) + assert_includes master_helper_methods, :delegate_attr + assert_includes master_helper_methods, :delegate_attr= end def call_controller(klass, action) @@ -139,7 +139,7 @@ class HelperTest < ActiveSupport::TestCase end def test_helper_for_nested_controller - assert_equal 'hello: Iz guuut!', + assert_equal "hello: Iz guuut!", call_controller(Fun::GamesController, "render_hello_world").last.body end @@ -168,49 +168,65 @@ class HelperTest < ActiveSupport::TestCase methods = AllHelpersController._helpers.instance_methods # abc_helper.rb - assert methods.include?(:bare_a) + assert_includes methods, :bare_a # fun/games_helper.rb - assert methods.include?(:stratego) + assert_includes methods, :stratego # fun/pdf_helper.rb - assert methods.include?(:foobar) + assert_includes methods, :foobar end def test_all_helpers_with_alternate_helper_dir - @controller_class.helpers_path = File.expand_path('../../fixtures/alternate_helpers', __FILE__) + @controller_class.helpers_path = File.expand_path("../../fixtures/alternate_helpers", __FILE__) # Reload helpers @controller_class._helpers = Module.new @controller_class.helper :all # helpers/abc_helper.rb should not be included - assert !master_helper_methods.include?(:bare_a) + assert_not_includes master_helper_methods, :bare_a # alternate_helpers/foo_helper.rb - assert master_helper_methods.include?(:baz) + assert_includes master_helper_methods, :baz end def test_helper_proxy methods = AllHelpersController.helpers.methods # Action View - assert methods.include?(:pluralize) + assert_includes methods, :pluralize # abc_helper.rb - assert methods.include?(:bare_a) + assert_includes methods, :bare_a # fun/games_helper.rb - assert methods.include?(:stratego) + assert_includes methods, :stratego # fun/pdf_helper.rb - assert methods.include?(:foobar) + assert_includes methods, :foobar + end + + def test_helper_proxy_in_instance + methods = AllHelpersController.new.helpers.methods + + # Action View + assert_includes methods, :pluralize + + # abc_helper.rb + assert_includes methods, :bare_a + + # fun/games_helper.rb + assert_includes methods, :stratego + + # fun/pdf_helper.rb + assert_includes methods, :foobar end def test_helper_proxy_config - AllHelpersController.config.my_var = 'smth' + AllHelpersController.config.my_var = "smth" - assert_equal 'smth', AllHelpersController.helpers.config.my_var + assert_equal "smth", AllHelpersController.helpers.config.my_var end private @@ -227,31 +243,30 @@ class HelperTest < ActiveSupport::TestCase end def test_helper=(helper_module) - silence_warnings { self.class.const_set('TestHelper', helper_module) } + silence_warnings { self.class.const_set("TestHelper", helper_module) } end end - class IsolatedHelpersTest < ActionController::TestCase class A < ActionController::Base def index - render :inline => '<%= shout %>' + render inline: "<%= shout %>" end end class B < A - helper { def shout; 'B' end } + helper { def shout; "B" end } def index - render :inline => '<%= shout %>' + render inline: "<%= shout %>" end end class C < A - helper { def shout; 'C' end } + helper { def shout; "C" end } def index - render :inline => '<%= shout %>' + render inline: "<%= shout %>" end end @@ -261,7 +276,7 @@ class IsolatedHelpersTest < ActionController::TestCase def setup super - @request.action = 'index' + @request.action = "index" end def test_helper_in_a @@ -269,10 +284,10 @@ class IsolatedHelpersTest < ActionController::TestCase end def test_helper_in_b - assert_equal 'B', call_controller(B, "index").last.body + assert_equal "B", call_controller(B, "index").last.body end def test_helper_in_c - assert_equal 'C', call_controller(C, "index").last.body + assert_equal "C", call_controller(C, "index").last.body end end diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb index adcf259317..d9ae787689 100644 --- a/actionpack/test/controller/http_basic_authentication_test.rb +++ b/actionpack/test/controller/http_basic_authentication_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class HttpBasicAuthenticationTest < ActionController::TestCase class DummyController < ActionController::Base @@ -7,88 +7,88 @@ class HttpBasicAuthenticationTest < ActionController::TestCase before_action :authenticate_long_credentials, only: :show before_action :auth_with_special_chars, only: :special_creds - http_basic_authenticate_with :name => "David", :password => "Goliath", :only => :search + http_basic_authenticate_with name: "David", password: "Goliath", only: :search def index render plain: "Hello Secret" end def display - render plain: 'Definitely Maybe' if @logged_in + render plain: "Definitely Maybe" if @logged_in end def show - render plain: 'Only for loooooong credentials' + render plain: "Only for loooooong credentials" end def special_creds - render plain: 'Only for special credentials' + render plain: "Only for special credentials" end def search - render plain: 'All inline' + render plain: "All inline" end private - def authenticate - authenticate_or_request_with_http_basic do |username, password| - username == 'lifo' && password == 'world' + def authenticate + authenticate_or_request_with_http_basic do |username, password| + username == "lifo" && password == "world" + end end - end - def authenticate_with_request - if authenticate_with_http_basic { |username, password| username == 'pretty' && password == 'please' } - @logged_in = true - else - request_http_basic_authentication("SuperSecret", "Authentication Failed\n") + def authenticate_with_request + if authenticate_with_http_basic { |username, password| username == "pretty" && password == "please" } + @logged_in = true + else + request_http_basic_authentication("SuperSecret", "Authentication Failed\n") + end end - end - def auth_with_special_chars - authenticate_or_request_with_http_basic do |username, password| - username == 'login!@#$%^&*()_+{}[];"\',./<>?`~ \n\r\t' && password == 'pwd:!@#$%^&*()_+{}[];"\',./<>?`~ \n\r\t' + def auth_with_special_chars + authenticate_or_request_with_http_basic do |username, password| + username == 'login!@#$%^&*()_+{}[];"\',./<>?`~ \n\r\t' && password == 'pwd:!@#$%^&*()_+{}[];"\',./<>?`~ \n\r\t' + end end - end - def authenticate_long_credentials - authenticate_or_request_with_http_basic do |username, password| - username == '1234567890123456789012345678901234567890' && password == '1234567890123456789012345678901234567890' + def authenticate_long_credentials + authenticate_or_request_with_http_basic do |username, password| + username == "1234567890123456789012345678901234567890" && password == "1234567890123456789012345678901234567890" + end end - end end - AUTH_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION'] + AUTH_HEADERS = ["HTTP_AUTHORIZATION", "X-HTTP_AUTHORIZATION", "X_HTTP_AUTHORIZATION", "REDIRECT_X_HTTP_AUTHORIZATION"] tests DummyController AUTH_HEADERS.each do |header| test "successful authentication with #{header.downcase}" do - @request.env[header] = encode_credentials('lifo', 'world') + @request.env[header] = encode_credentials("lifo", "world") get :index assert_response :success - assert_equal 'Hello Secret', @response.body, "Authentication failed for request header #{header}" + assert_equal "Hello Secret", @response.body, "Authentication failed for request header #{header}" end test "successful authentication with #{header.downcase} and long credentials" do - @request.env[header] = encode_credentials('1234567890123456789012345678901234567890', '1234567890123456789012345678901234567890') + @request.env[header] = encode_credentials("1234567890123456789012345678901234567890", "1234567890123456789012345678901234567890") get :show assert_response :success - assert_equal 'Only for loooooong credentials', @response.body, "Authentication failed for request header #{header} and long credentials" + assert_equal "Only for loooooong credentials", @response.body, "Authentication failed for request header #{header} and long credentials" end end AUTH_HEADERS.each do |header| test "unsuccessful authentication with #{header.downcase}" do - @request.env[header] = encode_credentials('h4x0r', 'world') + @request.env[header] = encode_credentials("h4x0r", "world") get :index assert_response :unauthorized assert_equal "HTTP Basic: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header}" end test "unsuccessful authentication with #{header.downcase} and long credentials" do - @request.env[header] = encode_credentials('h4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0r', 'worldworldworldworldworldworldworldworld') + @request.env[header] = encode_credentials("h4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0r", "worldworldworldworldworldworldworldworld") get :show assert_response :unauthorized @@ -104,19 +104,19 @@ class HttpBasicAuthenticationTest < ActionController::TestCase end def test_encode_credentials_has_no_newline - username = 'laskjdfhalksdjfhalkjdsfhalksdjfhklsdjhalksdjfhalksdjfhlakdsjfh' - password = 'kjfhueyt9485osdfasdkljfh4lkjhakldjfhalkdsjf' + username = "laskjdfhalksdjfhalkjdsfhalksdjfhklsdjhalksdjfhalksdjfhlakdsjfh" + password = "kjfhueyt9485osdfasdkljfh4lkjhakldjfhalkdsjf" result = ActionController::HttpAuthentication::Basic.encode_credentials( username, password) assert_no_match(/\n/, result) end test "successful authentication with uppercase authorization scheme" do - @request.env['HTTP_AUTHORIZATION'] = "BASIC #{::Base64.encode64("lifo:world")}" + @request.env["HTTP_AUTHORIZATION"] = "BASIC #{::Base64.encode64("lifo:world")}" get :index assert_response :success - assert_equal 'Hello Secret', @response.body, 'Authentication failed when authorization scheme BASIC' + assert_equal "Hello Secret", @response.body, "Authentication failed when authorization scheme BASIC" end test "authentication request without credential" do @@ -124,54 +124,54 @@ class HttpBasicAuthenticationTest < ActionController::TestCase assert_response :unauthorized assert_equal "Authentication Failed\n", @response.body - assert_equal 'Basic realm="SuperSecret"', @response.headers['WWW-Authenticate'] + assert_equal 'Basic realm="SuperSecret"', @response.headers["WWW-Authenticate"] end test "authentication request with invalid credential" do - @request.env['HTTP_AUTHORIZATION'] = encode_credentials('pretty', 'foo') + @request.env["HTTP_AUTHORIZATION"] = encode_credentials("pretty", "foo") get :display assert_response :unauthorized assert_equal "Authentication Failed\n", @response.body - assert_equal 'Basic realm="SuperSecret"', @response.headers['WWW-Authenticate'] + assert_equal 'Basic realm="SuperSecret"', @response.headers["WWW-Authenticate"] end test "authentication request with valid credential" do - @request.env['HTTP_AUTHORIZATION'] = encode_credentials('pretty', 'please') + @request.env["HTTP_AUTHORIZATION"] = encode_credentials("pretty", "please") get :display assert_response :success - assert_equal 'Definitely Maybe', @response.body + assert_equal "Definitely Maybe", @response.body end test "authentication request with valid credential special chars" do - @request.env['HTTP_AUTHORIZATION'] = encode_credentials('login!@#$%^&*()_+{}[];"\',./<>?`~ \n\r\t', 'pwd:!@#$%^&*()_+{}[];"\',./<>?`~ \n\r\t') + @request.env["HTTP_AUTHORIZATION"] = encode_credentials('login!@#$%^&*()_+{}[];"\',./<>?`~ \n\r\t', 'pwd:!@#$%^&*()_+{}[];"\',./<>?`~ \n\r\t') get :special_creds assert_response :success - assert_equal 'Only for special credentials', @response.body + assert_equal "Only for special credentials", @response.body end test "authenticate with class method" do - @request.env['HTTP_AUTHORIZATION'] = encode_credentials('David', 'Goliath') + @request.env["HTTP_AUTHORIZATION"] = encode_credentials("David", "Goliath") get :search assert_response :success - @request.env['HTTP_AUTHORIZATION'] = encode_credentials('David', 'WRONG!') + @request.env["HTTP_AUTHORIZATION"] = encode_credentials("David", "WRONG!") get :search assert_response :unauthorized end test "authentication request with wrong scheme" do - header = 'Bearer ' + encode_credentials('David', 'Goliath').split(' ', 2)[1] - @request.env['HTTP_AUTHORIZATION'] = header + header = "Bearer " + encode_credentials("David", "Goliath").split(" ", 2)[1] + @request.env["HTTP_AUTHORIZATION"] = header get :search assert_response :unauthorized end private - def encode_credentials(username, password) - "Basic #{::Base64.encode64("#{username}:#{password}")}" - end + def encode_credentials(username, password) + "Basic #{::Base64.encode64("#{username}:#{password}")}" + end end diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb index f06912bd5a..343b7b643d 100644 --- a/actionpack/test/controller/http_digest_authentication_test.rb +++ b/actionpack/test/controller/http_digest_authentication_test.rb @@ -1,41 +1,41 @@ -require 'abstract_unit' -require 'active_support/key_generator' +require "abstract_unit" +require "active_support/key_generator" class HttpDigestAuthenticationTest < ActionController::TestCase class DummyDigestController < ActionController::Base before_action :authenticate, only: :index before_action :authenticate_with_request, only: :display - USERS = { 'lifo' => 'world', 'pretty' => 'please', - 'dhh' => ::Digest::MD5::hexdigest(["dhh","SuperSecret","secret"].join(":"))} + USERS = { "lifo" => "world", "pretty" => "please", + "dhh" => ::Digest::MD5::hexdigest(["dhh","SuperSecret","secret"].join(":")) } def index render plain: "Hello Secret" end def display - render plain: 'Definitely Maybe' if @logged_in + render plain: "Definitely Maybe" if @logged_in end private - def authenticate - authenticate_or_request_with_http_digest("SuperSecret") do |username| - # Returns the password - USERS[username] + def authenticate + authenticate_or_request_with_http_digest("SuperSecret") do |username| + # Returns the password + USERS[username] + end end - end - def authenticate_with_request - if authenticate_with_http_digest("SuperSecret") { |username| USERS[username] } - @logged_in = true - else - request_http_digest_authentication("SuperSecret", "Authentication Failed") + def authenticate_with_request + if authenticate_with_http_digest("SuperSecret") { |username| USERS[username] } + @logged_in = true + else + request_http_digest_authentication("SuperSecret", "Authentication Failed") + end end - end end - AUTH_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION'] + AUTH_HEADERS = ["HTTP_AUTHORIZATION", "X-HTTP_AUTHORIZATION", "X_HTTP_AUTHORIZATION", "REDIRECT_X_HTTP_AUTHORIZATION"] tests DummyDigestController @@ -51,17 +51,17 @@ class HttpDigestAuthenticationTest < ActionController::TestCase AUTH_HEADERS.each do |header| test "successful authentication with #{header.downcase}" do - @request.env[header] = encode_credentials(:username => 'lifo', :password => 'world') + @request.env[header] = encode_credentials(username: "lifo", password: "world") get :index assert_response :success - assert_equal 'Hello Secret', @response.body, "Authentication failed for request header #{header}" + assert_equal "Hello Secret", @response.body, "Authentication failed for request header #{header}" end end AUTH_HEADERS.each do |header| test "unsuccessful authentication with #{header.downcase}" do - @request.env[header] = encode_credentials(:username => 'h4x0r', :password => 'world') + @request.env[header] = encode_credentials(username: "h4x0r", password: "world") get :index assert_response :unauthorized @@ -74,21 +74,21 @@ class HttpDigestAuthenticationTest < ActionController::TestCase assert_response :unauthorized assert_equal "Authentication Failed", @response.body - credentials = decode_credentials(@response.headers['WWW-Authenticate']) - assert_equal 'SuperSecret', credentials[:realm] + credentials = decode_credentials(@response.headers["WWW-Authenticate"]) + assert_equal "SuperSecret", credentials[:realm] end test "authentication request with nil credentials" do - @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => nil, :password => nil) + @request.env["HTTP_AUTHORIZATION"] = encode_credentials(username: nil, password: nil) get :index assert_response :unauthorized assert_equal "HTTP Digest: Access denied.\n", @response.body, "Authentication didn't fail for request" - assert_not_equal 'Hello Secret', @response.body, "Authentication didn't fail for request" + assert_not_equal "Hello Secret", @response.body, "Authentication didn't fail for request" end test "authentication request with invalid password" do - @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'foo') + @request.env["HTTP_AUTHORIZATION"] = encode_credentials(username: "pretty", password: "foo") get :display assert_response :unauthorized @@ -96,7 +96,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase end test "authentication request with invalid nonce" do - @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please', :nonce => "xxyyzz") + @request.env["HTTP_AUTHORIZATION"] = encode_credentials(username: "pretty", password: "please", nonce: "xxyyzz") get :display assert_response :unauthorized @@ -104,7 +104,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase end test "authentication request with invalid opaque" do - @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'foo', :opaque => "xxyyzz") + @request.env["HTTP_AUTHORIZATION"] = encode_credentials(username: "pretty", password: "foo", opaque: "xxyyzz") get :display assert_response :unauthorized @@ -112,7 +112,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase end test "authentication request with invalid realm" do - @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'foo', :realm => "NotSecret") + @request.env["HTTP_AUTHORIZATION"] = encode_credentials(username: "pretty", password: "foo", realm: "NotSecret") get :display assert_response :unauthorized @@ -120,127 +120,127 @@ class HttpDigestAuthenticationTest < ActionController::TestCase end test "authentication request with valid credential" do - @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') + @request.env["HTTP_AUTHORIZATION"] = encode_credentials(username: "pretty", password: "please") get :display assert_response :success - assert_equal 'Definitely Maybe', @response.body + assert_equal "Definitely Maybe", @response.body end test "authentication request with valid credential and nil session" do - @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') + @request.env["HTTP_AUTHORIZATION"] = encode_credentials(username: "pretty", password: "please") get :display assert_response :success - assert_equal 'Definitely Maybe', @response.body + assert_equal "Definitely Maybe", @response.body end test "authentication request with request-uri that doesn't match credentials digest-uri" do - @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') - @request.env['PATH_INFO'] = "/proxied/uri" + @request.env["HTTP_AUTHORIZATION"] = encode_credentials(username: "pretty", password: "please") + @request.env["PATH_INFO"] = "/proxied/uri" get :display assert_response :success - assert_equal 'Definitely Maybe', @response.body + assert_equal "Definitely Maybe", @response.body end test "authentication request with absolute request uri (as in webrick)" do - @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') + @request.env["HTTP_AUTHORIZATION"] = encode_credentials(username: "pretty", password: "please") @request.env["SERVER_NAME"] = "test.host" - @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest" + @request.env["PATH_INFO"] = "/http_digest_authentication_test/dummy_digest" get :display assert_response :success - assert_equal 'Definitely Maybe', @response.body + assert_equal "Definitely Maybe", @response.body end test "authentication request with absolute uri in credentials (as in IE)" do - @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:url => "http://test.host/http_digest_authentication_test/dummy_digest", - :username => 'pretty', :password => 'please') + @request.env["HTTP_AUTHORIZATION"] = encode_credentials(url: "http://test.host/http_digest_authentication_test/dummy_digest", + username: "pretty", password: "please") get :display assert_response :success - assert_equal 'Definitely Maybe', @response.body + assert_equal "Definitely Maybe", @response.body end test "authentication request with absolute uri in both request and credentials (as in Webrick with IE)" do - @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:url => "http://test.host/http_digest_authentication_test/dummy_digest", - :username => 'pretty', :password => 'please') - @request.env['SERVER_NAME'] = "test.host" - @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest" + @request.env["HTTP_AUTHORIZATION"] = encode_credentials(url: "http://test.host/http_digest_authentication_test/dummy_digest", + username: "pretty", password: "please") + @request.env["SERVER_NAME"] = "test.host" + @request.env["PATH_INFO"] = "/http_digest_authentication_test/dummy_digest" get :display assert_response :success - assert_equal 'Definitely Maybe', @response.body + assert_equal "Definitely Maybe", @response.body end test "authentication request with password stored as ha1 digest hash" do - @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'dhh', - :password => ::Digest::MD5::hexdigest(["dhh","SuperSecret","secret"].join(":")), - :password_is_ha1 => true) + @request.env["HTTP_AUTHORIZATION"] = encode_credentials(username: "dhh", + password: ::Digest::MD5::hexdigest(["dhh","SuperSecret","secret"].join(":")), + password_is_ha1: true) get :display assert_response :success - assert_equal 'Definitely Maybe', @response.body + assert_equal "Definitely Maybe", @response.body end test "authentication request with _method" do - @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please', :method => :post) - @request.env['rack.methodoverride.original_method'] = 'POST' + @request.env["HTTP_AUTHORIZATION"] = encode_credentials(username: "pretty", password: "please", method: :post) + @request.env["rack.methodoverride.original_method"] = "POST" put :display assert_response :success - assert_equal 'Definitely Maybe', @response.body + assert_equal "Definitely Maybe", @response.body end test "validate_digest_response should fail with nil returning password_procedure" do - @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => nil, :password => nil) - assert !ActionController::HttpAuthentication::Digest.validate_digest_response(@request, "SuperSecret"){nil} + @request.env["HTTP_AUTHORIZATION"] = encode_credentials(username: nil, password: nil) + assert !ActionController::HttpAuthentication::Digest.validate_digest_response(@request, "SuperSecret") { nil } end test "authentication request with request-uri ending in '/'" do - @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest/" - @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') + @request.env["PATH_INFO"] = "/http_digest_authentication_test/dummy_digest/" + @request.env["HTTP_AUTHORIZATION"] = encode_credentials(username: "pretty", password: "please") # simulate normalizing PATH_INFO - @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest" + @request.env["PATH_INFO"] = "/http_digest_authentication_test/dummy_digest" get :display assert_response :success - assert_equal 'Definitely Maybe', @response.body + assert_equal "Definitely Maybe", @response.body end test "authentication request with request-uri ending in '?'" do - @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest/?" - @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') + @request.env["PATH_INFO"] = "/http_digest_authentication_test/dummy_digest/?" + @request.env["HTTP_AUTHORIZATION"] = encode_credentials(username: "pretty", password: "please") # simulate normalizing PATH_INFO - @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest" + @request.env["PATH_INFO"] = "/http_digest_authentication_test/dummy_digest" get :display assert_response :success - assert_equal 'Definitely Maybe', @response.body + assert_equal "Definitely Maybe", @response.body end test "authentication request with absolute uri in credentials (as in IE) ending with /" do - @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest/" - @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:uri => "http://test.host/http_digest_authentication_test/dummy_digest/", - :username => 'pretty', :password => 'please') + @request.env["PATH_INFO"] = "/http_digest_authentication_test/dummy_digest/" + @request.env["HTTP_AUTHORIZATION"] = encode_credentials(uri: "http://test.host/http_digest_authentication_test/dummy_digest/", + username: "pretty", password: "please") # simulate normalizing PATH_INFO - @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest" + @request.env["PATH_INFO"] = "/http_digest_authentication_test/dummy_digest" get :display assert_response :success - assert_equal 'Definitely Maybe', @response.body + assert_equal "Definitely Maybe", @response.body end test "when sent a basic auth header, returns Unauthorized" do - @request.env['HTTP_AUTHORIZATION'] = 'Basic Gwf2aXq8ZLF3Hxq=' + @request.env["HTTP_AUTHORIZATION"] = "Basic Gwf2aXq8ZLF3Hxq=" get :display @@ -249,32 +249,32 @@ class HttpDigestAuthenticationTest < ActionController::TestCase private - def encode_credentials(options) - options.reverse_merge!(:nc => "00000001", :cnonce => "0a4f113b", :password_is_ha1 => false) - password = options.delete(:password) + def encode_credentials(options) + options.reverse_merge!(nc: "00000001", cnonce: "0a4f113b", password_is_ha1: false) + password = options.delete(:password) - # Perform unauthenticated request to retrieve digest parameters to use on subsequent request - method = options.delete(:method) || 'GET' + # Perform unauthenticated request to retrieve digest parameters to use on subsequent request + method = options.delete(:method) || "GET" - case method.to_s.upcase - when 'GET' - get :index - when 'POST' - post :index - end + case method.to_s.upcase + when "GET" + get :index + when "POST" + post :index + end - assert_response :unauthorized + assert_response :unauthorized - credentials = decode_credentials(@response.headers['WWW-Authenticate']) - credentials.merge!(options) - path_info = @request.env['PATH_INFO'].to_s - uri = options[:uri] || path_info - credentials.merge!(:uri => uri) - @request.env["ORIGINAL_FULLPATH"] = path_info - ActionController::HttpAuthentication::Digest.encode_credentials(method, credentials, password, options[:password_is_ha1]) - end + credentials = decode_credentials(@response.headers["WWW-Authenticate"]) + credentials.merge!(options) + path_info = @request.env["PATH_INFO"].to_s + uri = options[:uri] || path_info + credentials.merge!(uri: uri) + @request.env["ORIGINAL_FULLPATH"] = path_info + ActionController::HttpAuthentication::Digest.encode_credentials(method, credentials, password, options[:password_is_ha1]) + end - def decode_credentials(header) - ActionController::HttpAuthentication::Digest.decode_credentials(header) - end + def decode_credentials(header) + ActionController::HttpAuthentication::Digest.decode_credentials(header) + end end diff --git a/actionpack/test/controller/http_token_authentication_test.rb b/actionpack/test/controller/http_token_authentication_test.rb index 98e3c891a7..3842136682 100644 --- a/actionpack/test/controller/http_token_authentication_test.rb +++ b/actionpack/test/controller/http_token_authentication_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class HttpTokenAuthenticationTest < ActionController::TestCase class DummyController < ActionController::Base @@ -11,67 +11,67 @@ class HttpTokenAuthenticationTest < ActionController::TestCase end def display - render plain: 'Definitely Maybe' + render plain: "Definitely Maybe" end def show - render plain: 'Only for loooooong credentials' + render plain: "Only for loooooong credentials" end private - def authenticate - authenticate_or_request_with_http_token do |token, _| - token == 'lifo' + def authenticate + authenticate_or_request_with_http_token do |token, _| + token == "lifo" + end end - end - def authenticate_with_request - if authenticate_with_http_token { |token, options| token == '"quote" pretty' && options[:algorithm] == 'test' } - @logged_in = true - else - request_http_token_authentication("SuperSecret", "Authentication Failed\n") + def authenticate_with_request + if authenticate_with_http_token { |token, options| token == '"quote" pretty' && options[:algorithm] == "test" } + @logged_in = true + else + request_http_token_authentication("SuperSecret", "Authentication Failed\n") + end end - end - def authenticate_long_credentials - authenticate_or_request_with_http_token do |token, options| - token == '1234567890123456789012345678901234567890' && options[:algorithm] == 'test' + def authenticate_long_credentials + authenticate_or_request_with_http_token do |token, options| + token == "1234567890123456789012345678901234567890" && options[:algorithm] == "test" + end end - end end - AUTH_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION'] + AUTH_HEADERS = ["HTTP_AUTHORIZATION", "X-HTTP_AUTHORIZATION", "X_HTTP_AUTHORIZATION", "REDIRECT_X_HTTP_AUTHORIZATION"] tests DummyController AUTH_HEADERS.each do |header| test "successful authentication with #{header.downcase}" do - @request.env[header] = encode_credentials('lifo') + @request.env[header] = encode_credentials("lifo") get :index assert_response :success - assert_equal 'Hello Secret', @response.body, "Authentication failed for request header #{header}" + assert_equal "Hello Secret", @response.body, "Authentication failed for request header #{header}" end test "successful authentication with #{header.downcase} and long credentials" do - @request.env[header] = encode_credentials('1234567890123456789012345678901234567890', :algorithm => 'test') + @request.env[header] = encode_credentials("1234567890123456789012345678901234567890", algorithm: "test") get :show assert_response :success - assert_equal 'Only for loooooong credentials', @response.body, "Authentication failed for request header #{header} and long credentials" + assert_equal "Only for loooooong credentials", @response.body, "Authentication failed for request header #{header} and long credentials" end end AUTH_HEADERS.each do |header| test "unsuccessful authentication with #{header.downcase}" do - @request.env[header] = encode_credentials('h4x0r') + @request.env[header] = encode_credentials("h4x0r") get :index assert_response :unauthorized assert_equal "HTTP Token: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header}" end test "unsuccessful authentication with #{header.downcase} and long credentials" do - @request.env[header] = encode_credentials('h4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0r') + @request.env[header] = encode_credentials("h4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0r") get :show assert_response :unauthorized @@ -80,7 +80,7 @@ class HttpTokenAuthenticationTest < ActionController::TestCase end test "authentication request with badly formatted header" do - @request.env['HTTP_AUTHORIZATION'] = 'Token token$"lifo"' + @request.env["HTTP_AUTHORIZATION"] = 'Token token$"lifo"' get :index assert_response :unauthorized @@ -88,18 +88,18 @@ class HttpTokenAuthenticationTest < ActionController::TestCase end test "successful authentication request with Bearer instead of Token" do - @request.env['HTTP_AUTHORIZATION'] = 'Bearer lifo' + @request.env["HTTP_AUTHORIZATION"] = "Bearer lifo" get :index assert_response :success end test "authentication request with tab in header" do - @request.env['HTTP_AUTHORIZATION'] = "Token\ttoken=\"lifo\"" + @request.env["HTTP_AUTHORIZATION"] = "Token\ttoken=\"lifo\"" get :index assert_response :success - assert_equal 'Hello Secret', @response.body + assert_equal "Hello Secret", @response.body end test "authentication request without credential" do @@ -107,16 +107,16 @@ class HttpTokenAuthenticationTest < ActionController::TestCase assert_response :unauthorized assert_equal "Authentication Failed\n", @response.body - assert_equal 'Token realm="SuperSecret"', @response.headers['WWW-Authenticate'] + assert_equal 'Token realm="SuperSecret"', @response.headers["WWW-Authenticate"] end test "authentication request with invalid credential" do - @request.env['HTTP_AUTHORIZATION'] = encode_credentials('"quote" pretty') + @request.env["HTTP_AUTHORIZATION"] = encode_credentials('"quote" pretty') get :display assert_response :unauthorized assert_equal "Authentication Failed\n", @response.body - assert_equal 'Token realm="SuperSecret"', @response.headers['WWW-Authenticate'] + assert_equal 'Token realm="SuperSecret"', @response.headers["WWW-Authenticate"] end test "token_and_options returns correct token" do @@ -127,7 +127,7 @@ class HttpTokenAuthenticationTest < ActionController::TestCase end test "token_and_options returns correct token with value after the equal sign" do - token = 'rcHu+=HzSFw89Ypyhn/896A==f34' + token = "rcHu+=HzSFw89Ypyhn/896A==f34" actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token)).first expected = token assert_equal(expected, actual) @@ -148,7 +148,7 @@ class HttpTokenAuthenticationTest < ActionController::TestCase end test "token_and_options returns empty string with empty token" do - token = '' + token = "" actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token)).first expected = token assert_equal(expected, actual) @@ -156,10 +156,10 @@ class HttpTokenAuthenticationTest < ActionController::TestCase test "token_and_options returns correct token with nounce option" do token = "rcHu+HzSFw89Ypyhn/896A=" - nonce_hash = {nonce: "123abc"} + nonce_hash = { nonce: "123abc" } actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token, nonce_hash)) expected_token = token - expected_nonce = {"nonce" => nonce_hash[:nonce]} + expected_nonce = { "nonce" => nonce_hash[:nonce] } assert_equal(expected_token, actual.first) assert_equal(expected_nonce, actual.last) end @@ -190,7 +190,7 @@ class HttpTokenAuthenticationTest < ActionController::TestCase private - def sample_request(token, options = {nonce: "def"}) + def sample_request(token, options = { nonce: "def" }) authorization = options.inject([%{Token token="#{token}"}]) do |arr, (k, v)| arr << "#{k}=\"#{v}\"" end.join(", ") diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index ea50f05f4d..67a42989c2 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -1,10 +1,10 @@ -require 'abstract_unit' -require 'controller/fake_controllers' -require 'rails/engine' +require "abstract_unit" +require "controller/fake_controllers" +require "rails/engine" class SessionTest < ActiveSupport::TestCase StubApp = lambda { |env| - [200, {"Content-Type" => "text/html", "Content-Length" => "13"}, ["Hello, World!"]] + [200, { "Content-Type" => "text/html", "Content-Length" => "13" }, ["Hello, World!"]] } def setup @@ -31,95 +31,8 @@ class SessionTest < ActiveSupport::TestCase end end - def test_request_via_redirect_uses_given_method - path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"} - assert_called_with @session, :process, [:put, path, params: args, headers: headers] do - @session.stub :redirect?, false do - @session.request_via_redirect(:put, path, params: args, headers: headers) - end - end - end - - def test_deprecated_request_via_redirect_uses_given_method - path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" } - assert_called_with @session, :process, [:put, path, params: args, headers: headers] do - @session.stub :redirect?, false do - assert_deprecated { @session.request_via_redirect(:put, path, args, headers) } - end - end - end - - def test_request_via_redirect_follows_redirects - path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"} - value_series = [true, true, false] - assert_called @session, :follow_redirect!, times: 2 do - @session.stub :redirect?, ->{ value_series.shift } do - @session.request_via_redirect(:get, path, params: args, headers: headers) - end - end - end - - def test_request_via_redirect_returns_status - path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"} - @session.stub :redirect?, false do - @session.stub :status, 200 do - assert_equal 200, @session.request_via_redirect(:get, path, params: args, headers: headers) - end - end - end - - def test_deprecated_get_via_redirect - path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" } - - assert_called_with @session, :request_via_redirect, [:get, path, args, headers] do - assert_deprecated do - @session.get_via_redirect(path, args, headers) - end - end - end - - def test_deprecated_post_via_redirect - path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" } - - assert_called_with @session, :request_via_redirect, [:post, path, args, headers] do - assert_deprecated do - @session.post_via_redirect(path, args, headers) - end - end - end - - def test_deprecated_patch_via_redirect - path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" } - - assert_called_with @session, :request_via_redirect, [:patch, path, args, headers] do - assert_deprecated do - @session.patch_via_redirect(path, args, headers) - end - end - end - - def test_deprecated_put_via_redirect - path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" } - - assert_called_with @session, :request_via_redirect, [:put, path, args, headers] do - assert_deprecated do - @session.put_via_redirect(path, args, headers) - end - end - end - - def test_deprecated_delete_via_redirect - path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" } - - assert_called_with @session, :request_via_redirect, [:delete, path, args, headers] do - assert_deprecated do - @session.delete_via_redirect(path, args, headers) - end - end - end - def test_get - path = "/index"; params = "blah"; headers = { location: 'blah' } + path = "/index"; params = "blah"; headers = { location: "blah" } assert_called_with @session, :process, [:get, path, params: params, headers: headers] do @session.get(path, params: params, headers: headers) @@ -127,229 +40,88 @@ class SessionTest < ActiveSupport::TestCase end def test_get_with_env_and_headers - path = "/index"; params = "blah"; headers = { location: 'blah' }; env = { 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest' } + path = "/index"; params = "blah"; headers = { location: "blah" }; env = { "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest" } assert_called_with @session, :process, [:get, path, params: params, headers: headers, env: env] do @session.get(path, params: params, headers: headers, env: env) end end - def test_deprecated_get - path = "/index"; params = "blah"; headers = { location: 'blah' } - - assert_called_with @session, :process, [:get, path, params: params, headers: headers] do - assert_deprecated { - @session.get(path, params, headers) - } - end - end - def test_post - path = "/index"; params = "blah"; headers = { location: 'blah' } - assert_called_with @session, :process, [:post, path, params: params, headers: headers] do - assert_deprecated { - @session.post(path, params, headers) - } - end - end - - def test_deprecated_post - path = "/index"; params = "blah"; headers = { location: 'blah' } + path = "/index"; params = "blah"; headers = { location: "blah" } assert_called_with @session, :process, [:post, path, params: params, headers: headers] do @session.post(path, params: params, headers: headers) end end def test_patch - path = "/index"; params = "blah"; headers = { location: 'blah' } + path = "/index"; params = "blah"; headers = { location: "blah" } assert_called_with @session, :process, [:patch, path, params: params, headers: headers] do @session.patch(path, params: params, headers: headers) end end - def test_deprecated_patch - path = "/index"; params = "blah"; headers = { location: 'blah' } - assert_called_with @session, :process, [:patch, path, params: params, headers: headers] do - assert_deprecated { - @session.patch(path, params, headers) - } - end - end - def test_put - path = "/index"; params = "blah"; headers = { location: 'blah' } + path = "/index"; params = "blah"; headers = { location: "blah" } assert_called_with @session, :process, [:put, path, params: params, headers: headers] do @session.put(path, params: params, headers: headers) end end - def test_deprecated_put - path = "/index"; params = "blah"; headers = { location: 'blah' } - assert_called_with @session, :process, [:put, path, params: params, headers: headers] do - assert_deprecated { - @session.put(path, params, headers) - } - end - end - def test_delete - path = "/index"; params = "blah"; headers = { location: 'blah' } - assert_called_with @session, :process, [:delete, path, params: params, headers: headers] do - assert_deprecated { - @session.delete(path,params,headers) - } - end - end - - def test_deprecated_delete - path = "/index"; params = "blah"; headers = { location: 'blah' } + path = "/index"; params = "blah"; headers = { location: "blah" } assert_called_with @session, :process, [:delete, path, params: params, headers: headers] do @session.delete(path, params: params, headers: headers) end end def test_head - path = "/index"; params = "blah"; headers = { location: 'blah' } + path = "/index"; params = "blah"; headers = { location: "blah" } assert_called_with @session, :process, [:head, path, params: params, headers: headers] do @session.head(path, params: params, headers: headers) end end - def deprecated_test_head - path = "/index"; params = "blah"; headers = { location: 'blah' } - assert_called_with @session, :process, [:head, path, params: params, headers: headers] do - assert_deprecated { - @session.head(path, params, headers) - } - end - end - def test_xml_http_request_get - path = "/index"; params = "blah"; headers = { location: 'blah' } + path = "/index"; params = "blah"; headers = { location: "blah" } assert_called_with @session, :process, [:get, path, params: params, headers: headers, xhr: true] do @session.get(path, params: params, headers: headers, xhr: true) end end - def test_deprecated_xml_http_request_get - path = "/index"; params = "blah"; headers = { location: 'blah' } - assert_called_with @session, :process, [:get, path, params: params, headers: headers, xhr: true] do - @session.get(path, params: params, headers: headers, xhr: true) - end - end - - def test_deprecated_args_xml_http_request_get - path = "/index"; params = "blah"; headers = { location: 'blah' } - assert_called_with @session, :process, [:get, path, params: params, headers: headers, xhr: true] do - assert_deprecated(/xml_http_request/) { - @session.xml_http_request(:get, path, params, headers) - } - end - end - def test_xml_http_request_post - path = "/index"; params = "blah"; headers = { location: 'blah' } - assert_called_with @session, :process, [:post, path, params: params, headers: headers, xhr: true] do - @session.post(path, params: params, headers: headers, xhr: true) - end - end - - def test_deprecated_xml_http_request_post - path = "/index"; params = "blah"; headers = { location: 'blah' } + path = "/index"; params = "blah"; headers = { location: "blah" } assert_called_with @session, :process, [:post, path, params: params, headers: headers, xhr: true] do @session.post(path, params: params, headers: headers, xhr: true) end end - def test_deprecated_args_xml_http_request_post - path = "/index"; params = "blah"; headers = { location: 'blah' } - assert_called_with @session, :process, [:post, path, params: params, headers: headers, xhr: true] do - assert_deprecated(/xml_http_request/) { @session.xml_http_request(:post,path,params,headers) } - end - end - def test_xml_http_request_patch - path = "/index"; params = "blah"; headers = { location: 'blah' } - assert_called_with @session, :process, [:patch, path, params: params, headers: headers, xhr: true] do - @session.patch(path, params: params, headers: headers, xhr: true) - end - end - - def test_deprecated_xml_http_request_patch - path = "/index"; params = "blah"; headers = { location: 'blah' } + path = "/index"; params = "blah"; headers = { location: "blah" } assert_called_with @session, :process, [:patch, path, params: params, headers: headers, xhr: true] do @session.patch(path, params: params, headers: headers, xhr: true) end end - def test_deprecated_args_xml_http_request_patch - path = "/index"; params = "blah"; headers = { location: 'blah' } - assert_called_with @session, :process, [:patch, path, params: params, headers: headers, xhr: true] do - assert_deprecated(/xml_http_request/) { @session.xml_http_request(:patch,path,params,headers) } - end - end - def test_xml_http_request_put - path = "/index"; params = "blah"; headers = { location: 'blah' } + path = "/index"; params = "blah"; headers = { location: "blah" } assert_called_with @session, :process, [:put, path, params: params, headers: headers, xhr: true] do @session.put(path, params: params, headers: headers, xhr: true) end end - def test_deprecated_xml_http_request_put - path = "/index"; params = "blah"; headers = { location: 'blah' } - assert_called_with @session, :process, [:put, path, params: params, headers: headers, xhr: true] do - @session.put(path, params: params, headers: headers, xhr: true) - end - end - - def test_deprecated_args_xml_http_request_put - path = "/index"; params = "blah"; headers = { location: 'blah' } - assert_called_with @session, :process, [:put, path, params: params, headers: headers, xhr: true] do - assert_deprecated(/xml_http_request/) { @session.xml_http_request(:put, path, params, headers) } - end - end - def test_xml_http_request_delete - path = "/index"; params = "blah"; headers = { location: 'blah' } + path = "/index"; params = "blah"; headers = { location: "blah" } assert_called_with @session, :process, [:delete, path, params: params, headers: headers, xhr: true] do @session.delete(path, params: params, headers: headers, xhr: true) end end - def test_deprecated_xml_http_request_delete - path = "/index"; params = "blah"; headers = { location: 'blah' } - assert_called_with @session, :process, [:delete, path, params: params, headers: headers, xhr: true] do - assert_deprecated { @session.xml_http_request(:delete, path, params: params, headers: headers) } - end - end - - def test_deprecated_args_xml_http_request_delete - path = "/index"; params = "blah"; headers = { location: 'blah' } - assert_called_with @session, :process, [:delete, path, params: params, headers: headers, xhr: true] do - assert_deprecated(/xml_http_request/) { @session.xml_http_request(:delete, path, params, headers) } - end - end - def test_xml_http_request_head - path = "/index"; params = "blah"; headers = { location: 'blah' } + path = "/index"; params = "blah"; headers = { location: "blah" } assert_called_with @session, :process, [:head, path, params: params, headers: headers, xhr: true] do @session.head(path, params: params, headers: headers, xhr: true) end end - - def test_deprecated_xml_http_request_head - path = "/index"; params = "blah"; headers = { location: 'blah' } - assert_called_with @session, :process, [:head, path, params: params, headers: headers, xhr: true] do - assert_deprecated(/xml_http_request/) { @session.xml_http_request(:head, path, params: params, headers: headers) } - end - end - - def test_deprecated_args_xml_http_request_head - path = "/index"; params = "blah"; headers = { location: 'blah' } - assert_called_with @session, :process, [:head, path, params: params, headers: headers, xhr: true] do - assert_deprecated { @session.xml_http_request(:head, path, params, headers) } - end - end end class IntegrationTestTest < ActiveSupport::TestCase @@ -370,12 +142,12 @@ class IntegrationTestTest < ActiveSupport::TestCase def test_does_not_prevent_method_missing_passing_up_to_ancestors mixin = Module.new do def method_missing(name, *args) - name.to_s == 'foo' ? 'pass' : super + name.to_s == "foo" ? "pass" : super end end @test.class.superclass.__send__(:include, mixin) begin - assert_equal 'pass', @test.foo + assert_equal "pass", @test.foo ensure # leave other tests as unaffected as possible mixin.__send__(:remove_method, :method_missing) @@ -390,7 +162,7 @@ class IntegrationTestUsesCorrectClass < ActionDispatch::IntegrationTest reset! %w( get post head patch put delete ).each do |verb| - assert_nothing_raised("'#{verb}' should use integration test methods") { __send__(verb, '/') } + assert_nothing_raised { __send__(verb, "/") } end end end @@ -401,9 +173,10 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest respond_to do |format| format.html { render plain: "OK", status: 200 } format.js { render plain: "JS OK", status: 200 } - format.xml { render :xml => "<root></root>", :status => 200 } - format.rss { render :xml => "<root></root>", :status => 200 } - format.atom { render :xml => "<root></root>", :status => 200 } + format.json { render json: "JSON OK", status: 200 } + format.xml { render xml: "<root></root>", status: 200 } + format.rss { render xml: "<root></root>", status: 200 } + format.atom { render xml: "<root></root>", status: 200 } end end @@ -426,7 +199,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest end def set_cookie - cookies["foo"] = 'bar' + cookies["foo"] = "bar" head :ok end @@ -435,18 +208,18 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest end def redirect - redirect_to action_url('get') + redirect_to action_url("get") end def remove_header response.headers.delete params[:header] - head :ok, 'c' => '3' + head :ok, "c" => "3" end end def test_get with_test_route_set do - get '/get' + get "/get" assert_equal 200, status assert_equal "OK", status_message assert_response 200 @@ -463,7 +236,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest def test_get_xml_rss_atom %w[ application/xml application/rss+xml application/atom+xml ].each do |mime_string| with_test_route_set do - get "/get", headers: {"HTTP_ACCEPT" => mime_string} + get "/get", headers: { "HTTP_ACCEPT" => mime_string } assert_equal 200, status assert_equal "OK", status_message assert_response 200 @@ -480,7 +253,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest def test_post with_test_route_set do - post '/post' + post "/post" assert_equal 201, status assert_equal "Created", status_message assert_response 201 @@ -494,55 +267,55 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest end end - test 'response cookies are added to the cookie jar for the next request' do + 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" - get '/cookie_monster' + self.cookies["cookie_1"] = "sugar" + self.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) + assert_equal({ "cookie_1"=>"", "cookie_2"=>"oatmeal", "cookie_3"=>"chocolate" }, cookies.to_hash) end end - test 'cookie persist to next request' do + test "cookie persist to next request" do with_test_route_set do - get '/set_cookie' + get "/set_cookie" assert_response :success assert_equal "foo=bar; path=/", headers["Set-Cookie"] - assert_equal({"foo"=>"bar"}, cookies.to_hash) + assert_equal({ "foo"=>"bar" }, cookies.to_hash) - get '/get_cookie' + get "/get_cookie" assert_response :success assert_equal "bar", body assert_equal nil, headers["Set-Cookie"] - assert_equal({"foo"=>"bar"}, cookies.to_hash) + assert_equal({ "foo"=>"bar" }, cookies.to_hash) end end - test 'cookie persist to next request on another domain' do + test "cookie persist to next request on another domain" do with_test_route_set do host! "37s.backpack.test" - get '/set_cookie' + get "/set_cookie" assert_response :success assert_equal "foo=bar; path=/", headers["Set-Cookie"] - assert_equal({"foo"=>"bar"}, cookies.to_hash) + assert_equal({ "foo"=>"bar" }, cookies.to_hash) - get '/get_cookie' + get "/get_cookie" assert_response :success assert_equal "bar", body assert_equal nil, headers["Set-Cookie"] - assert_equal({"foo"=>"bar"}, cookies.to_hash) + assert_equal({ "foo"=>"bar" }, cookies.to_hash) end end def test_redirect with_test_route_set do - get '/redirect' + get "/redirect" assert_equal 302, status assert_equal "Found", status_message assert_response 302 @@ -556,27 +329,15 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest assert_response :success assert_equal "/get", path - get '/moved' + get "/moved" assert_response :redirect - assert_redirected_to '/method' + assert_redirected_to "/method" end end def test_xml_http_request_get with_test_route_set do - get '/get', xhr: true - assert_equal 200, status - assert_equal "OK", status_message - assert_response 200 - assert_response :success - assert_response :ok - assert_equal "JS OK", response.body - end - end - - def test_deprecated_xml_http_request_get - with_test_route_set do - assert_deprecated { xhr :get, '/get' } + get "/get", xhr: true assert_equal 200, status assert_equal "OK", status_message assert_response 200 @@ -588,7 +349,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest def test_request_with_bad_format with_test_route_set do - get '/get.php', xhr: true + get "/get.php", xhr: true assert_equal 406, status assert_response 406 assert_response :not_acceptable @@ -597,12 +358,12 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest def test_get_with_query_string with_test_route_set do - get '/get_with_params?foo=bar' - assert_equal '/get_with_params?foo=bar', request.env["REQUEST_URI"] - assert_equal '/get_with_params?foo=bar', request.fullpath + get "/get_with_params?foo=bar" + assert_equal "/get_with_params?foo=bar", request.env["REQUEST_URI"] + assert_equal "/get_with_params?foo=bar", request.fullpath assert_equal "foo=bar", request.env["QUERY_STRING"] - assert_equal 'foo=bar', request.query_string - assert_equal 'bar', request.parameters['foo'] + assert_equal "foo=bar", request.query_string + assert_equal "bar", request.parameters["foo"] assert_equal 200, status assert_equal "foo: bar", response.body @@ -611,77 +372,91 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest def test_get_with_parameters with_test_route_set do - get '/get_with_params', params: { foo: "bar" } - assert_equal '/get_with_params', request.env["PATH_INFO"] - assert_equal '/get_with_params', request.path_info - assert_equal 'foo=bar', request.env["QUERY_STRING"] - assert_equal 'foo=bar', request.query_string - assert_equal 'bar', request.parameters['foo'] + get "/get_with_params", params: { foo: "bar" } + assert_equal "/get_with_params", request.env["PATH_INFO"] + assert_equal "/get_with_params", request.path_info + assert_equal "foo=bar", request.env["QUERY_STRING"] + assert_equal "foo=bar", request.query_string + assert_equal "bar", request.parameters["foo"] assert_equal 200, status assert_equal "foo: bar", response.body end end + def test_post_then_get_with_parameters_do_not_leak_across_requests + with_test_route_set do + post "/post", params: { leaks: "does-leak?" } + + get "/get_with_params", params: { foo: "bar" } + + assert request.env["rack.input"].string.empty? + assert_equal "foo=bar", request.env["QUERY_STRING"] + assert_equal "foo=bar", request.query_string + assert_equal "bar", request.parameters["foo"] + assert request.parameters["leaks"].nil? + end + end + def test_head with_test_route_set do - head '/get' + head "/get" assert_equal 200, status assert_equal "", body - head '/post' + head "/post" assert_equal 201, status assert_equal "", body - get '/get/method' + get "/get/method" assert_equal 200, status assert_equal "method: get", body - head '/get/method' + head "/get/method" assert_equal 200, status assert_equal "", body end end def test_generate_url_with_controller - assert_equal 'http://www.example.com/foo', url_for(:controller => "foo") + assert_equal "http://www.example.com/foo", url_for(controller: "foo") end def test_port_via_host! with_test_route_set do - host! 'www.example.com:8080' - get '/get' + host! "www.example.com:8080" + get "/get" assert_equal 8080, request.port end end def test_port_via_process with_test_route_set do - get 'http://www.example.com:8080/get' + get "http://www.example.com:8080/get" assert_equal 8080, request.port end end def test_https_and_port_via_host_and_https! with_test_route_set do - host! 'www.example.com' + host! "www.example.com" https! true - get '/get' + get "/get" assert_equal 443, request.port assert_equal true, request.ssl? - host! 'www.example.com:443' + host! "www.example.com:443" https! true - get '/get' + get "/get" assert_equal 443, request.port assert_equal true, request.ssl? - host! 'www.example.com:8443' + host! "www.example.com:8443" https! true - get '/get' + get "/get" assert_equal 8443, request.port assert_equal true, request.ssl? end @@ -689,11 +464,11 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest def test_https_and_port_via_process with_test_route_set do - get 'https://www.example.com/get' + get "https://www.example.com/get" assert_equal 443, request.port assert_equal true, request.ssl? - get 'https://www.example.com:8443/get' + get "https://www.example.com:8443/get" assert_equal 8443, request.port assert_equal true, request.ssl? end @@ -701,14 +476,26 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest def test_respect_removal_of_default_headers_by_a_controller_action with_test_route_set do - with_default_headers 'a' => '1', 'b' => '2' do - get '/remove_header', params: { header: 'a' } + with_default_headers "a" => "1", "b" => "2" do + get "/remove_header", params: { header: "a" } end end - assert_not_includes @response.headers, 'a', 'Response should not include default header removed by the controller action' - assert_includes @response.headers, 'b' - assert_includes @response.headers, 'c' + assert_not_includes @response.headers, "a", "Response should not include default header removed by the controller action" + assert_includes @response.headers, "b" + assert_includes @response.headers, "c" + end + + def test_accept_not_overriden_when_xhr_true + with_test_route_set do + get "/get", headers: { "Accept" => "application/json" }, xhr: true + assert_equal "application/json", request.accept + assert_equal "application/json", response.content_type + + get "/get", headers: { "HTTP_ACCEPT" => "application/json" }, xhr: true + assert_equal "application/json", request.accept + assert_equal "application/json", response.content_type + end end private @@ -728,13 +515,15 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest end set.draw do - get 'moved' => redirect('/method') + get "moved" => redirect("/method") - match ':action', :to => controller, :via => [:get, :post], :as => :action - get 'get/:action', :to => controller, :as => :get_action + ActiveSupport::Deprecation.silence do + match ":action", to: controller, via: [:get, :post], as: :action + get "get/:action", to: controller, as: :get_action + end end - self.singleton_class.include(set.url_helpers) + singleton_class.include(set.url_helpers) yield end @@ -747,9 +536,9 @@ class MetalIntegrationTest < ActionDispatch::IntegrationTest class Poller def self.call(env) if env["PATH_INFO"] =~ /^\/success/ - [200, {"Content-Type" => "text/plain", "Content-Length" => "12"}, ["Hello World!"]] + [200, { "Content-Type" => "text/plain", "Content-Length" => "12" }, ["Hello World!"]] else - [404, {"Content-Type" => "text/plain", "Content-Length" => "0"}, []] + [404, { "Content-Type" => "text/plain", "Content-Length" => "0" }, []] end end end @@ -770,15 +559,15 @@ class MetalIntegrationTest < ActionDispatch::IntegrationTest get "/failure" assert_response 404 assert_response :not_found - assert_equal '', response.body + assert_equal "", response.body end def test_generate_url_without_controller - assert_equal 'http://www.example.com/foo', url_for(:controller => "foo") + assert_equal "http://www.example.com/foo", url_for(controller: "foo") end def test_pass_headers - get "/success", headers: {"Referer" => "http://www.example.com/foo", "Host" => "http://nohost.com"} + get "/success", headers: { "Referer" => "http://www.example.com/foo", "Host" => "http://nohost.com" } assert_equal "http://nohost.com", @request.env["HTTP_HOST"] assert_equal "http://www.example.com/foo", @request.env["HTTP_REFERER"] @@ -817,7 +606,6 @@ class MetalIntegrationTest < ActionDispatch::IntegrationTest get "https://test.com:80" assert_equal "test.com:80", @request.env["HTTP_HOST"] end - end class ApplicationIntegrationTest < ActionDispatch::IntegrationTest @@ -841,7 +629,7 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest end routes.draw do - get 'baz', :to => 'application_integration_test/test#index', :as => :baz + get "baz", to: "application_integration_test/test#index", as: :baz end def self.call(*) @@ -849,14 +637,14 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest end routes.draw do - get '', :to => 'application_integration_test/test#index', :as => :empty_string + get "", to: "application_integration_test/test#index", as: :empty_string - get 'foo', :to => 'application_integration_test/test#index', :as => :foo - get 'bar', :to => 'application_integration_test/test#index', :as => :bar + get "foo", to: "application_integration_test/test#index", as: :foo + get "bar", to: "application_integration_test/test#index", as: :bar - mount MountedApp => '/mounted', :as => "mounted" - get 'fooz' => proc { |env| [ 200, {'X-Cascade' => 'pass'}, [ "omg" ] ] }, :anchor => false - get 'fooz', :to => 'application_integration_test/test#index' + mount MountedApp => "/mounted", :as => "mounted" + get "fooz" => proc { |env| [ 200, { "X-Cascade" => "pass" }, [ "omg" ] ] }, :anchor => false + get "fooz", to: "application_integration_test/test#index" end def app @@ -864,30 +652,30 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest end test "includes route helpers" do - assert_equal '/', empty_string_path - assert_equal '/foo', foo_path - assert_equal '/bar', bar_path + assert_equal "/", empty_string_path + assert_equal "/foo", foo_path + assert_equal "/bar", bar_path end test "includes mounted helpers" do - assert_equal '/mounted/baz', mounted.baz_path + assert_equal "/mounted/baz", mounted.baz_path end test "path after cascade pass" do - get '/fooz' - assert_equal 'index', response.body - assert_equal '/fooz', path + get "/fooz" + assert_equal "index", response.body + assert_equal "/fooz", path end test "route helpers after controller access" do - get '/' - assert_equal '/', empty_string_path + get "/" + assert_equal "/", empty_string_path - get '/foo' - assert_equal '/foo', foo_path + get "/foo" + assert_equal "/foo", foo_path - get '/bar' - assert_equal '/bar', bar_path + get "/bar" + assert_equal "/bar", bar_path end test "missing route helper before controller access" do @@ -895,14 +683,14 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest end test "missing route helper after controller access" do - get '/foo' + get "/foo" assert_raise(NameError) { missing_path } end test "process do not modify the env passed as argument" do - env = { :SERVER_NAME => 'server', 'action_dispatch.custom' => 'custom' } + env = { :SERVER_NAME => "server", "action_dispatch.custom" => "custom" } old_env = env.dup - get '/foo', env: env + get "/foo", env: env assert_equal old_env, env end end @@ -924,7 +712,7 @@ class EnvironmentFilterIntegrationTest < ActionDispatch::IntegrationTest end routes.draw do - match '/post', :to => 'environment_filter_integration_test/test#post', :via => :post + match "/post", to: "environment_filter_integration_test/test#post", via: :post end def app @@ -932,11 +720,11 @@ class EnvironmentFilterIntegrationTest < ActionDispatch::IntegrationTest end test "filters rack request form vars" do - post "/post", params: { username: 'cjolly', password: 'secret' } + post "/post", params: { username: "cjolly", password: "secret" } - assert_equal 'cjolly', request.filtered_parameters['username'] - assert_equal '[FILTERED]', request.filtered_parameters['password'] - assert_equal '[FILTERED]', request.filtered_env['rack.request.form_vars'] + assert_equal "cjolly", request.filtered_parameters["username"] + assert_equal "[FILTERED]", request.filtered_parameters["password"] + assert_equal "[FILTERED]", request.filtered_env["rack.request.form_vars"] end end @@ -957,7 +745,7 @@ class UrlOptionsIntegrationTest < ActionDispatch::IntegrationTest class BarController < ActionController::Base def default_url_options - { :host => "bar.com" } + { host: "bar.com" } end def index @@ -978,9 +766,9 @@ class UrlOptionsIntegrationTest < ActionDispatch::IntegrationTest end routes.draw do - default_url_options :host => "foo.com" + default_url_options host: "foo.com" - scope :module => "url_options_integration_test" do + scope module: "url_options_integration_test" do get "/foo" => "foo#index", :as => :foos get "/foo/:id" => "foo#show", :as => :foo get "/foo/:id/edit" => "foo#edit", :as => :edit_foo @@ -1020,7 +808,7 @@ class UrlOptionsIntegrationTest < ActionDispatch::IntegrationTest test "current request path parameters are recalled" do get "/foo/1" assert_response :success - assert_equal "/foo/1/edit", url_for(:action => 'edit', :only_path => true) + assert_equal "/foo/1/edit", url_for(action: "edit", only_path: true) end end @@ -1044,12 +832,12 @@ class HeadWithStatusActionIntegrationTest < ActionDispatch::IntegrationTest end routes.draw do - get "/foo/status" => 'head_with_status_action_integration_test/foo#status' + get "/foo/status" => "head_with_status_action_integration_test/foo#status" end test "get /foo/status with head result does not cause stack overflow error" do assert_nothing_raised do - get '/foo/status' + get "/foo/status" end assert_response :ok end @@ -1058,7 +846,7 @@ end class IntegrationWithRoutingTest < ActionDispatch::IntegrationTest class FooController < ActionController::Base def index - render plain: 'ok' + render plain: "ok" end end @@ -1068,25 +856,25 @@ class IntegrationWithRoutingTest < ActionDispatch::IntegrationTest with_routing do |routes| routes.draw do namespace klass_namespace do - resources :foo, path: '/with' + resources :foo, path: "/with" end end - get '/integration_with_routing_test/with' + get "/integration_with_routing_test/with" assert_response 200 - assert_equal 'ok', response.body + assert_equal "ok", response.body end with_routing do |routes| routes.draw do namespace klass_namespace do - resources :foo, path: '/routing' + resources :foo, path: "/routing" end end - get '/integration_with_routing_test/routing' + get "/integration_with_routing_test/routing" assert_response 200 - assert_equal 'ok', response.body + assert_equal "ok", response.body end end end @@ -1098,19 +886,24 @@ class IntegrationRequestsWithoutSetup < ActionDispatch::IntegrationTest class FooController < ActionController::Base def ok - cookies[:key] = 'ok' - render plain: 'ok' + cookies[:key] = "ok" + render plain: "ok" end end def test_request with_routing do |routes| - routes.draw { get ':action' => FooController } - get '/ok' + routes.draw do + ActiveSupport::Deprecation.silence do + get ":action" => FooController + end + end + + get "/ok" assert_response 200 - assert_equal 'ok', response.body - assert_equal 'ok', cookies['key'] + assert_equal "ok", response.body + assert_equal "ok", cookies["key"] end end end @@ -1118,12 +911,12 @@ end # to ensure that session requirements in setup are persisted in the tests class IntegrationRequestsWithSessionSetup < ActionDispatch::IntegrationTest setup do - cookies['user_name'] = 'david' + cookies["user_name"] = "david" end def test_cookies_set_in_setup_are_persisted_through_the_session get "/foo" - assert_equal({"user_name"=>"david"}, cookies.to_hash) + assert_equal({ "user_name"=>"david" }, cookies.to_hash) end end @@ -1134,17 +927,33 @@ class IntegrationRequestEncodersTest < ActionDispatch::IntegrationTest end def foos_wibble - render plain: 'ok' + render plain: "ok" + end + end + + def test_standard_json_encoding_works + with_routing do |routes| + routes.draw do + ActiveSupport::Deprecation.silence do + post ":action" => FooController + end + end + + post "/foos_json.json", params: { foo: "fighters" }.to_json, + headers: { "Content-Type" => "application/json" } + + assert_response :success + assert_equal({ "foo" => "fighters" }, response.parsed_body) end end def test_encoding_as_json post_to_foos as: :json do assert_response :success - assert_match 'foos_json.json', request.path - assert_equal 'application/json', request.content_type - assert_equal({ 'foo' => 'fighters' }, request.request_parameters) - assert_equal({ 'foo' => 'fighters' }, response.parsed_body) + assert_match "foos_json.json", request.path + assert_equal "application/json", request.content_type + assert_equal({ "foo" => "fighters" }, request.request_parameters) + assert_equal({ "foo" => "fighters" }, response.parsed_body) end end @@ -1155,17 +964,17 @@ class IntegrationRequestEncodersTest < ActionDispatch::IntegrationTest end def test_registering_custom_encoder - Mime::Type.register 'text/wibble', :wibble + Mime::Type.register "text/wibble", :wibble ActionDispatch::IntegrationTest.register_encoder(:wibble, param_encoder: -> params { params }) post_to_foos as: :wibble do assert_response :success - assert_match 'foos_wibble.wibble', request.path - assert_equal 'text/wibble', request.content_type + assert_match "foos_wibble.wibble", request.path + assert_equal "text/wibble", request.content_type assert_equal Hash.new, request.request_parameters # Unregistered MIME Type can't be parsed. - assert_equal 'ok', response.parsed_body + assert_equal "ok", response.parsed_body end ensure Mime::Type.unregister :wibble @@ -1173,22 +982,96 @@ class IntegrationRequestEncodersTest < ActionDispatch::IntegrationTest def test_parsed_body_without_as_option with_routing do |routes| - routes.draw { get ':action' => FooController } + routes.draw do + ActiveSupport::Deprecation.silence do + get ":action" => FooController + end + end + + get "/foos_json.json", params: { foo: "heyo" } + + assert_equal({ "foo" => "heyo" }, response.parsed_body) + end + end + + def test_get_parameters_with_as_option + with_routing do |routes| + routes.draw do + ActiveSupport::Deprecation.silence do + get ":action" => FooController + end + end + + get "/foos_json?foo=heyo", as: :json + + assert_equal({ "foo" => "heyo" }, response.parsed_body) + end + end + + def test_get_request_with_json_uses_method_override_and_sends_a_post_request + with_routing do |routes| + routes.draw do + ActiveSupport::Deprecation.silence do + get ":action" => FooController + end + end - get '/foos_json.json', params: { foo: 'heyo' } + get "/foos_json", params: { foo: "heyo" }, as: :json - assert_equal({ 'foo' => 'heyo' }, response.parsed_body) + assert_equal "POST", request.method + assert_equal "GET", request.headers["X-Http-Method-Override"] + assert_equal({ "foo" => "heyo" }, response.parsed_body) end end private def post_to_foos(as:) with_routing do |routes| - routes.draw { post ':action' => FooController } + routes.draw do + ActiveSupport::Deprecation.silence do + post ":action" => FooController + end + end - post "/foos_#{as}", params: { foo: 'fighters' }, as: as + post "/foos_#{as}", params: { foo: "fighters" }, as: as yield end end end + +class IntegrationFileUploadTest < ActionDispatch::IntegrationTest + class IntegrationController < ActionController::Base + def test_file_upload + render plain: params[:file].size + end + end + + def self.routes + @routes ||= ActionDispatch::Routing::RouteSet.new + end + + def self.call(env) + routes.call(env) + end + + def app + self.class + end + + def self.fixture_path + File.dirname(__FILE__) + "/../fixtures/multipart" + end + + routes.draw do + post "test_file_upload", to: "integration_file_upload_test/integration#test_file_upload" + end + + def test_fixture_file_upload + post "/test_file_upload", + params: { + file: fixture_file_upload("/ruby_on_rails.jpg", "image/jpg") + } + assert_equal "45142", @response.body + end +end diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb index 0c3884cd38..e76628b936 100644 --- a/actionpack/test/controller/live_stream_test.rb +++ b/actionpack/test/controller/live_stream_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'concurrent/atomic/count_down_latch' +require "abstract_unit" +require "concurrent/atomic/count_down_latch" Thread.abort_on_exception = true module ActionController @@ -8,10 +8,10 @@ module ActionController include ActionController::Live def basic_sse - response.headers['Content-Type'] = 'text/event-stream' + response.headers["Content-Type"] = "text/event-stream" sse = SSE.new(response.stream) sse.write("{\"name\":\"John\"}") - sse.write({ name: "Ryan" }) + sse.write(name: "Ryan") ensure sse.close end @@ -19,7 +19,7 @@ module ActionController def sse_with_event sse = SSE.new(response.stream, event: "send-name") sse.write("{\"name\":\"John\"}") - sse.write({ name: "Ryan" }) + sse.write(name: "Ryan") ensure sse.close end @@ -115,7 +115,7 @@ module ActionController attr_accessor :latch, :tc, :error_latch def self.controller_path - 'test' + "test" end def set_cookie @@ -125,7 +125,7 @@ module ActionController end def render_text - render plain: 'zomg' + render plain: "zomg" end def default_header @@ -134,7 +134,7 @@ module ActionController end def basic_stream - response.headers['Content-Type'] = 'text/event-stream' + response.headers["Content-Type"] = "text/event-stream" %w{ hello world }.each do |word| response.stream.write word end @@ -142,7 +142,7 @@ module ActionController end def blocking_stream - response.headers['Content-Type'] = 'text/event-stream' + response.headers["Content-Type"] = "text/event-stream" %w{ hello world }.each do |word| response.stream.write word latch.wait @@ -150,10 +150,26 @@ module ActionController response.stream.close end + def write_sleep_autoload + path = File.join(File.dirname(__FILE__), "../fixtures") + ActiveSupport::Dependencies.autoload_paths << path + + response.headers["Content-Type"] = "text/event-stream" + response.stream.write "before load" + sleep 0.01 + silence_warning do + ::LoadMe + end + response.stream.close + latch.count_down + + ActiveSupport::Dependencies.autoload_paths.reject! { |p| p == path } + end + def thread_locals - tc.assert_equal 'aaron', Thread.current[:setting] + tc.assert_equal "aaron", Thread.current[:setting] - response.headers['Content-Type'] = 'text/event-stream' + response.headers["Content-Type"] = "text/event-stream" %w{ hello world }.each do |word| response.stream.write word end @@ -161,20 +177,20 @@ module ActionController end def with_stale - render plain: 'stale' if stale?(etag: "123", template: false) + render plain: "stale" if stale?(etag: "123", template: false) end def exception_in_view - render 'doesntexist' + render "doesntexist" end def exception_in_view_after_commit response.stream.write "" - render 'doesntexist' + render "doesntexist" end def exception_with_callback - response.headers['Content-Type'] = 'text/event-stream' + response.headers["Content-Type"] = "text/event-stream" response.stream.on_error do response.stream.write %(data: "500 Internal Server Error"\n\n) @@ -182,11 +198,11 @@ module ActionController end response.stream.write "" # make sure the response is committed - raise 'An exception occurred...' + raise "An exception occurred..." end def exception_in_controller - raise Exception, 'Exception in controller' + raise Exception, "Exception in controller" end def bad_request_error @@ -194,50 +210,50 @@ module ActionController end def exception_in_exception_callback - response.headers['Content-Type'] = 'text/event-stream' + response.headers["Content-Type"] = "text/event-stream" response.stream.on_error do - raise 'We need to go deeper.' + raise "We need to go deeper." end - response.stream.write '' + response.stream.write "" response.stream.write params[:widget][:didnt_check_for_nil] end def overfill_buffer_and_die logger = ActionController::Base.logger || Logger.new($stdout) response.stream.on_error do - logger.warn 'Error while streaming' + logger.warn "Error while streaming." error_latch.count_down end # Write until the buffer is full. It doesn't expose that # information directly, so we must hard-code its size: 10.times do - response.stream.write '.' + response.stream.write "." end # .. plus one more, because the #each frees up a slot: - response.stream.write '.' + response.stream.write "." latch.count_down # This write will block, and eventually raise - response.stream.write 'x' + response.stream.write "x" 20.times do - response.stream.write '.' + response.stream.write "." end end def ignore_client_disconnect response.stream.ignore_disconnect = true - response.stream.write '' # commit + response.stream.write "" # commit # These writes will be ignored 15.times do - response.stream.write 'x' + response.stream.write "x" end - logger.info 'Work complete' + logger.info "Work complete" latch.count_down end end @@ -245,8 +261,9 @@ module ActionController tests TestController def assert_stream_closed - assert response.stream.closed?, 'stream should be closed' - assert response.sent?, 'stream should be sent' + assert response.stream.closed?, "stream should be closed" + assert response.committed?, "response should be committed" + assert response.sent?, "response should be sent" end def capture_log_output @@ -270,21 +287,29 @@ module ActionController def test_set_cookie get :set_cookie - assert_equal({'hello' => 'world'}, @response.cookies) + assert_equal({ "hello" => "world" }, @response.cookies) assert_equal "hello world", @response.body end def test_write_to_stream get :basic_stream assert_equal "helloworld", @response.body - assert_equal 'text/event-stream', @response.headers['Content-Type'] + assert_equal "text/event-stream", @response.headers["Content-Type"] + end + + def test_delayed_autoload_after_write_within_interlock_hook + # Simulate InterlockHook + ActiveSupport::Dependencies.interlock.start_running + res = get :write_sleep_autoload + res.each {} + ActiveSupport::Dependencies.interlock.done_running end def test_async_stream rubinius_skip "https://github.com/rubinius/rubinius/issues/2934" @controller.latch = Concurrent::CountDownLatch.new - parts = ['hello', 'world'] + parts = ["hello", "world"] get :blocking_stream @@ -298,7 +323,7 @@ module ActionController end } - assert t.join(3), 'timeout expired before the thread terminated' + assert t.join(3), "timeout expired before the thread terminated" end def test_abort_with_full_buffer @@ -306,7 +331,7 @@ module ActionController @controller.error_latch = Concurrent::CountDownLatch.new capture_log_output do |output| - get :overfill_buffer_and_die, :format => 'plain' + get :overfill_buffer_and_die, format: "plain" t = Thread.new(response) { |resp| resp.await_commit @@ -320,7 +345,7 @@ module ActionController t.join @controller.error_latch.wait - assert_match 'Error while streaming', output.rewind && output.read + assert_match "Error while streaming", output.rewind && output.read end end @@ -343,26 +368,26 @@ module ActionController Timeout.timeout(3) do @controller.latch.wait end - assert_match 'Work complete', output.rewind && output.read + assert_match "Work complete", output.rewind && output.read end end def test_thread_locals_get_copied @controller.tc = self Thread.current[:originating_thread] = Thread.current.object_id - Thread.current[:setting] = 'aaron' + Thread.current[:setting] = "aaron" get :thread_locals end def test_live_stream_default_header get :default_header - assert response.headers['Content-Type'] + assert response.headers["Content-Type"] end def test_render_text get :render_text - assert_equal 'zomg', response.body + assert_equal "zomg", response.body assert_stream_closed end @@ -374,7 +399,7 @@ module ActionController capture_log_output do |output| get :exception_in_view_after_commit assert_match %r((window\.location = "/500\.html"</script></html>)$), response.body - assert_match 'Missing template test/doesntexist', output.rewind && output.read + assert_match "Missing template test/doesntexist", output.rewind && output.read assert_stream_closed end assert response.body @@ -388,8 +413,8 @@ module ActionController capture_log_output do |output| get :exception_in_view_after_commit, format: :json - assert_equal '', response.body - assert_match 'Missing template test/doesntexist', output.rewind && output.read + assert_equal "", response.body + assert_match "Missing template test/doesntexist", output.rewind && output.read assert_stream_closed end end @@ -398,34 +423,34 @@ module ActionController current_threads = Thread.list capture_log_output do |output| - get :exception_with_callback, format: 'text/event-stream' + get :exception_with_callback, format: "text/event-stream" # Wait on the execution of all threads (Thread.list - current_threads).each(&:join) assert_equal %(data: "500 Internal Server Error"\n\n), response.body - assert_match 'An exception occurred...', output.rewind && output.read + assert_match "An exception occurred...", output.rewind && output.read assert_stream_closed end end def test_exception_in_controller_before_streaming assert_raises(ActionController::LiveStreamTest::Exception) do - get :exception_in_controller, format: 'text/event-stream' + get :exception_in_controller, format: "text/event-stream" end end def test_bad_request_in_controller_before_streaming assert_raises(ActionController::BadRequest) do - get :bad_request_error, format: 'text/event-stream' + get :bad_request_error, format: "text/event-stream" end end def test_exceptions_raised_handling_exceptions_and_committed capture_log_output do |output| - get :exception_in_exception_callback, format: 'text/event-stream' - assert_equal '', response.body - assert_match 'We need to go deeper', output.rewind && output.read + get :exception_in_exception_callback, format: "text/event-stream" + assert_equal "", response.body + assert_match "We need to go deeper", output.rewind && output.read assert_stream_closed end end @@ -455,10 +480,10 @@ class LiveStreamRouterTest < ActionDispatch::IntegrationTest include ActionController::Live def index - response.headers['Content-Type'] = 'text/event-stream' + response.headers["Content-Type"] = "text/event-stream" sse = SSE.new(response.stream) sse.write("{\"name\":\"John\"}") - sse.write({ name: "Ryan" }) + sse.write(name: "Ryan") ensure sse.close end @@ -473,7 +498,7 @@ class LiveStreamRouterTest < ActionDispatch::IntegrationTest end routes.draw do - get '/test' => 'live_stream_router_test/test#index' + get "/test" => "live_stream_router_test/test#index" end def app diff --git a/actionpack/test/controller/localized_templates_test.rb b/actionpack/test/controller/localized_templates_test.rb index 3576015513..0f2242b693 100644 --- a/actionpack/test/controller/localized_templates_test.rb +++ b/actionpack/test/controller/localized_templates_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class LocalizedController < ActionController::Base def hello_world diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb index 6ae33be3c8..45a120acb6 100644 --- a/actionpack/test/controller/log_subscriber_test.rb +++ b/actionpack/test/controller/log_subscriber_test.rb @@ -4,7 +4,7 @@ require "action_controller/log_subscriber" module Another class LogSubscribersController < ActionController::Base - wrap_parameters :person, :include => :name, :format => :json + wrap_parameters :person, include: :name, format: :json class SpecialException < Exception end @@ -31,7 +31,7 @@ module Another end def data_sender - send_data "cool data", :filename => "file.txt" + send_data "cool data", filename: "file.txt" end def file_sender @@ -39,27 +39,27 @@ module Another end def with_fragment_cache - render :inline => "<%= cache('foo'){ 'bar' } %>" + render inline: "<%= cache('foo'){ 'bar' } %>" end def with_fragment_cache_and_percent_in_key - render :inline => "<%= cache('foo%bar'){ 'Contains % sign in key' } %>" + render inline: "<%= cache('foo%bar'){ 'Contains % sign in key' } %>" end def with_fragment_cache_if_with_true_condition - render :inline => "<%= cache_if(true, 'foo') { 'bar' } %>" + render inline: "<%= cache_if(true, 'foo') { 'bar' } %>" end def with_fragment_cache_if_with_false_condition - render :inline => "<%= cache_if(false, 'foo') { 'bar' } %>" + render inline: "<%= cache_if(false, 'foo') { 'bar' } %>" end def with_fragment_cache_unless_with_false_condition - render :inline => "<%= cache_unless(false, 'foo') { 'bar' } %>" + render inline: "<%= cache_unless(false, 'foo') { 'bar' } %>" end def with_fragment_cache_unless_with_true_condition - render :inline => "<%= cache_unless(true, 'foo') { 'bar' } %>" + render inline: "<%= cache_unless(true, 'foo') { 'bar' } %>" end def with_exception @@ -92,10 +92,11 @@ class ACLogSubscriberTest < ActionController::TestCase def setup super + ActionController::Base.enable_fragment_cache_logging = true @old_logger = ActionController::Base.logger - @cache_path = File.join Dir.tmpdir, Dir::Tmpname.make_tmpname('tmp', 'cache') + @cache_path = File.join Dir.tmpdir, Dir::Tmpname.make_tmpname("tmp", "cache") @controller.cache_store = :file_store, @cache_path ActionController::LogSubscriber.attach_to :action_controller end @@ -105,6 +106,7 @@ class ACLogSubscriberTest < ActionController::TestCase ActiveSupport::LogSubscriber.log_subscribers.clear FileUtils.rm_rf(@cache_path) ActionController::Base.logger = @old_logger + ActionController::Base.enable_fragment_cache_logging = true end def set_logger(logger) @@ -136,11 +138,11 @@ class ACLogSubscriberTest < ActionController::TestCase def test_process_action_without_parameters get :show wait - assert_nil logs.detect {|l| l =~ /Parameters/ } + assert_nil logs.detect { |l| l =~ /Parameters/ } end def test_process_action_with_parameters - get :show, params: { id: '10' } + get :show, params: { id: "10" } wait assert_equal 3, logs.size @@ -148,8 +150,8 @@ class ACLogSubscriberTest < ActionController::TestCase end def test_multiple_process_with_parameters - get :show, params: { id: '10' } - get :show, params: { id: '20' } + get :show, params: { id: "10" } + get :show, params: { id: "20" } wait @@ -159,8 +161,8 @@ class ACLogSubscriberTest < ActionController::TestCase end def test_process_action_with_wrapped_parameters - @request.env['CONTENT_TYPE'] = 'application/json' - post :show, params: { id: '10', name: 'jose' } + @request.env["CONTENT_TYPE"] = "application/json" + post :show, params: { id: "10", name: "jose" } wait assert_equal 3, logs.size @@ -183,11 +185,17 @@ class ACLogSubscriberTest < ActionController::TestCase assert_equal "test_value", @controller.last_payload[:test_key] end + def test_process_action_headers + get :show + wait + assert_equal "Rails Testing", @controller.last_payload[:headers]["User-Agent"] + end + def test_process_action_with_filter_parameters @request.env["action_dispatch.parameter_filter"] = [:lifo, :amount] get :show, params: { - lifo: 'Pratik', amount: '420', step: '1' + lifo: "Pratik", amount: "420", step: "1" } wait @@ -206,7 +214,7 @@ class ACLogSubscriberTest < ActionController::TestCase end def test_filter_redirect_url_by_string - @request.env['action_dispatch.redirect_filter'] = ['secret'] + @request.env["action_dispatch.redirect_filter"] = ["secret"] get :filterable_redirector wait @@ -215,7 +223,7 @@ class ACLogSubscriberTest < ActionController::TestCase end def test_filter_redirect_url_by_regexp - @request.env['action_dispatch.redirect_filter'] = [/secret\.foo.+/] + @request.env["action_dispatch.redirect_filter"] = [/secret\.foo.+/] get :filterable_redirector wait @@ -252,6 +260,20 @@ class ACLogSubscriberTest < ActionController::TestCase @controller.config.perform_caching = true end + def test_with_fragment_cache_when_log_disabled + @controller.config.perform_caching = true + ActionController::Base.enable_fragment_cache_logging = false + get :with_fragment_cache + wait + + assert_equal 2, logs.size + assert_equal "Processing by Another::LogSubscribersController#with_fragment_cache as HTML", logs[0] + assert_match(/Completed 200 OK in \d+ms/, logs[1]) + ensure + @controller.config.perform_caching = true + ActionController::Base.enable_fragment_cache_logging = true + end + def test_with_fragment_cache_if_with_true @controller.config.perform_caching = true get :with_fragment_cache_if_with_true_condition diff --git a/actionpack/test/controller/metal/renderers_test.rb b/actionpack/test/controller/metal/renderers_test.rb index 007866a559..7dc3dd6a6d 100644 --- a/actionpack/test/controller/metal/renderers_test.rb +++ b/actionpack/test/controller/metal/renderers_test.rb @@ -1,14 +1,20 @@ -require 'abstract_unit' -require 'active_support/core_ext/hash/conversions' +require "abstract_unit" +require "active_support/core_ext/hash/conversions" + +class MetalRenderingController < ActionController::Metal + include AbstractController::Rendering + include ActionController::Rendering + include ActionController::Renderers +end class MetalRenderingJsonController < MetalRenderingController class Model def to_json(options = {}) - { a: 'b' }.to_json(options) + { a: "b" }.to_json(options) end def to_xml(options = {}) - { a: 'b' }.to_xml(options) + { a: "b" }.to_xml(options) end end @@ -29,14 +35,14 @@ class RenderersMetalTest < ActionController::TestCase def test_render_json get :one assert_response :success - assert_equal({ a: 'b' }.to_json, @response.body) - assert_equal 'application/json', @response.content_type + assert_equal({ a: "b" }.to_json, @response.body) + assert_equal "application/json", @response.content_type end def test_render_xml get :two assert_response :success assert_equal(" ", @response.body) - assert_equal 'text/plain', @response.content_type + assert_equal "text/plain", @response.content_type end end diff --git a/actionpack/test/controller/mime/accept_format_test.rb b/actionpack/test/controller/mime/accept_format_test.rb index e20c08da4e..a2834c9f39 100644 --- a/actionpack/test/controller/mime/accept_format_test.rb +++ b/actionpack/test/controller/mime/accept_format_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class StarStarMimeController < ActionController::Base layout nil @@ -11,7 +11,7 @@ end class StarStarMimeControllerTest < ActionController::TestCase def test_javascript_with_format @request.accept = "text/javascript" - get :index, format: 'js' + get :index, format: "js" assert_match "function addition(a,b){ return a+b; }", @response.body end @@ -71,7 +71,7 @@ class MimeControllerLayoutsTest < ActionController::TestCase @request.accept = "text/iphone" get :index - assert_equal 'Hello iPhone', @response.body + assert_equal "Hello iPhone", @response.body end def test_format_with_inherited_layouts diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb index 76e2d3ff43..c5f8165d04 100644 --- a/actionpack/test/controller/mime/respond_to_test.rb +++ b/actionpack/test/controller/mime/respond_to_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" require "active_support/log_subscriber/test_helper" class RespondToController < ActionController::Base @@ -45,13 +45,12 @@ class RespondToController < ActionController::Base def json_xml_or_html respond_to do |type| - type.json { render body: 'JSON' } - type.xml { render :xml => 'XML' } - type.html { render body: 'HTML' } + type.json { render body: "JSON" } + type.xml { render xml: "XML" } + type.html { render body: "HTML" } end end - def forced_xml request.format = :xml @@ -74,6 +73,14 @@ class RespondToController < ActionController::Base end end + def missing_templates + respond_to do |type| + # This test requires a block that is empty + type.json {} + type.xml + end + end + def using_defaults_with_type_list respond_to(:html, :xml) end @@ -101,7 +108,6 @@ class RespondToController < ActionController::Base end end - def custom_constant_handling respond_to do |type| type.html { render body: "HTML" } @@ -125,8 +131,8 @@ class RespondToController < ActionController::Base def handle_any_any respond_to do |type| - type.html { render body: 'HTML' } - type.any { render body: 'Whatever you ask for, I got it' } + type.html { render body: "HTML" } + type.any { render body: "Whatever you ask for, I got it" } end end @@ -138,7 +144,7 @@ class RespondToController < ActionController::Base def json_with_callback respond_to do |type| - type.json { render :json => 'JS', :callback => 'alert' } + type.json { render json: "JS", callback: "alert" } end end @@ -155,12 +161,19 @@ class RespondToController < ActionController::Base request.format = "iphone" if request.env["HTTP_ACCEPT"] == "text/iphone" respond_to do |type| - type.html { @type = "Firefox"; render :action => "iphone_with_html_response_type" } - type.iphone { @type = "iPhone" ; render :action => "iphone_with_html_response_type" } + type.html { @type = "Firefox"; render action: "iphone_with_html_response_type" } + type.iphone { @type = "iPhone" ; render action: "iphone_with_html_response_type" } end end - def variant_with_implicit_rendering + def variant_with_implicit_template_rendering + # This has exactly one variant template defined in the file system (+mobile.html.erb), + # which raises the regular MissingTemplate error for other variants. + end + + def variant_without_implicit_template_rendering + # This differs from the above in that it does not have any templates defined in the file + # system, which triggers the ImplicitRender (204 No Content) behavior. end def variant_with_format_and_custom_render @@ -208,7 +221,7 @@ class RespondToController < ActionController::Base def variant_any respond_to do |format| format.html do |variant| - variant.any(:tablet, :phablet){ render body: "any" } + variant.any(:tablet, :phablet) { render body: "any" } variant.phone { render body: "phone" } end end @@ -225,7 +238,7 @@ class RespondToController < ActionController::Base def variant_inline_any respond_to do |format| - format.html.any(:tablet, :phablet){ render body: "any" } + format.html.any(:tablet, :phablet) { render body: "any" } format.html.phone { render body: "phone" } end end @@ -246,7 +259,7 @@ class RespondToController < ActionController::Base def variant_any_with_none respond_to do |format| - format.html.any(:none, :phone){ render body: "none or phone" } + format.html.any(:none, :phone) { render body: "none or phone" } end end @@ -254,8 +267,8 @@ class RespondToController < ActionController::Base respond_to do |format| format.html { render body: "HTML" } format.any(:js, :xml) do |variant| - variant.phone{ render body: "phone" } - variant.any(:tablet, :phablet){ render body: "tablet" } + variant.phone { render body: "phone" } + variant.any(:tablet, :phablet) { render body: "tablet" } end end end @@ -263,15 +276,17 @@ class RespondToController < ActionController::Base protected def set_layout case action_name - when "all_types_with_layout", "iphone_with_html_response_type" - "respond_to/layouts/standard" - when "iphone_with_html_response_type_without_layout" - "respond_to/layouts/missing" + when "all_types_with_layout", "iphone_with_html_response_type" + "respond_to/layouts/standard" + when "iphone_with_html_response_type_without_layout" + "respond_to/layouts/missing" end end end class RespondToControllerTest < ActionController::TestCase + NO_CONTENT_WARNING = "No template found for RespondToController#variant_without_implicit_template_rendering, rendering head :no_content" + def setup super @request.host = "www.example.com" @@ -288,10 +303,10 @@ class RespondToControllerTest < ActionController::TestCase def test_html @request.accept = "text/html" get :js_or_html - assert_equal 'HTML', @response.body + assert_equal "HTML", @response.body get :html_or_xml - assert_equal 'HTML', @response.body + assert_equal "HTML", @response.body assert_raises(ActionController::UnknownFormat) do get :just_xml @@ -301,29 +316,29 @@ class RespondToControllerTest < ActionController::TestCase def test_all @request.accept = "*/*" get :js_or_html - assert_equal 'HTML', @response.body # js is not part of all + assert_equal "HTML", @response.body # js is not part of all get :html_or_xml - assert_equal 'HTML', @response.body + assert_equal "HTML", @response.body get :just_xml - assert_equal 'XML', @response.body + assert_equal "XML", @response.body end def test_xml @request.accept = "application/xml" get :html_xml_or_rss - assert_equal 'XML', @response.body + assert_equal "XML", @response.body end def test_js_or_html @request.accept = "text/javascript, text/html" get :js_or_html, xhr: true - assert_equal 'JS', @response.body + assert_equal "JS", @response.body @request.accept = "text/javascript, text/html" get :html_or_xml, xhr: true - assert_equal 'HTML', @response.body + assert_equal "HTML", @response.body @request.accept = "text/javascript, text/html" @@ -335,25 +350,25 @@ class RespondToControllerTest < ActionController::TestCase def test_json_or_yaml_with_leading_star_star @request.accept = "*/*, application/json" get :json_xml_or_html - assert_equal 'HTML', @response.body + assert_equal "HTML", @response.body @request.accept = "*/* , application/json" get :json_xml_or_html - assert_equal 'HTML', @response.body + assert_equal "HTML", @response.body end def test_json_or_yaml get :json_or_yaml, xhr: true - assert_equal 'JSON', @response.body + assert_equal "JSON", @response.body - get :json_or_yaml, format: 'json' - assert_equal 'JSON', @response.body + get :json_or_yaml, format: "json" + assert_equal "JSON", @response.body - get :json_or_yaml, format: 'yaml' - assert_equal 'YAML', @response.body + get :json_or_yaml, format: "yaml" + assert_equal "YAML", @response.body - { 'YAML' => %w(text/yaml), - 'JSON' => %w(application/json text/x-json) + { "YAML" => %w(text/yaml), + "JSON" => %w(application/json text/x-json) }.each do |body, content_types| content_types.each do |content_type| @request.accept = content_type @@ -366,20 +381,20 @@ class RespondToControllerTest < ActionController::TestCase def test_js_or_anything @request.accept = "text/javascript, */*" get :js_or_html, xhr: true - assert_equal 'JS', @response.body + assert_equal "JS", @response.body get :html_or_xml, xhr: true - assert_equal 'HTML', @response.body + assert_equal "HTML", @response.body get :just_xml, xhr: true - assert_equal 'XML', @response.body + assert_equal "XML", @response.body end def test_using_defaults @request.accept = "*/*" get :using_defaults assert_equal "text/html", @response.content_type - assert_equal 'Hello world!', @response.body + assert_equal "Hello world!", @response.body @request.accept = "application/xml" get :using_defaults @@ -405,7 +420,7 @@ class RespondToControllerTest < ActionController::TestCase @request.accept = "*/*" get :using_defaults_with_type_list assert_equal "text/html", @response.content_type - assert_equal 'Hello world!', @response.body + assert_equal "Hello world!", @response.body @request.accept = "application/xml" get :using_defaults_with_type_list @@ -430,7 +445,7 @@ class RespondToControllerTest < ActionController::TestCase def test_synonyms @request.accept = "application/javascript" get :js_or_html - assert_equal 'JS', @response.body + assert_equal "JS", @response.body @request.accept = "application/x-xml" get :html_xml_or_rss @@ -441,82 +456,82 @@ class RespondToControllerTest < ActionController::TestCase @request.accept = "application/crazy-xml" get :custom_type_handling assert_equal "application/crazy-xml", @response.content_type - assert_equal 'Crazy XML', @response.body + assert_equal "Crazy XML", @response.body @request.accept = "text/html" get :custom_type_handling assert_equal "text/html", @response.content_type - assert_equal 'HTML', @response.body + assert_equal "HTML", @response.body end def test_xhtml_alias @request.accept = "application/xhtml+xml,application/xml" get :html_or_xml - assert_equal 'HTML', @response.body + assert_equal "HTML", @response.body end def test_firefox_simulation @request.accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5" get :html_or_xml - assert_equal 'HTML', @response.body + assert_equal "HTML", @response.body end def test_handle_any @request.accept = "*/*" get :handle_any - assert_equal 'HTML', @response.body + assert_equal "HTML", @response.body @request.accept = "text/javascript" get :handle_any - assert_equal 'Either JS or XML', @response.body + assert_equal "Either JS or XML", @response.body @request.accept = "text/xml" get :handle_any - assert_equal 'Either JS or XML', @response.body + assert_equal "Either JS or XML", @response.body end def test_handle_any_any @request.accept = "*/*" get :handle_any_any - assert_equal 'HTML', @response.body + assert_equal "HTML", @response.body end def test_handle_any_any_parameter_format - get :handle_any_any, format: 'html' - assert_equal 'HTML', @response.body + get :handle_any_any, format: "html" + assert_equal "HTML", @response.body end def test_handle_any_any_explicit_html @request.accept = "text/html" get :handle_any_any - assert_equal 'HTML', @response.body + assert_equal "HTML", @response.body end def test_handle_any_any_javascript @request.accept = "text/javascript" get :handle_any_any - assert_equal 'Whatever you ask for, I got it', @response.body + assert_equal "Whatever you ask for, I got it", @response.body end def test_handle_any_any_xml @request.accept = "text/xml" get :handle_any_any - assert_equal 'Whatever you ask for, I got it', @response.body + assert_equal "Whatever you ask for, I got it", @response.body end def test_handle_any_any_unkown_format - get :handle_any_any, format: 'php' - assert_equal 'Whatever you ask for, I got it', @response.body + get :handle_any_any, format: "php" + assert_equal "Whatever you ask for, I got it", @response.body end def test_browser_check_with_any_any @request.accept = "application/json, application/xml" get :json_xml_or_html - assert_equal 'JSON', @response.body + assert_equal "JSON", @response.body @request.accept = "application/json, application/xml, */*" get :json_xml_or_html - assert_equal 'HTML', @response.body + assert_equal "HTML", @response.body end def test_html_type_with_layout @@ -526,15 +541,15 @@ class RespondToControllerTest < ActionController::TestCase end def test_json_with_callback_sets_javascript_content_type - @request.accept = 'application/json' + @request.accept = "application/json" get :json_with_callback - assert_equal '/**/alert(JS)', @response.body - assert_equal 'text/javascript', @response.content_type + assert_equal "/**/alert(JS)", @response.body + assert_equal "text/javascript", @response.content_type end def test_xhr get :js_or_html, xhr: true - assert_equal 'JS', @response.body + assert_equal "JS", @response.body end def test_custom_constant @@ -615,31 +630,77 @@ class RespondToControllerTest < ActionController::TestCase end end + def test_missing_templates + get :missing_templates, format: :json + assert_response :no_content + get :missing_templates, format: :xml + assert_response :no_content + end + def test_invalid_variant + assert_raises(ActionController::UnknownFormat) do + get :variant_with_implicit_template_rendering, params: { v: :invalid } + end + end + + def test_variant_not_set_regular_unknown_format + assert_raises(ActionController::UnknownFormat) do + get :variant_with_implicit_template_rendering + end + end + + def test_variant_with_implicit_template_rendering + get :variant_with_implicit_template_rendering, params: { v: :mobile } + assert_equal "text/html", @response.content_type + assert_equal "mobile", @response.body + end + + def test_variant_without_implicit_rendering_from_browser + assert_raises(ActionController::UnknownFormat) do + get :variant_without_implicit_template_rendering, params: { v: :does_not_matter } + end + end + + def test_variant_variant_not_set_and_without_implicit_rendering_from_browser + assert_raises(ActionController::UnknownFormat) do + get :variant_without_implicit_template_rendering + end + end + + def test_variant_without_implicit_rendering_from_xhr logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new old_logger, ActionController::Base.logger = ActionController::Base.logger, logger - get :variant_with_implicit_rendering, params: { v: :invalid } + get :variant_without_implicit_template_rendering, xhr: true, params: { v: :does_not_matter } assert_response :no_content - assert_equal 1, logger.logged(:info).select{ |s| s =~ /No template found/ }.size, "Implicit head :no_content not logged" + + assert_equal 1, logger.logged(:info).select { |s| s == NO_CONTENT_WARNING }.size, "Implicit head :no_content not logged" ensure ActionController::Base.logger = old_logger end - def test_variant_not_set_regular_template_missing - get :variant_with_implicit_rendering + def test_variant_without_implicit_rendering_from_api + logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new + old_logger, ActionController::Base.logger = ActionController::Base.logger, logger + + get :variant_without_implicit_template_rendering, format: "json", params: { v: :does_not_matter } assert_response :no_content + + assert_equal 1, logger.logged(:info).select { |s| s == NO_CONTENT_WARNING }.size, "Implicit head :no_content not logged" + ensure + ActionController::Base.logger = old_logger end - def test_variant_with_implicit_rendering - get :variant_with_implicit_rendering, params: { v: :implicit } + def test_variant_variant_not_set_and_without_implicit_rendering_from_xhr + logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new + old_logger, ActionController::Base.logger = ActionController::Base.logger, logger + + get :variant_without_implicit_template_rendering, xhr: true assert_response :no_content - end - def test_variant_with_implicit_template_rendering - get :variant_with_implicit_rendering, params: { v: :mobile } - assert_equal "text/html", @response.content_type - assert_equal "mobile", @response.body + assert_equal 1, logger.logged(:info).select { |s| s == NO_CONTENT_WARNING }.size, "Implicit head :no_content not logged" + ensure + ActionController::Base.logger = old_logger end def test_variant_with_format_and_custom_render @@ -778,24 +839,3 @@ class RespondToControllerTest < ActionController::TestCase assert_equal "phone", @response.body end end - -class RespondToWithBlockOnDefaultRenderController < ActionController::Base - def show - default_render do - render body: 'default_render yielded' - end - end -end - -class RespondToWithBlockOnDefaultRenderControllerTest < ActionController::TestCase - def setup - super - @request.host = "www.example.com" - end - - def test_default_render_uses_block_when_no_template_exists - get :show - assert_equal "default_render yielded", @response.body - assert_equal "text/plain", @response.content_type - end -end diff --git a/actionpack/test/controller/new_base/bare_metal_test.rb b/actionpack/test/controller/new_base/bare_metal_test.rb index ee3c498b1c..9956f0b107 100644 --- a/actionpack/test/controller/new_base/bare_metal_test.rb +++ b/actionpack/test/controller/new_base/bare_metal_test.rb @@ -102,38 +102,38 @@ module BareMetalTest test "head :continue (100) does not return a content-type header" do headers = HeadController.action(:continue).call(Rack::MockRequest.env_for("/")).second - assert_nil headers['Content-Type'] - assert_nil headers['Content-Length'] + assert_nil headers["Content-Type"] + assert_nil headers["Content-Length"] end test "head :switching_protocols (101) does not return a content-type header" do headers = HeadController.action(:switching_protocols).call(Rack::MockRequest.env_for("/")).second - assert_nil headers['Content-Type'] - assert_nil headers['Content-Length'] + assert_nil headers["Content-Type"] + assert_nil headers["Content-Length"] end test "head :processing (102) does not return a content-type header" do headers = HeadController.action(:processing).call(Rack::MockRequest.env_for("/")).second - assert_nil headers['Content-Type'] - assert_nil headers['Content-Length'] + assert_nil headers["Content-Type"] + assert_nil headers["Content-Length"] end test "head :no_content (204) does not return a content-type header" do headers = HeadController.action(:no_content).call(Rack::MockRequest.env_for("/")).second - assert_nil headers['Content-Type'] - assert_nil headers['Content-Length'] + assert_nil headers["Content-Type"] + assert_nil headers["Content-Length"] end test "head :reset_content (205) does not return a content-type header" do headers = HeadController.action(:reset_content).call(Rack::MockRequest.env_for("/")).second - assert_nil headers['Content-Type'] - assert_nil headers['Content-Length'] + assert_nil headers["Content-Type"] + assert_nil headers["Content-Length"] end test "head :not_modified (304) does not return a content-type header" do headers = HeadController.action(:not_modified).call(Rack::MockRequest.env_for("/")).second - assert_nil headers['Content-Type'] - assert_nil headers['Content-Length'] + assert_nil headers["Content-Type"] + assert_nil headers["Content-Length"] end test "head :no_content (204) does not return any content" do diff --git a/actionpack/test/controller/new_base/base_test.rb b/actionpack/test/controller/new_base/base_test.rb index 0755dafe93..3765d406fc 100644 --- a/actionpack/test/controller/new_base/base_test.rb +++ b/actionpack/test/controller/new_base/base_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" # Tests the controller dispatching happy path module Dispatching @@ -69,48 +69,48 @@ module Dispatching end test "controller path" do - assert_equal 'dispatching/empty', EmptyController.controller_path + assert_equal "dispatching/empty", EmptyController.controller_path assert_equal EmptyController.controller_path, EmptyController.new.controller_path end test "non-default controller path" do - assert_equal 'i_am_not_default', NonDefaultPathController.controller_path + assert_equal "i_am_not_default", NonDefaultPathController.controller_path assert_equal NonDefaultPathController.controller_path, NonDefaultPathController.new.controller_path end test "sub controller path" do - assert_equal 'dispatching/sub_empty', SubEmptyController.controller_path + assert_equal "dispatching/sub_empty", SubEmptyController.controller_path assert_equal SubEmptyController.controller_path, SubEmptyController.new.controller_path end test "namespaced controller path" do - assert_equal 'dispatching/submodule/contained_empty', Submodule::ContainedEmptyController.controller_path + assert_equal "dispatching/submodule/contained_empty", Submodule::ContainedEmptyController.controller_path assert_equal Submodule::ContainedEmptyController.controller_path, Submodule::ContainedEmptyController.new.controller_path end test "namespaced non-default controller path" do - assert_equal 'i_am_extremely_not_default', Submodule::ContainedNonDefaultPathController.controller_path + assert_equal "i_am_extremely_not_default", Submodule::ContainedNonDefaultPathController.controller_path assert_equal Submodule::ContainedNonDefaultPathController.controller_path, Submodule::ContainedNonDefaultPathController.new.controller_path end test "namespaced sub controller path" do - assert_equal 'dispatching/submodule/contained_sub_empty', Submodule::ContainedSubEmptyController.controller_path + assert_equal "dispatching/submodule/contained_sub_empty", Submodule::ContainedSubEmptyController.controller_path assert_equal Submodule::ContainedSubEmptyController.controller_path, Submodule::ContainedSubEmptyController.new.controller_path end test "controller name" do - assert_equal 'empty', EmptyController.controller_name - assert_equal 'contained_empty', Submodule::ContainedEmptyController.controller_name + assert_equal "empty", EmptyController.controller_name + assert_equal "contained_empty", Submodule::ContainedEmptyController.controller_name end test "non-default path controller name" do - assert_equal 'non_default_path', NonDefaultPathController.controller_name - assert_equal 'contained_non_default_path', Submodule::ContainedNonDefaultPathController.controller_name + assert_equal "non_default_path", NonDefaultPathController.controller_name + assert_equal "contained_non_default_path", Submodule::ContainedNonDefaultPathController.controller_name end test "sub controller name" do - assert_equal 'sub_empty', SubEmptyController.controller_name - assert_equal 'contained_sub_empty', Submodule::ContainedSubEmptyController.controller_name + assert_equal "sub_empty", SubEmptyController.controller_name + assert_equal "contained_sub_empty", Submodule::ContainedSubEmptyController.controller_name end test "action methods" do diff --git a/actionpack/test/controller/new_base/content_negotiation_test.rb b/actionpack/test/controller/new_base/content_negotiation_test.rb index c0e92b3b05..b870745031 100644 --- a/actionpack/test/controller/new_base/content_negotiation_test.rb +++ b/actionpack/test/controller/new_base/content_negotiation_test.rb @@ -1,7 +1,6 @@ -require 'abstract_unit' +require "abstract_unit" module ContentNegotiation - # This has no layout and it works class BasicController < ActionController::Base self.view_paths = [ActionView::FixtureResolver.new( @@ -9,7 +8,7 @@ module ContentNegotiation )] def all - render plain: self.formats.inspect + render plain: formats.inspect end end diff --git a/actionpack/test/controller/new_base/content_type_test.rb b/actionpack/test/controller/new_base/content_type_test.rb index a9dcdde4b8..85089bafe2 100644 --- a/actionpack/test/controller/new_base/content_type_test.rb +++ b/actionpack/test/controller/new_base/content_type_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module ContentType class BaseController < ActionController::Base @@ -43,7 +43,9 @@ module ContentType test "default response is text/plain and UTF8" do with_routing do |set| set.draw do - get ':controller', :action => 'index' + ActiveSupport::Deprecation.silence do + get ":controller", action: "index" + end end get "/content_type/base" diff --git a/actionpack/test/controller/new_base/middleware_test.rb b/actionpack/test/controller/new_base/middleware_test.rb index 85a1f351f0..0493291c03 100644 --- a/actionpack/test/controller/new_base/middleware_test.rb +++ b/actionpack/test/controller/new_base/middleware_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module MiddlewareTest class MyMiddleware @@ -56,8 +56,8 @@ module MiddlewareTest end class ActionsController < ActionController::Metal - use MyMiddleware, :only => :show - middleware.insert_before MyMiddleware, ExclaimerMiddleware, :except => :index + use MyMiddleware, only: :show + middleware.insert_before MyMiddleware, ExclaimerMiddleware, except: :index def index self.response_body = "index" diff --git a/actionpack/test/controller/new_base/render_action_test.rb b/actionpack/test/controller/new_base/render_action_test.rb index 3bf1dd0ede..4b59a3d676 100644 --- a/actionpack/test/controller/new_base/render_action_test.rb +++ b/actionpack/test/controller/new_base/render_action_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module RenderAction # This has no layout and it works @@ -8,7 +8,7 @@ module RenderAction )] def hello_world - render :action => "hello_world" + render action: "hello_world" end def hello_world_as_string @@ -16,7 +16,7 @@ module RenderAction end def hello_world_as_string_with_options - render "hello_world", :status => 404 + render "hello_world", status: 404 end def hello_world_as_symbol @@ -24,25 +24,24 @@ module RenderAction end def hello_world_with_symbol - render :action => :hello_world + render action: :hello_world end def hello_world_with_layout - render :action => "hello_world", :layout => true + render action: "hello_world", layout: true end def hello_world_with_layout_false - render :action => "hello_world", :layout => false + render action: "hello_world", layout: false end def hello_world_with_layout_nil - render :action => "hello_world", :layout => nil + render action: "hello_world", layout: nil end def hello_world_with_custom_layout - render :action => "hello_world", :layout => "greetings" + render action: "hello_world", layout: "greetings" end - end class RenderActionTest < Rack::TestCase @@ -127,27 +126,27 @@ module RenderActionWithApplicationLayout )] def hello_world - render :action => "hello_world" + render action: "hello_world" end def hello_world_with_layout - render :action => "hello_world", :layout => true + render action: "hello_world", layout: true end def hello_world_with_layout_false - render :action => "hello_world", :layout => false + render action: "hello_world", layout: false end def hello_world_with_layout_nil - render :action => "hello_world", :layout => nil + render action: "hello_world", layout: nil end def hello_world_with_custom_layout - render :action => "hello_world", :layout => "greetings" + render action: "hello_world", layout: "greetings" end def with_builder_and_layout - render :action => "hello", :layout => "builder" + render action: "hello", layout: "builder" end end @@ -196,7 +195,6 @@ module RenderActionWithApplicationLayout assert_response "<html>\n<p>Hello</p>\n</html>\n" end end - end module RenderActionWithControllerLayout @@ -207,23 +205,23 @@ module RenderActionWithControllerLayout )] def hello_world - render :action => "hello_world" + render action: "hello_world" end def hello_world_with_layout - render :action => "hello_world", :layout => true + render action: "hello_world", layout: true end def hello_world_with_layout_false - render :action => "hello_world", :layout => false + render action: "hello_world", layout: false end def hello_world_with_layout_nil - render :action => "hello_world", :layout => nil + render action: "hello_world", layout: nil end def hello_world_with_custom_layout - render :action => "hello_world", :layout => "greetings" + render action: "hello_world", layout: "greetings" end end @@ -260,26 +258,25 @@ end module RenderActionWithBothLayouts class BasicController < ActionController::Base - self.view_paths = [ActionView::FixtureResolver.new({ - "render_action_with_both_layouts/basic/hello_world.html.erb" => "Hello World!", + self.view_paths = [ActionView::FixtureResolver.new( + "render_action_with_both_layouts/basic/hello_world.html.erb" => "Hello World!", "layouts/application.html.erb" => "Oh Hi <%= yield %> Bye", - "layouts/render_action_with_both_layouts/basic.html.erb" => "With Controller Layout! <%= yield %> Bye" - })] + "layouts/render_action_with_both_layouts/basic.html.erb" => "With Controller Layout! <%= yield %> Bye")] def hello_world - render :action => "hello_world" + render action: "hello_world" end def hello_world_with_layout - render :action => "hello_world", :layout => true + render action: "hello_world", layout: true end def hello_world_with_layout_false - render :action => "hello_world", :layout => false + render action: "hello_world", layout: false end def hello_world_with_layout_nil - render :action => "hello_world", :layout => nil + render action: "hello_world", layout: nil end end diff --git a/actionpack/test/controller/new_base/render_body_test.rb b/actionpack/test/controller/new_base/render_body_test.rb index f4a3db8b41..b1467a0deb 100644 --- a/actionpack/test/controller/new_base/render_body_test.rb +++ b/actionpack/test/controller/new_base/render_body_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module RenderBody class MinimalController < ActionController::Metal @@ -66,7 +66,7 @@ module RenderBody end def with_custom_content_type - response.headers['Content-Type'] = 'application/json' + response.headers["Content-Type"] = "application/json" render body: '["troll","face"]' end @@ -85,7 +85,7 @@ module RenderBody test "rendering body from an action with default options renders the body with the layout" do with_routing do |set| - set.draw { get ':controller', action: 'index' } + set.draw { ActiveSupport::Deprecation.silence { get ":controller", action: "index" } } get "/render_body/simple" assert_body "hello david" @@ -95,7 +95,7 @@ module RenderBody test "rendering body from an action with default options renders the body without the layout" do with_routing do |set| - set.draw { get ':controller', action: 'index' } + set.draw { ActiveSupport::Deprecation.silence { get ":controller", action: "index" } } get "/render_body/with_layout" @@ -150,7 +150,7 @@ module RenderBody get "/render_body/with_layout/with_custom_content_type" assert_equal %w{ troll face }, JSON.parse(response.body) - assert_equal 'application/json', response.headers['Content-Type'] + assert_equal "application/json", response.headers["Content-Type"] end test "rendering body with layout: false" do diff --git a/actionpack/test/controller/new_base/render_context_test.rb b/actionpack/test/controller/new_base/render_context_test.rb index 177a1c088d..b64468a94e 100644 --- a/actionpack/test/controller/new_base/render_context_test.rb +++ b/actionpack/test/controller/new_base/render_context_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" # This is testing the decoupling of view renderer and view context # by allowing the controller to be used as view context. This is @@ -18,24 +18,24 @@ module RenderContext def hello_world @value = "Hello" - render :action => "hello_world", :layout => false + render action: "hello_world", layout: false end def with_layout @value = "Hello" - render :action => "hello_world", :layout => "basic" + render action: "hello_world", layout: "basic" end protected - # 3) Set view_context to self - def view_context - self - end + # 3) Set view_context to self + def view_context + self + end - def __controller_method__ - "controller context!" - end + def __controller_method__ + "controller context!" + end end class RenderContextTest < Rack::TestCase diff --git a/actionpack/test/controller/new_base/render_file_test.rb b/actionpack/test/controller/new_base/render_file_test.rb index 0c21bb0719..6d651e0104 100644 --- a/actionpack/test/controller/new_base/render_file_test.rb +++ b/actionpack/test/controller/new_base/render_file_test.rb @@ -1,36 +1,36 @@ -require 'abstract_unit' +require "abstract_unit" module RenderFile class BasicController < ActionController::Base self.view_paths = File.dirname(__FILE__) def index - render :file => File.join(File.dirname(__FILE__), *%w[.. .. fixtures test hello_world]) + render file: File.join(File.dirname(__FILE__), *%w[.. .. fixtures test hello_world]) end def with_instance_variables - @secret = 'in the sauce' - render :file => File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_ivar') + @secret = "in the sauce" + render file: File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_ivar") end def relative_path - @secret = 'in the sauce' - render :file => '../../fixtures/test/render_file_with_ivar' + @secret = "in the sauce" + render file: "../../fixtures/test/render_file_with_ivar" end def relative_path_with_dot - @secret = 'in the sauce' - render :file => '../../fixtures/test/dot.directory/render_file_with_ivar' + @secret = "in the sauce" + render file: "../../fixtures/test/dot.directory/render_file_with_ivar" end def pathname - @secret = 'in the sauce' - render :file => Pathname.new(File.dirname(__FILE__)).join(*%w[.. .. fixtures test dot.directory render_file_with_ivar]) + @secret = "in the sauce" + render file: Pathname.new(File.dirname(__FILE__)).join(*%w[.. .. fixtures test dot.directory render_file_with_ivar]) end def with_locals - path = File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_locals') - render :file => path, :locals => {:secret => 'in the sauce'} + path = File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_locals") + render file: path, locals: { secret: "in the sauce" } end end diff --git a/actionpack/test/controller/new_base/render_html_test.rb b/actionpack/test/controller/new_base/render_html_test.rb index e9ea57e329..8019aa1eb5 100644 --- a/actionpack/test/controller/new_base/render_html_test.rb +++ b/actionpack/test/controller/new_base/render_html_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module RenderHtml class MinimalController < ActionController::Metal @@ -88,7 +88,7 @@ module RenderHtml test "rendering text from an action with default options renders the text with the layout" do with_routing do |set| - set.draw { get ':controller', action: 'index' } + set.draw { ActiveSupport::Deprecation.silence { get ":controller", action: "index" } } get "/render_html/simple" assert_body "hello david" @@ -98,7 +98,7 @@ module RenderHtml test "rendering text from an action with default options renders the text without the layout" do with_routing do |set| - set.draw { get ':controller', action: 'index' } + set.draw { ActiveSupport::Deprecation.silence { get ":controller", action: "index" } } get "/render_html/with_layout" diff --git a/actionpack/test/controller/new_base/render_implicit_action_test.rb b/actionpack/test/controller/new_base/render_implicit_action_test.rb index 5b4885f7e0..796283466a 100644 --- a/actionpack/test/controller/new_base/render_implicit_action_test.rb +++ b/actionpack/test/controller/new_base/render_implicit_action_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module RenderImplicitAction class SimpleController < ::ApplicationController @@ -6,7 +6,7 @@ module RenderImplicitAction "render_implicit_action/simple/hello_world.html.erb" => "Hello world!", "render_implicit_action/simple/hyphen-ated.html.erb" => "Hello hyphen-ated!", "render_implicit_action/simple/not_implemented.html.erb" => "Not Implemented" - ), ActionView::FileSystemResolver.new(File.expand_path('../../../controller', __FILE__))] + ), ActionView::FileSystemResolver.new(File.expand_path("../../../controller", __FILE__))] def hello_world() end end diff --git a/actionpack/test/controller/new_base/render_layout_test.rb b/actionpack/test/controller/new_base/render_layout_test.rb index 7ab3777026..0a3809560e 100644 --- a/actionpack/test/controller/new_base/render_layout_test.rb +++ b/actionpack/test/controller/new_base/render_layout_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module ControllerLayouts class ImplicitController < ::ApplicationController @@ -10,15 +10,15 @@ module ControllerLayouts )] def index - render :template => "basic" + render template: "basic" end def override - render :template => "basic", :layout => "override" + render template: "basic", layout: "override" end def layout_false - render :layout => false + render layout: false end def builder_override @@ -32,7 +32,7 @@ module ControllerLayouts )] def index - render :template => "basic" + render template: "basic" end end @@ -55,7 +55,6 @@ module ControllerLayouts get "/controller_layouts/implicit/override" assert_body "Override! Hello world!" end - end class LayoutOptionsTest < Rack::TestCase @@ -76,7 +75,7 @@ module ControllerLayouts )] def explicit - render :layout => "application" + render layout: "application" end end diff --git a/actionpack/test/controller/new_base/render_partial_test.rb b/actionpack/test/controller/new_base/render_partial_test.rb index 9e5022c9f4..4511826978 100644 --- a/actionpack/test/controller/new_base/render_partial_test.rb +++ b/actionpack/test/controller/new_base/render_partial_test.rb @@ -1,9 +1,7 @@ -require 'abstract_unit' +require "abstract_unit" module RenderPartial - class BasicController < ActionController::Base - self.view_paths = [ActionView::FixtureResolver.new( "render_partial/basic/_basic.html.erb" => "BasicPartial!", "render_partial/basic/basic.html.erb" => "<%= @test_unchanged = 'goodbye' %><%= render :partial => 'basic' %><%= @test_unchanged %>", @@ -16,16 +14,16 @@ module RenderPartial )] def html_with_json_inside_json - render :action => "with_json" + render action: "with_json" end def changing - @test_unchanged = 'hello' - render :action => "basic" + @test_unchanged = "hello" + render action: "basic" end def overridden - @test_unchanged = 'hello' + @test_unchanged = "hello" end end @@ -59,5 +57,4 @@ module RenderPartial assert_response("goodbyeOverriddenPartial!goodbye") end end - end diff --git a/actionpack/test/controller/new_base/render_plain_test.rb b/actionpack/test/controller/new_base/render_plain_test.rb index 0881442bd0..44be8dd380 100644 --- a/actionpack/test/controller/new_base/render_plain_test.rb +++ b/actionpack/test/controller/new_base/render_plain_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module RenderPlain class MinimalController < ActionController::Metal @@ -80,7 +80,7 @@ module RenderPlain test "rendering text from an action with default options renders the text with the layout" do with_routing do |set| - set.draw { get ':controller', action: 'index' } + set.draw { ActiveSupport::Deprecation.silence { get ":controller", action: "index" } } get "/render_plain/simple" assert_body "hello david" @@ -90,7 +90,7 @@ module RenderPlain test "rendering text from an action with default options renders the text without the layout" do with_routing do |set| - set.draw { get ':controller', action: 'index' } + set.draw { ActiveSupport::Deprecation.silence { get ":controller", action: "index" } } get "/render_plain/with_layout" diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb index 9ea056194a..5cd8f82323 100644 --- a/actionpack/test/controller/new_base/render_streaming_test.rb +++ b/actionpack/test/controller/new_base/render_streaming_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module RenderStreaming class BasicController < ActionController::Base @@ -12,32 +12,32 @@ module RenderStreaming layout "application" def hello_world - render :stream => true + render stream: true end def layout_exception - render :action => "hello_world", :stream => true, :layout => "boom" + render action: "hello_world", stream: true, layout: "boom" end def template_exception - render :action => "boom", :stream => true + render action: "boom", stream: true end def skip - render :action => "hello_world", :stream => false + render action: "hello_world", stream: false end def explicit - render :action => "hello_world", :stream => true + render action: "hello_world", stream: true end def no_layout - render :action => "hello_world", :stream => true, :layout => false + render action: "hello_world", stream: true, layout: false end def explicit_cache headers["Cache-Control"] = "private" - render :action => "hello_world", :stream => true + render action: "hello_world", stream: true end end diff --git a/actionpack/test/controller/new_base/render_template_test.rb b/actionpack/test/controller/new_base/render_template_test.rb index b06ce5db40..1102305f3e 100644 --- a/actionpack/test/controller/new_base/render_template_test.rb +++ b/actionpack/test/controller/new_base/render_template_test.rb @@ -1,8 +1,7 @@ -require 'abstract_unit' +require "abstract_unit" module RenderTemplate class WithoutLayoutController < ActionController::Base - self.view_paths = [ActionView::FixtureResolver.new( "test/basic.html.erb" => "Hello from basic.html.erb", "shared.html.erb" => "Elastica", @@ -18,11 +17,11 @@ module RenderTemplate )] def index - render :template => "test/basic" + render template: "test/basic" end def html_with_json_inside_json - render :template => "test/with_json" + render template: "test/with_json" end def index_without_key @@ -30,46 +29,46 @@ module RenderTemplate end def in_top_directory - render :template => 'shared' + render template: "shared" end def in_top_directory_with_slash - render :template => '/shared' + render template: "/shared" end def in_top_directory_with_slash_without_key - render '/shared' + render "/shared" end def with_locals - render :template => "locals", :locals => { :secret => 'area51' } + render template: "locals", locals: { secret: "area51" } end def with_locals_without_key - render "locals", :locals => { :secret => 'area51' } + render "locals", locals: { secret: "area51" } end def builder_template - render :template => "xml_template" + render template: "xml_template" end def with_raw - render :template => "with_raw" + render template: "with_raw" end def with_implicit_raw - render :template => "with_implicit_raw" + render template: "with_implicit_raw" end def with_error - render :template => "test/with_error" + render template: "test/with_error" end private - def show_detailed_exceptions? - request.local? - end + def show_detailed_exceptions? + request.local? + end end class TestWithoutLayout < Rack::TestCase @@ -126,7 +125,7 @@ module RenderTemplate assert_body "Hello <strong>this is also raw</strong> in an html template" assert_status 200 - get :with_implicit_raw, params: { format: 'text' } + get :with_implicit_raw, params: { format: "text" } assert_body "Hello <strong>this is also raw</strong> in a text template" assert_status 200 @@ -154,30 +153,30 @@ module RenderTemplate )] def index - render :template => "test/basic" + render template: "test/basic" end def with_layout - render :template => "test/basic", :layout => true + render template: "test/basic", layout: true end def with_layout_false - render :template => "test/basic", :layout => false + render template: "test/basic", layout: false end def with_layout_nil - render :template => "test/basic", :layout => nil + render template: "test/basic", layout: nil end def with_custom_layout - render :template => "test/basic", :layout => "greetings" + render template: "test/basic", layout: "greetings" end end class TestWithLayout < Rack::TestCase test "rendering with implicit layout" do with_routing do |set| - set.draw { get ':controller', :action => :index } + set.draw { ActiveSupport::Deprecation.silence { get ":controller", action: :index } } get "/render_template/with_layout" @@ -223,7 +222,7 @@ module RenderTemplate )] def with_forward_slash - render :template => "/test/basic" + render template: "/test/basic" end end diff --git a/actionpack/test/controller/new_base/render_test.rb b/actionpack/test/controller/new_base/render_test.rb index 963f2c2f5c..cea3f9b5fd 100644 --- a/actionpack/test/controller/new_base/render_test.rb +++ b/actionpack/test/controller/new_base/render_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module Render class BlankRenderController < ActionController::Base @@ -18,11 +18,11 @@ module Render end def access_request - render :action => "access_request" + render action: "access_request" end def render_action_name - render :action => "access_action_name" + render action: "access_action_name" end def overridden_with_own_view_paths_appended @@ -36,9 +36,9 @@ module Render private - def secretz - render plain: "FAIL WHALE!" - end + def secretz + render plain: "FAIL WHALE!" + end end class DoubleRenderController < ActionController::Base @@ -57,7 +57,9 @@ module Render test "render with blank" do with_routing do |set| set.draw do - get ":controller", :action => 'index' + ActiveSupport::Deprecation.silence do + get ":controller", action: "index" + end end get "/render/blank_render" @@ -70,7 +72,9 @@ module Render test "rendering more than once raises an exception" do with_routing do |set| set.draw do - get ":controller", :action => 'index' + ActiveSupport::Deprecation.silence do + get ":controller", action: "index" + end end assert_raises(AbstractController::DoubleRenderError) do diff --git a/actionpack/test/controller/new_base/render_text_test.rb b/actionpack/test/controller/new_base/render_text_test.rb deleted file mode 100644 index 048458178c..0000000000 --- a/actionpack/test/controller/new_base/render_text_test.rb +++ /dev/null @@ -1,188 +0,0 @@ -require 'abstract_unit' - -module RenderText - class MinimalController < ActionController::Metal - include AbstractController::Rendering - include ActionController::Rendering - - def index - render text: "Hello World!" - end - end - - class SimpleController < ActionController::Base - self.view_paths = [ActionView::FixtureResolver.new] - - def index - render text: "hello david" - end - end - - class WithLayoutController < ::ApplicationController - self.view_paths = [ActionView::FixtureResolver.new( - "layouts/application.html.erb" => "<%= yield %>, I'm here!", - "layouts/greetings.html.erb" => "<%= yield %>, I wish thee well.", - "layouts/ivar.html.erb" => "<%= yield %>, <%= @ivar %>" - )] - - def index - render text: "hello david" - end - - def custom_code - render text: "hello world", status: 404 - end - - def with_custom_code_as_string - render text: "hello world", status: "404 Not Found" - end - - def with_nil - render text: nil - end - - def with_nil_and_status - render text: nil, status: 403 - end - - def with_false - render text: false - end - - def with_layout_true - render text: "hello world", layout: true - end - - def with_layout_false - render text: "hello world", layout: false - end - - def with_layout_nil - render text: "hello world", layout: nil - end - - def with_custom_layout - render text: "hello world", layout: "greetings" - end - - def with_ivar_in_layout - @ivar = "hello world" - render text: "hello world", layout: "ivar" - end - end - - class RenderTextTest < Rack::TestCase - test "rendering text from a minimal controller" do - ActiveSupport::Deprecation.silence do - get "/render_text/minimal/index" - end - - assert_body "Hello World!" - assert_status 200 - end - - test "rendering text from an action with default options renders the text with the layout" do - with_routing do |set| - set.draw { get ':controller', action: 'index' } - - ActiveSupport::Deprecation.silence do - get "/render_text/simple" - end - - assert_body "hello david" - assert_status 200 - end - end - - test "rendering text from an action with default options renders the text without the layout" do - with_routing do |set| - set.draw { get ':controller', action: 'index' } - - ActiveSupport::Deprecation.silence do - get "/render_text/with_layout" - end - - assert_body "hello david" - assert_status 200 - end - end - - test "rendering text, while also providing a custom status code" do - ActiveSupport::Deprecation.silence do - get "/render_text/with_layout/custom_code" - end - - assert_body "hello world" - assert_status 404 - end - - test "rendering text with nil returns an empty body" do - ActiveSupport::Deprecation.silence do - get "/render_text/with_layout/with_nil" - end - - assert_body "" - assert_status 200 - end - - test "Rendering text with nil and custom status code returns an empty body and the status" do - ActiveSupport::Deprecation.silence do - get "/render_text/with_layout/with_nil_and_status" - end - - assert_body "" - assert_status 403 - end - - test "rendering text with false returns the string 'false'" do - ActiveSupport::Deprecation.silence do - get "/render_text/with_layout/with_false" - end - - assert_body "false" - assert_status 200 - end - - test "rendering text with layout: true" do - ActiveSupport::Deprecation.silence do - get "/render_text/with_layout/with_layout_true" - end - - assert_body "hello world, I'm here!" - assert_status 200 - end - - test "rendering text with layout: 'greetings'" do - ActiveSupport::Deprecation.silence do - get "/render_text/with_layout/with_custom_layout" - end - - assert_body "hello world, I wish thee well." - assert_status 200 - end - - test "rendering text with layout: false" do - ActiveSupport::Deprecation.silence do - get "/render_text/with_layout/with_layout_false" - end - - assert_body "hello world" - assert_status 200 - end - - test "rendering text with layout: nil" do - ActiveSupport::Deprecation.silence do - get "/render_text/with_layout/with_layout_nil" - end - - assert_body "hello world" - assert_status 200 - end - - test "rendering text displays deprecation warning" do - assert_deprecated do - get "/render_text/with_layout/with_layout_nil" - end - end - end -end diff --git a/actionpack/test/controller/new_base/render_xml_test.rb b/actionpack/test/controller/new_base/render_xml_test.rb index b8527a943d..8bab413377 100644 --- a/actionpack/test/controller/new_base/render_xml_test.rb +++ b/actionpack/test/controller/new_base/render_xml_test.rb @@ -1,7 +1,6 @@ -require 'abstract_unit' +require "abstract_unit" module RenderXml - # This has no layout and it works class BasicController < ActionController::Base self.view_paths = [ActionView::FixtureResolver.new( diff --git a/actionpack/test/controller/output_escaping_test.rb b/actionpack/test/controller/output_escaping_test.rb index c3c549fbfc..c7047d95ae 100644 --- a/actionpack/test/controller/output_escaping_test.rb +++ b/actionpack/test/controller/output_escaping_test.rb @@ -1,7 +1,6 @@ -require 'abstract_unit' +require "abstract_unit" class OutputEscapingTest < ActiveSupport::TestCase - test "escape_html shouldn't die when passed nil" do assert ERB::Util.h(nil).blank? end @@ -13,5 +12,4 @@ class OutputEscapingTest < ActiveSupport::TestCase test "escapeHTML shouldn't touch explicitly safe strings" do assert_equal "<", ERB::Util.h("<".html_safe) end - end diff --git a/actionpack/test/controller/parameter_encoding_test.rb b/actionpack/test/controller/parameter_encoding_test.rb new file mode 100644 index 0000000000..7840b4f5c4 --- /dev/null +++ b/actionpack/test/controller/parameter_encoding_test.rb @@ -0,0 +1,73 @@ +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 + + def test_foo + render body: params[:foo].encoding + end + + def test_bar + 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 + end +end + +class ParameterEncodingTest < ActionController::TestCase + tests ParameterEncodingController + + test "properly transcodes UTF8 parameters into declared encodings" do + post :test_foo, params: { "foo" => "foo", "bar" => "bar", "baz" => "baz" } + + assert_response :success + assert_equal "UTF-8", @response.body + end + + test "properly transcodes ASCII_8BIT parameters into declared encodings" 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" } + + 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 + end + + test "does not raise an error when passed a param declared as ASCII-8BIT that contains invalid bytes" do + get :test_bar, params: { "bar" => URI.parser.escape("bar\xE2baz".b) } + + assert_response :success + assert_equal "ASCII-8BIT", @response.body + end +end diff --git a/actionpack/test/controller/parameters/accessors_test.rb b/actionpack/test/controller/parameters/accessors_test.rb index bd43ff7697..2893eb7b91 100644 --- a/actionpack/test/controller/parameters/accessors_test.rb +++ b/actionpack/test/controller/parameters/accessors_test.rb @@ -1,17 +1,19 @@ -require 'abstract_unit' -require 'action_controller/metal/strong_parameters' -require 'active_support/core_ext/hash/transform_values' +require "abstract_unit" +require "action_controller/metal/strong_parameters" +require "active_support/core_ext/hash/transform_values" class ParametersAccessorsTest < ActiveSupport::TestCase setup do + ActionController::Parameters.permit_all_parameters = false + @params = ActionController::Parameters.new( person: { - age: '32', + age: "32", name: { - first: 'David', - last: 'Heinemeier Hansson' + first: "David", + last: "Heinemeier Hansson" }, - addresses: [{city: 'Chicago', state: 'Illinois'}] + addresses: [{ city: "Chicago", state: "Illinois" }] } ) end @@ -129,9 +131,79 @@ class ParametersAccessorsTest < ActiveSupport::TestCase assert_not @params[:person].values_at(:name).first.permitted? end - test "equality with another hash works" do - hash1 = { foo: :bar } - params1 = ActionController::Parameters.new(hash1) - assert(params1 == hash1) + test "is equal to Parameters instance with same params" do + params1 = ActionController::Parameters.new(a: 1, b: 2) + params2 = ActionController::Parameters.new(a: 1, b: 2) + assert(params1 == params2) + end + + test "is equal to Parameters instance with same permitted params" do + params1 = ActionController::Parameters.new(a: 1, b: 2).permit(:a) + params2 = ActionController::Parameters.new(a: 1, b: 2).permit(:a) + assert(params1 == params2) + end + + test "is equal to Parameters instance with same different source params, but same permitted params" do + params1 = ActionController::Parameters.new(a: 1, b: 2).permit(:a) + params2 = ActionController::Parameters.new(a: 1, c: 3).permit(:a) + assert(params1 == params2) + assert(params2 == params1) + end + + test "is not equal to an unpermitted Parameters instance with same params" do + params1 = ActionController::Parameters.new(a: 1).permit(:a) + params2 = ActionController::Parameters.new(a: 1) + assert(params1 != params2) + assert(params2 != params1) + end + + test "is not equal to Parameters instance with different permitted params" do + params1 = ActionController::Parameters.new(a: 1, b: 2).permit(:a, :b) + params2 = ActionController::Parameters.new(a: 1, b: 2).permit(:a) + assert(params1 != params2) + assert(params2 != params1) + end + + test "equality with simple types works" do + assert(@params != "Hello") + assert(@params != 42) + assert(@params != false) + end + + test "inspect shows both class name, parameters and permitted flag" do + assert_equal( + '<ActionController::Parameters {"person"=>{"age"=>"32", '\ + '"name"=>{"first"=>"David", "last"=>"Heinemeier Hansson"}, ' \ + '"addresses"=>[{"city"=>"Chicago", "state"=>"Illinois"}]}} permitted: false>', + @params.inspect + ) + end + + test "inspect prints updated permitted flag in the output" do + assert_match(/permitted: false/, @params.inspect) + + @params.permit! + + assert_match(/permitted: true/, @params.inspect) + end + + if Hash.method_defined?(:dig) + test "#dig delegates the dig method to its values" do + assert_equal "David", @params.dig(:person, :name, :first) + assert_equal "Chicago", @params.dig(:person, :addresses, 0, :city) + end + + test "#dig converts hashes to parameters" do + assert_kind_of ActionController::Parameters, @params.dig(:person) + assert_kind_of ActionController::Parameters, @params.dig(:person, :addresses, 0) + assert @params.dig(:person, :addresses).all? do |value| + value.is_a?(ActionController::Parameters) + end + end + else + test "ActionController::Parameters does not respond to #dig on Ruby 2.2" do + assert_not ActionController::Parameters.method_defined?(:dig) + assert_not @params.respond_to?(:dig) + end end end diff --git a/actionpack/test/controller/parameters/always_permitted_parameters_test.rb b/actionpack/test/controller/parameters/always_permitted_parameters_test.rb index efaf8a96c3..cd7c98f112 100644 --- a/actionpack/test/controller/parameters/always_permitted_parameters_test.rb +++ b/actionpack/test/controller/parameters/always_permitted_parameters_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'action_controller/metal/strong_parameters' +require "abstract_unit" +require "action_controller/metal/strong_parameters" class AlwaysPermittedParametersTest < ActiveSupport::TestCase def setup @@ -12,12 +12,6 @@ class AlwaysPermittedParametersTest < ActiveSupport::TestCase ActionController::Parameters.always_permitted_parameters = %w( controller action ) end - test "shows deprecations warning on NEVER_UNPERMITTED_PARAMS" do - assert_deprecated do - ActionController::Parameters::NEVER_UNPERMITTED_PARAMS - end - end - test "returns super on missing constant other than NEVER_UNPERMITTED_PARAMS" do ActionController::Parameters.superclass.stub :const_missing, "super" do assert_equal "super", ActionController::Parameters::NON_EXISTING_CONSTANT @@ -25,10 +19,9 @@ class AlwaysPermittedParametersTest < ActiveSupport::TestCase end test "permits parameters that are whitelisted" do - params = ActionController::Parameters.new({ + params = ActionController::Parameters.new( book: { pages: 65 }, - format: "json" - }) + format: "json") permitted = params.permit book: [:pages] assert permitted.permitted? end diff --git a/actionpack/test/controller/parameters/dup_test.rb b/actionpack/test/controller/parameters/dup_test.rb new file mode 100644 index 0000000000..fb707a1354 --- /dev/null +++ b/actionpack/test/controller/parameters/dup_test.rb @@ -0,0 +1,65 @@ +require "abstract_unit" +require "action_controller/metal/strong_parameters" +require "active_support/core_ext/object/deep_dup" + +class ParametersDupTest < ActiveSupport::TestCase + setup do + ActionController::Parameters.permit_all_parameters = false + + @params = ActionController::Parameters.new( + person: { + age: "32", + name: { + first: "David", + last: "Heinemeier Hansson" + }, + addresses: [{ city: "Chicago", state: "Illinois" }] + } + ) + end + + test "a duplicate maintains the original's permitted status" do + @params.permit! + dupped_params = @params.dup + assert dupped_params.permitted? + end + + test "a duplicate maintains the original's parameters" do + @params.permit! + dupped_params = @params.dup + assert_equal @params.to_h, dupped_params.to_h + end + + test "changes to a duplicate's parameters do not affect the original" do + dupped_params = @params.dup + dupped_params.delete(:person) + assert_not_equal @params, dupped_params + end + + test "changes to a duplicate's permitted status do not affect the original" do + dupped_params = @params.dup + dupped_params.permit! + assert_not_equal @params, dupped_params + end + + test "deep_dup content" do + dupped_params = @params.deep_dup + dupped_params[:person][:age] = "45" + dupped_params[:person][:addresses].clear + + assert_not_equal @params[:person][:age], dupped_params[:person][:age] + assert_not_equal @params[:person][:addresses], dupped_params[:person][:addresses] + end + + test "deep_dup @permitted" do + dupped_params = @params.deep_dup + dupped_params.permit! + + assert_not @params.permitted? + end + + test "deep_dup @permitted is being copied" do + @params.permit! + assert @params.deep_dup.permitted? + end +end diff --git a/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb b/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb index 9ce04b9aeb..c800c1d3df 100644 --- a/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb +++ b/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'action_controller/metal/strong_parameters' +require "abstract_unit" +require "action_controller/metal/strong_parameters" class LogOnUnpermittedParamsTest < ActiveSupport::TestCase def setup @@ -11,62 +11,58 @@ class LogOnUnpermittedParamsTest < ActiveSupport::TestCase end test "logs on unexpected param" do - params = ActionController::Parameters.new({ + params = ActionController::Parameters.new( book: { pages: 65 }, - fishing: "Turnips" - }) + fishing: "Turnips") - assert_logged("Unpermitted parameter: fishing") do + assert_logged("Unpermitted parameter: :fishing") do params.permit(book: [:pages]) end end test "logs on unexpected params" do - params = ActionController::Parameters.new({ + params = ActionController::Parameters.new( book: { pages: 65 }, fishing: "Turnips", - car: "Mersedes" - }) + car: "Mersedes") - assert_logged("Unpermitted parameters: fishing, car") do + assert_logged("Unpermitted parameters: :fishing, :car") do params.permit(book: [:pages]) end end test "logs on unexpected nested param" do - params = ActionController::Parameters.new({ - book: { pages: 65, title: "Green Cats and where to find then." } - }) + params = ActionController::Parameters.new( + book: { pages: 65, title: "Green Cats and where to find then." }) - assert_logged("Unpermitted parameter: title") do + assert_logged("Unpermitted parameter: :title") do params.permit(book: [:pages]) end end test "logs on unexpected nested params" do - params = ActionController::Parameters.new({ - book: { pages: 65, title: "Green Cats and where to find then.", author: "G. A. Dog" } - }) + params = ActionController::Parameters.new( + book: { pages: 65, title: "Green Cats and where to find then.", author: "G. A. Dog" }) - assert_logged("Unpermitted parameters: title, author") do + assert_logged("Unpermitted parameters: :title, :author") do params.permit(book: [:pages]) end end private - def assert_logged(message) - old_logger = ActionController::Base.logger - log = StringIO.new - ActionController::Base.logger = Logger.new(log) + def assert_logged(message) + old_logger = ActionController::Base.logger + log = StringIO.new + ActionController::Base.logger = Logger.new(log) - begin - yield + begin + yield - log.rewind - assert_match message, log.read - ensure - ActionController::Base.logger = old_logger + log.rewind + assert_match message, log.read + ensure + ActionController::Base.logger = old_logger + end end - end end diff --git a/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb b/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb index 15338059bc..88fb477c10 100644 --- a/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb +++ b/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb @@ -1,9 +1,9 @@ -require 'abstract_unit' -require 'action_controller/metal/strong_parameters' +require "abstract_unit" +require "action_controller/metal/strong_parameters" class MultiParameterAttributesTest < ActiveSupport::TestCase test "permitted multi-parameter attribute keys" do - params = ActionController::Parameters.new({ + params = ActionController::Parameters.new( book: { "shipped_at(1i)" => "2012", "shipped_at(2i)" => "3", @@ -15,8 +15,7 @@ class MultiParameterAttributesTest < ActiveSupport::TestCase "published_at(3i)" => "5", "price(1)" => "R$", "price(2f)" => "2.02" - } - }) + }) permitted = params.permit book: [ :shipped_at, :price ] diff --git a/actionpack/test/controller/parameters/mutators_test.rb b/actionpack/test/controller/parameters/mutators_test.rb index 744d8664be..e060e5180f 100644 --- a/actionpack/test/controller/parameters/mutators_test.rb +++ b/actionpack/test/controller/parameters/mutators_test.rb @@ -1,17 +1,17 @@ -require 'abstract_unit' -require 'action_controller/metal/strong_parameters' -require 'active_support/core_ext/hash/transform_values' +require "abstract_unit" +require "action_controller/metal/strong_parameters" +require "active_support/core_ext/hash/transform_values" class ParametersMutatorsTest < ActiveSupport::TestCase setup do @params = ActionController::Parameters.new( person: { - age: '32', + age: "32", name: { - first: 'David', - last: 'Heinemeier Hansson' + first: "David", + last: "Heinemeier Hansson" }, - addresses: [{city: 'Chicago', state: 'Illinois'}] + addresses: [{ city: "Chicago", state: "Illinois" }] } ) end diff --git a/actionpack/test/controller/parameters/nested_parameters_test.rb b/actionpack/test/controller/parameters/nested_parameters_permit_test.rb index 7151a8567c..5f86901e30 100644 --- a/actionpack/test/controller/parameters/nested_parameters_test.rb +++ b/actionpack/test/controller/parameters/nested_parameters_permit_test.rb @@ -1,13 +1,13 @@ -require 'abstract_unit' -require 'action_controller/metal/strong_parameters' +require "abstract_unit" +require "action_controller/metal/strong_parameters" -class NestedParametersTest < ActiveSupport::TestCase +class NestedParametersPermitTest < ActiveSupport::TestCase def assert_filtered_out(params, key) assert !params.has_key?(key), "key #{key.inspect} has not been filtered out" end test "permitted nested parameters" do - params = ActionController::Parameters.new({ + params = ActionController::Parameters.new( book: { title: "Romeo and Juliet", authors: [{ @@ -23,11 +23,10 @@ class NestedParametersTest < ActiveSupport::TestCase genre: "Tragedy" }, id: { - isbn: 'x' + isbn: "x" } }, - magazine: "Mjallo!" - }) + magazine: "Mjallo!") permitted = params.permit book: [ :title, { authors: [ :name ] }, { details: :pages }, :id ] @@ -45,74 +44,69 @@ class NestedParametersTest < ActiveSupport::TestCase end test "permitted nested parameters with a string or a symbol as a key" do - params = ActionController::Parameters.new({ + params = ActionController::Parameters.new( book: { - 'authors' => [ - { name: 'William Shakespeare', born: '1564-04-26' }, - { name: 'Christopher Marlowe' } + "authors" => [ + { name: "William Shakespeare", born: "1564-04-26" }, + { name: "Christopher Marlowe" } ] - } - }) + }) - permitted = params.permit book: [ { 'authors' => [ :name ] } ] + permitted = params.permit book: [ { "authors" => [ :name ] } ] - assert_equal 'William Shakespeare', permitted[:book]['authors'][0][:name] - assert_equal 'William Shakespeare', permitted[:book][:authors][0][:name] - assert_equal 'Christopher Marlowe', permitted[:book]['authors'][1][:name] - assert_equal 'Christopher Marlowe', permitted[:book][:authors][1][:name] + assert_equal "William Shakespeare", permitted[:book]["authors"][0][:name] + assert_equal "William Shakespeare", permitted[:book][:authors][0][:name] + assert_equal "Christopher Marlowe", permitted[:book]["authors"][1][:name] + assert_equal "Christopher Marlowe", permitted[:book][:authors][1][:name] permitted = params.permit book: [ { authors: [ :name ] } ] - assert_equal 'William Shakespeare', permitted[:book]['authors'][0][:name] - assert_equal 'William Shakespeare', permitted[:book][:authors][0][:name] - assert_equal 'Christopher Marlowe', permitted[:book]['authors'][1][:name] - assert_equal 'Christopher Marlowe', permitted[:book][:authors][1][:name] + assert_equal "William Shakespeare", permitted[:book]["authors"][0][:name] + assert_equal "William Shakespeare", permitted[:book][:authors][0][:name] + assert_equal "Christopher Marlowe", permitted[:book]["authors"][1][:name] + assert_equal "Christopher Marlowe", permitted[:book][:authors][1][:name] end test "nested arrays with strings" do - params = ActionController::Parameters.new({ + params = ActionController::Parameters.new( book: { genres: ["Tragedy"] - } - }) + }) - permitted = params.permit book: {genres: []} + permitted = params.permit book: { genres: [] } assert_equal ["Tragedy"], permitted[:book][:genres] end test "permit may specify symbols or strings" do - params = ActionController::Parameters.new({ + params = ActionController::Parameters.new( book: { title: "Romeo and Juliet", author: "William Shakespeare" }, - magazine: "Shakespeare Today" - }) + magazine: "Shakespeare Today") - permitted = params.permit({book: ["title", :author]}, "magazine") + permitted = params.permit({ book: ["title", :author] }, "magazine") assert_equal "Romeo and Juliet", permitted[:book][:title] assert_equal "William Shakespeare", permitted[:book][:author] assert_equal "Shakespeare Today", permitted[:magazine] end test "nested array with strings that should be hashes" do - params = ActionController::Parameters.new({ + params = ActionController::Parameters.new( book: { genres: ["Tragedy"] - } - }) + }) permitted = params.permit book: { genres: :type } assert_empty permitted[:book][:genres] end test "nested array with strings that should be hashes and additional values" do - params = ActionController::Parameters.new({ + params = ActionController::Parameters.new( book: { title: "Romeo and Juliet", genres: ["Tragedy"] - } - }) + }) permitted = params.permit book: [ :title, { genres: :type } ] assert_equal "Romeo and Juliet", permitted[:book][:title] @@ -120,66 +114,62 @@ class NestedParametersTest < ActiveSupport::TestCase end test "nested string that should be a hash" do - params = ActionController::Parameters.new({ + params = ActionController::Parameters.new( book: { genre: "Tragedy" - } - }) + }) permitted = params.permit book: { genre: :type } assert_nil permitted[:book][:genre] end test "fields_for-style nested params" do - params = ActionController::Parameters.new({ + params = ActionController::Parameters.new( book: { authors_attributes: { - :'0' => { name: 'William Shakespeare', age_of_death: '52' }, - :'1' => { name: 'Unattributed Assistant' }, - :'2' => { name: %w(injected names) } + '0': { name: "William Shakespeare", age_of_death: "52" }, + '1': { name: "Unattributed Assistant" }, + '2': { name: %w(injected names) } } - } - }) + }) permitted = params.permit book: { authors_attributes: [ :name ] } - assert_not_nil permitted[:book][:authors_attributes]['0'] - assert_not_nil permitted[:book][:authors_attributes]['1'] - assert_empty permitted[:book][:authors_attributes]['2'] - assert_equal 'William Shakespeare', permitted[:book][:authors_attributes]['0'][:name] - assert_equal 'Unattributed Assistant', permitted[:book][:authors_attributes]['1'][:name] + assert_not_nil permitted[:book][:authors_attributes]["0"] + assert_not_nil permitted[:book][:authors_attributes]["1"] + assert_empty permitted[:book][:authors_attributes]["2"] + assert_equal "William Shakespeare", permitted[:book][:authors_attributes]["0"][:name] + assert_equal "Unattributed Assistant", permitted[:book][:authors_attributes]["1"][:name] - assert_filtered_out permitted[:book][:authors_attributes]['0'], :age_of_death + assert_filtered_out permitted[:book][:authors_attributes]["0"], :age_of_death end test "fields_for-style nested params with negative numbers" do - params = ActionController::Parameters.new({ + params = ActionController::Parameters.new( book: { authors_attributes: { - :'-1' => { name: 'William Shakespeare', age_of_death: '52' }, - :'-2' => { name: 'Unattributed Assistant' } + '-1': { name: "William Shakespeare", age_of_death: "52" }, + '-2': { name: "Unattributed Assistant" } } - } - }) + }) permitted = params.permit book: { authors_attributes: [:name] } - assert_not_nil permitted[:book][:authors_attributes]['-1'] - assert_not_nil permitted[:book][:authors_attributes]['-2'] - assert_equal 'William Shakespeare', permitted[:book][:authors_attributes]['-1'][:name] - assert_equal 'Unattributed Assistant', permitted[:book][:authors_attributes]['-2'][:name] + assert_not_nil permitted[:book][:authors_attributes]["-1"] + assert_not_nil permitted[:book][:authors_attributes]["-2"] + assert_equal "William Shakespeare", permitted[:book][:authors_attributes]["-1"][:name] + assert_equal "Unattributed Assistant", permitted[:book][:authors_attributes]["-2"][:name] - assert_filtered_out permitted[:book][:authors_attributes]['-1'], :age_of_death + assert_filtered_out permitted[:book][:authors_attributes]["-1"], :age_of_death end test "nested number as key" do - params = ActionController::Parameters.new({ + params = ActionController::Parameters.new( product: { properties: { - '0' => "prop0", - '1' => "prop1" + "0" => "prop0", + "1" => "prop1" } - } - }) - params = params.require(:product).permit(:properties => ["0"]) + }) + params = params.require(:product).permit(properties: ["0"]) assert_not_nil params[:properties]["0"] assert_nil params[:properties]["1"] assert_equal "prop0", params[:properties]["0"] diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb index 3299f2d9d0..728d8e1279 100644 --- a/actionpack/test/controller/parameters/parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/parameters_permit_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'action_dispatch/http/upload' -require 'action_controller/metal/strong_parameters' +require "abstract_unit" +require "action_dispatch/http/upload" +require "action_controller/metal/strong_parameters" class ParametersPermitTest < ActiveSupport::TestCase def assert_filtered_out(params, key) @@ -10,32 +10,53 @@ class ParametersPermitTest < ActiveSupport::TestCase setup do @params = ActionController::Parameters.new( person: { - age: '32', + age: "32", name: { - first: 'David', - last: 'Heinemeier Hansson' + first: "David", + last: "Heinemeier Hansson" }, - addresses: [{city: 'Chicago', state: 'Illinois'}] + addresses: [{ city: "Chicago", state: "Illinois" }] } ) @struct_fields = [] %w(0 1 12).each do |number| - ['', 'i', 'f'].each do |suffix| + ["", "i", "f"].each do |suffix| @struct_fields << "sf(#{number}#{suffix})" end end end - test 'if nothing is permitted, the hash becomes empty' do - params = ActionController::Parameters.new(id: '1234') + def walk_permitted(params) + params.each do |k,v| + case v + when ActionController::Parameters + walk_permitted v + when Array + v.each { |x| walk_permitted v } + end + end + end + + test "iteration should not impact permit" do + hash = { "foo"=>{ "bar"=>{ "0"=>{ "baz"=>"hello", "zot"=>"1" } } } } + params = ActionController::Parameters.new(hash) + + walk_permitted params + + sanitized = params[:foo].permit(bar: [:baz]) + assert_equal({ "0"=>{ "baz"=>"hello" } }, sanitized[:bar].to_unsafe_h) + end + + test "if nothing is permitted, the hash becomes empty" do + params = ActionController::Parameters.new(id: "1234") permitted = params.permit assert permitted.permitted? assert permitted.empty? end - test 'key: permitted scalar values' do - values = ['a', :a, nil] + test "key: permitted scalar values" do + values = ["a", :a, nil] values += [0, 1.0, 2**128, BigDecimal.new(1)] values += [true, false] values += [Date.today, Time.now, DateTime.now] @@ -55,15 +76,15 @@ class ParametersPermitTest < ActiveSupport::TestCase end end - test 'key: unknown keys are filtered out' do - params = ActionController::Parameters.new(id: '1234', injected: 'injected') + test "key: unknown keys are filtered out" do + params = ActionController::Parameters.new(id: "1234", injected: "injected") permitted = params.permit(:id) - assert_equal '1234', permitted[:id] + assert_equal "1234", permitted[:id] assert_filtered_out permitted, :injected end - test 'key: arrays are filtered out' do - [[], [1], ['1']].each do |array| + test "key: arrays are filtered out" do + [[], [1], ["1"]].each do |array| params = ActionController::Parameters.new(id: array) permitted = params.permit(:id) assert_filtered_out permitted, :id @@ -76,8 +97,8 @@ class ParametersPermitTest < ActiveSupport::TestCase end end - test 'key: hashes are filtered out' do - [{}, {foo: 1}, {foo: 'bar'}].each do |hash| + test "key: hashes are filtered out" do + [{}, { foo: 1 }, { foo: "bar" }].each do |hash| params = ActionController::Parameters.new(id: hash) permitted = params.permit(:id) assert_filtered_out permitted, :id @@ -90,7 +111,7 @@ class ParametersPermitTest < ActiveSupport::TestCase end end - test 'key: non-permitted scalar values are filtered out' do + test "key: non-permitted scalar values are filtered out" do params = ActionController::Parameters.new(id: Object.new) permitted = params.permit(:id) assert_filtered_out permitted, :id @@ -102,19 +123,19 @@ class ParametersPermitTest < ActiveSupport::TestCase end end - test 'key: it is not assigned if not present in params' do - params = ActionController::Parameters.new(name: 'Joe') + test "key: it is not assigned if not present in params" do + params = ActionController::Parameters.new(name: "Joe") permitted = params.permit(:id) assert !permitted.has_key?(:id) end - test 'key to empty array: empty arrays pass' do + test "key to empty array: empty arrays pass" do params = ActionController::Parameters.new(id: []) permitted = params.permit(id: []) assert_equal [], permitted[:id] end - test 'do not break params filtering on nil values' do + test "do not break params filtering on nil values" do params = ActionController::Parameters.new(a: 1, b: [1, 2, 3], c: nil) permitted = params.permit(:a, c: [], b: []) @@ -123,24 +144,24 @@ class ParametersPermitTest < ActiveSupport::TestCase assert_equal nil, permitted[:c] end - test 'key to empty array: arrays of permitted scalars pass' do - [['foo'], [1], ['foo', 'bar'], [1, 2, 3]].each do |array| + test "key to empty array: arrays of permitted scalars pass" do + [["foo"], [1], ["foo", "bar"], [1, 2, 3]].each do |array| params = ActionController::Parameters.new(id: array) permitted = params.permit(id: []) assert_equal array, permitted[:id] end end - test 'key to empty array: permitted scalar values do not pass' do - ['foo', 1].each do |permitted_scalar| + test "key to empty array: permitted scalar values do not pass" do + ["foo", 1].each do |permitted_scalar| params = ActionController::Parameters.new(id: permitted_scalar) permitted = params.permit(id: []) assert_filtered_out permitted, :id end end - test 'key to empty array: arrays of non-permitted scalar do not pass' do - [[Object.new], [[]], [[1]], [{}], [{id: '1'}]].each do |non_permitted_scalar| + test "key to empty array: arrays of non-permitted scalar do not pass" do + [[Object.new], [[]], [[1]], [{}], [{ id: "1" }]].each do |non_permitted_scalar| params = ActionController::Parameters.new(id: non_permitted_scalar) permitted = params.permit(id: []) assert_filtered_out permitted, :id @@ -160,7 +181,7 @@ class ParametersPermitTest < ActiveSupport::TestCase assert_equal nil, params[:foo] end - test 'hashes in array values get wrapped' do + test "hashes in array values get wrapped" do params = ActionController::Parameters.new(foo: [{}, {}]) params[:foo].each do |hash| assert !hash.permitted? @@ -170,7 +191,7 @@ class ParametersPermitTest < ActiveSupport::TestCase # Strong params has an optimization to avoid looping every time you read # a key whose value is an array and building a new object. We check that # optimization here. - test 'arrays are converted at most once' do + test "arrays are converted at most once" do params = ActionController::Parameters.new(foo: [{}]) assert_same params[:foo], params[:foo] end @@ -181,11 +202,11 @@ class ParametersPermitTest < ActiveSupport::TestCase # This test checks that if we push a hash to an array (in-place modification) # the cache does not get fooled, the hash is still wrapped as strong params, # and not permitted. - test 'mutated arrays are detected' do - params = ActionController::Parameters.new(users: [{id: 1}]) + test "mutated arrays are detected" do + params = ActionController::Parameters.new(users: [{ id: 1 }]) permitted = params.permit(users: [:id]) - permitted[:users] << {injected: 1} + permitted[:users] << { injected: 1 } assert_not permitted[:users].last.permitted? end @@ -199,7 +220,7 @@ class ParametersPermitTest < ActiveSupport::TestCase assert_equal nil, @params.fetch(:foo) { nil } end - test 'KeyError in fetch block should not be covered up' do + test "KeyError in fetch block should not be covered up" do params = ActionController::Parameters.new e = assert_raises(KeyError) do params.fetch(:missing_key) { {}.fetch(:also_missing) } @@ -216,6 +237,13 @@ class ParametersPermitTest < ActiveSupport::TestCase assert @params.merge(a: "b").permitted? end + test "merge with parameters" do + other_params = ActionController::Parameters.new(id: "1234").permit! + merged_params = @params.merge(other_params) + + assert merged_params[:id] + end + test "modifying the parameters" do @params[:person][:hometown] = "Chicago" @params[:person][:family] = { brother: "Jonas" } @@ -224,11 +252,6 @@ class ParametersPermitTest < ActiveSupport::TestCase assert_equal "Jonas", @params[:person][:family][:brother] end - test "permit state is kept on a dup" do - @params.permit! - assert_equal @params.permitted?, @params.dup.permitted? - end - test "permit is recursive" do @params.permit! assert @params.permitted? @@ -240,9 +263,9 @@ class ParametersPermitTest < ActiveSupport::TestCase test "permitted takes a default value when Parameters.permit_all_parameters is set" do begin ActionController::Parameters.permit_all_parameters = true - params = ActionController::Parameters.new({ person: { + params = ActionController::Parameters.new(person: { age: "32", name: { first: "David", last: "Heinemeier Hansson" } - }}) + }) assert params.slice(:person).permitted? assert params[:person][:name].permitted? @@ -299,10 +322,10 @@ class ParametersPermitTest < ActiveSupport::TestCase end test "to_unsafe_h returns unfiltered params even after accessing few keys" do - params = ActionController::Parameters.new("f"=>{"language_facet"=>["Tibetan"]}) - expected = {"f"=>{"language_facet"=>["Tibetan"]}} + params = ActionController::Parameters.new("f"=>{ "language_facet"=>["Tibetan"] }) + expected = { "f"=>{ "language_facet"=>["Tibetan"] } } - assert params['f'].is_a? ActionController::Parameters + assert params["f"].is_a? ActionController::Parameters assert_equal expected, params.to_unsafe_h end @@ -313,10 +336,10 @@ class ParametersPermitTest < ActiveSupport::TestCase end.new params = ActionController::Parameters.new(prem: { likes: %i( dancing ) }) - assert_equal({ 'prem' => { 'likes' => %i( dancing ) } }, params.permit!.to_h) + assert_equal({ "prem" => { "likes" => %i( dancing ) } }, params.permit!.to_h) params = ActionController::Parameters.new(companies: [ company, :acme ]) - assert_equal({ 'companies' => [ company, :acme ] }, params.permit!.to_h) + assert_equal({ "companies" => [ company, :acme ] }, params.permit!.to_h) assert_not company.dupped end @@ -327,16 +350,31 @@ class ParametersPermitTest < ActiveSupport::TestCase end.new params = ActionController::Parameters.new(prem: { likes: %i( dancing ) }) - assert_equal({ 'prem' => { 'likes' => %i( dancing ) } }, params.to_unsafe_h) + assert_equal({ "prem" => { "likes" => %i( dancing ) } }, params.to_unsafe_h) params = ActionController::Parameters.new(companies: [ company, :acme ]) - assert_equal({ 'companies' => [ company, :acme ] }, params.to_unsafe_h) + assert_equal({ "companies" => [ company, :acme ] }, params.to_unsafe_h) assert_not company.dupped end test "include? returns true when the key is present" do assert @params.include? :person - assert @params.include? 'person' + assert @params.include? "person" assert_not @params.include? :gorilla end + + test "scalar values should be filtered when array or hash is specified" do + params = ActionController::Parameters.new(foo: "bar") + + assert params.permit(:foo).has_key?(:foo) + refute params.permit(foo: []).has_key?(:foo) + refute params.permit(foo: [:bar]).has_key?(:foo) + refute params.permit(foo: :bar).has_key?(:foo) + end + + test "#permitted? is false by default" do + params = ActionController::Parameters.new + + assert_equal false, params.permitted? + end end diff --git a/actionpack/test/controller/parameters/raise_on_unpermitted_params_test.rb b/actionpack/test/controller/parameters/raise_on_unpermitted_params_test.rb index f9cc9f96f1..8fab7b28e9 100644 --- a/actionpack/test/controller/parameters/raise_on_unpermitted_params_test.rb +++ b/actionpack/test/controller/parameters/raise_on_unpermitted_params_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'action_controller/metal/strong_parameters' +require "abstract_unit" +require "action_controller/metal/strong_parameters" class RaiseOnUnpermittedParamsTest < ActiveSupport::TestCase def setup @@ -11,10 +11,9 @@ class RaiseOnUnpermittedParamsTest < ActiveSupport::TestCase end test "raises on unexpected params" do - params = ActionController::Parameters.new({ + params = ActionController::Parameters.new( book: { pages: 65 }, - fishing: "Turnips" - }) + fishing: "Turnips") assert_raises(ActionController::UnpermittedParameters) do params.permit(book: [:pages]) @@ -22,9 +21,8 @@ class RaiseOnUnpermittedParamsTest < ActiveSupport::TestCase end test "raises on unexpected nested params" do - params = ActionController::Parameters.new({ - book: { pages: 65, title: "Green Cats and where to find then." } - }) + params = ActionController::Parameters.new( + book: { pages: 65, title: "Green Cats and where to find then." }) assert_raises(ActionController::UnpermittedParameters) do params.permit(book: [:pages]) diff --git a/actionpack/test/controller/parameters/serialization_test.rb b/actionpack/test/controller/parameters/serialization_test.rb new file mode 100644 index 0000000000..6fba2fde91 --- /dev/null +++ b/actionpack/test/controller/parameters/serialization_test.rb @@ -0,0 +1,53 @@ +require "abstract_unit" +require "action_controller/metal/strong_parameters" +require "active_support/core_ext/string/strip" + +class ParametersSerializationTest < ActiveSupport::TestCase + setup do + @old_permitted_parameters = ActionController::Parameters.permit_all_parameters + ActionController::Parameters.permit_all_parameters = false + end + + teardown do + ActionController::Parameters.permit_all_parameters = @old_permitted_parameters + end + + test "yaml serialization" do + params = ActionController::Parameters.new(key: :value) + yaml_dump = YAML.dump(params) + assert_match("--- !ruby/object:ActionController::Parameters", yaml_dump) + assert_match(/parameters: !ruby\/hash:ActiveSupport::HashWithIndifferentAccess\n\s+key: :value/, yaml_dump) + assert_match("permitted: false", yaml_dump) + end + + test "yaml deserialization" do + params = ActionController::Parameters.new(key: :value) + roundtripped = YAML.load(YAML.dump(params)) + + assert_equal params, roundtripped + assert_not roundtripped.permitted? + end + + test "yaml backwardscompatible with psych 2.0.8 format" do + params = YAML.load <<-end_of_yaml.strip_heredoc + --- !ruby/hash:ActionController::Parameters + key: :value + end_of_yaml + + assert_equal :value, params[:key] + assert_not params.permitted? + end + + test "yaml backwardscompatible with psych 2.0.9+ format" do + params = YAML.load(<<-end_of_yaml.strip_heredoc) + --- !ruby/hash-with-ivars:ActionController::Parameters + elements: + key: :value + ivars: + :@permitted: false + end_of_yaml + + assert_equal :value, params[:key] + assert_not params.permitted? + end +end diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb index 7226beed26..1549405fe7 100644 --- a/actionpack/test/controller/params_wrapper_test.rb +++ b/actionpack/test/controller/params_wrapper_test.rb @@ -1,10 +1,10 @@ -require 'abstract_unit' +require "abstract_unit" module Admin; class User; end; end module ParamsWrapperTestHelp def with_default_wrapper_options(&block) - @controller.class._set_wrapper_options({:format => [:json]}) + @controller.class._set_wrapper_options(format: [:json]) @controller.class.inherited(@controller.class) yield end @@ -48,17 +48,17 @@ class ParamsWrapperTest < ActionController::TestCase def test_filtered_parameters 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' } } + @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" } end end def test_derived_name_from_controller with_default_wrapper_options do - @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, params: { 'username' => 'sikachu' } - assert_parameters({ 'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }}) + @request.env["CONTENT_TYPE"] = "application/json" + post :parse, params: { "username" => "sikachu" } + assert_parameters("username" => "sikachu", "user" => { "username" => "sikachu" }) end end @@ -66,9 +66,9 @@ class ParamsWrapperTest < ActionController::TestCase with_default_wrapper_options do UsersController.wrap_parameters :person - @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, params: { 'username' => 'sikachu' } - assert_parameters({ 'username' => 'sikachu', 'person' => { 'username' => 'sikachu' }}) + @request.env["CONTENT_TYPE"] = "application/json" + post :parse, params: { "username" => "sikachu" } + assert_parameters("username" => "sikachu", "person" => { "username" => "sikachu" }) end end @@ -76,99 +76,99 @@ class ParamsWrapperTest < ActionController::TestCase with_default_wrapper_options do UsersController.wrap_parameters Person - @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, params: { 'username' => 'sikachu' } - assert_parameters({ 'username' => 'sikachu', 'person' => { 'username' => 'sikachu' }}) + @request.env["CONTENT_TYPE"] = "application/json" + post :parse, params: { "username" => "sikachu" } + assert_parameters("username" => "sikachu", "person" => { "username" => "sikachu" }) end end def test_specify_include_option with_default_wrapper_options do - UsersController.wrap_parameters :include => :username + UsersController.wrap_parameters include: :username - @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' } - assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }}) + @request.env["CONTENT_TYPE"] = "application/json" + post :parse, params: { "username" => "sikachu", "title" => "Developer" } + assert_parameters("username" => "sikachu", "title" => "Developer", "user" => { "username" => "sikachu" }) end end def test_specify_exclude_option with_default_wrapper_options do - UsersController.wrap_parameters :exclude => :title + UsersController.wrap_parameters exclude: :title - @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' } - assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }}) + @request.env["CONTENT_TYPE"] = "application/json" + post :parse, params: { "username" => "sikachu", "title" => "Developer" } + assert_parameters("username" => "sikachu", "title" => "Developer", "user" => { "username" => "sikachu" }) end end def test_specify_both_wrapper_name_and_include_option with_default_wrapper_options do - UsersController.wrap_parameters :person, :include => :username + UsersController.wrap_parameters :person, include: :username - @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' } - assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }}) + @request.env["CONTENT_TYPE"] = "application/json" + post :parse, params: { "username" => "sikachu", "title" => "Developer" } + assert_parameters("username" => "sikachu", "title" => "Developer", "person" => { "username" => "sikachu" }) end end def test_not_enabled_format with_default_wrapper_options do - @request.env['CONTENT_TYPE'] = 'application/xml' - post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' } - assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer' }) + @request.env["CONTENT_TYPE"] = "application/xml" + post :parse, params: { "username" => "sikachu", "title" => "Developer" } + assert_parameters("username" => "sikachu", "title" => "Developer") end end def test_wrap_parameters_false with_default_wrapper_options do UsersController.wrap_parameters false - @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' } - assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer' }) + @request.env["CONTENT_TYPE"] = "application/json" + post :parse, params: { "username" => "sikachu", "title" => "Developer" } + assert_parameters("username" => "sikachu", "title" => "Developer") end end def test_specify_format with_default_wrapper_options do - UsersController.wrap_parameters :format => :xml + UsersController.wrap_parameters format: :xml - @request.env['CONTENT_TYPE'] = 'application/xml' - post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' } - assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu', 'title' => 'Developer' }}) + @request.env["CONTENT_TYPE"] = "application/xml" + post :parse, params: { "username" => "sikachu", "title" => "Developer" } + assert_parameters("username" => "sikachu", "title" => "Developer", "user" => { "username" => "sikachu", "title" => "Developer" }) end end def test_not_wrap_reserved_parameters with_default_wrapper_options do - @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, params: { 'authenticity_token' => 'pwned', '_method' => 'put', 'utf8' => '☃', 'username' => 'sikachu' } - assert_parameters({ 'authenticity_token' => 'pwned', '_method' => 'put', 'utf8' => '☃', 'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }}) + @request.env["CONTENT_TYPE"] = "application/json" + post :parse, params: { "authenticity_token" => "pwned", "_method" => "put", "utf8" => "☃", "username" => "sikachu" } + assert_parameters("authenticity_token" => "pwned", "_method" => "put", "utf8" => "☃", "username" => "sikachu", "user" => { "username" => "sikachu" }) end end def test_no_double_wrap_if_key_exists with_default_wrapper_options do - @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, params: { 'user' => { 'username' => 'sikachu' }} - assert_parameters({ 'user' => { 'username' => 'sikachu' }}) + @request.env["CONTENT_TYPE"] = "application/json" + post :parse, params: { "user" => { "username" => "sikachu" } } + assert_parameters("user" => { "username" => "sikachu" }) end end def test_nested_params with_default_wrapper_options do - @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, params: { 'person' => { 'username' => 'sikachu' }} - assert_parameters({ 'person' => { 'username' => 'sikachu' }, 'user' => {'person' => { 'username' => 'sikachu' }}}) + @request.env["CONTENT_TYPE"] = "application/json" + post :parse, params: { "person" => { "username" => "sikachu" } } + assert_parameters("person" => { "username" => "sikachu" }, "user" => { "person" => { "username" => "sikachu" } }) end end def test_derived_wrapped_keys_from_matching_model assert_called(User, :attribute_names, times: 2, returns: ["username"]) do with_default_wrapper_options do - @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' } - assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }}) + @request.env["CONTENT_TYPE"] = "application/json" + post :parse, params: { "username" => "sikachu", "title" => "Developer" } + assert_parameters("username" => "sikachu", "title" => "Developer", "user" => { "username" => "sikachu" }) end end end @@ -178,37 +178,37 @@ class ParamsWrapperTest < ActionController::TestCase assert_called(Person, :attribute_names, times: 2, returns: ["username"]) do UsersController.wrap_parameters Person - @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' } - assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }}) + @request.env["CONTENT_TYPE"] = "application/json" + post :parse, params: { "username" => "sikachu", "title" => "Developer" } + assert_parameters("username" => "sikachu", "title" => "Developer", "person" => { "username" => "sikachu" }) end end end def test_not_wrapping_abstract_model with_default_wrapper_options do - @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' } - assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu', 'title' => 'Developer' }}) + @request.env["CONTENT_TYPE"] = "application/json" + post :parse, params: { "username" => "sikachu", "title" => "Developer" } + assert_parameters("username" => "sikachu", "title" => "Developer", "user" => { "username" => "sikachu", "title" => "Developer" }) end end def test_preserves_query_string_params with_default_wrapper_options do - @request.env['CONTENT_TYPE'] = 'application/json' - get :parse, params: { 'user' => { 'username' => 'nixon' } } + @request.env["CONTENT_TYPE"] = "application/json" + get :parse, params: { "user" => { "username" => "nixon" } } assert_parameters( - {'user' => { 'username' => 'nixon' } } + "user" => { "username" => "nixon" } ) end end def test_empty_parameter_set with_default_wrapper_options do - @request.env['CONTENT_TYPE'] = 'application/json' + @request.env["CONTENT_TYPE"] = "application/json" post :parse, params: {} assert_parameters( - {'user' => { } } + "user" => {} ) end end @@ -252,9 +252,9 @@ class NamespacedParamsWrapperTest < ActionController::TestCase def test_derived_name_from_controller with_default_wrapper_options do - @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, params: { 'username' => 'sikachu' } - assert_parameters({'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }}) + @request.env["CONTENT_TYPE"] = "application/json" + post :parse, params: { "username" => "sikachu" } + assert_parameters("username" => "sikachu", "user" => { "username" => "sikachu" }) end end @@ -262,9 +262,9 @@ class NamespacedParamsWrapperTest < ActionController::TestCase Admin.const_set(:User, Class.new(SampleOne)) begin with_default_wrapper_options do - @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' } - assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }}) + @request.env["CONTENT_TYPE"] = "application/json" + post :parse, params: { "username" => "sikachu", "title" => "Developer" } + assert_parameters("username" => "sikachu", "title" => "Developer", "user" => { "username" => "sikachu" }) end ensure Admin.send :remove_const, :User @@ -275,15 +275,14 @@ class NamespacedParamsWrapperTest < ActionController::TestCase Object.const_set(:User, Class.new(SampleTwo)) begin with_default_wrapper_options do - @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' } - assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'title' => 'Developer' }}) + @request.env["CONTENT_TYPE"] = "application/json" + post :parse, params: { "username" => "sikachu", "title" => "Developer" } + assert_parameters("username" => "sikachu", "title" => "Developer", "user" => { "title" => "Developer" }) end ensure Object.send :remove_const, :User end end - end class AnonymousControllerParamsWrapperTest < ActionController::TestCase @@ -302,18 +301,18 @@ class AnonymousControllerParamsWrapperTest < ActionController::TestCase def test_does_not_implicitly_wrap_params with_default_wrapper_options do - @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, params: { 'username' => 'sikachu' } - assert_parameters({ 'username' => 'sikachu' }) + @request.env["CONTENT_TYPE"] = "application/json" + post :parse, params: { "username" => "sikachu" } + assert_parameters("username" => "sikachu") end end def test_does_wrap_params_if_name_provided with_default_wrapper_options do - @controller.class.wrap_parameters(:name => "guest") - @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, params: { 'username' => 'sikachu' } - assert_parameters({ 'username' => 'sikachu', 'guest' => { 'username' => 'sikachu' }}) + @controller.class.wrap_parameters(name: "guest") + @request.env["CONTENT_TYPE"] = "application/json" + post :parse, params: { "username" => "sikachu" } + assert_parameters("username" => "sikachu", "guest" => { "username" => "sikachu" }) end end end @@ -323,7 +322,7 @@ class IrregularInflectionParamsWrapperTest < ActionController::TestCase class ParamswrappernewsItem def self.attribute_names - ['test_attr'] + ["test_attr"] end end @@ -343,24 +342,24 @@ class IrregularInflectionParamsWrapperTest < ActionController::TestCase def test_uses_model_attribute_names_with_irregular_inflection with_dup do ActiveSupport::Inflector.inflections do |inflect| - inflect.irregular 'paramswrappernews_item', 'paramswrappernews' + inflect.irregular "paramswrappernews_item", "paramswrappernews" end with_default_wrapper_options do - @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, params: { 'username' => 'sikachu', 'test_attr' => 'test_value' } - assert_parameters({ 'username' => 'sikachu', 'test_attr' => 'test_value', 'paramswrappernews_item' => { 'test_attr' => 'test_value' }}) + @request.env["CONTENT_TYPE"] = "application/json" + post :parse, params: { "username" => "sikachu", "test_attr" => "test_value" } + assert_parameters("username" => "sikachu", "test_attr" => "test_value", "paramswrappernews_item" => { "test_attr" => "test_value" }) end end end private - def with_dup - original = ActiveSupport::Inflector::Inflections.instance_variable_get(:@__instance__)[:en] - ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original.dup) - yield - ensure - ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original) - end + def with_dup + original = ActiveSupport::Inflector::Inflections.instance_variable_get(:@__instance__)[:en] + ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original.dup) + yield + ensure + ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original) + end end diff --git a/actionpack/test/controller/permitted_params_test.rb b/actionpack/test/controller/permitted_params_test.rb index 7c753a45a5..6205a09816 100644 --- a/actionpack/test/controller/permitted_params_test.rb +++ b/actionpack/test/controller/permitted_params_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class PeopleController < ActionController::Base def create diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb index 0b184eace9..0e61b92bcf 100644 --- a/actionpack/test/controller/redirect_test.rb +++ b/actionpack/test/controller/redirect_test.rb @@ -1,45 +1,59 @@ -require 'abstract_unit' +require "abstract_unit" + +class Workshop + extend ActiveModel::Naming + include ActiveModel::Conversion + attr_accessor :id + + def initialize(id) + @id = id + end + + def persisted? + id.present? + end + + def to_s + id.to_s + end +end class RedirectController < ActionController::Base # empty method not used anywhere to ensure methods like # `status` and `location` aren't called on `redirect_to` calls - def status; render plain: 'called status'; end - def location; render plain: 'called location'; end + def status; render plain: "called status"; end + def location; render plain: "called location"; end def simple_redirect - redirect_to :action => "hello_world" + redirect_to action: "hello_world" end def redirect_with_status - redirect_to({:action => "hello_world", :status => 301}) + redirect_to(action: "hello_world", status: 301) end def redirect_with_status_hash - redirect_to({:action => "hello_world"}, {:status => 301}) + redirect_to({ action: "hello_world" }, status: 301) end def redirect_with_protocol - redirect_to :action => "hello_world", :protocol => "https" + redirect_to action: "hello_world", protocol: "https" end def url_redirect_with_status - redirect_to("http://www.example.com", :status => :moved_permanently) + redirect_to("http://www.example.com", status: :moved_permanently) end def url_redirect_with_status_hash - redirect_to("http://www.example.com", {:status => 301}) + redirect_to("http://www.example.com", status: 301) end def relative_url_redirect_with_status - redirect_to("/things/stuff", :status => :found) + redirect_to("/things/stuff", status: :found) end def relative_url_redirect_with_status_hash - redirect_to("/things/stuff", {:status => 301}) - end - - def redirect_to_back_with_status - redirect_to :back, :status => 307 + redirect_to("/things/stuff", status: 301) end def redirect_back_with_status @@ -47,11 +61,11 @@ class RedirectController < ActionController::Base end def host_redirect - redirect_to :action => "other_host", :only_path => false, :host => 'other.test.host' + redirect_to action: "other_host", only_path: false, host: "other.test.host" end def module_redirect - redirect_to :controller => 'module_test/module_redirect', :action => "hello_world" + redirect_to controller: "module_test/module_redirect", action: "hello_world" end def redirect_to_url @@ -70,10 +84,6 @@ class RedirectController < ActionController::Base redirect_to "//www.rubyonrails.org/" end - def redirect_to_back - redirect_to :back - end - def redirect_to_existing_record redirect_to Workshop.new(5) end @@ -87,7 +97,7 @@ class RedirectController < ActionController::Base end def redirect_to_params - redirect_to ActionController::Parameters.new(status: 200, protocol: 'javascript', f: '%0Aeval(name)') + redirect_to ActionController::Parameters.new(status: 200, protocol: "javascript", f: "%0Aeval(name)") end def redirect_to_with_block @@ -100,7 +110,7 @@ class RedirectController < ActionController::Base end def redirect_to_with_block_and_options - redirect_to proc { {:action => "hello_world"} } + redirect_to proc { { action: "hello_world" } } end def redirect_with_header_break @@ -115,7 +125,7 @@ class RedirectController < ActionController::Base protected def dashbord_url(id, message) - url_for :action => "dashboard", :params => { "id" => id, "message" => message } + url_for action: "dashboard", params: { "id" => id, "message" => message } end end @@ -176,7 +186,6 @@ class RedirectTest < ActionController::TestCase assert_equal "http://www.example.com", redirect_to_url end - def test_relative_url_redirect_with_status get :relative_url_redirect_with_status assert_response 302 @@ -189,21 +198,10 @@ class RedirectTest < ActionController::TestCase assert_equal "http://test.host/things/stuff", redirect_to_url end - def test_redirect_to_back_with_status - @request.env["HTTP_REFERER"] = "http://www.example.com/coming/from" - - assert_deprecated do - get :redirect_to_back_with_status - end - - assert_response 307 - assert_equal "http://www.example.com/coming/from", redirect_to_url - end - def test_simple_redirect_using_options get :host_redirect assert_response :redirect - assert_redirected_to :action => "other_host", :only_path => false, :host => 'other.test.host' + assert_redirected_to action: "other_host", only_path: false, host: "other.test.host" end def test_module_redirect @@ -215,7 +213,7 @@ class RedirectTest < ActionController::TestCase def test_module_redirect_using_options get :module_redirect assert_response :redirect - assert_redirected_to :controller => 'module_test/module_redirect', :action => 'hello_world' + assert_redirected_to controller: "module_test/module_redirect", action: "hello_world" end def test_redirect_to_url @@ -242,29 +240,6 @@ class RedirectTest < ActionController::TestCase assert_equal "//www.rubyonrails.org/", redirect_to_url end - def test_redirect_to_back - @request.env["HTTP_REFERER"] = "http://www.example.com/coming/from" - - assert_deprecated do - get :redirect_to_back - end - - assert_response :redirect - assert_equal "http://www.example.com/coming/from", redirect_to_url - end - - def test_redirect_to_back_with_no_referer - assert_raise(ActionController::RedirectBackError) { - @request.env["HTTP_REFERER"] = nil - - assert_deprecated do - get :redirect_to_back - end - - get :redirect_to_back - } - end - def test_redirect_back referer = "http://www.example.com/coming/from" @request.env["HTTP_REFERER"] = referer @@ -286,7 +261,10 @@ class RedirectTest < ActionController::TestCase with_routing do |set| set.draw do resources :workshops - get ':controller/:action' + + ActiveSupport::Deprecation.silence do + get ":controller/:action" + end end get :redirect_to_existing_record @@ -310,7 +288,7 @@ class RedirectTest < ActionController::TestCase error = assert_raise(ArgumentError) do get :redirect_to_params end - assert_equal "Generating an URL from non sanitized request parameters is insecure!", error.message + assert_equal ActionDispatch::Routing::INSECURE_URL_PARAMETERS_MESSAGE, error.message end def test_redirect_to_with_block @@ -328,7 +306,9 @@ class RedirectTest < ActionController::TestCase def test_redirect_to_with_block_and_accepted_options with_routing do |set| set.draw do - get ':controller/:action' + ActiveSupport::Deprecation.silence do + get ":controller/:action" + end end get :redirect_to_with_block_and_options @@ -342,7 +322,7 @@ end module ModuleTest class ModuleRedirectController < ::RedirectController def module_redirect - redirect_to :controller => '/redirect', :action => "hello_world" + redirect_to controller: "/redirect", action: "hello_world" end end @@ -358,7 +338,7 @@ module ModuleTest def test_simple_redirect_using_options get :host_redirect assert_response :redirect - assert_redirected_to :action => "other_host", :only_path => false, :host => 'other.test.host' + assert_redirected_to action: "other_host", only_path: false, host: "other.test.host" end def test_module_redirect @@ -370,7 +350,7 @@ module ModuleTest def test_module_redirect_using_options get :module_redirect assert_response :redirect - assert_redirected_to :controller => '/redirect', :action => "hello_world" + assert_redirected_to controller: "/redirect", action: "hello_world" end end end diff --git a/actionpack/test/controller/render_js_test.rb b/actionpack/test/controller/render_js_test.rb index 6b661de064..290218d4a2 100644 --- a/actionpack/test/controller/render_js_test.rb +++ b/actionpack/test/controller/render_js_test.rb @@ -1,21 +1,21 @@ -require 'abstract_unit' -require 'controller/fake_models' -require 'pathname' +require "abstract_unit" +require "controller/fake_models" +require "pathname" class RenderJSTest < ActionController::TestCase class TestController < ActionController::Base protect_from_forgery def self.controller_path - 'test' + "test" end def render_vanilla_js_hello - render :js => "alert('hello')" + render js: "alert('hello')" end def show_partial - render :partial => 'partial' + render partial: "partial" end end @@ -28,7 +28,7 @@ class RenderJSTest < ActionController::TestCase end def test_should_render_js_partial - get :show_partial, format: 'js', xhr: true - assert_equal 'partial js', @response.body + get :show_partial, format: "js", xhr: true + assert_equal "partial js", @response.body end end diff --git a/actionpack/test/controller/render_json_test.rb b/actionpack/test/controller/render_json_test.rb index 3773900cc4..213829bd9e 100644 --- a/actionpack/test/controller/render_json_test.rb +++ b/actionpack/test/controller/render_json_test.rb @@ -1,18 +1,18 @@ -require 'abstract_unit' -require 'controller/fake_models' -require 'active_support/logger' -require 'pathname' +require "abstract_unit" +require "controller/fake_models" +require "active_support/logger" +require "pathname" class RenderJsonTest < ActionController::TestCase class JsonRenderable def as_json(options={}) - hash = { :a => :b, :c => :d, :e => :f } + hash = { a: :b, c: :d, e: :f } hash.except!(*options[:except]) if options[:except] hash end def to_json(options = {}) - super :except => [:c, :e] + super except: [:c, :e] end end @@ -20,47 +20,47 @@ class RenderJsonTest < ActionController::TestCase protect_from_forgery def self.controller_path - 'test' + "test" end def render_json_nil - render :json => nil + render json: nil end def render_json_render_to_string - render plain: render_to_string(json: '[]') + render plain: render_to_string(json: "[]") end def render_json_hello_world - render :json => ActiveSupport::JSON.encode(:hello => 'world') + render json: ActiveSupport::JSON.encode(hello: "world") end def render_json_hello_world_with_status - render :json => ActiveSupport::JSON.encode(:hello => 'world'), :status => 401 + render json: ActiveSupport::JSON.encode(hello: "world"), status: 401 end def render_json_hello_world_with_callback - render :json => ActiveSupport::JSON.encode(:hello => 'world'), :callback => 'alert' + render json: ActiveSupport::JSON.encode(hello: "world"), callback: "alert" end def render_json_with_custom_content_type - render :json => ActiveSupport::JSON.encode(:hello => 'world'), :content_type => 'text/javascript' + render json: ActiveSupport::JSON.encode(hello: "world"), content_type: "text/javascript" end def render_symbol_json - render :json => ActiveSupport::JSON.encode(:hello => 'world') + render json: ActiveSupport::JSON.encode(hello: "world") end def render_json_with_render_to_string - render :json => {:hello => render_to_string(:partial => 'partial')} + render json: { hello: render_to_string(partial: "partial") } end def render_json_with_extra_options - render :json => JsonRenderable.new, :except => [:c, :e] + render json: JsonRenderable.new, except: [:c, :e] end def render_json_without_options - render :json => JsonRenderable.new + render json: JsonRenderable.new end end @@ -77,20 +77,19 @@ class RenderJsonTest < ActionController::TestCase def test_render_json_nil get :render_json_nil - assert_equal 'null', @response.body - assert_equal 'application/json', @response.content_type + assert_equal "null", @response.body + assert_equal "application/json", @response.content_type end def test_render_json_render_to_string get :render_json_render_to_string - assert_equal '[]', @response.body + assert_equal "[]", @response.body end - def test_render_json get :render_json_hello_world assert_equal '{"hello":"world"}', @response.body - assert_equal 'application/json', @response.content_type + assert_equal "application/json", @response.content_type end def test_render_json_with_status @@ -102,31 +101,31 @@ class RenderJsonTest < ActionController::TestCase def test_render_json_with_callback get :render_json_hello_world_with_callback, xhr: true assert_equal '/**/alert({"hello":"world"})', @response.body - assert_equal 'text/javascript', @response.content_type + assert_equal "text/javascript", @response.content_type end def test_render_json_with_custom_content_type get :render_json_with_custom_content_type, xhr: true assert_equal '{"hello":"world"}', @response.body - assert_equal 'text/javascript', @response.content_type + assert_equal "text/javascript", @response.content_type end def test_render_symbol_json get :render_symbol_json assert_equal '{"hello":"world"}', @response.body - assert_equal 'application/json', @response.content_type + assert_equal "application/json", @response.content_type end def test_render_json_with_render_to_string get :render_json_with_render_to_string assert_equal '{"hello":"partial html"}', @response.body - assert_equal 'application/json', @response.content_type + assert_equal "application/json", @response.content_type end def test_render_json_forwards_extra_options get :render_json_with_extra_options assert_equal '{"a":"b"}', @response.body - assert_equal 'application/json', @response.content_type + assert_equal "application/json", @response.content_type end def test_render_json_calls_to_json_from_object diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index c814d4ea54..76139d59e2 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -1,31 +1,53 @@ -require 'abstract_unit' -require 'controller/fake_models' +require "abstract_unit" +require "controller/fake_models" class TestControllerWithExtraEtags < ActionController::Base + def self.controller_name; "test"; end + def self.controller_path; "test"; end + etag { nil } - etag { 'ab' } + etag { "ab" } etag { :cde } etag { [:f] } etag { nil } def fresh - render plain: "stale" if stale?(etag: '123', template: false) + render plain: "stale" if stale?(etag: "123", template: false) end def array render plain: "stale" if stale?(etag: %w(1 2 3), template: false) end + def strong + render plain: "stale" if stale?(strong_etag: "strong", template: false) + end + def with_template - if stale? template: 'test/hello_world' - render plain: 'stale' + if stale? template: "test/hello_world" + render plain: "stale" end end + + def with_implicit_template + fresh_when(etag: "123") + end end class ImplicitRenderTestController < ActionController::Base def empty_action end + + def empty_action_with_template + end +end + +module Namespaced + class ImplicitRenderTestController < ActionController::Base + def hello_world + fresh_when(etag: "abc") + end + end end class TestController < ActionController::Base @@ -49,8 +71,8 @@ class TestController < ActionController::Base end def conditional_hello - if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123]) - render :action => 'hello_world' + if stale?(last_modified: Time.now.utc.beginning_of_day, etag: [:foo, 123]) + render action: "hello_world" end end @@ -58,7 +80,7 @@ class TestController < ActionController::Base record = Struct.new(:updated_at, :cache_key).new(Time.now.utc.beginning_of_day, "foo/123") if stale?(record) - render :action => 'hello_world' + render action: "hello_world" end end @@ -93,70 +115,58 @@ class TestController < ActionController::Base old_record = Struct.new(:updated_at, :cache_key).new(ts - 1.day, "bar/123") if stale?(Collection.new([record, old_record])) - render action: 'hello_world' + render action: "hello_world" end end def conditional_hello_with_expires_in expires_in 60.1.seconds - render :action => 'hello_world' + render action: "hello_world" end def conditional_hello_with_expires_in_with_public - expires_in 1.minute, :public => true - render :action => 'hello_world' + expires_in 1.minute, public: true + render action: "hello_world" end def conditional_hello_with_expires_in_with_must_revalidate - expires_in 1.minute, :must_revalidate => true - render :action => 'hello_world' + expires_in 1.minute, must_revalidate: true + render action: "hello_world" end def conditional_hello_with_expires_in_with_public_and_must_revalidate - expires_in 1.minute, :public => true, :must_revalidate => true - render :action => 'hello_world' + expires_in 1.minute, public: true, must_revalidate: true + render action: "hello_world" end def conditional_hello_with_expires_in_with_public_with_more_keys - expires_in 1.minute, :public => true, 's-maxage' => 5.hours - render :action => 'hello_world' + expires_in 1.minute, :public => true, "s-maxage" => 5.hours + render action: "hello_world" end def conditional_hello_with_expires_in_with_public_with_more_keys_old_syntax - expires_in 1.minute, :public => true, :private => nil, 's-maxage' => 5.hours - render :action => 'hello_world' + expires_in 1.minute, :public => true, :private => nil, "s-maxage" => 5.hours + render action: "hello_world" end def conditional_hello_with_expires_now expires_now - render :action => 'hello_world' + render action: "hello_world" end def conditional_hello_with_cache_control_headers - response.headers['Cache-Control'] = 'no-transform' + response.headers["Cache-Control"] = "no-transform" expires_now - render :action => 'hello_world' - end - - def respond_with_empty_body - render nothing: true + render action: "hello_world" end def conditional_hello_with_bangs - render :action => 'hello_world' + render action: "hello_world" end - before_action :handle_last_modified_and_etags, :only=>:conditional_hello_with_bangs + before_action :handle_last_modified_and_etags, only: :conditional_hello_with_bangs def handle_last_modified_and_etags - fresh_when(:last_modified => Time.now.utc.beginning_of_day, :etag => [ :foo, 123 ]) - end - - def head_with_status_hash - head status: :created - end - - def head_with_hash_does_not_include_status - head warning: :deprecated + fresh_when(last_modified: Time.now.utc.beginning_of_day, etag: [ :foo, 123 ]) end def head_created @@ -164,19 +174,19 @@ class TestController < ActionController::Base end def head_created_with_application_json_content_type - head :created, :content_type => "application/json" + head :created, content_type: "application/json" end def head_ok_with_image_png_content_type - head :ok, :content_type => "image/png" + head :ok, content_type: "image/png" end def head_with_location_header - head :ok, :location => "/foo" + head :ok, location: "/foo" end def head_with_location_object - head :ok, :location => Customer.new("david", 1) + head :ok, location: Customer.new("david", 1) end def head_with_symbolic_status @@ -192,20 +202,20 @@ class TestController < ActionController::Base end def head_with_custom_header - head :ok, :x_custom_header => "something" + head :ok, x_custom_header: "something" end def head_with_www_authenticate_header - head :ok, 'WWW-Authenticate' => 'something' + head :ok, "WWW-Authenticate" => "something" end def head_with_status_code_first - head :forbidden, :x_custom_header => "something" + head :forbidden, x_custom_header: "something" end def head_and_return - head :ok and return - raise 'should not reach this line' + head(:ok) && return + raise "should not reach this line" end def head_with_no_content @@ -225,7 +235,7 @@ class TestController < ActionController::Base def determine_layout case action_name - when "hello_world", "layout_test", "rendering_without_layout", + when "hello_world", "layout_test", "rendering_without_layout", "rendering_nothing_on_layout", "render_text_hello_world", "render_text_hello_world_with_layout", "hello_world_with_layout_false", @@ -235,22 +245,35 @@ class TestController < ActionController::Base "render_with_explicit_string_template", "update_page", "update_page_with_instance_variables" - "layouts/standard" - when "action_talk_to_layout", "layout_overriding_layout" - "layouts/talk_from_action" - when "render_implicit_html_template_from_xhr_request" - (request.xhr? ? 'layouts/xhr' : 'layouts/standard') + "layouts/standard" + when "action_talk_to_layout", "layout_overriding_layout" + "layouts/talk_from_action" + when "render_implicit_html_template_from_xhr_request" + (request.xhr? ? "layouts/xhr" : "layouts/standard") end end end +module TemplateModificationHelper + private + def modify_template(name) + path = File.expand_path("../../fixtures/#{name}.erb", __FILE__) + original = File.read(path) + File.write(path, "#{original} Modified!") + ActionView::LookupContext::DetailsKey.clear + yield + ensure + File.write(path, original) + end +end + class MetalTestController < ActionController::Metal include AbstractController::Rendering include ActionView::Rendering include ActionController::Rendering def accessing_logger_in_template - render :inline => "<%= logger.class %>" + render inline: "<%= logger.class %>" end end @@ -264,14 +287,14 @@ class ExpiresInRenderTest < ActionController::TestCase def test_dynamic_render_with_file # This is extremely bad, but should be possible to do. - assert File.exist?(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb')) + assert File.exist?(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb")) response = get :dynamic_render_with_file, params: { id: '../\\../test/abstract_unit.rb' } - assert_equal File.read(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb')), + assert_equal File.read(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb")), response.body end def test_dynamic_render_with_absolute_path - file = Tempfile.new('name') + file = Tempfile.new("name") file.write "secrets!" file.flush assert_raises ActionView::MissingTemplate do @@ -283,7 +306,7 @@ class ExpiresInRenderTest < ActionController::TestCase end def test_dynamic_render - assert File.exist?(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb')) + assert File.exist?(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb")) assert_raises ActionView::MissingTemplate do get :dynamic_render, params: { id: '../\\../test/abstract_unit.rb' } end @@ -291,9 +314,9 @@ class ExpiresInRenderTest < ActionController::TestCase 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')) + 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')), + assert_equal File.read(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb")), response.body end @@ -344,12 +367,6 @@ class ExpiresInRenderTest < ActionController::TestCase assert_match(/no-transform/, @response.headers["Cache-Control"]) end - def test_render_nothing_deprecated - assert_deprecated do - get :respond_with_empty_body - end - end - def test_date_header_when_expires_in time = Time.mktime(2011,10,30) Time.stub :now, time do @@ -369,7 +386,7 @@ class LastModifiedRenderTest < ActionController::TestCase def test_responds_with_last_modified get :conditional_hello - assert_equal @last_modified, @response.headers['Last-Modified'] + assert_equal @last_modified, @response.headers["Last-Modified"] end def test_request_not_modified @@ -377,27 +394,27 @@ class LastModifiedRenderTest < ActionController::TestCase get :conditional_hello assert_equal 304, @response.status.to_i assert @response.body.blank? - assert_equal @last_modified, @response.headers['Last-Modified'] + assert_equal @last_modified, @response.headers["Last-Modified"] end def test_request_not_modified_but_etag_differs @request.if_modified_since = @last_modified - @request.if_none_match = "234" + @request.if_none_match = '"234"' get :conditional_hello assert_response :success end def test_request_modified - @request.if_modified_since = 'Thu, 16 Jul 2008 00:00:00 GMT' + @request.if_modified_since = "Thu, 16 Jul 2008 00:00:00 GMT" get :conditional_hello assert_equal 200, @response.status.to_i assert @response.body.present? - assert_equal @last_modified, @response.headers['Last-Modified'] + assert_equal @last_modified, @response.headers["Last-Modified"] end def test_responds_with_last_modified_with_record get :conditional_hello_with_record - assert_equal @last_modified, @response.headers['Last-Modified'] + assert_equal @last_modified, @response.headers["Last-Modified"] end def test_request_not_modified_with_record @@ -406,27 +423,27 @@ class LastModifiedRenderTest < ActionController::TestCase assert_equal 304, @response.status.to_i assert @response.body.blank? assert_not_nil @response.etag - assert_equal @last_modified, @response.headers['Last-Modified'] + assert_equal @last_modified, @response.headers["Last-Modified"] end def test_request_not_modified_but_etag_differs_with_record @request.if_modified_since = @last_modified - @request.if_none_match = "234" + @request.if_none_match = '"234"' get :conditional_hello_with_record assert_response :success end def test_request_modified_with_record - @request.if_modified_since = 'Thu, 16 Jul 2008 00:00:00 GMT' + @request.if_modified_since = "Thu, 16 Jul 2008 00:00:00 GMT" get :conditional_hello_with_record assert_equal 200, @response.status.to_i assert @response.body.present? - assert_equal @last_modified, @response.headers['Last-Modified'] + assert_equal @last_modified, @response.headers["Last-Modified"] end def test_responds_with_last_modified_with_collection_of_records get :conditional_hello_with_collection_of_records - assert_equal @last_modified, @response.headers['Last-Modified'] + assert_equal @last_modified, @response.headers["Last-Modified"] end def test_request_not_modified_with_collection_of_records @@ -434,27 +451,27 @@ class LastModifiedRenderTest < ActionController::TestCase get :conditional_hello_with_collection_of_records assert_equal 304, @response.status.to_i assert @response.body.blank? - assert_equal @last_modified, @response.headers['Last-Modified'] + assert_equal @last_modified, @response.headers["Last-Modified"] end def test_request_not_modified_but_etag_differs_with_collection_of_records @request.if_modified_since = @last_modified - @request.if_none_match = "234" + @request.if_none_match = '"234"' get :conditional_hello_with_collection_of_records assert_response :success end def test_request_modified_with_collection_of_records - @request.if_modified_since = 'Thu, 16 Jul 2008 00:00:00 GMT' + @request.if_modified_since = "Thu, 16 Jul 2008 00:00:00 GMT" get :conditional_hello_with_collection_of_records assert_equal 200, @response.status.to_i assert @response.body.present? - assert_equal @last_modified, @response.headers['Last-Modified'] + assert_equal @last_modified, @response.headers["Last-Modified"] end def test_request_with_bang_gets_last_modified get :conditional_hello_with_bangs - assert_equal @last_modified, @response.headers['Last-Modified'] + assert_equal @last_modified, @response.headers["Last-Modified"] assert_response :success end @@ -473,9 +490,28 @@ end class EtagRenderTest < ActionController::TestCase tests TestControllerWithExtraEtags + include TemplateModificationHelper + + def test_strong_etag + @request.if_none_match = strong_etag(["strong", "ab", :cde, [:f]]) + get :strong + assert_response :not_modified + + @request.if_none_match = "*" + get :strong + assert_response :not_modified + + @request.if_none_match = '"strong"' + get :strong + assert_response :ok + + @request.if_none_match = weak_etag(["strong", "ab", :cde, [:f]]) + get :strong + assert_response :ok + end def test_multiple_etags - @request.if_none_match = etag(["123", 'ab', :cde, [:f]]) + @request.if_none_match = weak_etag(["123", "ab", :cde, [:f]]) get :fresh assert_response :not_modified @@ -485,7 +521,7 @@ class EtagRenderTest < ActionController::TestCase end def test_array - @request.if_none_match = etag([%w(1 2 3), 'ab', :cde, [:f]]) + @request.if_none_match = weak_etag([%w(1 2 3), "ab", :cde, [:f]]) get :array assert_response :not_modified @@ -503,25 +539,60 @@ class EtagRenderTest < ActionController::TestCase get :with_template assert_response :not_modified - # Modify the template digest - path = File.expand_path('../../fixtures/test/hello_world.erb', __FILE__) - old = File.read(path) + modify_template("test/hello_world") do + request.if_none_match = etag + get :with_template + assert_response :ok + assert_not_equal etag, @response.etag + end + end + + def test_etag_reflects_implicit_template_digest + get :with_implicit_template + assert_response :ok + assert_not_nil etag = @response.etag - begin - File.write path, 'foo' - ActionView::Digestor.cache.clear + request.if_none_match = etag + get :with_implicit_template + assert_response :not_modified + modify_template("test/with_implicit_template") do request.if_none_match = etag - get :with_template + get :with_implicit_template assert_response :ok assert_not_equal etag, @response.etag - ensure - File.write path, old end end - def etag(record) - %(W/"#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(record))}") + private + def weak_etag(record) + "W/#{strong_etag record}" + end + + def strong_etag(record) + %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(record))}") + end +end + +class NamespacedEtagRenderTest < ActionController::TestCase + tests Namespaced::ImplicitRenderTestController + include TemplateModificationHelper + + def test_etag_reflects_template_digest + get :hello_world + assert_response :ok + assert_not_nil etag = @response.etag + + request.if_none_match = etag + get :hello_world + assert_response :not_modified + + modify_template("namespaced/implicit_render_test/hello_world") do + request.if_none_match = etag + get :hello_world + assert_response :ok + assert_not_equal etag, @response.etag + end end end @@ -534,13 +605,38 @@ class MetalRenderTest < ActionController::TestCase end end +class ActionControllerBaseRenderTest < ActionController::TestCase + def test_direct_render_to_string + ac = ActionController::Base.new() + assert_equal "Hello world!", ac.render_to_string(template: "test/hello_world") + end +end + class ImplicitRenderTest < ActionController::TestCase tests ImplicitRenderTestController - def test_implicit_no_content_response - get :empty_action + def test_implicit_no_content_response_as_browser + assert_raises(ActionController::UnknownFormat) do + get :empty_action + end + end + + def test_implicit_no_content_response_as_xhr + get :empty_action, xhr: true assert_response :no_content end + + def test_implicit_success_response_with_right_format + get :empty_action_with_template + assert_equal "<h1>Empty action rendered this implicitly.</h1>\n", @response.body + assert_response :success + end + + def test_implicit_unknown_format_response + assert_raises(ActionController::UnknownFormat) do + get :empty_action_with_template, format: "json" + end + end end class HeadRenderTest < ActionController::TestCase @@ -556,19 +652,6 @@ class HeadRenderTest < ActionController::TestCase assert_response :created end - def test_passing_hash_to_head_as_first_parameter_deprecated - assert_deprecated do - get :head_with_status_hash - end - end - - def test_head_with_default_value_is_deprecated - assert_deprecated do - get :head_with_hash_does_not_include_status - assert_response :ok - end - end - def test_head_created_with_application_json_content_type post :head_created_with_application_json_content_type assert @response.body.blank? @@ -594,7 +677,10 @@ class HeadRenderTest < ActionController::TestCase with_routing do |set| set.draw do resources :customers - get ':controller/:action' + + ActiveSupport::Deprecation.silence do + get ":controller/:action" + end end get :head_with_location_object @@ -629,7 +715,7 @@ class HeadRenderTest < ActionController::TestCase get :head_with_symbolic_status, params: { status: "no_content" } assert_equal 204, @response.status - assert !@response.headers.include?('Content-Length') + assert_not_includes @response.headers, "Content-Length" assert_response :no_content Rack::Utils::SYMBOL_TO_STATUS_CODE.each do |status, code| @@ -679,8 +765,8 @@ end class HttpCacheForeverTest < ActionController::TestCase class HttpCacheForeverController < ActionController::Base def cache_me_forever - http_cache_forever(public: params[:public], version: params[:version] || 'v1') do - render plain: 'hello' + http_cache_forever(public: params[:public]) do + render plain: "hello" end end end @@ -688,43 +774,36 @@ class HttpCacheForeverTest < ActionController::TestCase tests HttpCacheForeverController def test_cache_with_public - get :cache_me_forever, params: {public: true} + get :cache_me_forever, params: { public: true } + assert_response :ok assert_equal "max-age=#{100.years}, public", @response.headers["Cache-Control"] assert_not_nil @response.etag + assert @response.weak_etag? end def test_cache_with_private get :cache_me_forever + assert_response :ok assert_equal "max-age=#{100.years}, private", @response.headers["Cache-Control"] assert_not_nil @response.etag - assert_response :success + assert @response.weak_etag? end def test_cache_response_code_with_if_modified_since get :cache_me_forever - assert_response :success - @request.if_modified_since = @response.headers['Last-Modified'] + assert_response :ok + + @request.if_modified_since = @response.headers["Last-Modified"] get :cache_me_forever assert_response :not_modified end def test_cache_response_code_with_etag get :cache_me_forever - assert_response :success - @request.if_modified_since = @response.headers['Last-Modified'] - @request.if_none_match = @response.etag - - get :cache_me_forever - assert_response :not_modified - @request.if_modified_since = @response.headers['Last-Modified'] - @request.if_none_match = @response.etag + assert_response :ok - get :cache_me_forever, params: {version: 'v2'} - assert_response :success - @request.if_modified_since = @response.headers['Last-Modified'] @request.if_none_match = @response.etag - - get :cache_me_forever, params: {version: 'v2'} + get :cache_me_forever assert_response :not_modified end end diff --git a/actionpack/test/controller/render_xml_test.rb b/actionpack/test/controller/render_xml_test.rb index f0fd7ddc5e..24866d7d6a 100644 --- a/actionpack/test/controller/render_xml_test.rb +++ b/actionpack/test/controller/render_xml_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'controller/fake_models' -require 'pathname' +require "abstract_unit" +require "controller/fake_models" +require "pathname" class RenderXmlTest < ActionController::TestCase class XmlRenderable @@ -14,31 +14,31 @@ class RenderXmlTest < ActionController::TestCase protect_from_forgery def self.controller_path - 'test' + "test" end def render_with_location - render :xml => "<hello/>", :location => "http://example.com", :status => 201 + render xml: "<hello/>", location: "http://example.com", status: 201 end def render_with_object_location customer = Customer.new("Some guy", 1) - render :xml => "<customer/>", :location => customer, :status => :created + render xml: "<customer/>", location: customer, status: :created end def render_with_to_xml - render :xml => XmlRenderable.new + render xml: XmlRenderable.new end def formatted_xml_erb end def render_xml_with_custom_content_type - render :xml => "<blah/>", :content_type => "application/atomsvc+xml" + render xml: "<blah/>", content_type: "application/atomsvc+xml" end def render_xml_with_custom_options - render :xml => XmlRenderable.new, :root => "i-am-THE-xml" + render xml: XmlRenderable.new, root: "i-am-THE-xml" end end @@ -72,7 +72,10 @@ class RenderXmlTest < ActionController::TestCase with_routing do |set| set.draw do resources :customers - get ':controller/:action' + + ActiveSupport::Deprecation.silence do + get ":controller/:action" + end end get :render_with_object_location @@ -82,7 +85,7 @@ class RenderXmlTest < ActionController::TestCase def test_should_render_formatted_xml_erb_template get :formatted_xml_erb, format: :xml - assert_equal '<test>passed formatted xml erb</test>', @response.body + assert_equal "<test>passed formatted xml erb</test>", @response.body end def test_should_render_xml_but_keep_custom_content_type @@ -91,7 +94,7 @@ class RenderXmlTest < ActionController::TestCase end def test_should_use_implicit_content_type - get :implicit_content_type, format: 'atom' + get :implicit_content_type, format: "atom" assert_equal Mime[:atom], @response.content_type end end diff --git a/actionpack/test/controller/renderer_test.rb b/actionpack/test/controller/renderer_test.rb index 16d24fa82a..866600b935 100644 --- a/actionpack/test/controller/renderer_test.rb +++ b/actionpack/test/controller/renderer_test.rb @@ -1,92 +1,108 @@ -require 'abstract_unit' +require "abstract_unit" class RendererTest < ActiveSupport::TestCase - test 'action controller base has a renderer' do + test "action controller base has a renderer" do assert ActionController::Base.renderer end - test 'creating with a controller' do + test "creating with a controller" do controller = CommentsController renderer = ActionController::Renderer.for controller assert_equal controller, renderer.controller end - test 'creating from a controller' do + test "creating from a controller" do controller = AccountsController renderer = controller.renderer assert_equal controller, renderer.controller end - test 'rendering with a class renderer' do + test "rendering with a class renderer" do renderer = ApplicationController.renderer - content = renderer.render template: 'ruby_template' + content = renderer.render template: "ruby_template" - assert_equal 'Hello from Ruby code', content + assert_equal "Hello from Ruby code", content end - test 'rendering with an instance renderer' do + test "rendering with an instance renderer" do renderer = ApplicationController.renderer.new - content = renderer.render file: 'test/hello_world' + content = renderer.render file: "test/hello_world" - assert_equal 'Hello world!', content + assert_equal "Hello world!", content end - test 'rendering with a controller class' do - assert_equal 'Hello world!', ApplicationController.render('test/hello_world') + test "rendering with a controller class" do + assert_equal "Hello world!", ApplicationController.render("test/hello_world") end - test 'rendering with locals' do + test "rendering with locals" do renderer = ApplicationController.renderer - content = renderer.render template: 'test/render_file_with_locals', - locals: { secret: 'bar' } + content = renderer.render template: "test/render_file_with_locals", + locals: { secret: "bar" } assert_equal "The secret is bar\n", content end - test 'rendering with assigns' do + test "rendering with assigns" do renderer = ApplicationController.renderer - content = renderer.render template: 'test/render_file_with_ivar', - assigns: { secret: 'foo' } + content = renderer.render template: "test/render_file_with_ivar", + assigns: { secret: "foo" } assert_equal "The secret is foo\n", content end - test 'rendering with custom env' do - renderer = ApplicationController.renderer.new method: 'post' - content = renderer.render inline: '<%= request.post? %>' + test "rendering with custom env" do + renderer = ApplicationController.renderer.new method: "post" + content = renderer.render inline: "<%= request.post? %>" - assert_equal 'true', content + assert_equal "true", content end - test 'rendering with defaults' do + test "rendering with custom env using a key that is not in RACK_KEY_TRANSLATION" do + value = "warden is here" + renderer = ApplicationController.renderer.new warden: value + content = renderer.render inline: "<%= request.env['warden'] %>" + + assert_equal value, content + end + + test "rendering with defaults" do renderer = ApplicationController.renderer.new https: true - content = renderer.render inline: '<%= request.ssl? %>' + content = renderer.render inline: "<%= request.ssl? %>" - assert_equal 'true', content + assert_equal "true", content end - test 'same defaults from the same controller' do + test "same defaults from the same controller" do renderer_defaults = ->(controller) { controller.renderer.defaults } assert_equal renderer_defaults[AccountsController], renderer_defaults[AccountsController] assert_equal renderer_defaults[AccountsController], renderer_defaults[CommentsController] end - test 'rendering with different formats' do - html = 'Hello world!' + test "rendering with different formats" do + html = "Hello world!" xml = "<p>Hello world!</p>\n" - assert_equal html, render['respond_to/using_defaults'] - assert_equal xml, render['respond_to/using_defaults.xml.builder'] - assert_equal xml, render['respond_to/using_defaults', formats: :xml] + assert_equal html, render["respond_to/using_defaults"] + assert_equal xml, render["respond_to/using_defaults.xml.builder"] + assert_equal xml, render["respond_to/using_defaults", formats: :xml] end - test 'rendering with helpers' do + test "rendering with helpers" do assert_equal "<p>1\n<br />2</p>", render[inline: '<%= simple_format "1\n2" %>'] end + test "rendering with user specified defaults" do + ApplicationController.renderer.defaults.merge!(hello: "hello", https: true) + renderer = ApplicationController.renderer.new + content = renderer.render inline: "<%= request.ssl? %>" + + assert_equal "true", content + end + private def render @render ||= ApplicationController.renderer.method(:render) diff --git a/actionpack/test/controller/renderers_test.rb b/actionpack/test/controller/renderers_test.rb index e6c2e4636e..122f5be549 100644 --- a/actionpack/test/controller/renderers_test.rb +++ b/actionpack/test/controller/renderers_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'controller/fake_models' -require 'active_support/logger' +require "abstract_unit" +require "controller/fake_models" +require "active_support/logger" class RenderersTest < ActionController::TestCase class XmlRenderable @@ -11,13 +11,13 @@ class RenderersTest < ActionController::TestCase end class JsonRenderable def as_json(options={}) - hash = { :a => :b, :c => :d, :e => :f } + hash = { a: :b, c: :d, e: :f } hash.except!(*options[:except]) if options[:except] hash end def to_json(options = {}) - super :except => [:c, :e] + super except: [:c, :e] end end class CsvRenderable @@ -26,9 +26,8 @@ class RenderersTest < ActionController::TestCase end end class TestController < ActionController::Base - def render_simon_says - render :simon => "foo" + render simon: "foo" end def respond_to_mime @@ -36,7 +35,7 @@ class RenderersTest < ActionController::TestCase type.json do render json: JsonRenderable.new end - type.js { render json: 'JS', callback: 'alert' } + type.js { render json: "JS", callback: "alert" } type.csv { render csv: CsvRenderable.new } type.xml { render xml: XmlRenderable.new } type.html { render body: "HTML" } @@ -70,7 +69,7 @@ class RenderersTest < ActionController::TestCase def test_raises_missing_template_no_renderer assert_raise ActionView::MissingTemplate do - get :respond_to_mime, format: 'csv' + get :respond_to_mime, format: "csv" end assert_equal Mime[:csv], @response.content_type assert_equal "", @response.body @@ -81,7 +80,7 @@ class RenderersTest < ActionController::TestCase send_data value.to_csv, type: Mime[:csv] end @request.accept = "text/csv" - get :respond_to_mime, format: 'csv' + get :respond_to_mime, format: "csv" assert_equal Mime[:csv], @response.content_type assert_equal "c,s,v", @response.body ensure diff --git a/actionpack/test/controller/request/test_request_test.rb b/actionpack/test/controller/request/test_request_test.rb index e5d698d5c2..fa049fbc9f 100644 --- a/actionpack/test/controller/request/test_request_test.rb +++ b/actionpack/test/controller/request/test_request_test.rb @@ -1,12 +1,16 @@ -require 'abstract_unit' -require 'stringio' +require "abstract_unit" +require "stringio" class ActionController::TestRequestTest < ActionController::TestCase - def test_test_request_has_session_options_initialized assert @request.session_options end + def test_mutating_session_options_does_not_affect_default_options + @request.session_options[:myparam] = 123 + assert_equal nil, ActionController::TestSession::DEFAULT_OPTIONS[:myparam] + 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], @@ -19,5 +23,4 @@ class ActionController::TestRequestTest < ActionController::TestCase "Missing rack session option #{option} 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 1984ad8825..90d5ab3c67 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -1,42 +1,42 @@ -require 'abstract_unit' +require "abstract_unit" require "active_support/log_subscriber/test_helper" # common controller actions module RequestForgeryProtectionActions def index - render :inline => "<%= form_tag('/') {} %>" + render inline: "<%= form_tag('/') {} %>" end def show_button - render :inline => "<%= button_to('New', '/') %>" + render inline: "<%= button_to('New', '/') %>" end def unsafe - render plain: 'pwn' + render plain: "pwn" end def meta - render :inline => "<%= csrf_meta_tags %>" + render inline: "<%= csrf_meta_tags %>" end def form_for_remote - render :inline => "<%= form_for(:some_resource, :remote => true ) {} %>" + render inline: "<%= form_for(:some_resource, :remote => true ) {} %>" end def form_for_remote_with_token - render :inline => "<%= form_for(:some_resource, :remote => true, :authenticity_token => true ) {} %>" + render inline: "<%= form_for(:some_resource, :remote => true, :authenticity_token => true ) {} %>" end def form_for_with_token - render :inline => "<%= form_for(:some_resource, :authenticity_token => true ) {} %>" + render inline: "<%= form_for(:some_resource, :authenticity_token => true ) {} %>" end def form_for_remote_with_external_token - render :inline => "<%= form_for(:some_resource, :remote => true, :authenticity_token => 'external_token') {} %>" + render inline: "<%= form_for(:some_resource, :remote => true, :authenticity_token => 'external_token') {} %>" end def same_origin_js - render js: 'foo();' + render js: "foo();" end def negotiate_same_origin @@ -52,30 +52,29 @@ module RequestForgeryProtectionActions def negotiate_cross_origin negotiate_same_origin end - end # sample controllers class RequestForgeryProtectionControllerUsingResetSession < ActionController::Base include RequestForgeryProtectionActions - protect_from_forgery :only => %w(index meta same_origin_js negotiate_same_origin), :with => :reset_session + protect_from_forgery only: %w(index meta same_origin_js negotiate_same_origin), with: :reset_session end class RequestForgeryProtectionControllerUsingException < ActionController::Base include RequestForgeryProtectionActions - protect_from_forgery :only => %w(index meta same_origin_js negotiate_same_origin), :with => :exception + protect_from_forgery only: %w(index meta same_origin_js negotiate_same_origin), with: :exception end class RequestForgeryProtectionControllerUsingNullSession < ActionController::Base - protect_from_forgery :with => :null_session + protect_from_forgery with: :null_session def signed - cookies.signed[:foo] = 'bar' + cookies.signed[:foo] = "bar" head :ok end def encrypted - cookies.encrypted[:foo] = 'bar' + cookies.encrypted[:foo] = "bar" head :ok end @@ -90,65 +89,68 @@ class PrependProtectForgeryBaseController < ActionController::Base attr_accessor :called_callbacks def index - render inline: 'OK' + render inline: "OK" end protected - def add_called_callback(name) - @called_callbacks ||= [] - @called_callbacks << name - end - + def add_called_callback(name) + @called_callbacks ||= [] + @called_callbacks << name + end - def custom_action - add_called_callback("custom_action") - end + def custom_action + add_called_callback("custom_action") + end - def verify_authenticity_token - add_called_callback("verify_authenticity_token") - end + def verify_authenticity_token + add_called_callback("verify_authenticity_token") + end end class FreeCookieController < RequestForgeryProtectionControllerUsingResetSession self.allow_forgery_protection = false def index - render :inline => "<%= form_tag('/') {} %>" + render inline: "<%= form_tag('/') {} %>" end def show_button - render :inline => "<%= button_to('New', '/') %>" + render inline: "<%= button_to('New', '/') %>" end end class CustomAuthenticityParamController < RequestForgeryProtectionControllerUsingResetSession def form_authenticity_param - 'foobar' + "foobar" end end class PerFormTokensController < ActionController::Base - protect_from_forgery :with => :exception + protect_from_forgery with: :exception self.per_form_csrf_tokens = true def index - render inline: "<%= form_tag (params[:form_path] || '/per_form_tokens/post_one'), method: (params[:form_method] || :post) %>" + render inline: "<%= form_tag (params[:form_path] || '/per_form_tokens/post_one'), method: params[:form_method] %>" + end + + def button_to + render inline: "<%= button_to 'Button', (params[:form_path] || '/per_form_tokens/post_one'), method: params[:form_method] %>" end def post_one - render plain: '' + render plain: "" end def post_two - render plain: '' + render plain: "" end end # common test methods module RequestForgeryProtectionTests def setup - @token = Base64.strict_encode64('railstestrailstestrailstestrails') + @token = Base64.strict_encode64("railstestrailstestrailstestrails") @old_request_forgery_protection_token = ActionController::Base.request_forgery_protection_token ActionController::Base.request_forgery_protection_token = :custom_authenticity_token end @@ -162,7 +164,7 @@ module RequestForgeryProtectionTests assert_not_blocked do get :index end - assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token + assert_select "form>input[name=?][value=?]", "custom_authenticity_token", @token end end @@ -171,7 +173,7 @@ module RequestForgeryProtectionTests assert_not_blocked do get :show_button end - assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token + assert_select "form>input[name=?][value=?]", "custom_authenticity_token", @token end end @@ -202,7 +204,7 @@ module RequestForgeryProtectionTests assert_not_blocked do get :form_for_remote_with_external_token end - assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', 'external_token' + assert_select "form>input[name=?][value=?]", "custom_authenticity_token", "external_token" ensure ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original end @@ -212,7 +214,7 @@ module RequestForgeryProtectionTests assert_not_blocked do get :form_for_remote_with_external_token end - assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', 'external_token' + assert_select "form>input[name=?][value=?]", "custom_authenticity_token", "external_token" end def test_should_render_form_with_token_tag_if_remote_and_authenticity_token_requested @@ -220,7 +222,7 @@ module RequestForgeryProtectionTests assert_not_blocked do get :form_for_remote_with_token end - assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token + assert_select "form>input[name=?][value=?]", "custom_authenticity_token", @token end end @@ -229,7 +231,7 @@ module RequestForgeryProtectionTests assert_not_blocked do get :form_for_with_token end - assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token + assert_select "form>input[name=?][value=?]", "custom_authenticity_token", @token end end @@ -250,7 +252,7 @@ module RequestForgeryProtectionTests end def test_should_not_allow_post_without_token_irrespective_of_format - assert_blocked { post :index, format: 'xml' } + assert_blocked { post :index, format: "xml" } end def test_should_not_allow_patch_without_token @@ -299,25 +301,25 @@ module RequestForgeryProtectionTests def test_should_allow_post_with_token_in_header session[:_csrf_token] = @token - @request.env['HTTP_X_CSRF_TOKEN'] = @token + @request.env["HTTP_X_CSRF_TOKEN"] = @token assert_not_blocked { post :index } end def test_should_allow_delete_with_token_in_header session[:_csrf_token] = @token - @request.env['HTTP_X_CSRF_TOKEN'] = @token + @request.env["HTTP_X_CSRF_TOKEN"] = @token assert_not_blocked { delete :index } end def test_should_allow_patch_with_token_in_header session[:_csrf_token] = @token - @request.env['HTTP_X_CSRF_TOKEN'] = @token + @request.env["HTTP_X_CSRF_TOKEN"] = @token assert_not_blocked { patch :index } end def test_should_allow_put_with_token_in_header session[:_csrf_token] = @token - @request.env['HTTP_X_CSRF_TOKEN'] = @token + @request.env["HTTP_X_CSRF_TOKEN"] = @token assert_not_blocked { put :index } end @@ -326,7 +328,7 @@ module RequestForgeryProtectionTests session[:_csrf_token] = @token @controller.stub :form_authenticity_token, @token do assert_not_blocked do - @request.set_header 'HTTP_ORIGIN', 'http://test.host' + @request.set_header "HTTP_ORIGIN", "http://test.host" post :index, params: { custom_authenticity_token: @token } end end @@ -349,7 +351,7 @@ module RequestForgeryProtectionTests session[:_csrf_token] = @token @controller.stub :form_authenticity_token, @token do assert_blocked do - @request.set_header 'HTTP_ORIGIN', 'http://bad.host' + @request.set_header "HTTP_ORIGIN", "http://bad.host" post :index, params: { custom_authenticity_token: @token } end end @@ -389,50 +391,81 @@ module RequestForgeryProtectionTests def test_should_only_allow_same_origin_js_get_with_xhr_header assert_cross_origin_blocked { get :same_origin_js } - assert_cross_origin_blocked { get :same_origin_js, format: 'js' } + assert_cross_origin_blocked { get :same_origin_js, format: "js" } assert_cross_origin_blocked do - @request.accept = 'text/javascript' + @request.accept = "text/javascript" get :negotiate_same_origin end assert_cross_origin_not_blocked { get :same_origin_js, xhr: true } - assert_cross_origin_not_blocked { get :same_origin_js, xhr: true, format: 'js'} + assert_cross_origin_not_blocked { get :same_origin_js, xhr: true, format: "js" } assert_cross_origin_not_blocked do - @request.accept = 'text/javascript' + @request.accept = "text/javascript" get :negotiate_same_origin, xhr: true end end + def test_should_warn_on_not_same_origin_js + old_logger = ActionController::Base.logger + logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new + ActionController::Base.logger = logger + + begin + assert_cross_origin_blocked { get :same_origin_js } + + assert_equal 1, logger.logged(:warn).size + assert_match(/<script> tag on another site requested protected JavaScript/, logger.logged(:warn).last) + ensure + ActionController::Base.logger = old_logger + end + end + + def test_should_not_warn_if_csrf_logging_disabled_and_not_same_origin_js + old_logger = ActionController::Base.logger + logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new + ActionController::Base.logger = logger + ActionController::Base.log_warning_on_csrf_failure = false + + begin + assert_cross_origin_blocked { get :same_origin_js } + + assert_equal 0, logger.logged(:warn).size + ensure + ActionController::Base.logger = old_logger + ActionController::Base.log_warning_on_csrf_failure = true + end + end + # Allow non-GET requests since GET is all a remote <script> tag can muster. def test_should_allow_non_get_js_without_xhr_header session[:_csrf_token] = @token assert_cross_origin_not_blocked { post :same_origin_js, params: { custom_authenticity_token: @token } } - assert_cross_origin_not_blocked { post :same_origin_js, params: { format: 'js', custom_authenticity_token: @token } } + assert_cross_origin_not_blocked { post :same_origin_js, params: { format: "js", custom_authenticity_token: @token } } assert_cross_origin_not_blocked do - @request.accept = 'text/javascript' - post :negotiate_same_origin, params: { custom_authenticity_token: @token} + @request.accept = "text/javascript" + post :negotiate_same_origin, params: { custom_authenticity_token: @token } end end def test_should_only_allow_cross_origin_js_get_without_xhr_header_if_protection_disabled assert_cross_origin_not_blocked { get :cross_origin_js } - assert_cross_origin_not_blocked { get :cross_origin_js, format: 'js' } + assert_cross_origin_not_blocked { get :cross_origin_js, format: "js" } assert_cross_origin_not_blocked do - @request.accept = 'text/javascript' + @request.accept = "text/javascript" get :negotiate_cross_origin end assert_cross_origin_not_blocked { get :cross_origin_js, xhr: true } - assert_cross_origin_not_blocked { get :cross_origin_js, xhr: true, format: 'js' } + assert_cross_origin_not_blocked { get :cross_origin_js, xhr: true, format: "js" } assert_cross_origin_not_blocked do - @request.accept = 'text/javascript' + @request.accept = "text/javascript" get :negotiate_cross_origin, xhr: true end end def test_should_not_raise_error_if_token_is_not_a_string assert_blocked do - patch :index, params: { custom_authenticity_token: { foo: 'bar' } } + patch :index, params: { custom_authenticity_token: { foo: "bar" } } end end @@ -474,20 +507,11 @@ end class RequestForgeryProtectionControllerUsingResetSessionTest < ActionController::TestCase include RequestForgeryProtectionTests - setup do - @old_request_forgery_protection_token = ActionController::Base.request_forgery_protection_token - ActionController::Base.request_forgery_protection_token = :custom_authenticity_token - end - - teardown do - ActionController::Base.request_forgery_protection_token = @old_request_forgery_protection_token - end - - test 'should emit a csrf-param meta tag and a csrf-token meta tag' do - @controller.stub :form_authenticity_token, @token + '<=?' do + test "should emit a csrf-param meta tag and a csrf-token meta tag" do + @controller.stub :form_authenticity_token, @token + "<=?" do get :meta - assert_select 'meta[name=?][content=?]', 'csrf-param', 'custom_authenticity_token' - assert_select 'meta[name=?]', 'csrf-token' + assert_select "meta[name=?][content=?]", "csrf-param", "custom_authenticity_token" + assert_select "meta[name=?]", "csrf-token" regexp = "#{@token}<=\?" assert_match(/#{regexp}/, @response.body) end @@ -497,7 +521,7 @@ end class RequestForgeryProtectionControllerUsingNullSessionTest < ActionController::TestCase class NullSessionDummyKeyGenerator def generate_key(secret) - '03312270731a2ed0d11ed091c2338a06' + "03312270731a2ed0d11ed091c2338a06" end end @@ -505,17 +529,17 @@ class RequestForgeryProtectionControllerUsingNullSessionTest < ActionController: @request.env[ActionDispatch::Cookies::GENERATOR_KEY] = NullSessionDummyKeyGenerator.new end - test 'should allow to set signed cookies' do + test "should allow to set signed cookies" do post :signed assert_response :ok end - test 'should allow to set encrypted cookies' do + test "should allow to set encrypted cookies" do post :encrypted assert_response :ok end - test 'should allow reset_session' do + test "should allow reset_session" do post :try_to_reset_session assert_response :ok end @@ -575,26 +599,26 @@ class FreeCookieControllerTest < ActionController::TestCase def test_should_not_render_form_with_token_tag SecureRandom.stub :base64, @token do get :index - assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token, false + assert_select "form>div>input[name=?][value=?]", "authenticity_token", @token, false end end def test_should_not_render_button_to_with_token_tag SecureRandom.stub :base64, @token do get :show_button - assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token, false + assert_select "form>div>input[name=?][value=?]", "authenticity_token", @token, false end end def test_should_allow_all_methods_without_token SecureRandom.stub :base64, @token do [:post, :patch, :put, :delete].each do |method| - assert_nothing_raised { send(method, :index)} + assert_nothing_raised { send(method, :index) } end end end - test 'should not emit a csrf-token meta tag' do + test "should not emit a csrf-token meta tag" do SecureRandom.stub :base64, @token do get :meta assert @response.body.blank? @@ -621,7 +645,7 @@ class CustomAuthenticityParamControllerTest < ActionController::TestCase ActionController::Base.logger = @logger begin @controller.stub :valid_authenticity_token?, :true do - post :index, params: { custom_token_name: 'foobar' } + post :index, params: { custom_token_name: "foobar" } assert_equal 0, @logger.logged(:warn).size end ensure @@ -633,7 +657,7 @@ class CustomAuthenticityParamControllerTest < ActionController::TestCase ActionController::Base.logger = @logger begin - post :index, params: { custom_token_name: 'bazqux' } + post :index, params: { custom_token_name: "bazqux" } assert_equal 1, @logger.logged(:warn).size ensure ActionController::Base.logger = @old_logger @@ -642,30 +666,33 @@ class CustomAuthenticityParamControllerTest < ActionController::TestCase end class PerFormTokensControllerTest < ActionController::TestCase + def setup + @old_request_forgery_protection_token = ActionController::Base.request_forgery_protection_token + ActionController::Base.request_forgery_protection_token = :custom_authenticity_token + end + + def teardown + ActionController::Base.request_forgery_protection_token = @old_request_forgery_protection_token + end + def test_per_form_token_is_same_size_as_global_token get :index expected = ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH - actual = @controller.send(:per_form_csrf_token, session, '/path', 'post').size + actual = @controller.send(:per_form_csrf_token, session, "/path", "post").size assert_equal expected, actual end def test_accepts_token_for_correct_path_and_method get :index - form_token = nil - assert_select 'input[name=custom_authenticity_token]' do |elts| - form_token = elts.first['value'] - assert_not_nil form_token - end + form_token = assert_presence_and_fetch_form_csrf_token - actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token)) - expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post') - assert_equal expected, actual + assert_matches_session_token_on_server form_token # This is required because PATH_INFO isn't reset between requests. - @request.env['PATH_INFO'] = '/per_form_tokens/post_one' + @request.env["PATH_INFO"] = "/per_form_tokens/post_one" assert_nothing_raised do - post :post_one, params: {custom_authenticity_token: form_token} + post :post_one, params: { custom_authenticity_token: form_token } end assert_response :success end @@ -673,40 +700,72 @@ class PerFormTokensControllerTest < ActionController::TestCase def test_rejects_token_for_incorrect_path get :index - form_token = nil - assert_select 'input[name=custom_authenticity_token]' do |elts| - form_token = elts.first['value'] - assert_not_nil form_token - end + form_token = assert_presence_and_fetch_form_csrf_token - actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token)) - expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post') - assert_equal expected, actual + assert_matches_session_token_on_server form_token # This is required because PATH_INFO isn't reset between requests. - @request.env['PATH_INFO'] = '/per_form_tokens/post_two' + @request.env["PATH_INFO"] = "/per_form_tokens/post_two" assert_raises(ActionController::InvalidAuthenticityToken) do - post :post_two, params: {custom_authenticity_token: form_token} + post :post_two, params: { custom_authenticity_token: form_token } end end def test_rejects_token_for_incorrect_method get :index - form_token = nil - assert_select 'input[name=custom_authenticity_token]' do |elts| - form_token = elts.first['value'] - assert_not_nil form_token + form_token = assert_presence_and_fetch_form_csrf_token + + assert_matches_session_token_on_server form_token + + # This is required because PATH_INFO isn't reset between requests. + @request.env["PATH_INFO"] = "/per_form_tokens/post_one" + assert_raises(ActionController::InvalidAuthenticityToken) do + patch :post_one, params: { custom_authenticity_token: form_token } end + end - actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token)) - expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post') - assert_equal expected, actual + def test_rejects_token_for_incorrect_method_button_to + get :button_to, params: { form_method: "delete" } + + form_token = assert_presence_and_fetch_form_csrf_token + + assert_matches_session_token_on_server form_token, "delete" # This is required because PATH_INFO isn't reset between requests. - @request.env['PATH_INFO'] = '/per_form_tokens/post_one' + @request.env["PATH_INFO"] = "/per_form_tokens/post_one" assert_raises(ActionController::InvalidAuthenticityToken) do - patch :post_one, params: {custom_authenticity_token: form_token} + patch :post_one, params: { custom_authenticity_token: form_token } + end + end + + test "Accepts proper token for implicit post method on button_to tag" do + get :button_to + + form_token = assert_presence_and_fetch_form_csrf_token + + assert_matches_session_token_on_server form_token, "post" + + # This is required because PATH_INFO isn't reset between requests. + @request.env["PATH_INFO"] = "/per_form_tokens/post_one" + assert_nothing_raised do + post :post_one, params: { custom_authenticity_token: form_token } + end + end + + %w{delete post patch}.each do |verb| + test "Accepts proper token for #{verb} method on button_to tag" do + get :button_to, params: { form_method: verb } + + form_token = assert_presence_and_fetch_form_csrf_token + + assert_matches_session_token_on_server form_token, verb + + # This is required because PATH_INFO isn't reset between requests. + @request.env["PATH_INFO"] = "/per_form_tokens/post_one" + assert_nothing_raised do + send verb, :post_one, params: { custom_authenticity_token: form_token } + end end end @@ -716,47 +775,50 @@ class PerFormTokensControllerTest < ActionController::TestCase token = @controller.send(:form_authenticity_token) # This is required because PATH_INFO isn't reset between requests. - @request.env['PATH_INFO'] = '/per_form_tokens/post_one' + @request.env["PATH_INFO"] = "/per_form_tokens/post_one" assert_nothing_raised do - post :post_one, params: {custom_authenticity_token: token} + post :post_one, params: { custom_authenticity_token: token } end assert_response :success end def test_ignores_params - get :index, params: {form_path: '/per_form_tokens/post_one?foo=bar'} + get :index, params: { form_path: "/per_form_tokens/post_one?foo=bar" } - form_token = nil - assert_select 'input[name=custom_authenticity_token]' do |elts| - form_token = elts.first['value'] - assert_not_nil form_token - end + form_token = assert_presence_and_fetch_form_csrf_token - actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token)) - expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post') - assert_equal expected, actual + assert_matches_session_token_on_server form_token # This is required because PATH_INFO isn't reset between requests. - @request.env['PATH_INFO'] = '/per_form_tokens/post_one?foo=baz' + @request.env["PATH_INFO"] = "/per_form_tokens/post_one?foo=baz" assert_nothing_raised do - post :post_one, params: {custom_authenticity_token: form_token, baz: 'foo'} + post :post_one, params: { custom_authenticity_token: form_token, baz: "foo" } end assert_response :success end def test_ignores_trailing_slash_during_generation - get :index, params: {form_path: '/per_form_tokens/post_one/'} + get :index, params: { form_path: "/per_form_tokens/post_one/" } - form_token = nil - assert_select 'input[name=custom_authenticity_token]' do |elts| - form_token = elts.first['value'] - assert_not_nil form_token + form_token = assert_presence_and_fetch_form_csrf_token + + # This is required because PATH_INFO isn't reset between requests. + @request.env["PATH_INFO"] = "/per_form_tokens/post_one" + assert_nothing_raised do + post :post_one, params: { custom_authenticity_token: form_token } end + assert_response :success + end + + def test_ignores_origin_during_generation + get :index, params: { form_path: "https://example.com/per_form_tokens/post_one/" } + + form_token = assert_presence_and_fetch_form_csrf_token # This is required because PATH_INFO isn't reset between requests. - @request.env['PATH_INFO'] = '/per_form_tokens/post_one' + @request.env["PATH_INFO"] = "/per_form_tokens/post_one" assert_nothing_raised do - post :post_one, params: {custom_authenticity_token: form_token} + post :post_one, params: { custom_authenticity_token: form_token } end assert_response :success end @@ -764,34 +826,40 @@ class PerFormTokensControllerTest < ActionController::TestCase def test_ignores_trailing_slash_during_validation get :index - form_token = nil - assert_select 'input[name=custom_authenticity_token]' do |elts| - form_token = elts.first['value'] - assert_not_nil form_token - end + form_token = assert_presence_and_fetch_form_csrf_token # This is required because PATH_INFO isn't reset between requests. - @request.env['PATH_INFO'] = '/per_form_tokens/post_one/' + @request.env["PATH_INFO"] = "/per_form_tokens/post_one/" assert_nothing_raised do - post :post_one, params: {custom_authenticity_token: form_token} + post :post_one, params: { custom_authenticity_token: form_token } end assert_response :success end def test_method_is_case_insensitive - get :index, params: {form_method: "POST"} - - form_token = nil - assert_select 'input[name=custom_authenticity_token]' do |elts| - form_token = elts.first['value'] - assert_not_nil form_token - end + get :index, params: { form_method: "POST" } + form_token = assert_presence_and_fetch_form_csrf_token # This is required because PATH_INFO isn't reset between requests. - @request.env['PATH_INFO'] = '/per_form_tokens/post_one/' + @request.env["PATH_INFO"] = "/per_form_tokens/post_one/" assert_nothing_raised do - post :post_one, params: {custom_authenticity_token: form_token} + post :post_one, params: { custom_authenticity_token: form_token } end assert_response :success end + + private + def assert_presence_and_fetch_form_csrf_token + assert_select 'input[name="custom_authenticity_token"]' do |input| + form_csrf_token = input.first["value"] + assert_not_nil form_csrf_token + return form_csrf_token + end + end + + def assert_matches_session_token_on_server(form_token, method = "post") + actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token)) + expected = @controller.send(:per_form_csrf_token, session, "/per_form_tokens/post_one", method) + assert_equal expected, actual + end end diff --git a/actionpack/test/controller/required_params_test.rb b/actionpack/test/controller/required_params_test.rb index b6efcd6f9a..dd07c2486b 100644 --- a/actionpack/test/controller/required_params_test.rb +++ b/actionpack/test/controller/required_params_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class BooksController < ActionController::Base def create @@ -32,7 +32,6 @@ class ActionControllerRequiredParamsTest < ActionController::TestCase end class ParametersRequireTest < ActiveSupport::TestCase - test "required parameters should accept and return false value" do assert_equal(false, ActionController::Parameters.new(person: false).require(:person)) end @@ -50,17 +49,17 @@ class ParametersRequireTest < ActiveSupport::TestCase end test "require array when all required params are present" do - safe_params = ActionController::Parameters.new(person: {first_name: 'Gaurish', title: 'Mjallo', city: 'Barcelona'}) + safe_params = ActionController::Parameters.new(person: { first_name: "Gaurish", title: "Mjallo", city: "Barcelona" }) .require(:person) .require([:first_name, :title]) assert_kind_of Array, safe_params - assert_equal ['Gaurish', 'Mjallo'], safe_params + assert_equal ["Gaurish", "Mjallo"], safe_params end test "require array when a required param is missing" do assert_raises(ActionController::ParameterMissing) do - ActionController::Parameters.new(person: {first_name: 'Gaurish', title: nil}) + ActionController::Parameters.new(person: { first_name: "Gaurish", title: nil }) .require(:person) .require([:first_name, :title]) end @@ -73,9 +72,9 @@ class ParametersRequireTest < ActiveSupport::TestCase assert params.value?("cinco") end - test "Deprecated methods are deprecated" do - assert_deprecated do - ActionController::Parameters.new(foo: "bar").merge!({bar: "foo"}) + test "to_query is not supported" do + assert_raises(NoMethodError) do + ActionController::Parameters.new(foo: "bar").to_param end end end diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb index f42bef883f..a98e6479b7 100644 --- a/actionpack/test/controller/rescue_test.rb +++ b/actionpack/test/controller/rescue_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class RescueController < ActionController::Base class NotAuthorized < StandardError @@ -34,47 +34,47 @@ class RescueController < ActionController::Base # We use a fully-qualified name in some strings, and a relative constant # name in some other to test correct handling of both cases. - rescue_from NotAuthorized, :with => :deny_access - rescue_from 'RescueController::NotAuthorizedToRescueAsString', :with => :deny_access + rescue_from NotAuthorized, with: :deny_access + rescue_from "RescueController::NotAuthorizedToRescueAsString", with: :deny_access - rescue_from RecordInvalid, :with => :show_errors - rescue_from 'RescueController::RecordInvalidToRescueAsString', :with => :show_errors + rescue_from RecordInvalid, with: :show_errors + rescue_from "RescueController::RecordInvalidToRescueAsString", with: :show_errors - rescue_from NotAllowed, :with => proc { head :forbidden } - rescue_from 'RescueController::NotAllowedToRescueAsString', :with => proc { head :forbidden } + rescue_from NotAllowed, with: proc { head :forbidden } + rescue_from "RescueController::NotAllowedToRescueAsString", with: proc { head :forbidden } rescue_from InvalidRequest, with: proc { |exception| render plain: exception.message } - rescue_from 'InvalidRequestToRescueAsString', with: proc { |exception| render plain: exception.message } + rescue_from "InvalidRequestToRescueAsString", with: proc { |exception| render plain: exception.message } rescue_from BadGateway do head 502 end - rescue_from 'BadGatewayToRescueAsString' do + rescue_from "BadGatewayToRescueAsString" do head 502 end rescue_from ResourceUnavailable do |exception| render plain: exception.message end - rescue_from 'ResourceUnavailableToRescueAsString' do |exception| + rescue_from "ResourceUnavailableToRescueAsString" do |exception| render plain: exception.message end rescue_from ActionView::TemplateError do - render plain: 'action_view templater error' + render plain: "action_view templater error" end rescue_from IOError do - render plain: 'io error' + render plain: "io error" end - before_action(only: :before_action_raises) { raise 'umm nice' } + before_action(only: :before_action_raises) { raise "umm nice" } def before_action_raises end def raises - render plain: 'already rendered' + render plain: "already rendered" raise "don't panic!" end @@ -131,20 +131,22 @@ class RescueController < ActionController::Base def missing_template end - def io_error_in_view - begin - raise IOError.new('this is io error') - rescue - raise ActionView::TemplateError.new(nil) - end + def exception_with_more_specific_handler_for_wrapper + raise RecordInvalid + rescue + raise NotAuthorized end - def zero_division_error_in_view - begin - raise ZeroDivisionError.new('this is zero division error') - rescue - raise ActionView::TemplateError.new(nil) - end + def exception_with_more_specific_handler_for_cause + raise NotAuthorized + rescue + raise RecordInvalid + end + + def exception_with_no_handler_for_wrapper + raise RecordInvalid + rescue + raise RangeError end protected @@ -158,7 +160,6 @@ class RescueController < ActionController::Base end class ExceptionInheritanceRescueController < ActionController::Base - class ParentException < StandardError end @@ -168,9 +169,9 @@ class ExceptionInheritanceRescueController < ActionController::Base class GrandchildException < ChildException end - rescue_from ChildException, :with => lambda { head :ok } - rescue_from ParentException, :with => lambda { head :created } - rescue_from GrandchildException, :with => lambda { head :no_content } + rescue_from ChildException, with: lambda { head :ok } + rescue_from ParentException, with: lambda { head :created } + rescue_from GrandchildException, with: lambda { head :no_content } def raise_parent_exception raise ParentException @@ -204,7 +205,7 @@ class ControllerInheritanceRescueController < ExceptionInheritanceRescueControll class SecondExceptionInChildController < StandardError end - rescue_from FirstExceptionInChildController, 'SecondExceptionInChildController', :with => lambda { head :gone } + rescue_from FirstExceptionInChildController, "SecondExceptionInChildController", with: lambda { head :gone } def raise_first_exception_in_child_controller raise FirstExceptionInChildController @@ -233,17 +234,6 @@ class ControllerInheritanceRescueControllerTest < ActionController::TestCase end class RescueControllerTest < ActionController::TestCase - - def test_io_error_in_view - get :io_error_in_view - assert_equal 'io error', @response.body - end - - def test_zero_division_error_in_view - get :zero_division_error_in_view - assert_equal 'action_view templater error', @response.body - end - def test_rescue_handler get :not_authorized assert_response :forbidden @@ -258,7 +248,6 @@ class RescueControllerTest < ActionController::TestCase get :record_invalid end end - def test_rescue_handler_with_argument_as_string assert_called_with @controller, :show_errors, [Exception] do get :record_invalid_raise_as_string @@ -296,21 +285,35 @@ class RescueControllerTest < ActionController::TestCase get :resource_unavailable assert_equal "RescueController::ResourceUnavailable", @response.body end - def test_block_rescue_handler_with_argument_as_string get :resource_unavailable_raise_as_string assert_equal "RescueController::ResourceUnavailableToRescueAsString", @response.body end + + test "rescue when wrapper has more specific handler than cause" do + get :exception_with_more_specific_handler_for_wrapper + assert_response :forbidden + end + + test "rescue when cause has more specific handler than wrapper" do + get :exception_with_more_specific_handler_for_cause + assert_response :unprocessable_entity + end + + test "rescue when cause has handler, but wrapper doesnt" do + get :exception_with_no_handler_for_wrapper + assert_response :unprocessable_entity + end end class RescueTest < ActionDispatch::IntegrationTest class TestController < ActionController::Base class RecordInvalid < StandardError def message - 'invalid' + "invalid" end end - rescue_from RecordInvalid, :with => :show_errors + rescue_from RecordInvalid, with: :show_errors def foo render plain: "foo" @@ -321,7 +324,7 @@ class RescueTest < ActionDispatch::IntegrationTest end def b00m - raise 'b00m' + raise "b00m" end protected @@ -330,17 +333,17 @@ class RescueTest < ActionDispatch::IntegrationTest end end - test 'normal request' do + test "normal request" do with_test_routing do - get '/foo' - assert_equal 'foo', response.body + get "/foo" + assert_equal "foo", response.body end end - test 'rescue exceptions inside controller' do + test "rescue exceptions inside controller" do with_test_routing do - get '/invalid' - assert_equal 'invalid', response.body + get "/invalid" + assert_equal "invalid", response.body end end @@ -349,9 +352,9 @@ class RescueTest < ActionDispatch::IntegrationTest def with_test_routing with_routing do |set| set.draw do - get 'foo', :to => ::RescueTest::TestController.action(:foo) - get 'invalid', :to => ::RescueTest::TestController.action(:invalid) - get 'b00m', :to => ::RescueTest::TestController.action(:b00m) + get "foo", to: ::RescueTest::TestController.action(:foo) + get "invalid", to: ::RescueTest::TestController.action(:invalid) + get "b00m", to: ::RescueTest::TestController.action(:b00m) end yield end diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 4490abf7b2..b572e7e8d5 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -1,10 +1,24 @@ -require 'abstract_unit' -require 'active_support/core_ext/object/try' -require 'active_support/core_ext/object/with_options' -require 'active_support/core_ext/array/extract_options' +require "abstract_unit" +require "active_support/core_ext/object/try" +require "active_support/core_ext/object/with_options" +require "active_support/core_ext/array/extract_options" -class ResourcesTest < ActionController::TestCase +class AdminController < ResourcesController; end +class MessagesController < ResourcesController; end +class ProductsController < ResourcesController; end +class ThreadsController < ResourcesController; end + +module Backoffice + class ProductsController < ResourcesController; end + class ImagesController < ResourcesController; end + module Admin + class ProductsController < ResourcesController; end + class ImagesController < ResourcesController; end + end +end + +class ResourcesTest < ActionController::TestCase def test_default_restful_routes with_restful_routing :messages do assert_simply_restful_for :messages @@ -12,43 +26,43 @@ class ResourcesTest < ActionController::TestCase end def test_override_paths_for_member_and_collection_methods - collection_methods = { :rss => :get, :reorder => :post, :csv => :post } - member_methods = { :rss => :get, :atom => :get, :upload => :post, :fix => :post } - path_names = {:new => 'nuevo', :rss => 'canal', :fix => 'corrigir' } + collection_methods = { rss: :get, reorder: :post, csv: :post } + member_methods = { rss: :get, atom: :get, upload: :post, fix: :post } + path_names = { new: "nuevo", rss: "canal", fix: "corrigir" } with_restful_routing :messages, - :collection => collection_methods, - :member => member_methods, - :path_names => path_names do + collection: collection_methods, + member: member_methods, + path_names: path_names do assert_restful_routes_for :messages, - :collection => collection_methods, - :member => member_methods, - :path_names => path_names do |options| + collection: collection_methods, + member: member_methods, + path_names: path_names do |options| member_methods.each do |action, method| - assert_recognizes(options.merge(:action => action.to_s, :id => '1'), - :path => "/messages/1/#{path_names[action] || action}", - :method => method) + assert_recognizes(options.merge(action: action.to_s, id: "1"), + path: "/messages/1/#{path_names[action] || action}", + method: method) end collection_methods.each do |action, method| - assert_recognizes(options.merge(:action => action.to_s), - :path => "/messages/#{path_names[action] || action}", - :method => method) + assert_recognizes(options.merge(action: action.to_s), + path: "/messages/#{path_names[action] || action}", + method: method) end end assert_restful_named_routes_for :messages, - :collection => collection_methods, - :member => member_methods, - :path_names => path_names do |options| + collection: collection_methods, + member: member_methods, + path_names: path_names do |options| collection_methods.each_key do |action| - assert_named_route "/messages/#{path_names[action] || action}", "#{action}_messages_path", :action => action + assert_named_route "/messages/#{path_names[action] || action}", "#{action}_messages_path", action: action end member_methods.each_key do |action| - assert_named_route "/messages/1/#{path_names[action] || action}", "#{action}_message_path", :action => action, :id => "1" + assert_named_route "/messages/1/#{path_names[action] || action}", "#{action}_message_path", action: action, id: "1" end end @@ -63,182 +77,182 @@ class ResourcesTest < ActionController::TestCase end def test_multiple_resources_with_options - expected_options = {:controller => 'threads', :action => 'index'} + expected_options = { controller: "threads", action: "index" } with_restful_routing :messages, :comments, expected_options.slice(:controller) do - assert_recognizes(expected_options, :path => 'comments') - assert_recognizes(expected_options, :path => 'messages') + assert_recognizes(expected_options, path: "comments") + assert_recognizes(expected_options, path: "messages") end end def test_with_custom_conditions - with_restful_routing :messages, :conditions => { :subdomain => 'app' } do - assert @routes.recognize_path("/messages", :method => :get, :subdomain => 'app') + with_restful_routing :messages, conditions: { subdomain: "app" } do + assert @routes.recognize_path("/messages", method: :get, subdomain: "app") end end def test_irregular_id_with_no_constraints_should_raise_error - expected_options = {:controller => 'messages', :action => 'show', :id => '1.1.1'} + expected_options = { controller: "messages", action: "show", id: "1.1.1" } with_restful_routing :messages do assert_raise(Assertion) do - assert_recognizes(expected_options, :path => 'messages/1.1.1', :method => :get) + assert_recognizes(expected_options, path: "messages/1.1.1", method: :get) end end end def test_irregular_id_with_constraints_should_pass - expected_options = {:controller => 'messages', :action => 'show', :id => '1.1.1'} + expected_options = { controller: "messages", action: "show", id: "1.1.1" } - with_restful_routing(:messages, :constraints => {:id => /[0-9]\.[0-9]\.[0-9]/}) do - assert_recognizes(expected_options, :path => 'messages/1.1.1', :method => :get) + with_restful_routing(:messages, constraints: { id: /[0-9]\.[0-9]\.[0-9]/ }) do + assert_recognizes(expected_options, path: "messages/1.1.1", method: :get) end end def test_with_path_prefix_constraints - expected_options = {:controller => 'messages', :action => 'show', :thread_id => '1.1.1', :id => '1'} - with_restful_routing :messages, :path_prefix => '/thread/:thread_id', :constraints => {:thread_id => /[0-9]\.[0-9]\.[0-9]/} do - assert_recognizes(expected_options, :path => 'thread/1.1.1/messages/1', :method => :get) + expected_options = { controller: "messages", action: "show", thread_id: "1.1.1", id: "1" } + with_restful_routing :messages, path_prefix: "/thread/:thread_id", constraints: { thread_id: /[0-9]\.[0-9]\.[0-9]/ } do + assert_recognizes(expected_options, path: "thread/1.1.1/messages/1", method: :get) end end def test_irregular_id_constraints_should_get_passed_to_member_actions - expected_options = {:controller => 'messages', :action => 'custom', :id => '1.1.1'} + expected_options = { controller: "messages", action: "custom", id: "1.1.1" } - with_restful_routing(:messages, :member => {:custom => :get}, :constraints => {:id => /[0-9]\.[0-9]\.[0-9]/}) do - assert_recognizes(expected_options, :path => 'messages/1.1.1/custom', :method => :get) + with_restful_routing(:messages, member: { custom: :get }, constraints: { id: /[0-9]\.[0-9]\.[0-9]/ }) do + assert_recognizes(expected_options, path: "messages/1.1.1/custom", method: :get) end end def test_with_path_prefix - with_restful_routing :messages, :path_prefix => '/thread/:thread_id' do - assert_simply_restful_for :messages, :path_prefix => 'thread/5/', :options => { :thread_id => '5' } + with_restful_routing :messages, path_prefix: "/thread/:thread_id" do + assert_simply_restful_for :messages, path_prefix: "thread/5/", options: { thread_id: "5" } end end def test_multiple_with_path_prefix - with_restful_routing :messages, :comments, :path_prefix => '/thread/:thread_id' do - assert_simply_restful_for :messages, :path_prefix => 'thread/5/', :options => { :thread_id => '5' } - assert_simply_restful_for :comments, :path_prefix => 'thread/5/', :options => { :thread_id => '5' } + with_restful_routing :messages, :comments, path_prefix: "/thread/:thread_id" do + assert_simply_restful_for :messages, path_prefix: "thread/5/", options: { thread_id: "5" } + assert_simply_restful_for :comments, path_prefix: "thread/5/", options: { thread_id: "5" } end end def test_with_name_prefix - with_restful_routing :messages, :as => 'post_messages' do - assert_simply_restful_for :messages, :name_prefix => 'post_' + with_restful_routing :messages, as: "post_messages" do + assert_simply_restful_for :messages, name_prefix: "post_" end end def test_with_collection_actions - actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch } + actions = { "a" => :get, "b" => :put, "c" => :post, "d" => :delete, "e" => :patch } with_routing do |set| set.draw do resources :messages do - get :a, :on => :collection - put :b, :on => :collection - post :c, :on => :collection - delete :d, :on => :collection - patch :e, :on => :collection + get :a, on: :collection + put :b, on: :collection + post :c, on: :collection + delete :d, on: :collection + patch :e, on: :collection end end assert_restful_routes_for :messages do |options| actions.each do |action, method| - assert_recognizes(options.merge(:action => action), :path => "/messages/#{action}", :method => method) + assert_recognizes(options.merge(action: action), path: "/messages/#{action}", method: method) end end assert_restful_named_routes_for :messages do actions.each_key do |action| - assert_named_route "/messages/#{action}", "#{action}_messages_path", :action => action + assert_named_route "/messages/#{action}", "#{action}_messages_path", action: action end end end end def test_with_collection_actions_and_name_prefix - actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch } + actions = { "a" => :get, "b" => :put, "c" => :post, "d" => :delete, "e" => :patch } with_routing do |set| set.draw do - scope '/threads/:thread_id' do - resources :messages, :as => 'thread_messages' do - get :a, :on => :collection - put :b, :on => :collection - post :c, :on => :collection - delete :d, :on => :collection - patch :e, :on => :collection + scope "/threads/:thread_id" do + resources :messages, as: "thread_messages" do + get :a, on: :collection + put :b, on: :collection + post :c, on: :collection + delete :d, on: :collection + patch :e, on: :collection end end end - assert_restful_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options| + assert_restful_routes_for :messages, path_prefix: "threads/1/", name_prefix: "thread_", options: { thread_id: "1" } do |options| actions.each do |action, method| - assert_recognizes(options.merge(:action => action), :path => "/threads/1/messages/#{action}", :method => method) + assert_recognizes(options.merge(action: action), path: "/threads/1/messages/#{action}", method: method) end end - assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do + assert_restful_named_routes_for :messages, path_prefix: "threads/1/", name_prefix: "thread_", options: { thread_id: "1" } do actions.each_key do |action| - assert_named_route "/threads/1/messages/#{action}", "#{action}_thread_messages_path", :action => action + assert_named_route "/threads/1/messages/#{action}", "#{action}_thread_messages_path", action: action end end end end def test_with_collection_actions_and_name_prefix_and_member_action_with_same_name - actions = { 'a' => :get } + actions = { "a" => :get } with_routing do |set| set.draw do - scope '/threads/:thread_id' do - resources :messages, :as => 'thread_messages' do - get :a, :on => :collection - get :a, :on => :member + scope "/threads/:thread_id" do + resources :messages, as: "thread_messages" do + get :a, on: :collection + get :a, on: :member end end end - assert_restful_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options| + assert_restful_routes_for :messages, path_prefix: "threads/1/", name_prefix: "thread_", options: { thread_id: "1" } do |options| actions.each do |action, method| - assert_recognizes(options.merge(:action => action), :path => "/threads/1/messages/#{action}", :method => method) + assert_recognizes(options.merge(action: action), path: "/threads/1/messages/#{action}", method: method) end end - assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do + assert_restful_named_routes_for :messages, path_prefix: "threads/1/", name_prefix: "thread_", options: { thread_id: "1" } do actions.each_key do |action| - assert_named_route "/threads/1/messages/#{action}", "#{action}_thread_messages_path", :action => action + assert_named_route "/threads/1/messages/#{action}", "#{action}_thread_messages_path", action: action end end end end def test_with_collection_action_and_name_prefix_and_formatted - actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch } + actions = { "a" => :get, "b" => :put, "c" => :post, "d" => :delete, "e" => :patch } with_routing do |set| set.draw do - scope '/threads/:thread_id' do - resources :messages, :as => 'thread_messages' do - get :a, :on => :collection - put :b, :on => :collection - post :c, :on => :collection - delete :d, :on => :collection - patch :e, :on => :collection + scope "/threads/:thread_id" do + resources :messages, as: "thread_messages" do + get :a, on: :collection + put :b, on: :collection + post :c, on: :collection + delete :d, on: :collection + patch :e, on: :collection end end end - assert_restful_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options| + assert_restful_routes_for :messages, path_prefix: "threads/1/", name_prefix: "thread_", options: { thread_id: "1" } do |options| actions.each do |action, method| - assert_recognizes(options.merge(:action => action, :format => 'xml'), :path => "/threads/1/messages/#{action}.xml", :method => method) + assert_recognizes(options.merge(action: action, format: "xml"), path: "/threads/1/messages/#{action}.xml", method: method) end end - assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do + assert_restful_named_routes_for :messages, path_prefix: "threads/1/", name_prefix: "thread_", options: { thread_id: "1" } do actions.each_key do |action| - assert_named_route "/threads/1/messages/#{action}.xml", "#{action}_thread_messages_path", :action => action, :format => 'xml' + assert_named_route "/threads/1/messages/#{action}.xml", "#{action}_thread_messages_path", action: action, format: "xml" end end end @@ -246,11 +260,11 @@ class ResourcesTest < ActionController::TestCase def test_with_member_action [:patch, :put, :post].each do |method| - with_restful_routing :messages, :member => { :mark => method } do - mark_options = {:action => 'mark', :id => '1'} + with_restful_routing :messages, member: { mark: method } do + mark_options = { action: "mark", id: "1" } mark_path = "/messages/1/mark" assert_restful_routes_for :messages do |options| - assert_recognizes(options.merge(mark_options), :path => mark_path, :method => method) + assert_recognizes(options.merge(mark_options), path: mark_path, method: method) end assert_restful_named_routes_for :messages do @@ -261,24 +275,24 @@ class ResourcesTest < ActionController::TestCase end def test_with_member_action_and_requirement - expected_options = {:controller => 'messages', :action => 'mark', :id => '1.1.1'} + expected_options = { controller: "messages", action: "mark", id: "1.1.1" } - with_restful_routing(:messages, :constraints => {:id => /[0-9]\.[0-9]\.[0-9]/}, :member => { :mark => :get }) do - assert_recognizes(expected_options, :path => 'messages/1.1.1/mark', :method => :get) + with_restful_routing(:messages, constraints: { id: /[0-9]\.[0-9]\.[0-9]/ }, member: { mark: :get }) do + assert_recognizes(expected_options, path: "messages/1.1.1/mark", method: :get) end end def test_member_when_override_paths_for_default_restful_actions_with [:patch, :put, :post].each do |method| - with_restful_routing :messages, :member => { :mark => method }, :path_names => {:new => 'nuevo'} do - mark_options = {:action => 'mark', :id => '1', :controller => "messages"} + with_restful_routing :messages, member: { mark: method }, path_names: { new: "nuevo" } do + mark_options = { action: "mark", id: "1", controller: "messages" } mark_path = "/messages/1/mark" - assert_restful_routes_for :messages, :path_names => {:new => 'nuevo'} do |options| - assert_recognizes(options.merge(mark_options), :path => mark_path, :method => method) + assert_restful_routes_for :messages, path_names: { new: "nuevo" } do |options| + assert_recognizes(options.merge(mark_options), path: mark_path, method: method) end - assert_restful_named_routes_for :messages, :path_names => {:new => 'nuevo'} do + assert_restful_named_routes_for :messages, path_names: { new: "nuevo" } do assert_named_route mark_path, :mark_message_path, mark_options end end @@ -291,17 +305,17 @@ class ResourcesTest < ActionController::TestCase set.draw do resources :messages do member do - match :mark , :via => method - match :unmark, :via => method + match :mark , via: method + match :unmark, via: method end end end %w(mark unmark).each do |action| - action_options = {:action => action, :id => '1'} + action_options = { action: action, id: "1" } action_path = "/messages/1/#{action}" assert_restful_routes_for :messages do |options| - assert_recognizes(options.merge(action_options), :path => action_path, :method => method) + assert_recognizes(options.merge(action_options), path: action_path, method: method) end assert_restful_named_routes_for :messages do @@ -317,21 +331,21 @@ class ResourcesTest < ActionController::TestCase set.draw do resources :messages do collection do - match :search, :via => [:post, :get] + match :search, via: [:post, :get] end member do - match :toggle, :via => [:post, :get] + match :toggle, via: [:post, :get] end end end assert_restful_routes_for :messages do |options| [:get, :post].each do |method| - assert_recognizes(options.merge(:action => 'search'), :path => "/messages/search", :method => method) + assert_recognizes(options.merge(action: "search"), path: "/messages/search", method: method) end [:get, :post].each do |method| - assert_recognizes(options.merge(:action => 'toggle', :id => '1'), :path => '/messages/1/toggle', :method => method) + assert_recognizes(options.merge(action: "toggle", id: "1"), path: "/messages/1/toggle", method: method) end end end @@ -341,14 +355,14 @@ class ResourcesTest < ActionController::TestCase with_routing do |set| set.draw do resources :messages do - post :preview, :on => :new + post :preview, on: :new end end - preview_options = {:action => 'preview'} + preview_options = { action: "preview" } preview_path = "/messages/new/preview" assert_restful_routes_for :messages do |options| - assert_recognizes(options.merge(preview_options), :path => preview_path, :method => :post) + assert_recognizes(options.merge(preview_options), path: preview_path, method: :post) end assert_restful_named_routes_for :messages do @@ -360,20 +374,20 @@ class ResourcesTest < ActionController::TestCase def test_with_new_action_with_name_prefix with_routing do |set| set.draw do - scope('/threads/:thread_id') do - resources :messages, :as => "thread_messages" do - post :preview, :on => :new + scope("/threads/:thread_id") do + resources :messages, as: "thread_messages" do + post :preview, on: :new end end end - preview_options = {:action => 'preview', :thread_id => '1'} + preview_options = { action: "preview", thread_id: "1" } preview_path = "/threads/1/messages/new/preview" - assert_restful_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options| - assert_recognizes(options.merge(preview_options), :path => preview_path, :method => :post) + assert_restful_routes_for :messages, path_prefix: "threads/1/", name_prefix: "thread_", options: { thread_id: "1" } do |options| + assert_recognizes(options.merge(preview_options), path: preview_path, method: :post) end - assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do + assert_restful_named_routes_for :messages, path_prefix: "threads/1/", name_prefix: "thread_", options: { thread_id: "1" } do assert_named_route preview_path, :preview_new_thread_message_path, preview_options end end @@ -382,20 +396,20 @@ class ResourcesTest < ActionController::TestCase def test_with_formatted_new_action_with_name_prefix with_routing do |set| set.draw do - scope('/threads/:thread_id') do - resources :messages, :as => "thread_messages" do - post :preview, :on => :new + scope("/threads/:thread_id") do + resources :messages, as: "thread_messages" do + post :preview, on: :new end end end - preview_options = {:action => 'preview', :thread_id => '1', :format => 'xml'} + preview_options = { action: "preview", thread_id: "1", format: "xml" } preview_path = "/threads/1/messages/new/preview.xml" - assert_restful_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options| - assert_recognizes(options.merge(preview_options), :path => preview_path, :method => :post) + assert_restful_routes_for :messages, path_prefix: "threads/1/", name_prefix: "thread_", options: { thread_id: "1" } do |options| + assert_recognizes(options.merge(preview_options), path: preview_path, method: :post) end - assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do + assert_restful_named_routes_for :messages, path_prefix: "threads/1/", name_prefix: "thread_", options: { thread_id: "1" } do assert_named_route preview_path, :preview_new_thread_message_path, preview_options end end @@ -404,9 +418,9 @@ class ResourcesTest < ActionController::TestCase def test_override_new_method with_restful_routing :messages do assert_restful_routes_for :messages do |options| - assert_recognizes(options.merge(:action => "new"), :path => "/messages/new", :method => :get) + assert_recognizes(options.merge(action: "new"), path: "/messages/new", method: :get) assert_raise(ActionController::RoutingError) do - @routes.recognize_path("/messages/new", :method => :post) + @routes.recognize_path("/messages/new", method: :post) end end end @@ -414,13 +428,13 @@ class ResourcesTest < ActionController::TestCase with_routing do |set| set.draw do resources :messages do - match :new, :via => [:post, :get], :on => :new + match :new, via: [:post, :get], on: :new end end assert_restful_routes_for :messages do |options| - assert_recognizes(options.merge(:action => "new"), :path => "/messages/new", :method => :post) - assert_recognizes(options.merge(:action => "new"), :path => "/messages/new", :method => :get) + assert_recognizes(options.merge(action: "new"), path: "/messages/new", method: :post) + assert_recognizes(options.merge(action: "new"), path: "/messages/new", method: :get) end end end @@ -437,20 +451,20 @@ class ResourcesTest < ActionController::TestCase assert_simply_restful_for :threads assert_simply_restful_for :messages, - :name_prefix => 'thread_', - :path_prefix => 'threads/1/', - :options => { :thread_id => '1' } + name_prefix: "thread_", + path_prefix: "threads/1/", + options: { thread_id: "1" } assert_simply_restful_for :comments, - :name_prefix => 'thread_message_', - :path_prefix => 'threads/1/messages/2/', - :options => { :thread_id => '1', :message_id => '2' } + name_prefix: "thread_message_", + path_prefix: "threads/1/messages/2/", + options: { thread_id: "1", message_id: "2" } end end def test_shallow_nested_restful_routes with_routing do |set| set.draw do - resources :threads, :shallow => true do + resources :threads, shallow: true do resources :messages do resources :comments end @@ -458,17 +472,17 @@ class ResourcesTest < ActionController::TestCase end assert_simply_restful_for :threads, - :shallow => true + shallow: true assert_simply_restful_for :messages, - :name_prefix => 'thread_', - :path_prefix => 'threads/1/', - :shallow => true, - :options => { :thread_id => '1' } + name_prefix: "thread_", + path_prefix: "threads/1/", + shallow: true, + options: { thread_id: "1" } assert_simply_restful_for :comments, - :name_prefix => 'message_', - :path_prefix => 'messages/2/', - :shallow => true, - :options => { :message_id => '2' } + name_prefix: "message_", + path_prefix: "messages/2/", + shallow: true, + options: { message_id: "2" } end end @@ -477,7 +491,7 @@ class ResourcesTest < ActionController::TestCase set.draw do namespace :backoffice do namespace :admin do - resources :products, :shallow => true do + resources :products, shallow: true do resources :images end end @@ -485,18 +499,18 @@ class ResourcesTest < ActionController::TestCase end assert_simply_restful_for :products, - :controller => 'backoffice/admin/products', - :namespace => 'backoffice/admin/', - :name_prefix => 'backoffice_admin_', - :path_prefix => 'backoffice/admin/', - :shallow => true + controller: "backoffice/admin/products", + namespace: "backoffice/admin/", + name_prefix: "backoffice_admin_", + path_prefix: "backoffice/admin/", + shallow: true assert_simply_restful_for :images, - :controller => 'backoffice/admin/images', - :namespace => 'backoffice/admin/', - :name_prefix => 'backoffice_admin_product_', - :path_prefix => 'backoffice/admin/products/1/', - :shallow => true, - :options => { :product_id => '1' } + controller: "backoffice/admin/images", + namespace: "backoffice/admin/", + name_prefix: "backoffice_admin_product_", + path_prefix: "backoffice/admin/products/1/", + shallow: true, + options: { product_id: "1" } end end @@ -528,13 +542,13 @@ class ResourcesTest < ActionController::TestCase def test_should_create_nested_singleton_resource_routes with_routing do |set| set.draw do - resource :admin, :controller => 'admin' do + resource :admin, controller: "admin" do resource :account end end - assert_singleton_restful_for :admin, :controller => 'admin' - assert_singleton_restful_for :account, :name_prefix => "admin_", :path_prefix => 'admin/' + assert_singleton_restful_for :admin, controller: "admin" + assert_singleton_restful_for :account, name_prefix: "admin_", path_prefix: "admin/" end end @@ -543,14 +557,14 @@ class ResourcesTest < ActionController::TestCase with_routing do |set| set.draw do resource :account do - match :reset, :on => :member, :via => method + match :reset, on: :member, via: method end end - reset_options = {:action => 'reset'} + reset_options = { action: "reset" } reset_path = "/account/reset" assert_singleton_routes_for :account do |options| - assert_recognizes(options.merge(reset_options), :path => reset_path, :method => method) + assert_recognizes(options.merge(reset_options), path: reset_path, method: method) end assert_singleton_named_routes_for :account do @@ -565,16 +579,16 @@ class ResourcesTest < ActionController::TestCase with_routing do |set| set.draw do resource :account do - match :reset, :on => :member, :via => method - match :disable, :on => :member, :via => method + match :reset, on: :member, via: method + match :disable, on: :member, via: method end end %w(reset disable).each do |action| - action_options = {:action => action} + action_options = { action: action } action_path = "/account/#{action}" assert_singleton_routes_for :account do |options| - assert_recognizes(options.merge(action_options), :path => action_path, :method => method) + assert_recognizes(options.merge(action_options), path: action_path, method: method) end assert_singleton_named_routes_for :account do @@ -594,22 +608,22 @@ class ResourcesTest < ActionController::TestCase end assert_singleton_restful_for :account - assert_simply_restful_for :messages, :name_prefix => "account_", :path_prefix => 'account/' + assert_simply_restful_for :messages, name_prefix: "account_", path_prefix: "account/" end end def test_should_nest_resources_in_singleton_resource_with_path_scope with_routing do |set| set.draw do - scope ':site_id' do + scope ":site_id" do resource(:account) do resources :messages end end end - assert_singleton_restful_for :account, :path_prefix => '7/', :options => { :site_id => '7' } - assert_simply_restful_for :messages, :name_prefix => "account_", :path_prefix => '7/account/', :options => { :site_id => '7' } + assert_singleton_restful_for :account, path_prefix: "7/", options: { site_id: "7" } + assert_simply_restful_for :messages, name_prefix: "account_", path_prefix: "7/account/", options: { site_id: "7" } end end @@ -617,31 +631,31 @@ class ResourcesTest < ActionController::TestCase with_routing do |set| set.draw do resources :threads do - resource :admin, :controller => 'admin' + resource :admin, controller: "admin" end end assert_simply_restful_for :threads - assert_singleton_restful_for :admin, :controller => 'admin', :name_prefix => 'thread_', :path_prefix => 'threads/5/', :options => { :thread_id => '5' } + assert_singleton_restful_for :admin, controller: "admin", name_prefix: "thread_", path_prefix: "threads/5/", options: { thread_id: "5" } end end def test_should_not_allow_delete_or_patch_or_put_on_collection_path controller_name = :messages with_restful_routing controller_name do - options = { :controller => controller_name.to_s } + options = { controller: controller_name.to_s } collection_path = "/#{controller_name}" assert_raise(Assertion) do - assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :patch) + assert_recognizes(options.merge(action: "update"), path: collection_path, method: :patch) end assert_raise(Assertion) do - assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :put) + assert_recognizes(options.merge(action: "update"), path: collection_path, method: :put) end assert_raise(Assertion) do - assert_recognizes(options.merge(:action => 'destroy'), :path => collection_path, :method => :delete) + assert_recognizes(options.merge(action: "destroy"), path: collection_path, method: :delete) end end end @@ -649,15 +663,15 @@ class ResourcesTest < ActionController::TestCase def test_new_style_named_routes_for_resource with_routing do |set| set.draw do - scope '/threads/:thread_id' do - resources :messages, :as => 'thread_messages' do - get :search, :on => :collection - get :preview, :on => :new + scope "/threads/:thread_id" do + resources :messages, as: "thread_messages" do + get :search, on: :collection + get :preview, on: :new end end end - assert_simply_restful_for :messages, :name_prefix => 'thread_', :path_prefix => 'threads/1/', :options => { :thread_id => '1' } + assert_simply_restful_for :messages, name_prefix: "thread_", path_prefix: "threads/1/", options: { thread_id: "1" } assert_named_route "/threads/1/messages/search", "search_thread_messages_path", {} assert_named_route "/threads/1/messages/new", "new_thread_message_path", {} assert_named_route "/threads/1/messages/new/preview", "preview_new_thread_message_path", {} @@ -667,14 +681,14 @@ class ResourcesTest < ActionController::TestCase def test_new_style_named_routes_for_singleton_resource with_routing do |set| set.draw do - scope '/admin' do - resource :account, :as => :admin_account do - get :login, :on => :member - get :preview, :on => :new + scope "/admin" do + resource :account, as: :admin_account do + get :login, on: :member + get :preview, on: :new end end end - assert_singleton_restful_for :account, :name_prefix => 'admin_', :path_prefix => 'admin/' + assert_singleton_restful_for :account, name_prefix: "admin_", path_prefix: "admin/" assert_named_route "/admin/account/login", "login_admin_account_path", {} assert_named_route "/admin/account/new", "new_admin_account_path", {} assert_named_route "/admin/account/new/preview", "preview_new_admin_account_path", {} @@ -689,7 +703,7 @@ class ResourcesTest < ActionController::TestCase end end - assert_simply_restful_for :products, :controller => "backoffice/products", :name_prefix => 'backoffice_', :path_prefix => 'backoffice/' + assert_simply_restful_for :products, controller: "backoffice/products", name_prefix: "backoffice_", path_prefix: "backoffice/" end end @@ -703,19 +717,19 @@ class ResourcesTest < ActionController::TestCase end end - assert_simply_restful_for :products, :controller => "backoffice/admin/products", :name_prefix => 'backoffice_admin_', :path_prefix => 'backoffice/admin/' + assert_simply_restful_for :products, controller: "backoffice/admin/products", name_prefix: "backoffice_admin_", path_prefix: "backoffice/admin/" end end def test_resources_using_namespace with_routing do |set| set.draw do - namespace :backoffice, :path => nil, :as => nil do + namespace :backoffice, path: nil, as: nil do resources :products end end - assert_simply_restful_for :products, :controller => "backoffice/products" + assert_simply_restful_for :products, controller: "backoffice/products" end end @@ -729,7 +743,7 @@ class ResourcesTest < ActionController::TestCase end end - assert_simply_restful_for :images, :controller => "backoffice/images", :name_prefix => 'backoffice_product_', :path_prefix => 'backoffice/products/1/', :options => {:product_id => '1'} + assert_simply_restful_for :images, controller: "backoffice/images", name_prefix: "backoffice_product_", path_prefix: "backoffice/products/1/", options: { product_id: "1" } end end @@ -745,107 +759,107 @@ class ResourcesTest < ActionController::TestCase end end - assert_simply_restful_for :images, :controller => "backoffice/admin/images", :name_prefix => 'backoffice_admin_product_', :path_prefix => 'backoffice/admin/products/1/', :options => {:product_id => '1'} + assert_simply_restful_for :images, controller: "backoffice/admin/images", name_prefix: "backoffice_admin_product_", path_prefix: "backoffice/admin/products/1/", options: { product_id: "1" } end end def test_with_path_segment with_restful_routing :messages do assert_simply_restful_for :messages - assert_recognizes({:controller => "messages", :action => "index"}, "/messages") - assert_recognizes({:controller => "messages", :action => "index"}, "/messages/") + assert_recognizes({ controller: "messages", action: "index" }, "/messages") + assert_recognizes({ controller: "messages", action: "index" }, "/messages/") end - with_routing do |set| - set.draw do - resources :messages, :path => 'reviews' - end - assert_simply_restful_for :messages, :as => 'reviews' - assert_recognizes({:controller => "messages", :action => "index"}, "/reviews") - assert_recognizes({:controller => "messages", :action => "index"}, "/reviews/") - end + with_routing do |set| + set.draw do + resources :messages, path: "reviews" + end + assert_simply_restful_for :messages, as: "reviews" + assert_recognizes({ controller: "messages", action: "index" }, "/reviews") + assert_recognizes({ controller: "messages", action: "index" }, "/reviews/") + end end def test_multiple_with_path_segment_and_controller with_routing do |set| set.draw do - resources :products do - resources :product_reviews, :path => 'reviews', :controller => 'messages' - end - resources :tutors do - resources :tutor_reviews, :path => 'reviews', :controller => 'comments' - end + resources :products do + resources :product_reviews, path: "reviews", controller: "messages" + end + resources :tutors do + resources :tutor_reviews, path: "reviews", controller: "comments" + end end - assert_simply_restful_for :product_reviews, :controller=>'messages', :as => 'reviews', :name_prefix => 'product_', :path_prefix => 'products/1/', :options => {:product_id => '1'} - assert_simply_restful_for :tutor_reviews,:controller=>'comments', :as => 'reviews', :name_prefix => 'tutor_', :path_prefix => 'tutors/1/', :options => {:tutor_id => '1'} + assert_simply_restful_for :product_reviews, controller: "messages", as: "reviews", name_prefix: "product_", path_prefix: "products/1/", options: { product_id: "1" } + assert_simply_restful_for :tutor_reviews,controller: "comments", as: "reviews", name_prefix: "tutor_", path_prefix: "tutors/1/", options: { tutor_id: "1" } end end def test_with_path_segment_path_prefix_constraints - expected_options = {:controller => 'messages', :action => 'show', :thread_id => '1.1.1', :id => '1'} + expected_options = { controller: "messages", action: "show", thread_id: "1.1.1", id: "1" } with_routing do |set| set.draw do - scope '/thread/:thread_id', :constraints => { :thread_id => /[0-9]\.[0-9]\.[0-9]/ } do - resources :messages, :path => 'comments' + scope "/thread/:thread_id", constraints: { thread_id: /[0-9]\.[0-9]\.[0-9]/ } do + resources :messages, path: "comments" end end - assert_recognizes(expected_options, :path => 'thread/1.1.1/comments/1', :method => :get) + assert_recognizes(expected_options, path: "thread/1.1.1/comments/1", method: :get) end end def test_resource_has_only_show_action with_routing do |set| set.draw do - resources :products, :only => :show + resources :products, only: :show end - assert_resource_allowed_routes('products', {}, { :id => '1' }, :show, [:index, :new, :create, :edit, :update, :destroy]) - assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, :show, [:index, :new, :create, :edit, :update, :destroy]) + assert_resource_allowed_routes("products", {}, { id: "1" }, :show, [:index, :new, :create, :edit, :update, :destroy]) + assert_resource_allowed_routes("products", { format: "xml" }, { id: "1" }, :show, [:index, :new, :create, :edit, :update, :destroy]) end end def test_singleton_resource_has_only_show_action with_routing do |set| set.draw do - resource :account, :only => :show + resource :account, only: :show end - assert_singleton_resource_allowed_routes('accounts', {}, :show, [:index, :new, :create, :edit, :update, :destroy]) - assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' }, :show, [:index, :new, :create, :edit, :update, :destroy]) + assert_singleton_resource_allowed_routes("accounts", {}, :show, [:index, :new, :create, :edit, :update, :destroy]) + assert_singleton_resource_allowed_routes("accounts", { format: "xml" }, :show, [:index, :new, :create, :edit, :update, :destroy]) end end def test_resource_does_not_have_destroy_action with_routing do |set| set.draw do - resources :products, :except => :destroy + resources :products, except: :destroy end - assert_resource_allowed_routes('products', {}, { :id => '1' }, [:index, :new, :create, :show, :edit, :update], :destroy) - assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, [:index, :new, :create, :show, :edit, :update], :destroy) + assert_resource_allowed_routes("products", {}, { id: "1" }, [:index, :new, :create, :show, :edit, :update], :destroy) + assert_resource_allowed_routes("products", { format: "xml" }, { id: "1" }, [:index, :new, :create, :show, :edit, :update], :destroy) end end def test_singleton_resource_does_not_have_destroy_action with_routing do |set| set.draw do - resource :account, :except => :destroy + resource :account, except: :destroy end - assert_singleton_resource_allowed_routes('accounts', {}, [:new, :create, :show, :edit, :update], :destroy) - assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' }, [:new, :create, :show, :edit, :update], :destroy) + assert_singleton_resource_allowed_routes("accounts", {}, [:new, :create, :show, :edit, :update], :destroy) + assert_singleton_resource_allowed_routes("accounts", { format: "xml" }, [:new, :create, :show, :edit, :update], :destroy) end end def test_resource_has_only_create_action_and_named_route with_routing do |set| set.draw do - resources :products, :only => :create + resources :products, only: :create end - assert_resource_allowed_routes('products', {}, { :id => '1' }, :create, [:index, :new, :show, :edit, :update, :destroy]) - assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, :create, [:index, :new, :show, :edit, :update, :destroy]) + assert_resource_allowed_routes("products", {}, { id: "1" }, :create, [:index, :new, :show, :edit, :update, :destroy]) + assert_resource_allowed_routes("products", { format: "xml" }, { id: "1" }, :create, [:index, :new, :show, :edit, :update, :destroy]) assert_not_nil set.named_routes[:products] end @@ -854,11 +868,11 @@ class ResourcesTest < ActionController::TestCase def test_resource_has_only_update_action_and_named_route with_routing do |set| set.draw do - resources :products, :only => :update + resources :products, only: :update end - assert_resource_allowed_routes('products', {}, { :id => '1' }, :update, [:index, :new, :create, :show, :edit, :destroy]) - assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, :update, [:index, :new, :create, :show, :edit, :destroy]) + assert_resource_allowed_routes("products", {}, { id: "1" }, :update, [:index, :new, :create, :show, :edit, :destroy]) + assert_resource_allowed_routes("products", { format: "xml" }, { id: "1" }, :update, [:index, :new, :create, :show, :edit, :destroy]) assert_not_nil set.named_routes[:product] end @@ -867,11 +881,11 @@ class ResourcesTest < ActionController::TestCase def test_resource_has_only_destroy_action_and_named_route with_routing do |set| set.draw do - resources :products, :only => :destroy + resources :products, only: :destroy end - assert_resource_allowed_routes('products', {}, { :id => '1' }, :destroy, [:index, :new, :create, :show, :edit, :update]) - assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, :destroy, [:index, :new, :create, :show, :edit, :update]) + assert_resource_allowed_routes("products", {}, { id: "1" }, :destroy, [:index, :new, :create, :show, :edit, :update]) + assert_resource_allowed_routes("products", { format: "xml" }, { id: "1" }, :destroy, [:index, :new, :create, :show, :edit, :update]) assert_not_nil set.named_routes[:product] end @@ -880,11 +894,11 @@ class ResourcesTest < ActionController::TestCase def test_singleton_resource_has_only_create_action_and_named_route with_routing do |set| set.draw do - resource :account, :only => :create + resource :account, only: :create end - assert_singleton_resource_allowed_routes('accounts', {}, :create, [:new, :show, :edit, :update, :destroy]) - assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' }, :create, [:new, :show, :edit, :update, :destroy]) + assert_singleton_resource_allowed_routes("accounts", {}, :create, [:new, :show, :edit, :update, :destroy]) + assert_singleton_resource_allowed_routes("accounts", { format: "xml" }, :create, [:new, :show, :edit, :update, :destroy]) assert_not_nil set.named_routes[:account] end @@ -893,11 +907,11 @@ class ResourcesTest < ActionController::TestCase def test_singleton_resource_has_only_update_action_and_named_route with_routing do |set| set.draw do - resource :account, :only => :update + resource :account, only: :update end - assert_singleton_resource_allowed_routes('accounts', {}, :update, [:new, :create, :show, :edit, :destroy]) - assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' }, :update, [:new, :create, :show, :edit, :destroy]) + assert_singleton_resource_allowed_routes("accounts", {}, :update, [:new, :create, :show, :edit, :destroy]) + assert_singleton_resource_allowed_routes("accounts", { format: "xml" }, :update, [:new, :create, :show, :edit, :destroy]) assert_not_nil set.named_routes[:account] end @@ -906,11 +920,11 @@ class ResourcesTest < ActionController::TestCase def test_singleton_resource_has_only_destroy_action_and_named_route with_routing do |set| set.draw do - resource :account, :only => :destroy + resource :account, only: :destroy end - assert_singleton_resource_allowed_routes('accounts', {}, :destroy, [:new, :create, :show, :edit, :update]) - assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' }, :destroy, [:new, :create, :show, :edit, :update]) + assert_singleton_resource_allowed_routes("accounts", {}, :destroy, [:new, :create, :show, :edit, :update]) + assert_singleton_resource_allowed_routes("accounts", { format: "xml" }, :destroy, [:new, :create, :show, :edit, :update]) assert_not_nil set.named_routes[:account] end @@ -919,120 +933,120 @@ class ResourcesTest < ActionController::TestCase def test_resource_has_only_collection_action with_routing do |set| set.draw do - resources :products, :only => [] do - get :sale, :on => :collection + resources :products, only: [] do + get :sale, on: :collection end end - assert_resource_allowed_routes('products', {}, { :id => '1' }, [], [:index, :new, :create, :show, :edit, :update, :destroy]) - assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, [], [:index, :new, :create, :show, :edit, :update, :destroy]) + assert_resource_allowed_routes("products", {}, { id: "1" }, [], [:index, :new, :create, :show, :edit, :update, :destroy]) + assert_resource_allowed_routes("products", { format: "xml" }, { id: "1" }, [], [:index, :new, :create, :show, :edit, :update, :destroy]) - assert_recognizes({ :controller => 'products', :action => 'sale' }, :path => 'products/sale', :method => :get) - assert_recognizes({ :controller => 'products', :action => 'sale', :format => 'xml' }, :path => 'products/sale.xml', :method => :get) + assert_recognizes({ controller: "products", action: "sale" }, path: "products/sale", method: :get) + assert_recognizes({ controller: "products", action: "sale", format: "xml" }, path: "products/sale.xml", method: :get) end end def test_resource_has_only_member_action with_routing do |set| set.draw do - resources :products, :only => [] do - get :preview, :on => :member + resources :products, only: [] do + get :preview, on: :member end end - assert_resource_allowed_routes('products', {}, { :id => '1' }, [], [:index, :new, :create, :show, :edit, :update, :destroy]) - assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, [], [:index, :new, :create, :show, :edit, :update, :destroy]) + assert_resource_allowed_routes("products", {}, { id: "1" }, [], [:index, :new, :create, :show, :edit, :update, :destroy]) + assert_resource_allowed_routes("products", { format: "xml" }, { id: "1" }, [], [:index, :new, :create, :show, :edit, :update, :destroy]) - assert_recognizes({ :controller => 'products', :action => 'preview', :id => '1' }, :path => 'products/1/preview', :method => :get) - assert_recognizes({ :controller => 'products', :action => 'preview', :id => '1', :format => 'xml' }, :path => 'products/1/preview.xml', :method => :get) + assert_recognizes({ controller: "products", action: "preview", id: "1" }, path: "products/1/preview", method: :get) + assert_recognizes({ controller: "products", action: "preview", id: "1", format: "xml" }, path: "products/1/preview.xml", method: :get) end end def test_singleton_resource_has_only_member_action with_routing do |set| set.draw do - resource :account, :only => [] do + resource :account, only: [] do member do get :signup end end end - assert_singleton_resource_allowed_routes('accounts', {}, [], [:new, :create, :show, :edit, :update, :destroy]) - assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' }, [], [:new, :create, :show, :edit, :update, :destroy]) + assert_singleton_resource_allowed_routes("accounts", {}, [], [:new, :create, :show, :edit, :update, :destroy]) + assert_singleton_resource_allowed_routes("accounts", { format: "xml" }, [], [:new, :create, :show, :edit, :update, :destroy]) - assert_recognizes({ :controller => 'accounts', :action => 'signup' }, :path => 'account/signup', :method => :get) - assert_recognizes({ :controller => 'accounts', :action => 'signup', :format => 'xml' }, :path => 'account/signup.xml', :method => :get) + assert_recognizes({ controller: "accounts", action: "signup" }, path: "account/signup", method: :get) + assert_recognizes({ controller: "accounts", action: "signup", format: "xml" }, path: "account/signup.xml", method: :get) end end def test_nested_resource_has_only_show_and_member_action with_routing do |set| set.draw do - resources :products, :only => [:index, :show] do - resources :images, :only => :show do - get :thumbnail, :on => :member + resources :products, only: [:index, :show] do + resources :images, only: :show do + get :thumbnail, on: :member end end end - assert_resource_allowed_routes('images', { :product_id => '1' }, { :id => '2' }, :show, [:index, :new, :create, :edit, :update, :destroy], 'products/1/images') - assert_resource_allowed_routes('images', { :product_id => '1', :format => 'xml' }, { :id => '2' }, :show, [:index, :new, :create, :edit, :update, :destroy], 'products/1/images') + assert_resource_allowed_routes("images", { product_id: "1" }, { id: "2" }, :show, [:index, :new, :create, :edit, :update, :destroy], "products/1/images") + assert_resource_allowed_routes("images", { product_id: "1", format: "xml" }, { id: "2" }, :show, [:index, :new, :create, :edit, :update, :destroy], "products/1/images") - assert_recognizes({ :controller => 'images', :action => 'thumbnail', :product_id => '1', :id => '2' }, :path => 'products/1/images/2/thumbnail', :method => :get) - assert_recognizes({ :controller => 'images', :action => 'thumbnail', :product_id => '1', :id => '2', :format => 'jpg' }, :path => 'products/1/images/2/thumbnail.jpg', :method => :get) + assert_recognizes({ controller: "images", action: "thumbnail", product_id: "1", id: "2" }, path: "products/1/images/2/thumbnail", method: :get) + assert_recognizes({ controller: "images", action: "thumbnail", product_id: "1", id: "2", format: "jpg" }, path: "products/1/images/2/thumbnail.jpg", method: :get) end end def test_nested_resource_does_not_inherit_only_option with_routing do |set| set.draw do - resources :products, :only => :show do - resources :images, :except => :destroy + resources :products, only: :show do + resources :images, except: :destroy end end - assert_resource_allowed_routes('images', { :product_id => '1' }, { :id => '2' }, [:index, :new, :create, :show, :edit, :update], :destroy, 'products/1/images') - assert_resource_allowed_routes('images', { :product_id => '1', :format => 'xml' }, { :id => '2' }, [:index, :new, :create, :show, :edit, :update], :destroy, 'products/1/images') + assert_resource_allowed_routes("images", { product_id: "1" }, { id: "2" }, [:index, :new, :create, :show, :edit, :update], :destroy, "products/1/images") + assert_resource_allowed_routes("images", { product_id: "1", format: "xml" }, { id: "2" }, [:index, :new, :create, :show, :edit, :update], :destroy, "products/1/images") end end def test_nested_resource_does_not_inherit_only_option_by_default with_routing do |set| set.draw do - resources :products, :only => :show do + resources :products, only: :show do resources :images end end - assert_resource_allowed_routes('images', { :product_id => '1' }, { :id => '2' }, [:index, :new, :create, :show, :edit, :update, :destroy], [], 'products/1/images') - assert_resource_allowed_routes('images', { :product_id => '1', :format => 'xml' }, { :id => '2' }, [:index, :new, :create, :show, :edit, :update, :destroy], [], 'products/1/images') + assert_resource_allowed_routes("images", { product_id: "1" }, { id: "2" }, [:index, :new, :create, :show, :edit, :update, :destroy], [], "products/1/images") + assert_resource_allowed_routes("images", { product_id: "1", format: "xml" }, { id: "2" }, [:index, :new, :create, :show, :edit, :update, :destroy], [], "products/1/images") end end def test_nested_resource_does_not_inherit_except_option with_routing do |set| set.draw do - resources :products, :except => :show do - resources :images, :only => :destroy + resources :products, except: :show do + resources :images, only: :destroy end end - assert_resource_allowed_routes('images', { :product_id => '1' }, { :id => '2' }, :destroy, [:index, :new, :create, :show, :edit, :update], 'products/1/images') - assert_resource_allowed_routes('images', { :product_id => '1', :format => 'xml' }, { :id => '2' }, :destroy, [:index, :new, :create, :show, :edit, :update], 'products/1/images') + assert_resource_allowed_routes("images", { product_id: "1" }, { id: "2" }, :destroy, [:index, :new, :create, :show, :edit, :update], "products/1/images") + assert_resource_allowed_routes("images", { product_id: "1", format: "xml" }, { id: "2" }, :destroy, [:index, :new, :create, :show, :edit, :update], "products/1/images") end end def test_nested_resource_does_not_inherit_except_option_by_default with_routing do |set| set.draw do - resources :products, :except => :show do + resources :products, except: :show do resources :images end end - assert_resource_allowed_routes('images', { :product_id => '1' }, { :id => '2' }, [:index, :new, :create, :show, :edit, :update, :destroy], [], 'products/1/images') - assert_resource_allowed_routes('images', { :product_id => '1', :format => 'xml' }, { :id => '2' }, [:index, :new, :create, :show, :edit, :update, :destroy], [], 'products/1/images') + assert_resource_allowed_routes("images", { product_id: "1" }, { id: "2" }, [:index, :new, :create, :show, :edit, :update, :destroy], [], "products/1/images") + assert_resource_allowed_routes("images", { product_id: "1", format: "xml" }, { id: "2" }, [:index, :new, :create, :show, :edit, :update, :destroy], [], "products/1/images") end end @@ -1042,8 +1056,8 @@ class ResourcesTest < ActionController::TestCase resource :product end - assert_routing '/product', :controller => 'products', :action => 'show' - assert set.recognize_path("/product", :method => :get) + assert_routing "/product", controller: "products", action: "show" + assert set.recognize_path("/product", method: :get) end end @@ -1053,7 +1067,7 @@ class ResourcesTest < ActionController::TestCase match "/products", to: "products#show", via: :all end - assert_routing({ method: "all", path: "/products" }, { controller: "products", action: "show" }) + assert_routing({ method: "all", path: "/products" }, controller: "products", action: "show") end end @@ -1064,7 +1078,7 @@ class ResourcesTest < ActionController::TestCase end assert_raises(Minitest::Assertion) do - assert_routing({ method: "all", path: "/products" }, { controller: "products", action: "show" }) + assert_routing({ method: "all", path: "/products" }, controller: "products", action: "show") end end end @@ -1085,7 +1099,7 @@ class ResourcesTest < ActionController::TestCase with_routing do |set| set.draw do - scope(path_prefix || '') do + scope(path_prefix || "") do resources(*args) do if collection_methods collection do @@ -1111,7 +1125,7 @@ class ResourcesTest < ActionController::TestCase def with_singleton_resources(*args) with_routing do |set| - set.draw {resource(*args) } + set.draw { resource(*args) } yield end end @@ -1155,34 +1169,34 @@ class ResourcesTest < ActionController::TestCase formatted_edit_member_path = "#{member_path}/#{edit_action}.xml" with_options(route_options) do |controller| - controller.assert_routing collection_path, :action => 'index' - controller.assert_routing new_path, :action => 'new' - controller.assert_routing "#{collection_path}.xml", :action => 'index', :format => 'xml' - controller.assert_routing "#{new_path}.xml", :action => 'new', :format => 'xml' + controller.assert_routing collection_path, action: "index" + controller.assert_routing new_path, action: "new" + controller.assert_routing "#{collection_path}.xml", action: "index", format: "xml" + controller.assert_routing "#{new_path}.xml", action: "new", format: "xml" end with_options(options[:shallow_options]) do |controller| - controller.assert_routing member_path, :action => 'show', :id => '1' - controller.assert_routing edit_member_path, :action => 'edit', :id => '1' - controller.assert_routing "#{member_path}.xml", :action => 'show', :id => '1', :format => 'xml' - controller.assert_routing formatted_edit_member_path, :action => 'edit', :id => '1', :format => 'xml' - end - - assert_recognizes(route_options.merge(:action => 'index'), :path => collection_path, :method => :get) - assert_recognizes(route_options.merge(:action => 'new'), :path => new_path, :method => :get) - assert_recognizes(route_options.merge(:action => 'create'), :path => collection_path, :method => :post) - assert_recognizes(options[:shallow_options].merge(:action => 'show', :id => '1'), :path => member_path, :method => :get) - assert_recognizes(options[:shallow_options].merge(:action => 'edit', :id => '1'), :path => edit_member_path, :method => :get) - assert_recognizes(options[:shallow_options].merge(:action => 'update', :id => '1'), :path => member_path, :method => :put) - assert_recognizes(options[:shallow_options].merge(:action => 'destroy', :id => '1'), :path => member_path, :method => :delete) - - assert_recognizes(route_options.merge(:action => 'index', :format => 'xml'), :path => "#{collection_path}.xml", :method => :get) - assert_recognizes(route_options.merge(:action => 'new', :format => 'xml'), :path => "#{new_path}.xml", :method => :get) - assert_recognizes(route_options.merge(:action => 'create', :format => 'xml'), :path => "#{collection_path}.xml", :method => :post) - assert_recognizes(options[:shallow_options].merge(:action => 'show', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :get) - assert_recognizes(options[:shallow_options].merge(:action => 'edit', :id => '1', :format => 'xml'), :path => formatted_edit_member_path, :method => :get) - assert_recognizes(options[:shallow_options].merge(:action => 'update', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :put) - assert_recognizes(options[:shallow_options].merge(:action => 'destroy', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :delete) + controller.assert_routing member_path, action: "show", id: "1" + controller.assert_routing edit_member_path, action: "edit", id: "1" + controller.assert_routing "#{member_path}.xml", action: "show", id: "1", format: "xml" + controller.assert_routing formatted_edit_member_path, action: "edit", id: "1", format: "xml" + end + + assert_recognizes(route_options.merge(action: "index"), path: collection_path, method: :get) + assert_recognizes(route_options.merge(action: "new"), path: new_path, method: :get) + assert_recognizes(route_options.merge(action: "create"), path: collection_path, method: :post) + assert_recognizes(options[:shallow_options].merge(action: "show", id: "1"), path: member_path, method: :get) + assert_recognizes(options[:shallow_options].merge(action: "edit", id: "1"), path: edit_member_path, method: :get) + assert_recognizes(options[:shallow_options].merge(action: "update", id: "1"), path: member_path, method: :put) + assert_recognizes(options[:shallow_options].merge(action: "destroy", id: "1"), path: member_path, method: :delete) + + assert_recognizes(route_options.merge(action: "index", format: "xml"), path: "#{collection_path}.xml", method: :get) + assert_recognizes(route_options.merge(action: "new", format: "xml"), path: "#{new_path}.xml", method: :get) + assert_recognizes(route_options.merge(action: "create", format: "xml"), path: "#{collection_path}.xml", method: :post) + assert_recognizes(options[:shallow_options].merge(action: "show", id: "1", format: "xml"), path: "#{member_path}.xml", method: :get) + assert_recognizes(options[:shallow_options].merge(action: "edit", id: "1", format: "xml"), path: formatted_edit_member_path, method: :get) + assert_recognizes(options[:shallow_options].merge(action: "update", id: "1", format: "xml"), path: "#{member_path}.xml", method: :put) + assert_recognizes(options[:shallow_options].merge(action: "destroy", id: "1", format: "xml"), path: "#{member_path}.xml", method: :delete) yield route_options if block_given? end @@ -1214,7 +1228,7 @@ class ResourcesTest < ActionController::TestCase shallow_path = "/#{options[:shallow] ? options[:namespace] : options[:path_prefix]}#{path}" full_path = "/#{options[:path_prefix]}#{path}" name_prefix = options[:name_prefix] - shallow_prefix = options[:shallow] ? options[:namespace].try(:gsub, /\//, '_') : options[:name_prefix] + shallow_prefix = options[:shallow] ? options[:namespace].try(:gsub, /\//, "_") : options[:name_prefix] new_action = "new" edit_action = "edit" @@ -1224,14 +1238,14 @@ class ResourcesTest < ActionController::TestCase end assert_named_route "#{full_path}", "#{name_prefix}#{controller_name}_path", route_options - assert_named_route "#{full_path}.xml", "#{name_prefix}#{controller_name}_path", route_options.merge(:format => 'xml') - assert_named_route "#{shallow_path}/1", "#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1') - assert_named_route "#{shallow_path}/1.xml", "#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1', :format => 'xml') + assert_named_route "#{full_path}.xml", "#{name_prefix}#{controller_name}_path", route_options.merge(format: "xml") + assert_named_route "#{shallow_path}/1", "#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(id: "1") + assert_named_route "#{shallow_path}/1.xml", "#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(id: "1", format: "xml") assert_named_route "#{full_path}/#{new_action}", "new_#{name_prefix}#{singular_name}_path", route_options - assert_named_route "#{full_path}/#{new_action}.xml", "new_#{name_prefix}#{singular_name}_path", route_options.merge(:format => 'xml') - assert_named_route "#{shallow_path}/1/#{edit_action}", "edit_#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1') - assert_named_route "#{shallow_path}/1/#{edit_action}.xml", "edit_#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1', :format => 'xml') + assert_named_route "#{full_path}/#{new_action}.xml", "new_#{name_prefix}#{singular_name}_path", route_options.merge(format: "xml") + assert_named_route "#{shallow_path}/1/#{edit_action}", "edit_#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(id: "1") + assert_named_route "#{shallow_path}/1/#{edit_action}.xml", "edit_#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(id: "1", format: "xml") yield route_options if block_given? end @@ -1246,27 +1260,27 @@ class ResourcesTest < ActionController::TestCase formatted_edit_path = "#{full_path}/edit.xml" with_options route_options do |controller| - controller.assert_routing full_path, :action => 'show' - controller.assert_routing new_path, :action => 'new' - controller.assert_routing edit_path, :action => 'edit' - controller.assert_routing "#{full_path}.xml", :action => 'show', :format => 'xml' - controller.assert_routing "#{new_path}.xml", :action => 'new', :format => 'xml' - controller.assert_routing formatted_edit_path, :action => 'edit', :format => 'xml' - end - - assert_recognizes(route_options.merge(:action => 'show'), :path => full_path, :method => :get) - assert_recognizes(route_options.merge(:action => 'new'), :path => new_path, :method => :get) - assert_recognizes(route_options.merge(:action => 'edit'), :path => edit_path, :method => :get) - assert_recognizes(route_options.merge(:action => 'create'), :path => full_path, :method => :post) - assert_recognizes(route_options.merge(:action => 'update'), :path => full_path, :method => :put) - assert_recognizes(route_options.merge(:action => 'destroy'), :path => full_path, :method => :delete) - - assert_recognizes(route_options.merge(:action => 'show', :format => 'xml'), :path => "#{full_path}.xml", :method => :get) - assert_recognizes(route_options.merge(:action => 'new', :format => 'xml'), :path => "#{new_path}.xml", :method => :get) - assert_recognizes(route_options.merge(:action => 'edit', :format => 'xml'), :path => formatted_edit_path, :method => :get) - assert_recognizes(route_options.merge(:action => 'create', :format => 'xml'), :path => "#{full_path}.xml", :method => :post) - assert_recognizes(route_options.merge(:action => 'update', :format => 'xml'), :path => "#{full_path}.xml", :method => :put) - assert_recognizes(route_options.merge(:action => 'destroy', :format => 'xml'), :path => "#{full_path}.xml", :method => :delete) + controller.assert_routing full_path, action: "show" + controller.assert_routing new_path, action: "new" + controller.assert_routing edit_path, action: "edit" + controller.assert_routing "#{full_path}.xml", action: "show", format: "xml" + controller.assert_routing "#{new_path}.xml", action: "new", format: "xml" + controller.assert_routing formatted_edit_path, action: "edit", format: "xml" + end + + assert_recognizes(route_options.merge(action: "show"), path: full_path, method: :get) + assert_recognizes(route_options.merge(action: "new"), path: new_path, method: :get) + assert_recognizes(route_options.merge(action: "edit"), path: edit_path, method: :get) + assert_recognizes(route_options.merge(action: "create"), path: full_path, method: :post) + assert_recognizes(route_options.merge(action: "update"), path: full_path, method: :put) + assert_recognizes(route_options.merge(action: "destroy"), path: full_path, method: :delete) + + assert_recognizes(route_options.merge(action: "show", format: "xml"), path: "#{full_path}.xml", method: :get) + assert_recognizes(route_options.merge(action: "new", format: "xml"), path: "#{new_path}.xml", method: :get) + assert_recognizes(route_options.merge(action: "edit", format: "xml"), path: formatted_edit_path, method: :get) + assert_recognizes(route_options.merge(action: "create", format: "xml"), path: "#{full_path}.xml", method: :post) + assert_recognizes(route_options.merge(action: "update", format: "xml"), path: "#{full_path}.xml", method: :put) + assert_recognizes(route_options.merge(action: "destroy", format: "xml"), path: "#{full_path}.xml", method: :delete) yield route_options if block_given? end @@ -1283,12 +1297,12 @@ class ResourcesTest < ActionController::TestCase name_prefix = options[:name_prefix] assert_named_route "#{full_path}", "#{name_prefix}#{singleton_name}_path", route_options - assert_named_route "#{full_path}.xml", "#{name_prefix}#{singleton_name}_path", route_options.merge(:format => 'xml') + assert_named_route "#{full_path}.xml", "#{name_prefix}#{singleton_name}_path", route_options.merge(format: "xml") assert_named_route "#{full_path}/new", "new_#{name_prefix}#{singleton_name}_path", route_options - assert_named_route "#{full_path}/new.xml", "new_#{name_prefix}#{singleton_name}_path", route_options.merge(:format => 'xml') + assert_named_route "#{full_path}/new.xml", "new_#{name_prefix}#{singleton_name}_path", route_options.merge(format: "xml") assert_named_route "#{full_path}/edit", "edit_#{name_prefix}#{singleton_name}_path", route_options - assert_named_route "#{full_path}/edit.xml", "edit_#{name_prefix}#{singleton_name}_path", route_options.merge(:format => 'xml') + assert_named_route "#{full_path}/edit.xml", "edit_#{name_prefix}#{singleton_name}_path", route_options.merge(format: "xml") end def assert_named_route(expected, route, options) @@ -1299,7 +1313,7 @@ class ResourcesTest < ActionController::TestCase def assert_resource_methods(expected, resource, action_method, method) assert_equal expected.length, resource.send("#{action_method}_methods")[method].size, "#{resource.send("#{action_method}_methods")[method].inspect}" expected.each do |action| - assert resource.send("#{action_method}_methods")[method].include?(action) + assert_includes resource.send("#{action_method}_methods")[method], action, "#{method} not in #{action_method} methods: #{resource.send("#{action_method}_methods")[method].inspect}" end end @@ -1307,41 +1321,41 @@ class ResourcesTest < ActionController::TestCase def assert_resource_allowed_routes(controller, options, shallow_options, allowed, not_allowed, path = controller) shallow_path = "#{path}/#{shallow_options[:id]}" format = options[:format] && ".#{options[:format]}" - options.merge!(:controller => controller) + options.merge!(controller: controller) shallow_options.merge!(options) - assert_whether_allowed(allowed, not_allowed, options, 'index', "#{path}#{format}", :get) - assert_whether_allowed(allowed, not_allowed, options, 'new', "#{path}/new#{format}", :get) - assert_whether_allowed(allowed, not_allowed, options, 'create', "#{path}#{format}", :post) - assert_whether_allowed(allowed, not_allowed, shallow_options, 'show', "#{shallow_path}#{format}", :get) - assert_whether_allowed(allowed, not_allowed, shallow_options, 'edit', "#{shallow_path}/edit#{format}", :get) - assert_whether_allowed(allowed, not_allowed, shallow_options, 'update', "#{shallow_path}#{format}", :put) - assert_whether_allowed(allowed, not_allowed, shallow_options, 'destroy', "#{shallow_path}#{format}", :delete) + assert_whether_allowed(allowed, not_allowed, options, "index", "#{path}#{format}", :get) + assert_whether_allowed(allowed, not_allowed, options, "new", "#{path}/new#{format}", :get) + assert_whether_allowed(allowed, not_allowed, options, "create", "#{path}#{format}", :post) + assert_whether_allowed(allowed, not_allowed, shallow_options, "show", "#{shallow_path}#{format}", :get) + assert_whether_allowed(allowed, not_allowed, shallow_options, "edit", "#{shallow_path}/edit#{format}", :get) + assert_whether_allowed(allowed, not_allowed, shallow_options, "update", "#{shallow_path}#{format}", :put) + assert_whether_allowed(allowed, not_allowed, shallow_options, "destroy", "#{shallow_path}#{format}", :delete) end def assert_singleton_resource_allowed_routes(controller, options, allowed, not_allowed, path = controller.singularize) format = options[:format] && ".#{options[:format]}" - options.merge!(:controller => controller) + options.merge!(controller: controller) - assert_whether_allowed(allowed, not_allowed, options, 'new', "#{path}/new#{format}", :get) - assert_whether_allowed(allowed, not_allowed, options, 'create', "#{path}#{format}", :post) - assert_whether_allowed(allowed, not_allowed, options, 'show', "#{path}#{format}", :get) - assert_whether_allowed(allowed, not_allowed, options, 'edit', "#{path}/edit#{format}", :get) - assert_whether_allowed(allowed, not_allowed, options, 'update', "#{path}#{format}", :put) - assert_whether_allowed(allowed, not_allowed, options, 'destroy', "#{path}#{format}", :delete) + assert_whether_allowed(allowed, not_allowed, options, "new", "#{path}/new#{format}", :get) + assert_whether_allowed(allowed, not_allowed, options, "create", "#{path}#{format}", :post) + assert_whether_allowed(allowed, not_allowed, options, "show", "#{path}#{format}", :get) + assert_whether_allowed(allowed, not_allowed, options, "edit", "#{path}/edit#{format}", :get) + assert_whether_allowed(allowed, not_allowed, options, "update", "#{path}#{format}", :put) + assert_whether_allowed(allowed, not_allowed, options, "destroy", "#{path}#{format}", :delete) end def assert_whether_allowed(allowed, not_allowed, options, action, path, method) action = action.to_sym - options = options.merge(:action => action.to_s) - path_options = { :path => path, :method => method } + options = options.merge(action: action.to_s) + path_options = { path: path, method: method } if Array(allowed).include?(action) assert_recognizes options, path_options elsif Array(not_allowed).include?(action) assert_not_recognizes options, path_options else - raise Assertion, 'Invalid Action has passed' + raise Assertion, "Invalid Action has passed" end end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index a39fede5b9..7be2ad2b28 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -1,7 +1,7 @@ -require 'abstract_unit' -require 'controller/fake_controllers' -require 'active_support/core_ext/object/with_options' -require 'active_support/core_ext/object/json' +require "abstract_unit" +require "controller/fake_controllers" +require "active_support/core_ext/object/with_options" +require "active_support/core_ext/object/json" class MilestonesController < ActionController::Base def index() head :ok end @@ -15,11 +15,13 @@ class UriReservedCharactersRoutingTest < ActiveSupport::TestCase def setup @set = ActionDispatch::Routing::RouteSet.new @set.draw do - get ':controller/:action/:variable/*additional' + ActiveSupport::Deprecation.silence do + get ":controller/:action/:variable/*additional" + end end safe, unsafe = %w(: @ & = + $ , ;), %w(^ ? # [ ]) - hex = unsafe.map { |char| '%' + char.unpack('H2').first.upcase } + hex = unsafe.map { |char| "%" + char.unpack("H2").first.upcase } @segment = "#{safe.join}#{unsafe.join}".freeze @escaped = "#{safe.join}#{hex.join}".freeze @@ -27,30 +29,28 @@ class UriReservedCharactersRoutingTest < ActiveSupport::TestCase def test_route_generation_escapes_unsafe_path_characters assert_equal "/content/act#{@escaped}ion/var#{@escaped}iable/add#{@escaped}itional-1/add#{@escaped}itional-2", - url_for(@set, { - :controller => "content", - :action => "act#{@segment}ion", - :variable => "var#{@segment}iable", - :additional => ["add#{@segment}itional-1", "add#{@segment}itional-2"] - }) + url_for(@set, + controller: "content", + action: "act#{@segment}ion", + variable: "var#{@segment}iable", + additional: ["add#{@segment}itional-1", "add#{@segment}itional-2"]) end def test_route_recognition_unescapes_path_components - options = { :controller => "content", - :action => "act#{@segment}ion", - :variable => "var#{@segment}iable", - :additional => "add#{@segment}itional-1/add#{@segment}itional-2" } + options = { controller: "content", + action: "act#{@segment}ion", + variable: "var#{@segment}iable", + additional: "add#{@segment}itional-1/add#{@segment}itional-2" } assert_equal options, @set.recognize_path("/content/act#{@escaped}ion/var#{@escaped}iable/add#{@escaped}itional-1/add#{@escaped}itional-2") end def test_route_generation_allows_passing_non_string_values_to_generated_helper assert_equal "/content/action/variable/1/2", - url_for(@set, { - :controller => "content", - :action => "action", - :variable => "variable", - :additional => [1, 2] - }) + url_for(@set, + controller: "content", + action: "action", + variable: "variable", + additional: [1, 2]) end end @@ -84,106 +84,106 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_symbols_with_dashes rs.draw do - get '/:artist/:song-omg', :to => lambda { |env| + get "/:artist/:song-omg", to: lambda { |env| resp = ActiveSupport::JSON.encode ActionDispatch::Request.new(env).path_parameters [200, {}, [resp]] } end - hash = ActiveSupport::JSON.decode get(URI('http://example.org/journey/faithfully-omg')) - assert_equal({"artist"=>"journey", "song"=>"faithfully"}, hash) + hash = ActiveSupport::JSON.decode get(URI("http://example.org/journey/faithfully-omg")) + assert_equal({ "artist"=>"journey", "song"=>"faithfully" }, hash) end def test_id_with_dash rs.draw do - get '/journey/:id', :to => lambda { |env| + get "/journey/:id", to: lambda { |env| resp = ActiveSupport::JSON.encode ActionDispatch::Request.new(env).path_parameters [200, {}, [resp]] } end - hash = ActiveSupport::JSON.decode get(URI('http://example.org/journey/faithfully-omg')) - assert_equal({"id"=>"faithfully-omg"}, hash) + hash = ActiveSupport::JSON.decode get(URI("http://example.org/journey/faithfully-omg")) + assert_equal({ "id"=>"faithfully-omg" }, hash) end def test_dash_with_custom_regexp rs.draw do - get '/:artist/:song-omg', :constraints => { :song => /\d+/ }, :to => lambda { |env| + get "/:artist/:song-omg", constraints: { song: /\d+/ }, to: lambda { |env| resp = ActiveSupport::JSON.encode ActionDispatch::Request.new(env).path_parameters [200, {}, [resp]] } end - hash = ActiveSupport::JSON.decode get(URI('http://example.org/journey/123-omg')) - assert_equal({"artist"=>"journey", "song"=>"123"}, hash) - assert_equal 'Not Found', get(URI('http://example.org/journey/faithfully-omg')) + hash = ActiveSupport::JSON.decode get(URI("http://example.org/journey/123-omg")) + assert_equal({ "artist"=>"journey", "song"=>"123" }, hash) + assert_equal "Not Found", get(URI("http://example.org/journey/faithfully-omg")) end def test_pre_dash rs.draw do - get '/:artist/omg-:song', :to => lambda { |env| + get "/:artist/omg-:song", to: lambda { |env| resp = ActiveSupport::JSON.encode ActionDispatch::Request.new(env).path_parameters [200, {}, [resp]] } end - hash = ActiveSupport::JSON.decode get(URI('http://example.org/journey/omg-faithfully')) - assert_equal({"artist"=>"journey", "song"=>"faithfully"}, hash) + hash = ActiveSupport::JSON.decode get(URI("http://example.org/journey/omg-faithfully")) + assert_equal({ "artist"=>"journey", "song"=>"faithfully" }, hash) end def test_pre_dash_with_custom_regexp rs.draw do - get '/:artist/omg-:song', :constraints => { :song => /\d+/ }, :to => lambda { |env| + get "/:artist/omg-:song", constraints: { song: /\d+/ }, to: lambda { |env| resp = ActiveSupport::JSON.encode ActionDispatch::Request.new(env).path_parameters [200, {}, [resp]] } end - hash = ActiveSupport::JSON.decode get(URI('http://example.org/journey/omg-123')) - assert_equal({"artist"=>"journey", "song"=>"123"}, hash) - assert_equal 'Not Found', get(URI('http://example.org/journey/omg-faithfully')) + hash = ActiveSupport::JSON.decode get(URI("http://example.org/journey/omg-123")) + assert_equal({ "artist"=>"journey", "song"=>"123" }, hash) + assert_equal "Not Found", get(URI("http://example.org/journey/omg-faithfully")) end def test_star_paths_are_greedy rs.draw do - get "/*path", :to => lambda { |env| + get "/*path", to: lambda { |env| x = env["action_dispatch.request.path_parameters"][:path] [200, {}, [x]] - }, :format => false + }, format: false end - u = URI('http://example.org/foo/bar.html') - assert_equal u.path.sub(/^\//, ''), get(u) + u = URI("http://example.org/foo/bar.html") + assert_equal u.path.sub(/^\//, ""), get(u) end def test_star_paths_are_greedy_but_not_too_much rs.draw do - get "/*path", :to => lambda { |env| + get "/*path", to: lambda { |env| x = ActiveSupport::JSON.encode env["action_dispatch.request.path_parameters"] [200, {}, [x]] } end expected = { "path" => "foo/bar", "format" => "html" } - u = URI('http://example.org/foo/bar.html') + u = URI("http://example.org/foo/bar.html") assert_equal expected, ActiveSupport::JSON.decode(get(u)) end def test_optional_star_paths_are_greedy rs.draw do - get "/(*filters)", :to => lambda { |env| + get "/(*filters)", to: lambda { |env| x = env["action_dispatch.request.path_parameters"][:filters] [200, {}, [x]] - }, :format => false + }, format: false end - u = URI('http://example.org/ne_27.065938,-80.6092/sw_25.489856,-82.542794') - assert_equal u.path.sub(/^\//, ''), get(u) + u = URI("http://example.org/ne_27.065938,-80.6092/sw_25.489856,-82.542794") + assert_equal u.path.sub(/^\//, ""), get(u) end def test_optional_star_paths_are_greedy_but_not_too_much rs.draw do - get "/(*filters)", :to => lambda { |env| + get "/(*filters)", to: lambda { |env| x = ActiveSupport::JSON.encode env["action_dispatch.request.path_parameters"] [200, {}, [x]] } @@ -191,65 +191,65 @@ class LegacyRouteSetTests < ActiveSupport::TestCase expected = { "filters" => "ne_27.065938,-80.6092/sw_25.489856,-82", "format" => "542794" } - u = URI('http://example.org/ne_27.065938,-80.6092/sw_25.489856,-82.542794') + u = URI("http://example.org/ne_27.065938,-80.6092/sw_25.489856,-82.542794") assert_equal expected, ActiveSupport::JSON.decode(get(u)) end def test_regexp_precidence rs.draw do - get '/whois/:domain', :constraints => { - :domain => /\w+\.[\w\.]+/ }, - :to => lambda { |env| [200, {}, %w{regexp}] } + get "/whois/:domain", constraints: { + domain: /\w+\.[\w\.]+/ }, + to: lambda { |env| [200, {}, %w{regexp}] } - get '/whois/:id', :to => lambda { |env| [200, {}, %w{id}] } + get "/whois/:id", to: lambda { |env| [200, {}, %w{id}] } end - assert_equal 'regexp', get(URI('http://example.org/whois/example.org')) - assert_equal 'id', get(URI('http://example.org/whois/123')) + assert_equal "regexp", get(URI("http://example.org/whois/example.org")) + assert_equal "id", get(URI("http://example.org/whois/123")) end def test_class_and_lambda_constraints subdomain = Class.new { - def matches? request - request.subdomain.present? and request.subdomain != 'clients' + def matches?(request) + request.subdomain.present? && request.subdomain != "clients" end } rs.draw do - get '/', :constraints => subdomain.new, - :to => lambda { |env| [200, {}, %w{default}] } - get '/', :constraints => { :subdomain => 'clients' }, - :to => lambda { |env| [200, {}, %w{clients}] } + get "/", constraints: subdomain.new, + to: lambda { |env| [200, {}, %w{default}] } + get "/", constraints: { subdomain: "clients" }, + to: lambda { |env| [200, {}, %w{clients}] } end - assert_equal 'default', get(URI('http://www.example.org/')) - assert_equal 'clients', get(URI('http://clients.example.org/')) + assert_equal "default", get(URI("http://www.example.org/")) + assert_equal "clients", get(URI("http://clients.example.org/")) end def test_lambda_constraints rs.draw do - get '/', :constraints => lambda { |req| - req.subdomain.present? and req.subdomain != "clients" }, - :to => lambda { |env| [200, {}, %w{default}] } + get "/", constraints: lambda { |req| + req.subdomain.present? && req.subdomain != "clients" }, + to: lambda { |env| [200, {}, %w{default}] } - get '/', :constraints => lambda { |req| + get "/", constraints: lambda { |req| req.subdomain.present? && req.subdomain == "clients" }, - :to => lambda { |env| [200, {}, %w{clients}] } + to: lambda { |env| [200, {}, %w{clients}] } end - assert_equal 'default', get(URI('http://www.example.org/')) - assert_equal 'clients', get(URI('http://clients.example.org/')) + assert_equal "default", get(URI("http://www.example.org/")) + assert_equal "clients", get(URI("http://clients.example.org/")) end def test_scoped_lambda scope_called = false rs.draw do - scope '/foo', :constraints => lambda { |req| scope_called = true } do - get '/', :to => lambda { |env| [200, {}, %w{default}] } + scope "/foo", constraints: lambda { |req| scope_called = true } do + get "/", to: lambda { |env| [200, {}, %w{default}] } end end - assert_equal 'default', get(URI('http://www.example.org/foo/')) + assert_equal "default", get(URI("http://www.example.org/foo/")) assert scope_called, "scope constraint should be called" end @@ -257,36 +257,36 @@ class LegacyRouteSetTests < ActiveSupport::TestCase inner_called = false rs.draw do - scope '/foo', :constraints => lambda { |req| flunk "should not be called" } do - get '/', :constraints => lambda { |req| inner_called = true }, - :to => lambda { |env| [200, {}, %w{default}] } + scope "/foo", constraints: lambda { |req| flunk "should not be called" } do + get "/", constraints: lambda { |req| inner_called = true }, + to: lambda { |env| [200, {}, %w{default}] } end end - assert_equal 'default', get(URI('http://www.example.org/foo/')) + assert_equal "default", get(URI("http://www.example.org/foo/")) assert inner_called, "inner constraint should be called" end def test_empty_string_match rs.draw do - get '/:username', :constraints => { :username => /[^\/]+/ }, - :to => lambda { |e| [200, {}, ['foo']] } + get "/:username", constraints: { username: /[^\/]+/ }, + to: lambda { |e| [200, {}, ["foo"]] } end - assert_equal 'Not Found', get(URI('http://example.org/')) - assert_equal 'foo', get(URI('http://example.org/hello')) + assert_equal "Not Found", get(URI("http://example.org/")) + assert_equal "foo", get(URI("http://example.org/hello")) end def test_non_greedy_glob_regexp params = nil rs.draw do - get '/posts/:id(/*filters)', :constraints => { :filters => /.+?/ }, - :to => lambda { |e| + get "/posts/:id(/*filters)", constraints: { filters: /.+?/ }, + to: lambda { |e| params = e["action_dispatch.request.path_parameters"] - [200, {}, ['foo']] + [200, {}, ["foo"]] } end - assert_equal 'foo', get(URI('http://example.org/posts/1/foo.js')) - assert_equal({:id=>"1", :filters=>"foo", :format=>"js"}, params) + assert_equal "foo", get(URI("http://example.org/posts/1/foo.js")) + assert_equal({ id: "1", filters: "foo", format: "js" }, params) end def test_specific_controller_action_failure @@ -295,127 +295,136 @@ class LegacyRouteSetTests < ActiveSupport::TestCase end assert_raises(ActionController::UrlGenerationError) do - url_for(rs, :controller => "omg", :action => "lol") + url_for(rs, controller: "omg", action: "lol") end end def test_default_setup - rs.draw { get '/:controller(/:action(/:id))' } - assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/content")) - assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/content/list")) - assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/content/show/10")) + rs.draw { ActiveSupport::Deprecation.silence { get "/:controller(/:action(/:id))" } } + assert_equal({ controller: "content", action: "index" }, rs.recognize_path("/content")) + assert_equal({ controller: "content", action: "list" }, rs.recognize_path("/content/list")) + assert_equal({ controller: "content", action: "show", id: "10" }, rs.recognize_path("/content/show/10")) - assert_equal({:controller => "admin/user", :action => 'show', :id => '10'}, rs.recognize_path("/admin/user/show/10")) + assert_equal({ controller: "admin/user", action: "show", id: "10" }, rs.recognize_path("/admin/user/show/10")) - assert_equal '/admin/user/show/10', url_for(rs, { :controller => 'admin/user', :action => 'show', :id => 10 }) + assert_equal "/admin/user/show/10", url_for(rs, controller: "admin/user", action: "show", id: 10) - get URI('http://test.host/admin/user/list/10') + get URI("http://test.host/admin/user/list/10") - assert_equal({ :controller => 'admin/user', :action => 'list', :id => '10' }, + assert_equal({ controller: "admin/user", action: "list", id: "10" }, controller.request.path_parameters) - assert_equal '/admin/user/show', controller.url_for({ :action => 'show', :only_path => true }) - assert_equal '/admin/user/list/10', controller.url_for({:only_path => true}) + assert_equal "/admin/user/show", controller.url_for(action: "show", only_path: true) + assert_equal "/admin/user/list/10", controller.url_for(only_path: true) - assert_equal '/admin/stuff', controller.url_for({ :controller => 'stuff', :only_path => true }) - assert_equal '/stuff', controller.url_for({ :controller => '/stuff', :only_path => true }) + assert_equal "/admin/stuff", controller.url_for(controller: "stuff", only_path: true) + assert_equal "/stuff", controller.url_for(controller: "/stuff", only_path: true) end def test_route_with_colon_first rs.draw do - get '/:controller/:action/:id', action: 'index', id: nil - get ':url', controller: 'content', action: 'translate' + ActiveSupport::Deprecation.silence do + get "/:controller/:action/:id", action: "index", id: nil + end + + get ":url", controller: "content", action: "translate" end - assert_equal({controller: 'content', action: 'translate', url: 'example'}, rs.recognize_path('/example')) + assert_equal({ controller: "content", action: "translate", url: "example" }, rs.recognize_path("/example")) end def test_route_with_regexp_for_action - rs.draw { get '/:controller/:action', action: /auth[-|_].+/ } + rs.draw { ActiveSupport::Deprecation.silence { get "/:controller/:action", action: /auth[-|_].+/ } } - assert_equal({ action: 'auth_google', controller: 'content' }, rs.recognize_path('/content/auth_google')) - assert_equal({ action: 'auth-facebook', controller: 'content' }, rs.recognize_path('/content/auth-facebook')) + assert_equal({ action: "auth_google", controller: "content" }, rs.recognize_path("/content/auth_google")) + assert_equal({ action: "auth-facebook", controller: "content" }, rs.recognize_path("/content/auth-facebook")) - assert_equal '/content/auth_google', url_for(rs, { controller: "content", action: "auth_google" }) - assert_equal '/content/auth-facebook', url_for(rs, { controller: "content", action: "auth-facebook" }) + assert_equal "/content/auth_google", url_for(rs, controller: "content", action: "auth_google") + assert_equal "/content/auth-facebook", url_for(rs, controller: "content", action: "auth-facebook") end def test_route_with_regexp_for_controller rs.draw do - get ':controller/:admintoken(/:action(/:id))', :controller => /admin\/.+/ - get '/:controller(/:action(/:id))' + ActiveSupport::Deprecation.silence do + get ":controller/:admintoken(/:action(/:id))", controller: /admin\/.+/ + get "/:controller(/:action(/:id))" + end end - assert_equal({:controller => "admin/user", :admintoken => "foo", :action => "index"}, + assert_equal({ controller: "admin/user", admintoken: "foo", action: "index" }, rs.recognize_path("/admin/user/foo")) - assert_equal({:controller => "content", :action => "foo"}, + assert_equal({ controller: "content", action: "foo" }, rs.recognize_path("/content/foo")) - assert_equal '/admin/user/foo', url_for(rs, { :controller => "admin/user", :admintoken => "foo", :action => "index" }) - assert_equal '/content/foo', url_for(rs, { :controller => "content", :action => "foo" }) + assert_equal "/admin/user/foo", url_for(rs, controller: "admin/user", admintoken: "foo", action: "index") + assert_equal "/content/foo", url_for(rs, controller: "content", action: "foo") end def test_route_with_regexp_and_captures_for_controller rs.draw do - get '/:controller(/:action(/:id))', :controller => /admin\/(accounts|users)/ + ActiveSupport::Deprecation.silence do + get "/:controller(/:action(/:id))", controller: /admin\/(accounts|users)/ + end end - assert_equal({:controller => "admin/accounts", :action => "index"}, rs.recognize_path("/admin/accounts")) - assert_equal({:controller => "admin/users", :action => "index"}, rs.recognize_path("/admin/users")) + assert_equal({ controller: "admin/accounts", action: "index" }, rs.recognize_path("/admin/accounts")) + assert_equal({ controller: "admin/users", action: "index" }, rs.recognize_path("/admin/users")) assert_raise(ActionController::RoutingError) { rs.recognize_path("/admin/products") } end def test_route_with_regexp_and_dot rs.draw do - get ':controller/:action/:file', - :controller => /admin|user/, - :action => /upload|download/, - :defaults => {:file => nil}, - :constraints => {:file => %r{[^/]+(\.[^/]+)?}} + ActiveSupport::Deprecation.silence do + get ":controller/:action/:file", + controller: /admin|user/, + action: /upload|download/, + defaults: { file: nil }, + constraints: { file: %r{[^/]+(\.[^/]+)?} } + end end # Without a file extension - assert_equal '/user/download/file', - url_for(rs, { :controller => "user", :action => "download", :file => "file" }) + assert_equal "/user/download/file", + url_for(rs, controller: "user", action: "download", file: "file") - assert_equal({:controller => "user", :action => "download", :file => "file"}, + assert_equal({ controller: "user", action: "download", file: "file" }, rs.recognize_path("/user/download/file")) # Now, let's try a file with an extension, really a dot (.) - assert_equal '/user/download/file.jpg', - url_for(rs, { :controller => "user", :action => "download", :file => "file.jpg" }) + assert_equal "/user/download/file.jpg", + url_for(rs, controller: "user", action: "download", file: "file.jpg") - assert_equal({:controller => "user", :action => "download", :file => "file.jpg"}, + assert_equal({ controller: "user", action: "download", file: "file.jpg" }, rs.recognize_path("/user/download/file.jpg")) end def test_basic_named_route rs.draw do - root :to => 'content#list', :as => 'home' + root to: "content#list", as: "home" end assert_equal("http://test.host/", setup_for_named_route.send(:home_url)) end def test_named_route_with_option rs.draw do - get 'page/:title' => 'content#show_page', :as => 'page' + get "page/:title" => "content#show_page", :as => "page" end assert_equal("http://test.host/page/new%20stuff", - setup_for_named_route.send(:page_url, :title => 'new stuff')) + setup_for_named_route.send(:page_url, title: "new stuff")) end def test_named_route_with_default rs.draw do - get 'page/:title' => 'content#show_page', :title => 'AboutPage', :as => 'page' + get "page/:title" => "content#show_page", :title => "AboutPage", :as => "page" end assert_equal("http://test.host/page/AboutRails", - setup_for_named_route.send(:page_url, :title => "AboutRails")) + setup_for_named_route.send(:page_url, title: "AboutRails")) end def test_named_route_with_path_prefix rs.draw do scope "my" do - get 'page' => 'content#show_page', :as => 'page' + get "page" => "content#show_page", :as => "page" end end @@ -426,7 +435,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_named_route_with_blank_path_prefix rs.draw do scope "" do - get 'page' => 'content#show_page', :as => 'page' + get "page" => "content#show_page", :as => "page" end end @@ -436,7 +445,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_named_route_with_nested_controller rs.draw do - get 'admin/user' => 'admin/user#index', :as => "users" + get "admin/user" => "admin/user#index", :as => "users" end assert_equal("http://test.host/admin/user", @@ -445,7 +454,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_optimised_named_route_with_host rs.draw do - get 'page' => 'content#show_page', :as => 'pages', :host => 'foo.com' + get "page" => "content#show_page", :as => "pages", :host => "foo.com" end routes = setup_for_named_route assert_equal "http://foo.com/page", routes.pages_url @@ -457,13 +466,15 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_named_route_without_hash rs.draw do - get ':controller/:action/:id', :as => 'normal' + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id", as: "normal" + end end end def test_named_route_root rs.draw do - root :to => "hello#index" + root to: "hello#index" end routes = setup_for_named_route assert_equal("http://test.host/", routes.send(:root_url)) @@ -507,224 +518,249 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_named_route_with_regexps rs.draw do - get 'page/:year/:month/:day/:title' => 'page#show', :as => 'article', + get "page/:year/:month/:day/:title" => "page#show", :as => "article", :year => /\d+/, :month => /\d+/, :day => /\d+/ - get ':controller/:action/:id' + + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + end end routes = setup_for_named_route assert_equal "http://test.host/page/2005/6/10/hi", - routes.send(:article_url, :title => 'hi', :day => 10, :year => 2005, :month => 6) + routes.send(:article_url, title: "hi", day: 10, year: 2005, month: 6) end def test_changing_controller - rs.draw { get ':controller/:action/:id' } + rs.draw { ActiveSupport::Deprecation.silence { get ":controller/:action/:id" } } - get URI('http://test.host/admin/user/index/10') + get URI("http://test.host/admin/user/index/10") - assert_equal '/admin/stuff/show/10', - controller.url_for({:controller => 'stuff', :action => 'show', :id => 10, :only_path => true}) + assert_equal "/admin/stuff/show/10", + controller.url_for(controller: "stuff", action: "show", id: 10, only_path: true) end def test_paths_escaped rs.draw do - get 'file/*path' => 'content#show_file', :as => 'path' - get ':controller/:action/:id' + get "file/*path" => "content#show_file", :as => "path" + + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + end end # No + to space in URI escaping, only for query params. results = rs.recognize_path "/file/hello+world/how+are+you%3F" assert results, "Recognition should have succeeded" - assert_equal 'hello+world/how+are+you?', results[:path] + assert_equal "hello+world/how+are+you?", results[:path] # Use %20 for space instead. results = rs.recognize_path "/file/hello%20world/how%20are%20you%3F" assert results, "Recognition should have succeeded" - assert_equal 'hello world/how are you?', results[:path] + assert_equal "hello world/how are you?", results[:path] end def test_paths_slashes_unescaped_with_ordered_parameters rs.draw do - get '/file/*path' => 'content#index', :as => 'path' + get "/file/*path" => "content#index", :as => "path" end # No / to %2F in URI, only for query params. - assert_equal("/file/hello/world", setup_for_named_route.send(:path_path, ['hello', 'world'])) + assert_equal("/file/hello/world", setup_for_named_route.send(:path_path, ["hello", "world"])) end def test_non_controllers_cannot_be_matched rs.draw do - get ':controller/:action/:id' + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + end end assert_raise(ActionController::RoutingError) { rs.recognize_path("/not_a/show/10") } end def test_should_list_options_diff_when_routing_constraints_dont_match rs.draw do - get 'post/:id' => 'post#show', :constraints => { :id => /\d+/ }, :as => 'post' + get "post/:id" => "post#show", :constraints => { id: /\d+/ }, :as => "post" end assert_raise(ActionController::UrlGenerationError) do - url_for(rs, { :controller => 'post', :action => 'show', :bad_param => "foo", :use_route => "post" }) + url_for(rs, controller: "post", action: "show", bad_param: "foo", use_route: "post") end end def test_dynamic_path_allowed rs.draw do - get '*path' => 'content#show_file' + get "*path" => "content#show_file" end - assert_equal '/pages/boo', - url_for(rs, { :controller => 'content', :action => 'show_file', :path => %w(pages boo) }) + assert_equal "/pages/boo", + url_for(rs, controller: "content", action: "show_file", path: %w(pages boo)) end def test_dynamic_recall_paths_allowed rs.draw do - get '*path' => 'content#show_file' + get "*path" => "content#show_file" end - get URI('http://test.host/pages/boo') - assert_equal({:controller=>"content", :action=>"show_file", :path=>"pages/boo"}, + get URI("http://test.host/pages/boo") + assert_equal({ controller: "content", action: "show_file", path: "pages/boo" }, controller.request.path_parameters) - assert_equal '/pages/boo', - controller.url_for(:only_path => true) + assert_equal "/pages/boo", + controller.url_for(only_path: true) end def test_backwards rs.draw do - get 'page/:id(/:action)' => 'pages#show' - get ':controller(/:action(/:id))' + ActiveSupport::Deprecation.silence do + get "page/:id(/:action)" => "pages#show" + get ":controller(/:action(/:id))" + end end - get URI('http://test.host/pages/show') - assert_equal '/page/20', controller.url_for({ :id => 20, :only_path => true }) - assert_equal '/page/20', url_for(rs, { :controller => 'pages', :id => 20, :action => 'show' }) - assert_equal '/pages/boo', url_for(rs, { :controller => 'pages', :action => 'boo' }) + get URI("http://test.host/pages/show") + assert_equal "/page/20", controller.url_for(id: 20, only_path: true) + assert_equal "/page/20", url_for(rs, controller: "pages", id: 20, action: "show") + assert_equal "/pages/boo", url_for(rs, controller: "pages", action: "boo") end - def test_route_with_fixnum_default + def test_route_with_integer_default rs.draw do - get 'page(/:id)' => 'content#show_page', :id => 1 - get ':controller/:action/:id' + get "page(/:id)" => "content#show_page", :id => 1 + + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + end end - assert_equal '/page', url_for(rs, { :controller => 'content', :action => 'show_page' }) - assert_equal '/page', url_for(rs, { :controller => 'content', :action => 'show_page', :id => 1 }) - assert_equal '/page', url_for(rs, { :controller => 'content', :action => 'show_page', :id => '1' }) - assert_equal '/page/10', url_for(rs, { :controller => 'content', :action => 'show_page', :id => 10 }) + assert_equal "/page", url_for(rs, controller: "content", action: "show_page") + assert_equal "/page", url_for(rs, controller: "content", action: "show_page", id: 1) + assert_equal "/page", url_for(rs, controller: "content", action: "show_page", id: "1") + assert_equal "/page/10", url_for(rs, controller: "content", action: "show_page", id: 10) - assert_equal({:controller => "content", :action => 'show_page', :id => 1 }, rs.recognize_path("/page")) - assert_equal({:controller => "content", :action => 'show_page', :id => '1'}, rs.recognize_path("/page/1")) - assert_equal({:controller => "content", :action => 'show_page', :id => '10'}, rs.recognize_path("/page/10")) + assert_equal({ controller: "content", action: "show_page", id: 1 }, rs.recognize_path("/page")) + assert_equal({ controller: "content", action: "show_page", id: "1" }, rs.recognize_path("/page/1")) + assert_equal({ controller: "content", action: "show_page", id: "10" }, rs.recognize_path("/page/10")) end # For newer revision def test_route_with_text_default rs.draw do - get 'page/:id' => 'content#show_page', :id => 1 - get ':controller/:action/:id' + get "page/:id" => "content#show_page", :id => 1 + + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + end end - assert_equal '/page/foo', url_for(rs, { :controller => 'content', :action => 'show_page', :id => 'foo' }) - assert_equal({ :controller => "content", :action => 'show_page', :id => 'foo' }, rs.recognize_path("/page/foo")) + assert_equal "/page/foo", url_for(rs, controller: "content", action: "show_page", id: "foo") + assert_equal({ controller: "content", action: "show_page", id: "foo" }, rs.recognize_path("/page/foo")) token = "\321\202\320\265\320\272\321\201\321\202" # 'text' in Russian token.force_encoding(Encoding::BINARY) escaped_token = CGI::escape(token) - assert_equal '/page/' + escaped_token, url_for(rs, { :controller => 'content', :action => 'show_page', :id => token }) - assert_equal({ :controller => "content", :action => 'show_page', :id => token }, rs.recognize_path("/page/#{escaped_token}")) + assert_equal "/page/" + escaped_token, url_for(rs, controller: "content", action: "show_page", id: token) + assert_equal({ controller: "content", action: "show_page", id: token }, rs.recognize_path("/page/#{escaped_token}")) end def test_action_expiry - rs.draw { get ':controller(/:action(/:id))' } - get URI('http://test.host/content/show') - assert_equal '/content', controller.url_for(:controller => 'content', :only_path => true) + rs.draw { ActiveSupport::Deprecation.silence { get ":controller(/:action(/:id))" } } + get URI("http://test.host/content/show") + assert_equal "/content", controller.url_for(controller: "content", only_path: true) end def test_requirement_should_prevent_optional_id rs.draw do - get 'post/:id' => 'post#show', :constraints => {:id => /\d+/}, :as => 'post' + get "post/:id" => "post#show", :constraints => { id: /\d+/ }, :as => "post" end - assert_equal '/post/10', url_for(rs, { :controller => 'post', :action => 'show', :id => 10 }) + assert_equal "/post/10", url_for(rs, controller: "post", action: "show", id: 10) assert_raise(ActionController::UrlGenerationError) do - url_for(rs, { :controller => 'post', :action => 'show' }) + url_for(rs, controller: "post", action: "show") end end def test_both_requirement_and_optional rs.draw do - get('test(/:year)' => 'post#show', :as => 'blog', - :defaults => { :year => nil }, - :constraints => { :year => /\d{4}/ } + get("test(/:year)" => "post#show", :as => "blog", + :defaults => { year: nil }, + :constraints => { year: /\d{4}/ } ) - get ':controller/:action/:id' + + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + end end - assert_equal '/test', url_for(rs, { :controller => 'post', :action => 'show' }) - assert_equal '/test', url_for(rs, { :controller => 'post', :action => 'show', :year => nil }) + assert_equal "/test", url_for(rs, controller: "post", action: "show") + assert_equal "/test", url_for(rs, controller: "post", action: "show", year: nil) assert_equal("http://test.host/test", setup_for_named_route.send(:blog_url)) end def test_set_to_nil_forgets rs.draw do - get 'pages(/:year(/:month(/:day)))' => 'content#list_pages', :month => nil, :day => nil - get ':controller/:action/:id' + get "pages(/:year(/:month(/:day)))" => "content#list_pages", :month => nil, :day => nil + + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + end end - assert_equal '/pages/2005', - url_for(rs, { :controller => 'content', :action => 'list_pages', :year => 2005 }) - assert_equal '/pages/2005/6', - url_for(rs, { :controller => 'content', :action => 'list_pages', :year => 2005, :month => 6 }) - assert_equal '/pages/2005/6/12', - url_for(rs, { :controller => 'content', :action => 'list_pages', :year => 2005, :month => 6, :day => 12 }) + assert_equal "/pages/2005", + url_for(rs, controller: "content", action: "list_pages", year: 2005) + assert_equal "/pages/2005/6", + url_for(rs, controller: "content", action: "list_pages", year: 2005, month: 6) + assert_equal "/pages/2005/6/12", + url_for(rs, controller: "content", action: "list_pages", year: 2005, month: 6, day: 12) - get URI('http://test.host/pages/2005/6/12') - assert_equal({ :controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12' }, + get URI("http://test.host/pages/2005/6/12") + assert_equal({ controller: "content", action: "list_pages", year: "2005", month: "6", day: "12" }, controller.request.path_parameters) - assert_equal '/pages/2005/6/4', - controller.url_for({ :day => 4, :only_path => true }) + assert_equal "/pages/2005/6/4", + controller.url_for(day: 4, only_path: true) - assert_equal '/pages/2005/6', - controller.url_for({ :day => nil, :only_path => true }) + assert_equal "/pages/2005/6", + controller.url_for(day: nil, only_path: true) - assert_equal '/pages/2005', - controller.url_for({ :day => nil, :month => nil, :only_path => true }) + assert_equal "/pages/2005", + controller.url_for(day: nil, month: nil, only_path: true) end def test_root_url_generation_with_controller_and_action rs.draw do - root :to => "content#index" + root to: "content#index" end - assert_equal '/', url_for(rs, { :controller => 'content', :action => 'index' }) - assert_equal '/', url_for(rs, { :controller => 'content' }) + assert_equal "/", url_for(rs, controller: "content", action: "index") + assert_equal "/", url_for(rs, controller: "content") end def test_named_root_url_generation_with_controller_and_action rs.draw do - root :to => "content#index", :as => 'home' + root to: "content#index", as: "home" end - assert_equal '/', url_for(rs, { :controller => 'content', :action => 'index' }) - assert_equal '/', url_for(rs, { :controller => 'content' }) + assert_equal "/", url_for(rs, controller: "content", action: "index") + assert_equal "/", url_for(rs, controller: "content") assert_equal("http://test.host/", setup_for_named_route.send(:home_url)) end def test_named_route_method rs.draw do - get 'categories' => 'content#categories', :as => 'categories' - get ':controller(/:action(/:id))' + get "categories" => "content#categories", :as => "categories" + + ActiveSupport::Deprecation.silence do + get ":controller(/:action(/:id))" + end end - assert_equal '/categories', url_for(rs, { :controller => 'content', :action => 'categories' }) - assert_equal '/content/hi', url_for(rs, { :controller => 'content', :action => 'hi' }) + assert_equal "/categories", url_for(rs, controller: "content", action: "categories") + assert_equal "/content/hi", url_for(rs, controller: "content", action: "hi") end def test_named_routes_array @@ -734,52 +770,56 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_nil_defaults rs.draw do - get 'journal' => 'content#list_journal', + get "journal" => "content#list_journal", :date => nil, :user_id => nil - get ':controller/:action/:id' + + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + end end - assert_equal '/journal', url_for(rs, { - :controller => 'content', - :action => 'list_journal', - :date => nil, - :user_id => nil - }) + assert_equal "/journal", url_for(rs, + controller: "content", + action: "list_journal", + date: nil, + user_id: nil) end def setup_request_method_routes_for(method) rs.draw do - match '/match' => "books##{method}", :via => method.to_sym + match "/match" => "books##{method}", :via => method.to_sym end end %w(GET PATCH POST PUT DELETE).each do |request_method| define_method("test_request_method_recognized_with_#{request_method}") do setup_request_method_routes_for(request_method.downcase) - params = rs.recognize_path("/match", :method => request_method) + params = rs.recognize_path("/match", method: request_method) assert_equal request_method.downcase, params[:action] end end def test_recognize_array_of_methods rs.draw do - match '/match' => 'books#get_or_post', :via => [:get, :post] - put '/match' => 'books#not_get_or_post' + match "/match" => "books#get_or_post", :via => [:get, :post] + put "/match" => "books#not_get_or_post" end - params = rs.recognize_path("/match", :method => :post) - assert_equal 'get_or_post', params[:action] + params = rs.recognize_path("/match", method: :post) + assert_equal "get_or_post", params[:action] - params = rs.recognize_path("/match", :method => :put) - assert_equal 'not_get_or_post', params[:action] + params = rs.recognize_path("/match", method: :put) + assert_equal "not_get_or_post", params[:action] end def test_subpath_recognized rs.draw do - get '/books/:id/edit' => 'subpath_books#edit' - get '/items/:id/:action' => 'subpath_books' - get '/posts/new/:action' => 'subpath_books' - get '/posts/:id' => 'subpath_books#show' + ActiveSupport::Deprecation.silence do + get "/books/:id/edit" => "subpath_books#edit" + get "/items/:id/:action" => "subpath_books" + get "/posts/new/:action" => "subpath_books" + get "/posts/:id" => "subpath_books#show" + end end hash = rs.recognize_path "/books/17/edit" @@ -801,19 +841,21 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_subpath_generated rs.draw do - get '/books/:id/edit' => 'subpath_books#edit' - get '/items/:id/:action' => 'subpath_books' - get '/posts/new/:action' => 'subpath_books' + ActiveSupport::Deprecation.silence do + get "/books/:id/edit" => "subpath_books#edit" + get "/items/:id/:action" => "subpath_books" + get "/posts/new/:action" => "subpath_books" + end end - assert_equal "/books/7/edit", url_for(rs, { :controller => "subpath_books", :id => 7, :action => "edit" }) - assert_equal "/items/15/complete", url_for(rs, { :controller => "subpath_books", :id => 15, :action => "complete" }) - assert_equal "/posts/new/preview", url_for(rs, { :controller => "subpath_books", :action => "preview" }) + assert_equal "/books/7/edit", url_for(rs, controller: "subpath_books", id: 7, action: "edit") + assert_equal "/items/15/complete", url_for(rs, controller: "subpath_books", id: 15, action: "complete") + assert_equal "/posts/new/preview", url_for(rs, controller: "subpath_books", action: "preview") end def test_failed_constraints_raises_exception_with_violated_constraints rs.draw do - get 'foos/:id' => 'foos#show', :as => 'foo_with_requirement', :constraints => { :id => /\d+/ } + get "foos/:id" => "foos#show", :as => "foo_with_requirement", :constraints => { id: /\d+/ } end assert_raise(ActionController::UrlGenerationError) do @@ -824,11 +866,14 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_routes_changed_correctly_after_clear rs = ::ActionDispatch::Routing::RouteSet.new rs.draw do - get 'ca' => 'ca#aa' - get 'cb' => 'cb#ab' - get 'cc' => 'cc#ac' - get ':controller/:action/:id' - get ':controller/:action/:id.:format' + get "ca" => "ca#aa" + get "cb" => "cb#ab" + get "cc" => "cc#ac" + + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + get ":controller/:action/:id.:format" + end end hash = rs.recognize_path "/cc" @@ -837,10 +882,13 @@ class LegacyRouteSetTests < ActiveSupport::TestCase assert_equal %w(cc ac), [hash[:controller], hash[:action]] rs.draw do - get 'cb' => 'cb#ab' - get 'cc' => 'cc#ac' - get ':controller/:action/:id' - get ':controller/:action/:id.:format' + get "cb" => "cb#ab" + get "cc" => "cc#ac" + + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + get ":controller/:action/:id.:format" + end end hash = rs.recognize_path "/cc" @@ -871,57 +919,66 @@ class RouteSetTest < ActiveSupport::TestCase @default_route_set ||= begin set = ActionDispatch::Routing::RouteSet.new set.draw do - get '/:controller(/:action(/:id))' + + ActiveSupport::Deprecation.silence do + get "/:controller(/:action(/:id))" + end end set end end def test_generate_extras - set.draw { get ':controller/(:action(/:id))' } - path, extras = set.generate_extras(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world") + set.draw { ActiveSupport::Deprecation.silence { get ":controller/(:action(/:id))" } } + path, extras = set.generate_extras(controller: "foo", action: "bar", id: 15, this: "hello", that: "world") assert_equal "/foo/bar/15", path assert_equal %w(that this), extras.map(&:to_s).sort end def test_extra_keys - set.draw { get ':controller/:action/:id' } - extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world") + set.draw { ActiveSupport::Deprecation.silence { get ":controller/:action/:id" } } + extras = set.extra_keys(controller: "foo", action: "bar", id: 15, this: "hello", that: "world") assert_equal %w(that this), extras.map(&:to_s).sort end def test_generate_extras_not_first set.draw do - get ':controller/:action/:id.:format' - get ':controller/:action/:id' + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id.:format" + get ":controller/:action/:id" + end end - path, extras = set.generate_extras(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world") + path, extras = set.generate_extras(controller: "foo", action: "bar", id: 15, this: "hello", that: "world") assert_equal "/foo/bar/15", path assert_equal %w(that this), extras.map(&:to_s).sort end def test_generate_not_first set.draw do - get ':controller/:action/:id.:format' - get ':controller/:action/:id' + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id.:format" + get ":controller/:action/:id" + end end assert_equal "/foo/bar/15?this=hello", - url_for(set, { :controller => "foo", :action => "bar", :id => 15, :this => "hello" }) + url_for(set, controller: "foo", action: "bar", id: 15, this: "hello") end def test_extra_keys_not_first set.draw do - get ':controller/:action/:id.:format' - get ':controller/:action/:id' + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id.:format" + get ":controller/:action/:id" + end end - extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world") + extras = set.extra_keys(controller: "foo", action: "bar", id: 15, this: "hello", that: "world") assert_equal %w(that this), extras.map(&:to_s).sort end def test_draw assert_equal 0, set.routes.size set.draw do - get '/hello/world' => 'a#b' + get "/hello/world" => "a#b" end assert_equal 1, set.routes.size end @@ -929,16 +986,16 @@ class RouteSetTest < ActiveSupport::TestCase def test_draw_symbol_controller_name assert_equal 0, set.routes.size set.draw do - get '/users/index' => 'users#index' + get "/users/index" => "users#index" end - set.recognize_path('/users/index', :method => :get) + set.recognize_path("/users/index", method: :get) assert_equal 1, set.routes.size end def test_named_draw assert_equal 0, set.routes.size set.draw do - get '/hello/world' => 'a#b', :as => 'hello' + get "/hello/world" => "a#b", :as => "hello" end assert_equal 1, set.routes.size assert_equal set.routes.first, set.named_routes[:hello] @@ -947,57 +1004,57 @@ class RouteSetTest < ActiveSupport::TestCase def test_duplicate_named_route_raises_rather_than_pick_precedence assert_raise ArgumentError do set.draw do - get '/hello/world' => 'a#b', :as => 'hello' - get '/hello' => 'a#b', :as => 'hello' + get "/hello/world" => "a#b", :as => "hello" + get "/hello" => "a#b", :as => "hello" end end end def setup_named_route_test set.draw do - get '/people(/:id)' => 'people#show', :as => 'show' - get '/people' => 'people#index', :as => 'index' - get '/people/go/:foo/:bar/joe(/:id)' => 'people#multi', :as => 'multi' - get '/admin/users' => 'admin/users#index', :as => "users" + get "/people(/:id)" => "people#show", :as => "show" + get "/people" => "people#index", :as => "index" + get "/people/go/:foo/:bar/joe(/:id)" => "people#multi", :as => "multi" + get "/admin/users" => "admin/users#index", :as => "users" end - get URI('http://test.host/people') + get URI("http://test.host/people") controller end def test_named_route_url_method controller = setup_named_route_test - assert_equal "http://test.host/people/5", controller.send(:show_url, :id => 5) - assert_equal "/people/5", controller.send(:show_path, :id => 5) + assert_equal "http://test.host/people/5", controller.send(:show_url, id: 5) + assert_equal "/people/5", controller.send(:show_path, id: 5) assert_equal "http://test.host/people", controller.send(:index_url) assert_equal "/people", controller.send(:index_path) assert_equal "http://test.host/admin/users", controller.send(:users_url) - assert_equal '/admin/users', controller.send(:users_path) + assert_equal "/admin/users", controller.send(:users_path) end def test_named_route_url_method_with_anchor controller = setup_named_route_test - assert_equal "http://test.host/people/5#location", controller.send(:show_url, :id => 5, :anchor => 'location') - assert_equal "/people/5#location", controller.send(:show_path, :id => 5, :anchor => 'location') + assert_equal "http://test.host/people/5#location", controller.send(:show_url, id: 5, anchor: "location") + assert_equal "/people/5#location", controller.send(:show_path, id: 5, anchor: "location") - assert_equal "http://test.host/people#location", controller.send(:index_url, :anchor => 'location') - assert_equal "/people#location", controller.send(:index_path, :anchor => 'location') + assert_equal "http://test.host/people#location", controller.send(:index_url, anchor: "location") + assert_equal "/people#location", controller.send(:index_path, anchor: "location") - assert_equal "http://test.host/admin/users#location", controller.send(:users_url, :anchor => 'location') - assert_equal '/admin/users#location', controller.send(:users_path, :anchor => 'location') + assert_equal "http://test.host/admin/users#location", controller.send(:users_url, anchor: "location") + assert_equal "/admin/users#location", controller.send(:users_path, anchor: "location") assert_equal "http://test.host/people/go/7/hello/joe/5#location", - controller.send(:multi_url, 7, "hello", 5, :anchor => 'location') + controller.send(:multi_url, 7, "hello", 5, anchor: "location") assert_equal "http://test.host/people/go/7/hello/joe/5?baz=bar#location", - controller.send(:multi_url, 7, "hello", 5, :baz => "bar", :anchor => 'location') + controller.send(:multi_url, 7, "hello", 5, baz: "bar", anchor: "location") assert_equal "http://test.host/people?baz=bar#location", - controller.send(:index_url, :baz => "bar", :anchor => 'location') + controller.send(:index_url, baz: "bar", anchor: "location") assert_equal "http://test.host/people", controller.send(:index_url, anchor: nil) assert_equal "http://test.host/people", controller.send(:index_url, anchor: false) @@ -1005,17 +1062,17 @@ class RouteSetTest < ActiveSupport::TestCase def test_named_route_url_method_with_port controller = setup_named_route_test - assert_equal "http://test.host:8080/people/5", controller.send(:show_url, 5, :port=>8080) + assert_equal "http://test.host:8080/people/5", controller.send(:show_url, 5, port: 8080) end def test_named_route_url_method_with_host controller = setup_named_route_test - assert_equal "http://some.example.com/people/5", controller.send(:show_url, 5, :host=>"some.example.com") + assert_equal "http://some.example.com/people/5", controller.send(:show_url, 5, host: "some.example.com") end def test_named_route_url_method_with_protocol controller = setup_named_route_test - assert_equal "https://test.host/people/5", controller.send(:show_url, 5, :protocol => "https") + assert_equal "https://test.host/people/5", controller.send(:show_url, 5, protocol: "https") end def test_named_route_url_method_with_ordered_parameters @@ -1027,7 +1084,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_named_route_url_method_with_ordered_parameters_and_hash controller = setup_named_route_test assert_equal "http://test.host/people/go/7/hello/joe/5?baz=bar", - controller.send(:multi_url, 7, "hello", 5, :baz => "bar") + controller.send(:multi_url, 7, "hello", 5, baz: "bar") end def test_named_route_url_method_with_ordered_parameters_and_empty_hash @@ -1039,41 +1096,46 @@ class RouteSetTest < ActiveSupport::TestCase def test_named_route_url_method_with_no_positional_arguments controller = setup_named_route_test assert_equal "http://test.host/people?baz=bar", - controller.send(:index_url, :baz => "bar") + controller.send(:index_url, baz: "bar") end def test_draw_default_route set.draw do - get '/:controller/:action/:id' + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + end end assert_equal 1, set.routes.size - assert_equal '/users/show/10', url_for(set, { :controller => 'users', :action => 'show', :id => 10 }) - assert_equal '/users/index/10', url_for(set, { :controller => 'users', :id => 10 }) + assert_equal "/users/show/10", url_for(set, controller: "users", action: "show", id: 10) + assert_equal "/users/index/10", url_for(set, controller: "users", id: 10) - assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10')) - assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10/')) + assert_equal({ controller: "users", action: "index", id: "10" }, set.recognize_path("/users/index/10")) + assert_equal({ controller: "users", action: "index", id: "10" }, set.recognize_path("/users/index/10/")) end def test_route_with_parameter_shell set.draw do - get 'page/:id' => 'pages#show', :id => /\d+/ - get '/:controller(/:action(/:id))' + get "page/:id" => "pages#show", :id => /\d+/ + + ActiveSupport::Deprecation.silence do + get "/:controller(/:action(/:id))" + end end - assert_equal({:controller => 'pages', :action => 'index'}, request_path_params('/pages')) - assert_equal({:controller => 'pages', :action => 'index'}, request_path_params('/pages/index')) - assert_equal({:controller => 'pages', :action => 'list'}, request_path_params('/pages/list')) + assert_equal({ controller: "pages", action: "index" }, request_path_params("/pages")) + assert_equal({ controller: "pages", action: "index" }, request_path_params("/pages/index")) + assert_equal({ controller: "pages", action: "list" }, request_path_params("/pages/list")) - assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, request_path_params('/pages/show/10')) - assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, request_path_params('/page/10')) + assert_equal({ controller: "pages", action: "show", id: "10" }, request_path_params("/pages/show/10")) + assert_equal({ controller: "pages", action: "show", id: "10" }, request_path_params("/page/10")) end def test_route_constraints_on_request_object_with_anchors_are_valid assert_nothing_raised do set.draw do - get 'page/:id' => 'pages#show', :constraints => { :host => /^foo$/ } + get "page/:id" => "pages#show", :constraints => { host: /^foo$/ } end end end @@ -1081,27 +1143,27 @@ class RouteSetTest < ActiveSupport::TestCase def test_route_constraints_with_anchor_chars_are_invalid assert_raise ArgumentError do set.draw do - get 'page/:id' => 'pages#show', :id => /^\d+/ + get "page/:id" => "pages#show", :id => /^\d+/ end end assert_raise ArgumentError do set.draw do - get 'page/:id' => 'pages#show', :id => /\A\d+/ + get "page/:id" => "pages#show", :id => /\A\d+/ end end assert_raise ArgumentError do set.draw do - get 'page/:id' => 'pages#show', :id => /\d+$/ + get "page/:id" => "pages#show", :id => /\d+$/ end end assert_raise ArgumentError do set.draw do - get 'page/:id' => 'pages#show', :id => /\d+\Z/ + get "page/:id" => "pages#show", :id => /\d+\Z/ end end assert_raise ArgumentError do set.draw do - get 'page/:id' => 'pages#show', :id => /\d+\z/ + get "page/:id" => "pages#show", :id => /\d+\z/ end end end @@ -1109,7 +1171,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_route_constraints_with_options_method_condition_is_valid assert_nothing_raised do set.draw do - match 'valid/route' => 'pages#show', :via => :options + match "valid/route" => "pages#show", :via => :options end end end @@ -1119,16 +1181,16 @@ class RouteSetTest < ActiveSupport::TestCase get "/people" => "missing#index" end - assert_raises(ActionController::RoutingError) { request_path_params '/people' } + assert_raises(ActionController::RoutingError) { request_path_params "/people" } end def test_recognize_with_encoded_id_and_regex set.draw do - get 'page/:id' => 'pages#show', :id => /[a-zA-Z0-9\+]+/ + get "page/:id" => "pages#show", :id => /[a-zA-Z0-9\+]+/ end - assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, request_path_params('/page/10')) - assert_equal({:controller => 'pages', :action => 'show', :id => 'hello+world'}, request_path_params('/page/hello+world')) + assert_equal({ controller: "pages", action: "show", id: "10" }, request_path_params("/page/10")) + assert_equal({ controller: "pages", action: "show", id: "hello+world" }, request_path_params("/page/hello+world")) end def test_recognize_with_http_methods @@ -1141,65 +1203,65 @@ class RouteSetTest < ActiveSupport::TestCase delete "/people/:id" => "people#destroy" end - params = request_path_params("/people", :method => :get) + params = request_path_params("/people", method: :get) assert_equal("index", params[:action]) - params = request_path_params("/people", :method => :post) + params = request_path_params("/people", method: :post) assert_equal("create", params[:action]) - params = request_path_params("/people/5", :method => :put) + params = request_path_params("/people/5", method: :put) assert_equal("update", params[:action]) - params = request_path_params("/people/5", :method => :patch) + params = request_path_params("/people/5", method: :patch) assert_equal("update", params[:action]) assert_raise(ActionController::UnknownHttpMethod) { - request_path_params("/people", :method => :bacon) + request_path_params("/people", method: :bacon) } - params = request_path_params("/people/5", :method => :get) + params = request_path_params("/people/5", method: :get) assert_equal("show", params[:action]) assert_equal("5", params[:id]) - params = request_path_params("/people/5", :method => :put) + params = request_path_params("/people/5", method: :put) assert_equal("update", params[:action]) assert_equal("5", params[:id]) - params = request_path_params("/people/5", :method => :patch) + params = request_path_params("/people/5", method: :patch) assert_equal("update", params[:action]) assert_equal("5", params[:id]) - params = request_path_params("/people/5", :method => :delete) + params = request_path_params("/people/5", method: :delete) assert_equal("destroy", params[:action]) assert_equal("5", params[:id]) assert_raise(ActionController::RoutingError) { - request_path_params("/people/5", :method => :post) + request_path_params("/people/5", method: :post) } end def test_recognize_with_alias_in_conditions set.draw do - match "/people" => 'people#index', :as => 'people', :via => :get - root :to => "people#index" + match "/people" => "people#index", :as => "people", :via => :get + root to: "people#index" end - params = request_path_params("/people", :method => :get) + params = request_path_params("/people", method: :get) assert_equal("people", params[:controller]) assert_equal("index", params[:action]) - params = request_path_params("/", :method => :get) + params = request_path_params("/", method: :get) assert_equal("people", params[:controller]) assert_equal("index", params[:action]) end def test_typo_recognition set.draw do - get 'articles/:year/:month/:day/:title' => 'articles#permalink', + get "articles/:year/:month/:day/:title" => "articles#permalink", :year => /\d{4}/, :day => /\d{1,2}/, :month => /\d{1,2}/ end - params = request_path_params("/articles/2005/11/05/a-very-interesting-article", :method => :get) + params = request_path_params("/articles/2005/11/05/a-very-interesting-article", method: :get) assert_equal("permalink", params[:action]) assert_equal("2005", params[:year]) assert_equal("11", params[:month]) @@ -1210,7 +1272,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_routing_traversal_does_not_load_extra_classes assert !Object.const_defined?("Profiler__"), "Profiler should not be loaded" set.draw do - get '/profile' => 'profile#index' + get "/profile" => "profile#index" end request_path_params("/profile") rescue nil @@ -1226,17 +1288,17 @@ class RouteSetTest < ActiveSupport::TestCase get "people/:id(.:format)" => "people#show" end - params = request_path_params("/people/5", :method => :get) + params = request_path_params("/people/5", method: :get) assert_equal("show", params[:action]) assert_equal("5", params[:id]) - params = request_path_params("/people/5", :method => :put) + params = request_path_params("/people/5", method: :put) assert_equal("update", params[:action]) - params = request_path_params("/people/5", :method => :patch) + params = request_path_params("/people/5", method: :patch) assert_equal("update", params[:action]) - params = request_path_params("/people/5.png", :method => :get) + params = request_path_params("/people/5.png", method: :get) assert_equal("show", params[:action]) assert_equal("5", params[:id]) assert_equal("png", params[:format]) @@ -1244,18 +1306,18 @@ class RouteSetTest < ActiveSupport::TestCase def test_generate_with_default_action set.draw do - get "/people", :controller => "people", :action => "index" - get "/people/list", :controller => "people", :action => "list" + get "/people", controller: "people", action: "index" + get "/people/list", controller: "people", action: "list" end - url = url_for(set, { :controller => "people", :action => "list" }) + url = url_for(set, controller: "people", action: "list") assert_equal "/people/list", url end def test_root_map - set.draw { root :to => 'people#index' } + set.draw { root to: "people#index" } - params = request_path_params("", :method => :get) + params = request_path_params("", method: :get) assert_equal("people", params[:controller]) assert_equal("index", params[:action]) end @@ -1263,49 +1325,49 @@ class RouteSetTest < ActiveSupport::TestCase def test_namespace set.draw do - namespace 'api' do - get 'inventory' => 'products#inventory' + namespace "api" do + get "inventory" => "products#inventory" end end - params = request_path_params("/api/inventory", :method => :get) + params = request_path_params("/api/inventory", method: :get) assert_equal("api/products", params[:controller]) assert_equal("inventory", params[:action]) end def test_namespaced_root_map set.draw do - namespace 'api' do - root :to => 'products#index' + namespace "api" do + root to: "products#index" end end - params = request_path_params("/api", :method => :get) + params = request_path_params("/api", method: :get) assert_equal("api/products", params[:controller]) assert_equal("index", params[:action]) end def test_namespace_with_path_prefix set.draw do - scope :module => "api", :path => "prefix" do - get 'inventory' => 'products#inventory' + scope module: "api", path: "prefix" do + get "inventory" => "products#inventory" end end - params = request_path_params("/prefix/inventory", :method => :get) + params = request_path_params("/prefix/inventory", method: :get) assert_equal("api/products", params[:controller]) assert_equal("inventory", params[:action]) end def test_namespace_with_blank_path_prefix set.draw do - scope :module => "api", :path => "" do - get 'inventory' => 'products#inventory' + scope module: "api", path: "" do + get "inventory" => "products#inventory" end end - params = request_path_params("/inventory", :method => :get) + params = request_path_params("/inventory", method: :get) assert_equal("api/products", params[:controller]) assert_equal("inventory", params[:action]) end @@ -1314,33 +1376,38 @@ class RouteSetTest < ActiveSupport::TestCase @set = make_set false set.draw do - get ':controller/:id/:action' + ActiveSupport::Deprecation.silence do + get ":controller/:id/:action" + end end - get URI('http://test.host/people/7/show') + get URI("http://test.host/people/7/show") - assert_equal "/people/7/destroy", controller.url_for(:action => 'destroy', :only_path => true) + assert_equal "/people/7/destroy", controller.url_for(action: "destroy", only_path: true) end def test_use_static_path_when_possible @set = make_set false set.draw do - get 'about' => "welcome#about" - get ':controller/:action/:id' + get "about" => "welcome#about" + + ActiveSupport::Deprecation.silence do + get ":controller/:id/:action" + end end - get URI('http://test.host/welcom/get/7') + get URI("http://test.host/welcom/get/7") - assert_equal "/about", controller.url_for(:controller => 'welcome', - :action => 'about', - :only_path => true) + assert_equal "/about", controller.url_for(controller: "welcome", + action: "about", + only_path: true) end def test_generate - set.draw { get ':controller/:action/:id' } + set.draw { ActiveSupport::Deprecation.silence { get ":controller/:action/:id" } } - args = { :controller => "foo", :action => "bar", :id => "7", :x => "y" } + args = { controller: "foo", action: "bar", id: "7", x: "y" } assert_equal "/foo/bar/7?x=y", url_for(set, args) assert_equal ["/foo/bar/7", [:x]], set.generate_extras(args) assert_equal [:x], set.extra_keys(args) @@ -1349,22 +1416,26 @@ class RouteSetTest < ActiveSupport::TestCase def test_generate_with_path_prefix set.draw do scope "my" do - get ':controller(/:action(/:id))' + ActiveSupport::Deprecation.silence do + get ":controller(/:action(/:id))" + end end end - args = { :controller => "foo", :action => "bar", :id => "7", :x => "y" } + args = { controller: "foo", action: "bar", id: "7", x: "y" } assert_equal "/my/foo/bar/7?x=y", url_for(set, args) end def test_generate_with_blank_path_prefix set.draw do scope "" do - get ':controller(/:action(/:id))' + ActiveSupport::Deprecation.silence do + get ":controller(/:action(/:id))" + end end end - args = { :controller => "foo", :action => "bar", :id => "7", :x => "y" } + args = { controller: "foo", action: "bar", id: "7", x: "y" } assert_equal "/foo/bar/7?x=y", url_for(set, args) end @@ -1372,19 +1443,21 @@ class RouteSetTest < ActiveSupport::TestCase @set = make_set false set.draw do - get "/connection/manage(/:action)" => 'connection/manage#index' - get "/connection/connection" => "connection/connection#index" - get '/connection' => 'connection#index', :as => 'family_connection' + ActiveSupport::Deprecation.silence do + get "/connection/manage(/:action)" => "connection/manage#index" + get "/connection/connection" => "connection/connection#index" + get "/connection" => "connection#index", :as => "family_connection" + end end - assert_equal({ :controller => 'connection/manage', - :action => 'index', }, request_path_params('/connection/manage')) + assert_equal({ controller: "connection/manage", + action: "index", }, request_path_params("/connection/manage")) - url = controller.url_for({ :controller => "connection", :only_path => true }) + url = controller.url_for(controller: "connection", only_path: true) assert_equal "/connection/connection", url - url = controller.url_for({ :use_route => "family_connection", - :controller => "connection", :only_path => true }) + url = controller.url_for(use_route: "family_connection", + controller: "connection", only_path: true) assert_equal "/connection", url end @@ -1392,71 +1465,76 @@ class RouteSetTest < ActiveSupport::TestCase @set = make_set false set.draw do - get ':controller(/:action(/:id))' + ActiveSupport::Deprecation.silence do + get ":controller(/:action(/:id))" + end end - get URI('http://test.host/books/show/10') + get URI("http://test.host/books/show/10") - assert_equal '/books', controller.url_for(:controller => 'books', - :only_path => true, - :action => 'index') + assert_equal "/books", controller.url_for(controller: "books", + only_path: true, + action: "index") end def test_query_params_will_be_shown_when_recalled @set = make_set false set.draw do - get 'show_weblog/:parameter' => 'weblog#show' - get ':controller(/:action(/:id))' + get "show_weblog/:parameter" => "weblog#show" + + ActiveSupport::Deprecation.silence do + get ":controller(/:action(/:id))" + end end - get URI('http://test.host/weblog/show/1') + get URI("http://test.host/weblog/show/1") - assert_equal '/weblog/edit?parameter=1', controller.url_for( - {:action => 'edit', :parameter => 1, :only_path => true}) + assert_equal "/weblog/edit?parameter=1", controller.url_for( + action: "edit", parameter: 1, only_path: true) end def test_format_is_not_inherit set.draw do - get '/posts(.:format)' => 'posts#index' + get "/posts(.:format)" => "posts#index" end - get URI('http://test.host/posts.xml') - assert_equal({:controller => 'posts', :action => 'index', :format => 'xml'}, + get URI("http://test.host/posts.xml") + assert_equal({ controller: "posts", action: "index", format: "xml" }, controller.request.path_parameters) - assert_equal '/posts', controller.url_for( - {:controller => 'posts', :only_path => true}) + assert_equal "/posts", controller.url_for( + controller: "posts", only_path: true) - assert_equal '/posts.xml', controller.url_for( - {:controller => 'posts', :format => 'xml', :only_path => true}) + assert_equal "/posts.xml", controller.url_for( + controller: "posts", format: "xml", only_path: true) end def test_expiry_determination_should_consider_values_with_to_param @set = make_set false - set.draw { get 'projects/:project_id/:controller/:action' } + set.draw { ActiveSupport::Deprecation.silence { get "projects/:project_id/:controller/:action" } } - get URI('http://test.host/projects/1/weblog/show') + get URI("http://test.host/projects/1/weblog/show") assert_equal( - { :controller => 'weblog', :action => 'show', :project_id => '1' }, + { controller: "weblog", action: "show", project_id: "1" }, controller.request.path_parameters) - assert_equal '/projects/1/weblog/show', - controller.url_for({ :action => 'show', :project_id => 1, :only_path => true }) + assert_equal "/projects/1/weblog/show", + controller.url_for(action: "show", project_id: 1, only_path: true) end def test_named_route_in_nested_resource set.draw do resources :projects do member do - get 'milestones' => 'milestones#index', :as => 'milestones' + get "milestones" => "milestones#index", :as => "milestones" end end end - params = set.recognize_path("/projects/1/milestones", :method => :get) + params = set.recognize_path("/projects/1/milestones", method: :get) assert_equal("milestones", params[:controller]) assert_equal("index", params[:action]) end @@ -1465,7 +1543,7 @@ class RouteSetTest < ActiveSupport::TestCase assert_nothing_raised do set.draw do namespace :admin do - root :to => "home#index" + root to: "home#index" end end end @@ -1474,8 +1552,8 @@ class RouteSetTest < ActiveSupport::TestCase def test_setting_root_in_namespace_using_string assert_nothing_raised do set.draw do - namespace 'admin' do - root :to => "home#index" + namespace "admin" do + root to: "home#index" end end end @@ -1484,8 +1562,8 @@ class RouteSetTest < ActiveSupport::TestCase def test_route_constraints_with_unsupported_regexp_options_must_error assert_raise ArgumentError do set.draw do - get 'page/:name' => 'pages#show', - :constraints => { :name => /(david|jamis)/m } + get "page/:name" => "pages#show", + :constraints => { name: /(david|jamis)/m } end end end @@ -1493,14 +1571,14 @@ class RouteSetTest < ActiveSupport::TestCase def test_route_constraints_with_supported_options_must_not_error assert_nothing_raised do set.draw do - get 'page/:name' => 'pages#show', - :constraints => { :name => /(david|jamis)/i } + get "page/:name" => "pages#show", + :constraints => { name: /(david|jamis)/i } end end assert_nothing_raised do set.draw do - get 'page/:name' => 'pages#show', - :constraints => { :name => / # Desperately overcommented regexp + get "page/:name" => "pages#show", + :constraints => { name: / # Desperately overcommented regexp ( #Either david #The Creator | #Or @@ -1513,235 +1591,243 @@ class RouteSetTest < ActiveSupport::TestCase def test_route_with_subdomain_and_constraints_must_receive_params name_param = nil set.draw do - get 'page/:name' => 'pages#show', :constraints => lambda {|request| + get "page/:name" => "pages#show", :constraints => lambda { |request| name_param = request.params[:name] return true } end - assert_equal({:controller => 'pages', :action => 'show', :name => 'mypage'}, - set.recognize_path('http://subdomain.example.org/page/mypage')) - assert_equal(name_param, 'mypage') + assert_equal({ controller: "pages", action: "show", name: "mypage" }, + set.recognize_path("http://subdomain.example.org/page/mypage")) + assert_equal(name_param, "mypage") end def test_route_requirement_recognize_with_ignore_case set.draw do - get 'page/:name' => 'pages#show', - :constraints => {:name => /(david|jamis)/i} + get "page/:name" => "pages#show", + :constraints => { name: /(david|jamis)/i } end - assert_equal({:controller => 'pages', :action => 'show', :name => 'jamis'}, set.recognize_path('/page/jamis')) + assert_equal({ controller: "pages", action: "show", name: "jamis" }, set.recognize_path("/page/jamis")) assert_raise ActionController::RoutingError do - set.recognize_path('/page/davidjamis') + set.recognize_path("/page/davidjamis") end - assert_equal({:controller => 'pages', :action => 'show', :name => 'DAVID'}, set.recognize_path('/page/DAVID')) + assert_equal({ controller: "pages", action: "show", name: "DAVID" }, set.recognize_path("/page/DAVID")) end def test_route_requirement_generate_with_ignore_case set.draw do - get 'page/:name' => 'pages#show', - :constraints => {:name => /(david|jamis)/i} + get "page/:name" => "pages#show", + :constraints => { name: /(david|jamis)/i } end - url = url_for(set, { :controller => 'pages', :action => 'show', :name => 'david' }) + url = url_for(set, controller: "pages", action: "show", name: "david") assert_equal "/page/david", url assert_raise(ActionController::UrlGenerationError) do - url_for(set, { :controller => 'pages', :action => 'show', :name => 'davidjamis' }) + url_for(set, controller: "pages", action: "show", name: "davidjamis") end - url = url_for(set, { :controller => 'pages', :action => 'show', :name => 'JAMIS' }) + url = url_for(set, controller: "pages", action: "show", name: "JAMIS") assert_equal "/page/JAMIS", url end def test_route_requirement_recognize_with_extended_syntax set.draw do - get 'page/:name' => 'pages#show', - :constraints => {:name => / # Desperately overcommented regexp + get "page/:name" => "pages#show", + :constraints => { name: / # Desperately overcommented regexp ( #Either david #The Creator | #Or jamis #The Deployer - )/x} + )/x } end - assert_equal({:controller => 'pages', :action => 'show', :name => 'jamis'}, set.recognize_path('/page/jamis')) - assert_equal({:controller => 'pages', :action => 'show', :name => 'david'}, set.recognize_path('/page/david')) + assert_equal({ controller: "pages", action: "show", name: "jamis" }, set.recognize_path("/page/jamis")) + assert_equal({ controller: "pages", action: "show", name: "david" }, set.recognize_path("/page/david")) assert_raise ActionController::RoutingError do - set.recognize_path('/page/david #The Creator') + set.recognize_path("/page/david #The Creator") end assert_raise ActionController::RoutingError do - set.recognize_path('/page/David') + set.recognize_path("/page/David") end end def test_route_requirement_with_xi_modifiers set.draw do - get 'page/:name' => 'pages#show', - :constraints => {:name => / # Desperately overcommented regexp + get "page/:name" => "pages#show", + :constraints => { name: / # Desperately overcommented regexp ( #Either david #The Creator | #Or jamis #The Deployer - )/xi} + )/xi } end - assert_equal({:controller => 'pages', :action => 'show', :name => 'JAMIS'}, - set.recognize_path('/page/JAMIS')) + assert_equal({ controller: "pages", action: "show", name: "JAMIS" }, + set.recognize_path("/page/JAMIS")) assert_equal "/page/JAMIS", - url_for(set, { :controller => 'pages', :action => 'show', :name => 'JAMIS' }) + url_for(set, controller: "pages", action: "show", name: "JAMIS") end def test_routes_with_symbols set.draw do - get 'unnamed', :controller => :pages, :action => :show, :name => :as_symbol - get 'named' , :controller => :pages, :action => :show, :name => :as_symbol, :as => :named + get "unnamed", controller: :pages, action: :show, name: :as_symbol + get "named" , controller: :pages, action: :show, name: :as_symbol, as: :named end - assert_equal({:controller => 'pages', :action => 'show', :name => :as_symbol}, set.recognize_path('/unnamed')) - assert_equal({:controller => 'pages', :action => 'show', :name => :as_symbol}, set.recognize_path('/named')) + assert_equal({ controller: "pages", action: "show", name: :as_symbol }, set.recognize_path("/unnamed")) + assert_equal({ controller: "pages", action: "show", name: :as_symbol }, set.recognize_path("/named")) end def test_regexp_chunk_should_add_question_mark_for_optionals set.draw do - get '/' => 'foo#index' - get '/hello' => 'bar#index' + get "/" => "foo#index" + get "/hello" => "bar#index" end - assert_equal '/', url_for(set, { :controller => 'foo' }) - assert_equal '/hello', url_for(set, { :controller => 'bar' }) + assert_equal "/", url_for(set, controller: "foo") + assert_equal "/hello", url_for(set, controller: "bar") - assert_equal({:controller => "foo", :action => "index"}, set.recognize_path('/')) - assert_equal({:controller => "bar", :action => "index"}, set.recognize_path('/hello')) + assert_equal({ controller: "foo", action: "index" }, set.recognize_path("/")) + assert_equal({ controller: "bar", action: "index" }, set.recognize_path("/hello")) end def test_assign_route_options_with_anchor_chars set.draw do - get '/cars/:action/:person/:car/', :controller => 'cars' + ActiveSupport::Deprecation.silence do + get "/cars/:action/:person/:car/", controller: "cars" + end end - assert_equal '/cars/buy/1/2', url_for(set, { :controller => 'cars', :action => 'buy', :person => '1', :car => '2' }) + assert_equal "/cars/buy/1/2", url_for(set, controller: "cars", action: "buy", person: "1", car: "2") - assert_equal({:controller => "cars", :action => "buy", :person => "1", :car => "2"}, set.recognize_path('/cars/buy/1/2')) + assert_equal({ controller: "cars", action: "buy", person: "1", car: "2" }, set.recognize_path("/cars/buy/1/2")) end def test_segmentation_of_dot_path set.draw do - get '/books/:action.rss', :controller => 'books' + ActiveSupport::Deprecation.silence do + get "/books/:action.rss", controller: "books" + end end - assert_equal '/books/list.rss', url_for(set, { :controller => 'books', :action => 'list' }) + assert_equal "/books/list.rss", url_for(set, controller: "books", action: "list") - assert_equal({:controller => "books", :action => "list"}, set.recognize_path('/books/list.rss')) + assert_equal({ controller: "books", action: "list" }, set.recognize_path("/books/list.rss")) end def test_segmentation_of_dynamic_dot_path set.draw do - get '/books(/:action(.:format))', :controller => 'books' + ActiveSupport::Deprecation.silence do + get "/books(/:action(.:format))", controller: "books" + end end - assert_equal '/books/list.rss', url_for(set, { :controller => 'books', :action => 'list', :format => 'rss' }) - assert_equal '/books/list.xml', url_for(set, { :controller => 'books', :action => 'list', :format => 'xml' }) - assert_equal '/books/list', url_for(set, { :controller => 'books', :action => 'list' }) - assert_equal '/books', url_for(set, { :controller => 'books', :action => 'index' }) + assert_equal "/books/list.rss", url_for(set, controller: "books", action: "list", format: "rss") + assert_equal "/books/list.xml", url_for(set, controller: "books", action: "list", format: "xml") + assert_equal "/books/list", url_for(set, controller: "books", action: "list") + assert_equal "/books", url_for(set, controller: "books", action: "index") - assert_equal({:controller => "books", :action => "list", :format => "rss"}, set.recognize_path('/books/list.rss')) - assert_equal({:controller => "books", :action => "list", :format => "xml"}, set.recognize_path('/books/list.xml')) - assert_equal({:controller => "books", :action => "list"}, set.recognize_path('/books/list')) - assert_equal({:controller => "books", :action => "index"}, set.recognize_path('/books')) + assert_equal({ controller: "books", action: "list", format: "rss" }, set.recognize_path("/books/list.rss")) + assert_equal({ controller: "books", action: "list", format: "xml" }, set.recognize_path("/books/list.xml")) + assert_equal({ controller: "books", action: "list" }, set.recognize_path("/books/list")) + assert_equal({ controller: "books", action: "index" }, set.recognize_path("/books")) end def test_slashes_are_implied - set.draw { get("/:controller(/:action(/:id))") } + set.draw { ActiveSupport::Deprecation.silence { get("/:controller(/:action(/:id))") } } - assert_equal '/content', url_for(set, { :controller => 'content', :action => 'index' }) - assert_equal '/content/list', url_for(set, { :controller => 'content', :action => 'list' }) - assert_equal '/content/show/1', url_for(set, { :controller => 'content', :action => 'show', :id => '1' }) + assert_equal "/content", url_for(set, controller: "content", action: "index") + assert_equal "/content/list", url_for(set, controller: "content", action: "list") + assert_equal "/content/show/1", url_for(set, controller: "content", action: "show", id: "1") - assert_equal({:controller => "content", :action => "index"}, set.recognize_path('/content')) - assert_equal({:controller => "content", :action => "index"}, set.recognize_path('/content/index')) - assert_equal({:controller => "content", :action => "list"}, set.recognize_path('/content/list')) - assert_equal({:controller => "content", :action => "show", :id => "1"}, set.recognize_path('/content/show/1')) + assert_equal({ controller: "content", action: "index" }, set.recognize_path("/content")) + assert_equal({ controller: "content", action: "index" }, set.recognize_path("/content/index")) + assert_equal({ controller: "content", action: "list" }, set.recognize_path("/content/list")) + assert_equal({ controller: "content", action: "show", id: "1" }, set.recognize_path("/content/show/1")) end def test_default_route_recognition - expected = {:controller => 'pages', :action => 'show', :id => '10'} - assert_equal expected, default_route_set.recognize_path('/pages/show/10') - assert_equal expected, default_route_set.recognize_path('/pages/show/10/') + expected = { controller: "pages", action: "show", id: "10" } + assert_equal expected, default_route_set.recognize_path("/pages/show/10") + assert_equal expected, default_route_set.recognize_path("/pages/show/10/") - expected[:id] = 'jamis' - assert_equal expected, default_route_set.recognize_path('/pages/show/jamis/') + expected[:id] = "jamis" + assert_equal expected, default_route_set.recognize_path("/pages/show/jamis/") expected.delete :id - assert_equal expected, default_route_set.recognize_path('/pages/show') - assert_equal expected, default_route_set.recognize_path('/pages/show/') + assert_equal expected, default_route_set.recognize_path("/pages/show") + assert_equal expected, default_route_set.recognize_path("/pages/show/") - expected[:action] = 'index' - assert_equal expected, default_route_set.recognize_path('/pages/') - assert_equal expected, default_route_set.recognize_path('/pages') + expected[:action] = "index" + assert_equal expected, default_route_set.recognize_path("/pages/") + assert_equal expected, default_route_set.recognize_path("/pages") - assert_raise(ActionController::RoutingError) { default_route_set.recognize_path('/') } - assert_raise(ActionController::RoutingError) { default_route_set.recognize_path('/pages/how/goood/it/is/to/be/free') } + assert_raise(ActionController::RoutingError) { default_route_set.recognize_path("/") } + assert_raise(ActionController::RoutingError) { default_route_set.recognize_path("/pages/how/goood/it/is/to/be/free") } end def test_default_route_should_omit_default_action - assert_equal '/accounts', url_for(default_route_set, { :controller => 'accounts', :action => 'index' }) + assert_equal "/accounts", url_for(default_route_set, controller: "accounts", action: "index") end def test_default_route_should_include_default_action_when_id_present - assert_equal '/accounts/index/20', url_for(default_route_set, { :controller => 'accounts', :action => 'index', :id => '20' }) + assert_equal "/accounts/index/20", url_for(default_route_set, controller: "accounts", action: "index", id: "20") end def test_default_route_should_work_with_action_but_no_id - assert_equal '/accounts/list_all', url_for(default_route_set, { :controller => 'accounts', :action => 'list_all' }) + assert_equal "/accounts/list_all", url_for(default_route_set, controller: "accounts", action: "list_all") end def test_default_route_should_uri_escape_pluses - expected = { :controller => 'pages', :action => 'show', :id => 'hello world' } - assert_equal expected, default_route_set.recognize_path('/pages/show/hello%20world') - assert_equal '/pages/show/hello%20world', url_for(default_route_set, expected) + expected = { controller: "pages", action: "show", id: "hello world" } + assert_equal expected, default_route_set.recognize_path("/pages/show/hello%20world") + assert_equal "/pages/show/hello%20world", url_for(default_route_set, expected) - expected[:id] = 'hello+world' - assert_equal expected, default_route_set.recognize_path('/pages/show/hello+world') - assert_equal expected, default_route_set.recognize_path('/pages/show/hello%2Bworld') - assert_equal '/pages/show/hello+world', url_for(default_route_set, expected) + expected[:id] = "hello+world" + assert_equal expected, default_route_set.recognize_path("/pages/show/hello+world") + assert_equal expected, default_route_set.recognize_path("/pages/show/hello%2Bworld") + assert_equal "/pages/show/hello+world", url_for(default_route_set, expected) end def test_build_empty_query_string - assert_uri_equal '/foo', url_for(default_route_set, { :controller => 'foo' }) + assert_uri_equal "/foo", url_for(default_route_set, controller: "foo") end def test_build_query_string_with_nil_value - assert_uri_equal '/foo', url_for(default_route_set, { :controller => 'foo', :x => nil }) + assert_uri_equal "/foo", url_for(default_route_set, controller: "foo", x: nil) end def test_simple_build_query_string - assert_uri_equal '/foo?x=1&y=2', url_for(default_route_set, { :controller => 'foo', :x => '1', :y => '2' }) + assert_uri_equal "/foo?x=1&y=2", url_for(default_route_set, controller: "foo", x: "1", y: "2") end def test_convert_ints_build_query_string - assert_uri_equal '/foo?x=1&y=2', url_for(default_route_set, { :controller => 'foo', :x => 1, :y => 2 }) + assert_uri_equal "/foo?x=1&y=2", url_for(default_route_set, controller: "foo", x: 1, y: 2) end def test_escape_spaces_build_query_string - assert_uri_equal '/foo?x=hello+world&y=goodbye+world', url_for(default_route_set, { :controller => 'foo', :x => 'hello world', :y => 'goodbye world' }) + assert_uri_equal "/foo?x=hello+world&y=goodbye+world", url_for(default_route_set, controller: "foo", x: "hello world", y: "goodbye world") end def test_expand_array_build_query_string - assert_uri_equal '/foo?x%5B%5D=1&x%5B%5D=2', url_for(default_route_set, { :controller => 'foo', :x => [1, 2] }) + assert_uri_equal "/foo?x%5B%5D=1&x%5B%5D=2", url_for(default_route_set, controller: "foo", x: [1, 2]) end def test_escape_spaces_build_query_string_selected_keys - assert_uri_equal '/foo?x=hello+world', url_for(default_route_set, { :controller => 'foo', :x => 'hello world' }) + assert_uri_equal "/foo?x=hello+world", url_for(default_route_set, controller: "foo", x: "hello world") end def test_generate_with_default_params set.draw do - get 'dummy/page/:page' => 'dummy#show' - get 'dummy/dots/page.:page' => 'dummy#dots' - get 'ibocorp(/:page)' => 'ibocorp#show', - :constraints => { :page => /\d+/ }, - :defaults => { :page => 1 } - - get ':controller/:action/:id' + get "dummy/page/:page" => "dummy#show" + get "dummy/dots/page.:page" => "dummy#dots" + get "ibocorp(/:page)" => "ibocorp#show", + :constraints => { page: /\d+/ }, + :defaults => { page: 1 } + + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + end end - assert_equal '/ibocorp', url_for(set, { :controller => 'ibocorp', :action => "show", :page => 1 }) + assert_equal "/ibocorp", url_for(set, controller: "ibocorp", action: "show", page: 1) end include ActionDispatch::RoutingVerbs @@ -1752,17 +1838,21 @@ class RouteSetTest < ActiveSupport::TestCase @set = make_set false set.draw do - get "blog/", :controller => "blog", :action => "index" + get "blog/", controller: "blog", action: "index" get "blog(/:year(/:month(/:day)))", - :controller => "blog", - :action => "show_date", - :constraints => { :year => /(19|20)\d\d/, :month => /[01]?\d/, :day => /[0-3]?\d/ }, - :day => nil, :month => nil + controller: "blog", + action: "show_date", + constraints: { year: /(19|20)\d\d/, month: /[01]?\d/, day: /[0-3]?\d/ }, + day: nil, month: nil + + get "blog/show/:id", controller: "blog", action: "show", id: /\d+/ - get "blog/show/:id", :controller => "blog", :action => "show", :id => /\d+/ - get "blog/:controller/:action(/:id)" - get "*anything", :controller => "blog", :action => "unknown_request" + ActiveSupport::Deprecation.silence do + get "blog/:controller/:action(/:id)" + end + + get "*anything", controller: "blog", action: "unknown_request" end recognize_path = ->(path) { @@ -1770,24 +1860,24 @@ class RouteSetTest < ActiveSupport::TestCase controller.request.path_parameters } - assert_equal({:controller => "blog", :action => "index"}, recognize_path.("/blog")) - assert_equal({:controller => "blog", :action => "show", :id => "123"}, recognize_path.("/blog/show/123")) - assert_equal({:controller => "blog", :action => "show_date", :year => "2004", :day => nil, :month => nil }, recognize_path.("/blog/2004")) - assert_equal({:controller => "blog", :action => "show_date", :year => "2004", :month => "12", :day => nil }, recognize_path.("/blog/2004/12")) - assert_equal({:controller => "blog", :action => "show_date", :year => "2004", :month => "12", :day => "25"}, recognize_path.("/blog/2004/12/25")) - assert_equal({:controller => "articles", :action => "edit", :id => "123"}, recognize_path.("/blog/articles/edit/123")) - assert_equal({:controller => "articles", :action => "show_stats"}, recognize_path.("/blog/articles/show_stats")) - assert_equal({:controller => "blog", :action => "unknown_request", :anything => "blog/wibble"}, recognize_path.("/blog/wibble")) - assert_equal({:controller => "blog", :action => "unknown_request", :anything => "junk"}, recognize_path.("/junk")) + assert_equal({ controller: "blog", action: "index" }, recognize_path.("/blog")) + assert_equal({ controller: "blog", action: "show", id: "123" }, recognize_path.("/blog/show/123")) + assert_equal({ controller: "blog", action: "show_date", year: "2004", day: nil, month: nil }, recognize_path.("/blog/2004")) + assert_equal({ controller: "blog", action: "show_date", year: "2004", month: "12", day: nil }, recognize_path.("/blog/2004/12")) + assert_equal({ controller: "blog", action: "show_date", year: "2004", month: "12", day: "25" }, recognize_path.("/blog/2004/12/25")) + assert_equal({ controller: "articles", action: "edit", id: "123" }, recognize_path.("/blog/articles/edit/123")) + assert_equal({ controller: "articles", action: "show_stats" }, recognize_path.("/blog/articles/show_stats")) + assert_equal({ controller: "blog", action: "unknown_request", anything: "blog/wibble" }, recognize_path.("/blog/wibble")) + assert_equal({ controller: "blog", action: "unknown_request", anything: "junk" }, recognize_path.("/junk")) - get URI('http://example.org/blog/2006/07/28') + get URI("http://example.org/blog/2006/07/28") - assert_equal({:controller => "blog", :action => "show_date", :year => "2006", :month => "07", :day => "28"}, controller.request.path_parameters) - assert_equal("/blog/2006/07/25", controller.url_for({ :day => 25, :only_path => true })) - assert_equal("/blog/2005", controller.url_for({ :year => 2005, :only_path => true })) - assert_equal("/blog/show/123", controller.url_for({ :action => "show" , :id => 123, :only_path => true })) - assert_equal("/blog/2006", controller.url_for({ :year => 2006, :only_path => true })) - assert_equal("/blog/2006", controller.url_for({ :year => 2006, :month => nil, :only_path => true })) + assert_equal({ controller: "blog", action: "show_date", year: "2006", month: "07", day: "28" }, controller.request.path_parameters) + assert_equal("/blog/2006/07/25", controller.url_for(day: 25, only_path: true)) + assert_equal("/blog/2005", controller.url_for(year: 2005, only_path: true)) + assert_equal("/blog/show/123", controller.url_for(action: "show" , id: 123, only_path: true)) + assert_equal("/blog/2006", controller.url_for(year: 2006, only_path: true)) + assert_equal("/blog/2006", controller.url_for(year: 2006, month: nil, only_path: true)) end private @@ -1796,8 +1886,8 @@ class RouteSetTest < ActiveSupport::TestCase end def sort_query_string_params(uri) - path, qs = uri.split('?') - qs = qs.split('&').sort.join('&') if qs + path, qs = uri.split("?") + qs = qs.split("&").sort.join("&") if qs qs ? "#{path}?#{qs}" : path end end @@ -1812,52 +1902,59 @@ class RackMountIntegrationTests < ActiveSupport::TestCase resources :users, :posts end - namespace 'api' do - root :to => 'users#index' + namespace "api" do + root to: "users#index" end - get '/blog(/:year(/:month(/:day)))' => 'posts#show_date', + get "/blog(/:year(/:month(/:day)))" => "posts#show_date", :constraints => { - :year => /(19|20)\d\d/, - :month => /[01]?\d/, - :day => /[0-3]?\d/ + year: /(19|20)\d\d/, + month: /[01]?\d/, + day: /[0-3]?\d/ }, :day => nil, :month => nil - get 'archive/:year', :controller => 'archive', :action => 'index', - :defaults => { :year => nil }, - :constraints => { :year => /\d{4}/ }, - :as => "blog" + get "archive/:year", controller: "archive", action: "index", + defaults: { year: nil }, + constraints: { year: /\d{4}/ }, + as: "blog" resources :people - get 'legacy/people' => "people#index", :legacy => "true" - - get 'symbols', :controller => :symbols, :action => :show, :name => :as_symbol - get 'id_default(/:id)' => "foo#id_default", :id => 1 - match 'get_or_post' => "foo#get_or_post", :via => [:get, :post] - get 'optional/:optional' => "posts#index" - get 'projects/:project_id' => "project#index", :as => "project" - get 'clients' => "projects#index" - - get 'ignorecase/geocode/:postalcode' => 'geocode#show', :postalcode => /hx\d\d-\d[a-z]{2}/i - get 'extended/geocode/:postalcode' => 'geocode#show',:constraints => { - :postalcode => /# Postcode format + get "legacy/people" => "people#index", :legacy => "true" + + get "symbols", controller: :symbols, action: :show, name: :as_symbol + get "id_default(/:id)" => "foo#id_default", :id => 1 + match "get_or_post" => "foo#get_or_post", :via => [:get, :post] + get "optional/:optional" => "posts#index" + get "projects/:project_id" => "project#index", :as => "project" + get "clients" => "projects#index" + + get "ignorecase/geocode/:postalcode" => "geocode#show", :postalcode => /hx\d\d-\d[a-z]{2}/i + get "extended/geocode/:postalcode" => "geocode#show",:constraints => { + postalcode: /# Postcode format \d{5} #Prefix (-\d{4})? #Suffix /x }, :as => "geocode" - get 'news(.:format)' => "news#index" + get "news(.:format)" => "news#index" + + ActiveSupport::Deprecation.silence do + get "comment/:id(/:action)" => "comments#show" + get "ws/:controller(/:action(/:id))", ws: true + get "account(/:action)" => "account#subscription" + get "pages/:page_id/:controller(/:action(/:id))" + get ":controller/ping", action: "ping" + end + + get "ã“ã‚“ã«ã¡ã¯/世界", controller: "news", action: "index" + + ActiveSupport::Deprecation.silence do + match ":controller(/:action(/:id))(.:format)", via: :all + end - get 'comment/:id(/:action)' => "comments#show" - get 'ws/:controller(/:action(/:id))', :ws => true - get 'account(/:action)' => "account#subscription" - get 'pages/:page_id/:controller(/:action(/:id))' - get ':controller/ping', :action => 'ping' - get 'ã“ã‚“ã«ã¡ã¯/世界', :controller => 'news', :action => 'index' - match ':controller(/:action(/:id))(.:format)', :via => :all - root :to => "news#index" + root to: "news#index" } attr_reader :routes @@ -1869,118 +1966,118 @@ class RackMountIntegrationTests < ActiveSupport::TestCase end def test_recognize_path - assert_equal({:controller => 'admin/users', :action => 'index'}, @routes.recognize_path('/admin/users', :method => :get)) - assert_equal({:controller => 'admin/users', :action => 'create'}, @routes.recognize_path('/admin/users', :method => :post)) - assert_equal({:controller => 'admin/users', :action => 'new'}, @routes.recognize_path('/admin/users/new', :method => :get)) - assert_equal({:controller => 'admin/users', :action => 'show', :id => '1'}, @routes.recognize_path('/admin/users/1', :method => :get)) - assert_equal({:controller => 'admin/users', :action => 'update', :id => '1'}, @routes.recognize_path('/admin/users/1', :method => :put)) - assert_equal({:controller => 'admin/users', :action => 'destroy', :id => '1'}, @routes.recognize_path('/admin/users/1', :method => :delete)) - assert_equal({:controller => 'admin/users', :action => 'edit', :id => '1'}, @routes.recognize_path('/admin/users/1/edit', :method => :get)) - - assert_equal({:controller => 'admin/posts', :action => 'index'}, @routes.recognize_path('/admin/posts', :method => :get)) - assert_equal({:controller => 'admin/posts', :action => 'new'}, @routes.recognize_path('/admin/posts/new', :method => :get)) - - assert_equal({:controller => 'api/users', :action => 'index'}, @routes.recognize_path('/api', :method => :get)) - assert_equal({:controller => 'api/users', :action => 'index'}, @routes.recognize_path('/api/', :method => :get)) - - assert_equal({:controller => 'posts', :action => 'show_date', :year => '2009', :month => nil, :day => nil }, @routes.recognize_path('/blog/2009', :method => :get)) - assert_equal({:controller => 'posts', :action => 'show_date', :year => '2009', :month => '01', :day => nil }, @routes.recognize_path('/blog/2009/01', :method => :get)) - assert_equal({:controller => 'posts', :action => 'show_date', :year => '2009', :month => '01', :day => '01'}, @routes.recognize_path('/blog/2009/01/01', :method => :get)) - - assert_equal({:controller => 'archive', :action => 'index', :year => '2010'}, @routes.recognize_path('/archive/2010')) - assert_equal({:controller => 'archive', :action => 'index'}, @routes.recognize_path('/archive')) - - assert_equal({:controller => 'people', :action => 'index'}, @routes.recognize_path('/people', :method => :get)) - assert_equal({:controller => 'people', :action => 'index', :format => 'xml'}, @routes.recognize_path('/people.xml', :method => :get)) - assert_equal({:controller => 'people', :action => 'create'}, @routes.recognize_path('/people', :method => :post)) - assert_equal({:controller => 'people', :action => 'new'}, @routes.recognize_path('/people/new', :method => :get)) - assert_equal({:controller => 'people', :action => 'show', :id => '1'}, @routes.recognize_path('/people/1', :method => :get)) - assert_equal({:controller => 'people', :action => 'show', :id => '1', :format => 'xml'}, @routes.recognize_path('/people/1.xml', :method => :get)) - assert_equal({:controller => 'people', :action => 'update', :id => '1'}, @routes.recognize_path('/people/1', :method => :put)) - assert_equal({:controller => 'people', :action => 'destroy', :id => '1'}, @routes.recognize_path('/people/1', :method => :delete)) - assert_equal({:controller => 'people', :action => 'edit', :id => '1'}, @routes.recognize_path('/people/1/edit', :method => :get)) - assert_equal({:controller => 'people', :action => 'edit', :id => '1', :format => 'xml'}, @routes.recognize_path('/people/1/edit.xml', :method => :get)) - - assert_equal({:controller => 'symbols', :action => 'show', :name => :as_symbol}, @routes.recognize_path('/symbols')) - assert_equal({:controller => 'foo', :action => 'id_default', :id => '1'}, @routes.recognize_path('/id_default/1')) - assert_equal({:controller => 'foo', :action => 'id_default', :id => '2'}, @routes.recognize_path('/id_default/2')) - assert_equal({:controller => 'foo', :action => 'id_default', :id => 1 }, @routes.recognize_path('/id_default')) - assert_equal({:controller => 'foo', :action => 'get_or_post'}, @routes.recognize_path('/get_or_post', :method => :get)) - assert_equal({:controller => 'foo', :action => 'get_or_post'}, @routes.recognize_path('/get_or_post', :method => :post)) - assert_raise(ActionController::RoutingError) { @routes.recognize_path('/get_or_post', :method => :put) } - assert_raise(ActionController::RoutingError) { @routes.recognize_path('/get_or_post', :method => :delete) } - - assert_equal({:controller => 'posts', :action => 'index', :optional => 'bar'}, @routes.recognize_path('/optional/bar')) - assert_raise(ActionController::RoutingError) { @routes.recognize_path('/optional') } - - assert_equal({:controller => 'posts', :action => 'show', :id => '1', :ws => true}, @routes.recognize_path('/ws/posts/show/1', :method => :get)) - assert_equal({:controller => 'posts', :action => 'list', :ws => true}, @routes.recognize_path('/ws/posts/list', :method => :get)) - assert_equal({:controller => 'posts', :action => 'index', :ws => true}, @routes.recognize_path('/ws/posts', :method => :get)) - - assert_equal({:controller => 'account', :action => 'subscription'}, @routes.recognize_path('/account', :method => :get)) - assert_equal({:controller => 'account', :action => 'subscription'}, @routes.recognize_path('/account/subscription', :method => :get)) - assert_equal({:controller => 'account', :action => 'billing'}, @routes.recognize_path('/account/billing', :method => :get)) - - assert_equal({:page_id => '1', :controller => 'notes', :action => 'index'}, @routes.recognize_path('/pages/1/notes', :method => :get)) - assert_equal({:page_id => '1', :controller => 'notes', :action => 'list'}, @routes.recognize_path('/pages/1/notes/list', :method => :get)) - assert_equal({:page_id => '1', :controller => 'notes', :action => 'show', :id => '2'}, @routes.recognize_path('/pages/1/notes/show/2', :method => :get)) - - assert_equal({:controller => 'posts', :action => 'ping'}, @routes.recognize_path('/posts/ping', :method => :get)) - assert_equal({:controller => 'posts', :action => 'index'}, @routes.recognize_path('/posts', :method => :get)) - assert_equal({:controller => 'posts', :action => 'index'}, @routes.recognize_path('/posts/index', :method => :get)) - assert_equal({:controller => 'posts', :action => 'show'}, @routes.recognize_path('/posts/show', :method => :get)) - assert_equal({:controller => 'posts', :action => 'show', :id => '1'}, @routes.recognize_path('/posts/show/1', :method => :get)) - assert_equal({:controller => 'posts', :action => 'create'}, @routes.recognize_path('/posts/create', :method => :post)) - - assert_equal({:controller => 'geocode', :action => 'show', :postalcode => 'hx12-1az'}, @routes.recognize_path('/ignorecase/geocode/hx12-1az')) - assert_equal({:controller => 'geocode', :action => 'show', :postalcode => 'hx12-1AZ'}, @routes.recognize_path('/ignorecase/geocode/hx12-1AZ')) - assert_equal({:controller => 'geocode', :action => 'show', :postalcode => '12345-1234'}, @routes.recognize_path('/extended/geocode/12345-1234')) - assert_equal({:controller => 'geocode', :action => 'show', :postalcode => '12345'}, @routes.recognize_path('/extended/geocode/12345')) - - assert_equal({:controller => 'news', :action => 'index' }, @routes.recognize_path('/', :method => :get)) - assert_equal({:controller => 'news', :action => 'index', :format => 'rss'}, @routes.recognize_path('/news.rss', :method => :get)) - - assert_raise(ActionController::RoutingError) { @routes.recognize_path('/none', :method => :get) } + assert_equal({ controller: "admin/users", action: "index" }, @routes.recognize_path("/admin/users", method: :get)) + assert_equal({ controller: "admin/users", action: "create" }, @routes.recognize_path("/admin/users", method: :post)) + assert_equal({ controller: "admin/users", action: "new" }, @routes.recognize_path("/admin/users/new", method: :get)) + assert_equal({ controller: "admin/users", action: "show", id: "1" }, @routes.recognize_path("/admin/users/1", method: :get)) + assert_equal({ controller: "admin/users", action: "update", id: "1" }, @routes.recognize_path("/admin/users/1", method: :put)) + assert_equal({ controller: "admin/users", action: "destroy", id: "1" }, @routes.recognize_path("/admin/users/1", method: :delete)) + assert_equal({ controller: "admin/users", action: "edit", id: "1" }, @routes.recognize_path("/admin/users/1/edit", method: :get)) + + assert_equal({ controller: "admin/posts", action: "index" }, @routes.recognize_path("/admin/posts", method: :get)) + assert_equal({ controller: "admin/posts", action: "new" }, @routes.recognize_path("/admin/posts/new", method: :get)) + + assert_equal({ controller: "api/users", action: "index" }, @routes.recognize_path("/api", method: :get)) + assert_equal({ controller: "api/users", action: "index" }, @routes.recognize_path("/api/", method: :get)) + + assert_equal({ controller: "posts", action: "show_date", year: "2009", month: nil, day: nil }, @routes.recognize_path("/blog/2009", method: :get)) + assert_equal({ controller: "posts", action: "show_date", year: "2009", month: "01", day: nil }, @routes.recognize_path("/blog/2009/01", method: :get)) + assert_equal({ controller: "posts", action: "show_date", year: "2009", month: "01", day: "01" }, @routes.recognize_path("/blog/2009/01/01", method: :get)) + + assert_equal({ controller: "archive", action: "index", year: "2010" }, @routes.recognize_path("/archive/2010")) + assert_equal({ controller: "archive", action: "index" }, @routes.recognize_path("/archive")) + + assert_equal({ controller: "people", action: "index" }, @routes.recognize_path("/people", method: :get)) + assert_equal({ controller: "people", action: "index", format: "xml" }, @routes.recognize_path("/people.xml", method: :get)) + assert_equal({ controller: "people", action: "create" }, @routes.recognize_path("/people", method: :post)) + assert_equal({ controller: "people", action: "new" }, @routes.recognize_path("/people/new", method: :get)) + assert_equal({ controller: "people", action: "show", id: "1" }, @routes.recognize_path("/people/1", method: :get)) + assert_equal({ controller: "people", action: "show", id: "1", format: "xml" }, @routes.recognize_path("/people/1.xml", method: :get)) + assert_equal({ controller: "people", action: "update", id: "1" }, @routes.recognize_path("/people/1", method: :put)) + assert_equal({ controller: "people", action: "destroy", id: "1" }, @routes.recognize_path("/people/1", method: :delete)) + assert_equal({ controller: "people", action: "edit", id: "1" }, @routes.recognize_path("/people/1/edit", method: :get)) + assert_equal({ controller: "people", action: "edit", id: "1", format: "xml" }, @routes.recognize_path("/people/1/edit.xml", method: :get)) + + assert_equal({ controller: "symbols", action: "show", name: :as_symbol }, @routes.recognize_path("/symbols")) + assert_equal({ controller: "foo", action: "id_default", id: "1" }, @routes.recognize_path("/id_default/1")) + assert_equal({ controller: "foo", action: "id_default", id: "2" }, @routes.recognize_path("/id_default/2")) + assert_equal({ controller: "foo", action: "id_default", id: 1 }, @routes.recognize_path("/id_default")) + assert_equal({ controller: "foo", action: "get_or_post" }, @routes.recognize_path("/get_or_post", method: :get)) + assert_equal({ controller: "foo", action: "get_or_post" }, @routes.recognize_path("/get_or_post", method: :post)) + assert_raise(ActionController::RoutingError) { @routes.recognize_path("/get_or_post", method: :put) } + assert_raise(ActionController::RoutingError) { @routes.recognize_path("/get_or_post", method: :delete) } + + assert_equal({ controller: "posts", action: "index", optional: "bar" }, @routes.recognize_path("/optional/bar")) + assert_raise(ActionController::RoutingError) { @routes.recognize_path("/optional") } + + assert_equal({ controller: "posts", action: "show", id: "1", ws: true }, @routes.recognize_path("/ws/posts/show/1", method: :get)) + assert_equal({ controller: "posts", action: "list", ws: true }, @routes.recognize_path("/ws/posts/list", method: :get)) + assert_equal({ controller: "posts", action: "index", ws: true }, @routes.recognize_path("/ws/posts", method: :get)) + + assert_equal({ controller: "account", action: "subscription" }, @routes.recognize_path("/account", method: :get)) + assert_equal({ controller: "account", action: "subscription" }, @routes.recognize_path("/account/subscription", method: :get)) + assert_equal({ controller: "account", action: "billing" }, @routes.recognize_path("/account/billing", method: :get)) + + assert_equal({ page_id: "1", controller: "notes", action: "index" }, @routes.recognize_path("/pages/1/notes", method: :get)) + assert_equal({ page_id: "1", controller: "notes", action: "list" }, @routes.recognize_path("/pages/1/notes/list", method: :get)) + assert_equal({ page_id: "1", controller: "notes", action: "show", id: "2" }, @routes.recognize_path("/pages/1/notes/show/2", method: :get)) + + assert_equal({ controller: "posts", action: "ping" }, @routes.recognize_path("/posts/ping", method: :get)) + assert_equal({ controller: "posts", action: "index" }, @routes.recognize_path("/posts", method: :get)) + assert_equal({ controller: "posts", action: "index" }, @routes.recognize_path("/posts/index", method: :get)) + assert_equal({ controller: "posts", action: "show" }, @routes.recognize_path("/posts/show", method: :get)) + assert_equal({ controller: "posts", action: "show", id: "1" }, @routes.recognize_path("/posts/show/1", method: :get)) + assert_equal({ controller: "posts", action: "create" }, @routes.recognize_path("/posts/create", method: :post)) + + assert_equal({ controller: "geocode", action: "show", postalcode: "hx12-1az" }, @routes.recognize_path("/ignorecase/geocode/hx12-1az")) + assert_equal({ controller: "geocode", action: "show", postalcode: "hx12-1AZ" }, @routes.recognize_path("/ignorecase/geocode/hx12-1AZ")) + assert_equal({ controller: "geocode", action: "show", postalcode: "12345-1234" }, @routes.recognize_path("/extended/geocode/12345-1234")) + assert_equal({ controller: "geocode", action: "show", postalcode: "12345" }, @routes.recognize_path("/extended/geocode/12345")) + + assert_equal({ controller: "news", action: "index" }, @routes.recognize_path("/", method: :get)) + assert_equal({ controller: "news", action: "index", format: "rss" }, @routes.recognize_path("/news.rss", method: :get)) + + assert_raise(ActionController::RoutingError) { @routes.recognize_path("/none", method: :get) } end def test_generate_extras - assert_equal ['/people', []], @routes.generate_extras(:controller => 'people') - assert_equal ['/people', [:foo]], @routes.generate_extras(:controller => 'people', :foo => 'bar') - assert_equal ['/people', []], @routes.generate_extras(:controller => 'people', :action => 'index') - assert_equal ['/people', [:foo]], @routes.generate_extras(:controller => 'people', :action => 'index', :foo => 'bar') - assert_equal ['/people/new', []], @routes.generate_extras(:controller => 'people', :action => 'new') - assert_equal ['/people/new', [:foo]], @routes.generate_extras(:controller => 'people', :action => 'new', :foo => 'bar') - assert_equal ['/people/1', []], @routes.generate_extras(:controller => 'people', :action => 'show', :id => '1') - assert_equal ['/people/1', [:bar, :foo]], sort_extras!(@routes.generate_extras(:controller => 'people', :action => 'show', :id => '1', :foo => '2', :bar => '3')) - assert_equal ['/people', [:person]], @routes.generate_extras(:controller => 'people', :action => 'create', :person => { :first_name => 'Josh', :last_name => 'Peek' }) - assert_equal ['/people', [:people]], @routes.generate_extras(:controller => 'people', :action => 'create', :people => ['Josh', 'Dave']) - - assert_equal ['/posts/show/1', []], @routes.generate_extras(:controller => 'posts', :action => 'show', :id => '1') - assert_equal ['/posts/show/1', [:bar, :foo]], sort_extras!(@routes.generate_extras(:controller => 'posts', :action => 'show', :id => '1', :foo => '2', :bar => '3')) - assert_equal ['/posts', []], @routes.generate_extras(:controller => 'posts', :action => 'index') - assert_equal ['/posts', [:foo]], @routes.generate_extras(:controller => 'posts', :action => 'index', :foo => 'bar') + assert_equal ["/people", []], @routes.generate_extras(controller: "people") + assert_equal ["/people", [:foo]], @routes.generate_extras(controller: "people", foo: "bar") + assert_equal ["/people", []], @routes.generate_extras(controller: "people", action: "index") + assert_equal ["/people", [:foo]], @routes.generate_extras(controller: "people", action: "index", foo: "bar") + assert_equal ["/people/new", []], @routes.generate_extras(controller: "people", action: "new") + assert_equal ["/people/new", [:foo]], @routes.generate_extras(controller: "people", action: "new", foo: "bar") + assert_equal ["/people/1", []], @routes.generate_extras(controller: "people", action: "show", id: "1") + assert_equal ["/people/1", [:bar, :foo]], sort_extras!(@routes.generate_extras(controller: "people", action: "show", id: "1", foo: "2", bar: "3")) + assert_equal ["/people", [:person]], @routes.generate_extras(controller: "people", action: "create", person: { first_name: "Josh", last_name: "Peek" }) + assert_equal ["/people", [:people]], @routes.generate_extras(controller: "people", action: "create", people: ["Josh", "Dave"]) + + assert_equal ["/posts/show/1", []], @routes.generate_extras(controller: "posts", action: "show", id: "1") + assert_equal ["/posts/show/1", [:bar, :foo]], sort_extras!(@routes.generate_extras(controller: "posts", action: "show", id: "1", foo: "2", bar: "3")) + assert_equal ["/posts", []], @routes.generate_extras(controller: "posts", action: "index") + assert_equal ["/posts", [:foo]], @routes.generate_extras(controller: "posts", action: "index", foo: "bar") end def test_extras - params = {:controller => 'people'} + params = { controller: "people" } assert_equal [], @routes.extra_keys(params) - assert_equal({:controller => 'people'}, params) + assert_equal({ controller: "people", action: "index" }, params) - params = {:controller => 'people', :foo => 'bar'} + params = { controller: "people", foo: "bar" } assert_equal [:foo], @routes.extra_keys(params) - assert_equal({:controller => 'people', :foo => 'bar'}, params) + assert_equal({ controller: "people", action: "index", foo: "bar" }, params) - params = {:controller => 'people', :action => 'create', :person => { :name => 'Josh'}} + params = { controller: "people", action: "create", person: { name: "Josh" } } assert_equal [:person], @routes.extra_keys(params) - assert_equal({:controller => 'people', :action => 'create', :person => { :name => 'Josh'}}, params) + assert_equal({ controller: "people", action: "create", person: { name: "Josh" } }, params) end def test_unicode_path - assert_equal({:controller => 'news', :action => 'index'}, @routes.recognize_path(URI.parser.escape('ã“ã‚“ã«ã¡ã¯/世界'), :method => :get)) + assert_equal({ controller: "news", action: "index" }, @routes.recognize_path(URI.parser.escape("ã“ã‚“ã«ã¡ã¯/世界"), method: :get)) end def test_downcased_unicode_path - assert_equal({:controller => 'news', :action => 'index'}, @routes.recognize_path(URI.parser.escape('ã“ã‚“ã«ã¡ã¯/世界').downcase, :method => :get)) + assert_equal({ controller: "news", action: "index" }, @routes.recognize_path(URI.parser.escape("ã“ã‚“ã«ã¡ã¯/世界").downcase, method: :get)) end private diff --git a/actionpack/test/controller/runner_test.rb b/actionpack/test/controller/runner_test.rb index 3e9383abb2..3c0c1907f9 100644 --- a/actionpack/test/controller/runner_test.rb +++ b/actionpack/test/controller/runner_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'action_dispatch/testing/integration' +require "abstract_unit" +require "action_dispatch/testing/integration" module ActionDispatch class RunnerTest < ActiveSupport::TestCase diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb index 2820425c31..8dc565ac8d 100644 --- a/actionpack/test/controller/send_file_test.rb +++ b/actionpack/test/controller/send_file_test.rb @@ -1,9 +1,9 @@ -require 'abstract_unit' +require "abstract_unit" module TestFileUtils def file_name() File.basename(__FILE__) end def file_path() File.expand_path(__FILE__) end - def file_data() @data ||= File.open(file_path, 'rb') { |f| f.read } end + def file_data() @data ||= File.open(file_path, "rb") { |f| f.read } end end class SendFileController < ActionController::Base @@ -11,6 +11,8 @@ class SendFileController < ActionController::Base include ActionController::Testing layout "layouts/standard" # to make sure layouts don't interfere + before_action :file, only: :file_from_before_action + attr_writer :options def options @options ||= {} @@ -20,11 +22,15 @@ class SendFileController < ActionController::Base send_file(file_path, options) end + def file_from_before_action + raise "No file sent from before action." + end + def test_send_file_headers_bang options = { - :type => Mime[:png], - :disposition => 'disposition', - :filename => 'filename' + type: Mime[:png], + disposition: "disposition", + filename: "filename" } send_data "foo", options @@ -32,32 +38,32 @@ class SendFileController < ActionController::Base def test_send_file_headers_with_disposition_as_a_symbol options = { - :type => Mime[:png], - :disposition => :disposition, - :filename => 'filename' + type: Mime[:png], + disposition: :disposition, + filename: "filename" } send_data "foo", options end def test_send_file_headers_with_mime_lookup_with_symbol - options = { :type => :png } + options = { type: :png } send_data "foo", options end def test_send_file_headers_with_bad_symbol - options = { :type => :this_type_is_not_registered } + options = { type: :this_type_is_not_registered } send_data "foo", options end def test_send_file_headers_with_nil_content_type - options = { :type => nil } + options = { type: nil } send_data "foo", options end def test_send_file_headers_guess_type_from_extension - options = { :filename => params[:filename] } + options = { filename: params[:filename] } send_data "foo", options end @@ -78,9 +84,9 @@ class SendFileTest < ActionController::TestCase end def test_file_nostream - @controller.options = { :stream => false } + @controller.options = { stream: false } response = nil - assert_nothing_raised { response = process('file') } + assert_nothing_raised { response = process("file") } assert_not_nil response body = response.body assert_kind_of String, body @@ -89,12 +95,12 @@ class SendFileTest < ActionController::TestCase def test_file_stream response = nil - assert_nothing_raised { response = process('file') } + assert_nothing_raised { response = process("file") } assert_not_nil response assert_respond_to response.stream, :each assert_respond_to response.stream, :to_path - require 'stringio' + require "stringio" output = StringIO.new output.binmode output.string.force_encoding(file_data.encoding) @@ -103,16 +109,16 @@ class SendFileTest < ActionController::TestCase end def test_file_url_based_filename - @controller.options = { :url_based_filename => true } + @controller.options = { url_based_filename: true } response = nil - assert_nothing_raised { response = process('file') } + assert_nothing_raised { response = process("file") } assert_not_nil response assert_equal "attachment", response.headers["Content-Disposition"] end def test_data response = nil - assert_nothing_raised { response = process('data') } + assert_nothing_raised { response = process("data") } assert_not_nil response assert_kind_of String, response.body @@ -120,10 +126,10 @@ class SendFileTest < ActionController::TestCase end def test_headers_after_send_shouldnt_include_charset - response = process('data') + response = process("data") assert_equal "application/octet-stream", response.headers["Content-Type"] - response = process('file') + response = process("file") assert_equal "application/octet-stream", response.headers["Content-Type"] end @@ -135,25 +141,24 @@ class SendFileTest < ActionController::TestCase 5.times do get :test_send_file_headers_bang - assert_equal 'image/png', response.content_type - assert_equal 'disposition; filename="filename"', response.get_header('Content-Disposition') - assert_equal 'binary', response.get_header('Content-Transfer-Encoding') - assert_equal 'private', response.get_header('Cache-Control') + assert_equal "image/png", response.content_type + assert_equal 'disposition; filename="filename"', response.get_header("Content-Disposition") + assert_equal "binary", response.get_header("Content-Transfer-Encoding") + assert_equal "private", response.get_header("Cache-Control") end end def test_send_file_headers_with_disposition_as_a_symbol get :test_send_file_headers_with_disposition_as_a_symbol - assert_equal 'disposition; filename="filename"', response.get_header('Content-Disposition') + assert_equal 'disposition; filename="filename"', response.get_header("Content-Disposition") end def test_send_file_headers_with_mime_lookup_with_symbol get __method__ - assert_equal 'image/png', response.content_type + assert_equal "image/png", response.content_type end - def test_send_file_headers_with_bad_symbol error = assert_raise(ArgumentError) { get __method__ } assert_equal "Unknown MIME type this_type_is_not_registered", error.message @@ -166,15 +171,15 @@ class SendFileTest < ActionController::TestCase def test_send_file_headers_guess_type_from_extension { - 'image.png' => 'image/png', - 'image.jpeg' => 'image/jpeg', - 'image.jpg' => 'image/jpeg', - 'image.tif' => 'image/tiff', - 'image.gif' => 'image/gif', - 'movie.mpg' => 'video/mpeg', - 'file.zip' => 'application/zip', - 'file.unk' => 'application/octet-stream', - 'zip' => 'application/octet-stream' + "image.png" => "image/png", + "image.jpeg" => "image/jpeg", + "image.jpg" => "image/jpeg", + "image.tif" => "image/tiff", + "image.gif" => "image/gif", + "movie.mpg" => "video/mpeg", + "file.zip" => "application/zip", + "file.unk" => "application/octet-stream", + "zip" => "application/octet-stream" }.each do |filename,expected_type| get __method__, params: { filename: filename } assert_equal expected_type, response.content_type @@ -182,31 +187,40 @@ class SendFileTest < ActionController::TestCase end def test_send_file_with_default_content_disposition_header - process('data') - assert_equal 'attachment', @controller.headers['Content-Disposition'] + process("data") + assert_equal "attachment", @controller.headers["Content-Disposition"] end def test_send_file_without_content_disposition_header - @controller.options = {:disposition => nil} - process('data') - assert_nil @controller.headers['Content-Disposition'] + @controller.options = { disposition: nil } + process("data") + assert_nil @controller.headers["Content-Disposition"] + end + + def test_send_file_from_before_action + response = nil + assert_nothing_raised { response = process("file_from_before_action") } + assert_not_nil response + + assert_kind_of String, response.body + assert_equal file_data, response.body end %w(file data).each do |method| define_method "test_send_#{method}_status" do - @controller.options = { :stream => false, :status => 500 } + @controller.options = { stream: false, status: 500 } assert_not_nil process(method) assert_equal 500, @response.status end define_method "test_send_#{method}_content_type" do - @controller.options = { :stream => false, :content_type => "application/x-ruby" } + @controller.options = { stream: false, content_type: "application/x-ruby" } assert_nothing_raised { assert_not_nil process(method) } assert_equal "application/x-ruby", @response.content_type end define_method "test_default_send_#{method}_status" do - @controller.options = { :stream => false } + @controller.options = { stream: false } assert_nothing_raised { assert_not_nil process(method) } assert_equal 200, @response.status end @@ -214,9 +228,23 @@ class SendFileTest < ActionController::TestCase def test_send_file_with_action_controller_live @controller = SendFileWithActionControllerLive.new - @controller.options = { :content_type => "application/x-ruby" } + @controller.options = { content_type: "application/x-ruby" } - response = process('file') + response = process("file") assert_equal 200, response.status end + + def test_send_file_charset_with_type_options_key + @controller = SendFileWithActionControllerLive.new + @controller.options = { type: "text/calendar; charset=utf-8" } + response = process("file") + assert_equal "text/calendar; charset=utf-8", 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"] + end end diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb index 786dc15444..38c601ee81 100644 --- a/actionpack/test/controller/show_exceptions_test.rb +++ b/actionpack/test/controller/show_exceptions_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module ShowExceptions class ShowExceptionsController < ActionController::Base @@ -10,11 +10,11 @@ module ShowExceptions end def boom - raise 'boom!' + raise "boom!" end def another_boom - raise 'boom!' + raise "boom!" end def show_detailed_exceptions? @@ -23,26 +23,26 @@ module ShowExceptions end class ShowExceptionsTest < ActionDispatch::IntegrationTest - test 'show error page from a remote ip' do + test "show error page from a remote ip" do @app = ShowExceptionsController.action(:boom) - self.remote_addr = '208.77.188.166' - get '/' + self.remote_addr = "208.77.188.166" + get "/" assert_equal "500 error fixture\n", body end - test 'show diagnostics from a local ip if show_detailed_exceptions? is set to request.local?' do + test "show diagnostics from a local ip if show_detailed_exceptions? is set to request.local?" do @app = ShowExceptionsController.action(:boom) - ['127.0.0.1', '127.0.0.127', '127.12.1.1', '::1', '0:0:0:0:0:0:0:1', '0:0:0:0:0:0:0:1%0'].each do |ip_address| + ["127.0.0.1", "127.0.0.127", "127.12.1.1", "::1", "0:0:0:0:0:0:0:1", "0:0:0:0:0:0:0:1%0"].each do |ip_address| self.remote_addr = ip_address - get '/' + get "/" assert_match(/boom/, body) end end - test 'show diagnostics from a remote ip when env is already set' do + test "show diagnostics from a remote ip when env is already set" do @app = ShowExceptionsController.action(:another_boom) - self.remote_addr = '208.77.188.166' - get '/' + self.remote_addr = "208.77.188.166" + get "/" assert_match(/boom/, body) end end @@ -50,21 +50,21 @@ module ShowExceptions class ShowExceptionsOverriddenController < ShowExceptionsController private - def show_detailed_exceptions? - params['detailed'] == '1' - end + def show_detailed_exceptions? + params["detailed"] == "1" + end end class ShowExceptionsOverriddenTest < ActionDispatch::IntegrationTest - test 'show error page' do + test "show error page" do @app = ShowExceptionsOverriddenController.action(:boom) - get '/', params: { 'detailed' => '0' } + get "/", params: { "detailed" => "0" } assert_equal "500 error fixture\n", body end - test 'show diagnostics message' do + test "show diagnostics message" do @app = ShowExceptionsOverriddenController.action(:boom) - get '/', params: { 'detailed' => '1' } + get "/", params: { "detailed" => "1" } assert_match(/boom/, body) end end @@ -72,25 +72,25 @@ module ShowExceptions class ShowExceptionsFormatsTest < ActionDispatch::IntegrationTest def test_render_json_exception @app = ShowExceptionsOverriddenController.action(:boom) - get "/", headers: { 'HTTP_ACCEPT' => 'application/json' } + get "/", headers: { "HTTP_ACCEPT" => "application/json" } assert_response :internal_server_error - assert_equal 'application/json', response.content_type.to_s - assert_equal({ :status => 500, :error => 'Internal Server Error' }.to_json, response.body) + assert_equal "application/json", response.content_type.to_s + assert_equal({ status: 500, error: "Internal Server Error" }.to_json, response.body) end def test_render_xml_exception @app = ShowExceptionsOverriddenController.action(:boom) - get "/", headers: { 'HTTP_ACCEPT' => 'application/xml' } + get "/", headers: { "HTTP_ACCEPT" => "application/xml" } assert_response :internal_server_error - assert_equal 'application/xml', response.content_type.to_s - assert_equal({ :status => 500, :error => 'Internal Server Error' }.to_xml, response.body) + assert_equal "application/xml", response.content_type.to_s + assert_equal({ status: 500, error: "Internal Server Error" }.to_xml, response.body) end def test_render_fallback_exception @app = ShowExceptionsOverriddenController.action(:boom) - get "/", headers: { 'HTTP_ACCEPT' => 'text/csv' } + get "/", headers: { "HTTP_ACCEPT" => "text/csv" } assert_response :internal_server_error - assert_equal 'text/html', response.content_type.to_s + assert_equal "text/html", response.content_type.to_s end end @@ -101,9 +101,9 @@ module ShowExceptions @app.instance_variable_set(:@exceptions_app, nil) $stderr = StringIO.new - get '/', headers: { 'HTTP_ACCEPT' => 'text/json' } + get "/", headers: { "HTTP_ACCEPT" => "text/json" } assert_response :internal_server_error - assert_equal 'text/plain', response.content_type.to_s + assert_equal "text/plain", response.content_type.to_s ensure @app.instance_variable_set(:@exceptions_app, @exceptions_app) $stderr = STDERR diff --git a/actionpack/test/controller/streaming_test.rb b/actionpack/test/controller/streaming_test.rb index 6ee6444065..d685467cad 100644 --- a/actionpack/test/controller/streaming_test.rb +++ b/actionpack/test/controller/streaming_test.rb @@ -1,10 +1,10 @@ -require 'abstract_unit' +require "abstract_unit" module ActionController class StreamingResponseTest < ActionController::TestCase class TestController < ActionController::Base def self.controller_path - 'test' + "test" end def basic_stream diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index b9caddcdb7..33f11e67ef 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -1,40 +1,40 @@ -require 'abstract_unit' -require 'controller/fake_controllers' -require 'active_support/json/decoding' -require 'rails/engine' +require "abstract_unit" +require "controller/fake_controllers" +require "active_support/json/decoding" +require "rails/engine" class TestCaseTest < ActionController::TestCase def self.fixture_path; end; class TestController < ActionController::Base def no_op - render plain: 'dummy' + render plain: "dummy" end def set_flash flash["test"] = ">#{flash["test"]}<" - render plain: 'ignore me' + render plain: "ignore me" end def delete_flash flash.delete("test") - render plain: 'ignore me' + render plain: "ignore me" end def set_flash_now flash.now["test_now"] = ">#{flash["test_now"]}<" - render plain: 'ignore me' + render plain: "ignore me" end def set_session - session['string'] = 'A wonder' - session[:symbol] = 'it works' - render plain: 'Success' + session["string"] = "A wonder" + session[:symbol] = "it works" + render plain: "Success" end def reset_the_session reset_session - render plain: 'ignore me' + render plain: "ignore me" end def render_raw_post @@ -126,20 +126,24 @@ XML end def redirect_to_same_controller - redirect_to controller: 'test', action: 'test_uri', id: 5 + redirect_to controller: "test", action: "test_uri", id: 5 end def redirect_to_different_controller - redirect_to controller: 'fail', id: 5 + redirect_to controller: "fail", id: 5 end def create - head :created, location: 'created resource' + head :created, location: "created resource" + end + + def render_cookie + render plain: cookies["foo"] end def delete_cookie cookies.delete("foo") - render plain: 'ok' + render plain: "ok" end def test_without_body @@ -150,6 +154,10 @@ XML render html: '<body class="foo"></body>'.html_safe end + def boom + raise "boom!" + end + private def generate_url(opts) @@ -160,16 +168,18 @@ XML def setup super @controller = TestController.new - @request.delete_header 'PATH_INFO' + @request.delete_header "PATH_INFO" @routes = ActionDispatch::Routing::RouteSet.new.tap do |r| r.draw do - get ':controller(/:action(/:id))' + ActiveSupport::Deprecation.silence do + get ":controller(/:action(/:id))" + end end end end class DefaultUrlOptionsCachingController < ActionController::Base - before_action { @dynamic_opt = 'opt' } + before_action { @dynamic_opt = "opt" } def test_url_options_reset render plain: url_for @@ -187,49 +197,41 @@ XML def test_assert_select_without_body get :test_without_body - assert_select 'body', 0 - assert_select 'div.foo' + assert_select "body", 0 + assert_select "div.foo" end def test_assert_select_with_body get :test_with_body - assert_select 'body.foo' + assert_select "body.foo" end def test_url_options_reset @controller = DefaultUrlOptionsCachingController.new get :test_url_options_reset - assert_nil @request.params['dynamic_opt'] + assert_nil @request.params["dynamic_opt"] assert_match(/dynamic_opt=opt/, @response.body) end def test_raw_post_handling - params = Hash[:page, { name: 'page name' }, 'some key', 123] + params = Hash[:page, { name: "page name" }, "some key", 123] post :render_raw_post, params: params.dup assert_equal params.to_query, @response.body end def test_body_stream - params = Hash[:page, { name: 'page name' }, 'some key', 123] + params = Hash[:page, { name: "page name" }, "some key", 123] post :render_body, params: params.dup assert_equal params.to_query, @response.body end - def test_deprecated_body_stream - params = Hash[:page, { name: 'page name' }, 'some key', 123] - - assert_deprecated { post :render_body, params.dup } - - assert_equal params.to_query, @response.body - end - def test_document_body_and_params_with_post post :test_params, params: { id: 1 } - assert_equal({"id"=>"1", "controller"=>"test_case_test/test", "action"=>"test_params"}, ::JSON.parse(@response.body)) + assert_equal({ "id"=>"1", "controller"=>"test_case_test/test", "action"=>"test_params" }, ::JSON.parse(@response.body)) end def test_document_body_with_post @@ -237,21 +239,11 @@ XML assert_equal "document body", @response.body end - def test_deprecated_document_body_with_post - assert_deprecated { post :render_body, "document body" } - assert_equal "document body", @response.body - end - def test_document_body_with_put put :render_body, body: "document body" assert_equal "document body", @response.body end - def test_deprecated_document_body_with_put - assert_deprecated { put :render_body, "document body" } - assert_equal "document body", @response.body - end - def test_head head :test_params assert_equal 200, @response.status @@ -259,31 +251,21 @@ XML def test_process_without_flash process :set_flash - assert_equal '><', flash['test'] - end - - def test_deprecated_process_with_flash - assert_deprecated { process :set_flash, "GET", nil, nil, { "test" => "value" } } - assert_equal '>value<', flash['test'] + assert_equal "><", flash["test"] end def test_process_with_flash process :set_flash, method: "GET", flash: { "test" => "value" } - assert_equal '>value<', flash['test'] - end - - def test_deprecated_process_with_flash_now - assert_deprecated { process :set_flash_now, "GET", nil, nil, { "test_now" => "value_now" } } - assert_equal '>value_now<', flash['test_now'] + assert_equal ">value<", flash["test"] end def test_process_with_flash_now process :set_flash_now, method: "GET", flash: { "test_now" => "value_now" } - assert_equal '>value_now<', flash['test_now'] + assert_equal ">value_now<", flash["test_now"] end def test_process_delete_flash @@ -295,64 +277,38 @@ XML def test_process_with_session process :set_session - assert_equal 'A wonder', session['string'], "A value stored in the session should be available by string key" - assert_equal 'A wonder', session[:string], "Test session hash should allow indifferent access" - assert_equal 'it works', session['symbol'], "Test session hash should allow indifferent access" - assert_equal 'it works', session[:symbol], "Test session hash should allow indifferent access" - end - - def test_process_with_session_arg - assert_deprecated { process :no_op, "GET", nil, { 'string' => 'value1', symbol: 'value2' } } - assert_equal 'value1', session['string'] - assert_equal 'value1', session[:string] - assert_equal 'value2', session['symbol'] - assert_equal 'value2', session[:symbol] + assert_equal "A wonder", session["string"], "A value stored in the session should be available by string key" + assert_equal "A wonder", session[:string], "Test session hash should allow indifferent access" + assert_equal "it works", session["symbol"], "Test session hash should allow indifferent access" + assert_equal "it works", session[:symbol], "Test session hash should allow indifferent access" end def test_process_with_session_kwarg - process :no_op, method: "GET", session: { 'string' => 'value1', symbol: 'value2' } - assert_equal 'value1', session['string'] - assert_equal 'value1', session[:string] - assert_equal 'value2', session['symbol'] - assert_equal 'value2', session[:symbol] - end - - def test_deprecated_process_merges_session_arg - session[:foo] = 'bar' - assert_deprecated { - get :no_op, nil, { bar: 'baz' } - } - assert_equal 'bar', session[:foo] - assert_equal 'baz', session[:bar] + process :no_op, method: "GET", session: { "string" => "value1", symbol: "value2" } + assert_equal "value1", session["string"] + assert_equal "value1", session[:string] + assert_equal "value2", session["symbol"] + assert_equal "value2", session[:symbol] end def test_process_merges_session_arg - session[:foo] = 'bar' - get :no_op, session: { bar: 'baz' } - assert_equal 'bar', session[:foo] - assert_equal 'baz', session[:bar] - end - - def test_deprecated_merged_session_arg_is_retained_across_requests - assert_deprecated { - get :no_op, nil, { foo: 'bar' } - } - assert_equal 'bar', session[:foo] - get :no_op - assert_equal 'bar', session[:foo] + session[:foo] = "bar" + get :no_op, session: { bar: "baz" } + assert_equal "bar", session[:foo] + assert_equal "baz", session[:bar] end def test_merged_session_arg_is_retained_across_requests - get :no_op, session: { foo: 'bar' } - assert_equal 'bar', session[:foo] + get :no_op, session: { foo: "bar" } + assert_equal "bar", session[:foo] get :no_op - assert_equal 'bar', session[:foo] + assert_equal "bar", session[:foo] end def test_process_overwrites_existing_session_arg - session[:foo] = 'bar' - get :no_op, session: { foo: 'baz' } - assert_equal 'baz', session[:foo] + session[:foo] = "bar" + get :no_op, session: { foo: "baz" } + assert_equal "baz", session[:foo] end def test_session_is_cleared_from_controller_after_reset_session @@ -383,11 +339,6 @@ XML assert_equal "/test_case_test/test/test_uri", @response.body end - def test_deprecated_process_with_request_uri_with_params - assert_deprecated { process :test_uri, "GET", id: 7 } - assert_equal "/test_case_test/test/test_uri/7", @response.body - end - def test_process_with_request_uri_with_params process :test_uri, method: "GET", @@ -396,14 +347,8 @@ XML assert_equal "/test_case_test/test/test_uri/7", @response.body end - def test_deprecated_process_with_request_uri_with_params_with_explicit_uri - @request.env['PATH_INFO'] = "/explicit/uri" - assert_deprecated { process :test_uri, "GET", id: 7 } - assert_equal "/explicit/uri", @response.body - end - def test_process_with_request_uri_with_params_with_explicit_uri - @request.env['PATH_INFO'] = "/explicit/uri" + @request.env["PATH_INFO"] = "/explicit/uri" process :test_uri, method: "GET", params: { id: 7 } assert_equal "/explicit/uri", @response.body end @@ -411,13 +356,13 @@ XML def test_process_with_query_string process :test_query_string, method: "GET", - params: { q: 'test' } + params: { q: "test" } assert_equal "q=test", @response.body end def test_process_with_query_string_with_explicit_uri - @request.env['PATH_INFO'] = '/explicit/uri' - @request.env['QUERY_STRING'] = 'q=test?extra=question' + @request.env["PATH_INFO"] = "/explicit/uri" + @request.env["QUERY_STRING"] = "q=test?extra=question" process :test_query_string assert_equal "q=test?extra=question", @response.body end @@ -434,7 +379,7 @@ XML begin $stderr = StringIO.new - assert_select 'area' #This will cause a warning if content is processed as HTML + assert_select "area" #This will cause a warning if content is processed as HTML $stderr.rewind && err = $stderr.read ensure $stderr = STDERR @@ -444,21 +389,21 @@ XML end def test_assert_generates - assert_generates 'controller/action/5', controller: 'controller', action: 'action', id: '5' - assert_generates 'controller/action/7', { id: "7" }, { controller: "controller", action: "action" } - assert_generates 'controller/action/5', { controller: "controller", action: "action", id: "5", name: "bob" }, {}, { name: "bob" } - assert_generates 'controller/action/7', { id: "7", name: "bob" }, { controller: "controller", action: "action" }, { name: "bob" } - assert_generates 'controller/action/7', { id: "7" }, { controller: "controller", action: "action", name: "bob" }, {} + assert_generates "controller/action/5", controller: "controller", action: "action", id: "5" + assert_generates "controller/action/7", { id: "7" }, controller: "controller", action: "action" + assert_generates "controller/action/5", { controller: "controller", action: "action", id: "5", name: "bob" }, {}, name: "bob" + assert_generates "controller/action/7", { id: "7", name: "bob" }, { controller: "controller", action: "action" }, name: "bob" + assert_generates "controller/action/7", { id: "7" }, { controller: "controller", action: "action", name: "bob" }, {} end def test_assert_routing - assert_routing 'content', controller: 'content', action: 'index' + assert_routing "content", controller: "content", action: "index" end def test_assert_routing_with_method with_routing do |set| set.draw { resources(:content) } - assert_routing({ method: 'post', path: 'content' }, { controller: 'content', action: 'create' }) + assert_routing({ method: "post", path: "content" }, controller: "content", action: "create") end end @@ -466,125 +411,101 @@ XML with_routing do |set| set.draw do namespace :admin do - get 'user' => 'user#index' + get "user" => "user#index" end end - assert_routing 'admin/user', controller: 'admin/user', action: 'index' + assert_routing "admin/user", controller: "admin/user", action: "index" end end def test_assert_routing_with_glob with_routing do |set| - set.draw { get('*path' => "pages#show") } - assert_routing('/company/about', { controller: 'pages', action: 'show', path: 'company/about' }) + set.draw { get("*path" => "pages#show") } + assert_routing("/company/about", controller: "pages", action: "show", path: "company/about") end end - def test_deprecated_params_passing - assert_deprecated { - get :test_params, page: { name: "Page name", month: '4', year: '2004', day: '6' } - } - parsed_params = ::JSON.parse(@response.body) - assert_equal( - { - 'controller' => 'test_case_test/test', 'action' => 'test_params', - 'page' => { 'name' => "Page name", 'month' => '4', 'year' => '2004', 'day' => '6' } - }, - parsed_params - ) - end - def test_params_passing get :test_params, params: { page: { name: "Page name", - month: '4', - year: '2004', - day: '6' + month: "4", + year: "2004", + day: "6" } } parsed_params = ::JSON.parse(@response.body) assert_equal( { - 'controller' => 'test_case_test/test', 'action' => 'test_params', - 'page' => { 'name' => "Page name", 'month' => '4', 'year' => '2004', 'day' => '6' } + "controller" => "test_case_test/test", "action" => "test_params", + "page" => { "name" => "Page name", "month" => "4", "year" => "2004", "day" => "6" } }, parsed_params ) end def test_query_param_named_action - get :test_query_parameters, params: {action: 'foobar'} + get :test_query_parameters, params: { action: "foobar" } parsed_params = JSON.parse(@response.body) - assert_equal({'action' => 'foobar'}, parsed_params) + assert_equal({ "action" => "foobar" }, parsed_params) end def test_request_param_named_action - post :test_request_parameters, params: {action: 'foobar'} + post :test_request_parameters, params: { action: "foobar" } parsed_params = eval(@response.body) - assert_equal({'action' => 'foobar'}, parsed_params) + assert_equal({ "action" => "foobar" }, parsed_params) end def test_kwarg_params_passing_with_session_and_flash get :test_params, params: { page: { name: "Page name", - month: '4', - year: '2004', - day: '6' + month: "4", + year: "2004", + day: "6" } - }, session: { 'foo' => 'bar' }, flash: { notice: 'created' } + }, session: { "foo" => "bar" }, flash: { notice: "created" } parsed_params = ::JSON.parse(@response.body) assert_equal( - {'controller' => 'test_case_test/test', 'action' => 'test_params', - 'page' => {'name' => "Page name", 'month' => '4', 'year' => '2004', 'day' => '6'}}, + { "controller" => "test_case_test/test", "action" => "test_params", + "page" => { "name" => "Page name", "month" => "4", "year" => "2004", "day" => "6" } }, parsed_params ) - assert_equal 'bar', session[:foo] - assert_equal 'created', flash[:notice] + assert_equal "bar", session[:foo] + assert_equal "created", flash[:notice] end - def test_params_passing_with_fixnums + def test_params_passing_with_integer get :test_params, params: { page: { name: "Page name", month: 4, year: 2004, day: 6 } } parsed_params = ::JSON.parse(@response.body) assert_equal( - {'controller' => 'test_case_test/test', 'action' => 'test_params', - 'page' => {'name' => "Page name", 'month' => '4', 'year' => '2004', 'day' => '6'}}, + { "controller" => "test_case_test/test", "action" => "test_params", + "page" => { "name" => "Page name", "month" => "4", "year" => "2004", "day" => "6" } }, parsed_params ) end - def test_params_passing_with_fixnums_when_not_html_request - get :test_params, params: { format: 'json', count: 999 } + def test_params_passing_with_integers_when_not_html_request + get :test_params, params: { format: "json", count: 999 } parsed_params = ::JSON.parse(@response.body) assert_equal( - {'controller' => 'test_case_test/test', 'action' => 'test_params', - 'format' => 'json', 'count' => '999' }, + { "controller" => "test_case_test/test", "action" => "test_params", + "format" => "json", "count" => "999" }, parsed_params ) end def test_params_passing_path_parameter_is_string_when_not_html_request - get :test_params, params: { format: 'json', id: 1 } - parsed_params = ::JSON.parse(@response.body) - assert_equal( - {'controller' => 'test_case_test/test', 'action' => 'test_params', - 'format' => 'json', 'id' => '1' }, - parsed_params - ) - end - - def test_deprecated_params_passing_path_parameter_is_string_when_not_html_request - assert_deprecated { get :test_params, format: 'json', id: 1 } + get :test_params, params: { format: "json", id: 1 } parsed_params = ::JSON.parse(@response.body) assert_equal( - {'controller' => 'test_case_test/test', 'action' => 'test_params', - 'format' => 'json', 'id' => '1' }, + { "controller" => "test_case_test/test", "action" => "test_params", + "format" => "json", "id" => "1" }, parsed_params ) end @@ -592,13 +513,13 @@ XML def test_params_passing_with_frozen_values assert_nothing_raised do get :test_params, params: { - frozen: 'icy'.freeze, frozens: ['icy'.freeze].freeze, deepfreeze: { frozen: 'icy'.freeze }.freeze + frozen: "icy".freeze, frozens: ["icy".freeze].freeze, deepfreeze: { frozen: "icy".freeze }.freeze } end parsed_params = ::JSON.parse(@response.body) assert_equal( - {'controller' => 'test_case_test/test', 'action' => 'test_params', - 'frozen' => 'icy', 'frozens' => ['icy'], 'deepfreeze' => { 'frozen' => 'icy' }}, + { "controller" => "test_case_test/test", "action" => "test_params", + "frozen" => "icy", "frozens" => ["icy"], "deepfreeze" => { "frozen" => "icy" } }, parsed_params ) end @@ -610,8 +531,8 @@ XML end test "set additional HTTP headers" do - @request.headers['Referer'] = "http://nohost.com/home" - @request.headers['Content-Type'] = "application/rss+xml" + @request.headers["Referer"] = "http://nohost.com/home" + @request.headers["Content-Type"] = "application/rss+xml" get :test_headers parsed_env = ActiveSupport::JSON.decode(@response.body) assert_equal "http://nohost.com/home", parsed_env["HTTP_REFERER"] @@ -619,36 +540,50 @@ XML end test "set additional env variables" do - @request.headers['HTTP_REFERER'] = "http://example.com/about" - @request.headers['CONTENT_TYPE'] = "application/json" + @request.headers["HTTP_REFERER"] = "http://example.com/about" + @request.headers["CONTENT_TYPE"] = "application/json" get :test_headers parsed_env = ActiveSupport::JSON.decode(@response.body) assert_equal "http://example.com/about", parsed_env["HTTP_REFERER"] assert_equal "application/json", parsed_env["CONTENT_TYPE"] end + def test_using_as_json_sets_request_content_type_to_json + post :render_body, params: { bool_value: true, str_value: "string", num_value: 2 }, as: :json + + assert_equal "application/json", @request.headers["CONTENT_TYPE"] + assert_equal true, @request.request_parameters[:bool_value] + assert_equal "string", @request.request_parameters[:str_value] + assert_equal 2, @request.request_parameters[:num_value] + end + + def test_using_as_json_sets_format_json + post :render_body, params: { bool_value: true, str_value: "string", num_value: 2 }, as: :json + assert_equal "json", @request.format + end + def test_mutating_content_type_headers_for_plain_text_files_sets_the_header - @request.headers['Content-Type'] = 'text/plain' - post :render_body, params: { name: 'foo.txt' } + @request.headers["Content-Type"] = "text/plain" + post :render_body, params: { name: "foo.txt" } - assert_equal 'text/plain', @request.headers['Content-type'] - assert_equal 'foo.txt', @request.request_parameters[:name] - assert_equal 'render_body', @request.path_parameters[:action] + assert_equal "text/plain", @request.headers["Content-type"] + assert_equal "foo.txt", @request.request_parameters[:name] + assert_equal "render_body", @request.path_parameters[:action] end def test_mutating_content_type_headers_for_html_files_sets_the_header - @request.headers['Content-Type'] = 'text/html' - post :render_body, params: { name: 'foo.html' } + @request.headers["Content-Type"] = "text/html" + post :render_body, params: { name: "foo.html" } - assert_equal 'text/html', @request.headers['Content-type'] - assert_equal 'foo.html', @request.request_parameters[:name] - assert_equal 'render_body', @request.path_parameters[:action] + assert_equal "text/html", @request.headers["Content-type"] + assert_equal "foo.html", @request.request_parameters[:name] + assert_equal "render_body", @request.path_parameters[:action] end def test_mutating_content_type_headers_for_non_registered_mime_type_raises_an_error assert_raises(RuntimeError) do - @request.headers['Content-Type'] = 'type/fake' - post :render_body, params: { name: 'foo.fake' } + @request.headers["Content-Type"] = "type/fake" + post :render_body, params: { name: "foo.fake" } end end @@ -659,21 +594,19 @@ XML assert_kind_of String, @request.path_parameters[:id] end - def test_deprecared_id_converted_to_string - assert_deprecated { get :test_params, id: 20, foo: Object.new} - assert_kind_of String, @request.path_parameters[:id] - end - def test_array_path_parameter_handled_properly with_routing do |set| set.draw do - get 'file/*path', to: 'test_case_test/test#test_params' - get ':controller/:action' + get "file/*path", to: "test_case_test/test#test_params" + + ActiveSupport::Deprecation.silence do + get ":controller/:action" + end end - get :test_params, params: { path: ['hello', 'world'] } - assert_equal ['hello', 'world'], @request.path_parameters[:path] - assert_equal 'hello/world', @request.path_parameters[:path].to_param + get :test_params, params: { path: ["hello", "world"] } + assert_equal ["hello", "world"], @request.path_parameters[:path] + assert_equal "hello/world", @request.path_parameters[:path].to_param end end @@ -691,8 +624,8 @@ XML routes_id = @routes.object_id begin - with_routing { raise 'fail' } - fail 'Should not be here.' + with_routing { raise "fail" } + fail "Should not be here." rescue RuntimeError end @@ -711,46 +644,23 @@ XML def test_header_properly_reset_after_remote_http_request get :test_params, xhr: true - assert_nil @request.env['HTTP_X_REQUESTED_WITH'] - assert_nil @request.env['HTTP_ACCEPT'] - end - - def test_deprecated_xhr_with_params - assert_deprecated { xhr :get, :test_params, params: { id: 1 } } - - assert_equal({"id"=>"1", "controller"=>"test_case_test/test", "action"=>"test_params"}, ::JSON.parse(@response.body)) + assert_nil @request.env["HTTP_X_REQUESTED_WITH"] + assert_nil @request.env["HTTP_ACCEPT"] end def test_xhr_with_params get :test_params, params: { id: 1 }, xhr: true - assert_equal({"id"=>"1", "controller"=>"test_case_test/test", "action"=>"test_params"}, ::JSON.parse(@response.body)) + assert_equal({ "id"=>"1", "controller"=>"test_case_test/test", "action"=>"test_params" }, ::JSON.parse(@response.body)) end def test_xhr_with_session get :set_session, xhr: true - assert_equal 'A wonder', session['string'], "A value stored in the session should be available by string key" - assert_equal 'A wonder', session[:string], "Test session hash should allow indifferent access" - assert_equal 'it works', session['symbol'], "Test session hash should allow indifferent access" - assert_equal 'it works', session[:symbol], "Test session hash should allow indifferent access" - end - - def test_deprecated_xhr_with_session - assert_deprecated { xhr :get, :set_session } - - assert_equal 'A wonder', session['string'], "A value stored in the session should be available by string key" - assert_equal 'A wonder', session[:string], "Test session hash should allow indifferent access" - assert_equal 'it works', session['symbol'], "Test session hash should allow indifferent access" - assert_equal 'it works', session[:symbol], "Test session hash should allow indifferent access" - end - - def test_deprecated_params_reset_between_post_requests - assert_deprecated { post :no_op, foo: "bar" } - assert_equal "bar", @request.params[:foo] - - post :no_op - assert @request.params[:foo].blank? + assert_equal "A wonder", session["string"], "A value stored in the session should be available by string key" + assert_equal "A wonder", session[:string], "Test session hash should allow indifferent access" + assert_equal "it works", session["symbol"], "Test session hash should allow indifferent access" + assert_equal "it works", session[:symbol], "Test session hash should allow indifferent access" end def test_params_reset_between_post_requests @@ -791,48 +701,62 @@ XML end def test_request_format - get :test_format, params: { format: 'html' } - assert_equal 'text/html', @response.body + get :test_format, params: { format: "html" } + assert_equal "text/html", @response.body - get :test_format, params: { format: 'json' } - assert_equal 'application/json', @response.body + get :test_format, params: { format: "json" } + assert_equal "application/json", @response.body - get :test_format, params: { format: 'xml' } - assert_equal 'application/xml', @response.body + get :test_format, params: { format: "xml" } + assert_equal "application/xml", @response.body get :test_format - assert_equal 'text/html', @response.body + assert_equal "text/html", @response.body end def test_request_format_kwarg - get :test_format, format: 'html' - assert_equal 'text/html', @response.body + get :test_format, format: "html" + assert_equal "text/html", @response.body - get :test_format, format: 'json' - assert_equal 'application/json', @response.body + get :test_format, format: "json" + assert_equal "application/json", @response.body - get :test_format, format: 'xml' - assert_equal 'application/xml', @response.body + get :test_format, format: "xml" + assert_equal "application/xml", @response.body get :test_format - assert_equal 'text/html', @response.body + assert_equal "text/html", @response.body end def test_request_format_kwarg_overrides_params - get :test_format, format: 'json', params: { format: 'html' } - assert_equal 'application/json', @response.body + get :test_format, format: "json", params: { format: "html" } + assert_equal "application/json", @response.body end def test_should_have_knowledge_of_client_side_cookie_state_even_if_they_are_not_set - cookies['foo'] = 'bar' + cookies["foo"] = "bar" get :no_op - assert_equal 'bar', cookies['foo'] + assert_equal "bar", cookies["foo"] + end + + def test_cookies_should_be_escaped_properly + cookies["foo"] = "+" + get :render_cookie + assert_equal "+", @response.body end def test_should_detect_if_cookie_is_deleted - cookies['foo'] = 'bar' + cookies["foo"] = "bar" get :delete_cookie - assert_nil cookies['foo'] + assert_nil cookies["foo"] + end + + def test_multiple_mixed_method_process_should_scrub_rack_input + post :test_params, params: { id: 1, foo: "an foo" } + assert_equal({ "id"=>"1", "foo" => "an foo", "controller"=>"test_case_test/test", "action"=>"test_params" }, ::JSON.parse(@response.body)) + + get :test_params, params: { bar: "an bar" } + assert_equal({ "bar"=>"an bar", "controller"=>"test_case_test/test", "action"=>"test_params" }, ::JSON.parse(@response.body)) end %w(controller response request).each do |variable| @@ -851,15 +775,15 @@ XML end end - FILES_DIR = File.dirname(__FILE__) + '/../fixtures/multipart' + FILES_DIR = File.dirname(__FILE__) + "/../fixtures/multipart" - READ_BINARY = 'rb:binary' - READ_PLAIN = 'r:binary' + READ_BINARY = "rb:binary" + READ_PLAIN = "r:binary" def test_test_uploaded_file - filename = 'mona_lisa.jpg' + filename = "ruby_on_rails.jpg" path = "#{FILES_DIR}/#{filename}" - content_type = 'image/png' + content_type = "image/png" expected = File.read(path) expected.force_encoding(Encoding::BINARY) @@ -877,15 +801,15 @@ XML def test_fixture_path_is_accessed_from_self_instead_of_active_support_test_case TestCaseTest.stub :fixture_path, FILES_DIR do - uploaded_file = fixture_file_upload('/mona_lisa.jpg', 'image/png') - assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read + uploaded_file = fixture_file_upload("/ruby_on_rails.jpg", "image/png") + assert_equal File.open("#{FILES_DIR}/ruby_on_rails.jpg", READ_PLAIN).read, uploaded_file.read end end def test_test_uploaded_file_with_binary - filename = 'mona_lisa.jpg' + filename = "ruby_on_rails.jpg" path = "#{FILES_DIR}/#{filename}" - content_type = 'image/png' + content_type = "image/png" binary_uploaded_file = Rack::Test::UploadedFile.new(path, content_type, :binary) assert_equal File.open(path, READ_BINARY).read, binary_uploaded_file.read @@ -895,9 +819,9 @@ XML end def test_fixture_file_upload_with_binary - filename = 'mona_lisa.jpg' + filename = "ruby_on_rails.jpg" path = "#{FILES_DIR}/#{filename}" - content_type = 'image/jpg' + content_type = "image/jpg" binary_file_upload = fixture_file_upload(path, content_type, :binary) assert_equal File.open(path, READ_BINARY).read, binary_file_upload.read @@ -907,50 +831,48 @@ XML end def test_fixture_file_upload_should_be_able_access_to_tempfile - file = fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg") + file = fixture_file_upload(FILES_DIR + "/ruby_on_rails.jpg", "image/jpg") assert file.respond_to?(:tempfile), "expected tempfile should respond on fixture file object, got nothing" end def test_fixture_file_upload post :test_file_upload, params: { - file: fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg") + file: fixture_file_upload(FILES_DIR + "/ruby_on_rails.jpg", "image/jpg") } - assert_equal '159528', @response.body + assert_equal "45142", @response.body end def test_fixture_file_upload_relative_to_fixture_path TestCaseTest.stub :fixture_path, FILES_DIR do - uploaded_file = fixture_file_upload("mona_lisa.jpg", "image/jpg") - assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read + uploaded_file = fixture_file_upload("ruby_on_rails.jpg", "image/jpg") + assert_equal File.open("#{FILES_DIR}/ruby_on_rails.jpg", READ_PLAIN).read, uploaded_file.read end end - def test_fixture_file_upload_ignores_nil_fixture_path - uploaded_file = fixture_file_upload("#{FILES_DIR}/mona_lisa.jpg", "image/jpg") - assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read + def test_fixture_file_upload_ignores_fixture_path_given_full_path + TestCaseTest.stub :fixture_path, File.dirname(__FILE__) do + uploaded_file = fixture_file_upload("#{FILES_DIR}/ruby_on_rails.jpg", "image/jpg") + assert_equal File.open("#{FILES_DIR}/ruby_on_rails.jpg", READ_PLAIN).read, uploaded_file.read + end end - def test_deprecated_action_dispatch_uploaded_file_upload - filename = 'mona_lisa.jpg' - path = "#{FILES_DIR}/#{filename}" - assert_deprecated { - post :test_file_upload, file: Rack::Test::UploadedFile.new(path, "image/jpg", true) - } - assert_equal '159528', @response.body + def test_fixture_file_upload_ignores_nil_fixture_path + uploaded_file = fixture_file_upload("#{FILES_DIR}/ruby_on_rails.jpg", "image/jpg") + assert_equal File.open("#{FILES_DIR}/ruby_on_rails.jpg", READ_PLAIN).read, uploaded_file.read end def test_action_dispatch_uploaded_file_upload - filename = 'mona_lisa.jpg' + filename = "ruby_on_rails.jpg" path = "#{FILES_DIR}/#{filename}" post :test_file_upload, params: { file: Rack::Test::UploadedFile.new(path, "image/jpg", true) } - assert_equal '159528', @response.body + assert_equal "45142", @response.body end def test_test_uploaded_file_exception_when_file_doesnt_exist - assert_raise(RuntimeError) { Rack::Test::UploadedFile.new('non_existent_file') } + assert_raise(RuntimeError) { Rack::Test::UploadedFile.new("non_existent_file") } end def test_redirect_url_only_cares_about_location_header @@ -958,21 +880,41 @@ XML assert_response :created # Redirect url doesn't care that it wasn't a :redirect response. - assert_equal 'created resource', @response.redirect_url + assert_equal "created resource", @response.redirect_url assert_equal @response.redirect_url, redirect_to_url # Must be a :redirect response. assert_raise(ActiveSupport::TestCase::Assertion) do - assert_redirected_to 'created resource' + assert_redirected_to "created resource" end end + + def test_exception_in_action_reaches_test + assert_raise(RuntimeError) do + process :boom, method: "GET" + end + end + + def test_request_state_is_cleared_after_exception + assert_raise(RuntimeError) do + process :boom, + method: "GET", + params: { q: "test1" } + end + + process :test_query_string, + method: "GET", + params: { q: "test2" } + + assert_equal "q=test2", @response.body + end end class ResponseDefaultHeadersTest < ActionController::TestCase class TestController < ActionController::Base def remove_header headers.delete params[:header] - head :ok, 'C' => '3' + head :ok, "C" => "3" end # Render a head response, but don't touch default headers @@ -983,7 +925,7 @@ class ResponseDefaultHeadersTest < ActionController::TestCase def before_setup @original = ActionDispatch::Response.default_headers - @defaults = { 'A' => '1', 'B' => '2' } + @defaults = { "A" => "1", "B" => "2" } ActionDispatch::Response.default_headers = @defaults super end @@ -995,10 +937,12 @@ class ResponseDefaultHeadersTest < ActionController::TestCase def setup super @controller = TestController.new - @request.env['PATH_INFO'] = nil + @request.env["PATH_INFO"] = nil @routes = ActionDispatch::Routing::RouteSet.new.tap do |r| r.draw do - get ':controller(/:action(/:id))' + ActiveSupport::Deprecation.silence do + get ":controller(/:action(/:id))" + end end end end @@ -1007,18 +951,18 @@ class ResponseDefaultHeadersTest < ActionController::TestCase get :leave_alone # Response headers start out with the defaults - assert_equal @defaults.merge('Content-Type' => 'text/html'), response.headers + assert_equal @defaults.merge("Content-Type" => "text/html"), response.headers end test "response deletes a default header" do - get :remove_header, params: { header: 'A' } + get :remove_header, params: { header: "A" } assert_response :ok # After a request, the response in the test case doesn't have the # defaults merged on top again. - assert_not_includes response.headers, 'A' - assert_includes response.headers, 'B' - assert_includes response.headers, 'C' + assert_not_includes response.headers, "A" + assert_includes response.headers, "B" + assert_includes response.headers, "C" end end @@ -1027,13 +971,13 @@ module EngineControllerTests isolate_namespace EngineControllerTests routes.draw do - get '/' => 'bar#index' + get "/" => "bar#index" end end class BarController < ActionController::Base def index - render plain: 'bar' + render plain: "bar" end end @@ -1042,7 +986,7 @@ module EngineControllerTests def test_engine_controller_route get :index - assert_equal @response.body, 'bar' + assert_equal @response.body, "bar" end end @@ -1055,7 +999,7 @@ module EngineControllerTests def test_engine_controller_route get :index - assert_equal @response.body, 'bar' + assert_equal @response.body, "bar" end end end @@ -1096,7 +1040,7 @@ class CrazySymbolNameTest < ActionController::TestCase end class CrazyStringNameTest < ActionController::TestCase - tests 'content' + tests "content" def test_set_controller_class_using_string assert_equal ContentController, self.class.controller_class @@ -1109,8 +1053,8 @@ class NamedRoutesControllerTest < ActionController::TestCase def test_should_be_able_to_use_named_routes_before_a_request_is_done with_routing do |set| set.draw { resources :contents } - assert_equal 'http://test.host/contents/new', new_content_url - assert_equal 'http://test.host/contents/1', content_url(id: 1) + assert_equal "http://test.host/contents/new", new_content_url + assert_equal "http://test.host/contents/1", content_url(id: 1) end end end @@ -1125,14 +1069,16 @@ class AnonymousControllerTest < ActionController::TestCase @routes = ActionDispatch::Routing::RouteSet.new.tap do |r| r.draw do - get ':controller(/:action(/:id))' + ActiveSupport::Deprecation.silence do + get ":controller(/:action(/:id))" + end end end end def test_controller_name get :index - assert_equal 'anonymous', @response.body + assert_equal "anonymous", @response.body end end @@ -1150,19 +1096,19 @@ class RoutingDefaultsTest < ActionController::TestCase @routes = ActionDispatch::Routing::RouteSet.new.tap do |r| r.draw do - get '/posts/:id', to: 'anonymous#post', bucket_type: 'post' - get '/projects/:id', to: 'anonymous#project', defaults: { bucket_type: 'project' } + get "/posts/:id", to: "anonymous#post", bucket_type: "post" + get "/projects/:id", to: "anonymous#project", defaults: { bucket_type: "project" } end end end def test_route_option_can_be_passed_via_process - get :post, params: { id: 1, bucket_type: 'post'} - assert_equal '/posts/1', @response.body + get :post, params: { id: 1, bucket_type: "post" } + assert_equal "/posts/1", @response.body end def test_route_default_is_not_required_for_building_request_uri get :project, params: { id: 2 } - assert_equal '/projects/2', @response.body + assert_equal "/projects/2", @response.body end end diff --git a/actionpack/test/controller/url_for_integration_test.rb b/actionpack/test/controller/url_for_integration_test.rb index dfc2712e3e..c7ea6c5ef6 100644 --- a/actionpack/test/controller/url_for_integration_test.rb +++ b/actionpack/test/controller/url_for_integration_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'controller/fake_controllers' -require 'active_support/core_ext/object/with_options' +require "abstract_unit" +require "controller/fake_controllers" +require "active_support/core_ext/object/with_options" module ActionPack class URLForIntegrationTest < ActiveSupport::TestCase @@ -14,51 +14,54 @@ module ActionPack resources :users, :posts end - namespace 'api' do - root :to => 'users#index' + namespace "api" do + root to: "users#index" end - get '/blog(/:year(/:month(/:day)))' => 'posts#show_date', + get "/blog(/:year(/:month(/:day)))" => "posts#show_date", :constraints => { - :year => /(19|20)\d\d/, - :month => /[01]?\d/, - :day => /[0-3]?\d/ + year: /(19|20)\d\d/, + month: /[01]?\d/, + day: /[0-3]?\d/ }, :day => nil, :month => nil - get 'archive/:year', :controller => 'archive', :action => 'index', - :defaults => { :year => nil }, - :constraints => { :year => /\d{4}/ }, - :as => "blog" + get "archive/:year", controller: "archive", action: "index", + defaults: { year: nil }, + constraints: { year: /\d{4}/ }, + as: "blog" resources :people #match 'legacy/people' => "people#index", :legacy => "true" - get 'symbols', :controller => :symbols, :action => :show, :name => :as_symbol - get 'id_default(/:id)' => "foo#id_default", :id => 1 - match 'get_or_post' => "foo#get_or_post", :via => [:get, :post] - get 'optional/:optional' => "posts#index" - get 'projects/:project_id' => "project#index", :as => "project" - get 'clients' => "projects#index" + get "symbols", controller: :symbols, action: :show, name: :as_symbol + get "id_default(/:id)" => "foo#id_default", :id => 1 + match "get_or_post" => "foo#get_or_post", :via => [:get, :post] + get "optional/:optional" => "posts#index" + get "projects/:project_id" => "project#index", :as => "project" + get "clients" => "projects#index" - get 'ignorecase/geocode/:postalcode' => 'geocode#show', :postalcode => /hx\d\d-\d[a-z]{2}/i - get 'extended/geocode/:postalcode' => 'geocode#show',:constraints => { - :postalcode => /# Postcode format + get "ignorecase/geocode/:postalcode" => "geocode#show", :postalcode => /hx\d\d-\d[a-z]{2}/i + get "extended/geocode/:postalcode" => "geocode#show",:constraints => { + postalcode: /# Postcode format \d{5} #Prefix (-\d{4})? #Suffix /x }, :as => "geocode" - get 'news(.:format)' => "news#index" + get "news(.:format)" => "news#index" - get 'comment/:id(/:action)' => "comments#show" - get 'ws/:controller(/:action(/:id))', :ws => true - get 'account(/:action)' => "account#subscription" - get 'pages/:page_id/:controller(/:action(/:id))' - get ':controller/ping', :action => 'ping' - get ':controller(/:action(/:id))(.:format)' - root :to => "news#index" + ActiveSupport::Deprecation.silence { + get "comment/:id(/:action)" => "comments#show" + get "ws/:controller(/:action(/:id))", ws: true + get "account(/:action)" => "account#subscription" + get "pages/:page_id/:controller(/:action(/:id))" + get ":controller/ping", action: "ping" + get ":controller(/:action(/:id))(.:format)" + } + + root to: "news#index" } attr_reader :routes @@ -70,111 +73,111 @@ module ActionPack end [ - ['/admin/users',[ { :use_route => 'admin_users' }]], - ['/admin/users',[ { :controller => 'admin/users' }]], - ['/admin/users',[ { :controller => 'admin/users', :action => 'index' }]], - ['/admin/users',[ { :action => 'index' }, { :controller => 'admin/users', :action => 'index' }, '/admin/users']], - ['/admin/users',[ { :controller => 'users', :action => 'index' }, { :controller => 'admin/accounts', :action => 'show', :id => '1' }, '/admin/accounts/show/1']], - ['/people',[ { :controller => '/people', :action => 'index' }, {:controller=>"admin/accounts", :action=>"foo", :id=>"bar"}, '/admin/accounts/foo/bar']], - - ['/admin/posts',[ { :controller => 'admin/posts' }]], - ['/admin/posts/new',[ { :controller => 'admin/posts', :action => 'new' }]], - - ['/blog/2009',[ { :controller => 'posts', :action => 'show_date', :year => 2009 }]], - ['/blog/2009/1',[ { :controller => 'posts', :action => 'show_date', :year => 2009, :month => 1 }]], - ['/blog/2009/1/1',[ { :controller => 'posts', :action => 'show_date', :year => 2009, :month => 1, :day => 1 }]], - - ['/archive/2010',[ { :controller => 'archive', :action => 'index', :year => '2010' }]], - ['/archive',[ { :controller => 'archive', :action => 'index' }]], - ['/archive?year=january',[ { :controller => 'archive', :action => 'index', :year => 'january' }]], - - ['/people',[ { :controller => 'people', :action => 'index' }]], - ['/people',[ { :action => 'index' }, { :controller => 'people', :action => 'index' }, '/people']], - ['/people',[ { :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' }, '/people/show/1']], - ['/people',[ { :controller => 'people', :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' }, '/people/show/1']], - ['/people',[ {}, { :controller => 'people', :action => 'index' }, '/people']], - ['/people/1',[ { :controller => 'people', :action => 'show' }, { :controller => 'people', :action => 'show', :id => '1' }, '/people/show/1']], - ['/people/new',[ { :use_route => 'new_person' }]], - ['/people/new',[ { :controller => 'people', :action => 'new' }]], - ['/people/1',[ { :use_route => 'person', :id => '1' }]], - ['/people/1',[ { :controller => 'people', :action => 'show', :id => '1' }]], - ['/people/1.xml',[ { :controller => 'people', :action => 'show', :id => '1', :format => 'xml' }]], - ['/people/1',[ { :controller => 'people', :action => 'show', :id => 1 }]], - ['/people/1',[ { :controller => 'people', :action => 'show', :id => Model.new('1') }]], - ['/people/1',[ { :action => 'show', :id => '1' }, { :controller => 'people', :action => 'index' }, '/people']], - ['/people/1',[ { :action => 'show', :id => 1 }, { :controller => 'people', :action => 'show', :id => '1' }, '/people/show/1']], - ['/people',[ { :controller => 'people', :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' }, '/people/show/1']], - ['/people/1',[ {}, { :controller => 'people', :action => 'show', :id => '1' }, '/people/show/1']], - ['/people/1',[ { :controller => 'people', :action => 'show' }, { :controller => 'people', :action => 'index', :id => '1' }, '/people/index/1']], - ['/people/1/edit',[ { :controller => 'people', :action => 'edit', :id => '1' }]], - ['/people/1/edit.xml',[ { :controller => 'people', :action => 'edit', :id => '1', :format => 'xml' }]], - ['/people/1/edit',[ { :use_route => 'edit_person', :id => '1' }]], - ['/people/1?legacy=true',[ { :controller => 'people', :action => 'show', :id => '1', :legacy => 'true' }]], - ['/people?legacy=true',[ { :controller => 'people', :action => 'index', :legacy => 'true' }]], - - ['/id_default/2',[ { :controller => 'foo', :action => 'id_default', :id => '2' }]], - ['/id_default',[ { :controller => 'foo', :action => 'id_default', :id => '1' }]], - ['/id_default',[ { :controller => 'foo', :action => 'id_default', :id => 1 }]], - ['/id_default',[ { :controller => 'foo', :action => 'id_default' }]], - ['/optional/bar',[ { :controller => 'posts', :action => 'index', :optional => 'bar' }]], - ['/posts',[ { :controller => 'posts', :action => 'index' }]], - - ['/project',[ { :controller => 'project', :action => 'index' }]], - ['/projects/1',[ { :controller => 'project', :action => 'index', :project_id => '1' }]], - ['/projects/1',[ { :controller => 'project', :action => 'index'}, {:project_id => '1', :controller => 'project', :action => 'index' }, '/projects/1']], - ['/projects/1',[ { :use_route => 'project', :controller => 'project', :action => 'index', :project_id => '1' }]], - ['/projects/1',[ { :use_route => 'project', :controller => 'project', :action => 'index' }, { :controller => 'project', :action => 'index', :project_id => '1' }, '/projects/1']], - - ['/clients',[ { :controller => 'projects', :action => 'index' }]], - ['/clients?project_id=1',[ { :controller => 'projects', :action => 'index', :project_id => '1' }]], - ['/clients',[ { :controller => 'projects', :action => 'index' }, { :project_id => '1', :controller => 'project', :action => 'index' }, '/projects/1']], - - ['/comment/20',[ { :id => 20 }, { :controller => 'comments', :action => 'show' }, '/comments/show']], - ['/comment/20',[ { :controller => 'comments', :id => 20, :action => 'show' }]], - ['/comments/boo',[ { :controller => 'comments', :action => 'boo' }]], - - ['/ws/posts/show/1',[ { :controller => 'posts', :action => 'show', :id => '1', :ws => true }]], - ['/ws/posts',[ { :controller => 'posts', :action => 'index', :ws => true }]], - - ['/account',[ { :controller => 'account', :action => 'subscription' }]], - ['/account/billing',[ { :controller => 'account', :action => 'billing' }]], - - ['/pages/1/notes/show/1',[ { :page_id => '1', :controller => 'notes', :action => 'show', :id => '1' }]], - ['/pages/1/notes/list',[ { :page_id => '1', :controller => 'notes', :action => 'list' }]], - ['/pages/1/notes',[ { :page_id => '1', :controller => 'notes', :action => 'index' }]], - ['/pages/1/notes',[ { :page_id => '1', :controller => 'notes' }]], - ['/notes',[ { :page_id => nil, :controller => 'notes' }]], - ['/notes',[ { :controller => 'notes' }]], - ['/notes/print',[ { :controller => 'notes', :action => 'print' }]], - ['/notes/print',[ {}, { :controller => 'notes', :action => 'print' }, '/notes/print']], - - ['/notes/index/1',[ { :controller => 'notes' }, { :controller => 'notes', :action => 'index', :id => '1' }, '/notes/index/1']], - ['/notes/index/1',[ { :controller => 'notes' }, { :controller => 'notes', :id => '1', :action => 'index' }, '/notes/index/1']], - ['/notes/index/1',[ { :action => 'index' }, { :controller => 'notes', :id => '1', :action => 'index' }, '/notes/index/1']], - ['/notes/index/1',[ {}, { :controller => 'notes', :id => '1', :action => 'index' }, '/notes/index/1']], - ['/notes/show/1',[ {}, { :controller => 'notes', :action => 'show', :id => '1' }, '/notes/show/1']], - ['/posts',[ { :controller => 'posts' }, { :controller => 'notes', :action => 'show', :id => '1' }, '/notes/show/1']], - ['/notes/list',[ { :action => 'list' }, { :controller => 'notes', :action => 'show', :id => '1' }, '/notes/show/1']], - - ['/posts/ping',[ { :controller => 'posts', :action => 'ping' }]], - ['/posts/show/1',[ { :controller => 'posts', :action => 'show', :id => '1' }]], - ['/posts/show/1',[ { :controller => 'posts', :action => 'show', :id => '1', :format => '' }]], - ['/posts',[ { :controller => 'posts' }]], - ['/posts',[ { :controller => 'posts', :action => 'index' }]], - ['/posts/create',[ { :action => 'create' }, {:day=>nil, :month=>nil, :controller=>"posts", :action=>"show_date"}, '/blog']], - ['/posts?foo=bar',[ { :controller => 'posts', :foo => 'bar' }]], - ['/posts?foo%5B%5D=bar&foo%5B%5D=baz', [{ :controller => 'posts', :foo => ['bar', 'baz'] }]], - ['/posts?page=2', [{ :controller => 'posts', :page => 2 }]], - ['/posts?q%5Bfoo%5D%5Ba%5D=b', [{ :controller => 'posts', :q => { :foo => { :a => 'b'}} }]], - - ['/news.rss', [{ :controller => 'news', :action => 'index', :format => 'rss' }]], + ["/admin/users",[ { use_route: "admin_users" }]], + ["/admin/users",[ { controller: "admin/users" }]], + ["/admin/users",[ { controller: "admin/users", action: "index" }]], + ["/admin/users",[ { action: "index" }, { controller: "admin/users", action: "index" }, "/admin/users"]], + ["/admin/users",[ { controller: "users", action: "index" }, { controller: "admin/accounts", action: "show", id: "1" }, "/admin/accounts/show/1"]], + ["/people",[ { controller: "/people", action: "index" }, { controller: "admin/accounts", action: "foo", id: "bar" }, "/admin/accounts/foo/bar"]], + + ["/admin/posts",[ { controller: "admin/posts" }]], + ["/admin/posts/new",[ { controller: "admin/posts", action: "new" }]], + + ["/blog/2009",[ { controller: "posts", action: "show_date", year: 2009 }]], + ["/blog/2009/1",[ { controller: "posts", action: "show_date", year: 2009, month: 1 }]], + ["/blog/2009/1/1",[ { controller: "posts", action: "show_date", year: 2009, month: 1, day: 1 }]], + + ["/archive/2010",[ { controller: "archive", action: "index", year: "2010" }]], + ["/archive",[ { controller: "archive", action: "index" }]], + ["/archive?year=january",[ { controller: "archive", action: "index", year: "january" }]], + + ["/people",[ { controller: "people", action: "index" }]], + ["/people",[ { action: "index" }, { controller: "people", action: "index" }, "/people"]], + ["/people",[ { action: "index" }, { controller: "people", action: "show", id: "1" }, "/people/show/1"]], + ["/people",[ { controller: "people", action: "index" }, { controller: "people", action: "show", id: "1" }, "/people/show/1"]], + ["/people",[ {}, { controller: "people", action: "index" }, "/people"]], + ["/people/1",[ { controller: "people", action: "show" }, { controller: "people", action: "show", id: "1" }, "/people/show/1"]], + ["/people/new",[ { use_route: "new_person" }]], + ["/people/new",[ { controller: "people", action: "new" }]], + ["/people/1",[ { use_route: "person", id: "1" }]], + ["/people/1",[ { controller: "people", action: "show", id: "1" }]], + ["/people/1.xml",[ { controller: "people", action: "show", id: "1", format: "xml" }]], + ["/people/1",[ { controller: "people", action: "show", id: 1 }]], + ["/people/1",[ { controller: "people", action: "show", id: Model.new("1") }]], + ["/people/1",[ { action: "show", id: "1" }, { controller: "people", action: "index" }, "/people"]], + ["/people/1",[ { action: "show", id: 1 }, { controller: "people", action: "show", id: "1" }, "/people/show/1"]], + ["/people",[ { controller: "people", action: "index" }, { controller: "people", action: "show", id: "1" }, "/people/show/1"]], + ["/people/1",[ {}, { controller: "people", action: "show", id: "1" }, "/people/show/1"]], + ["/people/1",[ { controller: "people", action: "show" }, { controller: "people", action: "index", id: "1" }, "/people/index/1"]], + ["/people/1/edit",[ { controller: "people", action: "edit", id: "1" }]], + ["/people/1/edit.xml",[ { controller: "people", action: "edit", id: "1", format: "xml" }]], + ["/people/1/edit",[ { use_route: "edit_person", id: "1" }]], + ["/people/1?legacy=true",[ { controller: "people", action: "show", id: "1", legacy: "true" }]], + ["/people?legacy=true",[ { controller: "people", action: "index", legacy: "true" }]], + + ["/id_default/2",[ { controller: "foo", action: "id_default", id: "2" }]], + ["/id_default",[ { controller: "foo", action: "id_default", id: "1" }]], + ["/id_default",[ { controller: "foo", action: "id_default", id: 1 }]], + ["/id_default",[ { controller: "foo", action: "id_default" }]], + ["/optional/bar",[ { controller: "posts", action: "index", optional: "bar" }]], + ["/posts",[ { controller: "posts", action: "index" }]], + + ["/project",[ { controller: "project", action: "index" }]], + ["/projects/1",[ { controller: "project", action: "index", project_id: "1" }]], + ["/projects/1",[ { controller: "project", action: "index" }, { project_id: "1", controller: "project", action: "index" }, "/projects/1"]], + ["/projects/1",[ { use_route: "project", controller: "project", action: "index", project_id: "1" }]], + ["/projects/1",[ { use_route: "project", controller: "project", action: "index" }, { controller: "project", action: "index", project_id: "1" }, "/projects/1"]], + + ["/clients",[ { controller: "projects", action: "index" }]], + ["/clients?project_id=1",[ { controller: "projects", action: "index", project_id: "1" }]], + ["/clients",[ { controller: "projects", action: "index" }, { project_id: "1", controller: "project", action: "index" }, "/projects/1"]], + + ["/comment/20",[ { id: 20 }, { controller: "comments", action: "show" }, "/comments/show"]], + ["/comment/20",[ { controller: "comments", id: 20, action: "show" }]], + ["/comments/boo",[ { controller: "comments", action: "boo" }]], + + ["/ws/posts/show/1",[ { controller: "posts", action: "show", id: "1", ws: true }]], + ["/ws/posts",[ { controller: "posts", action: "index", ws: true }]], + + ["/account",[ { controller: "account", action: "subscription" }]], + ["/account/billing",[ { controller: "account", action: "billing" }]], + + ["/pages/1/notes/show/1",[ { page_id: "1", controller: "notes", action: "show", id: "1" }]], + ["/pages/1/notes/list",[ { page_id: "1", controller: "notes", action: "list" }]], + ["/pages/1/notes",[ { page_id: "1", controller: "notes", action: "index" }]], + ["/pages/1/notes",[ { page_id: "1", controller: "notes" }]], + ["/notes",[ { page_id: nil, controller: "notes" }]], + ["/notes",[ { controller: "notes" }]], + ["/notes/print",[ { controller: "notes", action: "print" }]], + ["/notes/print",[ {}, { controller: "notes", action: "print" }, "/notes/print"]], + + ["/notes/index/1",[ { controller: "notes" }, { controller: "notes", action: "index", id: "1" }, "/notes/index/1"]], + ["/notes/index/1",[ { controller: "notes" }, { controller: "notes", id: "1", action: "index" }, "/notes/index/1"]], + ["/notes/index/1",[ { action: "index" }, { controller: "notes", id: "1", action: "index" }, "/notes/index/1"]], + ["/notes/index/1",[ {}, { controller: "notes", id: "1", action: "index" }, "/notes/index/1"]], + ["/notes/show/1",[ {}, { controller: "notes", action: "show", id: "1" }, "/notes/show/1"]], + ["/posts",[ { controller: "posts" }, { controller: "notes", action: "show", id: "1" }, "/notes/show/1"]], + ["/notes/list",[ { action: "list" }, { controller: "notes", action: "show", id: "1" }, "/notes/show/1"]], + + ["/posts/ping",[ { controller: "posts", action: "ping" }]], + ["/posts/show/1",[ { controller: "posts", action: "show", id: "1" }]], + ["/posts/show/1",[ { controller: "posts", action: "show", id: "1", format: "" }]], + ["/posts",[ { controller: "posts" }]], + ["/posts",[ { controller: "posts", action: "index" }]], + ["/posts/create",[ { action: "create" }, { day: nil, month: nil, controller: "posts", action: "show_date" }, "/blog"]], + ["/posts?foo=bar",[ { controller: "posts", foo: "bar" }]], + ["/posts?foo%5B%5D=bar&foo%5B%5D=baz", [{ controller: "posts", foo: ["bar", "baz"] }]], + ["/posts?page=2", [{ controller: "posts", page: 2 }]], + ["/posts?q%5Bfoo%5D%5Ba%5D=b", [{ controller: "posts", q: { foo: { a: "b" } } }]], + + ["/news.rss", [{ controller: "news", action: "index", format: "rss" }]], ].each_with_index do |(url, params), i| if params.length > 1 hash, path_params, route = *params hash[:only_path] = true define_method("test_#{url.gsub(/\W/, '_')}_#{i}") do - get URI('http://test.host' + route.to_s) + get URI("http://test.host" + route.to_s) assert_equal path_params, controller.request.path_parameters assert_equal url, controller.url_for(hash), params.inspect end diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb index 67212fea38..8d7190365d 100644 --- a/actionpack/test/controller/url_for_test.rb +++ b/actionpack/test/controller/url_for_test.rb @@ -1,10 +1,16 @@ -require 'abstract_unit' +require "abstract_unit" module AbstractController module Testing class UrlForTest < ActionController::TestCase class W - include ActionDispatch::Routing::RouteSet.new.tap { |r| r.draw { get ':controller(/:action(/:id(.:format)))' } }.url_helpers + include ActionDispatch::Routing::RouteSet.new.tap { |r| + r.draw { + ActiveSupport::Deprecation.silence { + get ":controller(/:action(/:id(.:format)))" + } + } + }.url_helpers end def teardown @@ -15,23 +21,23 @@ module AbstractController klass = Class.new { include ActionDispatch::Routing::RouteSet.new.tap { |r| r.draw { - get "/foo/(:bar/(:baz))/:zot", :as => 'fun', - :controller => :articles, - :action => :index + get "/foo/(:bar/(:baz))/:zot", as: "fun", + controller: :articles, + action: :index } }.url_helpers - self.default_url_options[:host] = 'example.com' + self.default_url_options[:host] = "example.com" } - path = klass.new.fun_path({:controller => :articles, - :baz => "baz", - :zot => "zot"}) + path = klass.new.fun_path(controller: :articles, + baz: "baz", + zot: "zot") # :bar key isn't provided - assert_equal '/foo/zot', path + assert_equal "/foo/zot", path end def add_host!(app = W) - app.default_url_options[:host] = 'www.basecamphq.com' + app.default_url_options[:host] = "www.basecamphq.com" end def add_port! @@ -39,166 +45,166 @@ module AbstractController end def add_numeric_host! - W.default_url_options[:host] = '127.0.0.1' + W.default_url_options[:host] = "127.0.0.1" end def test_exception_is_thrown_without_host assert_raise ArgumentError do - W.new.url_for :controller => 'c', :action => 'a', :id => 'i' + W.new.url_for controller: "c", action: "a", id: "i" end end def test_anchor - assert_equal('/c/a#anchor', - W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => 'anchor') + assert_equal("/c/a#anchor", + W.new.url_for(only_path: true, controller: "c", action: "a", anchor: "anchor") ) end def test_nil_anchor assert_equal( - '/c/a', - W.new.url_for(only_path: true, controller: 'c', action: 'a', anchor: nil) + "/c/a", + W.new.url_for(only_path: true, controller: "c", action: "a", anchor: nil) ) end def test_false_anchor assert_equal( - '/c/a', - W.new.url_for(only_path: true, controller: 'c', action: 'a', anchor: false) + "/c/a", + W.new.url_for(only_path: true, controller: "c", action: "a", anchor: false) ) end def test_anchor_should_call_to_param - assert_equal('/c/a#anchor', - W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anchor')) + assert_equal("/c/a#anchor", + W.new.url_for(only_path: true, controller: "c", action: "a", anchor: Struct.new(:to_param).new("anchor")) ) end def test_anchor_should_escape_unsafe_pchar - assert_equal('/c/a#%23anchor', - W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('#anchor')) + assert_equal("/c/a#%23anchor", + W.new.url_for(only_path: true, controller: "c", action: "a", anchor: Struct.new(:to_param).new("#anchor")) ) end def test_anchor_should_not_escape_safe_pchar - assert_equal('/c/a#name=user&email=user@domain.com', - W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('name=user&email=user@domain.com')) + assert_equal("/c/a#name=user&email=user@domain.com", + W.new.url_for(only_path: true, controller: "c", action: "a", anchor: Struct.new(:to_param).new("name=user&email=user@domain.com")) ) end def test_default_host add_host! - assert_equal('http://www.basecamphq.com/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i') + assert_equal("http://www.basecamphq.com/c/a/i", + W.new.url_for(controller: "c", action: "a", id: "i") ) end def test_host_may_be_overridden add_host! - assert_equal('http://37signals.basecamphq.com/c/a/i', - W.new.url_for(:host => '37signals.basecamphq.com', :controller => 'c', :action => 'a', :id => 'i') + assert_equal("http://37signals.basecamphq.com/c/a/i", + W.new.url_for(host: "37signals.basecamphq.com", controller: "c", action: "a", id: "i") ) end def test_subdomain_may_be_changed add_host! - assert_equal('http://api.basecamphq.com/c/a/i', - W.new.url_for(:subdomain => 'api', :controller => 'c', :action => 'a', :id => 'i') + assert_equal("http://api.basecamphq.com/c/a/i", + W.new.url_for(subdomain: "api", controller: "c", action: "a", id: "i") ) end def test_subdomain_may_be_object - model = Class.new { def self.to_param; 'api'; end } + model = Class.new { def self.to_param; "api"; end } add_host! - assert_equal('http://api.basecamphq.com/c/a/i', - W.new.url_for(:subdomain => model, :controller => 'c', :action => 'a', :id => 'i') + assert_equal("http://api.basecamphq.com/c/a/i", + W.new.url_for(subdomain: model, controller: "c", action: "a", id: "i") ) end def test_subdomain_may_be_removed add_host! - assert_equal('http://basecamphq.com/c/a/i', - W.new.url_for(:subdomain => false, :controller => 'c', :action => 'a', :id => 'i') + assert_equal("http://basecamphq.com/c/a/i", + W.new.url_for(subdomain: false, controller: "c", action: "a", id: "i") ) end def test_subdomain_may_be_removed_with_blank_string - W.default_url_options[:host] = 'api.basecamphq.com' - assert_equal('http://basecamphq.com/c/a/i', - W.new.url_for(:subdomain => '', :controller => 'c', :action => 'a', :id => 'i') + W.default_url_options[:host] = "api.basecamphq.com" + assert_equal("http://basecamphq.com/c/a/i", + W.new.url_for(subdomain: "", controller: "c", action: "a", id: "i") ) end def test_multiple_subdomains_may_be_removed - W.default_url_options[:host] = 'mobile.www.api.basecamphq.com' - assert_equal('http://basecamphq.com/c/a/i', - W.new.url_for(:subdomain => false, :controller => 'c', :action => 'a', :id => 'i') + W.default_url_options[:host] = "mobile.www.api.basecamphq.com" + assert_equal("http://basecamphq.com/c/a/i", + W.new.url_for(subdomain: false, controller: "c", action: "a", id: "i") ) end def test_subdomain_may_be_accepted_with_numeric_host add_numeric_host! - assert_equal('http://127.0.0.1/c/a/i', - W.new.url_for(:subdomain => 'api', :controller => 'c', :action => 'a', :id => 'i') + assert_equal("http://127.0.0.1/c/a/i", + W.new.url_for(subdomain: "api", controller: "c", action: "a", id: "i") ) end def test_domain_may_be_changed add_host! - assert_equal('http://www.37signals.com/c/a/i', - W.new.url_for(:domain => '37signals.com', :controller => 'c', :action => 'a', :id => 'i') + assert_equal("http://www.37signals.com/c/a/i", + W.new.url_for(domain: "37signals.com", controller: "c", action: "a", id: "i") ) end def test_tld_length_may_be_changed add_host! - assert_equal('http://mobile.www.basecamphq.com/c/a/i', - W.new.url_for(:subdomain => 'mobile', :tld_length => 2, :controller => 'c', :action => 'a', :id => 'i') + assert_equal("http://mobile.www.basecamphq.com/c/a/i", + W.new.url_for(subdomain: "mobile", tld_length: 2, controller: "c", action: "a", id: "i") ) end def test_port add_host! - assert_equal('http://www.basecamphq.com:3000/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :port => 3000) + assert_equal("http://www.basecamphq.com:3000/c/a/i", + W.new.url_for(controller: "c", action: "a", id: "i", port: 3000) ) end def test_default_port add_host! add_port! - assert_equal('http://www.basecamphq.com:3000/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i') + assert_equal("http://www.basecamphq.com:3000/c/a/i", + W.new.url_for(controller: "c", action: "a", id: "i") ) end def test_protocol add_host! - assert_equal('https://www.basecamphq.com/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https') + assert_equal("https://www.basecamphq.com/c/a/i", + W.new.url_for(controller: "c", action: "a", id: "i", protocol: "https") ) end def test_protocol_with_and_without_separators add_host! - assert_equal('https://www.basecamphq.com/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https') + assert_equal("https://www.basecamphq.com/c/a/i", + W.new.url_for(controller: "c", action: "a", id: "i", protocol: "https") ) - assert_equal('https://www.basecamphq.com/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https:') + assert_equal("https://www.basecamphq.com/c/a/i", + W.new.url_for(controller: "c", action: "a", id: "i", protocol: "https:") ) - assert_equal('https://www.basecamphq.com/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https://') + assert_equal("https://www.basecamphq.com/c/a/i", + W.new.url_for(controller: "c", action: "a", id: "i", protocol: "https://") ) end def test_without_protocol add_host! - assert_equal('//www.basecamphq.com/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => '//') + assert_equal("//www.basecamphq.com/c/a/i", + W.new.url_for(controller: "c", action: "a", id: "i", protocol: "//") ) - assert_equal('//www.basecamphq.com/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => false) + assert_equal("//www.basecamphq.com/c/a/i", + W.new.url_for(controller: "c", action: "a", id: "i", protocol: false) ) end @@ -206,74 +212,74 @@ module AbstractController add_host! add_port! - assert_equal('//www.basecamphq.com:3000/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => '//') + assert_equal("//www.basecamphq.com:3000/c/a/i", + W.new.url_for(controller: "c", action: "a", id: "i", protocol: "//") ) - assert_equal('//www.basecamphq.com:3000/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => false) + assert_equal("//www.basecamphq.com:3000/c/a/i", + W.new.url_for(controller: "c", action: "a", id: "i", protocol: false) ) end def test_trailing_slash add_host! - options = {:controller => 'foo', :trailing_slash => true, :action => 'bar', :id => '33'} - assert_equal('http://www.basecamphq.com/foo/bar/33/', W.new.url_for(options) ) + options = { controller: "foo", trailing_slash: true, action: "bar", id: "33" } + assert_equal("http://www.basecamphq.com/foo/bar/33/", W.new.url_for(options) ) end def test_trailing_slash_with_protocol add_host! - options = { :trailing_slash => true,:protocol => 'https', :controller => 'foo', :action => 'bar', :id => '33'} - assert_equal('https://www.basecamphq.com/foo/bar/33/', W.new.url_for(options) ) - assert_equal 'https://www.basecamphq.com/foo/bar/33/?query=string', W.new.url_for(options.merge({:query => 'string'})) + options = { trailing_slash: true,protocol: "https", controller: "foo", action: "bar", id: "33" } + assert_equal("https://www.basecamphq.com/foo/bar/33/", W.new.url_for(options) ) + assert_equal "https://www.basecamphq.com/foo/bar/33/?query=string", W.new.url_for(options.merge(query: "string")) end def test_trailing_slash_with_only_path - options = {:controller => 'foo', :trailing_slash => true} - assert_equal '/foo/', W.new.url_for(options.merge({:only_path => true})) - options.update({:action => 'bar', :id => '33'}) - assert_equal '/foo/bar/33/', W.new.url_for(options.merge({:only_path => true})) - assert_equal '/foo/bar/33/?query=string', W.new.url_for(options.merge({:query => 'string',:only_path => true})) + options = { controller: "foo", trailing_slash: true } + assert_equal "/foo/", W.new.url_for(options.merge(only_path: true)) + options.update(action: "bar", id: "33") + assert_equal "/foo/bar/33/", W.new.url_for(options.merge(only_path: true)) + assert_equal "/foo/bar/33/?query=string", W.new.url_for(options.merge(query: "string",only_path: true)) end def test_trailing_slash_with_anchor - options = {:trailing_slash => true, :controller => 'foo', :action => 'bar', :id => '33', :only_path => true, :anchor=> 'chapter7'} - assert_equal '/foo/bar/33/#chapter7', W.new.url_for(options) - assert_equal '/foo/bar/33/?query=string#chapter7', W.new.url_for(options.merge({:query => 'string'})) + options = { trailing_slash: true, controller: "foo", action: "bar", id: "33", only_path: true, anchor: "chapter7" } + assert_equal "/foo/bar/33/#chapter7", W.new.url_for(options) + assert_equal "/foo/bar/33/?query=string#chapter7", W.new.url_for(options.merge(query: "string")) end def test_trailing_slash_with_params - url = W.new.url_for(:trailing_slash => true, :only_path => true, :controller => 'cont', :action => 'act', :p1 => 'cafe', :p2 => 'link') + url = W.new.url_for(trailing_slash: true, only_path: true, controller: "cont", action: "act", p1: "cafe", p2: "link") params = extract_params(url) - assert_equal({p1: 'cafe'}.to_query, params[0]) - assert_equal({p2: 'link'}.to_query, params[1]) + assert_equal({ p1: "cafe" }.to_query, params[0]) + assert_equal({ p2: "link" }.to_query, params[1]) end def test_relative_url_root_is_respected add_host! - assert_equal('https://www.basecamphq.com/subdir/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https', :script_name => '/subdir') + assert_equal("https://www.basecamphq.com/subdir/c/a/i", + W.new.url_for(controller: "c", action: "a", id: "i", protocol: "https", script_name: "/subdir") ) end def test_relative_url_root_is_respected_with_environment_variable # `config.relative_url_root` is set by ENV['RAILS_RELATIVE_URL_ROOT'] w = Class.new { - config = ActionDispatch::Routing::RouteSet::Config.new '/subdir' + config = ActionDispatch::Routing::RouteSet::Config.new "/subdir" r = ActionDispatch::Routing::RouteSet.new(config) - r.draw { get ':controller(/:action(/:id(.:format)))' } + r.draw { ActiveSupport::Deprecation.silence { get ":controller(/:action(/:id(.:format)))" } } include r.url_helpers } add_host!(w) - assert_equal('https://www.basecamphq.com/subdir/c/a/i', - w.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https') + assert_equal("https://www.basecamphq.com/subdir/c/a/i", + w.new.url_for(controller: "c", action: "a", id: "i", protocol: "https") ) end def test_named_routes with_routing do |set| set.draw do - get 'this/is/verbose', :to => 'home#index', :as => :no_args - get 'home/sweet/home/:user', :to => 'home#index', :as => :home + get "this/is/verbose", to: "home#index", as: :no_args + get "home/sweet/home/:user", to: "home#index", as: :home end # We need to create a new class in order to install the new named route. @@ -281,124 +287,127 @@ module AbstractController controller = kls.new assert controller.respond_to?(:home_url) - assert_equal 'http://www.basecamphq.com/home/sweet/home/again', - controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again') + assert_equal "http://www.basecamphq.com/home/sweet/home/again", + controller.send(:home_url, host: "www.basecamphq.com", user: "again") - assert_equal("/home/sweet/home/alabama", controller.send(:home_path, :user => 'alabama', :host => 'unused')) - assert_equal("http://www.basecamphq.com/home/sweet/home/alabama", controller.send(:home_url, :user => 'alabama', :host => 'www.basecamphq.com')) - assert_equal("http://www.basecamphq.com/this/is/verbose", controller.send(:no_args_url, :host=>'www.basecamphq.com')) + assert_equal("/home/sweet/home/alabama", controller.send(:home_path, user: "alabama", host: "unused")) + assert_equal("http://www.basecamphq.com/home/sweet/home/alabama", controller.send(:home_url, user: "alabama", host: "www.basecamphq.com")) + assert_equal("http://www.basecamphq.com/this/is/verbose", controller.send(:no_args_url, host: "www.basecamphq.com")) end end def test_relative_url_root_is_respected_for_named_routes with_routing do |set| set.draw do - get '/home/sweet/home/:user', :to => 'home#index', :as => :home + get "/home/sweet/home/:user", to: "home#index", as: :home end kls = Class.new { include set.url_helpers } controller = kls.new - assert_equal 'http://www.basecamphq.com/subdir/home/sweet/home/again', - controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again', :script_name => "/subdir") + assert_equal "http://www.basecamphq.com/subdir/home/sweet/home/again", + controller.send(:home_url, host: "www.basecamphq.com", user: "again", script_name: "/subdir") end end def test_using_nil_script_name_properly_concats_with_original_script_name add_host! - assert_equal('https://www.basecamphq.com/subdir/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https', :script_name => nil, :original_script_name => '/subdir') + assert_equal("https://www.basecamphq.com/subdir/c/a/i", + W.new.url_for(controller: "c", action: "a", id: "i", protocol: "https", script_name: nil, original_script_name: "/subdir") ) end def test_only_path with_routing do |set| set.draw do - get 'home/sweet/home/:user', :to => 'home#index', :as => :home - get ':controller/:action/:id' + get "home/sweet/home/:user", to: "home#index", as: :home + + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + end end # We need to create a new class in order to install the new named route. kls = Class.new { include set.url_helpers } controller = kls.new assert_respond_to controller, :home_url - assert_equal '/brave/new/world', - controller.url_for(:controller => 'brave', :action => 'new', :id => 'world', :only_path => true) + assert_equal "/brave/new/world", + controller.url_for(controller: "brave", action: "new", id: "world", only_path: true) - assert_equal("/home/sweet/home/alabama", controller.home_path(:user => 'alabama', :host => 'unused')) - assert_equal("/home/sweet/home/alabama", controller.home_path('alabama')) + assert_equal("/home/sweet/home/alabama", controller.home_path(user: "alabama", host: "unused")) + assert_equal("/home/sweet/home/alabama", controller.home_path("alabama")) end end def test_one_parameter - assert_equal('/c/a?param=val', - W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :param => 'val') + assert_equal("/c/a?param=val", + W.new.url_for(only_path: true, controller: "c", action: "a", param: "val") ) end def test_two_parameters - url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :p1 => 'X1', :p2 => 'Y2') + url = W.new.url_for(only_path: true, controller: "c", action: "a", p1: "X1", p2: "Y2") params = extract_params(url) - assert_equal({p1: 'X1'}.to_query, params[0]) - assert_equal({p2: 'Y2'}.to_query, params[1]) + assert_equal({ p1: "X1" }.to_query, params[0]) + assert_equal({ p2: "Y2" }.to_query, params[1]) end def test_hash_parameter - url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:name => 'Bob', :category => 'prof'}) + url = W.new.url_for(only_path: true, controller: "c", action: "a", query: { name: "Bob", category: "prof" }) params = extract_params(url) - assert_equal({'query[category]' => 'prof'}.to_query, params[0]) - assert_equal({'query[name]' => 'Bob'}.to_query, params[1]) + assert_equal({ "query[category]" => "prof" }.to_query, params[0]) + assert_equal({ "query[name]" => "Bob" }.to_query, params[1]) end def test_array_parameter - url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => ['Bob', 'prof']) + url = W.new.url_for(only_path: true, controller: "c", action: "a", query: ["Bob", "prof"]) params = extract_params(url) - assert_equal({'query[]' => 'Bob'}.to_query, params[0]) - assert_equal({'query[]' => 'prof'}.to_query, params[1]) + assert_equal({ "query[]" => "Bob" }.to_query, params[0]) + assert_equal({ "query[]" => "prof" }.to_query, params[1]) end def test_hash_recursive_parameters - url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:person => {:name => 'Bob', :position => 'prof'}, :hobby => 'piercing'}) + url = W.new.url_for(only_path: true, controller: "c", action: "a", query: { person: { name: "Bob", position: "prof" }, hobby: "piercing" }) params = extract_params(url) - assert_equal({'query[hobby]' => 'piercing'}.to_query, params[0]) - assert_equal({'query[person][name]' => 'Bob' }.to_query, params[1]) - assert_equal({'query[person][position]' => 'prof' }.to_query, params[2]) + assert_equal({ "query[hobby]" => "piercing" }.to_query, params[0]) + assert_equal({ "query[person][name]" => "Bob" }.to_query, params[1]) + assert_equal({ "query[person][position]" => "prof" }.to_query, params[2]) end def test_hash_recursive_and_array_parameters - url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :id => 101, :query => {:person => {:name => 'Bob', :position => ['prof', 'art director']}, :hobby => 'piercing'}) + url = W.new.url_for(only_path: true, controller: "c", action: "a", id: 101, query: { person: { name: "Bob", position: ["prof", "art director"] }, hobby: "piercing" }) assert_match(%r(^/c/a/101), url) params = extract_params(url) - assert_equal({'query[hobby]' => 'piercing' }.to_query, params[0]) - assert_equal({'query[person][name]' => 'Bob' }.to_query, params[1]) - assert_equal({'query[person][position][]' => 'art director'}.to_query, params[2]) - assert_equal({'query[person][position][]' => 'prof' }.to_query, params[3]) + assert_equal({ "query[hobby]" => "piercing" }.to_query, params[0]) + assert_equal({ "query[person][name]" => "Bob" }.to_query, params[1]) + assert_equal({ "query[person][position][]" => "art director" }.to_query, params[2]) + assert_equal({ "query[person][position][]" => "prof" }.to_query, params[3]) end def test_url_action_controller_parameters add_host! assert_raise(ArgumentError) do - W.new.url_for(ActionController::Parameters.new(:controller => 'c', :action => 'a', protocol: 'javascript', f: '%0Aeval(name)')) + W.new.url_for(ActionController::Parameters.new(controller: "c", action: "a", protocol: "javascript", f: "%0Aeval(name)")) end end def test_path_generation_for_symbol_parameter_keys - assert_generates("/image", :controller=> :image) + assert_generates("/image", controller: :image) end def test_named_routes_with_nil_keys with_routing do |set| set.draw do - get 'posts.:format', :to => 'posts#index', :as => :posts - get '/', :to => 'posts#index', :as => :main + get "posts.:format", to: "posts#index", as: :posts + get "/", to: "posts#index", as: :main end # We need to create a new class in order to install the new named route. kls = Class.new { include set.url_helpers } - kls.default_url_options[:host] = 'www.basecamphq.com' + kls.default_url_options[:host] = "www.basecamphq.com" controller = kls.new - params = {:action => :index, :controller => :posts, :format => :xml} + params = { action: :index, controller: :posts, format: :xml } assert_equal("http://www.basecamphq.com/posts.xml", controller.send(:url_for, params)) params[:format] = nil assert_equal("http://www.basecamphq.com/", controller.send(:url_for, params)) @@ -409,7 +418,7 @@ module AbstractController first_class = Class.new { include ActionController::UrlFor } second_class = Class.new { include ActionController::UrlFor } - first_host, second_host = 'firsthost.com', 'secondhost.com' + first_host, second_host = "firsthost.com", "secondhost.com" first_class.default_url_options[:host] = first_host second_class.default_url_options[:host] = second_host @@ -419,25 +428,25 @@ module AbstractController end def test_with_stringified_keys - assert_equal("/c", W.new.url_for('controller' => 'c', 'only_path' => true)) - assert_equal("/c/a", W.new.url_for('controller' => 'c', 'action' => 'a', 'only_path' => true)) + assert_equal("/c", W.new.url_for("controller" => "c", "only_path" => true)) + assert_equal("/c/a", W.new.url_for("controller" => "c", "action" => "a", "only_path" => true)) end def test_with_hash_with_indifferent_access - W.default_url_options[:controller] = 'd' + W.default_url_options[:controller] = "d" W.default_url_options[:only_path] = false - assert_equal("/c", W.new.url_for(ActiveSupport::HashWithIndifferentAccess.new('controller' => 'c', 'only_path' => true))) + assert_equal("/c", W.new.url_for(ActiveSupport::HashWithIndifferentAccess.new("controller" => "c", "only_path" => true))) - W.default_url_options[:action] = 'b' - assert_equal("/c/a", W.new.url_for(ActiveSupport::HashWithIndifferentAccess.new('controller' => 'c', 'action' => 'a', 'only_path' => true))) + W.default_url_options[:action] = "b" + assert_equal("/c/a", W.new.url_for(ActiveSupport::HashWithIndifferentAccess.new("controller" => "c", "action" => "a", "only_path" => true))) end def test_url_params_with_nil_to_param_are_not_in_url - assert_equal("/c/a", W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :id => Struct.new(:to_param).new(nil))) + assert_equal("/c/a", W.new.url_for(only_path: true, controller: "c", action: "a", id: Struct.new(:to_param).new(nil))) end def test_false_url_params_are_included_in_query - assert_equal("/c/a?show=false", W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :show => false)) + assert_equal("/c/a?show=false", W.new.url_for(only_path: true, controller: "c", action: "a", show: false)) end def test_url_generation_with_array_and_hash @@ -449,11 +458,11 @@ module AbstractController end kls = Class.new { include set.url_helpers } - kls.default_url_options[:host] = 'www.basecamphq.com' + kls.default_url_options[:host] = "www.basecamphq.com" controller = kls.new assert_equal("http://www.basecamphq.com/admin/posts/new?param=value", - controller.send(:url_for, [:new, :admin, :post, { param: 'value' }]) + controller.send(:url_for, [:new, :admin, :post, { param: "value" }]) ) end end @@ -467,9 +476,9 @@ module AbstractController end kls = Class.new { include set.url_helpers } - kls.default_url_options[:host] = 'www.basecamphq.com' + kls.default_url_options[:host] = "www.basecamphq.com" - original_components = [:new, :admin, :post, { param: 'value' }] + original_components = [:new, :admin, :post, { param: "value" }] components = original_components.dup kls.new.url_for(components) @@ -480,7 +489,7 @@ module AbstractController private def extract_params(url) - url.split('?', 2).last.split('&').sort + url.split("?", 2).last.split("&").sort end end end diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb index 5f2abc9606..a055e6d177 100644 --- a/actionpack/test/controller/url_rewriter_test.rb +++ b/actionpack/test/controller/url_rewriter_test.rb @@ -1,12 +1,12 @@ -require 'abstract_unit' -require 'controller/fake_controllers' +require "abstract_unit" +require "controller/fake_controllers" class UrlRewriterTests < ActionController::TestCase class Rewriter def initialize(request) @options = { - :host => request.host_with_port, - :protocol => request.protocol + host: request.host_with_port, + protocol: request.protocol } end @@ -20,70 +20,71 @@ class UrlRewriterTests < ActionController::TestCase @rewriter = Rewriter.new(@request) #.new(@request, @params) @routes = ActionDispatch::Routing::RouteSet.new.tap do |r| r.draw do - get ':controller(/:action(/:id))' + ActiveSupport::Deprecation.silence do + get ":controller(/:action(/:id))" + end end end end def test_port - assert_equal('http://test.host:1271/c/a/i', - @rewriter.rewrite(@routes, :controller => 'c', :action => 'a', :id => 'i', :port => 1271) + assert_equal("http://test.host:1271/c/a/i", + @rewriter.rewrite(@routes, controller: "c", action: "a", id: "i", port: 1271) ) end def test_protocol_with_and_without_separator - assert_equal('https://test.host/c/a/i', - @rewriter.rewrite(@routes, :protocol => 'https', :controller => 'c', :action => 'a', :id => 'i') + assert_equal("https://test.host/c/a/i", + @rewriter.rewrite(@routes, protocol: "https", controller: "c", action: "a", id: "i") ) - assert_equal('https://test.host/c/a/i', - @rewriter.rewrite(@routes, :protocol => 'https://', :controller => 'c', :action => 'a', :id => 'i') + assert_equal("https://test.host/c/a/i", + @rewriter.rewrite(@routes, protocol: "https://", controller: "c", action: "a", id: "i") ) end def test_user_name_and_password assert_equal( - 'http://david:secret@test.host/c/a/i', - @rewriter.rewrite(@routes, :user => "david", :password => "secret", :controller => 'c', :action => 'a', :id => 'i') + "http://david:secret@test.host/c/a/i", + @rewriter.rewrite(@routes, user: "david", password: "secret", controller: "c", action: "a", id: "i") ) end def test_user_name_and_password_with_escape_codes assert_equal( - 'http://openid.aol.com%2Fnextangler:one+two%3F@test.host/c/a/i', - @rewriter.rewrite(@routes, :user => "openid.aol.com/nextangler", :password => "one two?", :controller => 'c', :action => 'a', :id => 'i') + "http://openid.aol.com%2Fnextangler:one+two%3F@test.host/c/a/i", + @rewriter.rewrite(@routes, user: "openid.aol.com/nextangler", password: "one two?", controller: "c", action: "a", id: "i") ) end def test_anchor assert_equal( - 'http://test.host/c/a/i#anchor', - @rewriter.rewrite(@routes, :controller => 'c', :action => 'a', :id => 'i', :anchor => 'anchor') + "http://test.host/c/a/i#anchor", + @rewriter.rewrite(@routes, controller: "c", action: "a", id: "i", anchor: "anchor") ) end def test_anchor_should_call_to_param assert_equal( - 'http://test.host/c/a/i#anchor', - @rewriter.rewrite(@routes, :controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anchor')) + "http://test.host/c/a/i#anchor", + @rewriter.rewrite(@routes, controller: "c", action: "a", id: "i", anchor: Struct.new(:to_param).new("anchor")) ) end def test_anchor_should_be_uri_escaped assert_equal( - 'http://test.host/c/a/i#anc/hor', - @rewriter.rewrite(@routes, :controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anc/hor')) + "http://test.host/c/a/i#anc/hor", + @rewriter.rewrite(@routes, controller: "c", action: "a", id: "i", anchor: Struct.new(:to_param).new("anc/hor")) ) end def test_trailing_slash - options = {:controller => 'foo', :action => 'bar', :id => '3', :only_path => true} - assert_equal '/foo/bar/3', @rewriter.rewrite(@routes, options) - assert_equal '/foo/bar/3?query=string', @rewriter.rewrite(@routes, options.merge({:query => 'string'})) - options.update({:trailing_slash => true}) - assert_equal '/foo/bar/3/', @rewriter.rewrite(@routes, options) - options.update({:query => 'string'}) - assert_equal '/foo/bar/3/?query=string', @rewriter.rewrite(@routes, options) + options = { controller: "foo", action: "bar", id: "3", only_path: true } + assert_equal "/foo/bar/3", @rewriter.rewrite(@routes, options) + assert_equal "/foo/bar/3?query=string", @rewriter.rewrite(@routes, options.merge(query: "string")) + options.update(trailing_slash: true) + assert_equal "/foo/bar/3/", @rewriter.rewrite(@routes, options) + options.update(query: "string") + assert_equal "/foo/bar/3/?query=string", @rewriter.rewrite(@routes, options) end end - diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb index 6d377c4691..6f97a4b62e 100644 --- a/actionpack/test/controller/webservice_test.rb +++ b/actionpack/test/controller/webservice_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/json/decoding' +require "abstract_unit" +require "active_support/json/decoding" class WebServiceTest < ActionDispatch::IntegrationTest class TestController < ActionController::Base @@ -7,7 +7,7 @@ class WebServiceTest < ActionDispatch::IntegrationTest if params[:full] render plain: dump_params_keys else - render plain: (params.keys - ['controller', 'action']).sort.join(", ") + render plain: (params.keys - ["controller", "action"]).sort.join(", ") end end @@ -35,7 +35,7 @@ class WebServiceTest < ActionDispatch::IntegrationTest def test_check_parameters with_test_route_set do get "/" - assert_equal '', @controller.response.body + assert_equal "", @controller.response.body end end @@ -43,11 +43,11 @@ class WebServiceTest < ActionDispatch::IntegrationTest with_test_route_set do post "/", params: '{"entry":{"summary":"content..."}}', - headers: { 'CONTENT_TYPE' => 'application/json' } + headers: { "CONTENT_TYPE" => "application/json" } - assert_equal 'entry', @controller.response.body + assert_equal "entry", @controller.response.body assert @controller.params.has_key?(:entry) - assert_equal 'content...', @controller.params["entry"]['summary'] + assert_equal "content...", @controller.params["entry"]["summary"] end end @@ -55,34 +55,34 @@ class WebServiceTest < ActionDispatch::IntegrationTest with_test_route_set do put "/", params: '{"entry":{"summary":"content..."}}', - headers: { 'CONTENT_TYPE' => 'application/json' } + headers: { "CONTENT_TYPE" => "application/json" } - assert_equal 'entry', @controller.response.body + assert_equal "entry", @controller.response.body assert @controller.params.has_key?(:entry) - assert_equal 'content...', @controller.params["entry"]['summary'] + assert_equal "content...", @controller.params["entry"]["summary"] end end def test_register_and_use_json_simple with_test_route_set do - with_params_parsers Mime[:json] => Proc.new { |data| ActiveSupport::JSON.decode(data)['request'].with_indifferent_access } do + with_params_parsers Mime[:json] => Proc.new { |data| ActiveSupport::JSON.decode(data)["request"].with_indifferent_access } do post "/", params: '{"request":{"summary":"content...","title":"JSON"}}', - headers: { 'CONTENT_TYPE' => 'application/json' } + headers: { "CONTENT_TYPE" => "application/json" } - assert_equal 'summary, title', @controller.response.body + assert_equal "summary, title", @controller.response.body assert @controller.params.has_key?(:summary) assert @controller.params.has_key?(:title) - assert_equal 'content...', @controller.params["summary"] - assert_equal 'JSON', @controller.params["title"] + assert_equal "content...", @controller.params["summary"] + assert_equal "JSON", @controller.params["title"] end end end def test_use_json_with_empty_request with_test_route_set do - assert_nothing_raised { post "/", headers: { 'CONTENT_TYPE' => 'application/json' } } - assert_equal '', @controller.response.body + assert_nothing_raised { post "/", headers: { "CONTENT_TYPE" => "application/json" } } + assert_equal "", @controller.response.body end end @@ -90,20 +90,20 @@ class WebServiceTest < ActionDispatch::IntegrationTest with_test_route_set do post "/?full=1", params: '{"first-key":{"sub-key":"..."}}', - headers: { 'CONTENT_TYPE' => 'application/json' } - assert_equal 'action, controller, first-key(sub-key), full', @controller.response.body - assert_equal "...", @controller.params['first-key']['sub-key'] + headers: { "CONTENT_TYPE" => "application/json" } + assert_equal "action, controller, first-key(sub-key), full", @controller.response.body + assert_equal "...", @controller.params["first-key"]["sub-key"] end end def test_parsing_json_doesnot_rescue_exception req = Class.new(ActionDispatch::Request) do def params_parsers - { Mime[:json] => Proc.new { |data| raise Interrupt } } + { json: Proc.new { |data| raise Interrupt } } end - def content_length; get_header('rack.input').length; end - end.new({ 'rack.input' => StringIO.new('{"title":"JSON"}}'), 'CONTENT_TYPE' => 'application/json' }) + def content_length; get_header("rack.input").length; end + end.new("rack.input" => StringIO.new('{"title":"JSON"}}'), "CONTENT_TYPE" => "application/json") assert_raises(Interrupt) do req.request_parameters @@ -125,7 +125,7 @@ class WebServiceTest < ActionDispatch::IntegrationTest def with_test_route_set with_routing do |set| set.draw do - match '/', :to => 'web_service_test/test#assign_parameters', :via => :all + match "/", to: "web_service_test/test#assign_parameters", via: :all end yield end diff --git a/actionpack/test/dispatch/callbacks_test.rb b/actionpack/test/dispatch/callbacks_test.rb index 5ba76d9ab9..57e21a22c6 100644 --- a/actionpack/test/dispatch/callbacks_test.rb +++ b/actionpack/test/dispatch/callbacks_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class DispatcherTest < ActiveSupport::TestCase class Foo @@ -7,7 +7,7 @@ class DispatcherTest < ActiveSupport::TestCase class DummyApp def call(env) - [200, {}, 'response'] + [200, {}, "response"] end end @@ -37,13 +37,19 @@ class DispatcherTest < ActiveSupport::TestCase def test_to_prepare_and_cleanup_delegation prepared = cleaned = false - ActionDispatch::Callbacks.to_prepare { prepared = true } - ActionDispatch::Callbacks.to_prepare { cleaned = true } + assert_deprecated do + ActionDispatch::Callbacks.to_prepare { prepared = true } + ActionDispatch::Callbacks.to_prepare { cleaned = true } + end - ActionDispatch::Reloader.prepare! + assert_deprecated do + ActionDispatch::Reloader.prepare! + end assert prepared - ActionDispatch::Reloader.cleanup! + assert_deprecated do + ActionDispatch::Reloader.cleanup! + end assert cleaned end @@ -51,8 +57,7 @@ class DispatcherTest < ActiveSupport::TestCase def dispatch(&block) ActionDispatch::Callbacks.new(block || DummyApp.new).call( - {'rack.input' => StringIO.new('')} + "rack.input" => StringIO.new("") ) end - end diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index dfcef14344..6dcd62572a 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -1,7 +1,7 @@ -require 'abstract_unit' -require 'openssl' -require 'active_support/key_generator' -require 'active_support/message_verifier' +require "abstract_unit" +require "openssl" +require "active_support/key_generator" +require "active_support/message_verifier" class CookieJarTest < ActiveSupport::TestCase attr_reader :request @@ -12,26 +12,26 @@ class CookieJarTest < ActiveSupport::TestCase def test_fetch x = Object.new - assert_not request.cookie_jar.key?('zzzzzz') - assert_equal x, request.cookie_jar.fetch('zzzzzz', x) - assert_not request.cookie_jar.key?('zzzzzz') + assert_not request.cookie_jar.key?("zzzzzz") + assert_equal x, request.cookie_jar.fetch("zzzzzz", x) + assert_not request.cookie_jar.key?("zzzzzz") end def test_fetch_exists x = Object.new - request.cookie_jar['foo'] = 'bar' - assert_equal 'bar', request.cookie_jar.fetch('foo', x) + request.cookie_jar["foo"] = "bar" + assert_equal "bar", request.cookie_jar.fetch("foo", x) end def test_fetch_block x = Object.new - assert_not request.cookie_jar.key?('zzzzzz') - assert_equal x, request.cookie_jar.fetch('zzzzzz') { x } + assert_not request.cookie_jar.key?("zzzzzz") + assert_equal x, request.cookie_jar.fetch("zzzzzz") { x } end def test_key_is_to_s - request.cookie_jar['foo'] = 'bar' - assert_equal 'bar', request.cookie_jar.fetch(:foo) + request.cookie_jar["foo"] = "bar" + assert_equal "bar", request.cookie_jar.fetch(:foo) end def test_fetch_type_error @@ -41,19 +41,19 @@ class CookieJarTest < ActiveSupport::TestCase end def test_each - request.cookie_jar['foo'] = :bar + request.cookie_jar["foo"] = :bar list = [] request.cookie_jar.each do |k,v| list << [k, v] end - assert_equal [['foo', :bar]], list + assert_equal [["foo", :bar]], list end def test_enumerable - request.cookie_jar['foo'] = :bar + request.cookie_jar["foo"] = :bar actual = request.cookie_jar.map { |k,v| [k.to_s, v.to_s] } - assert_equal [['foo', 'bar']], actual + assert_equal [["foo", "bar"]], actual end def test_key_methods @@ -68,7 +68,7 @@ class CookieJarTest < ActiveSupport::TestCase def test_write_doesnt_set_a_nil_header headers = {} request.cookie_jar.write(headers) - assert !headers.include?('Set-Cookie') + assert_not_includes headers, "Set-Cookie" end end @@ -100,7 +100,7 @@ class CookiesTest < ActionController::TestCase end def authenticate_for_fourteen_days_with_symbols - cookies[:user_name] = { :value => "david", :expires => Time.utc(2005, 10, 10,5) } + cookies[:user_name] = { value: "david", expires: Time.utc(2005, 10, 10,5) } head :ok end @@ -123,17 +123,17 @@ class CookiesTest < ActionController::TestCase alias delete_cookie logout def delete_cookie_with_path - cookies.delete("user_name", :path => '/beaten') + cookies.delete("user_name", path: "/beaten") head :ok end def authenticate_with_http_only - cookies["user_name"] = { :value => "david", :httponly => true } + cookies["user_name"] = { value: "david", httponly: true } head :ok end def authenticate_with_secure - cookies["user_name"] = { :value => "david", :secure => true } + cookies["user_name"] = { value: "david", secure: true } head :ok end @@ -153,7 +153,7 @@ class CookiesTest < ActionController::TestCase end def set_encrypted_cookie - cookies.encrypted[:foo] = 'bar' + cookies.encrypted[:foo] = "bar" head :ok end @@ -173,7 +173,7 @@ class CookiesTest < ActionController::TestCase end def set_wrapped_encrypted_cookie - cookies.encrypted[:foo] = JSONWrapper.new('bar') + cookies.encrypted[:foo] = JSONWrapper.new("bar") head :ok end @@ -183,12 +183,12 @@ class CookiesTest < ActionController::TestCase end def set_invalid_encrypted_cookie - cookies[:invalid_cookie] = 'invalid--9170e00a57cfc27083363b5c75b835e477bd90cf' + cookies[:invalid_cookie] = "invalid--9170e00a57cfc27083363b5c75b835e477bd90cf" head :ok end def raise_data_overflow - cookies.signed[:foo] = 'bye!' * 1024 + cookies.signed[:foo] = "bye!" * 1024 head :ok end @@ -205,47 +205,47 @@ class CookiesTest < ActionController::TestCase def delete_and_set_cookie cookies.delete :user_name - cookies[:user_name] = { :value => "david", :expires => Time.utc(2005, 10, 10,5) } + cookies[:user_name] = { value: "david", expires: Time.utc(2005, 10, 10,5) } head :ok end def set_cookie_with_domain - cookies[:user_name] = {:value => "rizwanreza", :domain => :all} + cookies[:user_name] = { value: "rizwanreza", domain: :all } head :ok end def set_cookie_with_domain_all_as_string - cookies[:user_name] = {:value => "rizwanreza", :domain => 'all'} + cookies[:user_name] = { value: "rizwanreza", domain: "all" } head :ok end def delete_cookie_with_domain - cookies.delete(:user_name, :domain => :all) + cookies.delete(:user_name, domain: :all) head :ok end def delete_cookie_with_domain_all_as_string - cookies.delete(:user_name, :domain => 'all') + cookies.delete(:user_name, domain: "all") head :ok end def set_cookie_with_domain_and_tld - cookies[:user_name] = {:value => "rizwanreza", :domain => :all, :tld_length => 2} + cookies[:user_name] = { value: "rizwanreza", domain: :all, tld_length: 2 } head :ok end def delete_cookie_with_domain_and_tld - cookies.delete(:user_name, :domain => :all, :tld_length => 2) + cookies.delete(:user_name, domain: :all, tld_length: 2) head :ok end def set_cookie_with_domains - cookies[:user_name] = {:value => "rizwanreza", :domain => %w(example1.com example2.com .example3.com)} + cookies[:user_name] = { value: "rizwanreza", domain: %w(example1.com example2.com .example3.com) } head :ok end def delete_cookie_with_domains - cookies.delete(:user_name, :domain => %w(example1.com example2.com .example3.com)) + cookies.delete(:user_name, domain: %w(example1.com example2.com .example3.com)) head :ok end @@ -255,7 +255,7 @@ class CookiesTest < ActionController::TestCase end def string_key - cookies['user_name'] = "dhh" + cookies["user_name"] = "dhh" head :ok end @@ -265,7 +265,7 @@ class CookiesTest < ActionController::TestCase end def string_key_mock - cookies['user_name'] = "david" if cookies['user_name'] == "andrew" + cookies["user_name"] = "david" if cookies["user_name"] == "andrew" head :ok end @@ -276,7 +276,7 @@ class CookiesTest < ActionController::TestCase tests TestController - SALT = 'b3c631c314c0bbca50c1b2843150fe33' + SALT = "b3c631c314c0bbca50c1b2843150fe33" def setup super @@ -293,57 +293,57 @@ class CookiesTest < ActionController::TestCase def test_setting_cookie get :authenticate assert_cookie_header "user_name=david; path=/" - assert_equal({"user_name" => "david"}, @response.cookies) + assert_equal({ "user_name" => "david" }, @response.cookies) end def test_setting_the_same_value_to_cookie - request.cookies[:user_name] = 'david' + request.cookies[:user_name] = "david" get :authenticate assert_predicate response.cookies, :empty? end def test_setting_the_same_value_to_permanent_cookie - request.cookies[:user_name] = 'Jamie' + request.cookies[:user_name] = "Jamie" get :set_permanent_cookie - assert_equal({'user_name' => 'Jamie'}, response.cookies) + assert_equal({ "user_name" => "Jamie" }, response.cookies) end def test_setting_with_escapable_characters get :set_with_with_escapable_characters assert_cookie_header "that+%26+guy=foo+%26+bar+%3D%3E+baz; path=/" - assert_equal({"that & guy" => "foo & bar => baz"}, @response.cookies) + assert_equal({ "that & guy" => "foo & bar => baz" }, @response.cookies) end def test_setting_cookie_for_fourteen_days get :authenticate_for_fourteen_days assert_cookie_header "user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000" - assert_equal({"user_name" => "david"}, @response.cookies) + assert_equal({ "user_name" => "david" }, @response.cookies) end def test_setting_cookie_for_fourteen_days_with_symbols get :authenticate_for_fourteen_days_with_symbols assert_cookie_header "user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000" - assert_equal({"user_name" => "david"}, @response.cookies) + assert_equal({ "user_name" => "david" }, @response.cookies) end def test_setting_cookie_with_http_only get :authenticate_with_http_only assert_cookie_header "user_name=david; path=/; HttpOnly" - assert_equal({"user_name" => "david"}, @response.cookies) + assert_equal({ "user_name" => "david" }, @response.cookies) end def test_setting_cookie_with_secure @request.env["HTTPS"] = "on" get :authenticate_with_secure assert_cookie_header "user_name=david; path=/; secure" - assert_equal({"user_name" => "david"}, @response.cookies) + assert_equal({ "user_name" => "david" }, @response.cookies) end def test_setting_cookie_with_secure_when_always_write_cookie_is_true old_cookie, @request.cookie_jar.always_write_cookie = @request.cookie_jar.always_write_cookie, true get :authenticate_with_secure assert_cookie_header "user_name=david; path=/; secure" - assert_equal({"user_name" => "david"}, @response.cookies) + assert_equal({ "user_name" => "david" }, @response.cookies) ensure @request.cookie_jar.always_write_cookie = old_cookie end @@ -351,14 +351,14 @@ class CookiesTest < ActionController::TestCase def test_not_setting_cookie_with_secure get :authenticate_with_secure assert_not_cookie_header "user_name=david; path=/; secure" - assert_not_equal({"user_name" => "david"}, @response.cookies) + assert_not_equal({ "user_name" => "david" }, @response.cookies) end def test_multiple_cookies get :set_multiple_cookies assert_equal 2, @response.cookies.size assert_cookie_header "user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000\nlogin=XJ-122; path=/" - assert_equal({"login" => "XJ-122", "user_name" => "david"}, @response.cookies) + assert_equal({ "login" => "XJ-122", "user_name" => "david" }, @response.cookies) end def test_setting_test_cookie @@ -366,14 +366,14 @@ class CookiesTest < ActionController::TestCase end def test_expiring_cookie - request.cookies[:user_name] = 'Joe' + request.cookies[:user_name] = "Joe" get :logout assert_cookie_header "user_name=; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000" - assert_equal({"user_name" => nil}, @response.cookies) + assert_equal({ "user_name" => nil }, @response.cookies) end def test_delete_cookie_with_path - request.cookies[:user_name] = 'Joe' + request.cookies[:user_name] = "Joe" get :delete_cookie_with_path assert_cookie_header "user_name=; path=/beaten; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000" end @@ -385,16 +385,16 @@ class CookiesTest < ActionController::TestCase end def test_deleted_cookie_predicate - cookies[:user_name] = 'Joe' + cookies[:user_name] = "Joe" cookies.delete("user_name") assert cookies.deleted?("user_name") assert_equal false, cookies.deleted?("another") end def test_deleted_cookie_predicate_with_mismatching_options - cookies[:user_name] = 'Joe' - cookies.delete("user_name", :path => "/path") - assert_equal false, cookies.deleted?("user_name", :path => "/different") + cookies[:user_name] = "Joe" + cookies.delete("user_name", path: "/path") + assert_equal false, cookies.deleted?("user_name", path: "/different") end def test_cookies_persist_throughout_request @@ -410,7 +410,7 @@ class CookiesTest < ActionController::TestCase def test_read_permanent_cookie get :set_permanent_cookie - assert_equal 'Jamie', @controller.send(:cookies).permanent[:user_name] + assert_equal "Jamie", @controller.send(:cookies).permanent[:user_name] end def test_signed_cookie_using_default_digest @@ -423,12 +423,12 @@ class CookiesTest < ActionController::TestCase signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"] secret = key_generator.generate_key(signed_cookie_salt) - verifier = ActiveSupport::MessageVerifier.new(secret, serializer: Marshal, digest: 'SHA1') + verifier = ActiveSupport::MessageVerifier.new(secret, serializer: Marshal, digest: "SHA1") assert_equal verifier.generate(45), cookies[:user_id] end def test_signed_cookie_using_custom_digest - @request.env["action_dispatch.cookies_digest"] = 'SHA256' + @request.env["action_dispatch.cookies_digest"] = "SHA256" get :set_signed_cookie cookies = @controller.send :cookies assert_not_equal 45, cookies[:user_id] @@ -438,7 +438,7 @@ class CookiesTest < ActionController::TestCase signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"] secret = key_generator.generate_key(signed_cookie_salt) - verifier = ActiveSupport::MessageVerifier.new(secret, serializer: Marshal, digest: 'SHA256') + verifier = ActiveSupport::MessageVerifier.new(secret, serializer: Marshal, digest: "SHA256") assert_equal verifier.generate(45), cookies[:user_id] end @@ -469,15 +469,15 @@ class CookiesTest < ActionController::TestCase @request.env["action_dispatch.cookies_serializer"] = :json get :set_wrapped_signed_cookie cookies = @controller.send :cookies - assert_not_equal 'wrapped: 45', cookies[:user_id] - assert_equal 'wrapped: 45', cookies.signed[:user_id] + assert_not_equal "wrapped: 45", cookies[:user_id] + assert_equal "wrapped: 45", cookies.signed[:user_id] end def test_signed_cookie_using_custom_serializer @request.env["action_dispatch.cookies_serializer"] = CustomSerializer get :set_signed_cookie assert_not_equal 45, cookies[:user_id] - assert_equal '45 was dumped and loaded', cookies.signed[:user_id] + assert_equal "45 was dumped and loaded", cookies.signed[:user_id] end def test_signed_cookie_using_hybrid_serializer_can_migrate_marshal_dumped_value_to_json @@ -497,7 +497,7 @@ class CookiesTest < ActionController::TestCase assert_equal 45, cookies.signed[:user_id] verifier = ActiveSupport::MessageVerifier.new(secret, serializer: JSON) - assert_equal 45, verifier.verify(@response.cookies['user_id']) + assert_equal 45, verifier.verify(@response.cookies["user_id"]) end def test_signed_cookie_using_hybrid_serializer_can_read_from_json_dumped_value @@ -520,70 +520,70 @@ class CookiesTest < ActionController::TestCase def test_accessing_nonexistent_signed_cookie_should_not_raise_an_invalid_signature get :set_signed_cookie - assert_nil @controller.send(:cookies).signed[:non_existant_attribute] + assert_nil @controller.send(:cookies).signed[:non_existent_attribute] end def test_encrypted_cookie_using_default_serializer get :set_encrypted_cookie cookies = @controller.send :cookies - assert_not_equal 'bar', cookies[:foo] + assert_not_equal "bar", cookies[:foo] assert_raise TypeError do cookies.signed[:foo] end - assert_equal 'bar', cookies.encrypted[:foo] + assert_equal "bar", cookies.encrypted[:foo] end def test_encrypted_cookie_using_marshal_serializer @request.env["action_dispatch.cookies_serializer"] = :marshal get :set_encrypted_cookie cookies = @controller.send :cookies - assert_not_equal 'bar', cookies[:foo] + assert_not_equal "bar", cookies[:foo] assert_raises TypeError do cookies.signed[:foo] end - assert_equal 'bar', cookies.encrypted[:foo] + assert_equal "bar", cookies.encrypted[:foo] end def test_encrypted_cookie_using_json_serializer @request.env["action_dispatch.cookies_serializer"] = :json get :set_encrypted_cookie cookies = @controller.send :cookies - assert_not_equal 'bar', cookies[:foo] + assert_not_equal "bar", cookies[:foo] assert_raises ::JSON::ParserError do cookies.signed[:foo] end - assert_equal 'bar', cookies.encrypted[:foo] + assert_equal "bar", cookies.encrypted[:foo] end def test_wrapped_encrypted_cookie_using_json_serializer @request.env["action_dispatch.cookies_serializer"] = :json get :set_wrapped_encrypted_cookie cookies = @controller.send :cookies - assert_not_equal 'wrapped: bar', cookies[:foo] + assert_not_equal "wrapped: bar", cookies[:foo] assert_raises ::JSON::ParserError do cookies.signed[:foo] end - assert_equal 'wrapped: bar', cookies.encrypted[:foo] + assert_equal "wrapped: bar", cookies.encrypted[:foo] end def test_encrypted_cookie_using_custom_serializer @request.env["action_dispatch.cookies_serializer"] = CustomSerializer get :set_encrypted_cookie - assert_not_equal 'bar', cookies.encrypted[:foo] - assert_equal 'bar was dumped and loaded', cookies.encrypted[:foo] + assert_not_equal "bar", cookies.encrypted[:foo] + assert_equal "bar was dumped and loaded", cookies.encrypted[:foo] end def test_encrypted_cookie_using_custom_digest - @request.env["action_dispatch.cookies_digest"] = 'SHA256' + @request.env["action_dispatch.cookies_digest"] = "SHA256" get :set_encrypted_cookie cookies = @controller.send :cookies - assert_not_equal 'bar', cookies[:foo] - assert_equal 'bar', cookies.encrypted[:foo] + assert_not_equal "bar", cookies[:foo] + assert_equal "bar", cookies.encrypted[:foo] sign_secret = @request.env["action_dispatch.key_generator"].generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"]) - sha1_verifier = ActiveSupport::MessageVerifier.new(sign_secret, serializer: ActiveSupport::MessageEncryptor::NullSerializer, digest: 'SHA1') - sha256_verifier = ActiveSupport::MessageVerifier.new(sign_secret, serializer: ActiveSupport::MessageEncryptor::NullSerializer, digest: 'SHA256') + sha1_verifier = ActiveSupport::MessageVerifier.new(sign_secret, serializer: ActiveSupport::MessageEncryptor::NullSerializer, digest: "SHA1") + sha256_verifier = ActiveSupport::MessageVerifier.new(sign_secret, serializer: ActiveSupport::MessageEncryptor::NullSerializer, digest: "SHA256") assert_raises(ActiveSupport::MessageVerifier::InvalidSignature) do sha1_verifier.verify(cookies[:foo]) @@ -603,7 +603,7 @@ class CookiesTest < ActionController::TestCase secret = key_generator.generate_key(encrypted_cookie_salt) sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt) - marshal_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: Marshal).encrypt_and_sign("bar") + marshal_value = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: Marshal).encrypt_and_sign("bar") @request.headers["Cookie"] = "foo=#{marshal_value}" get :get_encrypted_cookie @@ -612,7 +612,7 @@ class CookiesTest < ActionController::TestCase assert_not_equal "bar", cookies[:foo] assert_equal "bar", cookies.encrypted[:foo] - encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON) + encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON) assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"]) end @@ -624,7 +624,7 @@ class CookiesTest < ActionController::TestCase encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"] secret = key_generator.generate_key(encrypted_cookie_salt) sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt) - json_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON).encrypt_and_sign("bar") + json_value = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON).encrypt_and_sign("bar") @request.headers["Cookie"] = "foo=#{json_value}" get :get_encrypted_cookie @@ -636,9 +636,22 @@ class CookiesTest < ActionController::TestCase assert_nil @response.cookies["foo"] end + def test_compat_encrypted_cookie_using_64_byte_key + # Cookie generated with 64 bytes secret + message = ["566d4e75536d686e633246564e6b493062557079626c566d51574d30515430394c53315665564a694e4563786555744f57537454576b396a5a31566a626e52525054303d2d2d34663234333330623130623261306163363562316266323335396164666364613564643134623131"].pack("H*") + @request.headers["Cookie"] = "foo=#{message}" + + get :get_encrypted_cookie + + cookies = @controller.send :cookies + assert_not_equal "bar", cookies[:foo] + assert_equal "bar", cookies.encrypted[:foo] + assert_nil @response.cookies["foo"] + end + def test_accessing_nonexistent_encrypted_cookie_should_not_raise_invalid_message get :set_encrypted_cookie - assert_nil @controller.send(:cookies).encrypted[:non_existant_attribute] + assert_nil @controller.send(:cookies).encrypted[:non_existent_attribute] end def test_setting_invalid_encrypted_cookie_should_return_nil_when_accessing_it @@ -653,10 +666,10 @@ class CookiesTest < ActionController::TestCase end def test_delete_and_set_cookie - request.cookies[:user_name] = 'Joe' + request.cookies[:user_name] = "Joe" get :delete_and_set_cookie assert_cookie_header "user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000" - assert_equal({"user_name" => "david"}, @response.cookies) + assert_equal({ "user_name" => "david" }, @response.cookies) end def test_raise_data_overflow @@ -687,7 +700,7 @@ class CookiesTest < ActionController::TestCase get :set_signed_cookie } - assert_raise(ArgumentError, ''.inspect) { + assert_raise(ArgumentError, "".inspect) { @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("") get :set_signed_cookie } @@ -789,18 +802,18 @@ class CookiesTest < ActionController::TestCase @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee" @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9" - legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate('bar') + legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate("bar") @request.headers["Cookie"] = "foo=#{legacy_value}" get :get_encrypted_cookie - assert_equal 'bar', @controller.send(:cookies).encrypted[:foo] + assert_equal "bar", @controller.send(:cookies).encrypted[:foo] key_generator = @request.env["action_dispatch.key_generator"] secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"]) sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"]) - encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret) - assert_equal 'bar', encryptor.decrypt_and_verify(@response.cookies["foo"]) + encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret) + assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"]) end def test_legacy_json_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_cookie_jar_if_both_secret_token_and_secret_key_base_are_set @@ -828,18 +841,18 @@ class CookiesTest < ActionController::TestCase @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee" @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9" - legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate('bar') + legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate("bar") @request.headers["Cookie"] = "foo=#{legacy_value}" get :get_encrypted_cookie - assert_equal 'bar', @controller.send(:cookies).encrypted[:foo] + assert_equal "bar", @controller.send(:cookies).encrypted[:foo] key_generator = @request.env["action_dispatch.key_generator"] secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"]) sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"]) - encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON) - assert_equal 'bar', encryptor.decrypt_and_verify(@response.cookies["foo"]) + encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON) + assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"]) end def test_legacy_json_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_hybrid_jar_if_both_secret_token_and_secret_key_base_are_set @@ -867,18 +880,18 @@ class CookiesTest < ActionController::TestCase @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee" @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9" - legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate('bar') + legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate("bar") @request.headers["Cookie"] = "foo=#{legacy_value}" get :get_encrypted_cookie - assert_equal 'bar', @controller.send(:cookies).encrypted[:foo] + assert_equal "bar", @controller.send(:cookies).encrypted[:foo] key_generator = @request.env["action_dispatch.key_generator"] secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"]) sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"]) - encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON) - assert_equal 'bar', encryptor.decrypt_and_verify(@response.cookies["foo"]) + encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON) + assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"]) end def test_legacy_marshal_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_hybrid_jar_if_both_secret_token_and_secret_key_base_are_set @@ -906,18 +919,18 @@ class CookiesTest < ActionController::TestCase @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee" @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9" - legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate('bar') + legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate("bar") @request.headers["Cookie"] = "foo=#{legacy_value}" get :get_encrypted_cookie - assert_equal 'bar', @controller.send(:cookies).encrypted[:foo] + assert_equal "bar", @controller.send(:cookies).encrypted[:foo] key_generator = @request.env["action_dispatch.key_generator"] secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"]) sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"]) - encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON) - assert_equal 'bar', encryptor.decrypt_and_verify(@response.cookies["foo"]) + encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON) + assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"]) end def test_legacy_signed_cookie_is_treated_as_nil_by_signed_cookie_jar_if_tampered @@ -998,7 +1011,7 @@ class CookiesTest < ActionController::TestCase end def test_deleting_cookie_with_all_domain_option - request.cookies[:user_name] = 'Joe' + request.cookies[:user_name] = "Joe" get :delete_cookie_with_domain assert_response :success assert_cookie_header "user_name=; domain=.nextangle.com; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000" @@ -1032,7 +1045,7 @@ class CookiesTest < ActionController::TestCase end def test_deleting_cookie_with_all_domain_option_and_tld_length - request.cookies[:user_name] = 'Joe' + request.cookies[:user_name] = "Joe" get :delete_cookie_with_domain_and_tld assert_response :success assert_cookie_header "user_name=; domain=.nextangle.com; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000" @@ -1061,7 +1074,7 @@ class CookiesTest < ActionController::TestCase def test_deletings_cookie_with_several_preset_domains_using_one_of_these_domains @request.host = "example2.com" - request.cookies[:user_name] = 'Joe' + request.cookies[:user_name] = "Joe" get :delete_cookie_with_domains assert_response :success assert_cookie_header "user_name=; domain=example2.com; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000" @@ -1069,7 +1082,7 @@ class CookiesTest < ActionController::TestCase def test_deletings_cookie_with_several_preset_domains_using_other_domain @request.host = "other-domain.com" - request.cookies[:user_name] = 'Joe' + request.cookies[:user_name] = "Joe" get :delete_cookie_with_domains assert_response :success assert_cookie_header "user_name=; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000" @@ -1078,20 +1091,20 @@ class CookiesTest < ActionController::TestCase def test_cookies_hash_is_indifferent_access get :symbol_key assert_equal "david", cookies[:user_name] - assert_equal "david", cookies['user_name'] + assert_equal "david", cookies["user_name"] get :string_key assert_equal "dhh", cookies[:user_name] - assert_equal "dhh", cookies['user_name'] + assert_equal "dhh", cookies["user_name"] end def test_setting_request_cookies_is_indifferent_access cookies.clear cookies[:user_name] = "andrew" get :string_key_mock - assert_equal "david", cookies['user_name'] + assert_equal "david", cookies["user_name"] cookies.clear - cookies['user_name'] = "andrew" + cookies["user_name"] = "andrew" get :symbol_key_mock assert_equal "david", cookies[:user_name] end @@ -1102,11 +1115,11 @@ class CookiesTest < ActionController::TestCase assert_equal "david", cookies[:user_name] get :noop - assert !@response.headers.include?("Set-Cookie") + assert_not_includes @response.headers, "Set-Cookie" assert_equal "david", cookies[:user_name] get :noop - assert !@response.headers.include?("Set-Cookie") + assert_not_includes @response.headers, "Set-Cookie" assert_equal "david", cookies[:user_name] end @@ -1123,57 +1136,57 @@ class CookiesTest < ActionController::TestCase end def test_can_set_http_cookie_header - @request.env['HTTP_COOKIE'] = 'user_name=david' + @request.env["HTTP_COOKIE"] = "user_name=david" get :noop - assert_equal 'david', cookies['user_name'] - assert_equal 'david', cookies[:user_name] + assert_equal "david", cookies["user_name"] + assert_equal "david", cookies[:user_name] get :noop - assert_equal 'david', cookies['user_name'] - assert_equal 'david', cookies[:user_name] + assert_equal "david", cookies["user_name"] + assert_equal "david", cookies[:user_name] - @request.env['HTTP_COOKIE'] = 'user_name=andrew' + @request.env["HTTP_COOKIE"] = "user_name=andrew" get :noop - assert_equal 'andrew', cookies['user_name'] - assert_equal 'andrew', cookies[:user_name] + assert_equal "andrew", cookies["user_name"] + assert_equal "andrew", cookies[:user_name] end def test_can_set_request_cookies - @request.cookies['user_name'] = 'david' + @request.cookies["user_name"] = "david" get :noop - assert_equal 'david', cookies['user_name'] - assert_equal 'david', cookies[:user_name] + assert_equal "david", cookies["user_name"] + assert_equal "david", cookies[:user_name] get :noop - assert_equal 'david', cookies['user_name'] - assert_equal 'david', cookies[:user_name] + assert_equal "david", cookies["user_name"] + assert_equal "david", cookies[:user_name] - @request.cookies[:user_name] = 'andrew' + @request.cookies[:user_name] = "andrew" get :noop - assert_equal 'andrew', cookies['user_name'] - assert_equal 'andrew', cookies[:user_name] + assert_equal "andrew", cookies["user_name"] + assert_equal "andrew", cookies[:user_name] end def test_cookies_precedence_over_http_cookie - @request.env['HTTP_COOKIE'] = 'user_name=andrew' + @request.env["HTTP_COOKIE"] = "user_name=andrew" get :authenticate - assert_equal 'david', cookies['user_name'] - assert_equal 'david', cookies[:user_name] + assert_equal "david", cookies["user_name"] + assert_equal "david", cookies[:user_name] get :noop - assert_equal 'david', cookies['user_name'] - assert_equal 'david', cookies[:user_name] + assert_equal "david", cookies["user_name"] + assert_equal "david", cookies[:user_name] end def test_cookies_precedence_over_request_cookies - @request.cookies['user_name'] = 'andrew' + @request.cookies["user_name"] = "andrew" get :authenticate - assert_equal 'david', cookies['user_name'] - assert_equal 'david', cookies[:user_name] + assert_equal "david", cookies["user_name"] + assert_equal "david", cookies[:user_name] get :noop - assert_equal 'david', cookies['user_name'] - assert_equal 'david', cookies[:user_name] + assert_equal "david", cookies["user_name"] + assert_equal "david", cookies[:user_name] end private diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb index 159bf10545..2c5e09e283 100644 --- a/actionpack/test/dispatch/debug_exceptions_test.rb +++ b/actionpack/test/dispatch/debug_exceptions_test.rb @@ -1,7 +1,6 @@ -require 'abstract_unit' +require "abstract_unit" class DebugExceptionsTest < ActionDispatch::IntegrationTest - class Boomer attr_accessor :closed @@ -20,46 +19,46 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest end def method_that_raises - raise StandardError.new 'error in framework' + raise StandardError.new "error in framework" end def call(env) - env['action_dispatch.show_detailed_exceptions'] = @detailed + env["action_dispatch.show_detailed_exceptions"] = @detailed req = ActionDispatch::Request.new(env) case req.path - when "/pass" + when %r{/pass} [404, { "X-Cascade" => "pass" }, self] - when "/not_found" + when %r{/not_found} raise AbstractController::ActionNotFound - when "/runtime_error" + when %r{/runtime_error} raise RuntimeError - when "/method_not_allowed" + when %r{/method_not_allowed} raise ActionController::MethodNotAllowed - when "/unknown_http_method" + when %r{/unknown_http_method} raise ActionController::UnknownHttpMethod - when "/not_implemented" + when %r{/not_implemented} raise ActionController::NotImplemented - when "/unprocessable_entity" + when %r{/unprocessable_entity} raise ActionController::InvalidAuthenticityToken - when "/not_found_original_exception" + when %r{/not_found_original_exception} begin raise AbstractController::ActionNotFound.new rescue - raise ActionView::Template::Error.new('template') + raise ActionView::Template::Error.new("template") end - when "/missing_template" - raise ActionView::MissingTemplate.new(%w(foo), 'foo/index', %w(foo), false, 'mailer') - when "/bad_request" + when %r{/missing_template} + raise ActionView::MissingTemplate.new(%w(foo), "foo/index", %w(foo), false, "mailer") + when %r{/bad_request} raise ActionController::BadRequest - when "/missing_keys" + when %r{/missing_keys} raise ActionController::UrlGenerationError, "No route matches" - when "/parameter_missing" + when %r{/parameter_missing} raise ActionController::ParameterMissing, :missing_param_key - when "/original_syntax_error" - eval 'broke_syntax =' # `eval` need for raise native SyntaxError at runtime - when "/syntax_error_into_view" + when %r{/original_syntax_error} + eval "broke_syntax =" # `eval` need for raise native SyntaxError at runtime + when %r{/syntax_error_into_view} begin - eval 'broke_syntax =' + eval "broke_syntax =" rescue Exception template = ActionView::Template.new(File.read(__FILE__), __FILE__, @@ -67,7 +66,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest {}) raise ActionView::Template::Error.new(template) end - when "/framework_raises" + when %r{/framework_raises} method_that_raises else raise "puke!" @@ -75,96 +74,89 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest end end - class BoomerAPI < Boomer - def call(env) - env['action_dispatch.show_detailed_exceptions'] = @detailed - raise "puke!" - end - end - RoutesApp = Struct.new(:routes).new(SharedTestRoutes) ProductionApp = ActionDispatch::DebugExceptions.new(Boomer.new(false), RoutesApp) DevelopmentApp = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp) - test 'skip diagnosis if not showing detailed exceptions' do + test "skip diagnosis if not showing detailed exceptions" do @app = ProductionApp assert_raise RuntimeError do - get "/", headers: { 'action_dispatch.show_exceptions' => true } + get "/", headers: { "action_dispatch.show_exceptions" => true } end end - test 'skip diagnosis if not showing exceptions' do + test "skip diagnosis if not showing exceptions" do @app = DevelopmentApp assert_raise RuntimeError do - get "/", headers: { 'action_dispatch.show_exceptions' => false } + get "/", headers: { "action_dispatch.show_exceptions" => false } end end - test 'raise an exception on cascade pass' do + test "raise an exception on cascade pass" do @app = ProductionApp assert_raise ActionController::RoutingError do - get "/pass", headers: { 'action_dispatch.show_exceptions' => true } + get "/pass", headers: { "action_dispatch.show_exceptions" => true } end end - test 'closes the response body on cascade pass' do + test "closes the response body on cascade pass" do boomer = Boomer.new(false) @app = ActionDispatch::DebugExceptions.new(boomer) assert_raise ActionController::RoutingError do - get "/pass", headers: { 'action_dispatch.show_exceptions' => true } + get "/pass", headers: { "action_dispatch.show_exceptions" => true } end assert boomer.closed, "Expected to close the response body" end - test 'displays routes in a table when a RoutingError occurs' do + test "displays routes in a table when a RoutingError occurs" do @app = DevelopmentApp - get "/pass", headers: { 'action_dispatch.show_exceptions' => true } + get "/pass", headers: { "action_dispatch.show_exceptions" => true } routing_table = body[/route_table.*<.table>/m] - assert_match '/:controller(/:action)(.:format)', routing_table - assert_match ':controller#:action', routing_table - assert_no_match '<|>', routing_table, "there should not be escaped html in the output" + assert_match "/:controller(/:action)(.:format)", routing_table + assert_match ":controller#:action", routing_table + assert_no_match "<|>", routing_table, "there should not be escaped html in the output" end - test 'displays request and response info when a RoutingError occurs' do + test "displays request and response info when a RoutingError occurs" do @app = DevelopmentApp - get "/pass", headers: { 'action_dispatch.show_exceptions' => true } + get "/pass", headers: { "action_dispatch.show_exceptions" => true } - assert_select 'h2', /Request/ - assert_select 'h2', /Response/ + assert_select "h2", /Request/ + assert_select "h2", /Response/ end test "rescue with diagnostics message" do @app = DevelopmentApp - get "/", headers: { 'action_dispatch.show_exceptions' => true } + get "/", headers: { "action_dispatch.show_exceptions" => true } assert_response 500 assert_match(/puke/, body) - get "/not_found", headers: { 'action_dispatch.show_exceptions' => true } + get "/not_found", headers: { "action_dispatch.show_exceptions" => true } assert_response 404 assert_match(/#{AbstractController::ActionNotFound.name}/, body) - get "/method_not_allowed", headers: { 'action_dispatch.show_exceptions' => true } + get "/method_not_allowed", headers: { "action_dispatch.show_exceptions" => true } assert_response 405 assert_match(/ActionController::MethodNotAllowed/, body) - get "/unknown_http_method", headers: { 'action_dispatch.show_exceptions' => true } + get "/unknown_http_method", headers: { "action_dispatch.show_exceptions" => true } assert_response 405 assert_match(/ActionController::UnknownHttpMethod/, body) - get "/bad_request", headers: { 'action_dispatch.show_exceptions' => true } + get "/bad_request", headers: { "action_dispatch.show_exceptions" => true } assert_response 400 assert_match(/ActionController::BadRequest/, body) - get "/parameter_missing", headers: { 'action_dispatch.show_exceptions' => true } + get "/parameter_missing", headers: { "action_dispatch.show_exceptions" => true } assert_response 400 assert_match(/ActionController::ParameterMissing/, body) end test "rescue with text error for xhr request" do @app = DevelopmentApp - xhr_request_env = {'action_dispatch.show_exceptions' => true, 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest'} + xhr_request_env = { "action_dispatch.show_exceptions" => true, "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest" } get "/", headers: xhr_request_env assert_response 500 @@ -173,12 +165,12 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest assert_equal "text/plain", response.content_type assert_match(/RuntimeError\npuke/, body) - Rails.stub :root, Pathname.new('.') do + Rails.stub :root, Pathname.new(".") do get "/", headers: xhr_request_env assert_response 500 - assert_match 'Extracted source (around line #', body - assert_select 'pre', { count: 0 }, body + assert_match "Extracted source (around line #", body + assert_select "pre", { count: 0 }, body end get "/not_found", headers: xhr_request_env @@ -212,73 +204,91 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest assert_match(/ActionController::ParameterMissing/, body) end - test "rescue with json error for API request" do + test "rescue with JSON error for JSON API request" do @app = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp, :api) - get "/", headers: { 'action_dispatch.show_exceptions' => true } + get "/", headers: { "action_dispatch.show_exceptions" => true }, as: :json assert_response 500 assert_no_match(/<header>/, body) assert_no_match(/<body>/, body) assert_equal "application/json", response.content_type assert_match(/RuntimeError: puke/, body) - get "/not_found", headers: { 'action_dispatch.show_exceptions' => true } + get "/not_found", headers: { "action_dispatch.show_exceptions" => true }, as: :json assert_response 404 assert_no_match(/<body>/, body) assert_equal "application/json", response.content_type assert_match(/#{AbstractController::ActionNotFound.name}/, body) - get "/method_not_allowed", headers: { 'action_dispatch.show_exceptions' => true } + get "/method_not_allowed", headers: { "action_dispatch.show_exceptions" => true }, as: :json assert_response 405 assert_no_match(/<body>/, body) assert_equal "application/json", response.content_type assert_match(/ActionController::MethodNotAllowed/, body) - get "/unknown_http_method", headers: { 'action_dispatch.show_exceptions' => true } + get "/unknown_http_method", headers: { "action_dispatch.show_exceptions" => true }, as: :json assert_response 405 assert_no_match(/<body>/, body) assert_equal "application/json", response.content_type assert_match(/ActionController::UnknownHttpMethod/, body) - get "/bad_request", headers: { 'action_dispatch.show_exceptions' => true } + get "/bad_request", headers: { "action_dispatch.show_exceptions" => true }, as: :json assert_response 400 assert_no_match(/<body>/, body) assert_equal "application/json", response.content_type assert_match(/ActionController::BadRequest/, body) - get "/parameter_missing", headers: { 'action_dispatch.show_exceptions' => true } + get "/parameter_missing", headers: { "action_dispatch.show_exceptions" => true }, as: :json assert_response 400 assert_no_match(/<body>/, body) assert_equal "application/json", response.content_type assert_match(/ActionController::ParameterMissing/, body) end - test "rescue with json on API request returns only allowed formats or json as a fallback" do + test "rescue with HTML format for HTML API request" do @app = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp, :api) - get "/index.json", headers: { 'action_dispatch.show_exceptions' => true } + get "/index.html", headers: { "action_dispatch.show_exceptions" => true } assert_response 500 - assert_equal "application/json", response.content_type - assert_match(/RuntimeError: puke/, body) + assert_match(/<header>/, body) + assert_match(/<body>/, body) + assert_equal "text/html", response.content_type + assert_match(/puke/, body) + end - get "/index.html", headers: { 'action_dispatch.show_exceptions' => true } - assert_response 500 - assert_no_match(/<header>/, body) - assert_no_match(/<body>/, body) - assert_equal "application/json", response.content_type - assert_match(/RuntimeError: puke/, body) + test "rescue with XML format for XML API requests" do + @app = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp, :api) - get "/index.xml", headers: { 'action_dispatch.show_exceptions' => true } + get "/index.xml", headers: { "action_dispatch.show_exceptions" => true } assert_response 500 assert_equal "application/xml", response.content_type assert_match(/RuntimeError: puke/, body) end + test "rescue with JSON format as fallback if API request format is not supported" do + begin + Mime::Type.register "text/wibble", :wibble + + ActionDispatch::IntegrationTest.register_encoder(:wibble, + param_encoder: -> params { params }) + + @app = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp, :api) + + get "/index", headers: { "action_dispatch.show_exceptions" => true }, as: :wibble + assert_response 500 + assert_equal "application/json", response.content_type + assert_match(/RuntimeError: puke/, body) + + ensure + Mime::Type.unregister :wibble + end + end + test "does not show filtered parameters" do @app = DevelopmentApp - get "/", params: { "foo"=>"bar" }, headers: { 'action_dispatch.show_exceptions' => true, - 'action_dispatch.parameter_filter' => [:foo] } + get "/", params: { "foo"=>"bar" }, headers: { "action_dispatch.show_exceptions" => true, + "action_dispatch.parameter_filter" => [:foo] } assert_response 500 assert_match(""foo"=>"[FILTERED]"", body) end @@ -286,7 +296,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest test "show registered original exception for wrapped exceptions" do @app = DevelopmentApp - get "/not_found_original_exception", headers: { 'action_dispatch.show_exceptions' => true } + get "/not_found_original_exception", headers: { "action_dispatch.show_exceptions" => true } assert_response 404 assert_match(/AbstractController::ActionNotFound/, body) end @@ -294,7 +304,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest test "named urls missing keys raise 500 level error" do @app = DevelopmentApp - get "/missing_keys", headers: { 'action_dispatch.show_exceptions' => true } + get "/missing_keys", headers: { "action_dispatch.show_exceptions" => true } assert_response 500 assert_match(/ActionController::UrlGenerationError/, body) @@ -303,11 +313,11 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest test "show the controller name in the diagnostics template when controller name is present" do @app = DevelopmentApp get("/runtime_error", headers: { - 'action_dispatch.show_exceptions' => true, - 'action_dispatch.request.parameters' => { - 'action' => 'show', - 'id' => 'unknown', - 'controller' => 'featured_tile' + "action_dispatch.show_exceptions" => true, + "action_dispatch.request.parameters" => { + "action" => "show", + "id" => "unknown", + "controller" => "featured_tile" } }) assert_response 500 @@ -318,18 +328,18 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest @app = DevelopmentApp params = { - 'id' => 'unknown', - 'someparam' => { - 'foo' => 'bar', - 'abc' => 'goo' + "id" => "unknown", + "someparam" => { + "foo" => "bar", + "abc" => "goo" } } get("/runtime_error", headers: { - 'action_dispatch.show_exceptions' => true, - 'action_dispatch.request.parameters' => { - 'action' => 'show', - 'controller' => 'featured_tile' + "action_dispatch.show_exceptions" => true, + "action_dispatch.request.parameters" => { + "action" => "show", + "controller" => "featured_tile" }.merge(params) }) assert_response 500 @@ -340,52 +350,75 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest test "sets the HTTP charset parameter" do @app = DevelopmentApp - get "/", headers: { 'action_dispatch.show_exceptions' => true } + get "/", headers: { "action_dispatch.show_exceptions" => true } assert_equal "text/html; charset=utf-8", response.headers["Content-Type"] end - test 'uses logger from env' do + test "uses logger from env" do @app = DevelopmentApp output = StringIO.new - get "/", headers: { 'action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => Logger.new(output) } + get "/", headers: { "action_dispatch.show_exceptions" => true, "action_dispatch.logger" => Logger.new(output) } assert_match(/puke/, output.rewind && output.read) end - test 'uses backtrace cleaner from env' do + test "logs only what is necessary" do + @app = DevelopmentApp + io = StringIO.new + logger = ActiveSupport::Logger.new(io) + + _old, ActionView::Base.logger = ActionView::Base.logger, logger + begin + get "/", headers: { "action_dispatch.show_exceptions" => true, "action_dispatch.logger" => logger } + ensure + ActionView::Base.logger = _old + end + + output = io.rewind && io.read + lines = output.lines + + # Other than the first three... + assert_equal([" \n", "RuntimeError (puke!):\n", " \n"], lines.slice!(0, 3)) + lines.each do |line| + # .. all the remaining lines should be from the backtrace + assert_match(/:\d+:in /, line) + end + end + + test "uses backtrace cleaner from env" do @app = DevelopmentApp backtrace_cleaner = ActiveSupport::BacktraceCleaner.new - backtrace_cleaner.stub :clean, ['passed backtrace cleaner'] do - get "/", headers: { 'action_dispatch.show_exceptions' => true, 'action_dispatch.backtrace_cleaner' => backtrace_cleaner } + backtrace_cleaner.stub :clean, ["passed backtrace cleaner"] do + get "/", headers: { "action_dispatch.show_exceptions" => true, "action_dispatch.backtrace_cleaner" => backtrace_cleaner } assert_match(/passed backtrace cleaner/, body) end end - test 'logs exception backtrace when all lines silenced' do + test "logs exception backtrace when all lines silenced" do output = StringIO.new backtrace_cleaner = ActiveSupport::BacktraceCleaner.new backtrace_cleaner.add_silencer { true } - env = {'action_dispatch.show_exceptions' => true, - 'action_dispatch.logger' => Logger.new(output), - 'action_dispatch.backtrace_cleaner' => backtrace_cleaner} + env = { "action_dispatch.show_exceptions" => true, + "action_dispatch.logger" => Logger.new(output), + "action_dispatch.backtrace_cleaner" => backtrace_cleaner } get "/", headers: env assert_operator((output.rewind && output.read).lines.count, :>, 10) end - test 'display backtrace when error type is SyntaxError' do + test "display backtrace when error type is SyntaxError" do @app = DevelopmentApp - get '/original_syntax_error', headers: { 'action_dispatch.backtrace_cleaner' => ActiveSupport::BacktraceCleaner.new } + get "/original_syntax_error", headers: { "action_dispatch.backtrace_cleaner" => ActiveSupport::BacktraceCleaner.new } assert_response 500 - assert_select '#Application-Trace' do - assert_select 'pre code', /syntax error, unexpected/ + assert_select "#Application-Trace" do + assert_select "pre code", /syntax error, unexpected/ end end - test 'display backtrace on template missing errors' do + test "display backtrace on template missing errors" do @app = DevelopmentApp get "/missing_template" @@ -394,56 +427,56 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest assert_select "#container h2", /^Missing template/ - assert_select '#Application-Trace' - assert_select '#Framework-Trace' - assert_select '#Full-Trace' + assert_select "#Application-Trace" + assert_select "#Framework-Trace" + assert_select "#Full-Trace" - assert_select 'h2', /Request/ + assert_select "h2", /Request/ end - test 'display backtrace when error type is SyntaxError wrapped by ActionView::Template::Error' do + test "display backtrace when error type is SyntaxError wrapped by ActionView::Template::Error" do @app = DevelopmentApp - get '/syntax_error_into_view', headers: { 'action_dispatch.backtrace_cleaner' => ActiveSupport::BacktraceCleaner.new } + get "/syntax_error_into_view", headers: { "action_dispatch.backtrace_cleaner" => ActiveSupport::BacktraceCleaner.new } assert_response 500 - assert_select '#Application-Trace' do - assert_select 'pre code', /syntax error, unexpected/ + assert_select "#Application-Trace" do + assert_select "pre code", /syntax error, unexpected/ end end - test 'debug exceptions app shows user code that caused the error in source view' do + test "debug exceptions app shows user code that caused the error in source view" do @app = DevelopmentApp - Rails.stub :root, Pathname.new('.') do + Rails.stub :root, Pathname.new(".") do cleaner = ActiveSupport::BacktraceCleaner.new.tap do |bc| bc.add_silencer { |line| line =~ /method_that_raises/ } bc.add_silencer { |line| line !~ %r{test/dispatch/debug_exceptions_test.rb} } end - get '/framework_raises', headers: { 'action_dispatch.backtrace_cleaner' => cleaner } + get "/framework_raises", headers: { "action_dispatch.backtrace_cleaner" => cleaner } # Assert correct error assert_response 500 - assert_select 'h2', /error in framework/ + assert_select "h2", /error in framework/ # assert source view line is the call to method_that_raises - assert_select 'div.source:not(.hidden)' do - assert_select 'pre .line.active', /method_that_raises/ + assert_select "div.source:not(.hidden)" do + assert_select "pre .line.active", /method_that_raises/ end # assert first source view (hidden) that throws the error - assert_select 'div.source:first' do - assert_select 'pre .line.active', /raise StandardError\.new/ + assert_select "div.source:first" do + assert_select "pre .line.active", /raise StandardError\.new/ end # assert application trace refers to line that calls method_that_raises is first - assert_select '#Application-Trace' do - assert_select 'pre code a:first', %r{test/dispatch/debug_exceptions_test\.rb:\d+:in `call} + assert_select "#Application-Trace" do + assert_select "pre code a:first", %r{test/dispatch/debug_exceptions_test\.rb:\d+:in `call} end # assert framework trace that threw the error is first - assert_select '#Framework-Trace' do - assert_select 'pre code a:first', /method_that_raises/ + assert_select "#Framework-Trace" do + assert_select "pre code a:first", /method_that_raises/ end end end diff --git a/actionpack/test/dispatch/exception_wrapper_test.rb b/actionpack/test/dispatch/exception_wrapper_test.rb index dfbb91c0ca..316661a116 100644 --- a/actionpack/test/dispatch/exception_wrapper_test.rb +++ b/actionpack/test/dispatch/exception_wrapper_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module ActionDispatch class ExceptionWrapperTest < ActionDispatch::IntegrationTest @@ -21,43 +21,49 @@ module ActionDispatch @cleaner.add_silencer { |line| line !~ /^lib/ } end - test '#source_extracts fetches source fragments for every backtrace entry' do + test "#source_extracts fetches source fragments for every backtrace entry" do exception = TestError.new("lib/file.rb:42:in `index'") wrapper = ExceptionWrapper.new(nil, exception) - assert_called_with(wrapper, :source_fragment, ['lib/file.rb', 42], returns: 'foo') do - assert_equal [ code: 'foo', line_number: 42 ], wrapper.source_extracts + assert_called_with(wrapper, :source_fragment, ["lib/file.rb", 42], returns: "foo") do + assert_equal [ code: "foo", line_number: 42 ], wrapper.source_extracts end end - test '#source_extracts works with Windows paths' do + test "#source_extracts works with Windows paths" do exc = TestError.new("c:/path/to/rails/app/controller.rb:27:in 'index':") wrapper = ExceptionWrapper.new(nil, exc) - assert_called_with(wrapper, :source_fragment, ['c:/path/to/rails/app/controller.rb', 27], returns: 'nothing') do - assert_equal [ code: 'nothing', line_number: 27 ], wrapper.source_extracts + assert_called_with(wrapper, :source_fragment, ["c:/path/to/rails/app/controller.rb", 27], returns: "nothing") do + assert_equal [ code: "nothing", line_number: 27 ], wrapper.source_extracts end end - test '#source_extracts works with non standard backtrace' do - exc = TestError.new('invalid') + test "#source_extracts works with non standard backtrace" do + exc = TestError.new("invalid") wrapper = ExceptionWrapper.new(nil, exc) - assert_called_with(wrapper, :source_fragment, ['invalid', 0], returns: 'nothing') do - assert_equal [ code: 'nothing', line_number: 0 ], wrapper.source_extracts + assert_called_with(wrapper, :source_fragment, ["invalid", 0], returns: "nothing") do + assert_equal [ code: "nothing", line_number: 0 ], wrapper.source_extracts end end - test '#application_trace returns traces only from the application' do + test "#application_trace returns traces only from the application" do exception = TestError.new(caller.prepend("lib/file.rb:42:in `index'")) wrapper = ExceptionWrapper.new(@cleaner, exception) assert_equal [ "lib/file.rb:42:in `index'" ], wrapper.application_trace end - test '#application_trace cannot be nil' do + test "#status_code returns 400 for Rack::Utils::ParameterTypeError" do + exception = Rack::Utils::ParameterTypeError.new + wrapper = ExceptionWrapper.new(@cleaner, exception) + assert_equal 400, wrapper.status_code + end + + test "#application_trace cannot be nil" do nil_backtrace_wrapper = ExceptionWrapper.new(@cleaner, BadlyDefinedError.new) nil_cleaner_wrapper = ExceptionWrapper.new(nil, BadlyDefinedError.new) @@ -65,14 +71,14 @@ module ActionDispatch assert_equal [], nil_cleaner_wrapper.application_trace end - test '#framework_trace returns traces outside the application' do + test "#framework_trace returns traces outside the application" do exception = TestError.new(caller.prepend("lib/file.rb:42:in `index'")) wrapper = ExceptionWrapper.new(@cleaner, exception) assert_equal caller, wrapper.framework_trace end - test '#framework_trace cannot be nil' do + test "#framework_trace cannot be nil" do nil_backtrace_wrapper = ExceptionWrapper.new(@cleaner, BadlyDefinedError.new) nil_cleaner_wrapper = ExceptionWrapper.new(nil, BadlyDefinedError.new) @@ -80,14 +86,14 @@ module ActionDispatch assert_equal [], nil_cleaner_wrapper.framework_trace end - test '#full_trace returns application and framework traces' do + test "#full_trace returns application and framework traces" do exception = TestError.new(caller.prepend("lib/file.rb:42:in `index'")) wrapper = ExceptionWrapper.new(@cleaner, exception) assert_equal exception.backtrace, wrapper.full_trace end - test '#full_trace cannot be nil' do + test "#full_trace cannot be nil" do nil_backtrace_wrapper = ExceptionWrapper.new(@cleaner, BadlyDefinedError.new) nil_cleaner_wrapper = ExceptionWrapper.new(nil, BadlyDefinedError.new) @@ -95,14 +101,14 @@ module ActionDispatch assert_equal [], nil_cleaner_wrapper.full_trace end - test '#traces returns every trace by category enumerated with an index' do + test "#traces returns every trace by category enumerated with an index" do exception = TestError.new("lib/file.rb:42:in `index'", "/gems/rack.rb:43:in `index'") wrapper = ExceptionWrapper.new(@cleaner, exception) assert_equal({ - 'Application Trace' => [ id: 0, trace: "lib/file.rb:42:in `index'" ], - 'Framework Trace' => [ id: 1, trace: "/gems/rack.rb:43:in `index'" ], - 'Full Trace' => [ + "Application Trace" => [ id: 0, trace: "lib/file.rb:42:in `index'" ], + "Framework Trace" => [ id: 1, trace: "/gems/rack.rb:43:in `index'" ], + "Full Trace" => [ { id: 0, trace: "lib/file.rb:42:in `index'" }, { id: 1, trace: "/gems/rack.rb:43:in `index'" } ] diff --git a/actionpack/test/dispatch/executor_test.rb b/actionpack/test/dispatch/executor_test.rb new file mode 100644 index 0000000000..0b4e0849c3 --- /dev/null +++ b/actionpack/test/dispatch/executor_test.rb @@ -0,0 +1,134 @@ +require "abstract_unit" + +class ExecutorTest < ActiveSupport::TestCase + class MyBody < Array + def initialize(&block) + @on_close = block + end + + def foo + "foo" + end + + def bar + "bar" + end + + def close + @on_close.call if @on_close + end + end + + def test_returned_body_object_always_responds_to_close + body = call_and_return_body + assert_respond_to body, :close + end + + def test_returned_body_object_always_responds_to_close_even_if_called_twice + body = call_and_return_body + assert_respond_to body, :close + body.close + + body = call_and_return_body + assert_respond_to body, :close + body.close + end + + def test_returned_body_object_behaves_like_underlying_object + body = call_and_return_body do + b = MyBody.new + b << "hello" + b << "world" + [200, { "Content-Type" => "text/html" }, b] + end + assert_equal 2, body.size + assert_equal "hello", body[0] + assert_equal "world", body[1] + assert_equal "foo", body.foo + assert_equal "bar", body.bar + end + + def test_it_calls_close_on_underlying_object_when_close_is_called_on_body + close_called = false + body = call_and_return_body do + b = MyBody.new do + close_called = true + end + [200, { "Content-Type" => "text/html" }, b] + end + body.close + assert close_called + end + + def test_returned_body_object_responds_to_all_methods_supported_by_underlying_object + body = call_and_return_body do + [200, { "Content-Type" => "text/html" }, MyBody.new] + end + assert_respond_to body, :size + assert_respond_to body, :each + assert_respond_to body, :foo + assert_respond_to body, :bar + end + + def test_run_callbacks_are_called_before_close + running = false + executor.to_run { running = true } + + body = call_and_return_body + assert running + + running = false + body.close + assert !running + end + + def test_complete_callbacks_are_called_on_close + completed = false + executor.to_complete { completed = true } + + body = call_and_return_body + assert !completed + + body.close + assert completed + end + + def test_complete_callbacks_are_called_on_exceptions + completed = false + executor.to_complete { completed = true } + + begin + call_and_return_body do + raise "error" + end + rescue + end + + assert completed + end + + def test_callbacks_execute_in_shared_context + result = false + executor.to_run { @in_shared_context = true } + executor.to_complete { result = @in_shared_context } + + call_and_return_body.close + assert result + assert !defined?(@in_shared_context) # it's not in the test itself + end + + private + def call_and_return_body(&block) + app = middleware(block || proc { [200, {}, "response"] }) + _, _, body = app.call("rack.input" => StringIO.new("")) + body + end + + def middleware(inner_app) + ActionDispatch::Executor.new(inner_app, executor) + end + + def executor + @executor ||= Class.new(ActiveSupport::Executor) + end +end diff --git a/actionpack/test/dispatch/header_test.rb b/actionpack/test/dispatch/header_test.rb index 7f1ef121b7..6febd5cb68 100644 --- a/actionpack/test/dispatch/header_test.rb +++ b/actionpack/test/dispatch/header_test.rb @@ -18,14 +18,14 @@ class HeaderTest < ActiveSupport::TestCase "HTTP_REFERER" => "/some/page", "Host" => "http://test.com") - assert_equal({"Content-Type" => "application/json", + assert_equal({ "Content-Type" => "application/json", "HTTP_REFERER" => "/some/page", - "Host" => "http://test.com"}, headers.env) + "Host" => "http://test.com" }, headers.env) end test "#env returns the headers as env variables" do - assert_equal({"CONTENT_TYPE" => "text/plain", - "HTTP_REFERER" => "/some/page"}, @headers.env) + assert_equal({ "CONTENT_TYPE" => "text/plain", + "HTTP_REFERER" => "/some/page" }, @headers.env) end test "#each iterates through the env variables" do @@ -44,20 +44,20 @@ class HeaderTest < ActiveSupport::TestCase test "add to multivalued headers" do # Sets header when not present - @headers.add 'Foo', '1' - assert_equal '1', @headers['Foo'] + @headers.add "Foo", "1" + assert_equal "1", @headers["Foo"] # Ignores nil values - @headers.add 'Foo', nil - assert_equal '1', @headers['Foo'] + @headers.add "Foo", nil + assert_equal "1", @headers["Foo"] # Converts value to string - @headers.add 'Foo', 1 - assert_equal '1,1', @headers['Foo'] + @headers.add "Foo", 1 + assert_equal "1,1", @headers["Foo"] # Case-insensitive - @headers.add 'fOo', 2 - assert_equal '1,1,2', @headers['foO'] + @headers.add "fOo", 2 + assert_equal "1,1,2", @headers["foO"] end test "headers can contain numbers" do @@ -76,9 +76,9 @@ class HeaderTest < ActiveSupport::TestCase test "key?" do assert @headers.key?("CONTENT_TYPE") - assert @headers.include?("CONTENT_TYPE") + assert_includes @headers, "CONTENT_TYPE" assert @headers.key?("Content-Type") - assert @headers.include?("Content-Type") + assert_includes @headers, "Content-Type" end test "fetch with block" do @@ -105,28 +105,28 @@ class HeaderTest < ActiveSupport::TestCase test "#merge! headers with mutation" do @headers.merge!("Host" => "http://example.test", "Content-Type" => "text/html") - assert_equal({"HTTP_HOST" => "http://example.test", + assert_equal({ "HTTP_HOST" => "http://example.test", "CONTENT_TYPE" => "text/html", - "HTTP_REFERER" => "/some/page"}, @headers.env) + "HTTP_REFERER" => "/some/page" }, @headers.env) end test "#merge! env with mutation" do @headers.merge!("HTTP_HOST" => "http://first.com", "CONTENT_TYPE" => "text/html") - assert_equal({"HTTP_HOST" => "http://first.com", + assert_equal({ "HTTP_HOST" => "http://first.com", "CONTENT_TYPE" => "text/html", - "HTTP_REFERER" => "/some/page"}, @headers.env) + "HTTP_REFERER" => "/some/page" }, @headers.env) end test "merge without mutation" do combined = @headers.merge("HTTP_HOST" => "http://example.com", "CONTENT_TYPE" => "text/html") - assert_equal({"HTTP_HOST" => "http://example.com", + assert_equal({ "HTTP_HOST" => "http://example.com", "CONTENT_TYPE" => "text/html", - "HTTP_REFERER" => "/some/page"}, combined.env) + "HTTP_REFERER" => "/some/page" }, combined.env) - assert_equal({"CONTENT_TYPE" => "text/plain", - "HTTP_REFERER" => "/some/page"}, @headers.env) + assert_equal({ "CONTENT_TYPE" => "text/plain", + "HTTP_REFERER" => "/some/page" }, @headers.env) end test "env variables with . are not modified" do @@ -151,11 +151,17 @@ class HeaderTest < ActiveSupport::TestCase end test "headers directly modifies the passed environment" do - env = {"HTTP_REFERER" => "/"} + env = { "HTTP_REFERER" => "/" } headers = make_headers(env) - headers['Referer'] = "http://example.com/" + headers["Referer"] = "http://example.com/" headers.merge! "CONTENT_TYPE" => "text/plain" - assert_equal({"HTTP_REFERER"=>"http://example.com/", - "CONTENT_TYPE"=>"text/plain"}, env) + assert_equal({ "HTTP_REFERER"=>"http://example.com/", + "CONTENT_TYPE"=>"text/plain" }, env) + end + + test "fetch exception" do + assert_raises KeyError do + @headers.fetch(:some_key_that_doesnt_exist) + end end end diff --git a/actionpack/test/dispatch/live_response_test.rb b/actionpack/test/dispatch/live_response_test.rb index e4475f4233..d10fc7d575 100644 --- a/actionpack/test/dispatch/live_response_test.rb +++ b/actionpack/test/dispatch/live_response_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'concurrent/atomic/count_down_latch' +require "abstract_unit" +require "concurrent/atomic/count_down_latch" module ActionController module Live @@ -10,7 +10,7 @@ module ActionController end def test_header_merge - header = @response.header.merge('Foo' => 'Bar') + header = @response.header.merge("Foo" => "Bar") assert_kind_of(ActionController::Live::Response::Header, header) assert_not_equal header, @response.header end @@ -18,7 +18,7 @@ module ActionController def test_initialize_with_default_headers r = Class.new(Live::Response) do def self.default_headers - { 'omg' => 'g' } + { "omg" => "g" } end end @@ -30,42 +30,42 @@ module ActionController latch = Concurrent::CountDownLatch.new t = Thread.new { - @response.stream.write 'foo' + @response.stream.write "foo" latch.wait @response.stream.close } @response.await_commit @response.each do |part| - assert_equal 'foo', part + assert_equal "foo", part latch.count_down end assert t.join end def test_setting_body_populates_buffer - @response.body = 'omg' + @response.body = "omg" @response.close - assert_equal ['omg'], @response.body_parts + assert_equal ["omg"], @response.body_parts end def test_cache_control_is_set - @response.stream.write 'omg' - assert_equal 'no-cache', @response.headers['Cache-Control'] + @response.stream.write "omg" + assert_equal "no-cache", @response.headers["Cache-Control"] end def test_content_length_is_removed - @response.headers['Content-Length'] = "1234" - @response.stream.write 'omg' - assert_nil @response.headers['Content-Length'] + @response.headers["Content-Length"] = "1234" + @response.stream.write "omg" + assert_nil @response.headers["Content-Length"] end def test_headers_cannot_be_written_after_webserver_reads - @response.stream.write 'omg' + @response.stream.write "omg" latch = Concurrent::CountDownLatch.new t = Thread.new { - @response.stream.each do + @response.each do latch.count_down end } @@ -73,10 +73,10 @@ module ActionController latch.wait assert @response.headers.frozen? e = assert_raises(ActionDispatch::IllegalStateError) do - @response.headers['Content-Length'] = "zomg" + @response.headers["Content-Length"] = "zomg" end - assert_equal 'header already sent', e.message + assert_equal "header already sent", e.message @response.stream.close t.join end @@ -87,9 +87,9 @@ module ActionController @response.each { |x| } e = assert_raises(ActionDispatch::IllegalStateError) do - @response.headers['Content-Length'] = "zomg" + @response.headers["Content-Length"] = "zomg" end - assert_equal 'header already sent', e.message + assert_equal "header already sent", e.message end end end diff --git a/actionpack/test/dispatch/mapper_test.rb b/actionpack/test/dispatch/mapper_test.rb index df27e41997..1596d23b1e 100644 --- a/actionpack/test/dispatch/mapper_test.rb +++ b/actionpack/test/dispatch/mapper_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module ActionDispatch module Routing @@ -50,15 +50,15 @@ module ActionDispatch fakeset = FakeSet.new mapper = Mapper.new fakeset assert_raises(ArgumentError) do - mapper.match '/', :to => 'posts#index', :as => :main + mapper.match "/", to: "posts#index", as: :main end end def test_unscoped_formatted fakeset = FakeSet.new mapper = Mapper.new fakeset - mapper.get '/foo', :to => 'posts#index', :as => :main, :format => true - assert_equal({:controller=>"posts", :action=>"index"}, + mapper.get "/foo", to: "posts#index", as: :main, format: true + assert_equal({ controller: "posts", action: "index" }, fakeset.defaults.first) assert_equal "/foo.:format", fakeset.asts.first.to_s end @@ -67,9 +67,9 @@ module ActionDispatch fakeset = FakeSet.new mapper = Mapper.new fakeset mapper.scope(format: true) do - mapper.get '/foo', :to => 'posts#index', :as => :main + mapper.get "/foo", to: "posts#index", as: :main end - assert_equal({:controller=>"posts", :action=>"index"}, + assert_equal({ controller: "posts", action: "index" }, fakeset.defaults.first) assert_equal "/foo.:format", fakeset.asts.first.to_s end @@ -78,18 +78,18 @@ module ActionDispatch fakeset = FakeSet.new mapper = Mapper.new fakeset mapper.scope(omg: :awesome) do - mapper.get '/', :to => 'posts#index', :as => :main + mapper.get "/", to: "posts#index", as: :main end - assert_equal({:omg=>:awesome, :controller=>"posts", :action=>"index"}, + assert_equal({ omg: :awesome, controller: "posts", action: "index" }, fakeset.defaults.first) assert_equal("GET", fakeset.routes.first.verb) end def test_mapping_requirements - options = { } + options = {} scope = Mapper::Scope.new({}) - ast = Journey::Parser.parse '/store/:name(*rest)' - m = Mapper::Mapping.build(scope, FakeSet.new, ast, 'foo', 'bar', nil, [:get], nil, {}, true, options) + ast = Journey::Parser.parse "/store/:name(*rest)" + m = Mapper::Mapping.build(scope, FakeSet.new, ast, "foo", "bar", nil, [:get], nil, {}, true, options) assert_equal(/.+?/, m.requirements[:rest]) end @@ -97,16 +97,28 @@ module ActionDispatch fakeset = FakeSet.new mapper = Mapper.new fakeset mapper.scope(via: :put) do - mapper.match '/', :to => 'posts#index', :as => :main + mapper.match "/", to: "posts#index", as: :main end assert_equal("PUT", fakeset.routes.first.verb) end + def test_to_scope + fakeset = FakeSet.new + mapper = Mapper.new fakeset + mapper.scope(to: "posts#index") do + mapper.get :all + mapper.post :most + end + + assert_equal "posts#index", fakeset.routes.to_a[0].defaults[:to] + assert_equal "posts#index", fakeset.routes.to_a[1].defaults[:to] + end + def test_map_slash fakeset = FakeSet.new mapper = Mapper.new fakeset - mapper.get '/', :to => 'posts#index', :as => :main - assert_equal '/', fakeset.asts.first.to_s + mapper.get "/", to: "posts#index", as: :main + assert_equal "/", fakeset.asts.first.to_s end def test_map_more_slashes @@ -114,31 +126,31 @@ module ActionDispatch mapper = Mapper.new fakeset # FIXME: is this a desired behavior? - mapper.get '/one/two/', :to => 'posts#index', :as => :main - assert_equal '/one/two(.:format)', fakeset.asts.first.to_s + mapper.get "/one/two/", to: "posts#index", as: :main + assert_equal "/one/two(.:format)", fakeset.asts.first.to_s end def test_map_wildcard fakeset = FakeSet.new mapper = Mapper.new fakeset - mapper.get '/*path', :to => 'pages#show' - assert_equal '/*path(.:format)', fakeset.asts.first.to_s + mapper.get "/*path", to: "pages#show" + assert_equal "/*path(.:format)", fakeset.asts.first.to_s assert_equal(/.+?/, fakeset.requirements.first[:path]) end def test_map_wildcard_with_other_element fakeset = FakeSet.new mapper = Mapper.new fakeset - mapper.get '/*path/foo/:bar', :to => 'pages#show' - assert_equal '/*path/foo/:bar(.:format)', fakeset.asts.first.to_s + mapper.get "/*path/foo/:bar", to: "pages#show" + assert_equal "/*path/foo/:bar(.:format)", fakeset.asts.first.to_s assert_equal(/.+?/, fakeset.requirements.first[:path]) end def test_map_wildcard_with_multiple_wildcard fakeset = FakeSet.new mapper = Mapper.new fakeset - mapper.get '/*foo/*bar', :to => 'pages#show' - assert_equal '/*foo/*bar(.:format)', fakeset.asts.first.to_s + mapper.get "/*foo/*bar", to: "pages#show" + assert_equal "/*foo/*bar(.:format)", fakeset.asts.first.to_s assert_equal(/.+?/, fakeset.requirements.first[:foo]) assert_equal(/.+?/, fakeset.requirements.first[:bar]) end @@ -146,16 +158,16 @@ module ActionDispatch def test_map_wildcard_with_format_false fakeset = FakeSet.new mapper = Mapper.new fakeset - mapper.get '/*path', :to => 'pages#show', :format => false - assert_equal '/*path', fakeset.asts.first.to_s + mapper.get "/*path", to: "pages#show", format: false + assert_equal "/*path", fakeset.asts.first.to_s assert_nil fakeset.requirements.first[:path] end def test_map_wildcard_with_format_true fakeset = FakeSet.new mapper = Mapper.new fakeset - mapper.get '/*path', :to => 'pages#show', :format => true - assert_equal '/*path.:format', fakeset.asts.first.to_s + mapper.get "/*path", to: "pages#show", format: true + assert_equal "/*path.:format", fakeset.asts.first.to_s end def test_raising_error_when_path_is_not_passed @@ -178,6 +190,19 @@ module ActionDispatch mapper.mount as: "exciting" end end + + def test_scope_does_not_destructively_mutate_default_options + fakeset = FakeSet.new + mapper = Mapper.new fakeset + + frozen = { foo: :bar }.freeze + + assert_nothing_raised do + mapper.scope(defaults: frozen) do + # pass + end + end + end end end end diff --git a/actionpack/test/dispatch/middleware_stack_test.rb b/actionpack/test/dispatch/middleware_stack_test.rb index a8c8e0784f..481aa22b10 100644 --- a/actionpack/test/dispatch/middleware_stack_test.rb +++ b/actionpack/test/dispatch/middleware_stack_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class MiddlewareStackTest < ActiveSupport::TestCase class FooMiddleware; end @@ -18,14 +18,6 @@ class MiddlewareStackTest < ActiveSupport::TestCase @stack.use BarMiddleware end - def test_delete_with_string_is_deprecated - assert_deprecated do - assert_difference "@stack.size", -1 do - @stack.delete FooMiddleware.name - end - end - end - def test_delete_works assert_difference "@stack.size", -1 do @stack.delete FooMiddleware @@ -39,30 +31,12 @@ class MiddlewareStackTest < ActiveSupport::TestCase assert_equal BazMiddleware, @stack.last.klass end - test "use should push middleware as a string onto the stack" do - assert_deprecated do - assert_difference "@stack.size" do - @stack.use "MiddlewareStackTest::BazMiddleware" - end - assert_equal BazMiddleware, @stack.last.klass - end - end - - test "use should push middleware as a symbol onto the stack" do - assert_deprecated do - assert_difference "@stack.size" do - @stack.use :"MiddlewareStackTest::BazMiddleware" - end - assert_equal BazMiddleware, @stack.last.klass - end - end - test "use should push middleware class with arguments onto the stack" do assert_difference "@stack.size" do - @stack.use BazMiddleware, true, :foo => "bar" + @stack.use BazMiddleware, true, foo: "bar" end assert_equal BazMiddleware, @stack.last.klass - assert_equal([true, {:foo => "bar"}], @stack.last.args) + assert_equal([true, { foo: "bar" }], @stack.last.args) end test "use should push middleware class with block arguments onto the stack" do @@ -102,15 +76,13 @@ class MiddlewareStackTest < ActiveSupport::TestCase test "swaps one middleware out for same middleware class" do assert_equal FooMiddleware, @stack[0].klass - @stack.swap(FooMiddleware, FooMiddleware, Proc.new { |env| [500, {}, ['error!']] }) + @stack.swap(FooMiddleware, FooMiddleware, Proc.new { |env| [500, {}, ["error!"]] }) assert_equal FooMiddleware, @stack[0].klass end test "unshift adds a new middleware at the beginning of the stack" do - assert_deprecated do - @stack.unshift :"MiddlewareStackTest::BazMiddleware" - assert_equal BazMiddleware, @stack.first.klass - end + @stack.unshift MiddlewareStackTest::BazMiddleware + assert_equal BazMiddleware, @stack.first.klass end test "raise an error on invalid index" do @@ -123,15 +95,6 @@ class MiddlewareStackTest < ActiveSupport::TestCase end end - test "lazy evaluates middleware class" do - assert_deprecated do - assert_difference "@stack.size" do - @stack.use "MiddlewareStackTest::BazMiddleware" - end - assert_equal BazMiddleware, @stack.last.klass - end - end - test "can check if Middleware are equal - Class" do assert_equal @stack.last, BarMiddleware end diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb index 149e37bf3d..e558efb88d 100644 --- a/actionpack/test/dispatch/mime_type_test.rb +++ b/actionpack/test/dispatch/mime_type_test.rb @@ -1,9 +1,9 @@ -require 'abstract_unit' +require "abstract_unit" class MimeTypeTest < ActiveSupport::TestCase test "parse single" do Mime::LOOKUP.each_key do |mime_type| - unless mime_type == 'image/*' + unless mime_type == "image/*" assert_equal [Mime::Type.lookup(mime_type)], Mime::Type.parse(mime_type) end end @@ -15,7 +15,7 @@ class MimeTypeTest < ActiveSupport::TestCase begin mime = Mime::Type.register("text/x-mobile", :mobile) assert_equal mime, Mime[:mobile] - assert_equal mime, Mime::Type.lookup('text/x-mobile') + assert_equal mime, Mime::Type.lookup("text/x-mobile") assert_equal mime, Mime::Type.lookup_by_extension(:mobile) Mime::Type.unregister(:mobile) @@ -44,25 +44,25 @@ class MimeTypeTest < ActiveSupport::TestCase accept = "text/*" expect = [Mime[:html], Mime[:text], Mime[:js], Mime[:css], Mime[:ics], Mime[:csv], Mime[:vcf], Mime[:xml], Mime[:yaml], Mime[:json]] parsed = Mime::Type.parse(accept) - assert_equal expect, parsed + assert_equal expect.map(&:to_s).sort!, parsed.map(&:to_s).sort! end test "parse application with trailing star" do accept = "application/*" - expect = [Mime[:html], Mime[:js], Mime[:xml], Mime[:rss], Mime[:atom], Mime[:yaml], Mime[:url_encoded_form], Mime[:json], Mime[:pdf], Mime[:zip]] + expect = [Mime[:html], Mime[:js], Mime[:xml], Mime[:rss], Mime[:atom], Mime[:yaml], Mime[:url_encoded_form], Mime[:json], Mime[:pdf], Mime[:zip], Mime[:gzip]] parsed = Mime::Type.parse(accept) - assert_equal expect, parsed + assert_equal expect.map(&:to_s).sort!, parsed.map(&:to_s).sort! end test "parse without q" do accept = "text/xml,application/xhtml+xml,text/yaml,application/xml,text/html,image/png,text/plain,application/pdf,*/*" - expect = [Mime[:html], Mime[:xml], Mime[:yaml], Mime[:png], Mime[:text], Mime[:pdf], '*/*'] + expect = [Mime[:html], Mime[:xml], Mime[:yaml], Mime[:png], Mime[:text], Mime[:pdf], "*/*"] assert_equal expect.map(&:to_s), Mime::Type.parse(accept).map(&:to_s) end test "parse with q" do accept = "text/xml,application/xhtml+xml,text/yaml; q=0.3,application/xml,text/html; q=0.8,image/png,text/plain; q=0.5,application/pdf,*/*; q=0.2" - expect = [Mime[:html], Mime[:xml], Mime[:png], Mime[:pdf], Mime[:text], Mime[:yaml], '*/*'] + expect = [Mime[:html], Mime[:xml], Mime[:png], Mime[:pdf], Mime[:text], Mime[:yaml], "*/*"] assert_equal expect.map(&:to_s), Mime::Type.parse(accept).map(&:to_s) end @@ -81,7 +81,7 @@ class MimeTypeTest < ActiveSupport::TestCase # Accept header send with user HTTP_USER_AGENT: Sunrise/0.42j (Windows XP) test "parse broken acceptlines" do accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/*,,*/*;q=0.5" - expect = [Mime[:html], Mime[:xml], "image/*", Mime[:text], '*/*'] + expect = [Mime[:html], Mime[:xml], "image/*", Mime[:text], "*/*"] assert_equal expect.map(&:to_s), Mime::Type.parse(accept).map(&:to_s) end @@ -89,7 +89,7 @@ class MimeTypeTest < ActiveSupport::TestCase # (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; InfoPath.1) test "parse other broken acceptlines" do accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, , pronto/1.00.00, sslvpn/1.00.00.00, */*" - expect = ['image/gif', 'image/x-xbitmap', 'image/jpeg','image/pjpeg', 'application/x-shockwave-flash', 'application/vnd.ms-excel', 'application/vnd.ms-powerpoint', 'application/msword', 'pronto/1.00.00', 'sslvpn/1.00.00.00', '*/*'] + expect = ["image/gif", "image/x-xbitmap", "image/jpeg","image/pjpeg", "application/x-shockwave-flash", "application/vnd.ms-excel", "application/vnd.ms-powerpoint", "application/msword", "pronto/1.00.00", "sslvpn/1.00.00.00", "*/*"] assert_equal expect.map(&:to_s), Mime::Type.parse(accept).map(&:to_s) end @@ -141,14 +141,14 @@ class MimeTypeTest < ActiveSupport::TestCase test "register alias" do begin Mime::Type.register_alias "application/xhtml+xml", :foobar - assert_equal Mime[:html], Mime::EXTENSION_LOOKUP['foobar'] + assert_equal Mime[:html], Mime::EXTENSION_LOOKUP["foobar"] ensure Mime::Type.unregister(:foobar) end end test "type should be equal to symbol" do - assert_equal Mime[:html], 'application/xhtml+xml' + assert_equal Mime[:html], "application/xhtml+xml" assert_equal Mime[:html], :html end @@ -167,18 +167,6 @@ class MimeTypeTest < ActiveSupport::TestCase end end - test "deprecated lookup" do - assert_deprecated do - Mime::HTML - end - end - - test "deprecated const_defined?" do - assert_deprecated do - Mime.const_defined? :HTML - end - end - test "references gives preference to symbols before strings" do assert_equal :html, Mime[:html].ref another = Mime::Type.lookup("foo/bar") @@ -192,6 +180,6 @@ class MimeTypeTest < ActiveSupport::TestCase assert Mime[:js] !~ "text/html" assert !(Mime[:js] !~ "text/javascript") assert !(Mime[:js] !~ "application/javascript") - assert Mime[:html] =~ 'application/xhtml+xml' + assert Mime[:html] =~ "application/xhtml+xml" end end diff --git a/actionpack/test/dispatch/mount_test.rb b/actionpack/test/dispatch/mount_test.rb index d027f09762..a7d5ba2345 100644 --- a/actionpack/test/dispatch/mount_test.rb +++ b/actionpack/test/dispatch/mount_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'rails/engine' +require "abstract_unit" +require "rails/engine" class TestRoutingMount < ActionDispatch::IntegrationTest Router = ActionDispatch::Routing::RouteSet.new @@ -15,30 +15,30 @@ class TestRoutingMount < ActionDispatch::IntegrationTest def self.routes; Object.new; end def self.call(env) - [200, {"Content-Type" => "text/html"}, ["OK"]] + [200, { "Content-Type" => "text/html" }, ["OK"]] end end Router.draw do SprocketsApp = lambda { |env| - [200, {"Content-Type" => "text/html"}, ["#{env["SCRIPT_NAME"]} -- #{env["PATH_INFO"]}"]] + [200, { "Content-Type" => "text/html" }, ["#{env["SCRIPT_NAME"]} -- #{env["PATH_INFO"]}"]] } - mount SprocketsApp, :at => "/sprockets" + mount SprocketsApp, at: "/sprockets" mount SprocketsApp => "/shorthand" - mount SinatraLikeApp, :at => "/fakeengine", :as => :fake - mount SinatraLikeApp, :at => "/getfake", :via => :get + mount SinatraLikeApp, at: "/fakeengine", as: :fake + mount SinatraLikeApp, at: "/getfake", via: :get scope "/its_a" do - mount SprocketsApp, :at => "/sprocket" + mount SprocketsApp, at: "/sprocket" end resources :users do - mount AppWithRoutes, :at => "/fakeengine", :as => :fake_mounted_at_resource + mount AppWithRoutes, at: "/fakeengine", as: :fake_mounted_at_resource end - mount SprocketsApp, :at => "/", :via => :get + mount SprocketsApp, at: "/", via: :get end APP = RoutedRackApp.new Router @@ -64,7 +64,7 @@ class TestRoutingMount < ActionDispatch::IntegrationTest end def test_mounting_works_with_nested_script_name - get "/foo/sprockets/omg", headers: { 'SCRIPT_NAME' => '/foo', 'PATH_INFO' => '/sprockets/omg' } + get "/foo/sprockets/omg", headers: { "SCRIPT_NAME" => "/foo", "PATH_INFO" => "/sprockets/omg" } assert_equal "/foo/sprockets -- /omg", response.body end diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb index d75e31db62..bb2fc53add 100644 --- a/actionpack/test/dispatch/prefix_generation_test.rb +++ b/actionpack/test/dispatch/prefix_generation_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'rack/test' -require 'rails/engine' +require "abstract_unit" +require "rack/test" +require "rails/engine" module TestGenerationPrefix class Post @@ -26,42 +26,42 @@ module TestGenerationPrefix class BlogEngine < Rails::Engine routes.draw do - get "/posts/:id", :to => "inside_engine_generating#show", :as => :post - get "/posts", :to => "inside_engine_generating#index", :as => :posts - get "/url_to_application", :to => "inside_engine_generating#url_to_application" - get "/polymorphic_path_for_engine", :to => "inside_engine_generating#polymorphic_path_for_engine" - get "/conflicting_url", :to => "inside_engine_generating#conflicting" - get "/foo", :to => "never#invoked", :as => :named_helper_that_should_be_invoked_only_in_respond_to_test - - get "/relative_path_root", :to => redirect("") - get "/relative_path_redirect", :to => redirect("foo") - get "/relative_option_root", :to => redirect(:path => "") - get "/relative_option_redirect", :to => redirect(:path => "foo") - get "/relative_custom_root", :to => redirect { |params, request| "" } - get "/relative_custom_redirect", :to => redirect { |params, request| "foo" } - - get "/absolute_path_root", :to => redirect("/") - get "/absolute_path_redirect", :to => redirect("/foo") - get "/absolute_option_root", :to => redirect(:path => "/") - get "/absolute_option_redirect", :to => redirect(:path => "/foo") - get "/absolute_custom_root", :to => redirect { |params, request| "/" } - get "/absolute_custom_redirect", :to => redirect { |params, request| "/foo" } + get "/posts/:id", to: "inside_engine_generating#show", as: :post + get "/posts", to: "inside_engine_generating#index", as: :posts + get "/url_to_application", to: "inside_engine_generating#url_to_application" + get "/polymorphic_path_for_engine", to: "inside_engine_generating#polymorphic_path_for_engine" + get "/conflicting_url", to: "inside_engine_generating#conflicting" + get "/foo", to: "never#invoked", as: :named_helper_that_should_be_invoked_only_in_respond_to_test + + get "/relative_path_root", to: redirect("") + get "/relative_path_redirect", to: redirect("foo") + get "/relative_option_root", to: redirect(path: "") + get "/relative_option_redirect", to: redirect(path: "foo") + get "/relative_custom_root", to: redirect { |params, request| "" } + get "/relative_custom_redirect", to: redirect { |params, request| "foo" } + + get "/absolute_path_root", to: redirect("/") + get "/absolute_path_redirect", to: redirect("/foo") + get "/absolute_option_root", to: redirect(path: "/") + get "/absolute_option_redirect", to: redirect(path: "/foo") + get "/absolute_custom_root", to: redirect { |params, request| "/" } + get "/absolute_custom_redirect", to: redirect { |params, request| "/foo" } end end class RailsApplication < Rails::Engine routes.draw do - scope "/:omg", :omg => "awesome" do + scope "/:omg", omg: "awesome" do mount BlogEngine => "/blog", :as => "blog_engine" end - get "/posts/:id", :to => "outside_engine_generating#post", :as => :post - get "/generate", :to => "outside_engine_generating#index" - get "/polymorphic_path_for_app", :to => "outside_engine_generating#polymorphic_path_for_app" - get "/polymorphic_path_for_engine", :to => "outside_engine_generating#polymorphic_path_for_engine" - get "/polymorphic_with_url_for", :to => "outside_engine_generating#polymorphic_with_url_for" - get "/conflicting_url", :to => "outside_engine_generating#conflicting" - get "/ivar_usage", :to => "outside_engine_generating#ivar_usage" - root :to => "outside_engine_generating#index" + get "/posts/:id", to: "outside_engine_generating#post", as: :post + get "/generate", to: "outside_engine_generating#index" + get "/polymorphic_path_for_app", to: "outside_engine_generating#polymorphic_path_for_app" + get "/polymorphic_path_for_engine", to: "outside_engine_generating#polymorphic_path_for_engine" + get "/polymorphic_with_url_for", to: "outside_engine_generating#polymorphic_with_url_for" + get "/conflicting_url", to: "outside_engine_generating#conflicting" + get "/ivar_usage", to: "outside_engine_generating#ivar_usage" + root to: "outside_engine_generating#index" end end @@ -81,9 +81,9 @@ module TestGenerationPrefix end def url_to_application - path = main_app.url_for(:controller => "outside_engine_generating", - :action => "index", - :only_path => true) + path = main_app.url_for(controller: "outside_engine_generating", + action: "index", + only_path: true) render plain: path end @@ -166,7 +166,7 @@ module TestGenerationPrefix assert_equal "/pure-awesomeness/blog/posts/1", last_response.body end - test "[ENGINE] url_helpers from engine have higher priotity than application's url_helpers" do + 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 end @@ -238,7 +238,7 @@ module TestGenerationPrefix end test "[APP] generating engine's route includes default_url_options[:script_name]" do - RailsApplication.routes.default_url_options = {:script_name => "/something"} + RailsApplication.routes.default_url_options = { script_name: "/something" } get "/generate" assert_equal "/something/awesome/blog/posts/1", last_response.body end @@ -269,16 +269,16 @@ module TestGenerationPrefix end test "[OBJECT] generating engine's route includes prefix" do - assert_equal "/awesome/blog/posts/1", engine_object.post_path(:id => 1) + assert_equal "/awesome/blog/posts/1", engine_object.post_path(id: 1) end test "[OBJECT] generating engine's route includes dynamic prefix" do - assert_equal "/pure-awesomeness/blog/posts/3", engine_object.post_path(:id => 3, :omg => "pure-awesomeness") + assert_equal "/pure-awesomeness/blog/posts/3", engine_object.post_path(id: 3, omg: "pure-awesomeness") end test "[OBJECT] generating engine's route includes default_url_options[:script_name]" do - RailsApplication.routes.default_url_options = {:script_name => "/something"} - assert_equal "/something/pure-awesomeness/blog/posts/3", engine_object.post_path(:id => 3, :omg => "pure-awesomeness") + RailsApplication.routes.default_url_options = { script_name: "/something" } + assert_equal "/something/pure-awesomeness/blog/posts/3", engine_object.post_path(id: 3, omg: "pure-awesomeness") end test "[OBJECT] generating application's route" do @@ -286,7 +286,7 @@ module TestGenerationPrefix end test "[OBJECT] generating application's route includes default_url_options[:script_name]" do - RailsApplication.routes.default_url_options = {:script_name => "/something"} + RailsApplication.routes.default_url_options = { script_name: "/something" } assert_equal "/something/", app_object.root_path end @@ -296,11 +296,11 @@ module TestGenerationPrefix end test "[OBJECT] generating engine's route with url_for" do - path = engine_object.url_for(:controller => "inside_engine_generating", - :action => "show", - :only_path => true, - :omg => "omg", - :id => 1) + path = engine_object.url_for(controller: "inside_engine_generating", + action: "show", + only_path: true, + omg: "omg", + id: 1) assert_equal "/omg/blog/posts/1", path end @@ -308,7 +308,7 @@ module TestGenerationPrefix path = engine_object.posts_path assert_equal "/awesome/blog/posts", path - path = engine_object.posts_url(:host => "example.com") + path = engine_object.posts_url(host: "example.com") assert_equal "http://example.com/awesome/blog/posts", path end @@ -316,17 +316,17 @@ module TestGenerationPrefix path = engine_object.polymorphic_path(Post.new) assert_equal "/awesome/blog/posts/1", path - path = engine_object.polymorphic_url(Post.new, :host => "www.example.com") + path = engine_object.polymorphic_url(Post.new, host: "www.example.com") assert_equal "http://www.example.com/awesome/blog/posts/1", path 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 end - + def expected_redirect_body(url) %(<html><body>You are being <a href="#{url}">redirected</a>.</body></html>) end @@ -340,21 +340,21 @@ module TestGenerationPrefix @routes ||= begin routes = ActionDispatch::Routing::RouteSet.new routes.draw do - get "/posts/:id", :to => "posts#show", :as => :post - - get "/relative_path_root", :to => redirect("") - get "/relative_path_redirect", :to => redirect("foo") - get "/relative_option_root", :to => redirect(:path => "") - get "/relative_option_redirect", :to => redirect(:path => "foo") - get "/relative_custom_root", :to => redirect { |params, request| "" } - get "/relative_custom_redirect", :to => redirect { |params, request| "foo" } - - get "/absolute_path_root", :to => redirect("/") - get "/absolute_path_redirect", :to => redirect("/foo") - get "/absolute_option_root", :to => redirect(:path => "/") - get "/absolute_option_redirect", :to => redirect(:path => "/foo") - get "/absolute_custom_root", :to => redirect { |params, request| "/" } - get "/absolute_custom_redirect", :to => redirect { |params, request| "/foo" } + get "/posts/:id", to: "posts#show", as: :post + + get "/relative_path_root", to: redirect("") + get "/relative_path_redirect", to: redirect("foo") + get "/relative_option_root", to: redirect(path: "") + get "/relative_option_redirect", to: redirect(path: "foo") + get "/relative_custom_root", to: redirect { |params, request| "" } + get "/relative_custom_redirect", to: redirect { |params, request| "foo" } + + get "/absolute_path_root", to: redirect("/") + get "/absolute_path_redirect", to: redirect("/foo") + get "/absolute_option_root", to: redirect(path: "/") + get "/absolute_option_redirect", to: redirect(path: "/foo") + get "/absolute_custom_root", to: redirect { |params, request| "/" } + get "/absolute_custom_redirect", to: redirect { |params, request| "/foo" } end routes @@ -362,7 +362,7 @@ module TestGenerationPrefix end def self.call(env) - env['action_dispatch.routes'] = routes + env["action_dispatch.routes"] = routes routes.call(env) end end @@ -450,14 +450,14 @@ module TestGenerationPrefix get "/absolute_custom_redirect" verify_redirect "http://example.org/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 end - + def expected_redirect_body(url) %(<html><body>You are being <a href="#{url}">redirected</a>.</body></html>) end diff --git a/actionpack/test/dispatch/rack_cache_test.rb b/actionpack/test/dispatch/rack_cache_test.rb index 79d8a64d29..d7bb90abbf 100644 --- a/actionpack/test/dispatch/rack_cache_test.rb +++ b/actionpack/test/dispatch/rack_cache_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'action_dispatch/http/rack_cache' +require "abstract_unit" +require "action_dispatch/http/rack_cache" class RackCacheMetaStoreTest < ActiveSupport::TestCase class ReadWriteHash < ::Hash @@ -12,7 +12,7 @@ class RackCacheMetaStoreTest < ActiveSupport::TestCase end test "stuff is deep duped" do - @store.write(:foo, { :bar => :original }) + @store.write(:foo, bar: :original) hash = @store.read(:foo) hash[:bar] = :changed hash = @store.read(:foo) diff --git a/actionpack/test/dispatch/reloader_test.rb b/actionpack/test/dispatch/reloader_test.rb index 62e8197e20..e74b8e40fd 100644 --- a/actionpack/test/dispatch/reloader_test.rb +++ b/actionpack/test/dispatch/reloader_test.rb @@ -1,18 +1,20 @@ -require 'abstract_unit' +require "abstract_unit" class ReloaderTest < ActiveSupport::TestCase Reloader = ActionDispatch::Reloader teardown do - Reloader.reset_callbacks :prepare - Reloader.reset_callbacks :cleanup + ActiveSupport::Reloader.reset_callbacks :prepare + ActiveSupport::Reloader.reset_callbacks :complete 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 } + 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 @@ -60,9 +62,15 @@ class ReloaderTest < ActiveSupport::TestCase def test_condition_specifies_when_to_reload i, j = 0, 0, 0, 0 - Reloader.to_prepare { |*args| i += 1 } - Reloader.to_cleanup { |*args| j += 1 } - app = Reloader.new(lambda { |env| [200, {}, []] }, lambda { i < 3 }) + 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 } + + app = Reloader.new(lambda { |env| [200, {}, []] }, x) 5.times do resp = app.call({}) resp[2].close @@ -109,7 +117,9 @@ class ReloaderTest < ActiveSupport::TestCase def test_cleanup_callbacks_are_called_when_body_is_closed cleaned = false - Reloader.to_cleanup { cleaned = true } + assert_deprecated do + Reloader.to_cleanup { cleaned = true } + end body = call_and_return_body assert !cleaned @@ -120,7 +130,9 @@ class ReloaderTest < ActiveSupport::TestCase def test_prepare_callbacks_arent_called_when_body_is_closed prepared = false - Reloader.to_prepare { prepared = true } + assert_deprecated do + Reloader.to_prepare { prepared = true } + end body = call_and_return_body prepared = false @@ -131,31 +143,43 @@ class ReloaderTest < ActiveSupport::TestCase def test_manual_reloading prepared = cleaned = false - Reloader.to_prepare { prepared = true } - Reloader.to_cleanup { cleaned = true } + assert_deprecated do + Reloader.to_prepare { prepared = true } + Reloader.to_cleanup { cleaned = true } + end - Reloader.prepare! + assert_deprecated do + Reloader.prepare! + end assert prepared assert !cleaned prepared = cleaned = false - Reloader.cleanup! - assert !prepared + assert_deprecated do + Reloader.cleanup! + end + assert prepared assert cleaned end def test_prepend_prepare_callback i = 10 - Reloader.to_prepare { i += 1 } - Reloader.to_prepare(:prepend => true) { i = 0 } + assert_deprecated do + Reloader.to_prepare { i += 1 } + Reloader.to_prepare(prepend: true) { i = 0 } + end - Reloader.prepare! + assert_deprecated do + Reloader.prepare! + end assert_equal 1, i end def test_cleanup_callbacks_are_called_on_exceptions cleaned = false - Reloader.to_cleanup { cleaned = true } + assert_deprecated do + Reloader.to_cleanup { cleaned = true } + end begin call_and_return_body do @@ -169,8 +193,11 @@ class ReloaderTest < ActiveSupport::TestCase private def call_and_return_body(&block) - @response ||= 'response' - @reloader ||= Reloader.new(block || proc {[200, {}, @response]}) - @reloader.call({'rack.input' => StringIO.new('')})[2] + x = Class.new(ActiveSupport::Reloader) + x.check = lambda { true } + + @response ||= "response" + @reloader ||= Reloader.new(block || proc { [200, {}, @response] }, x) + @reloader.call("rack.input" => StringIO.new(""))[2] end end diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb index a3992ad008..10234a4815 100644 --- a/actionpack/test/dispatch/request/json_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class JsonParamsParsingTest < ActionDispatch::IntegrationTest class TestController < ActionController::Base @@ -18,44 +18,44 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest test "parses json params for application json" do assert_parses( - {"person" => {"name" => "David"}}, - "{\"person\": {\"name\": \"David\"}}", { 'CONTENT_TYPE' => 'application/json' } + { "person" => { "name" => "David" } }, + "{\"person\": {\"name\": \"David\"}}", "CONTENT_TYPE" => "application/json" ) end test "parses boolean and number json params for application json" do assert_parses( - {"item" => {"enabled" => false, "count" => 10}}, - "{\"item\": {\"enabled\": false, \"count\": 10}}", { 'CONTENT_TYPE' => 'application/json' } + { "item" => { "enabled" => false, "count" => 10 } }, + "{\"item\": {\"enabled\": false, \"count\": 10}}", "CONTENT_TYPE" => "application/json" ) end test "parses json params for application jsonrequest" do assert_parses( - {"person" => {"name" => "David"}}, - "{\"person\": {\"name\": \"David\"}}", { 'CONTENT_TYPE' => 'application/jsonrequest' } + { "person" => { "name" => "David" } }, + "{\"person\": {\"name\": \"David\"}}", "CONTENT_TYPE" => "application/jsonrequest" ) end - test "parses json params for application/vnd.api+json" do + test "does not parse unregistered media types such as application/vnd.api+json" do assert_parses( - {"person" => {"name" => "David"}}, - "{\"person\": {\"name\": \"David\"}}", { 'CONTENT_TYPE' => 'application/vnd.api+json' } + {}, + "{\"person\": {\"name\": \"David\"}}", "CONTENT_TYPE" => "application/vnd.api+json" ) end test "nils are stripped from collections" do assert_parses( - {"person" => []}, - "{\"person\":[null]}", { 'CONTENT_TYPE' => 'application/json' } + { "person" => [] }, + "{\"person\":[null]}", "CONTENT_TYPE" => "application/json" ) assert_parses( - {"person" => ['foo']}, - "{\"person\":[\"foo\",null]}", { 'CONTENT_TYPE' => 'application/json' } + { "person" => ["foo"] }, + "{\"person\":[\"foo\",null]}", "CONTENT_TYPE" => "application/json" ) assert_parses( - {"person" => []}, - "{\"person\":[null, null]}", { 'CONTENT_TYPE' => 'application/json' } + { "person" => [] }, + "{\"person\":[null, null]}", "CONTENT_TYPE" => "application/json" ) end @@ -63,7 +63,7 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest with_test_routing do output = StringIO.new json = "[\"person]\": {\"name\": \"David\"}}" - post "/parse", params: json, headers: { 'CONTENT_TYPE' => 'application/json', 'action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => ActiveSupport::Logger.new(output) } + post "/parse", params: json, headers: { "CONTENT_TYPE" => "application/json", "action_dispatch.show_exceptions" => true, "action_dispatch.logger" => ActiveSupport::Logger.new(output) } assert_response :bad_request output.rewind && err = output.read assert err =~ /Error occurred while parsing request parameters/ @@ -75,7 +75,9 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest begin $stderr = StringIO.new # suppress the log json = "[\"person]\": {\"name\": \"David\"}}" - exception = assert_raise(ActionDispatch::ParamsParser::ParseError) { post "/parse", json, {'CONTENT_TYPE' => 'application/json', 'action_dispatch.show_exceptions' => false} } + exception = assert_raise(ActionDispatch::Http::Parameters::ParseError) do + post "/parse", params: json, headers: { "CONTENT_TYPE" => "application/json", "action_dispatch.show_exceptions" => false } + end assert_equal JSON::ParserError, exception.cause.class assert_equal exception.cause.message, exception.message ensure @@ -84,9 +86,9 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest end end - test 'raw_post is not empty for JSON request' do + test "raw_post is not empty for JSON request" do with_test_routing do - post '/parse', params: '{"posts": [{"title": "Post Title"}]}', headers: { 'CONTENT_TYPE' => 'application/json' } + post "/parse", params: '{"posts": [{"title": "Post Title"}]}', headers: { "CONTENT_TYPE" => "application/json" } assert_equal '{"posts": [{"title": "Post Title"}]}', request.raw_post end end @@ -103,7 +105,9 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest def with_test_routing with_routing do |set| set.draw do - post ':action', :to => ::JsonParamsParsingTest::TestController + ActiveSupport::Deprecation.silence do + post ":action", to: ::JsonParamsParsingTest::TestController + end end yield end @@ -112,7 +116,7 @@ end class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest class UsersController < ActionController::Base - wrap_parameters :format => :json + wrap_parameters format: :json class << self attr_accessor :last_request_parameters, :last_parameters @@ -131,30 +135,51 @@ class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest test "parses json params for application json" do assert_parses( - {"user" => {"username" => "sikachu"}, "username" => "sikachu"}, - "{\"username\": \"sikachu\"}", { 'CONTENT_TYPE' => 'application/json' } + { "user" => { "username" => "sikachu" }, "username" => "sikachu" }, + "{\"username\": \"sikachu\"}", "CONTENT_TYPE" => "application/json" ) end test "parses json params for application jsonrequest" do assert_parses( - {"user" => {"username" => "sikachu"}, "username" => "sikachu"}, - "{\"username\": \"sikachu\"}", { 'CONTENT_TYPE' => 'application/jsonrequest' } + { "user" => { "username" => "sikachu" }, "username" => "sikachu" }, + "{\"username\": \"sikachu\"}", "CONTENT_TYPE" => "application/jsonrequest" ) end - test "parses json params for application/vnd.api+json" do + test "parses json with non-object JSON content" do assert_parses( - {"user" => {"username" => "sikachu"}, "username" => "sikachu"}, - "{\"username\": \"sikachu\"}", { 'CONTENT_TYPE' => 'application/vnd.api+json' } + { "user" => { "_json" => "string content" }, "_json" => "string content" }, + "\"string content\"", "CONTENT_TYPE" => "application/json" ) end - test "parses json with non-object JSON content" do - assert_parses( - {"user" => {"_json" => "string content" }, "_json" => "string content" }, - "\"string content\"", { 'CONTENT_TYPE' => 'application/json' } - ) + test "parses json params after custom json mime type registered" do + begin + Mime::Type.unregister :json + Mime::Type.register "application/json", :json, %w(application/vnd.rails+json) + assert_parses( + { "user" => { "username" => "meinac" }, "username" => "meinac" }, + "{\"username\": \"meinac\"}", "CONTENT_TYPE" => "application/json" + ) + ensure + Mime::Type.unregister :json + Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest ) + end + end + + test "parses json params after custom json mime type registered with synonym" do + begin + Mime::Type.unregister :json + Mime::Type.register "application/json", :json, %w(application/vnd.rails+json) + assert_parses( + { "user" => { "username" => "meinac" }, "username" => "meinac" }, + "{\"username\": \"meinac\"}", "CONTENT_TYPE" => "application/vnd.rails+json" + ) + ensure + Mime::Type.unregister :json + Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest ) + end end private @@ -163,14 +188,16 @@ class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest post "/parse", params: actual, headers: headers assert_response :ok assert_equal(expected, UsersController.last_request_parameters) - assert_equal(expected.merge({"action" => "parse"}), UsersController.last_parameters) + assert_equal(expected.merge("action" => "parse"), UsersController.last_parameters) end end def with_test_routing(controller) with_routing do |set| set.draw do - post ':action', :to => controller + ActiveSupport::Deprecation.silence do + post ":action", to: controller + end end yield end diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb index b36fbd3c76..eb4bb14ed1 100644 --- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class MultipartParamsParsingTest < ActionDispatch::IntegrationTest class TestController < ActionController::Base @@ -21,136 +21,136 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest end end - FIXTURE_PATH = File.dirname(__FILE__) + '/../../fixtures/multipart' + FIXTURE_PATH = File.dirname(__FILE__) + "/../../fixtures/multipart" def teardown TestController.last_request_parameters = nil end test "parses single parameter" do - assert_equal({ 'foo' => 'bar' }, parse_multipart('single_parameter')) + assert_equal({ "foo" => "bar" }, parse_multipart("single_parameter")) end test "parses bracketed parameters" do - assert_equal({ 'foo' => { 'baz' => 'bar'}}, parse_multipart('bracketed_param')) + assert_equal({ "foo" => { "baz" => "bar" } }, parse_multipart("bracketed_param")) end test "parse single utf8 parameter" do - assert_equal({ 'Iñtërnâtiônà lizætiøn_name' => 'Iñtërnâtiônà lizætiøn_value'}, - parse_multipart('single_utf8_param'), "request.request_parameters") + assert_equal({ "Iñtërnâtiônà lizætiøn_name" => "Iñtërnâtiônà lizætiøn_value" }, + parse_multipart("single_utf8_param"), "request.request_parameters") assert_equal( - 'Iñtërnâtiônà lizætiøn_value', - TestController.last_parameters['Iñtërnâtiônà lizætiøn_name'], "request.parameters") + "Iñtërnâtiônà lizætiøn_value", + TestController.last_parameters["Iñtërnâtiônà lizætiøn_name"], "request.parameters") end test "parse bracketed utf8 parameter" do - assert_equal({ 'Iñtërnâtiônà lizætiøn_name' => { - 'Iñtërnâtiônà lizætiøn_nested_name' => 'Iñtërnâtiônà lizætiøn_value'} }, - parse_multipart('bracketed_utf8_param'), "request.request_parameters") + assert_equal({ "Iñtërnâtiônà lizætiøn_name" => { + "Iñtërnâtiônà lizætiøn_nested_name" => "Iñtërnâtiônà lizætiøn_value" } }, + parse_multipart("bracketed_utf8_param"), "request.request_parameters") assert_equal( - {'Iñtërnâtiônà lizætiøn_nested_name' => 'Iñtërnâtiônà lizætiøn_value'}, - TestController.last_parameters['Iñtërnâtiônà lizætiøn_name'], "request.parameters") + { "Iñtërnâtiônà lizætiøn_nested_name" => "Iñtërnâtiônà lizætiøn_value" }, + TestController.last_parameters["Iñtërnâtiônà lizætiøn_name"], "request.parameters") end test "parses text file" do - params = parse_multipart('text_file') + params = parse_multipart("text_file") assert_equal %w(file foo), params.keys.sort - assert_equal 'bar', params['foo'] + assert_equal "bar", params["foo"] - file = params['file'] - assert_equal 'file.txt', file.original_filename + file = params["file"] + assert_equal "file.txt", file.original_filename assert_equal "text/plain", file.content_type - assert_equal 'contents', file.read + assert_equal "contents", file.read end test "parses utf8 filename with percent character" do - params = parse_multipart('utf8_filename') + params = parse_multipart("utf8_filename") assert_equal %w(file foo), params.keys.sort - assert_equal 'bar', params['foo'] + assert_equal "bar", params["foo"] - file = params['file'] - assert_equal 'ファイル%å.txt', file.original_filename + file = params["file"] + assert_equal "ファイル%å.txt", file.original_filename assert_equal "text/plain", file.content_type - assert_equal 'contents', file.read + assert_equal "contents", file.read end test "parses boundary problem file" do - params = parse_multipart('boundary_problem_file') + params = parse_multipart("boundary_problem_file") assert_equal %w(file foo), params.keys.sort - file = params['file'] - foo = params['foo'] + file = params["file"] + foo = params["foo"] - assert_equal 'file.txt', file.original_filename + assert_equal "file.txt", file.original_filename assert_equal "text/plain", file.content_type - assert_equal 'bar', foo + assert_equal "bar", foo end test "parses large text file" do - params = parse_multipart('large_text_file') + params = parse_multipart("large_text_file") assert_equal %w(file foo), params.keys.sort - assert_equal 'bar', params['foo'] + assert_equal "bar", params["foo"] - file = params['file'] + file = params["file"] - assert_equal 'file.txt', file.original_filename + assert_equal "file.txt", file.original_filename assert_equal "text/plain", file.content_type - assert_equal(('a' * 20480), file.read) + assert_equal(("a" * 20480), file.read) end test "parses binary file" do - params = parse_multipart('binary_file') + params = parse_multipart("binary_file") assert_equal %w(file flowers foo), params.keys.sort - assert_equal 'bar', params['foo'] + assert_equal "bar", params["foo"] - file = params['file'] - assert_equal 'file.csv', file.original_filename + file = params["file"] + assert_equal "file.csv", file.original_filename assert_nil file.content_type - assert_equal 'contents', file.read + assert_equal "contents", file.read - file = params['flowers'] - assert_equal 'flowers.jpg', file.original_filename + file = params["flowers"] + assert_equal "flowers.jpg", file.original_filename assert_equal "image/jpeg", file.content_type assert_equal 19512, file.size end test "parses mixed files" do - params = parse_multipart('mixed_files') + params = parse_multipart("mixed_files") assert_equal %w(files foo), params.keys.sort - assert_equal 'bar', params['foo'] + assert_equal "bar", params["foo"] # Rack doesn't handle multipart/mixed for us. - files = params['files'] + files = params["files"] assert_equal 19756, files.bytesize end test "does not create tempfile if no file has been selected" do - params = parse_multipart('none') + params = parse_multipart("none") assert_equal %w(submit-name), params.keys.sort - assert_equal 'Larry', params['submit-name'] - assert_equal nil, params['files'] + assert_equal "Larry", params["submit-name"] + assert_equal nil, params["files"] end test "parses empty upload file" do - params = parse_multipart('empty') + params = parse_multipart("empty") assert_equal %w(files submit-name), params.keys.sort - assert_equal 'Larry', params['submit-name'] - assert params['files'] - assert_equal "", params['files'].read + assert_equal "Larry", params["submit-name"] + assert params["files"] + assert_equal "", params["files"].read end test "uploads and reads binary file" do with_test_routing do - fixture = FIXTURE_PATH + "/mona_lisa.jpg" - params = { :uploaded_data => fixture_file_upload(fixture, "image/jpg") } - post '/read', params: params + fixture = FIXTURE_PATH + "/ruby_on_rails.jpg" + params = { uploaded_data: fixture_file_upload(fixture, "image/jpg") } + post "/read", params: params end end test "uploads and reads file" do with_test_routing do - post '/read', params: { uploaded_data: fixture_file_upload(FIXTURE_PATH + "/hello.txt", "text/plain") } + post "/read", params: { uploaded_data: fixture_file_upload(FIXTURE_PATH + "/hello.txt", "text/plain") } assert_equal "File: Hello", response.body end end @@ -159,7 +159,9 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest test "does not raise EOFError on GET request with multipart content-type" do with_routing do |set| set.draw do - get ':action', controller: 'multipart_params_parsing_test/test' + ActiveSupport::Deprecation.silence do + get ":action", controller: "multipart_params_parsing_test/test" + end end headers = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x" } get "/parse", headers: headers @@ -169,7 +171,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest private def fixture(name) - File.open(File.join(FIXTURE_PATH, name), 'rb') do |file| + File.open(File.join(FIXTURE_PATH, name), "rb") do |file| { "rack.input" => file.read, "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x", "CONTENT_LENGTH" => file.stat.size.to_s } @@ -188,7 +190,9 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest def with_test_routing with_routing do |set| set.draw do - post ':action', :controller => 'multipart_params_parsing_test/test' + ActiveSupport::Deprecation.silence do + post ":action", controller: "multipart_params_parsing_test/test" + end end yield end diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb index bc6716525e..5c992be216 100644 --- a/actionpack/test/dispatch/request/query_string_parsing_test.rb +++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class QueryStringParsingTest < ActionDispatch::IntegrationTest class TestController < ActionController::Base @@ -29,92 +29,92 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest test "query string" do assert_parses( - {"action" => "create_customer", "full_name" => "David Heinemeier Hansson", "customerId" => "1"}, + { "action" => "create_customer", "full_name" => "David Heinemeier Hansson", "customerId" => "1" }, "action=create_customer&full_name=David%20Heinemeier%20Hansson&customerId=1" ) end test "deep query string" do assert_parses( - {'x' => {'y' => {'z' => '10'}}}, + { "x" => { "y" => { "z" => "10" } } }, "x[y][z]=10" ) end test "deep query string with array" do - assert_parses({'x' => {'y' => {'z' => ['10']}}}, 'x[y][z][]=10') - assert_parses({'x' => {'y' => {'z' => ['10', '5']}}}, 'x[y][z][]=10&x[y][z][]=5') + assert_parses({ "x" => { "y" => { "z" => ["10"] } } }, "x[y][z][]=10") + assert_parses({ "x" => { "y" => { "z" => ["10", "5"] } } }, "x[y][z][]=10&x[y][z][]=5") end test "deep query string with array of hash" do - assert_parses({'x' => {'y' => [{'z' => '10'}]}}, 'x[y][][z]=10') - assert_parses({'x' => {'y' => [{'z' => '10', 'w' => '10'}]}}, 'x[y][][z]=10&x[y][][w]=10') - assert_parses({'x' => {'y' => [{'z' => '10', 'v' => {'w' => '10'}}]}}, 'x[y][][z]=10&x[y][][v][w]=10') + assert_parses({ "x" => { "y" => [{ "z" => "10" }] } }, "x[y][][z]=10") + assert_parses({ "x" => { "y" => [{ "z" => "10", "w" => "10" }] } }, "x[y][][z]=10&x[y][][w]=10") + assert_parses({ "x" => { "y" => [{ "z" => "10", "v" => { "w" => "10" } }] } }, "x[y][][z]=10&x[y][][v][w]=10") end test "deep query string with array of hashes with one pair" do - assert_parses({'x' => {'y' => [{'z' => '10'}, {'z' => '20'}]}}, 'x[y][][z]=10&x[y][][z]=20') + assert_parses({ "x" => { "y" => [{ "z" => "10" }, { "z" => "20" }] } }, "x[y][][z]=10&x[y][][z]=20") end test "deep query string with array of hashes with multiple pairs" do assert_parses( - {'x' => {'y' => [{'z' => '10', 'w' => 'a'}, {'z' => '20', 'w' => 'b'}]}}, - 'x[y][][z]=10&x[y][][w]=a&x[y][][z]=20&x[y][][w]=b' + { "x" => { "y" => [{ "z" => "10", "w" => "a" }, { "z" => "20", "w" => "b" }] } }, + "x[y][][z]=10&x[y][][w]=a&x[y][][z]=20&x[y][][w]=b" ) end test "query string with nil" do assert_parses( - { "action" => "create_customer", "full_name" => ''}, + { "action" => "create_customer", "full_name" => "" }, "action=create_customer&full_name=" ) end test "query string with array" do assert_parses( - { "action" => "create_customer", "selected" => ["1", "2", "3"]}, + { "action" => "create_customer", "selected" => ["1", "2", "3"] }, "action=create_customer&selected[]=1&selected[]=2&selected[]=3" ) end test "query string with amps" do assert_parses( - { "action" => "create_customer", "name" => "Don't & Does"}, + { "action" => "create_customer", "name" => "Don't & Does" }, "action=create_customer&name=Don%27t+%26+Does" ) end test "query string with many equal" do assert_parses( - { "action" => "create_customer", "full_name" => "abc=def=ghi"}, + { "action" => "create_customer", "full_name" => "abc=def=ghi" }, "action=create_customer&full_name=abc=def=ghi" ) end test "query string without equal" do - assert_parses({"action" => nil}, "action") - assert_parses({"action" => {"foo" => nil}}, "action[foo]") - assert_parses({"action" => {"foo" => { "bar" => nil }}}, "action[foo][bar]") - assert_parses({"action" => {"foo" => { "bar" => [] }}}, "action[foo][bar][]") - assert_parses({"action" => {"foo" => [] }}, "action[foo][]") - assert_parses({"action"=>{"foo"=>[{"bar"=>nil}]}}, "action[foo][][bar]") + assert_parses({ "action" => nil }, "action") + assert_parses({ "action" => { "foo" => nil } }, "action[foo]") + assert_parses({ "action" => { "foo" => { "bar" => nil } } }, "action[foo][bar]") + assert_parses({ "action" => { "foo" => { "bar" => [] } } }, "action[foo][bar][]") + assert_parses({ "action" => { "foo" => [] } }, "action[foo][]") + assert_parses({ "action"=>{ "foo"=>[{ "bar"=>nil }] } }, "action[foo][][bar]") end def test_array_parses_without_nil - assert_parses({"action" => ['1']}, "action[]=1&action[]") + assert_parses({ "action" => ["1"] }, "action[]=1&action[]") end test "perform_deep_munge" do old_perform_deep_munge = ActionDispatch::Request::Utils.perform_deep_munge ActionDispatch::Request::Utils.perform_deep_munge = false begin - assert_parses({"action" => nil}, "action") - assert_parses({"action" => {"foo" => nil}}, "action[foo]") - assert_parses({"action" => {"foo" => {"bar" => nil}}}, "action[foo][bar]") - assert_parses({"action" => {"foo" => {"bar" => [nil]}}}, "action[foo][bar][]") - assert_parses({"action" => {"foo" => [nil]}}, "action[foo][]") - assert_parses({"action" => {"foo" => [{"bar" => nil}]}}, "action[foo][][bar]") - assert_parses({"action" => ['1',nil]}, "action[]=1&action[]") + assert_parses({ "action" => nil }, "action") + assert_parses({ "action" => { "foo" => nil } }, "action[foo]") + assert_parses({ "action" => { "foo" => { "bar" => nil } } }, "action[foo][bar]") + assert_parses({ "action" => { "foo" => { "bar" => [nil] } } }, "action[foo][bar][]") + assert_parses({ "action" => { "foo" => [nil] } }, "action[foo][]") + assert_parses({ "action" => { "foo" => [{ "bar" => nil }] } }, "action[foo][][bar]") + assert_parses({ "action" => ["1",nil] }, "action[]=1&action[]") ensure ActionDispatch::Request::Utils.perform_deep_munge = old_perform_deep_munge end @@ -129,14 +129,14 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest test "query string with many ampersands" do assert_parses( - { "action" => "create_customer", "full_name" => "David Heinemeier Hansson"}, + { "action" => "create_customer", "full_name" => "David Heinemeier Hansson" }, "&action=create_customer&&&full_name=David%20Heinemeier%20Hansson" ) end test "unbalanced query string with array" do assert_parses( - {'location' => ["1", "2"], 'age_group' => ["2"]}, + { "location" => ["1", "2"], "age_group" => ["2"] }, "location[]=1&location[]=2&age_group[]=2" ) end @@ -144,7 +144,9 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest test "ambiguous query string returns a bad request" do with_routing do |set| set.draw do - get ':action', :to => ::QueryStringParsingTest::TestController + ActiveSupport::Deprecation.silence do + get ":action", to: ::QueryStringParsingTest::TestController + end end get "/parse", headers: { "QUERY_STRING" => "foo[]=bar&foo[4]=bar" } @@ -156,7 +158,9 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest def assert_parses(expected, actual) with_routing do |set| set.draw do - get ':action', :to => ::QueryStringParsingTest::TestController + ActiveSupport::Deprecation.silence do + get ":action", to: ::QueryStringParsingTest::TestController + end end @app = self.class.build_app(set) do |middleware| middleware.use(EarlyParse) diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb index 7dcbcc5c21..311b80ea0a 100644 --- a/actionpack/test/dispatch/request/session_test.rb +++ b/actionpack/test/dispatch/request/session_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'action_dispatch/middleware/session/abstract_store' +require "abstract_unit" +require "action_dispatch/middleware/session/abstract_store" module ActionDispatch class Request @@ -17,18 +17,18 @@ module ActionDispatch def test_to_hash s = Session.create(store, req, {}) - s['foo'] = 'bar' - assert_equal 'bar', s['foo'] - assert_equal({'foo' => 'bar'}, s.to_hash) + s["foo"] = "bar" + assert_equal "bar", s["foo"] + assert_equal({ "foo" => "bar" }, s.to_hash) end def test_create_merges_old s = Session.create(store, req, {}) - s['foo'] = 'bar' + s["foo"] = "bar" s1 = Session.create(store, req, {}) assert_not_equal s, s1 - assert_equal 'bar', s1['foo'] + assert_equal "bar", s1["foo"] end def test_find @@ -40,7 +40,7 @@ module ActionDispatch def test_destroy s = Session.create(store, req, {}) - s['rails'] = 'ftw' + s["rails"] = "ftw" s.destroy @@ -49,22 +49,22 @@ module ActionDispatch def test_keys s = Session.create(store, req, {}) - s['rails'] = 'ftw' - s['adequate'] = 'awesome' + s["rails"] = "ftw" + s["adequate"] = "awesome" assert_equal %w[rails adequate], s.keys end def test_values s = Session.create(store, req, {}) - s['rails'] = 'ftw' - s['adequate'] = 'awesome' + s["rails"] = "ftw" + s["adequate"] = "awesome" assert_equal %w[ftw awesome], s.values end def test_clear s = Session.create(store, req, {}) - s['rails'] = 'ftw' - s['adequate'] = 'awesome' + s["rails"] = "ftw" + s["adequate"] = "awesome" s.clear assert_empty(s.values) @@ -72,19 +72,19 @@ module ActionDispatch def test_update s = Session.create(store, req, {}) - s['rails'] = 'ftw' + s["rails"] = "ftw" - s.update(:rails => 'awesome') + s.update(rails: "awesome") - assert_equal(['rails'], s.keys) - assert_equal('awesome', s['rails']) + assert_equal(["rails"], s.keys) + assert_equal("awesome", s["rails"]) end def test_delete s = Session.create(store, req, {}) - s['rails'] = 'ftw' + s["rails"] = "ftw" - s.delete('rails') + s.delete("rails") assert_empty(s.keys) end @@ -92,13 +92,13 @@ module ActionDispatch def test_fetch session = Session.create(store, req, {}) - session['one'] = '1' - assert_equal '1', session.fetch(:one) + session["one"] = "1" + assert_equal "1", session.fetch(:one) - assert_equal '2', session.fetch(:two, '2') + assert_equal "2", session.fetch(:two, "2") assert_nil session.fetch(:two, nil) - assert_equal 'three', session.fetch(:three) {|el| el.to_s } + assert_equal "three", session.fetch(:three) { |el| el.to_s } assert_raise KeyError do session.fetch(:three) @@ -106,12 +106,38 @@ module ActionDispatch end private - def store - Class.new { - def load_session(env); [1, {}]; end - def session_exists?(env); true; end - def delete_session(env, id, options); 123; end - }.new + def store + Class.new { + def load_session(env); [1, {}]; end + def session_exists?(env); true; end + def delete_session(env, id, options); 123; end + }.new + end + end + + class SessionIntegrationTest < ActionDispatch::IntegrationTest + class MySessionApp + def call(env) + request = Rack::Request.new(env) + request.session["hello"] = "Hello from MySessionApp!" + [ 200, {}, ["Hello from MySessionApp!"] ] + end + end + + Router = ActionDispatch::Routing::RouteSet.new + Router.draw do + get "/mysessionapp" => MySessionApp.new + end + + def app + @app ||= RoutedRackApp.new(Router) + end + + def test_session_follows_rack_api_contract_1 + get "/mysessionapp" + assert_response :ok + assert_equal "Hello from MySessionApp!", @response.body + assert_equal "Hello from MySessionApp!", session["hello"] end end end diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb index 365edf849a..5c7558e48d 100644 --- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest class TestController < ActionController::Base @@ -18,7 +18,7 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest test "parses unbalanced query string with array" do query = "location[]=1&location[]=2&age_group[]=2" - expected = { 'location' => ["1", "2"], 'age_group' => ["2"] } + expected = { "location" => ["1", "2"], "age_group" => ["2"] } assert_parses expected, query end @@ -140,7 +140,9 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest def with_test_routing with_routing do |set| set.draw do - post ':action', to: ::UrlEncodedParamsParsingTest::TestController + ActiveSupport::Deprecation.silence do + post ":action", to: ::UrlEncodedParamsParsingTest::TestController + end end yield end diff --git a/actionpack/test/dispatch/request_id_test.rb b/actionpack/test/dispatch/request_id_test.rb index 00d8caf8f4..4fcd45acf5 100644 --- a/actionpack/test/dispatch/request_id_test.rb +++ b/actionpack/test/dispatch/request_id_test.rb @@ -1,16 +1,16 @@ -require 'abstract_unit' +require "abstract_unit" class RequestIdTest < ActiveSupport::TestCase test "passing on the request id from the outside" do - assert_equal "external-uu-rid", stub_request('HTTP_X_REQUEST_ID' => 'external-uu-rid').request_id + assert_equal "external-uu-rid", stub_request("HTTP_X_REQUEST_ID" => "external-uu-rid").request_id end test "ensure that only alphanumeric uurids are accepted" do - assert_equal "X-Hacked-HeaderStuff", stub_request('HTTP_X_REQUEST_ID' => '; X-Hacked-Header: Stuff').request_id + assert_equal "X-Hacked-HeaderStuff", stub_request("HTTP_X_REQUEST_ID" => "; X-Hacked-Header: Stuff").request_id end test "ensure that 255 char limit on the request id is being enforced" do - assert_equal "X" * 255, stub_request('HTTP_X_REQUEST_ID' => 'X' * 500).request_id + assert_equal "X" * 255, stub_request("HTTP_X_REQUEST_ID" => "X" * 500).request_id end test "generating a request id when none is supplied" do @@ -18,15 +18,15 @@ class RequestIdTest < ActiveSupport::TestCase end test "uuid alias" do - assert_equal "external-uu-rid", stub_request('HTTP_X_REQUEST_ID' => 'external-uu-rid').uuid + assert_equal "external-uu-rid", stub_request("HTTP_X_REQUEST_ID" => "external-uu-rid").uuid end private - def stub_request(env = {}) - ActionDispatch::RequestId.new(lambda { |environment| [ 200, environment, [] ] }).call(env) - ActionDispatch::Request.new(env) - end + def stub_request(env = {}) + ActionDispatch::RequestId.new(lambda { |environment| [ 200, environment, [] ] }).call(env) + ActionDispatch::Request.new(env) + end end class RequestIdResponseTest < ActionDispatch::IntegrationTest @@ -38,32 +38,31 @@ class RequestIdResponseTest < ActionDispatch::IntegrationTest test "request id is passed all the way to the response" do with_test_route_set do - get '/' + get "/" assert_match(/\w+/, @response.headers["X-Request-Id"]) end end test "request id given on request is passed all the way to the response" do with_test_route_set do - get '/', headers: { 'HTTP_X_REQUEST_ID' => 'X' * 500 } + get "/", headers: { "HTTP_X_REQUEST_ID" => "X" * 500 } assert_equal "X" * 255, @response.headers["X-Request-Id"] end end - private - def with_test_route_set - with_routing do |set| - set.draw do - get '/', :to => ::RequestIdResponseTest::TestController.action(:index) - end + def with_test_route_set + with_routing do |set| + set.draw do + get "/", to: ::RequestIdResponseTest::TestController.action(:index) + end - @app = self.class.build_app(set) do |middleware| - middleware.use ActionDispatch::RequestId - end + @app = self.class.build_app(set) do |middleware| + middleware.use ActionDispatch::RequestId + end - yield + yield + end end - end end diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 0edad72fd9..13a87b8976 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class BaseRequestTest < ActiveSupport::TestCase def setup @@ -14,7 +14,7 @@ class BaseRequestTest < ActiveSupport::TestCase end def url_for(options = {}) - options = { host: 'www.example.com' }.merge!(options) + options = { host: "www.example.com" }.merge!(options) ActionDispatch::Http::URL.url_for(options) end @@ -22,7 +22,7 @@ class BaseRequestTest < ActiveSupport::TestCase def stub_request(env = {}) ip_spoofing_check = env.key?(:ip_spoofing_check) ? env.delete(:ip_spoofing_check) : true @trusted_proxies ||= nil - ip_app = ActionDispatch::RemoteIp.new(Proc.new { }, ip_spoofing_check, @trusted_proxies) + ip_app = ActionDispatch::RemoteIp.new(Proc.new {}, ip_spoofing_check, @trusted_proxies) ActionDispatch::Http::URL.tld_length = env.delete(:tld_length) if env.key?(:tld_length) ip_app.call(env) @@ -34,78 +34,78 @@ end class RequestUrlFor < BaseRequestTest test "url_for class method" do - e = assert_raise(ArgumentError) { url_for(:host => nil) } + e = assert_raise(ArgumentError) { url_for(host: nil) } assert_match(/Please provide the :host parameter/, e.message) - assert_equal '/books', url_for(:only_path => true, :path => '/books') - - assert_equal 'http://www.example.com/books/?q=code', url_for(trailing_slash: true, path: '/books?q=code') - assert_equal 'http://www.example.com/books/?spareslashes=////', url_for(trailing_slash: true, path: '/books?spareslashes=////') - - assert_equal 'http://www.example.com', url_for - assert_equal 'http://api.example.com', url_for(:subdomain => 'api') - assert_equal 'http://example.com', url_for(:subdomain => false) - assert_equal 'http://www.ror.com', url_for(:domain => 'ror.com') - assert_equal 'http://api.ror.co.uk', url_for(:host => 'www.ror.co.uk', :subdomain => 'api', :tld_length => 2) - assert_equal 'http://www.example.com:8080', url_for(:port => 8080) - assert_equal 'https://www.example.com', url_for(:protocol => 'https') - assert_equal 'http://www.example.com/docs', url_for(:path => '/docs') - assert_equal 'http://www.example.com#signup', url_for(:anchor => 'signup') - assert_equal 'http://www.example.com/', url_for(:trailing_slash => true) - assert_equal 'http://dhh:supersecret@www.example.com', url_for(:user => 'dhh', :password => 'supersecret') - assert_equal 'http://www.example.com?search=books', url_for(:params => { :search => 'books' }) - assert_equal 'http://www.example.com?params=', url_for(:params => '') - assert_equal 'http://www.example.com?params=1', url_for(:params => 1) + assert_equal "/books", url_for(only_path: true, path: "/books") + + assert_equal "http://www.example.com/books/?q=code", url_for(trailing_slash: true, path: "/books?q=code") + assert_equal "http://www.example.com/books/?spareslashes=////", url_for(trailing_slash: true, path: "/books?spareslashes=////") + + assert_equal "http://www.example.com", url_for + assert_equal "http://api.example.com", url_for(subdomain: "api") + assert_equal "http://example.com", url_for(subdomain: false) + assert_equal "http://www.ror.com", url_for(domain: "ror.com") + assert_equal "http://api.ror.co.uk", url_for(host: "www.ror.co.uk", subdomain: "api", tld_length: 2) + assert_equal "http://www.example.com:8080", url_for(port: 8080) + assert_equal "https://www.example.com", url_for(protocol: "https") + assert_equal "http://www.example.com/docs", url_for(path: "/docs") + assert_equal "http://www.example.com#signup", url_for(anchor: "signup") + assert_equal "http://www.example.com/", url_for(trailing_slash: true) + assert_equal "http://dhh:supersecret@www.example.com", url_for(user: "dhh", password: "supersecret") + assert_equal "http://www.example.com?search=books", url_for(params: { search: "books" }) + assert_equal "http://www.example.com?params=", url_for(params: "") + assert_equal "http://www.example.com?params=1", url_for(params: 1) end end class RequestIP < BaseRequestTest test "remote ip" do - request = stub_request 'REMOTE_ADDR' => '1.2.3.4' - assert_equal '1.2.3.4', request.remote_ip + request = stub_request "REMOTE_ADDR" => "1.2.3.4" + assert_equal "1.2.3.4", request.remote_ip - request = stub_request 'REMOTE_ADDR' => '1.2.3.4,3.4.5.6' - assert_equal '3.4.5.6', request.remote_ip + request = stub_request "REMOTE_ADDR" => "1.2.3.4,3.4.5.6" + assert_equal "3.4.5.6", request.remote_ip - request = stub_request 'REMOTE_ADDR' => '1.2.3.4', - 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' - assert_equal '3.4.5.6', request.remote_ip + request = stub_request "REMOTE_ADDR" => "1.2.3.4", + "HTTP_X_FORWARDED_FOR" => "3.4.5.6" + assert_equal "3.4.5.6", request.remote_ip - request = stub_request 'REMOTE_ADDR' => '127.0.0.1', - 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' - assert_equal '3.4.5.6', request.remote_ip + request = stub_request "REMOTE_ADDR" => "127.0.0.1", + "HTTP_X_FORWARDED_FOR" => "3.4.5.6" + assert_equal "3.4.5.6", request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,unknown' - assert_equal '3.4.5.6', request.remote_ip + request = stub_request "HTTP_X_FORWARDED_FOR" => "3.4.5.6,unknown" + assert_equal "3.4.5.6", request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,172.16.0.1' - assert_equal '3.4.5.6', request.remote_ip + request = stub_request "HTTP_X_FORWARDED_FOR" => "3.4.5.6,172.16.0.1" + assert_equal "3.4.5.6", request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,192.168.0.1' - assert_equal '3.4.5.6', request.remote_ip + request = stub_request "HTTP_X_FORWARDED_FOR" => "3.4.5.6,192.168.0.1" + assert_equal "3.4.5.6", request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,10.0.0.1' - assert_equal '3.4.5.6', request.remote_ip + request = stub_request "HTTP_X_FORWARDED_FOR" => "3.4.5.6,10.0.0.1" + assert_equal "3.4.5.6", request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6, 10.0.0.1, 10.0.0.1' - assert_equal '3.4.5.6', request.remote_ip + request = stub_request "HTTP_X_FORWARDED_FOR" => "3.4.5.6, 10.0.0.1, 10.0.0.1" + assert_equal "3.4.5.6", request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,127.0.0.1' - assert_equal '3.4.5.6', request.remote_ip + request = stub_request "HTTP_X_FORWARDED_FOR" => "3.4.5.6,127.0.0.1" + assert_equal "3.4.5.6", request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,192.168.0.1' + request = stub_request "HTTP_X_FORWARDED_FOR" => "unknown,192.168.0.1" assert_equal 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" => "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' + request = stub_request "HTTP_X_FORWARDED_FOR" => "not_ip_address" assert_equal nil, request.remote_ip end test "remote ip spoof detection" do - request = stub_request 'HTTP_X_FORWARDED_FOR' => '1.1.1.1', - 'HTTP_CLIENT_IP' => '2.2.2.2' + request = stub_request "HTTP_X_FORWARDED_FOR" => "1.1.1.1", + "HTTP_CLIENT_IP" => "2.2.2.2" e = assert_raise(ActionDispatch::RemoteIp::IpSpoofAttackError) { request.remote_ip } @@ -115,60 +115,60 @@ class RequestIP < BaseRequestTest end test "remote ip with spoof detection disabled" do - request = stub_request 'HTTP_X_FORWARDED_FOR' => '1.1.1.1', - 'HTTP_CLIENT_IP' => '2.2.2.2', + request = stub_request "HTTP_X_FORWARDED_FOR" => "1.1.1.1", + "HTTP_CLIENT_IP" => "2.2.2.2", :ip_spoofing_check => false - assert_equal '1.1.1.1', request.remote_ip + assert_equal "1.1.1.1", request.remote_ip end test "remote ip spoof protection ignores private addresses" do - request = stub_request 'HTTP_X_FORWARDED_FOR' => '172.17.19.51', - 'HTTP_CLIENT_IP' => '172.17.19.51', - 'REMOTE_ADDR' => '1.1.1.1', - 'HTTP_X_BLUECOAT_VIA' => 'de462e07a2db325e' - assert_equal '1.1.1.1', request.remote_ip + request = stub_request "HTTP_X_FORWARDED_FOR" => "172.17.19.51", + "HTTP_CLIENT_IP" => "172.17.19.51", + "REMOTE_ADDR" => "1.1.1.1", + "HTTP_X_BLUECOAT_VIA" => "de462e07a2db325e" + assert_equal "1.1.1.1", request.remote_ip end test "remote ip v6" do - request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334' - assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip + request = stub_request "REMOTE_ADDR" => "2001:0db8:85a3:0000:0000:8a2e:0370:7334" + assert_equal "2001:0db8:85a3:0000:0000:8a2e:0370:7334", request.remote_ip - request = stub_request 'REMOTE_ADDR' => '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 + request = stub_request "REMOTE_ADDR" => "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 - request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', - 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' - assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip + request = stub_request "REMOTE_ADDR" => "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "HTTP_X_FORWARDED_FOR" => "fe80:0000:0000:0000:0202:b3ff:fe1e:8329" + assert_equal "fe80:0000:0000:0000:0202:b3ff:fe1e:8329", request.remote_ip - request = stub_request 'REMOTE_ADDR' => '::1', - 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' - assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip + request = stub_request "REMOTE_ADDR" => "::1", + "HTTP_X_FORWARDED_FOR" => "fe80:0000:0000:0000:0202:b3ff:fe1e:8329" + assert_equal "fe80:0000:0000:0000:0202:b3ff:fe1e:8329", request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,unknown' - assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip + request = stub_request "HTTP_X_FORWARDED_FOR" => "fe80:0000:0000:0000:0202:b3ff:fe1e:8329,unknown" + assert_equal "fe80:0000:0000:0000:0202:b3ff:fe1e:8329", request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,::1' - assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip + request = stub_request "HTTP_X_FORWARDED_FOR" => "fe80:0000:0000:0000:0202:b3ff:fe1e:8329,::1" + assert_equal "fe80:0000:0000:0000:0202:b3ff:fe1e:8329", request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329, ::1, ::1' - assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip + request = stub_request "HTTP_X_FORWARDED_FOR" => "fe80:0000:0000:0000:0202:b3ff:fe1e:8329, ::1, ::1" + assert_equal "fe80:0000:0000:0000:0202:b3ff:fe1e:8329", request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,::1' + request = stub_request "HTTP_X_FORWARDED_FOR" => "unknown,::1" assert_equal 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 + 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 - request = stub_request 'HTTP_X_FORWARDED_FOR' => 'FE00::, FDFF::' - assert_equal 'FE00::', request.remote_ip + request = stub_request "HTTP_X_FORWARDED_FOR" => "FE00::, FDFF::" + assert_equal "FE00::", request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => 'not_ip_address' + request = stub_request "HTTP_X_FORWARDED_FOR" => "not_ip_address" assert_equal nil, request.remote_ip end test "remote ip v6 spoof detection" do - request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', - 'HTTP_CLIENT_IP' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334' + request = stub_request "HTTP_X_FORWARDED_FOR" => "fe80:0000:0000:0000:0202:b3ff:fe1e:8329", + "HTTP_CLIENT_IP" => "2001:0db8:85a3:0000:0000:8a2e:0370:7334" e = assert_raise(ActionDispatch::RemoteIp::IpSpoofAttackError) { request.remote_ip } @@ -178,139 +178,139 @@ class RequestIP < BaseRequestTest end test "remote ip v6 spoof detection disabled" do - request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', - 'HTTP_CLIENT_IP' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + request = stub_request "HTTP_X_FORWARDED_FOR" => "fe80:0000:0000:0000:0202:b3ff:fe1e:8329", + "HTTP_CLIENT_IP" => "2001:0db8:85a3:0000:0000:8a2e:0370:7334", :ip_spoofing_check => false - assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip + assert_equal "fe80:0000:0000:0000:0202:b3ff:fe1e:8329", request.remote_ip end test "remote ip with user specified trusted proxies String" do @trusted_proxies = "67.205.106.73" - request = stub_request 'REMOTE_ADDR' => '3.4.5.6', - 'HTTP_X_FORWARDED_FOR' => '67.205.106.73' - assert_equal '3.4.5.6', request.remote_ip + request = stub_request "REMOTE_ADDR" => "3.4.5.6", + "HTTP_X_FORWARDED_FOR" => "67.205.106.73" + assert_equal "3.4.5.6", request.remote_ip - request = stub_request 'REMOTE_ADDR' => '172.16.0.1,67.205.106.73', - 'HTTP_X_FORWARDED_FOR' => '67.205.106.73' - assert_equal '67.205.106.73', request.remote_ip + request = stub_request "REMOTE_ADDR" => "172.16.0.1,67.205.106.73", + "HTTP_X_FORWARDED_FOR" => "67.205.106.73" + assert_equal "67.205.106.73", request.remote_ip - request = stub_request 'REMOTE_ADDR' => '67.205.106.73,3.4.5.6', - 'HTTP_X_FORWARDED_FOR' => '67.205.106.73' - assert_equal '3.4.5.6', request.remote_ip + request = stub_request "REMOTE_ADDR" => "67.205.106.73,3.4.5.6", + "HTTP_X_FORWARDED_FOR" => "67.205.106.73" + assert_equal "3.4.5.6", request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '67.205.106.73,unknown' + request = stub_request "HTTP_X_FORWARDED_FOR" => "67.205.106.73,unknown" assert_equal 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 + 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 end test "remote ip v6 with user specified trusted proxies String" do - @trusted_proxies = 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' + @trusted_proxies = "fe80:0000:0000:0000:0202:b3ff:fe1e:8329" - request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', - 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' - assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip + request = stub_request "REMOTE_ADDR" => "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "HTTP_X_FORWARDED_FOR" => "fe80:0000:0000:0000:0202:b3ff:fe1e:8329" + assert_equal "2001:0db8:85a3:0000:0000:8a2e:0370:7334", request.remote_ip - request = stub_request 'REMOTE_ADDR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,2001:0db8:85a3:0000:0000:8a2e:0370:7334', - 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' - assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip + request = stub_request "REMOTE_ADDR" => "fe80:0000:0000:0000:0202:b3ff:fe1e:8329,2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "HTTP_X_FORWARDED_FOR" => "fe80:0000:0000:0000:0202:b3ff:fe1e:8329" + assert_equal "2001:0db8:85a3:0000:0000:8a2e:0370:7334", request.remote_ip - request = stub_request 'REMOTE_ADDR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,::1', - 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' - assert_equal '::1', request.remote_ip + request = stub_request "REMOTE_ADDR" => "fe80:0000:0000:0000:0202:b3ff:fe1e:8329,::1", + "HTTP_X_FORWARDED_FOR" => "fe80:0000:0000:0000:0202:b3ff:fe1e:8329" + assert_equal "::1", request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' + request = stub_request "HTTP_X_FORWARDED_FOR" => "unknown,fe80:0000:0000:0000:0202:b3ff:fe1e:8329" assert_equal 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' + 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 end test "remote ip with user specified trusted proxies Regexp" do @trusted_proxies = /^67\.205\.106\.73$/i - request = stub_request 'REMOTE_ADDR' => '67.205.106.73', - 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' - assert_equal '3.4.5.6', request.remote_ip + request = stub_request "REMOTE_ADDR" => "67.205.106.73", + "HTTP_X_FORWARDED_FOR" => "3.4.5.6" + assert_equal "3.4.5.6", request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '10.0.0.1, 9.9.9.9, 3.4.5.6, 67.205.106.73' - assert_equal '3.4.5.6', request.remote_ip + request = stub_request "HTTP_X_FORWARDED_FOR" => "10.0.0.1, 9.9.9.9, 3.4.5.6, 67.205.106.73" + assert_equal "3.4.5.6", request.remote_ip end test "remote ip v6 with user specified trusted proxies Regexp" do @trusted_proxies = /^fe80:0000:0000:0000:0202:b3ff:fe1e:8329$/i - request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', - 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' - assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip + request = stub_request "REMOTE_ADDR" => "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "HTTP_X_FORWARDED_FOR" => "fe80:0000:0000:0000:0202:b3ff:fe1e:8329" + assert_equal "2001:0db8:85a3:0000:0000:8a2e:0370:7334", 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' - assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', 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" + assert_equal "2001:0db8:85a3:0000:0000:8a2e:0370:7334", request.remote_ip end test "remote ip middleware not present still returns an IP" do - request = stub_request('REMOTE_ADDR' => '127.0.0.1') - assert_equal '127.0.0.1', request.remote_ip + request = stub_request("REMOTE_ADDR" => "127.0.0.1") + assert_equal "127.0.0.1", request.remote_ip end end class RequestDomain < BaseRequestTest test "domains" do - request = stub_request 'HTTP_HOST' => "192.168.1.200" + request = stub_request "HTTP_HOST" => "192.168.1.200" assert_nil request.domain - request = stub_request 'HTTP_HOST' => "foo.192.168.1.200" + request = stub_request "HTTP_HOST" => "foo.192.168.1.200" assert_nil request.domain - request = stub_request 'HTTP_HOST' => "192.168.1.200.com" + request = stub_request "HTTP_HOST" => "192.168.1.200.com" assert_equal "200.com", request.domain - request = stub_request 'HTTP_HOST' => 'www.rubyonrails.org' + request = stub_request "HTTP_HOST" => "www.rubyonrails.org" assert_equal "rubyonrails.org", request.domain - request = stub_request 'HTTP_HOST' => "www.rubyonrails.co.uk" + request = stub_request "HTTP_HOST" => "www.rubyonrails.co.uk" assert_equal "rubyonrails.co.uk", request.domain(2) - request = stub_request 'HTTP_HOST' => "www.rubyonrails.co.uk", :tld_length => 2 + request = stub_request "HTTP_HOST" => "www.rubyonrails.co.uk", :tld_length => 2 assert_equal "rubyonrails.co.uk", request.domain end test "subdomains" do - request = stub_request 'HTTP_HOST' => "foobar.foobar.com" + request = stub_request "HTTP_HOST" => "foobar.foobar.com" assert_equal %w( foobar ), request.subdomains assert_equal "foobar", request.subdomain - request = stub_request 'HTTP_HOST' => "192.168.1.200" + request = stub_request "HTTP_HOST" => "192.168.1.200" assert_equal [], request.subdomains assert_equal "", request.subdomain - request = stub_request 'HTTP_HOST' => "foo.192.168.1.200" + request = stub_request "HTTP_HOST" => "foo.192.168.1.200" assert_equal [], request.subdomains assert_equal "", request.subdomain - request = stub_request 'HTTP_HOST' => "192.168.1.200.com" + request = stub_request "HTTP_HOST" => "192.168.1.200.com" assert_equal %w( 192 168 1 ), request.subdomains assert_equal "192.168.1", request.subdomain - request = stub_request 'HTTP_HOST' => nil + request = stub_request "HTTP_HOST" => nil assert_equal [], request.subdomains assert_equal "", request.subdomain - request = stub_request 'HTTP_HOST' => "www.rubyonrails.org" + request = stub_request "HTTP_HOST" => "www.rubyonrails.org" assert_equal %w( www ), request.subdomains assert_equal "www", request.subdomain - request = stub_request 'HTTP_HOST' => "www.rubyonrails.co.uk" + request = stub_request "HTTP_HOST" => "www.rubyonrails.co.uk" assert_equal %w( www ), request.subdomains(2) assert_equal "www", request.subdomain(2) - request = stub_request 'HTTP_HOST' => "dev.www.rubyonrails.co.uk" + request = stub_request "HTTP_HOST" => "dev.www.rubyonrails.co.uk" assert_equal %w( dev www ), request.subdomains(2) assert_equal "dev.www", request.subdomain(2) - request = stub_request 'HTTP_HOST' => "dev.www.rubyonrails.co.uk", :tld_length => 2 + request = stub_request "HTTP_HOST" => "dev.www.rubyonrails.co.uk", :tld_length => 2 assert_equal %w( dev www ), request.subdomains assert_equal "dev.www", request.subdomain end @@ -321,7 +321,7 @@ class RequestPort < BaseRequestTest request = stub_request assert_equal 80, request.standard_port - request = stub_request 'HTTPS' => 'on' + request = stub_request "HTTPS" => "on" assert_equal 443, request.standard_port end @@ -330,86 +330,97 @@ class RequestPort < BaseRequestTest assert !request.ssl? assert request.standard_port? - request = stub_request 'HTTPS' => 'on' + request = stub_request "HTTPS" => "on" assert request.ssl? assert request.standard_port? - request = stub_request 'HTTP_HOST' => 'www.example.org:8080' + request = stub_request "HTTP_HOST" => "www.example.org:8080" assert !request.ssl? assert !request.standard_port? - request = stub_request 'HTTP_HOST' => 'www.example.org:8443', 'HTTPS' => 'on' + request = stub_request "HTTP_HOST" => "www.example.org:8443", "HTTPS" => "on" assert request.ssl? assert !request.standard_port? end test "optional port" do - request = stub_request 'HTTP_HOST' => 'www.example.org:80' + request = stub_request "HTTP_HOST" => "www.example.org:80" assert_equal nil, request.optional_port - request = stub_request 'HTTP_HOST' => 'www.example.org:8080' + request = stub_request "HTTP_HOST" => "www.example.org:8080" assert_equal 8080, request.optional_port end test "port string" do - request = stub_request 'HTTP_HOST' => 'www.example.org:80' - assert_equal '', request.port_string + request = stub_request "HTTP_HOST" => "www.example.org:80" + assert_equal "", request.port_string - request = stub_request 'HTTP_HOST' => 'www.example.org:8080' - assert_equal ':8080', request.port_string + request = stub_request "HTTP_HOST" => "www.example.org:8080" + assert_equal ":8080", request.port_string + end + + test "server port" do + request = stub_request "SERVER_PORT" => "8080" + assert_equal 8080, request.server_port + + request = stub_request "SERVER_PORT" => "80" + assert_equal 80, request.server_port + + request = stub_request "SERVER_PORT" => "" + assert_equal 0, request.server_port end end class RequestPath < BaseRequestTest test "full path" do - request = stub_request 'SCRIPT_NAME' => '', 'PATH_INFO' => '/path/of/some/uri', 'QUERY_STRING' => 'mapped=1' + request = stub_request "SCRIPT_NAME" => "", "PATH_INFO" => "/path/of/some/uri", "QUERY_STRING" => "mapped=1" assert_equal "/path/of/some/uri?mapped=1", request.fullpath assert_equal "/path/of/some/uri", request.path_info - request = stub_request 'SCRIPT_NAME' => '', 'PATH_INFO' => '/path/of/some/uri' + request = stub_request "SCRIPT_NAME" => "", "PATH_INFO" => "/path/of/some/uri" assert_equal "/path/of/some/uri", request.fullpath assert_equal "/path/of/some/uri", request.path_info - request = stub_request 'SCRIPT_NAME' => '', 'PATH_INFO' => '/' + request = stub_request "SCRIPT_NAME" => "", "PATH_INFO" => "/" assert_equal "/", request.fullpath assert_equal "/", request.path_info - request = stub_request 'SCRIPT_NAME' => '', 'PATH_INFO' => '/', 'QUERY_STRING' => 'm=b' + request = stub_request "SCRIPT_NAME" => "", "PATH_INFO" => "/", "QUERY_STRING" => "m=b" assert_equal "/?m=b", request.fullpath assert_equal "/", request.path_info - request = stub_request 'SCRIPT_NAME' => '/hieraki', 'PATH_INFO' => '/' + request = stub_request "SCRIPT_NAME" => "/hieraki", "PATH_INFO" => "/" assert_equal "/hieraki/", request.fullpath assert_equal "/", request.path_info - request = stub_request 'SCRIPT_NAME' => '/collaboration/hieraki', 'PATH_INFO' => '/books/edit/2' + request = stub_request "SCRIPT_NAME" => "/collaboration/hieraki", "PATH_INFO" => "/books/edit/2" assert_equal "/collaboration/hieraki/books/edit/2", request.fullpath assert_equal "/books/edit/2", request.path_info - request = stub_request 'SCRIPT_NAME' => '/path', 'PATH_INFO' => '/of/some/uri', 'QUERY_STRING' => 'mapped=1' + request = stub_request "SCRIPT_NAME" => "/path", "PATH_INFO" => "/of/some/uri", "QUERY_STRING" => "mapped=1" assert_equal "/path/of/some/uri?mapped=1", request.fullpath assert_equal "/of/some/uri", request.path_info end test "original_fullpath returns ORIGINAL_FULLPATH" do - request = stub_request('ORIGINAL_FULLPATH' => "/foo?bar") + request = stub_request("ORIGINAL_FULLPATH" => "/foo?bar") path = request.original_fullpath assert_equal "/foo?bar", path end test "original_url returns url built using ORIGINAL_FULLPATH" do - request = stub_request('ORIGINAL_FULLPATH' => "/foo?bar", - 'HTTP_HOST' => "example.org", - 'rack.url_scheme' => "http") + request = stub_request("ORIGINAL_FULLPATH" => "/foo?bar", + "HTTP_HOST" => "example.org", + "rack.url_scheme" => "http") url = request.original_url assert_equal "http://example.org/foo?bar", url end test "original_fullpath returns fullpath if ORIGINAL_FULLPATH is not present" do - request = stub_request('PATH_INFO' => "/foo", - 'QUERY_STRING' => "bar") + request = stub_request("PATH_INFO" => "/foo", + "QUERY_STRING" => "bar") path = request.original_fullpath assert_equal "/foo?bar", path @@ -417,58 +428,78 @@ class RequestPath < BaseRequestTest end class RequestHost < BaseRequestTest + test "host without specifying port" do + request = stub_request "HTTP_HOST" => "rubyonrails.org" + assert_equal "rubyonrails.org", request.host_with_port + end + test "host with default port" do - request = stub_request 'HTTP_HOST' => 'rubyonrails.org:80' + request = stub_request "HTTP_HOST" => "rubyonrails.org:80" assert_equal "rubyonrails.org", request.host_with_port end test "host with non default port" do - request = stub_request 'HTTP_HOST' => 'rubyonrails.org:81' + request = stub_request "HTTP_HOST" => "rubyonrails.org:81" assert_equal "rubyonrails.org:81", request.host_with_port end + test "raw without specifying port" do + request = stub_request "HTTP_HOST" => "rubyonrails.org" + assert_equal "rubyonrails.org", request.raw_host_with_port + end + + test "raw host with default port" do + request = stub_request "HTTP_HOST" => "rubyonrails.org:80" + assert_equal "rubyonrails.org:80", request.raw_host_with_port + end + + test "raw host with non default port" do + request = stub_request "HTTP_HOST" => "rubyonrails.org:81" + assert_equal "rubyonrails.org:81", request.raw_host_with_port + end + test "proxy request" do - request = stub_request 'HTTP_HOST' => 'glu.ttono.us:80' + request = stub_request "HTTP_HOST" => "glu.ttono.us:80" assert_equal "glu.ttono.us", request.host_with_port end test "http host" do - request = stub_request 'HTTP_HOST' => "rubyonrails.org:8080" + request = stub_request "HTTP_HOST" => "rubyonrails.org:8080" assert_equal "rubyonrails.org", request.host assert_equal "rubyonrails.org:8080", request.host_with_port - request = stub_request 'HTTP_X_FORWARDED_HOST' => "www.firsthost.org, www.secondhost.org" + request = stub_request "HTTP_X_FORWARDED_HOST" => "www.firsthost.org, www.secondhost.org" assert_equal "www.secondhost.org", request.host - request = stub_request 'HTTP_X_FORWARDED_HOST' => "", 'HTTP_HOST' => "rubyonrails.org" + request = stub_request "HTTP_X_FORWARDED_HOST" => "", "HTTP_HOST" => "rubyonrails.org" assert_equal "rubyonrails.org", request.host end test "http host with default port overrides server port" do - request = stub_request 'HTTP_HOST' => "rubyonrails.org" + request = stub_request "HTTP_HOST" => "rubyonrails.org" assert_equal "rubyonrails.org", request.host_with_port end test "host with port if http standard port is specified" do - request = stub_request 'HTTP_X_FORWARDED_HOST' => "glu.ttono.us:80" + request = stub_request "HTTP_X_FORWARDED_HOST" => "glu.ttono.us:80" assert_equal "glu.ttono.us", request.host_with_port end test "host with port if https standard port is specified" do request = stub_request( - 'HTTP_X_FORWARDED_PROTO' => "https", - 'HTTP_X_FORWARDED_HOST' => "glu.ttono.us:443" + "HTTP_X_FORWARDED_PROTO" => "https", + "HTTP_X_FORWARDED_HOST" => "glu.ttono.us:443" ) assert_equal "glu.ttono.us", request.host_with_port end test "host if ipv6 reference" do - request = stub_request 'HTTP_HOST' => "[2001:1234:5678:9abc:def0::dead:beef]" + request = stub_request "HTTP_HOST" => "[2001:1234:5678:9abc:def0::dead:beef]" assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", request.host end test "host if ipv6 reference with port" do - request = stub_request 'HTTP_HOST' => "[2001:1234:5678:9abc:def0::dead:beef]:8008" + request = stub_request "HTTP_HOST" => "[2001:1234:5678:9abc:def0::dead:beef]:8008" assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", request.host end end @@ -559,28 +590,28 @@ end class RequestParamsParsing < BaseRequestTest test "doesnt break when content type has charset" do request = stub_request( - 'REQUEST_METHOD' => 'POST', - 'CONTENT_LENGTH' => "flamenco=love".length, - 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', - 'rack.input' => StringIO.new("flamenco=love") + "REQUEST_METHOD" => "POST", + "CONTENT_LENGTH" => "flamenco=love".length, + "CONTENT_TYPE" => "application/x-www-form-urlencoded; charset=utf-8", + "rack.input" => StringIO.new("flamenco=love") ) - assert_equal({"flamenco"=> "love"}, request.request_parameters) + assert_equal({ "flamenco"=> "love" }, request.request_parameters) end test "doesnt interpret request uri as query string when missing" do - request = stub_request('REQUEST_URI' => 'foo') + request = stub_request("REQUEST_URI" => "foo") assert_equal({}, request.query_parameters) end end class RequestRewind < BaseRequestTest test "body should be rewound" do - data = 'rewind' + data = "rewind" env = { - 'rack.input' => StringIO.new(data), - 'CONTENT_LENGTH' => data.length, - 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8' + "rack.input" => StringIO.new(data), + "CONTENT_LENGTH" => data.length, + "CONTENT_TYPE" => "application/x-www-form-urlencoded; charset=utf-8" } # Read the request body by parsing params. @@ -593,18 +624,18 @@ class RequestRewind < BaseRequestTest test "raw_post rewinds rack.input if RAW_POST_DATA is nil" do request = stub_request( - 'rack.input' => StringIO.new("raw"), - 'CONTENT_LENGTH' => 3 + "rack.input" => StringIO.new("raw"), + "CONTENT_LENGTH" => 3 ) assert_equal "raw", request.raw_post - assert_equal "raw", request.env['rack.input'].read + assert_equal "raw", request.env["rack.input"].read end end class RequestProtocol < BaseRequestTest test "server software" do - assert_equal 'lighttpd', stub_request('SERVER_SOFTWARE' => 'lighttpd/1.4.5').server_software - assert_equal 'apache', stub_request('SERVER_SOFTWARE' => 'Apache3.422').server_software + assert_equal "lighttpd", stub_request("SERVER_SOFTWARE" => "lighttpd/1.4.5").server_software + assert_equal "apache", stub_request("SERVER_SOFTWARE" => "Apache3.422").server_software end test "xml http request" do @@ -613,35 +644,35 @@ class RequestProtocol < BaseRequestTest assert !request.xml_http_request? assert !request.xhr? - request = stub_request 'HTTP_X_REQUESTED_WITH' => 'DefinitelyNotAjax1.0' + request = stub_request "HTTP_X_REQUESTED_WITH" => "DefinitelyNotAjax1.0" assert !request.xml_http_request? assert !request.xhr? - request = stub_request 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest' + request = stub_request "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest" assert request.xml_http_request? assert request.xhr? end test "reports ssl" do assert !stub_request.ssl? - assert stub_request('HTTPS' => 'on').ssl? + assert stub_request("HTTPS" => "on").ssl? end test "reports ssl when proxied via lighttpd" do - assert stub_request('HTTP_X_FORWARDED_PROTO' => 'https').ssl? + assert stub_request("HTTP_X_FORWARDED_PROTO" => "https").ssl? end test "scheme returns https when proxied" do - request = stub_request 'rack.url_scheme' => 'http' + request = stub_request "rack.url_scheme" => "http" assert !request.ssl? - assert_equal 'http', request.scheme + assert_equal "http", request.scheme request = stub_request( - 'rack.url_scheme' => 'http', - 'HTTP_X_FORWARDED_PROTO' => 'https' + "rack.url_scheme" => "http", + "HTTP_X_FORWARDED_PROTO" => "https" ) assert request.ssl? - assert_equal 'https', request.scheme + assert_equal "https", request.scheme end end @@ -650,7 +681,7 @@ class RequestMethod < BaseRequestTest overridden by middleware".squish do ActionDispatch::Request::HTTP_METHODS.each do |method| - request = stub_request('REQUEST_METHOD' => method) + request = stub_request("REQUEST_METHOD" => method) assert_equal method, request.method assert_equal method.underscore.to_sym, request.method_symbol @@ -658,36 +689,36 @@ class RequestMethod < BaseRequestTest end test "allow request method hacking" do - request = stub_request('REQUEST_METHOD' => 'POST') + request = stub_request("REQUEST_METHOD" => "POST") - assert_equal 'POST', request.request_method - assert_equal 'POST', request.env["REQUEST_METHOD"] + assert_equal "POST", request.request_method + assert_equal "POST", request.env["REQUEST_METHOD"] - request.request_method = 'GET' + request.request_method = "GET" - assert_equal 'GET', request.request_method - assert_equal 'GET', request.env["REQUEST_METHOD"] + assert_equal "GET", request.request_method + assert_equal "GET", request.env["REQUEST_METHOD"] assert request.get? end test "invalid http method raises exception" do assert_raise(ActionController::UnknownHttpMethod) do - stub_request('REQUEST_METHOD' => 'RANDOM_METHOD').request_method + stub_request("REQUEST_METHOD" => "RANDOM_METHOD").request_method end end test "method returns original value of environment request method on POST" do - request = stub_request('rack.methodoverride.original_method' => 'POST') - assert_equal 'POST', request.method + request = stub_request("rack.methodoverride.original_method" => "POST") + assert_equal "POST", request.method end test "method raises exception on invalid HTTP method" do assert_raise(ActionController::UnknownHttpMethod) do - stub_request('rack.methodoverride.original_method' => '_RANDOM_METHOD').method + stub_request("rack.methodoverride.original_method" => "_RANDOM_METHOD").method end assert_raise(ActionController::UnknownHttpMethod) do - stub_request('REQUEST_METHOD' => '_RANDOM_METHOD').method + stub_request("REQUEST_METHOD" => "_RANDOM_METHOD").method end end @@ -699,7 +730,7 @@ class RequestMethod < BaseRequestTest I18n.available_locales = [:nl] I18n.config.enforce_available_locales = true assert_raise(ActionController::UnknownHttpMethod) do - stub_request('REQUEST_METHOD' => '_RANDOM_METHOD').method + stub_request("REQUEST_METHOD" => "_RANDOM_METHOD").method end ensure I18n.available_locales = old_locales @@ -709,7 +740,7 @@ class RequestMethod < BaseRequestTest test "post masquerading as patch" do request = stub_request( - 'REQUEST_METHOD' => 'PATCH', + "REQUEST_METHOD" => "PATCH", "rack.methodoverride.original_method" => "POST" ) @@ -720,7 +751,7 @@ class RequestMethod < BaseRequestTest test "post masquerading as put" do request = stub_request( - 'REQUEST_METHOD' => 'PUT', + "REQUEST_METHOD" => "PUT", "rack.methodoverride.original_method" => "POST" ) assert_equal "POST", request.method @@ -753,29 +784,29 @@ end class RequestFormat < BaseRequestTest test "xml format" do request = stub_request - assert_called(request, :parameters, times: 2, returns: {format: :xml}) do + assert_called(request, :parameters, times: 2, returns: { format: :xml }) do assert_equal Mime[:xml], request.format end end test "xhtml format" do request = stub_request - assert_called(request, :parameters, times: 2, returns: {format: :xhtml}) do + assert_called(request, :parameters, times: 2, returns: { format: :xhtml }) do assert_equal Mime[:html], request.format end end test "txt format" do request = stub_request - assert_called(request, :parameters, times: 2, returns: {format: :txt}) do + assert_called(request, :parameters, times: 2, returns: { format: :txt }) do assert_equal Mime[:text], request.format end end test "XMLHttpRequest" do request = stub_request( - 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest', - 'HTTP_ACCEPT' => [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(",") + "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", + "HTTP_ACCEPT" => [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(",") ) assert_called(request, :parameters, times: 1, returns: {}) do @@ -786,56 +817,56 @@ class RequestFormat < BaseRequestTest test "can override format with parameter negative" do request = stub_request - assert_called(request, :parameters, times: 2, returns: {format: :txt}) do + assert_called(request, :parameters, times: 2, returns: { format: :txt }) do assert !request.format.xml? end end test "can override format with parameter positive" do request = stub_request - assert_called(request, :parameters, times: 2, returns: {format: :xml}) do + assert_called(request, :parameters, times: 2, returns: { format: :xml }) do assert request.format.xml? end end test "formats text/html with accept header" do - request = stub_request 'HTTP_ACCEPT' => 'text/html' + request = stub_request "HTTP_ACCEPT" => "text/html" assert_equal [Mime[:html]], request.formats end test "formats blank with accept header" do - request = stub_request 'HTTP_ACCEPT' => '' + request = stub_request "HTTP_ACCEPT" => "" assert_equal [Mime[:html]], request.formats end test "formats XMLHttpRequest with accept header" do - request = stub_request 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" + request = stub_request "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest" assert_equal [Mime[:js]], request.formats end test "formats application/xml with accept header" do - request = stub_request('CONTENT_TYPE' => 'application/xml; charset=UTF-8', - 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest") + request = stub_request("CONTENT_TYPE" => "application/xml; charset=UTF-8", + "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest") assert_equal [Mime[:xml]], request.formats end test "formats format:text with accept header" do request = stub_request - assert_called(request, :parameters, times: 2, returns: {format: :txt}) do + assert_called(request, :parameters, times: 2, returns: { format: :txt }) do assert_equal [Mime[:text]], request.formats end end test "formats format:unknown with accept header" do request = stub_request - assert_called(request, :parameters, times: 2, returns: {format: :unknown}) do + assert_called(request, :parameters, times: 2, returns: { format: :unknown }) do assert_instance_of Mime::NullType, request.format end end test "format is not nil with unknown format" do request = stub_request - assert_called(request, :parameters, times: 2, returns: {format: :hello}) do + assert_called(request, :parameters, times: 2, returns: { format: :hello }) do assert request.format.nil? assert_not request.format.html? assert_not request.format.xml? @@ -850,7 +881,7 @@ class RequestFormat < BaseRequestTest end test "formats with xhr request" do - request = stub_request 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" + request = stub_request "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest" assert_called(request, :parameters, times: 1, returns: {}) do assert_equal [Mime[:js]], request.formats end @@ -861,36 +892,36 @@ class RequestFormat < BaseRequestTest ActionDispatch::Request.ignore_accept_header = true begin - request = stub_request 'HTTP_ACCEPT' => 'application/xml' + request = stub_request "HTTP_ACCEPT" => "application/xml" assert_called(request, :parameters, times: 1, returns: {}) do assert_equal [ Mime[:html] ], request.formats end - request = stub_request 'HTTP_ACCEPT' => 'koz-asked/something-crazy' + request = stub_request "HTTP_ACCEPT" => "koz-asked/something-crazy" assert_called(request, :parameters, times: 1, returns: {}) do assert_equal [ Mime[:html] ], request.formats end - request = stub_request 'HTTP_ACCEPT' => '*/*;q=0.1' + request = stub_request "HTTP_ACCEPT" => "*/*;q=0.1" assert_called(request, :parameters, times: 1, returns: {}) do assert_equal [ Mime[:html] ], request.formats end - request = stub_request 'HTTP_ACCEPT' => 'application/jxw' + request = stub_request "HTTP_ACCEPT" => "application/jxw" assert_called(request, :parameters, times: 1, returns: {}) do assert_equal [ Mime[:html] ], request.formats end - request = stub_request 'HTTP_ACCEPT' => 'application/xml', - 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" + request = stub_request "HTTP_ACCEPT" => "application/xml", + "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest" assert_called(request, :parameters, times: 1, returns: {}) do assert_equal [ Mime[:js] ], request.formats end - request = stub_request 'HTTP_ACCEPT' => 'application/xml', - 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" - assert_called(request, :parameters, times: 2, returns: {format: :json}) do + request = stub_request "HTTP_ACCEPT" => "application/xml", + "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest" + assert_called(request, :parameters, times: 2, returns: { format: :json }) do assert_equal [ Mime[:json] ], request.formats end ensure @@ -899,20 +930,20 @@ class RequestFormat < BaseRequestTest end test "format taken from the path extension" do - request = stub_request 'PATH_INFO' => '/foo.xml' + request = stub_request "PATH_INFO" => "/foo.xml" assert_called(request, :parameters, times: 1, returns: {}) do assert_equal [Mime[:xml]], request.formats end - request = stub_request 'PATH_INFO' => '/foo.123' + request = stub_request "PATH_INFO" => "/foo.123" assert_called(request, :parameters, times: 1, returns: {}) do assert_equal [Mime[:html]], request.formats end end test "formats from accept headers have higher precedence than path extension" do - request = stub_request 'HTTP_ACCEPT' => 'application/json', - 'PATH_INFO' => '/foo.xml' + request = stub_request "HTTP_ACCEPT" => "application/json", + "PATH_INFO" => "/foo.xml" assert_called(request, :parameters, times: 1, returns: {}) do assert_equal [Mime[:json]], request.formats @@ -922,7 +953,7 @@ end class RequestMimeType < BaseRequestTest test "content type" do - assert_equal Mime[:html], stub_request('CONTENT_TYPE' => 'text/html').content_mime_type + assert_equal Mime[:html], stub_request("CONTENT_TYPE" => "text/html").content_mime_type end test "no content type" do @@ -930,21 +961,21 @@ class RequestMimeType < BaseRequestTest end test "content type is XML" do - assert_equal Mime[:xml], stub_request('CONTENT_TYPE' => 'application/xml').content_mime_type + assert_equal Mime[:xml], stub_request("CONTENT_TYPE" => "application/xml").content_mime_type end test "content type with charset" do - assert_equal Mime[:xml], stub_request('CONTENT_TYPE' => 'application/xml; charset=UTF-8').content_mime_type + assert_equal Mime[:xml], stub_request("CONTENT_TYPE" => "application/xml; charset=UTF-8").content_mime_type end test "user agent" do - assert_equal 'TestAgent', stub_request('HTTP_USER_AGENT' => 'TestAgent').user_agent + assert_equal "TestAgent", stub_request("HTTP_USER_AGENT" => "TestAgent").user_agent end test "negotiate_mime" do request = stub_request( - 'HTTP_ACCEPT' => 'text/html', - 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" + "HTTP_ACCEPT" => "text/html", + "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest" ) assert_equal nil, request.negotiate_mime([Mime[:xml], Mime[:json]]) @@ -954,8 +985,8 @@ class RequestMimeType < BaseRequestTest test "negotiate_mime with content_type" do request = stub_request( - 'CONTENT_TYPE' => 'application/xml; charset=UTF-8', - 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" + "CONTENT_TYPE" => "application/xml; charset=UTF-8", + "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest" ) assert_equal Mime[:xml], request.negotiate_mime([Mime[:xml], Mime[:csv]]) @@ -966,11 +997,11 @@ class RequestParameters < BaseRequestTest test "parameters" do request = stub_request - assert_called(request, :request_parameters, times: 2, returns: {"foo" => 1}) do - assert_called(request, :query_parameters, times: 2, returns: {"bar" => 2}) do - assert_equal({"foo" => 1, "bar" => 2}, request.parameters) - assert_equal({"foo" => 1}, request.request_parameters) - assert_equal({"bar" => 2}, request.query_parameters) + assert_called(request, :request_parameters, times: 2, returns: { "foo" => 1 }) do + assert_called(request, :query_parameters, times: 2, returns: { "bar" => 2 }) do + assert_equal({ "foo" => 1, "bar" => 2 }, request.parameters) + assert_equal({ "foo" => 1 }, request.request_parameters) + assert_equal({ "bar" => 2 }, request.query_parameters) end end end @@ -987,17 +1018,13 @@ class RequestParameters < BaseRequestTest end test "path parameters with invalid UTF8 encoding" do - request = stub_request( - "action_dispatch.request.path_parameters" => { foo: "\xBE" } - ) + request = stub_request err = assert_raises(ActionController::BadRequest) do - request.check_path_parameters! + request.path_parameters = { foo: "\xBE" } end - assert_match "Invalid parameter encoding", err.message - assert_match "foo", err.message - assert_match "\\xBE", err.message + assert_equal "Invalid path parameters: Non UTF-8 value: \xBE", err.message end test "parameters not accessible after rack parse error of invalid UTF8 character" do @@ -1017,10 +1044,10 @@ class RequestParameters < BaseRequestTest test "parameters not accessible after rack parse error 1" do request = stub_request( - 'REQUEST_METHOD' => 'POST', - 'CONTENT_LENGTH' => "a%=".length, - 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', - 'rack.input' => StringIO.new("a%=") + "REQUEST_METHOD" => "POST", + "CONTENT_LENGTH" => "a%=".length, + "CONTENT_TYPE" => "application/x-www-form-urlencoded; charset=utf-8", + "rack.input" => StringIO.new("a%=") ) assert_raises(ActionController::BadRequest) do @@ -1042,31 +1069,30 @@ class RequestParameters < BaseRequestTest end end - class RequestParameterFilter < BaseRequestTest test "process parameter filter" do test_hashes = [ - [{'foo'=>'bar'},{'foo'=>'bar'},%w'food'], - [{'foo'=>'bar'},{'foo'=>'[FILTERED]'},%w'foo'], - [{'foo'=>'bar', 'bar'=>'foo'},{'foo'=>'[FILTERED]', 'bar'=>'foo'},%w'foo baz'], - [{'foo'=>'bar', 'baz'=>'foo'},{'foo'=>'[FILTERED]', 'baz'=>'[FILTERED]'},%w'foo baz'], - [{'bar'=>{'foo'=>'bar','bar'=>'foo'}},{'bar'=>{'foo'=>'[FILTERED]','bar'=>'foo'}},%w'fo'], - [{'foo'=>{'foo'=>'bar','bar'=>'foo'}},{'foo'=>'[FILTERED]'},%w'f banana'], - [{'deep'=>{'cc'=>{'code'=>'bar','bar'=>'foo'},'ss'=>{'code'=>'bar'}}},{'deep'=>{'cc'=>{'code'=>'[FILTERED]','bar'=>'foo'},'ss'=>{'code'=>'bar'}}},%w'deep.cc.code'], - [{'baz'=>[{'foo'=>'baz'}, "1"]}, {'baz'=>[{'foo'=>'[FILTERED]'}, "1"]}, [/foo/]]] + [{ "foo"=>"bar" },{ "foo"=>"bar" },%w'food'], + [{ "foo"=>"bar" },{ "foo"=>"[FILTERED]" },%w'foo'], + [{ "foo"=>"bar", "bar"=>"foo" },{ "foo"=>"[FILTERED]", "bar"=>"foo" },%w'foo baz'], + [{ "foo"=>"bar", "baz"=>"foo" },{ "foo"=>"[FILTERED]", "baz"=>"[FILTERED]" },%w'foo baz'], + [{ "bar"=>{ "foo"=>"bar","bar"=>"foo" } },{ "bar"=>{ "foo"=>"[FILTERED]","bar"=>"foo" } },%w'fo'], + [{ "foo"=>{ "foo"=>"bar","bar"=>"foo" } },{ "foo"=>"[FILTERED]" },%w'f banana'], + [{ "deep"=>{ "cc"=>{ "code"=>"bar","bar"=>"foo" },"ss"=>{ "code"=>"bar" } } },{ "deep"=>{ "cc"=>{ "code"=>"[FILTERED]","bar"=>"foo" },"ss"=>{ "code"=>"bar" } } },%w'deep.cc.code'], + [{ "baz"=>[{ "foo"=>"baz" }, "1"] }, { "baz"=>[{ "foo"=>"[FILTERED]" }, "1"] }, [/foo/]]] test_hashes.each do |before_filter, after_filter, filter_words| parameter_filter = ActionDispatch::Http::ParameterFilter.new(filter_words) assert_equal after_filter, parameter_filter.filter(before_filter) - filter_words << 'blah' + filter_words << "blah" filter_words << lambda { |key, value| value.reverse! if key =~ /bargain/ } parameter_filter = ActionDispatch::Http::ParameterFilter.new(filter_words) - before_filter['barg'] = {:bargain=>'gain', 'blah'=>'bar', 'bar'=>{'bargain'=>{'blah'=>'foo'}}} - after_filter['barg'] = {:bargain=>'niag', 'blah'=>'[FILTERED]', 'bar'=>{'bargain'=>{'blah'=>'[FILTERED]'}}} + before_filter["barg"] = { :bargain=>"gain", "blah"=>"bar", "bar"=>{ "bargain"=>{ "blah"=>"foo" } } } + after_filter["barg"] = { :bargain=>"niag", "blah"=>"[FILTERED]", "bar"=>{ "bargain"=>{ "blah"=>"[FILTERED]" } } } assert_equal after_filter, parameter_filter.filter(before_filter) end @@ -1074,12 +1100,12 @@ class RequestParameterFilter < BaseRequestTest test "filtered_parameters returns params filtered" do request = stub_request( - 'action_dispatch.request.parameters' => { - 'lifo' => 'Pratik', - 'amount' => '420', - 'step' => '1' + "action_dispatch.request.parameters" => { + "lifo" => "Pratik", + "amount" => "420", + "step" => "1" }, - 'action_dispatch.parameter_filter' => [:lifo, :amount] + "action_dispatch.parameter_filter" => [:lifo, :amount] ) params = request.filtered_parameters @@ -1090,12 +1116,12 @@ class RequestParameterFilter < BaseRequestTest test "filtered_env filters env as a whole" do request = stub_request( - 'action_dispatch.request.parameters' => { - 'amount' => '420', - 'step' => '1' + "action_dispatch.request.parameters" => { + "amount" => "420", + "step" => "1" }, "RAW_POST_DATA" => "yada yada", - 'action_dispatch.parameter_filter' => [:lifo, :amount] + "action_dispatch.parameter_filter" => [:lifo, :amount] ) request = stub_request(request.filtered_env) @@ -1107,9 +1133,9 @@ class RequestParameterFilter < BaseRequestTest test "filtered_path returns path with filtered query string" do %w(; &).each do |sep| request = stub_request( - 'QUERY_STRING' => %w(username=sikachu secret=bd4f21f api_key=b1bc3b3cd352f68d79d7).join(sep), - 'PATH_INFO' => '/authenticate', - 'action_dispatch.parameter_filter' => [:secret, :api_key] + "QUERY_STRING" => %w(username=sikachu secret=bd4f21f api_key=b1bc3b3cd352f68d79d7).join(sep), + "PATH_INFO" => "/authenticate", + "action_dispatch.parameter_filter" => [:secret, :api_key] ) path = request.filtered_path @@ -1119,9 +1145,9 @@ class RequestParameterFilter < BaseRequestTest test "filtered_path should not unescape a genuine '[FILTERED]' value" do request = stub_request( - 'QUERY_STRING' => "secret=bd4f21f&genuine=%5BFILTERED%5D", - 'PATH_INFO' => '/authenticate', - 'action_dispatch.parameter_filter' => [:secret] + "QUERY_STRING" => "secret=bd4f21f&genuine=%5BFILTERED%5D", + "PATH_INFO" => "/authenticate", + "action_dispatch.parameter_filter" => [:secret] ) path = request.filtered_path @@ -1130,9 +1156,9 @@ class RequestParameterFilter < BaseRequestTest test "filtered_path should preserve duplication of keys in query string" do request = stub_request( - 'QUERY_STRING' => "username=sikachu&secret=bd4f21f&username=fxn", - 'PATH_INFO' => '/authenticate', - 'action_dispatch.parameter_filter' => [:secret] + "QUERY_STRING" => "username=sikachu&secret=bd4f21f&username=fxn", + "PATH_INFO" => "/authenticate", + "action_dispatch.parameter_filter" => [:secret] ) path = request.filtered_path @@ -1141,9 +1167,9 @@ class RequestParameterFilter < BaseRequestTest test "filtered_path should ignore searchparts" do request = stub_request( - 'QUERY_STRING' => "secret", - 'PATH_INFO' => '/authenticate', - 'action_dispatch.parameter_filter' => [:secret] + "QUERY_STRING" => "secret", + "PATH_INFO" => "/authenticate", + "action_dispatch.parameter_filter" => [:secret] ) path = request.filtered_path @@ -1152,37 +1178,42 @@ class RequestParameterFilter < BaseRequestTest end class RequestEtag < BaseRequestTest - test "if_none_match_etags none" do + test "always matches *" do + request = stub_request("HTTP_IF_NONE_MATCH" => "*") + + assert_equal "*", request.if_none_match + assert_equal ["*"], request.if_none_match_etags + + assert request.etag_matches?('"strong"') + assert request.etag_matches?('W/"weak"') + assert_not request.etag_matches?(nil) + end + + test "doesn't match absent If-None-Match" do request = stub_request assert_equal nil, request.if_none_match assert_equal [], request.if_none_match_etags - assert !request.etag_matches?("foo") - assert !request.etag_matches?(nil) - end - - test "if_none_match_etags single" do - header = 'the-etag' - request = stub_request('HTTP_IF_NONE_MATCH' => header) - assert_equal header, request.if_none_match - assert_equal [header], request.if_none_match_etags - assert request.etag_matches?("the-etag") + assert_not request.etag_matches?("foo") + assert_not request.etag_matches?(nil) end - test "if_none_match_etags quoted single" do + test "matches opaque ETag validators without unquoting" do header = '"the-etag"' - request = stub_request('HTTP_IF_NONE_MATCH' => header) + request = stub_request("HTTP_IF_NONE_MATCH" => header) assert_equal header, request.if_none_match - assert_equal ['the-etag'], request.if_none_match_etags - assert request.etag_matches?("the-etag") + assert_equal ['"the-etag"'], request.if_none_match_etags + + assert request.etag_matches?('"the-etag"') + assert_not request.etag_matches?("the-etag") end test "if_none_match_etags multiple" do header = 'etag1, etag2, "third etag", "etag4"' - expected = ['etag1', 'etag2', 'third etag', 'etag4'] - request = stub_request('HTTP_IF_NONE_MATCH' => header) + expected = ["etag1", "etag2", '"third etag"', '"etag4"'] + request = stub_request("HTTP_IF_NONE_MATCH" => header) assert_equal header, request.if_none_match assert_equal expected, request.if_none_match_etags @@ -1198,7 +1229,7 @@ class RequestVariant < BaseRequestTest @request = stub_request end - test 'setting variant to a symbol' do + test "setting variant to a symbol" do @request.variant = :phone assert @request.variant.phone? @@ -1207,7 +1238,7 @@ class RequestVariant < BaseRequestTest assert_not @request.variant.any?(:tablet, :desktop) end - test 'setting variant to an array of symbols' do + test "setting variant to an array of symbols" do @request.variant = [:phone, :tablet] assert @request.variant.phone? @@ -1217,7 +1248,7 @@ class RequestVariant < BaseRequestTest assert_not @request.variant.any?(:desktop, :watch) end - test 'clearing variant' do + test "clearing variant" do @request.variant = nil assert @request.variant.empty? @@ -1225,35 +1256,35 @@ class RequestVariant < BaseRequestTest assert_not @request.variant.any?(:phone, :tablet) end - test 'setting variant to a non-symbol value' do + test "setting variant to a non-symbol value" do assert_raise ArgumentError do - @request.variant = 'phone' + @request.variant = "phone" end end - test 'setting variant to an array containing a non-symbol value' do + test "setting variant to an array containing a non-symbol value" do assert_raise ArgumentError do - @request.variant = [:phone, 'tablet'] + @request.variant = [:phone, "tablet"] end end end class RequestFormData < BaseRequestTest - test 'media_type is from the FORM_DATA_MEDIA_TYPES array' do - assert stub_request('CONTENT_TYPE' => 'application/x-www-form-urlencoded').form_data? - assert stub_request('CONTENT_TYPE' => 'multipart/form-data').form_data? + test "media_type is from the FORM_DATA_MEDIA_TYPES array" do + assert stub_request("CONTENT_TYPE" => "application/x-www-form-urlencoded").form_data? + assert stub_request("CONTENT_TYPE" => "multipart/form-data").form_data? end - test 'media_type is not from the FORM_DATA_MEDIA_TYPES array' do - assert !stub_request('CONTENT_TYPE' => 'application/xml').form_data? - assert !stub_request('CONTENT_TYPE' => 'multipart/related').form_data? + test "media_type is not from the FORM_DATA_MEDIA_TYPES array" do + assert !stub_request("CONTENT_TYPE" => "application/xml").form_data? + assert !stub_request("CONTENT_TYPE" => "multipart/related").form_data? end - test 'no Content-Type header is provided and the request_method is POST' do - request = stub_request('REQUEST_METHOD' => 'POST') + test "no Content-Type header is provided and the request_method is POST" do + request = stub_request("REQUEST_METHOD" => "POST") - assert_equal '', request.media_type - assert_equal 'POST', request.request_method + assert_equal "", request.media_type + assert_equal "POST", request.request_method assert !request.form_data? end end diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 8b3849cb7a..4e547ab7d5 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'timeout' -require 'rack/content_length' +require "abstract_unit" +require "timeout" +require "rack/content_length" class ResponseTest < ActiveSupport::TestCase def setup @@ -37,6 +37,39 @@ class ResponseTest < ActiveSupport::TestCase assert_equal "closed stream", e.message end + def test_each_isnt_called_if_str_body_is_written + # Controller writes and reads response body + each_counter = 0 + @response.body = Object.new.tap { |o| o.singleton_class.send(:define_method, :each) { |&block| each_counter += 1; block.call "foo" } } + @response["X-Foo"] = @response.body + + assert_equal 1, each_counter, "#each was not called once" + + # Build response + status, headers, body = @response.to_a + + assert_equal 200, status + assert_equal "foo", headers["X-Foo"] + assert_equal "foo", body.each.to_a.join + + # Show that #each was not called twice + assert_equal 1, each_counter, "#each was not called once" + end + + def test_set_header_after_read_body_during_action + @response.body + + # set header after the action reads back @response.body + @response["x-header"] = "Best of all possible worlds." + + # the response can be built. + status, headers, body = @response.to_a + assert_equal 200, status + assert_equal "", body.body + + assert_equal "Best of all possible worlds.", headers["x-header"] + end + def test_read_body_during_action @response.body = "Hello, World!" @@ -66,15 +99,15 @@ class ResponseTest < ActiveSupport::TestCase end def test_response_charset_writer - @response.charset = 'utf-16' - assert_equal 'utf-16', @response.charset + @response.charset = "utf-16" + assert_equal "utf-16", @response.charset @response.charset = nil - assert_equal 'utf-8', @response.charset + assert_equal "utf-8", @response.charset end def test_setting_content_type_header_impacts_content_type_method - @response.headers['Content-Type'] = "application/aaron" - assert_equal 'application/aaron', @response.content_type + @response.headers["Content-Type"] = "application/aaron" + assert_equal "application/aaron", @response.content_type end test "simple output" do @@ -92,14 +125,14 @@ class ResponseTest < ActiveSupport::TestCase end test "status handled properly in initialize" do - assert_equal 200, ActionDispatch::Response.new('200 OK').status + assert_equal 200, ActionDispatch::Response.new("200 OK").status end def test_only_set_charset_still_defaults_to_text_html response = ActionDispatch::Response.new response.charset = "utf-16" _,headers,_ = response.to_a - assert_equal "text/html; charset=utf-16", headers['Content-Type'] + assert_equal "text/html; charset=utf-16", headers["Content-Type"] end test "utf8 output" do @@ -112,6 +145,26 @@ class ResponseTest < ActiveSupport::TestCase }, headers) end + test "content length" do + [100, 101, 102, 204].each do |c| + @response = ActionDispatch::Response.new + @response.status = c.to_s + @response.set_header "Content-Length", "0" + _, headers, _ = @response.to_a + assert !headers.has_key?("Content-Length"), "#{c} must not have a Content-Length header field" + end + end + + test "does not contain a message-body" do + [100, 101, 102, 204, 304].each do |c| + @response = ActionDispatch::Response.new + @response.status = c.to_s + @response.body = "Body must not be included" + _, _, body = @response.to_a + assert_empty body, "#{c} must not have a message-body but actually contains #{body}" + end + end + test "content type" do [204, 304].each do |c| @response = ActionDispatch::Response.new @@ -131,7 +184,7 @@ class ResponseTest < ActiveSupport::TestCase test "does not include Status header" do @response.status = "200 OK" _, headers, _ = @response.to_a - assert !headers.has_key?('Status') + assert !headers.has_key?("Status") end test "response code" do @@ -168,61 +221,78 @@ class ResponseTest < ActiveSupport::TestCase end test "cookies" do - @response.set_cookie("user_name", :value => "david", :path => "/") + @response.set_cookie("user_name", value: "david", path: "/") _status, headers, _body = @response.to_a assert_equal "user_name=david; path=/", headers["Set-Cookie"] - assert_equal({"user_name" => "david"}, @response.cookies) + assert_equal({ "user_name" => "david" }, @response.cookies) end test "multiple cookies" do - @response.set_cookie("user_name", :value => "david", :path => "/") - @response.set_cookie("login", :value => "foo&bar", :path => "/", :expires => Time.utc(2005, 10, 10,5)) + @response.set_cookie("user_name", value: "david", path: "/") + @response.set_cookie("login", value: "foo&bar", path: "/", expires: Time.utc(2005, 10, 10,5)) _status, headers, _body = @response.to_a assert_equal "user_name=david; path=/\nlogin=foo%26bar; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000", headers["Set-Cookie"] - assert_equal({"login" => "foo&bar", "user_name" => "david"}, @response.cookies) + assert_equal({ "login" => "foo&bar", "user_name" => "david" }, @response.cookies) end test "delete cookies" do - @response.set_cookie("user_name", :value => "david", :path => "/") - @response.set_cookie("login", :value => "foo&bar", :path => "/", :expires => Time.utc(2005, 10, 10,5)) + @response.set_cookie("user_name", value: "david", path: "/") + @response.set_cookie("login", value: "foo&bar", path: "/", expires: Time.utc(2005, 10, 10,5)) @response.delete_cookie("login") - assert_equal({"user_name" => "david", "login" => nil}, @response.cookies) + assert_equal({ "user_name" => "david", "login" => nil }, @response.cookies) end - test "read cache control" do + test "read ETag and Cache-Control" do resp = ActionDispatch::Response.new.tap { |response| response.cache_control[:public] = true - response.etag = '123' - response.body = 'Hello' + response.etag = "123" + response.body = "Hello" } resp.to_a + assert resp.etag? + assert resp.weak_etag? + assert_not resp.strong_etag? assert_equal('W/"202cb962ac59075b964b07152d234b70"', resp.etag) - assert_equal({:public => true}, resp.cache_control) + assert_equal({ public: true }, resp.cache_control) - assert_equal('public', resp.headers['Cache-Control']) - assert_equal('W/"202cb962ac59075b964b07152d234b70"', resp.headers['ETag']) + assert_equal("public", resp.headers["Cache-Control"]) + assert_equal('W/"202cb962ac59075b964b07152d234b70"', resp.headers["ETag"]) + end + + test "read strong ETag" do + resp = ActionDispatch::Response.new.tap { |response| + response.cache_control[:public] = true + response.strong_etag = "123" + response.body = "Hello" + } + resp.to_a + + assert resp.etag? + assert_not resp.weak_etag? + assert resp.strong_etag? + assert_equal('"202cb962ac59075b964b07152d234b70"', resp.etag) end test "read charset and content type" do resp = ActionDispatch::Response.new.tap { |response| - response.charset = 'utf-16' + response.charset = "utf-16" response.content_type = Mime[:xml] - response.body = 'Hello' + response.body = "Hello" } resp.to_a - assert_equal('utf-16', resp.charset) + assert_equal("utf-16", resp.charset) assert_equal(Mime[:xml], resp.content_type) - assert_equal('application/xml; charset=utf-16', resp.headers['Content-Type']) + assert_equal("application/xml; charset=utf-16", resp.headers["Content-Type"]) end test "read content type with default charset utf-8" do original = ActionDispatch::Response.default_charset begin - resp = ActionDispatch::Response.new(200, { "Content-Type" => "text/xml" }) - assert_equal('utf-8', resp.charset) + resp = ActionDispatch::Response.new(200, "Content-Type" => "text/xml") + assert_equal("utf-8", resp.charset) ensure ActionDispatch::Response.default_charset = original end @@ -231,9 +301,9 @@ class ResponseTest < ActiveSupport::TestCase test "read content type with charset utf-16" do original = ActionDispatch::Response.default_charset begin - ActionDispatch::Response.default_charset = 'utf-16' - resp = ActionDispatch::Response.new(200, { "Content-Type" => "text/xml" }) - assert_equal('utf-16', resp.charset) + ActionDispatch::Response.default_charset = "utf-16" + resp = ActionDispatch::Response.new(200, "Content-Type" => "text/xml") + assert_equal("utf-16", resp.charset) ensure ActionDispatch::Response.default_charset = original end @@ -243,18 +313,18 @@ class ResponseTest < ActiveSupport::TestCase original_default_headers = ActionDispatch::Response.default_headers begin ActionDispatch::Response.default_headers = { - 'X-Frame-Options' => 'DENY', - 'X-Content-Type-Options' => 'nosniff', - 'X-XSS-Protection' => '1;' + "X-Frame-Options" => "DENY", + "X-Content-Type-Options" => "nosniff", + "X-XSS-Protection" => "1;" } resp = ActionDispatch::Response.create.tap { |response| - response.body = 'Hello' + response.body = "Hello" } resp.to_a - assert_equal('DENY', resp.headers['X-Frame-Options']) - assert_equal('nosniff', resp.headers['X-Content-Type-Options']) - assert_equal('1;', resp.headers['X-XSS-Protection']) + assert_equal("DENY", resp.headers["X-Frame-Options"]) + assert_equal("nosniff", resp.headers["X-Content-Type-Options"]) + assert_equal("1;", resp.headers["X-XSS-Protection"]) ensure ActionDispatch::Response.default_headers = original_default_headers end @@ -264,14 +334,14 @@ class ResponseTest < ActiveSupport::TestCase original_default_headers = ActionDispatch::Response.default_headers begin ActionDispatch::Response.default_headers = { - 'X-XX-XXXX' => 'Here is my phone number' + "X-XX-XXXX" => "Here is my phone number" } resp = ActionDispatch::Response.create.tap { |response| - response.body = 'Hello' + response.body = "Hello" } resp.to_a - assert_equal('Here is my phone number', resp.headers['X-XX-XXXX']) + assert_equal("Here is my phone number", resp.headers["X-XX-XXXX"]) ensure ActionDispatch::Response.default_headers = original_default_headers end @@ -283,13 +353,13 @@ class ResponseTest < ActiveSupport::TestCase end test "can be explicitly destructured into status, headers and an enumerable body" do - response = ActionDispatch::Response.new(404, { 'Content-Type' => 'text/plain' }, ['Not Found']) + response = ActionDispatch::Response.new(404, { "Content-Type" => "text/plain" }, ["Not Found"]) response.request = ActionDispatch::Request.empty status, headers, body = *response assert_equal 404, status - assert_equal({ 'Content-Type' => 'text/plain' }, headers) - assert_equal ['Not Found'], body.each.to_a + assert_equal({ "Content-Type" => "text/plain" }, headers) + assert_equal ["Not Found"], body.each.to_a end test "[response.to_a].flatten does not recurse infinitely" do @@ -302,74 +372,74 @@ class ResponseTest < ActiveSupport::TestCase end test "compatibility with Rack::ContentLength" do - @response.body = 'Hello' + @response.body = "Hello" app = lambda { |env| @response.to_a } env = Rack::MockRequest.env_for("/") status, headers, body = app.call(env) - assert_nil headers['Content-Length'] + assert_nil headers["Content-Length"] status, headers, body = Rack::ContentLength.new(app).call(env) - assert_equal '5', headers['Content-Length'] + assert_equal "5", headers["Content-Length"] end end class ResponseHeadersTest < ActiveSupport::TestCase def setup @response = ActionDispatch::Response.create - @response.set_header 'Foo', '1' + @response.set_header "Foo", "1" end - test 'has_header?' do - assert @response.has_header? 'Foo' - assert_not @response.has_header? 'foo' + test "has_header?" do + assert @response.has_header? "Foo" + assert_not @response.has_header? "foo" assert_not @response.has_header? nil end - test 'get_header' do - assert_equal '1', @response.get_header('Foo') - assert_nil @response.get_header('foo') + test "get_header" do + assert_equal "1", @response.get_header("Foo") + assert_nil @response.get_header("foo") assert_nil @response.get_header(nil) end - test 'set_header' do - assert_equal '2', @response.set_header('Foo', '2') - assert @response.has_header?('Foo') - assert_equal '2', @response.get_header('Foo') + test "set_header" do + assert_equal "2", @response.set_header("Foo", "2") + assert @response.has_header?("Foo") + assert_equal "2", @response.get_header("Foo") - assert_nil @response.set_header('Foo', nil) - assert @response.has_header?('Foo') - assert_nil @response.get_header('Foo') + assert_nil @response.set_header("Foo", nil) + assert @response.has_header?("Foo") + assert_nil @response.get_header("Foo") end - test 'delete_header' do + test "delete_header" do assert_nil @response.delete_header(nil) - assert_nil @response.delete_header('foo') - assert @response.has_header?('Foo') + assert_nil @response.delete_header("foo") + assert @response.has_header?("Foo") - assert_equal '1', @response.delete_header('Foo') - assert_not @response.has_header?('Foo') + assert_equal "1", @response.delete_header("Foo") + assert_not @response.has_header?("Foo") end - test 'add_header' do + test "add_header" do # Add a value to an existing header - assert_equal '1,2', @response.add_header('Foo', '2') - assert_equal '1,2', @response.get_header('Foo') + assert_equal "1,2", @response.add_header("Foo", "2") + assert_equal "1,2", @response.get_header("Foo") # Add nil to an existing header - assert_equal '1,2', @response.add_header('Foo', nil) - assert_equal '1,2', @response.get_header('Foo') + assert_equal "1,2", @response.add_header("Foo", nil) + assert_equal "1,2", @response.get_header("Foo") # Add nil to a nonexistent header - assert_nil @response.add_header('Bar', nil) - assert_not @response.has_header?('Bar') - assert_nil @response.get_header('Bar') + assert_nil @response.add_header("Bar", nil) + assert_not @response.has_header?("Bar") + assert_nil @response.get_header("Bar") # Add a value to a nonexistent header - assert_equal '1', @response.add_header('Bar', '1') - assert @response.has_header?('Bar') - assert_equal '1', @response.get_header('Bar') + assert_equal "1", @response.add_header("Bar", "1") + assert @response.has_header?("Bar") + assert_equal "1", @response.get_header("Bar") end end @@ -378,71 +448,87 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest @app = lambda { |env| ActionDispatch::Response.new.tap { |resp| resp.cache_control[:public] = true - resp.etag = '123' - resp.body = 'Hello' + resp.etag = "123" + resp.body = "Hello" resp.request = ActionDispatch::Request.empty }.to_a } - get '/' + get "/" assert_response :success - assert_equal('public', @response.headers['Cache-Control']) - assert_equal('W/"202cb962ac59075b964b07152d234b70"', @response.headers['ETag']) + assert_equal("public", @response.headers["Cache-Control"]) + assert_equal('W/"202cb962ac59075b964b07152d234b70"', @response.headers["ETag"]) assert_equal('W/"202cb962ac59075b964b07152d234b70"', @response.etag) - assert_equal({:public => true}, @response.cache_control) + assert_equal({ public: true }, @response.cache_control) end test "response cache control from rackish app" do @app = lambda { |env| [200, - {'ETag' => 'W/"202cb962ac59075b964b07152d234b70"', - 'Cache-Control' => 'public'}, ['Hello']] + { "ETag" => 'W/"202cb962ac59075b964b07152d234b70"', + "Cache-Control" => "public" }, ["Hello"]] } - get '/' + get "/" assert_response :success - assert_equal('public', @response.headers['Cache-Control']) - assert_equal('W/"202cb962ac59075b964b07152d234b70"', @response.headers['ETag']) + assert_equal("public", @response.headers["Cache-Control"]) + assert_equal('W/"202cb962ac59075b964b07152d234b70"', @response.headers["ETag"]) assert_equal('W/"202cb962ac59075b964b07152d234b70"', @response.etag) - assert_equal({:public => true}, @response.cache_control) + assert_equal({ public: true }, @response.cache_control) end test "response charset and content type from railsish app" do @app = lambda { |env| ActionDispatch::Response.new.tap { |resp| - resp.charset = 'utf-16' + resp.charset = "utf-16" resp.content_type = Mime[:xml] - resp.body = 'Hello' + resp.body = "Hello" resp.request = ActionDispatch::Request.empty }.to_a } - get '/' + get "/" assert_response :success - assert_equal('utf-16', @response.charset) + assert_equal("utf-16", @response.charset) assert_equal(Mime[:xml], @response.content_type) - assert_equal('application/xml; charset=utf-16', @response.headers['Content-Type']) + assert_equal("application/xml; charset=utf-16", @response.headers["Content-Type"]) end test "response charset and content type from rackish app" do @app = lambda { |env| [200, - {'Content-Type' => 'application/xml; charset=utf-16'}, - ['Hello']] + { "Content-Type" => "application/xml; charset=utf-16" }, + ["Hello"]] } - get '/' + get "/" assert_response :success - assert_equal('utf-16', @response.charset) + assert_equal("utf-16", @response.charset) assert_equal(Mime[:xml], @response.content_type) - assert_equal('application/xml; charset=utf-16', @response.headers['Content-Type']) + assert_equal("application/xml; charset=utf-16", @response.headers["Content-Type"]) + end + + test "strong ETag validator" do + @app = lambda { |env| + ActionDispatch::Response.new.tap { |resp| + resp.strong_etag = "123" + resp.body = "Hello" + resp.request = ActionDispatch::Request.empty + }.to_a + } + + get "/" + assert_response :ok + + assert_equal('"202cb962ac59075b964b07152d234b70"', @response.headers["ETag"]) + assert_equal('"202cb962ac59075b964b07152d234b70"', @response.etag) end end diff --git a/actionpack/test/dispatch/routing/concerns_test.rb b/actionpack/test/dispatch/routing/concerns_test.rb index 7ef513b0c8..2d71c37562 100644 --- a/actionpack/test/dispatch/routing/concerns_test.rb +++ b/actionpack/test/dispatch/routing/concerns_test.rb @@ -1,4 +1,6 @@ -require 'abstract_unit' +require "abstract_unit" + +class ReviewsController < ResourcesController; end class RoutingConcernsTest < ActionDispatch::IntegrationTest class Reviewable diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb index f72a87b994..a4babf8554 100644 --- a/actionpack/test/dispatch/routing/inspector_test.rb +++ b/actionpack/test/dispatch/routing/inspector_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'rails/engine' -require 'action_dispatch/routing/inspector' +require "abstract_unit" +require "rails/engine" +require "action_dispatch/routing/inspector" class MountedRackApp def self.call(env) @@ -30,11 +30,11 @@ module ActionDispatch end end engine.routes.draw do - get '/cart', :to => 'cart#show' + get "/cart", to: "cart#show" end output = draw do - get '/custom/assets', :to => 'custom_assets#show' + get "/custom/assets", to: "custom_assets#show" mount engine => "/blog", :as => "blog" end @@ -71,7 +71,7 @@ module ActionDispatch def test_cart_inspect output = draw do - get '/cart', :to => 'cart#show' + get "/cart", to: "cart#show" end assert_equal [ @@ -82,7 +82,7 @@ module ActionDispatch def test_articles_inspect_with_multiple_verbs output = draw do - match 'articles/:id', to: 'articles#update', via: [:put, :patch] + match "articles/:id", to: "articles#update", via: [:put, :patch] end assert_equal [ @@ -93,7 +93,7 @@ module ActionDispatch def test_inspect_shows_custom_assets output = draw do - get '/custom/assets', :to => 'custom_assets#show' + get "/custom/assets", to: "custom_assets#show" end assert_equal [ @@ -122,7 +122,7 @@ module ActionDispatch def test_inspect_routes_shows_root_route output = draw do - root :to => 'pages#main' + root to: "pages#main" end assert_equal [ @@ -133,7 +133,9 @@ module ActionDispatch def test_inspect_routes_shows_dynamic_action_route output = draw do - get 'api/:action' => 'api' + ActiveSupport::Deprecation.silence do + get "api/:action" => "api" + end end assert_equal [ @@ -144,7 +146,9 @@ module ActionDispatch def test_inspect_routes_shows_controller_and_action_only_route output = draw do - get ':controller/:action' + ActiveSupport::Deprecation.silence do + get ":controller/:action" + end end assert_equal [ @@ -155,7 +159,9 @@ module ActionDispatch def test_inspect_routes_shows_controller_and_action_route_with_constraints output = draw do - get ':controller(/:action(/:id))', :id => /\d+/ + ActiveSupport::Deprecation.silence do + get ":controller(/:action(/:id))", id: /\d+/ + end end assert_equal [ @@ -164,20 +170,20 @@ module ActionDispatch ], output end - def test_rake_routes_shows_route_with_defaults + def test_rails_routes_shows_route_with_defaults output = draw do - get 'photos/:id' => 'photos#show', :defaults => {:format => 'jpg'} + get "photos/:id" => "photos#show", :defaults => { format: "jpg" } end assert_equal [ "Prefix Verb URI Pattern Controller#Action", - %Q[ GET /photos/:id(.:format) photos#show {:format=>"jpg"}] + ' GET /photos/:id(.:format) photos#show {:format=>"jpg"}' ], output end - def test_rake_routes_shows_route_with_constraints + def test_rails_routes_shows_route_with_constraints output = draw do - get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/ + get "photos/:id" => "photos#show", :id => /[A-Z]\d{5}/ end assert_equal [ @@ -186,15 +192,15 @@ module ActionDispatch ], output end - def test_rake_routes_shows_routes_with_dashes + def test_rails_routes_shows_routes_with_dashes output = draw do - get 'about-us' => 'pages#about_us' - get 'our-work/latest' + get "about-us" => "pages#about_us" + get "our-work/latest" resources :photos, only: [:show] do - get 'user-favorites', on: :collection - get 'preview-photo', on: :member - get 'summary-text' + get "user-favorites", on: :collection + get "preview-photo", on: :member + get "summary-text" end end @@ -209,9 +215,9 @@ module ActionDispatch ], output end - def test_rake_routes_shows_route_with_rack_app + def test_rails_routes_shows_route_with_rack_app output = draw do - get 'foo/:id' => MountedRackApp, :id => /[A-Z]\d{5}/ + get "foo/:id" => MountedRackApp, :id => /[A-Z]\d{5}/ end assert_equal [ @@ -220,9 +226,9 @@ module ActionDispatch ], output end - def test_rake_routes_shows_named_route_with_mounted_rack_app + def test_rails_routes_shows_named_route_with_mounted_rack_app output = draw do - mount MountedRackApp => '/foo' + mount MountedRackApp => "/foo" end assert_equal [ @@ -231,9 +237,9 @@ module ActionDispatch ], output end - def test_rake_routes_shows_overridden_named_route_with_mounted_rack_app_with_name + def test_rails_routes_shows_overridden_named_route_with_mounted_rack_app_with_name output = draw do - mount MountedRackApp => '/foo', as: 'blog' + mount MountedRackApp => "/foo", as: "blog" end assert_equal [ @@ -242,7 +248,7 @@ module ActionDispatch ], output end - def test_rake_routes_shows_route_with_rack_app_nested_with_dynamic_constraints + def test_rails_routes_shows_route_with_rack_app_nested_with_dynamic_constraints constraint = Class.new do def inspect "( my custom constraint )" @@ -250,8 +256,8 @@ module ActionDispatch end output = draw do - scope :constraint => constraint.new do - mount MountedRackApp => '/foo' + scope constraint: constraint.new do + mount MountedRackApp => "/foo" end end @@ -261,18 +267,18 @@ module ActionDispatch ], output end - def test_rake_routes_dont_show_app_mounted_in_assets_prefix + def test_rails_routes_dont_show_app_mounted_in_assets_prefix output = draw do - get '/sprockets' => MountedRackApp + get "/sprockets" => MountedRackApp end assert_no_match(/MountedRackApp/, output.first) assert_no_match(/\/sprockets/, output.first) end - def test_rake_routes_shows_route_defined_in_under_assets_prefix + def test_rails_routes_shows_route_defined_in_under_assets_prefix output = draw do - scope '/sprockets' do - get '/foo' => 'foo#bar' + scope "/sprockets" do + get "/foo" => "foo#bar" end end assert_equal [ @@ -283,9 +289,9 @@ module ActionDispatch def test_redirect output = draw do - get "/foo" => redirect("/foo/bar"), :constraints => { :subdomain => "admin" } + get "/foo" => redirect("/foo/bar"), :constraints => { subdomain: "admin" } get "/bar" => redirect(path: "/foo/bar", status: 307) - get "/foobar" => redirect{ "/foo/bar" } + get "/foobar" => redirect { "/foo/bar" } end assert_equal [ @@ -297,7 +303,7 @@ module ActionDispatch end def test_routes_can_be_filtered - output = draw('posts') do + output = draw("posts") do resources :articles resources :posts end @@ -314,7 +320,7 @@ module ActionDispatch end def test_routes_can_be_filtered_with_namespaced_controllers - output = draw('admin/posts') do + output = draw("admin/posts") do resources :articles namespace :admin do resources :posts @@ -332,21 +338,22 @@ module ActionDispatch " DELETE /admin/posts/:id(.:format) admin/posts#destroy"], output end - def test_regression_route_with_controller_regexp output = draw do - get ':controller(/:action)', controller: /api\/[^\/]+/, format: false + ActiveSupport::Deprecation.silence do + get ":controller(/:action)", controller: /api\/[^\/]+/, format: false + end end assert_equal ["Prefix Verb URI Pattern Controller#Action", - " GET /:controller(/:action) (?-mix:api\\/[^\\/]+)#:action"], output + " GET /:controller(/:action) :controller#:action"], output end def test_inspect_routes_shows_resources_route_when_assets_disabled @set = ActionDispatch::Routing::RouteSet.new output = draw do - get '/cart', to: 'cart#show' + get "/cart", to: "cart#show" end assert_equal [ @@ -356,8 +363,8 @@ module ActionDispatch end def test_routes_with_undefined_filter - output = draw(controller: 'Rails::MissingController') do - get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/ + output = draw(controller: "Rails::MissingController") do + get "photos/:id" => "photos#show", :id => /[A-Z]\d{5}/ end assert_equal [ @@ -367,8 +374,8 @@ module ActionDispatch end def test_no_routes_matched_filter - output = draw('rails/dummy') do - get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/ + output = draw("rails/dummy") do + get "photos/:id" => "photos#show", :id => /[A-Z]\d{5}/ end assert_equal [ @@ -378,7 +385,7 @@ module ActionDispatch end def test_no_routes_were_defined - output = draw('Rails::DummyController') {} + output = draw("Rails::DummyController") {} assert_equal [ "You don't have any routes defined!", @@ -389,6 +396,28 @@ module ActionDispatch ], output end + def test_displaying_routes_for_internal_engines + engine = Class.new(Rails::Engine) do + def self.inspect + "Blog::Engine" + end + end + engine.routes.draw do + get "/cart", to: "cart#show" + post "/cart", to: "cart#create" + patch "/cart", to: "cart#update" + end + + output = draw do + get "/custom/assets", to: "custom_assets#show" + mount engine => "/blog", as: "blog", internal: true + end + + assert_equal [ + " Prefix Verb URI Pattern Controller#Action", + "custom_assets GET /custom/assets(.:format) custom_assets#show", + ], output + end end end end diff --git a/actionpack/test/dispatch/routing/ipv6_redirect_test.rb b/actionpack/test/dispatch/routing/ipv6_redirect_test.rb index f1b2e8cfc7..4987ed84e4 100644 --- a/actionpack/test/dispatch/routing/ipv6_redirect_test.rb +++ b/actionpack/test/dispatch/routing/ipv6_redirect_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class IPv6IntegrationTest < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new @@ -7,17 +7,17 @@ class IPv6IntegrationTest < ActionDispatch::IntegrationTest class ::BadRouteRequestController < ActionController::Base include Routes.url_helpers def index - render :text => foo_path + render text: foo_path end def foo - redirect_to :action => :index + redirect_to action: :index end end Routes.draw do - get "/", :to => 'bad_route_request#index', :as => :index - get "/foo", :to => "bad_route_request#foo", :as => :foo + get "/", to: "bad_route_request#index", as: :index + get "/foo", to: "bad_route_request#foo", as: :foo end def _routes @@ -32,14 +32,13 @@ class IPv6IntegrationTest < ActionDispatch::IntegrationTest test "bad IPv6 redirection" do # def test_simple_redirect request_env = { - 'REMOTE_ADDR' => 'fd07:2fa:6cff:2112:225:90ff:fec7:22aa', - 'HTTP_HOST' => '[fd07:2fa:6cff:2112:225:90ff:fec7:22aa]:3000', - 'SERVER_NAME' => '[fd07:2fa:6cff:2112:225:90ff:fec7:22aa]', - 'SERVER_PORT' => 3000 } + "REMOTE_ADDR" => "fd07:2fa:6cff:2112:225:90ff:fec7:22aa", + "HTTP_HOST" => "[fd07:2fa:6cff:2112:225:90ff:fec7:22aa]:3000", + "SERVER_NAME" => "[fd07:2fa:6cff:2112:225:90ff:fec7:22aa]", + "SERVER_PORT" => 3000 } - get '/foo', env: request_env + get "/foo", env: request_env assert_response :redirect - assert_equal 'http://[fd07:2fa:6cff:2112:225:90ff:fec7:22aa]:3000/', redirect_to_url + assert_equal "http://[fd07:2fa:6cff:2112:225:90ff:fec7:22aa]:3000/", redirect_to_url end - end diff --git a/actionpack/test/dispatch/routing/route_set_test.rb b/actionpack/test/dispatch/routing/route_set_test.rb index 9327fe12c6..ace35dda53 100644 --- a/actionpack/test/dispatch/routing/route_set_test.rb +++ b/actionpack/test/dispatch/routing/route_set_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module ActionDispatch module Routing @@ -9,7 +9,7 @@ module ActionDispatch end def call(env) - [ 200, { 'Content-Type' => 'text/plain' }, [response] ] + [ 200, { "Content-Type" => "text/plain" }, [response] ] end end @@ -21,7 +21,7 @@ module ActionDispatch assert empty? draw do - get 'foo', to: SimpleApp.new('foo#index') + get "foo", to: SimpleApp.new("foo#index") end assert_not empty? @@ -29,101 +29,101 @@ module ActionDispatch test "url helpers are added when route is added" do draw do - get 'foo', to: SimpleApp.new('foo#index') + get "foo", to: SimpleApp.new("foo#index") end - assert_equal '/foo', url_helpers.foo_path + assert_equal "/foo", url_helpers.foo_path assert_raises NoMethodError do - assert_equal '/bar', url_helpers.bar_path + assert_equal "/bar", url_helpers.bar_path end draw do - get 'foo', to: SimpleApp.new('foo#index') - get 'bar', to: SimpleApp.new('bar#index') + get "foo", to: SimpleApp.new("foo#index") + get "bar", to: SimpleApp.new("bar#index") end - assert_equal '/foo', url_helpers.foo_path - assert_equal '/bar', url_helpers.bar_path + assert_equal "/foo", url_helpers.foo_path + assert_equal "/bar", url_helpers.bar_path end test "url helpers are updated when route is updated" do draw do - get 'bar', to: SimpleApp.new('bar#index'), as: :bar + get "bar", to: SimpleApp.new("bar#index"), as: :bar end - assert_equal '/bar', url_helpers.bar_path + assert_equal "/bar", url_helpers.bar_path draw do - get 'baz', to: SimpleApp.new('baz#index'), as: :bar + get "baz", to: SimpleApp.new("baz#index"), as: :bar end - assert_equal '/baz', url_helpers.bar_path + assert_equal "/baz", url_helpers.bar_path end test "url helpers are removed when route is removed" do draw do - get 'foo', to: SimpleApp.new('foo#index') - get 'bar', to: SimpleApp.new('bar#index') + get "foo", to: SimpleApp.new("foo#index") + get "bar", to: SimpleApp.new("bar#index") end - assert_equal '/foo', url_helpers.foo_path - assert_equal '/bar', url_helpers.bar_path + assert_equal "/foo", url_helpers.foo_path + assert_equal "/bar", url_helpers.bar_path draw do - get 'foo', to: SimpleApp.new('foo#index') + get "foo", to: SimpleApp.new("foo#index") end - assert_equal '/foo', url_helpers.foo_path + assert_equal "/foo", url_helpers.foo_path assert_raises NoMethodError do - assert_equal '/bar', url_helpers.bar_path + assert_equal "/bar", url_helpers.bar_path end end test "only_path: true with *_url and no :host option" do draw do - get 'foo', to: SimpleApp.new('foo#index') + get "foo", to: SimpleApp.new("foo#index") end - assert_equal '/foo', url_helpers.foo_url(only_path: true) + assert_equal "/foo", url_helpers.foo_url(only_path: true) end test "only_path: false with *_url and no :host option" do draw do - get 'foo', to: SimpleApp.new('foo#index') + get "foo", to: SimpleApp.new("foo#index") end assert_raises ArgumentError do - assert_equal 'http://example.com/foo', url_helpers.foo_url(only_path: false) + assert_equal "http://example.com/foo", url_helpers.foo_url(only_path: false) end end test "only_path: false with *_url and local :host option" do draw do - get 'foo', to: SimpleApp.new('foo#index') + get "foo", to: SimpleApp.new("foo#index") end - assert_equal 'http://example.com/foo', url_helpers.foo_url(only_path: false, host: 'example.com') + assert_equal "http://example.com/foo", url_helpers.foo_url(only_path: false, host: "example.com") end test "only_path: false with *_url and global :host option" do - @set.default_url_options = { host: 'example.com' } + @set.default_url_options = { host: "example.com" } draw do - get 'foo', to: SimpleApp.new('foo#index') + get "foo", to: SimpleApp.new("foo#index") end - assert_equal 'http://example.com/foo', url_helpers.foo_url(only_path: false) + assert_equal "http://example.com/foo", url_helpers.foo_url(only_path: false) end test "explicit keys win over implicit keys" do draw do resources :foo do - resources :bar, to: SimpleApp.new('foo#show') + resources :bar, to: SimpleApp.new("foo#show") end end - assert_equal '/foo/1/bar/2', url_helpers.foo_bar_path(1, 2) - assert_equal '/foo/1/bar/2', url_helpers.foo_bar_path(2, foo_id: 1) + assert_equal "/foo/1/bar/2", url_helpers.foo_bar_path(1, 2) + assert_equal "/foo/1/bar/2", url_helpers.foo_bar_path(2, foo_id: 1) end test "having an optional scope with resources" do @@ -133,9 +133,9 @@ module ActionDispatch end end - assert_equal '/users/1', url_helpers.user_path(1) - assert_equal '/users/1', url_helpers.user_path(1, foo: nil) - assert_equal '/a/users/1', url_helpers.user_path(1, foo: 'a') + assert_equal "/users/1", url_helpers.user_path(1) + assert_equal "/users/1", url_helpers.user_path(1, foo: nil) + assert_equal "/a/users/1", url_helpers.user_path(1, foo: "a") end private diff --git a/actionpack/test/dispatch/routing_assertions_test.rb b/actionpack/test/dispatch/routing_assertions_test.rb index 56ea644f22..917ce7e668 100644 --- a/actionpack/test/dispatch/routing_assertions_test.rb +++ b/actionpack/test/dispatch/routing_assertions_test.rb @@ -1,130 +1,129 @@ -require 'abstract_unit' -require 'controller/fake_controllers' +require "abstract_unit" +require "controller/fake_controllers" class SecureArticlesController < ArticlesController; end class BlockArticlesController < ArticlesController; end class QueryArticlesController < ArticlesController; end class RoutingAssertionsTest < ActionController::TestCase - def setup @routes = ActionDispatch::Routing::RouteSet.new @routes.draw do resources :articles - scope 'secure', :constraints => { :protocol => 'https://' } do - resources :articles, :controller => 'secure_articles' + scope "secure", constraints: { protocol: "https://" } do + resources :articles, controller: "secure_articles" end - scope 'block', :constraints => lambda { |r| r.ssl? } do - resources :articles, :controller => 'block_articles' + scope "block", constraints: lambda { |r| r.ssl? } do + resources :articles, controller: "block_articles" end - scope 'query', :constraints => lambda { |r| r.params[:use_query] == 'true' } do - resources :articles, :controller => 'query_articles' + scope "query", constraints: lambda { |r| r.params[:use_query] == "true" } do + resources :articles, controller: "query_articles" end end end def test_assert_generates - assert_generates('/articles', { :controller => 'articles', :action => 'index' }) - assert_generates('/articles/1', { :controller => 'articles', :action => 'show', :id => '1' }) + assert_generates("/articles", controller: "articles", action: "index") + assert_generates("/articles/1", controller: "articles", action: "show", id: "1") end def test_assert_generates_with_defaults - assert_generates('/articles/1/edit', { :controller => 'articles', :action => 'edit' }, { :id => '1' }) + assert_generates("/articles/1/edit", { controller: "articles", action: "edit" }, id: "1") end def test_assert_generates_with_extras - assert_generates('/articles', { :controller => 'articles', :action => 'index', :page => '1' }, {}, { :page => '1' }) + assert_generates("/articles", { controller: "articles", action: "index", page: "1" }, {}, page: "1") end def test_assert_recognizes - assert_recognizes({ :controller => 'articles', :action => 'index' }, '/articles') - assert_recognizes({ :controller => 'articles', :action => 'show', :id => '1' }, '/articles/1') + assert_recognizes({ controller: "articles", action: "index" }, "/articles") + assert_recognizes({ controller: "articles", action: "show", id: "1" }, "/articles/1") end def test_assert_recognizes_with_extras - assert_recognizes({ :controller => 'articles', :action => 'index', :page => '1' }, '/articles', { :page => '1' }) + assert_recognizes({ controller: "articles", action: "index", page: "1" }, "/articles", page: "1") end def test_assert_recognizes_with_method - assert_recognizes({ :controller => 'articles', :action => 'create' }, { :path => '/articles', :method => :post }) - assert_recognizes({ :controller => 'articles', :action => 'update', :id => '1' }, { :path => '/articles/1', :method => :put }) + assert_recognizes({ controller: "articles", action: "create" }, path: "/articles", method: :post) + assert_recognizes({ controller: "articles", action: "update", id: "1" }, path: "/articles/1", method: :put) end def test_assert_recognizes_with_hash_constraint assert_raise(Assertion) do - assert_recognizes({ :controller => 'secure_articles', :action => 'index' }, 'http://test.host/secure/articles') + assert_recognizes({ controller: "secure_articles", action: "index" }, "http://test.host/secure/articles") end - assert_recognizes({ :controller => 'secure_articles', :action => 'index', :protocol => 'https://' }, 'https://test.host/secure/articles') + assert_recognizes({ controller: "secure_articles", action: "index", protocol: "https://" }, "https://test.host/secure/articles") end def test_assert_recognizes_with_block_constraint assert_raise(Assertion) do - assert_recognizes({ :controller => 'block_articles', :action => 'index' }, 'http://test.host/block/articles') + assert_recognizes({ controller: "block_articles", action: "index" }, "http://test.host/block/articles") end - assert_recognizes({ :controller => 'block_articles', :action => 'index' }, 'https://test.host/block/articles') + assert_recognizes({ controller: "block_articles", action: "index" }, "https://test.host/block/articles") end def test_assert_recognizes_with_query_constraint assert_raise(Assertion) do - assert_recognizes({ :controller => 'query_articles', :action => 'index', :use_query => 'false' }, '/query/articles', { :use_query => 'false' }) + assert_recognizes({ controller: "query_articles", action: "index", use_query: "false" }, "/query/articles", use_query: "false") end - assert_recognizes({ :controller => 'query_articles', :action => 'index', :use_query => 'true' }, '/query/articles', { :use_query => 'true' }) + assert_recognizes({ controller: "query_articles", action: "index", use_query: "true" }, "/query/articles", use_query: "true") end def test_assert_recognizes_raises_message err = assert_raise(Assertion) do - assert_recognizes({ :controller => 'secure_articles', :action => 'index' }, 'http://test.host/secure/articles', {}, "This is a really bad msg") + assert_recognizes({ controller: "secure_articles", action: "index" }, "http://test.host/secure/articles", {}, "This is a really bad msg") end assert_match err.message, "This is a really bad msg" end def test_assert_routing - assert_routing('/articles', :controller => 'articles', :action => 'index') + assert_routing("/articles", controller: "articles", action: "index") end def test_assert_routing_raises_message err = assert_raise(Assertion) do - assert_routing('/thisIsNotARoute', { :controller => 'articles', :action => 'edit', :id => '1' }, { :id => '1' }, {}, "This is a really bad msg") + assert_routing("/thisIsNotARoute", { controller: "articles", action: "edit", id: "1" }, { id: "1" }, {}, "This is a really bad msg") end assert_match err.message, "This is a really bad msg" end def test_assert_routing_with_defaults - assert_routing('/articles/1/edit', { :controller => 'articles', :action => 'edit', :id => '1' }, { :id => '1' }) + assert_routing("/articles/1/edit", { controller: "articles", action: "edit", id: "1" }, id: "1") end def test_assert_routing_with_extras - assert_routing('/articles', { :controller => 'articles', :action => 'index', :page => '1' }, { }, { :page => '1' }) + assert_routing("/articles", { controller: "articles", action: "index", page: "1" }, {}, page: "1") end def test_assert_routing_with_hash_constraint assert_raise(Assertion) do - assert_routing('http://test.host/secure/articles', { :controller => 'secure_articles', :action => 'index' }) + assert_routing("http://test.host/secure/articles", controller: "secure_articles", action: "index") end - assert_routing('https://test.host/secure/articles', { :controller => 'secure_articles', :action => 'index', :protocol => 'https://' }) + assert_routing("https://test.host/secure/articles", controller: "secure_articles", action: "index", protocol: "https://") end def test_assert_routing_with_block_constraint assert_raise(Assertion) do - assert_routing('http://test.host/block/articles', { :controller => 'block_articles', :action => 'index' }) + assert_routing("http://test.host/block/articles", controller: "block_articles", action: "index") end - assert_routing('https://test.host/block/articles', { :controller => 'block_articles', :action => 'index' }) + assert_routing("https://test.host/block/articles", controller: "block_articles", action: "index") end def test_with_routing with_routing do |routes| routes.draw do - resources :articles, :path => 'artikel' + resources :articles, path: "artikel" end - assert_routing('/artikel', :controller => 'articles', :action => 'index') + assert_routing("/artikel", controller: "articles", action: "index") assert_raise(Assertion) do - assert_routing('/articles', { :controller => 'articles', :action => 'index' }) + assert_routing("/articles", controller: "articles", action: "index") end end end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 5ead9357ae..c01065932a 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1,10 +1,10 @@ -require 'erb' -require 'abstract_unit' -require 'controller/fake_controllers' +require "erb" +require "abstract_unit" +require "controller/fake_controllers" class TestRoutingMapper < ActionDispatch::IntegrationTest SprocketsApp = lambda { |env| - [200, {"Content-Type" => "text/html"}, ["javascripts"]] + [200, { "Content-Type" => "text/html" }, ["javascripts"]] } class IpRestrictor @@ -28,87 +28,87 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest def test_logout draw do controller :sessions do - delete 'logout' => :destroy + delete "logout" => :destroy end end - delete '/logout' - assert_equal 'sessions#destroy', @response.body + delete "/logout" + assert_equal "sessions#destroy", @response.body - assert_equal '/logout', logout_path - assert_equal '/logout', url_for(:controller => 'sessions', :action => 'destroy', :only_path => true) + assert_equal "/logout", logout_path + assert_equal "/logout", url_for(controller: "sessions", action: "destroy", only_path: true) end def test_login draw do - default_url_options :host => "rubyonrails.org" + default_url_options host: "rubyonrails.org" controller :sessions do - get 'login' => :new - post 'login' => :create + get "login" => :new + post "login" => :create end end - get '/login' - assert_equal 'sessions#new', @response.body - assert_equal '/login', login_path + get "/login" + assert_equal "sessions#new", @response.body + assert_equal "/login", login_path - post '/login' - assert_equal 'sessions#create', @response.body + post "/login" + assert_equal "sessions#create", @response.body - assert_equal '/login', url_for(:controller => 'sessions', :action => 'create', :only_path => true) - assert_equal '/login', url_for(:controller => 'sessions', :action => 'new', :only_path => true) + assert_equal "/login", url_for(controller: "sessions", action: "create", only_path: true) + assert_equal "/login", url_for(controller: "sessions", action: "new", only_path: true) - assert_equal 'http://rubyonrails.org/login', url_for(:controller => 'sessions', :action => 'create') - assert_equal 'http://rubyonrails.org/login', login_url + assert_equal "http://rubyonrails.org/login", url_for(controller: "sessions", action: "create") + assert_equal "http://rubyonrails.org/login", login_url end def test_login_redirect draw do - get 'account/login', :to => redirect("/login") + get "account/login", to: redirect("/login") end - get '/account/login' - verify_redirect 'http://www.example.com/login' + get "/account/login" + verify_redirect "http://www.example.com/login" end def test_logout_redirect_without_to draw do - get 'account/logout' => redirect("/logout"), :as => :logout_redirect + get "account/logout" => redirect("/logout"), :as => :logout_redirect end - assert_equal '/account/logout', logout_redirect_path - get '/account/logout' - verify_redirect 'http://www.example.com/logout' + assert_equal "/account/logout", logout_redirect_path + get "/account/logout" + verify_redirect "http://www.example.com/logout" end def test_namespace_redirect draw do namespace :private do - root :to => redirect('/private/index') - get "index", :to => 'private#index' + root to: redirect("/private/index") + get "index", to: "private#index" end end - get '/private' - verify_redirect 'http://www.example.com/private/index' + get "/private" + verify_redirect "http://www.example.com/private/index" end def test_redirect_with_failing_constraint draw do - get 'hi', to: redirect("/foo"), constraints: ::TestRoutingMapper::GrumpyRestrictor + get "hi", to: redirect("/foo"), constraints: ::TestRoutingMapper::GrumpyRestrictor end - get '/hi' + get "/hi" assert_equal 404, status end def test_redirect_with_passing_constraint draw do - get 'hi', to: redirect("/foo"), constraints: ->(req) { true } + get "hi", to: redirect("/foo"), constraints: ->(req) { true } end - get '/hi' + get "/hi" assert_equal 301, status end @@ -116,7 +116,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_raise(ArgumentError) do draw do namespace :admin do - get '/:controller(/:action(/:id(.:format)))' + ActiveSupport::Deprecation.silence do + get "/:controller(/:action(/:id(.:format)))" + end end end end @@ -125,11 +127,13 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest def test_namespace_without_controller_segment draw do namespace :admin do - get 'hello/:controllers/:action' + ActiveSupport::Deprecation.silence do + get "hello/:controllers/:action" + end end end - get '/admin/hello/foo/new' - assert_equal 'foo', @request.params["controllers"] + get "/admin/hello/foo/new" + assert_equal "foo", @request.params["controllers"] end def test_session_singleton_resource @@ -140,30 +144,30 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/session' - assert_equal 'sessions#create', @response.body - assert_equal '/session', session_path + get "/session" + assert_equal "sessions#create", @response.body + assert_equal "/session", session_path - post '/session' - assert_equal 'sessions#create', @response.body + post "/session" + assert_equal "sessions#create", @response.body - put '/session' - assert_equal 'sessions#update', @response.body + put "/session" + assert_equal "sessions#update", @response.body - delete '/session' - assert_equal 'sessions#destroy', @response.body + delete "/session" + assert_equal "sessions#destroy", @response.body - get '/session/new' - assert_equal 'sessions#new', @response.body - assert_equal '/session/new', new_session_path + get "/session/new" + assert_equal "sessions#new", @response.body + assert_equal "/session/new", new_session_path - get '/session/edit' - assert_equal 'sessions#edit', @response.body - assert_equal '/session/edit', edit_session_path + get "/session/edit" + assert_equal "sessions#edit", @response.body + assert_equal "/session/edit", edit_session_path - post '/session/reset' - assert_equal 'sessions#reset', @response.body - assert_equal '/session/reset', reset_session_path + post "/session/reset" + assert_equal "sessions#reset", @response.body + assert_equal "/session/reset", reset_session_path end def test_session_singleton_resource_for_api_app @@ -180,28 +184,28 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest @app = RoutedRackApp.new routes end - get '/session' - assert_equal 'sessions#create', @response.body - assert_equal '/session', session_path + get "/session" + assert_equal "sessions#create", @response.body + assert_equal "/session", session_path - post '/session' - assert_equal 'sessions#create', @response.body + post "/session" + assert_equal "sessions#create", @response.body - put '/session' - assert_equal 'sessions#update', @response.body + put "/session" + assert_equal "sessions#update", @response.body - delete '/session' - assert_equal 'sessions#destroy', @response.body + delete "/session" + assert_equal "sessions#destroy", @response.body - post '/session/reset' - assert_equal 'sessions#reset', @response.body - assert_equal '/session/reset', reset_session_path + post "/session/reset" + assert_equal "sessions#reset", @response.body + assert_equal "/session/reset", reset_session_path - get '/session/new' - assert_equal 'Not Found', @response.body + get "/session/new" + assert_equal "Not Found", @response.body - get '/session/edit' - assert_equal 'Not Found', @response.body + get "/session/edit" + assert_equal "Not Found", @response.body end def test_session_info_nested_singleton_resource @@ -211,9 +215,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/session/info' - assert_equal 'infos#show', @response.body - assert_equal '/session/info', session_info_path + get "/session/info" + assert_equal "infos#show", @response.body + assert_equal "/session/info", session_info_path end def test_member_on_resource @@ -225,236 +229,243 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/session/crush' - assert_equal 'sessions#crush', @response.body - assert_equal '/session/crush', crush_session_path + get "/session/crush" + assert_equal "sessions#crush", @response.body + assert_equal "/session/crush", crush_session_path end def test_redirect_modulo draw do - get 'account/modulo/:name', :to => redirect("/%{name}s") + get "account/modulo/:name", to: redirect("/%{name}s") end - get '/account/modulo/name' - verify_redirect 'http://www.example.com/names' + get "/account/modulo/name" + verify_redirect "http://www.example.com/names" end def test_redirect_proc draw do - get 'account/proc/:name', :to => redirect {|params, req| "/#{params[:name].pluralize}" } + get "account/proc/:name", to: redirect { |params, req| "/#{params[:name].pluralize}" } end - get '/account/proc/person' - verify_redirect 'http://www.example.com/people' + get "/account/proc/person" + verify_redirect "http://www.example.com/people" end def test_redirect_proc_with_request draw do - get 'account/proc_req' => redirect {|params, req| "/#{req.method}" } + get "account/proc_req" => redirect { |params, req| "/#{req.method}" } end - get '/account/proc_req' - verify_redirect 'http://www.example.com/GET' + get "/account/proc_req" + verify_redirect "http://www.example.com/GET" end def test_redirect_hash_with_subdomain draw do - get 'mobile', :to => redirect(:subdomain => 'mobile') + get "mobile", to: redirect(subdomain: "mobile") end - get '/mobile' - verify_redirect 'http://mobile.example.com/mobile' + get "/mobile" + verify_redirect "http://mobile.example.com/mobile" end def test_redirect_hash_with_domain_and_path draw do - get 'documentation', :to => redirect(:domain => 'example-documentation.com', :path => '') + get "documentation", to: redirect(domain: "example-documentation.com", path: "") end - get '/documentation' - verify_redirect 'http://www.example-documentation.com' + get "/documentation" + verify_redirect "http://www.example-documentation.com" end def test_redirect_hash_with_path draw do - get 'new_documentation', :to => redirect(:path => '/documentation/new') + get "new_documentation", to: redirect(path: "/documentation/new") end - get '/new_documentation' - verify_redirect 'http://www.example.com/documentation/new' + get "/new_documentation" + verify_redirect "http://www.example.com/documentation/new" end def test_redirect_hash_with_host draw do - get 'super_new_documentation', :to => redirect(:host => 'super-docs.com') + get "super_new_documentation", to: redirect(host: "super-docs.com") end - get '/super_new_documentation?section=top' - verify_redirect 'http://super-docs.com/super_new_documentation?section=top' + get "/super_new_documentation?section=top" + verify_redirect "http://super-docs.com/super_new_documentation?section=top" end def test_redirect_hash_path_substitution draw do - get 'stores/:name', :to => redirect(:subdomain => 'stores', :path => '/%{name}') + get "stores/:name", to: redirect(subdomain: "stores", path: "/%{name}") end - get '/stores/iernest' - verify_redirect 'http://stores.example.com/iernest' + get "/stores/iernest" + verify_redirect "http://stores.example.com/iernest" end def test_redirect_hash_path_substitution_with_catch_all draw do - get 'stores/:name(*rest)', :to => redirect(:subdomain => 'stores', :path => '/%{name}%{rest}') + get "stores/:name(*rest)", to: redirect(subdomain: "stores", path: "/%{name}%{rest}") end - get '/stores/iernest/products' - verify_redirect 'http://stores.example.com/iernest/products' + get "/stores/iernest/products" + verify_redirect "http://stores.example.com/iernest/products" end def test_redirect_class draw do - get 'youtube_favorites/:youtube_id/:name', :to => redirect(YoutubeFavoritesRedirector) + get "youtube_favorites/:youtube_id/:name", to: redirect(YoutubeFavoritesRedirector) end - get '/youtube_favorites/oHg5SJYRHA0/rick-rolld' - verify_redirect 'http://www.youtube.com/watch?v=oHg5SJYRHA0' + get "/youtube_favorites/oHg5SJYRHA0/rick-rolld" + verify_redirect "http://www.youtube.com/watch?v=oHg5SJYRHA0" end def test_openid draw do - match 'openid/login', :via => [:get, :post], :to => "openid#login" + match "openid/login", via: [:get, :post], to: "openid#login" end - get '/openid/login' - assert_equal 'openid#login', @response.body + get "/openid/login" + assert_equal "openid#login", @response.body - post '/openid/login' - assert_equal 'openid#login', @response.body + post "/openid/login" + assert_equal "openid#login", @response.body end def test_bookmarks draw do - scope "bookmark", :controller => "bookmarks", :as => :bookmark do - get :new, :path => "build" - post :create, :path => "create", :as => "" + scope "bookmark", controller: "bookmarks", as: :bookmark do + get :new, path: "build" + post :create, path: "create", as: "" put :update - get :remove, :action => :destroy, :as => :remove + get :remove, action: :destroy, as: :remove end end - get '/bookmark/build' - assert_equal 'bookmarks#new', @response.body - assert_equal '/bookmark/build', bookmark_new_path + get "/bookmark/build" + assert_equal "bookmarks#new", @response.body + assert_equal "/bookmark/build", bookmark_new_path - post '/bookmark/create' - assert_equal 'bookmarks#create', @response.body - assert_equal '/bookmark/create', bookmark_path + post "/bookmark/create" + assert_equal "bookmarks#create", @response.body + assert_equal "/bookmark/create", bookmark_path - put '/bookmark/update' - assert_equal 'bookmarks#update', @response.body - assert_equal '/bookmark/update', bookmark_update_path + put "/bookmark/update" + assert_equal "bookmarks#update", @response.body + assert_equal "/bookmark/update", bookmark_update_path - get '/bookmark/remove' - assert_equal 'bookmarks#destroy', @response.body - assert_equal '/bookmark/remove', bookmark_remove_path + get "/bookmark/remove" + assert_equal "bookmarks#destroy", @response.body + assert_equal "/bookmark/remove", bookmark_remove_path end def test_pagemarks - tc = self draw do - scope "pagemark", :controller => "pagemarks", :as => :pagemark do - tc.assert_deprecated do - get "new", :path => "build" - end - post "create", :as => "" + scope "pagemark", controller: "pagemarks", as: :pagemark do + get "build", action: "new", as: "new" + post "create", as: "" put "update" - get "remove", :action => :destroy, :as => :remove + get "remove", action: :destroy, as: :remove + get "", action: :show, as: :show end end - get '/pagemark/build' - assert_equal 'pagemarks#new', @response.body - assert_equal '/pagemark/build', pagemark_new_path + get "/pagemark/build" + assert_equal "pagemarks#new", @response.body + assert_equal "/pagemark/build", pagemark_new_path + + post "/pagemark/create" + assert_equal "pagemarks#create", @response.body + assert_equal "/pagemark/create", pagemark_path - post '/pagemark/create' - assert_equal 'pagemarks#create', @response.body - assert_equal '/pagemark/create', pagemark_path + put "/pagemark/update" + assert_equal "pagemarks#update", @response.body + assert_equal "/pagemark/update", pagemark_update_path - put '/pagemark/update' - assert_equal 'pagemarks#update', @response.body - assert_equal '/pagemark/update', pagemark_update_path + get "/pagemark/remove" + assert_equal "pagemarks#destroy", @response.body + assert_equal "/pagemark/remove", pagemark_remove_path - get '/pagemark/remove' - assert_equal 'pagemarks#destroy', @response.body - assert_equal '/pagemark/remove', pagemark_remove_path + get "/pagemark" + assert_equal "pagemarks#show", @response.body + assert_equal "/pagemark", pagemark_show_path end def test_admin draw do - constraints(:ip => /192\.168\.1\.\d\d\d/) do - get 'admin' => "queenbee#index" + constraints(ip: /192\.168\.1\.\d\d\d/) do + get "admin" => "queenbee#index" end constraints ::TestRoutingMapper::IpRestrictor do - get 'admin/accounts' => "queenbee#accounts" + get "admin/accounts" => "queenbee#accounts" end - get 'admin/passwords' => "queenbee#passwords", :constraints => ::TestRoutingMapper::IpRestrictor + get "admin/passwords" => "queenbee#passwords", :constraints => ::TestRoutingMapper::IpRestrictor end - get '/admin', headers: { 'REMOTE_ADDR' => '192.168.1.100' } - assert_equal 'queenbee#index', @response.body + get "/admin", headers: { "REMOTE_ADDR" => "192.168.1.100" } + assert_equal "queenbee#index", @response.body - get '/admin', headers: { 'REMOTE_ADDR' => '10.0.0.100' } - assert_equal 'pass', @response.headers['X-Cascade'] + get "/admin", headers: { "REMOTE_ADDR" => "10.0.0.100" } + assert_equal "pass", @response.headers["X-Cascade"] - get '/admin/accounts', headers: { 'REMOTE_ADDR' => '192.168.1.100' } - assert_equal 'queenbee#accounts', @response.body + get "/admin/accounts", headers: { "REMOTE_ADDR" => "192.168.1.100" } + assert_equal "queenbee#accounts", @response.body - get '/admin/accounts', headers: { 'REMOTE_ADDR' => '10.0.0.100' } - assert_equal 'pass', @response.headers['X-Cascade'] + get "/admin/accounts", headers: { "REMOTE_ADDR" => "10.0.0.100" } + assert_equal "pass", @response.headers["X-Cascade"] - get '/admin/passwords', headers: { 'REMOTE_ADDR' => '192.168.1.100' } - assert_equal 'queenbee#passwords', @response.body + get "/admin/passwords", headers: { "REMOTE_ADDR" => "192.168.1.100" } + assert_equal "queenbee#passwords", @response.body - get '/admin/passwords', headers: { 'REMOTE_ADDR' => '10.0.0.100' } - assert_equal 'pass', @response.headers['X-Cascade'] + get "/admin/passwords", headers: { "REMOTE_ADDR" => "10.0.0.100" } + assert_equal "pass", @response.headers["X-Cascade"] end def test_global draw do controller(:global) do - get 'global/hide_notice' - get 'global/export', :action => :export, :as => :export_request - get '/export/:id/:file', :action => :export, :as => :export_download, :constraints => { :file => /.*/ } - get 'global/:action' + get "global/hide_notice" + get "global/export", action: :export, as: :export_request + get "/export/:id/:file", action: :export, as: :export_download, constraints: { file: /.*/ } + + ActiveSupport::Deprecation.silence do + get "global/:action" + end end end - get '/global/dashboard' - assert_equal 'global#dashboard', @response.body + get "/global/dashboard" + assert_equal "global#dashboard", @response.body - get '/global/export' - assert_equal 'global#export', @response.body + get "/global/export" + assert_equal "global#export", @response.body - get '/global/hide_notice' - assert_equal 'global#hide_notice', @response.body + get "/global/hide_notice" + assert_equal "global#hide_notice", @response.body - get '/export/123/foo.txt' - assert_equal 'global#export', @response.body + get "/export/123/foo.txt" + assert_equal "global#export", @response.body - assert_equal '/global/export', export_request_path - assert_equal '/global/hide_notice', global_hide_notice_path - assert_equal '/export/123/foo.txt', export_download_path(:id => 123, :file => 'foo.txt') + assert_equal "/global/export", export_request_path + assert_equal "/global/hide_notice", global_hide_notice_path + assert_equal "/export/123/foo.txt", export_download_path(id: 123, file: "foo.txt") end def test_local draw do - get "/local/:action", :controller => "local" + ActiveSupport::Deprecation.silence do + get "/local/:action", controller: "local" + end end - get '/local/dashboard' - assert_equal 'local#dashboard', @response.body + get "/local/dashboard" + assert_equal "local#dashboard", @response.body end # tests the use of dup in url_for @@ -464,7 +475,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end # without dup, additional (and possibly unwanted) values will be present in the options (eg. :host) - original_options = {:controller => 'projects', :action => 'status'} + original_options = { controller: "projects", action: "status" } options = original_options.dup url_for options @@ -478,23 +489,23 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest get "/projects/status(.:format)" end - controller = '/projects' - options = {:controller => controller, :action => 'status', :only_path => true} + controller = "/projects" + options = { controller: controller, action: "status", only_path: true } url = url_for(options) - assert_equal '/projects/status', url - assert_equal '/projects', controller + assert_equal "/projects/status", url + assert_equal "/projects", controller end # tests the arguments modification free version of define_hash_access def test_named_route_with_no_side_effects draw do resources :customers do - get "profile", :on => :member + get "profile", on: :member end end - original_options = { :host => 'test.host' } + original_options = { host: "test.host" } options = original_options.dup profile_customer_url("customer_model", options) @@ -508,45 +519,45 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest get "/projects/status(.:format)" end - assert_equal '/projects/status', url_for(:controller => 'projects', :action => 'status', :only_path => true) - assert_equal '/projects/status.json', url_for(:controller => 'projects', :action => 'status', :format => 'json', :only_path => true) + assert_equal "/projects/status", url_for(controller: "projects", action: "status", only_path: true) + assert_equal "/projects/status.json", url_for(controller: "projects", action: "status", format: "json", only_path: true) end def test_projects draw do - resources :projects, :controller => :project + resources :projects, controller: :project end - get '/projects' - assert_equal 'project#index', @response.body - assert_equal '/projects', projects_path + get "/projects" + assert_equal "project#index", @response.body + assert_equal "/projects", projects_path - post '/projects' - assert_equal 'project#create', @response.body + post "/projects" + assert_equal "project#create", @response.body - get '/projects.xml' - assert_equal 'project#index', @response.body - assert_equal '/projects.xml', projects_path(:format => 'xml') + get "/projects.xml" + assert_equal "project#index", @response.body + assert_equal "/projects.xml", projects_path(format: "xml") - get '/projects/new' - assert_equal 'project#new', @response.body - assert_equal '/projects/new', new_project_path + get "/projects/new" + assert_equal "project#new", @response.body + assert_equal "/projects/new", new_project_path - get '/projects/new.xml' - assert_equal 'project#new', @response.body - assert_equal '/projects/new.xml', new_project_path(:format => 'xml') + get "/projects/new.xml" + assert_equal "project#new", @response.body + assert_equal "/projects/new.xml", new_project_path(format: "xml") - get '/projects/1' - assert_equal 'project#show', @response.body - assert_equal '/projects/1', project_path(:id => '1') + get "/projects/1" + assert_equal "project#show", @response.body + assert_equal "/projects/1", project_path(id: "1") - get '/projects/1.xml' - assert_equal 'project#show', @response.body - assert_equal '/projects/1.xml', project_path(:id => '1', :format => 'xml') + get "/projects/1.xml" + assert_equal "project#show", @response.body + assert_equal "/projects/1.xml", project_path(id: "1", format: "xml") - get '/projects/1/edit' - assert_equal 'project#edit', @response.body - assert_equal '/projects/1/edit', edit_project_path(:id => '1') + get "/projects/1/edit" + assert_equal "project#edit", @response.body + assert_equal "/projects/1/edit", edit_project_path(id: "1") end def test_projects_for_api_app @@ -560,166 +571,166 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest @app = RoutedRackApp.new routes end - get '/projects' - assert_equal 'project#index', @response.body - assert_equal '/projects', projects_path + get "/projects" + assert_equal "project#index", @response.body + assert_equal "/projects", projects_path - post '/projects' - assert_equal 'project#create', @response.body + post "/projects" + assert_equal "project#create", @response.body - get '/projects.xml' - assert_equal 'project#index', @response.body - assert_equal '/projects.xml', projects_path(format: 'xml') + get "/projects.xml" + assert_equal "project#index", @response.body + assert_equal "/projects.xml", projects_path(format: "xml") - get '/projects/1' - assert_equal 'project#show', @response.body - assert_equal '/projects/1', project_path(id: '1') + get "/projects/1" + assert_equal "project#show", @response.body + assert_equal "/projects/1", project_path(id: "1") - get '/projects/1.xml' - assert_equal 'project#show', @response.body - assert_equal '/projects/1.xml', project_path(id: '1', format: 'xml') + get "/projects/1.xml" + assert_equal "project#show", @response.body + assert_equal "/projects/1.xml", project_path(id: "1", format: "xml") - get '/projects/1/edit' - assert_equal 'Not Found', @response.body + get "/projects/1/edit" + assert_equal "Not Found", @response.body end def test_projects_with_post_action_and_new_path_on_collection draw do - resources :projects, :controller => :project do - post 'new', :action => 'new', :on => :collection, :as => :new + resources :projects, controller: :project do + post "new", action: "new", on: :collection, as: :new end end - post '/projects/new' + post "/projects/new" assert_equal "project#new", @response.body assert_equal "/projects/new", new_projects_path end def test_projects_involvements draw do - resources :projects, :controller => :project do + resources :projects, controller: :project do resources :involvements, :attachments end end - get '/projects/1/involvements' - assert_equal 'involvements#index', @response.body - assert_equal '/projects/1/involvements', project_involvements_path(:project_id => '1') + get "/projects/1/involvements" + assert_equal "involvements#index", @response.body + assert_equal "/projects/1/involvements", project_involvements_path(project_id: "1") - get '/projects/1/involvements/new' - assert_equal 'involvements#new', @response.body - assert_equal '/projects/1/involvements/new', new_project_involvement_path(:project_id => '1') + get "/projects/1/involvements/new" + assert_equal "involvements#new", @response.body + assert_equal "/projects/1/involvements/new", new_project_involvement_path(project_id: "1") - get '/projects/1/involvements/1' - assert_equal 'involvements#show', @response.body - assert_equal '/projects/1/involvements/1', project_involvement_path(:project_id => '1', :id => '1') + get "/projects/1/involvements/1" + assert_equal "involvements#show", @response.body + assert_equal "/projects/1/involvements/1", project_involvement_path(project_id: "1", id: "1") - put '/projects/1/involvements/1' - assert_equal 'involvements#update', @response.body + put "/projects/1/involvements/1" + assert_equal "involvements#update", @response.body - delete '/projects/1/involvements/1' - assert_equal 'involvements#destroy', @response.body + delete "/projects/1/involvements/1" + assert_equal "involvements#destroy", @response.body - get '/projects/1/involvements/1/edit' - assert_equal 'involvements#edit', @response.body - assert_equal '/projects/1/involvements/1/edit', edit_project_involvement_path(:project_id => '1', :id => '1') + get "/projects/1/involvements/1/edit" + assert_equal "involvements#edit", @response.body + assert_equal "/projects/1/involvements/1/edit", edit_project_involvement_path(project_id: "1", id: "1") end def test_projects_attachments draw do - resources :projects, :controller => :project do + resources :projects, controller: :project do resources :involvements, :attachments end end - get '/projects/1/attachments' - assert_equal 'attachments#index', @response.body - assert_equal '/projects/1/attachments', project_attachments_path(:project_id => '1') + get "/projects/1/attachments" + assert_equal "attachments#index", @response.body + assert_equal "/projects/1/attachments", project_attachments_path(project_id: "1") end def test_projects_participants draw do - resources :projects, :controller => :project do + resources :projects, controller: :project do resources :participants do - put :update_all, :on => :collection + put :update_all, on: :collection end end end - get '/projects/1/participants' - assert_equal 'participants#index', @response.body - assert_equal '/projects/1/participants', project_participants_path(:project_id => '1') + get "/projects/1/participants" + assert_equal "participants#index", @response.body + assert_equal "/projects/1/participants", project_participants_path(project_id: "1") - put '/projects/1/participants/update_all' - assert_equal 'participants#update_all', @response.body - assert_equal '/projects/1/participants/update_all', update_all_project_participants_path(:project_id => '1') + put "/projects/1/participants/update_all" + assert_equal "participants#update_all", @response.body + assert_equal "/projects/1/participants/update_all", update_all_project_participants_path(project_id: "1") end def test_projects_companies draw do - resources :projects, :controller => :project do + resources :projects, controller: :project do resources :companies do resources :people - resource :avatar, :controller => :avatar + resource :avatar, controller: :avatar end end end - get '/projects/1/companies' - assert_equal 'companies#index', @response.body - assert_equal '/projects/1/companies', project_companies_path(:project_id => '1') + get "/projects/1/companies" + assert_equal "companies#index", @response.body + assert_equal "/projects/1/companies", project_companies_path(project_id: "1") - get '/projects/1/companies/1/people' - assert_equal 'people#index', @response.body - assert_equal '/projects/1/companies/1/people', project_company_people_path(:project_id => '1', :company_id => '1') + get "/projects/1/companies/1/people" + assert_equal "people#index", @response.body + assert_equal "/projects/1/companies/1/people", project_company_people_path(project_id: "1", company_id: "1") - get '/projects/1/companies/1/avatar' - assert_equal 'avatar#show', @response.body - assert_equal '/projects/1/companies/1/avatar', project_company_avatar_path(:project_id => '1', :company_id => '1') + get "/projects/1/companies/1/avatar" + assert_equal "avatar#show", @response.body + assert_equal "/projects/1/companies/1/avatar", project_company_avatar_path(project_id: "1", company_id: "1") end def test_project_manager draw do resources :projects do - resource :manager, :as => :super_manager do + resource :manager, as: :super_manager do post :fire end end end - get '/projects/1/manager' - assert_equal 'managers#show', @response.body - assert_equal '/projects/1/manager', project_super_manager_path(:project_id => '1') + get "/projects/1/manager" + assert_equal "managers#show", @response.body + assert_equal "/projects/1/manager", project_super_manager_path(project_id: "1") - get '/projects/1/manager/new' - assert_equal 'managers#new', @response.body - assert_equal '/projects/1/manager/new', new_project_super_manager_path(:project_id => '1') + get "/projects/1/manager/new" + assert_equal "managers#new", @response.body + assert_equal "/projects/1/manager/new", new_project_super_manager_path(project_id: "1") - post '/projects/1/manager/fire' - assert_equal 'managers#fire', @response.body - assert_equal '/projects/1/manager/fire', fire_project_super_manager_path(:project_id => '1') + post "/projects/1/manager/fire" + assert_equal "managers#fire", @response.body + assert_equal "/projects/1/manager/fire", fire_project_super_manager_path(project_id: "1") end def test_project_images draw do resources :projects do - resources :images, :as => :funny_images do - post :revise, :on => :member + resources :images, as: :funny_images do + post :revise, on: :member end end end - get '/projects/1/images' - assert_equal 'images#index', @response.body - assert_equal '/projects/1/images', project_funny_images_path(:project_id => '1') + get "/projects/1/images" + assert_equal "images#index", @response.body + assert_equal "/projects/1/images", project_funny_images_path(project_id: "1") - get '/projects/1/images/new' - assert_equal 'images#new', @response.body - assert_equal '/projects/1/images/new', new_project_funny_image_path(:project_id => '1') + get "/projects/1/images/new" + assert_equal "images#new", @response.body + assert_equal "/projects/1/images/new", new_project_funny_image_path(project_id: "1") - post '/projects/1/images/1/revise' - assert_equal 'images#revise', @response.body - assert_equal '/projects/1/images/1/revise', revise_project_funny_image_path(:project_id => '1', :id => '1') + post "/projects/1/images/1/revise" + assert_equal "images#revise", @response.body + assert_equal "/projects/1/images/1/revise", revise_project_funny_image_path(project_id: "1", id: "1") end def test_projects_people @@ -740,181 +751,181 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/projects/1/people' - assert_equal 'people#index', @response.body - assert_equal '/projects/1/people', project_people_path(:project_id => '1') + get "/projects/1/people" + assert_equal "people#index", @response.body + assert_equal "/projects/1/people", project_people_path(project_id: "1") - get '/projects/1/people/1' - assert_equal 'people#show', @response.body - assert_equal '/projects/1/people/1', project_person_path(:project_id => '1', :id => '1') + get "/projects/1/people/1" + assert_equal "people#show", @response.body + assert_equal "/projects/1/people/1", project_person_path(project_id: "1", id: "1") - get '/projects/1/people/1/7a2dec8/avatar' - assert_equal 'avatars#show', @response.body - assert_equal '/projects/1/people/1/7a2dec8/avatar', project_person_avatar_path(:project_id => '1', :person_id => '1', :access_token => '7a2dec8') + get "/projects/1/people/1/7a2dec8/avatar" + assert_equal "avatars#show", @response.body + assert_equal "/projects/1/people/1/7a2dec8/avatar", project_person_avatar_path(project_id: "1", person_id: "1", access_token: "7a2dec8") - put '/projects/1/people/1/accessible_projects' - assert_equal 'people#accessible_projects', @response.body - assert_equal '/projects/1/people/1/accessible_projects', accessible_projects_project_person_path(:project_id => '1', :id => '1') + put "/projects/1/people/1/accessible_projects" + assert_equal "people#accessible_projects", @response.body + assert_equal "/projects/1/people/1/accessible_projects", accessible_projects_project_person_path(project_id: "1", id: "1") - post '/projects/1/people/1/resend' - assert_equal 'people#resend', @response.body - assert_equal '/projects/1/people/1/resend', resend_project_person_path(:project_id => '1', :id => '1') + post "/projects/1/people/1/resend" + assert_equal "people#resend", @response.body + assert_equal "/projects/1/people/1/resend", resend_project_person_path(project_id: "1", id: "1") - post '/projects/1/people/1/generate_new_password' - assert_equal 'people#generate_new_password', @response.body - assert_equal '/projects/1/people/1/generate_new_password', generate_new_password_project_person_path(:project_id => '1', :id => '1') + post "/projects/1/people/1/generate_new_password" + assert_equal "people#generate_new_password", @response.body + assert_equal "/projects/1/people/1/generate_new_password", generate_new_password_project_person_path(project_id: "1", id: "1") end def test_projects_with_resources_path_names draw do - resources_path_names :correlation_indexes => "info_about_correlation_indexes" + resources_path_names correlation_indexes: "info_about_correlation_indexes" resources :projects do - get :correlation_indexes, :on => :collection + get :correlation_indexes, on: :collection end end - get '/projects/info_about_correlation_indexes' - assert_equal 'projects#correlation_indexes', @response.body - assert_equal '/projects/info_about_correlation_indexes', correlation_indexes_projects_path + get "/projects/info_about_correlation_indexes" + assert_equal "projects#correlation_indexes", @response.body + assert_equal "/projects/info_about_correlation_indexes", correlation_indexes_projects_path end def test_projects_posts draw do resources :projects do resources :posts do - get :archive, :toggle_view, :on => :collection - post :preview, :on => :member + get :archive, :toggle_view, on: :collection + post :preview, on: :member resource :subscription resources :comments do - post :preview, :on => :collection + post :preview, on: :collection end end end end - get '/projects/1/posts' - assert_equal 'posts#index', @response.body - assert_equal '/projects/1/posts', project_posts_path(:project_id => '1') + get "/projects/1/posts" + assert_equal "posts#index", @response.body + assert_equal "/projects/1/posts", project_posts_path(project_id: "1") - get '/projects/1/posts/archive' - assert_equal 'posts#archive', @response.body - assert_equal '/projects/1/posts/archive', archive_project_posts_path(:project_id => '1') + get "/projects/1/posts/archive" + assert_equal "posts#archive", @response.body + assert_equal "/projects/1/posts/archive", archive_project_posts_path(project_id: "1") - get '/projects/1/posts/toggle_view' - assert_equal 'posts#toggle_view', @response.body - assert_equal '/projects/1/posts/toggle_view', toggle_view_project_posts_path(:project_id => '1') + get "/projects/1/posts/toggle_view" + assert_equal "posts#toggle_view", @response.body + assert_equal "/projects/1/posts/toggle_view", toggle_view_project_posts_path(project_id: "1") - post '/projects/1/posts/1/preview' - assert_equal 'posts#preview', @response.body - assert_equal '/projects/1/posts/1/preview', preview_project_post_path(:project_id => '1', :id => '1') + post "/projects/1/posts/1/preview" + assert_equal "posts#preview", @response.body + assert_equal "/projects/1/posts/1/preview", preview_project_post_path(project_id: "1", id: "1") - get '/projects/1/posts/1/subscription' - assert_equal 'subscriptions#show', @response.body - assert_equal '/projects/1/posts/1/subscription', project_post_subscription_path(:project_id => '1', :post_id => '1') + get "/projects/1/posts/1/subscription" + assert_equal "subscriptions#show", @response.body + assert_equal "/projects/1/posts/1/subscription", project_post_subscription_path(project_id: "1", post_id: "1") - get '/projects/1/posts/1/comments' - assert_equal 'comments#index', @response.body - assert_equal '/projects/1/posts/1/comments', project_post_comments_path(:project_id => '1', :post_id => '1') + get "/projects/1/posts/1/comments" + assert_equal "comments#index", @response.body + assert_equal "/projects/1/posts/1/comments", project_post_comments_path(project_id: "1", post_id: "1") - post '/projects/1/posts/1/comments/preview' - assert_equal 'comments#preview', @response.body - assert_equal '/projects/1/posts/1/comments/preview', preview_project_post_comments_path(:project_id => '1', :post_id => '1') + post "/projects/1/posts/1/comments/preview" + assert_equal "comments#preview", @response.body + assert_equal "/projects/1/posts/1/comments/preview", preview_project_post_comments_path(project_id: "1", post_id: "1") end def test_replies draw do resources :replies do member do - put :answer, :action => :mark_as_answer - delete :answer, :action => :unmark_as_answer + put :answer, action: :mark_as_answer + delete :answer, action: :unmark_as_answer end end end - put '/replies/1/answer' - assert_equal 'replies#mark_as_answer', @response.body + put "/replies/1/answer" + assert_equal "replies#mark_as_answer", @response.body - delete '/replies/1/answer' - assert_equal 'replies#unmark_as_answer', @response.body + delete "/replies/1/answer" + assert_equal "replies#unmark_as_answer", @response.body end def test_resource_routes_with_only_and_except draw do - resources :posts, :only => [:index, :show] do - resources :comments, :except => :destroy + resources :posts, only: [:index, :show] do + resources :comments, except: :destroy end end - get '/posts' - assert_equal 'posts#index', @response.body - assert_equal '/posts', posts_path + get "/posts" + assert_equal "posts#index", @response.body + assert_equal "/posts", posts_path - get '/posts/1' - assert_equal 'posts#show', @response.body - assert_equal '/posts/1', post_path(:id => 1) + get "/posts/1" + assert_equal "posts#show", @response.body + assert_equal "/posts/1", post_path(id: 1) - get '/posts/1/comments' - assert_equal 'comments#index', @response.body - assert_equal '/posts/1/comments', post_comments_path(:post_id => 1) + get "/posts/1/comments" + assert_equal "comments#index", @response.body + assert_equal "/posts/1/comments", post_comments_path(post_id: 1) - post '/posts' - assert_equal 'pass', @response.headers['X-Cascade'] - put '/posts/1' - assert_equal 'pass', @response.headers['X-Cascade'] - delete '/posts/1' - assert_equal 'pass', @response.headers['X-Cascade'] - delete '/posts/1/comments' - assert_equal 'pass', @response.headers['X-Cascade'] + post "/posts" + assert_equal "pass", @response.headers["X-Cascade"] + put "/posts/1" + assert_equal "pass", @response.headers["X-Cascade"] + delete "/posts/1" + assert_equal "pass", @response.headers["X-Cascade"] + delete "/posts/1/comments" + assert_equal "pass", @response.headers["X-Cascade"] end def test_resource_routes_only_create_update_destroy draw do - resource :past, :only => :destroy - resource :present, :only => :update - resource :future, :only => :create + resource :past, only: :destroy + resource :present, only: :update + resource :future, only: :create end - delete '/past' - assert_equal 'pasts#destroy', @response.body - assert_equal '/past', past_path + delete "/past" + assert_equal "pasts#destroy", @response.body + assert_equal "/past", past_path - patch '/present' - assert_equal 'presents#update', @response.body - assert_equal '/present', present_path + patch "/present" + assert_equal "presents#update", @response.body + assert_equal "/present", present_path - put '/present' - assert_equal 'presents#update', @response.body - assert_equal '/present', present_path + put "/present" + assert_equal "presents#update", @response.body + assert_equal "/present", present_path - post '/future' - assert_equal 'futures#create', @response.body - assert_equal '/future', future_path + post "/future" + assert_equal "futures#create", @response.body + assert_equal "/future", future_path end def test_resources_routes_only_create_update_destroy draw do - resources :relationships, :only => [:create, :destroy] - resources :friendships, :only => [:update] + resources :relationships, only: [:create, :destroy] + resources :friendships, only: [:update] end - post '/relationships' - assert_equal 'relationships#create', @response.body - assert_equal '/relationships', relationships_path + post "/relationships" + assert_equal "relationships#create", @response.body + assert_equal "/relationships", relationships_path - delete '/relationships/1' - assert_equal 'relationships#destroy', @response.body - assert_equal '/relationships/1', relationship_path(1) + delete "/relationships/1" + assert_equal "relationships#destroy", @response.body + assert_equal "/relationships/1", relationship_path(1) - patch '/friendships/1' - assert_equal 'friendships#update', @response.body - assert_equal '/friendships/1', friendship_path(1) + patch "/friendships/1" + assert_equal "friendships#update", @response.body + assert_equal "/friendships/1", friendship_path(1) - put '/friendships/1' - assert_equal 'friendships#update', @response.body - assert_equal '/friendships/1', friendship_path(1) + put "/friendships/1" + assert_equal "friendships#update", @response.body + assert_equal "/friendships/1", friendship_path(1) end def test_resource_with_slugs_in_ids @@ -922,153 +933,153 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest resources :posts end - get '/posts/rails-rocks' - assert_equal 'posts#show', @response.body - assert_equal '/posts/rails-rocks', post_path(:id => 'rails-rocks') + get "/posts/rails-rocks" + assert_equal "posts#show", @response.body + assert_equal "/posts/rails-rocks", post_path(id: "rails-rocks") end def test_resources_for_uncountable_names draw do resources :sheep do - get "_it", :on => :member + get "_it", on: :member end end - assert_equal '/sheep', sheep_index_path - assert_equal '/sheep/1', sheep_path(1) - assert_equal '/sheep/new', new_sheep_path - assert_equal '/sheep/1/edit', edit_sheep_path(1) - assert_equal '/sheep/1/_it', _it_sheep_path(1) + assert_equal "/sheep", sheep_index_path + assert_equal "/sheep/1", sheep_path(1) + assert_equal "/sheep/new", new_sheep_path + assert_equal "/sheep/1/edit", edit_sheep_path(1) + assert_equal "/sheep/1/_it", _it_sheep_path(1) end def test_resource_does_not_modify_passed_options - options = {:id => /.+?/, :format => /json|xml/} + options = { id: /.+?/, format: /json|xml/ } draw { resource :user, options } - assert_equal({:id => /.+?/, :format => /json|xml/}, options) + assert_equal({ id: /.+?/, format: /json|xml/ }, options) end def test_resources_does_not_modify_passed_options - options = {:id => /.+?/, :format => /json|xml/} + options = { id: /.+?/, format: /json|xml/ } draw { resources :users, options } - assert_equal({:id => /.+?/, :format => /json|xml/}, options) + assert_equal({ id: /.+?/, format: /json|xml/ }, options) end def test_path_names draw do - scope 'pt', :as => 'pt' do - resources :projects, :path_names => { :edit => 'editar', :new => 'novo' }, :path => 'projetos' - resource :admin, :path_names => { :new => 'novo', :activate => 'ativar' }, :path => 'administrador' do - put :activate, :on => :member + scope "pt", as: "pt" do + resources :projects, path_names: { edit: "editar", new: "novo" }, path: "projetos" + resource :admin, path_names: { new: "novo", activate: "ativar" }, path: "administrador" do + put :activate, on: :member end end end - get '/pt/projetos' - assert_equal 'projects#index', @response.body - assert_equal '/pt/projetos', pt_projects_path + get "/pt/projetos" + assert_equal "projects#index", @response.body + assert_equal "/pt/projetos", pt_projects_path - get '/pt/projetos/1/editar' - assert_equal 'projects#edit', @response.body - assert_equal '/pt/projetos/1/editar', edit_pt_project_path(1) + get "/pt/projetos/1/editar" + assert_equal "projects#edit", @response.body + assert_equal "/pt/projetos/1/editar", edit_pt_project_path(1) - get '/pt/administrador' - assert_equal 'admins#show', @response.body - assert_equal '/pt/administrador', pt_admin_path + get "/pt/administrador" + assert_equal "admins#show", @response.body + assert_equal "/pt/administrador", pt_admin_path - get '/pt/administrador/novo' - assert_equal 'admins#new', @response.body - assert_equal '/pt/administrador/novo', new_pt_admin_path + get "/pt/administrador/novo" + assert_equal "admins#new", @response.body + assert_equal "/pt/administrador/novo", new_pt_admin_path - put '/pt/administrador/ativar' - assert_equal 'admins#activate', @response.body - assert_equal '/pt/administrador/ativar', activate_pt_admin_path + put "/pt/administrador/ativar" + assert_equal "admins#activate", @response.body + assert_equal "/pt/administrador/ativar", activate_pt_admin_path end def test_path_option_override draw do - scope 'pt', :as => 'pt' do - resources :projects, :path_names => { :new => 'novo' }, :path => 'projetos' do - put :close, :on => :member, :path => 'fechar' - get :open, :on => :new, :path => 'abrir' + scope "pt", as: "pt" do + resources :projects, path_names: { new: "novo" }, path: "projetos" do + put :close, on: :member, path: "fechar" + get :open, on: :new, path: "abrir" end end end - get '/pt/projetos/novo/abrir' - assert_equal 'projects#open', @response.body - assert_equal '/pt/projetos/novo/abrir', open_new_pt_project_path + get "/pt/projetos/novo/abrir" + assert_equal "projects#open", @response.body + assert_equal "/pt/projetos/novo/abrir", open_new_pt_project_path - put '/pt/projetos/1/fechar' - assert_equal 'projects#close', @response.body - assert_equal '/pt/projetos/1/fechar', close_pt_project_path(1) + put "/pt/projetos/1/fechar" + assert_equal "projects#close", @response.body + assert_equal "/pt/projetos/1/fechar", close_pt_project_path(1) end def test_sprockets draw do - get 'sprockets.js' => ::TestRoutingMapper::SprocketsApp + get "sprockets.js" => ::TestRoutingMapper::SprocketsApp end - get '/sprockets.js' - assert_equal 'javascripts', @response.body + get "/sprockets.js" + assert_equal "javascripts", @response.body end def test_update_person_route draw do - get 'people/:id/update', :to => 'people#update', :as => :update_person + get "people/:id/update", to: "people#update", as: :update_person end - get '/people/1/update' - assert_equal 'people#update', @response.body + get "/people/1/update" + assert_equal "people#update", @response.body - assert_equal '/people/1/update', update_person_path(:id => 1) + assert_equal "/people/1/update", update_person_path(id: 1) end def test_update_project_person draw do - get '/projects/:project_id/people/:id/update', :to => 'people#update', :as => :update_project_person + get "/projects/:project_id/people/:id/update", to: "people#update", as: :update_project_person end - get '/projects/1/people/2/update' - assert_equal 'people#update', @response.body + get "/projects/1/people/2/update" + assert_equal "people#update", @response.body - assert_equal '/projects/1/people/2/update', update_project_person_path(:project_id => 1, :id => 2) + assert_equal "/projects/1/people/2/update", update_project_person_path(project_id: 1, id: 2) end def test_forum_products draw do namespace :forum do - resources :products, :path => '' do + resources :products, path: "" do resources :questions end end end - get '/forum' - assert_equal 'forum/products#index', @response.body - assert_equal '/forum', forum_products_path + get "/forum" + assert_equal "forum/products#index", @response.body + assert_equal "/forum", forum_products_path - get '/forum/basecamp' - assert_equal 'forum/products#show', @response.body - assert_equal '/forum/basecamp', forum_product_path(:id => 'basecamp') + get "/forum/basecamp" + assert_equal "forum/products#show", @response.body + assert_equal "/forum/basecamp", forum_product_path(id: "basecamp") - get '/forum/basecamp/questions' - assert_equal 'forum/questions#index', @response.body - assert_equal '/forum/basecamp/questions', forum_product_questions_path(:product_id => 'basecamp') + get "/forum/basecamp/questions" + assert_equal "forum/questions#index", @response.body + assert_equal "/forum/basecamp/questions", forum_product_questions_path(product_id: "basecamp") - get '/forum/basecamp/questions/1' - assert_equal 'forum/questions#show', @response.body - assert_equal '/forum/basecamp/questions/1', forum_product_question_path(:product_id => 'basecamp', :id => 1) + get "/forum/basecamp/questions/1" + assert_equal "forum/questions#show", @response.body + assert_equal "/forum/basecamp/questions/1", forum_product_question_path(product_id: "basecamp", id: 1) end def test_articles_perma draw do - get 'articles/:year/:month/:day/:title', :to => "articles#show", :as => :article + get "articles/:year/:month/:day/:title", to: "articles#show", as: :article end - get '/articles/2009/08/18/rails-3' - assert_equal 'articles#show', @response.body + get "/articles/2009/08/18/rails-3" + assert_equal "articles#show", @response.body - assert_equal '/articles/2009/8/18/rails-3', article_path(:year => 2009, :month => 8, :day => 18, :title => 'rails-3') + assert_equal "/articles/2009/8/18/rails-3", article_path(year: 2009, month: 8, day: 18, title: "rails-3") end def test_account_namespace @@ -1078,17 +1089,17 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/account/subscription' - assert_equal 'account/subscriptions#show', @response.body - assert_equal '/account/subscription', account_subscription_path + get "/account/subscription" + assert_equal "account/subscriptions#show", @response.body + assert_equal "/account/subscription", account_subscription_path - get '/account/credit' - assert_equal 'account/credits#show', @response.body - assert_equal '/account/credit', account_credit_path + get "/account/credit" + assert_equal "account/credits#show", @response.body + assert_equal "/account/credit", account_credit_path - get '/account/credit_card' - assert_equal 'account/credit_cards#show', @response.body - assert_equal '/account/credit_card', account_credit_card_path + get "/account/credit_card" + assert_equal "account/credit_cards#show", @response.body + assert_equal "/account/credit_card", account_credit_card_path end def test_nested_namespace @@ -1100,9 +1111,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/account/admin/subscription' - assert_equal 'account/admin/subscriptions#show', @response.body - assert_equal '/account/admin/subscription', account_admin_subscription_path + get "/account/admin/subscription" + assert_equal "account/admin/subscriptions#show", @response.body + assert_equal "/account/admin/subscription", account_admin_subscription_path end def test_namespace_nested_in_resources @@ -1118,155 +1129,155 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/clients/1/google/account' - assert_equal '/clients/1/google/account', client_google_account_path(1) - assert_equal 'google/accounts#show', @response.body + get "/clients/1/google/account" + assert_equal "/clients/1/google/account", client_google_account_path(1) + assert_equal "google/accounts#show", @response.body - get '/clients/1/google/account/secret/info' - assert_equal '/clients/1/google/account/secret/info', client_google_account_secret_info_path(1) - assert_equal 'google/secret/infos#show', @response.body + get "/clients/1/google/account/secret/info" + assert_equal "/clients/1/google/account/secret/info", client_google_account_secret_info_path(1) + assert_equal "google/secret/infos#show", @response.body end def test_namespace_with_options draw do - namespace :users, :path => 'usuarios' do - root :to => 'home#index' + namespace :users, path: "usuarios" do + root to: "home#index" end end - get '/usuarios' - assert_equal '/usuarios', users_root_path - assert_equal 'users/home#index', @response.body + get "/usuarios" + assert_equal "/usuarios", users_root_path + assert_equal "users/home#index", @response.body end def test_namespaced_shallow_routes_with_module_option draw do - namespace :foo, module: 'bar' do + namespace :foo, module: "bar" do resources :posts, only: [:index, :show] do resources :comments, only: [:index, :show], shallow: true end end end - get '/foo/posts' - assert_equal '/foo/posts', foo_posts_path - assert_equal 'bar/posts#index', @response.body + get "/foo/posts" + assert_equal "/foo/posts", foo_posts_path + assert_equal "bar/posts#index", @response.body - get '/foo/posts/1' - assert_equal '/foo/posts/1', foo_post_path('1') - assert_equal 'bar/posts#show', @response.body + get "/foo/posts/1" + assert_equal "/foo/posts/1", foo_post_path("1") + assert_equal "bar/posts#show", @response.body - get '/foo/posts/1/comments' - assert_equal '/foo/posts/1/comments', foo_post_comments_path('1') - assert_equal 'bar/comments#index', @response.body + get "/foo/posts/1/comments" + assert_equal "/foo/posts/1/comments", foo_post_comments_path("1") + assert_equal "bar/comments#index", @response.body - get '/foo/comments/2' - assert_equal '/foo/comments/2', foo_comment_path('2') - assert_equal 'bar/comments#show', @response.body + get "/foo/comments/2" + assert_equal "/foo/comments/2", foo_comment_path("2") + assert_equal "bar/comments#show", @response.body end def test_namespaced_shallow_routes_with_path_option draw do - namespace :foo, path: 'bar' do + namespace :foo, path: "bar" do resources :posts, only: [:index, :show] do resources :comments, only: [:index, :show], shallow: true end end end - get '/bar/posts' - assert_equal '/bar/posts', foo_posts_path - assert_equal 'foo/posts#index', @response.body + get "/bar/posts" + assert_equal "/bar/posts", foo_posts_path + assert_equal "foo/posts#index", @response.body - get '/bar/posts/1' - assert_equal '/bar/posts/1', foo_post_path('1') - assert_equal 'foo/posts#show', @response.body + get "/bar/posts/1" + assert_equal "/bar/posts/1", foo_post_path("1") + assert_equal "foo/posts#show", @response.body - get '/bar/posts/1/comments' - assert_equal '/bar/posts/1/comments', foo_post_comments_path('1') - assert_equal 'foo/comments#index', @response.body + get "/bar/posts/1/comments" + assert_equal "/bar/posts/1/comments", foo_post_comments_path("1") + assert_equal "foo/comments#index", @response.body - get '/bar/comments/2' - assert_equal '/bar/comments/2', foo_comment_path('2') - assert_equal 'foo/comments#show', @response.body + get "/bar/comments/2" + assert_equal "/bar/comments/2", foo_comment_path("2") + assert_equal "foo/comments#show", @response.body end def test_namespaced_shallow_routes_with_as_option draw do - namespace :foo, as: 'bar' do + namespace :foo, as: "bar" do resources :posts, only: [:index, :show] do resources :comments, only: [:index, :show], shallow: true end end end - get '/foo/posts' - assert_equal '/foo/posts', bar_posts_path - assert_equal 'foo/posts#index', @response.body + get "/foo/posts" + assert_equal "/foo/posts", bar_posts_path + assert_equal "foo/posts#index", @response.body - get '/foo/posts/1' - assert_equal '/foo/posts/1', bar_post_path('1') - assert_equal 'foo/posts#show', @response.body + get "/foo/posts/1" + assert_equal "/foo/posts/1", bar_post_path("1") + assert_equal "foo/posts#show", @response.body - get '/foo/posts/1/comments' - assert_equal '/foo/posts/1/comments', bar_post_comments_path('1') - assert_equal 'foo/comments#index', @response.body + get "/foo/posts/1/comments" + assert_equal "/foo/posts/1/comments", bar_post_comments_path("1") + assert_equal "foo/comments#index", @response.body - get '/foo/comments/2' - assert_equal '/foo/comments/2', bar_comment_path('2') - assert_equal 'foo/comments#show', @response.body + get "/foo/comments/2" + assert_equal "/foo/comments/2", bar_comment_path("2") + assert_equal "foo/comments#show", @response.body end def test_namespaced_shallow_routes_with_shallow_path_option draw do - namespace :foo, shallow_path: 'bar' do + namespace :foo, shallow_path: "bar" do resources :posts, only: [:index, :show] do resources :comments, only: [:index, :show], shallow: true end end end - get '/foo/posts' - assert_equal '/foo/posts', foo_posts_path - assert_equal 'foo/posts#index', @response.body + get "/foo/posts" + assert_equal "/foo/posts", foo_posts_path + assert_equal "foo/posts#index", @response.body - get '/foo/posts/1' - assert_equal '/foo/posts/1', foo_post_path('1') - assert_equal 'foo/posts#show', @response.body + get "/foo/posts/1" + assert_equal "/foo/posts/1", foo_post_path("1") + assert_equal "foo/posts#show", @response.body - get '/foo/posts/1/comments' - assert_equal '/foo/posts/1/comments', foo_post_comments_path('1') - assert_equal 'foo/comments#index', @response.body + get "/foo/posts/1/comments" + assert_equal "/foo/posts/1/comments", foo_post_comments_path("1") + assert_equal "foo/comments#index", @response.body - get '/bar/comments/2' - assert_equal '/bar/comments/2', foo_comment_path('2') - assert_equal 'foo/comments#show', @response.body + get "/bar/comments/2" + assert_equal "/bar/comments/2", foo_comment_path("2") + assert_equal "foo/comments#show", @response.body end def test_namespaced_shallow_routes_with_shallow_prefix_option draw do - namespace :foo, shallow_prefix: 'bar' do + namespace :foo, shallow_prefix: "bar" do resources :posts, only: [:index, :show] do resources :comments, only: [:index, :show], shallow: true end end end - get '/foo/posts' - assert_equal '/foo/posts', foo_posts_path - assert_equal 'foo/posts#index', @response.body + get "/foo/posts" + assert_equal "/foo/posts", foo_posts_path + assert_equal "foo/posts#index", @response.body - get '/foo/posts/1' - assert_equal '/foo/posts/1', foo_post_path('1') - assert_equal 'foo/posts#show', @response.body + get "/foo/posts/1" + assert_equal "/foo/posts/1", foo_post_path("1") + assert_equal "foo/posts#show", @response.body - get '/foo/posts/1/comments' - assert_equal '/foo/posts/1/comments', foo_post_comments_path('1') - assert_equal 'foo/comments#index', @response.body + get "/foo/posts/1/comments" + assert_equal "/foo/posts/1/comments", foo_post_comments_path("1") + assert_equal "foo/comments#index", @response.body - get '/foo/comments/2' - assert_equal '/foo/comments/2', bar_comment_path('2') - assert_equal 'foo/comments#show', @response.body + get "/foo/comments/2" + assert_equal "/foo/comments/2", bar_comment_path("2") + assert_equal "foo/comments#show", @response.body end def test_namespace_containing_numbers @@ -1276,81 +1287,81 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/v2/subscriptions' - assert_equal 'v2/subscriptions#index', @response.body - assert_equal '/v2/subscriptions', v2_subscriptions_path + get "/v2/subscriptions" + assert_equal "v2/subscriptions#index", @response.body + assert_equal "/v2/subscriptions", v2_subscriptions_path end def test_articles_with_id draw do controller :articles do - scope '/articles', :as => 'article' do - scope :path => '/:title', :title => /[a-z]+/, :as => :with_title do - get '/:id', :action => :with_id, :as => "" + scope "/articles", as: "article" do + scope path: "/:title", title: /[a-z]+/, as: :with_title do + get "/:id", action: :with_id, as: "" end end end end - get '/articles/rails/1' - assert_equal 'articles#with_id', @response.body + get "/articles/rails/1" + assert_equal "articles#with_id", @response.body - get '/articles/123/1' - assert_equal 'pass', @response.headers['X-Cascade'] + get "/articles/123/1" + assert_equal "pass", @response.headers["X-Cascade"] - assert_equal '/articles/rails/1', article_with_title_path(:title => 'rails', :id => 1) + assert_equal "/articles/rails/1", article_with_title_path(title: "rails", id: 1) end def test_access_token_rooms draw do - scope ':access_token', :constraints => { :access_token => /\w{5,5}/ } do + scope ":access_token", constraints: { access_token: /\w{5,5}/ } do resources :rooms end end - get '/12345/rooms' - assert_equal 'rooms#index', @response.body + get "/12345/rooms" + assert_equal "rooms#index", @response.body - get '/12345/rooms/1' - assert_equal 'rooms#show', @response.body + get "/12345/rooms/1" + assert_equal "rooms#show", @response.body - get '/12345/rooms/1/edit' - assert_equal 'rooms#edit', @response.body + get "/12345/rooms/1/edit" + assert_equal "rooms#edit", @response.body end def test_root draw do - root :to => 'projects#index' + root to: "projects#index" end - assert_equal '/', root_path - get '/' - assert_equal 'projects#index', @response.body + assert_equal "/", root_path + get "/" + assert_equal "projects#index", @response.body end def test_scoped_root draw do - scope '(:locale)', :locale => /en|pl/ do - root :to => 'projects#index' + scope "(:locale)", locale: /en|pl/ do + root to: "projects#index" end end - assert_equal '/en', root_path(:locale => 'en') - get '/en' - assert_equal 'projects#index', @response.body + assert_equal "/en", root_path(locale: "en") + get "/en" + assert_equal "projects#index", @response.body end def test_scoped_root_as_name draw do - scope '(:locale)', :locale => /en|pl/ do - root :to => 'projects#index', :as => 'projects' + scope "(:locale)", locale: /en|pl/ do + root to: "projects#index", as: "projects" end end - assert_equal '/en', projects_path(:locale => 'en') - assert_equal '/', projects_path - get '/en' - assert_equal 'projects#index', @response.body + assert_equal "/en", projects_path(locale: "en") + assert_equal "/", projects_path + get "/en" + assert_equal "projects#index", @response.body end def test_scope_with_format_option @@ -1368,10 +1379,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal "/scoped/index", no_format_scoped_path assert_equal "/scoped/index?format=html", no_format_scoped_path(format: "html") - get '/scoped/index' + get "/scoped/index" assert_equal "scoped#index", @response.body - get '/scoped/index.html' + get "/scoped/index.html" assert_equal "Not Found", @response.body end @@ -1406,63 +1417,63 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest def test_index draw do - get '/info' => 'projects#info', :as => 'info' + get "/info" => "projects#info", :as => "info" end - assert_equal '/info', info_path - get '/info' - assert_equal 'projects#info', @response.body + assert_equal "/info", info_path + get "/info" + assert_equal "projects#info", @response.body end def test_match_with_many_paths_containing_a_slash draw do - get 'get/first', 'get/second', 'get/third', :to => 'get#show' + get "get/first", "get/second", "get/third", to: "get#show" end - get '/get/first' - assert_equal 'get#show', @response.body + get "/get/first" + assert_equal "get#show", @response.body - get '/get/second' - assert_equal 'get#show', @response.body + get "/get/second" + assert_equal "get#show", @response.body - get '/get/third' - assert_equal 'get#show', @response.body + get "/get/third" + assert_equal "get#show", @response.body end def test_match_shorthand_with_no_scope draw do - get 'account/overview' + get "account/overview" end - assert_equal '/account/overview', account_overview_path - get '/account/overview' - assert_equal 'account#overview', @response.body + assert_equal "/account/overview", account_overview_path + get "/account/overview" + assert_equal "account#overview", @response.body end def test_match_shorthand_inside_namespace draw do namespace :account do - get 'shorthand' + get "shorthand" end end - assert_equal '/account/shorthand', account_shorthand_path - get '/account/shorthand' - assert_equal 'account#shorthand', @response.body + assert_equal "/account/shorthand", account_shorthand_path + get "/account/shorthand" + assert_equal "account#shorthand", @response.body end def test_match_shorthand_with_multiple_paths_inside_namespace draw do namespace :proposals do - put 'activate', 'inactivate' + put "activate", "inactivate" end end - put '/proposals/activate' - assert_equal 'proposals#activate', @response.body + put "/proposals/activate" + assert_equal "proposals#activate", @response.body - put '/proposals/inactivate' - assert_equal 'proposals#inactivate', @response.body + put "/proposals/inactivate" + assert_equal "proposals#inactivate", @response.body end def test_match_shorthand_inside_namespace_with_controller @@ -1472,126 +1483,130 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - assert_equal '/api/products/list', api_products_list_path - get '/api/products/list' - assert_equal 'api/products#list', @response.body + assert_equal "/api/products/list", api_products_list_path + get "/api/products/list" + assert_equal "api/products#list", @response.body end def test_match_shorthand_inside_scope_with_variables_with_controller draw do - scope ':locale' do - match 'questions/new', via: [:get] + scope ":locale" do + match "questions/new", via: [:get] end end - get '/de/questions/new' - assert_equal 'questions#new', @response.body - assert_equal 'de', @request.params[:locale] + get "/de/questions/new" + assert_equal "questions#new", @response.body + assert_equal "de", @request.params[:locale] end def test_match_shorthand_inside_nested_namespaces_and_scopes_with_controller draw do namespace :api do namespace :v3 do - scope ':locale' do + scope ":locale" do get "products/list" end end end end - get '/api/v3/en/products/list' - assert_equal 'api/v3/products#list', @response.body + get "/api/v3/en/products/list" + assert_equal "api/v3/products#list", @response.body end def test_not_matching_shorthand_with_dynamic_parameters draw do - get ':controller/:action/admin' + ActiveSupport::Deprecation.silence do + get ":controller/:action/admin" + end end - get '/finances/overview/admin' - assert_equal 'finances#overview', @response.body + get "/finances/overview/admin" + assert_equal "finances#overview", @response.body end def test_controller_option_with_nesting_and_leading_slash draw do - scope '/job', controller: 'job' do - scope ':id', action: 'manage_applicant' do + scope "/job", controller: "job" do + scope ":id", action: "manage_applicant" do get "/active" end end end - get '/job/5/active' - assert_equal 'job#manage_applicant', @response.body + get "/job/5/active" + assert_equal "job#manage_applicant", @response.body end def test_dynamically_generated_helpers_on_collection_do_not_clobber_resources_url_helper draw do resources :replies do collection do - get 'page/:page' => 'replies#index', :page => %r{\d+} - get ':page' => 'replies#index', :page => %r{\d+} + get "page/:page" => "replies#index", :page => %r{\d+} + get ":page" => "replies#index", :page => %r{\d+} end end end - assert_equal '/replies', replies_path + assert_equal "/replies", replies_path end def test_scoped_controller_with_namespace_and_action draw do namespace :account do - get ':action/callback', :action => /twitter|github/, :controller => "callbacks", :as => :callback + ActiveSupport::Deprecation.silence do + get ":action/callback", action: /twitter|github/, controller: "callbacks", as: :callback + end end end - assert_equal '/account/twitter/callback', account_callback_path("twitter") - get '/account/twitter/callback' - assert_equal 'account/callbacks#twitter', @response.body + assert_equal "/account/twitter/callback", account_callback_path("twitter") + get "/account/twitter/callback" + assert_equal "account/callbacks#twitter", @response.body - get '/account/whatever/callback' - assert_equal 'Not Found', @response.body + get "/account/whatever/callback" + assert_equal "Not Found", @response.body end def test_convention_match_nested_and_with_leading_slash draw do - get '/account/nested/overview' + get "/account/nested/overview" end - assert_equal '/account/nested/overview', account_nested_overview_path - get '/account/nested/overview' - assert_equal 'account/nested#overview', @response.body + assert_equal "/account/nested/overview", account_nested_overview_path + get "/account/nested/overview" + assert_equal "account/nested#overview", @response.body end def test_convention_with_explicit_end draw do - get 'sign_in' => "sessions#new" + get "sign_in" => "sessions#new" end - get '/sign_in' - assert_equal 'sessions#new', @response.body - assert_equal '/sign_in', sign_in_path + get "/sign_in" + assert_equal "sessions#new", @response.body + assert_equal "/sign_in", sign_in_path end def test_redirect_with_complete_url_and_status draw do - get 'account/google' => redirect('http://www.google.com/', :status => 302) + get "account/google" => redirect("http://www.google.com/", status: 302) end - get '/account/google' - verify_redirect 'http://www.google.com/', 302 + get "/account/google" + verify_redirect "http://www.google.com/", 302 end def test_redirect_with_port draw do - get 'account/login', :to => redirect("/login") + get "account/login", to: redirect("/login") end - previous_host, self.host = self.host, 'www.example.com:3000' + previous_host, self.host = self.host, "www.example.com:3000" - get '/account/login' - verify_redirect 'http://www.example.com:3000/login' + get "/account/login" + verify_redirect "http://www.example.com:3000/login" ensure self.host = previous_host end @@ -1599,270 +1614,310 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest def test_normalize_namespaced_matches draw do namespace :account do - get 'description', :action => :description, :as => "description" + get "description", action: :description, as: "description" end end - assert_equal '/account/description', account_description_path + assert_equal "/account/description", account_description_path - get '/account/description' - assert_equal 'account#description', @response.body + get "/account/description" + assert_equal "account#description", @response.body end def test_namespaced_roots draw do namespace :account do - root :to => "account#index" + root to: "account#index" end end - assert_equal '/account', account_root_path - get '/account' - assert_equal 'account/account#index', @response.body + assert_equal "/account", account_root_path + get "/account" + assert_equal "account/account#index", @response.body end def test_optional_scoped_root draw do - scope '(:locale)', :locale => /en|pl/ do - root :to => 'projects#index' + scope "(:locale)", locale: /en|pl/ do + root to: "projects#index" end end - assert_equal '/en', root_path("en") - get '/en' - assert_equal 'projects#index', @response.body + assert_equal "/en", root_path("en") + get "/en" + assert_equal "projects#index", @response.body end def test_optional_scoped_path draw do - scope '(:locale)', :locale => /en|pl/ do + scope "(:locale)", locale: /en|pl/ do resources :descriptions end end - assert_equal '/en/descriptions', descriptions_path("en") - assert_equal '/descriptions', descriptions_path(nil) - assert_equal '/en/descriptions/1', description_path("en", 1) - assert_equal '/descriptions/1', description_path(nil, 1) + assert_equal "/en/descriptions", descriptions_path("en") + assert_equal "/descriptions", descriptions_path(nil) + assert_equal "/en/descriptions/1", description_path("en", 1) + assert_equal "/descriptions/1", description_path(nil, 1) - get '/en/descriptions' - assert_equal 'descriptions#index', @response.body + get "/en/descriptions" + assert_equal "descriptions#index", @response.body - get '/descriptions' - assert_equal 'descriptions#index', @response.body + get "/descriptions" + assert_equal "descriptions#index", @response.body - get '/en/descriptions/1' - assert_equal 'descriptions#show', @response.body + get "/en/descriptions/1" + assert_equal "descriptions#show", @response.body - get '/descriptions/1' - assert_equal 'descriptions#show', @response.body + get "/descriptions/1" + assert_equal "descriptions#show", @response.body end def test_nested_optional_scoped_path draw do namespace :admin do - scope '(:locale)', :locale => /en|pl/ do + scope "(:locale)", locale: /en|pl/ do resources :descriptions end end end - assert_equal '/admin/en/descriptions', admin_descriptions_path("en") - assert_equal '/admin/descriptions', admin_descriptions_path(nil) - assert_equal '/admin/en/descriptions/1', admin_description_path("en", 1) - assert_equal '/admin/descriptions/1', admin_description_path(nil, 1) + assert_equal "/admin/en/descriptions", admin_descriptions_path("en") + assert_equal "/admin/descriptions", admin_descriptions_path(nil) + assert_equal "/admin/en/descriptions/1", admin_description_path("en", 1) + assert_equal "/admin/descriptions/1", admin_description_path(nil, 1) - get '/admin/en/descriptions' - assert_equal 'admin/descriptions#index', @response.body + get "/admin/en/descriptions" + assert_equal "admin/descriptions#index", @response.body - get '/admin/descriptions' - assert_equal 'admin/descriptions#index', @response.body + get "/admin/descriptions" + assert_equal "admin/descriptions#index", @response.body - get '/admin/en/descriptions/1' - assert_equal 'admin/descriptions#show', @response.body + get "/admin/en/descriptions/1" + assert_equal "admin/descriptions#show", @response.body - get '/admin/descriptions/1' - assert_equal 'admin/descriptions#show', @response.body + get "/admin/descriptions/1" + assert_equal "admin/descriptions#show", @response.body end def test_nested_optional_path_shorthand draw do - scope '(:locale)', :locale => /en|pl/ do + scope "(:locale)", locale: /en|pl/ do get "registrations/new" end end - get '/registrations/new' + get "/registrations/new" assert_nil @request.params[:locale] - get '/en/registrations/new' - assert_equal 'en', @request.params[:locale] + get "/en/registrations/new" + assert_equal "en", @request.params[:locale] end def test_default_string_params draw do - get 'inline_pages/(:id)', :to => 'pages#show', :id => 'home' - get 'default_pages/(:id)', :to => 'pages#show', :defaults => { :id => 'home' } + get "inline_pages/(:id)", to: "pages#show", id: "home" + get "default_pages/(:id)", to: "pages#show", defaults: { id: "home" } - defaults :id => 'home' do - get 'scoped_pages/(:id)', :to => 'pages#show' + defaults id: "home" do + get "scoped_pages/(:id)", to: "pages#show" end end - get '/inline_pages' - assert_equal 'home', @request.params[:id] + get "/inline_pages" + assert_equal "home", @request.params[:id] - get '/default_pages' - assert_equal 'home', @request.params[:id] + get "/default_pages" + assert_equal "home", @request.params[:id] - get '/scoped_pages' - assert_equal 'home', @request.params[:id] + get "/scoped_pages" + assert_equal "home", @request.params[:id] end def test_default_integer_params draw do - get 'inline_pages/(:page)', to: 'pages#show', page: 1 - get 'default_pages/(:page)', to: 'pages#show', defaults: { page: 1 } + get "inline_pages/(:page)", to: "pages#show", page: 1 + get "default_pages/(:page)", to: "pages#show", defaults: { page: 1 } defaults page: 1 do - get 'scoped_pages/(:page)', to: 'pages#show' + get "scoped_pages/(:page)", to: "pages#show" end end - get '/inline_pages' + get "/inline_pages" assert_equal 1, @request.params[:page] - get '/default_pages' + get "/default_pages" assert_equal 1, @request.params[:page] - get '/scoped_pages' + get "/scoped_pages" assert_equal 1, @request.params[:page] end + def test_keyed_default_string_params_with_match + draw do + match "/", to: "pages#show", via: :get, defaults: { id: "home" } + end + + get "/" + assert_equal "home", @request.params[:id] + end + + def test_default_string_params_with_match + draw do + match "/", to: "pages#show", via: :get, id: "home" + end + + get "/" + assert_equal "home", @request.params[:id] + end + + def test_keyed_default_string_params_with_root + draw do + root to: "pages#show", defaults: { id: "home" } + end + + get "/" + assert_equal "home", @request.params[:id] + end + + def test_default_string_params_with_root + draw do + root to: "pages#show", id: "home" + end + + get "/" + assert_equal "home", @request.params[:id] + end + def test_resource_constraints draw do - resources :products, :constraints => { :id => /\d{4}/ } do - root :to => "products#root" - get :favorite, :on => :collection + resources :products, constraints: { id: /\d{4}/ } do + root to: "products#root" + get :favorite, on: :collection resources :images end - resource :dashboard, :constraints => { :ip => /192\.168\.1\.\d{1,3}/ } + resource :dashboard, constraints: { ip: /192\.168\.1\.\d{1,3}/ } end - get '/products/1' - assert_equal 'pass', @response.headers['X-Cascade'] - get '/products' - assert_equal 'products#root', @response.body - get '/products/favorite' - assert_equal 'products#favorite', @response.body - get '/products/0001' - assert_equal 'products#show', @response.body + get "/products/1" + assert_equal "pass", @response.headers["X-Cascade"] + get "/products" + assert_equal "products#root", @response.body + get "/products/favorite" + assert_equal "products#favorite", @response.body + get "/products/0001" + assert_equal "products#show", @response.body - get '/products/1/images' - assert_equal 'pass', @response.headers['X-Cascade'] - get '/products/0001/images' - assert_equal 'images#index', @response.body - get '/products/0001/images/0001' - assert_equal 'images#show', @response.body + get "/products/1/images" + assert_equal "pass", @response.headers["X-Cascade"] + get "/products/0001/images" + assert_equal "images#index", @response.body + get "/products/0001/images/0001" + assert_equal "images#show", @response.body - get '/dashboard', headers: { 'REMOTE_ADDR' => '10.0.0.100' } - assert_equal 'pass', @response.headers['X-Cascade'] - get '/dashboard', headers: { 'REMOTE_ADDR' => '192.168.1.100' } - assert_equal 'dashboards#show', @response.body + get "/dashboard", headers: { "REMOTE_ADDR" => "10.0.0.100" } + assert_equal "pass", @response.headers["X-Cascade"] + get "/dashboard", headers: { "REMOTE_ADDR" => "192.168.1.100" } + assert_equal "dashboards#show", @response.body end def test_root_works_in_the_resources_scope draw do resources :products do - root :to => "products#root" + root to: "products#root" end end - get '/products' - assert_equal 'products#root', @response.body - assert_equal '/products', products_root_path + get "/products" + assert_equal "products#root", @response.body + assert_equal "/products", products_root_path end def test_module_scope draw do - resource :token, :module => :api + resource :token, module: :api end - get '/token' - assert_equal 'api/tokens#show', @response.body - assert_equal '/token', token_path + get "/token" + assert_equal "api/tokens#show", @response.body + assert_equal "/token", token_path end def test_path_scope draw do - scope :path => 'api' do + scope path: "api" do resource :me - get '/' => 'mes#index' + get "/" => "mes#index" end end - get '/api/me' - assert_equal 'mes#show', @response.body - assert_equal '/api/me', me_path + get "/api/me" + assert_equal "mes#show", @response.body + assert_equal "/api/me", me_path - get '/api' - assert_equal 'mes#index', @response.body + get "/api" + assert_equal "mes#index", @response.body end def test_symbol_scope draw do - scope :path => 'api' do + scope path: "api" do scope :v2 do - resource :me, as: 'v2_me' - get '/' => 'mes#index' + resource :me, as: "v2_me" + get "/" => "mes#index" end scope :v3, :admin do - resource :me, as: 'v3_me' + resource :me, as: "v3_me" end end end - get '/api/v2/me' - assert_equal 'mes#show', @response.body - assert_equal '/api/v2/me', v2_me_path + get "/api/v2/me" + assert_equal "mes#show", @response.body + assert_equal "/api/v2/me", v2_me_path - get '/api/v2' - assert_equal 'mes#index', @response.body + get "/api/v2" + assert_equal "mes#index", @response.body - get '/api/v3/admin/me' - assert_equal 'mes#show', @response.body + get "/api/v3/admin/me" + assert_equal "mes#show", @response.body end def test_url_generator_for_generic_route draw do - get "whatever/:controller(/:action(/:id))" + ActiveSupport::Deprecation.silence do + get "whatever/:controller(/:action(/:id))" + end end - get '/whatever/foo/bar' - assert_equal 'foo#bar', @response.body + get "/whatever/foo/bar" + assert_equal "foo#bar", @response.body - assert_equal 'http://www.example.com/whatever/foo/bar/1', - url_for(:controller => "foo", :action => "bar", :id => 1) + assert_equal "http://www.example.com/whatever/foo/bar/1", + url_for(controller: "foo", action: "bar", id: 1) end def test_url_generator_for_namespaced_generic_route draw do - get "whatever/:controller(/:action(/:id))", :id => /\d+/ + ActiveSupport::Deprecation.silence do + get "whatever/:controller(/:action(/:id))", id: /\d+/ + end end - get '/whatever/foo/bar/show' - assert_equal 'foo/bar#show', @response.body + get "/whatever/foo/bar/show" + assert_equal "foo/bar#show", @response.body - get '/whatever/foo/bar/show/1' - assert_equal 'foo/bar#show', @response.body + get "/whatever/foo/bar/show/1" + assert_equal "foo/bar#show", @response.body - assert_equal 'http://www.example.com/whatever/foo/bar/show', - url_for(:controller => "foo/bar", :action => "show") + assert_equal "http://www.example.com/whatever/foo/bar/show", + url_for(controller: "foo/bar", action: "show") - assert_equal 'http://www.example.com/whatever/foo/bar/show/1', - url_for(:controller => "foo/bar", :action => "show", :id => '1') + assert_equal "http://www.example.com/whatever/foo/bar/show/1", + url_for(controller: "foo/bar", action: "show", id: "1") end def test_resource_new_actions @@ -1873,16 +1928,16 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - scope 'pt', :as => 'pt' do - resources :projects, :path_names => { :new => 'novo' }, :path => 'projetos' do - post :preview, :on => :new + scope "pt", as: "pt" do + resources :projects, path_names: { new: "novo" }, path: "projetos" do + post :preview, on: :new end - resource :admin, :path_names => { :new => 'novo' }, :path => 'administrador' do - post :preview, :on => :new + resource :admin, path_names: { new: "novo" }, path: "administrador" do + post :preview, on: :new end - resources :products, :path_names => { :new => 'novo' } do + resources :products, path_names: { new: "novo" } do new do post :preview end @@ -1896,58 +1951,58 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - assert_equal '/replies/new/preview', preview_new_reply_path - assert_equal '/pt/projetos/novo/preview', preview_new_pt_project_path - assert_equal '/pt/administrador/novo/preview', preview_new_pt_admin_path - assert_equal '/pt/products/novo/preview', preview_new_pt_product_path - assert_equal '/profile/new/preview', preview_new_profile_path + assert_equal "/replies/new/preview", preview_new_reply_path + assert_equal "/pt/projetos/novo/preview", preview_new_pt_project_path + assert_equal "/pt/administrador/novo/preview", preview_new_pt_admin_path + assert_equal "/pt/products/novo/preview", preview_new_pt_product_path + assert_equal "/profile/new/preview", preview_new_profile_path - post '/replies/new/preview' - assert_equal 'replies#preview', @response.body + post "/replies/new/preview" + assert_equal "replies#preview", @response.body - post '/pt/projetos/novo/preview' - assert_equal 'projects#preview', @response.body + post "/pt/projetos/novo/preview" + assert_equal "projects#preview", @response.body - post '/pt/administrador/novo/preview' - assert_equal 'admins#preview', @response.body + post "/pt/administrador/novo/preview" + assert_equal "admins#preview", @response.body - post '/pt/products/novo/preview' - assert_equal 'products#preview', @response.body + post "/pt/products/novo/preview" + assert_equal "products#preview", @response.body - post '/profile/new/preview' - assert_equal 'profiles#preview', @response.body + post "/profile/new/preview" + assert_equal "profiles#preview", @response.body end def test_resource_merges_options_from_scope draw do - scope :only => :show do + scope only: :show do resource :account end end assert_raise(NoMethodError) { new_account_path } - get '/account/new' + get "/account/new" assert_equal 404, status end def test_resources_merges_options_from_scope draw do - scope :only => [:index, :show] do + scope only: [:index, :show] do resources :products do resources :images end end end - assert_raise(NoMethodError) { edit_product_path('1') } + assert_raise(NoMethodError) { edit_product_path("1") } - get '/products/1/edit' + get "/products/1/edit" assert_equal 404, status - assert_raise(NoMethodError) { edit_product_image_path('1', '2') } + assert_raise(NoMethodError) { edit_product_image_path("1", "2") } - post '/products/1/images/2/edit' + post "/products/1/images/2/edit" assert_equal 404, status end @@ -1962,7 +2017,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - resources :threads, :shallow => true do + resources :threads, shallow: true do resource :owner resources :messages do resources :comments do @@ -1974,105 +2029,105 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/api/teams' - assert_equal 'api/teams#index', @response.body - assert_equal '/api/teams', api_teams_path + get "/api/teams" + assert_equal "api/teams#index", @response.body + assert_equal "/api/teams", api_teams_path - get '/api/teams/new' - assert_equal 'api/teams#new', @response.body - assert_equal '/api/teams/new', new_api_team_path + get "/api/teams/new" + assert_equal "api/teams#new", @response.body + assert_equal "/api/teams/new", new_api_team_path - get '/api/teams/1' - assert_equal 'api/teams#show', @response.body - assert_equal '/api/teams/1', api_team_path(:id => '1') + get "/api/teams/1" + assert_equal "api/teams#show", @response.body + assert_equal "/api/teams/1", api_team_path(id: "1") - get '/api/teams/1/edit' - assert_equal 'api/teams#edit', @response.body - assert_equal '/api/teams/1/edit', edit_api_team_path(:id => '1') + get "/api/teams/1/edit" + assert_equal "api/teams#edit", @response.body + assert_equal "/api/teams/1/edit", edit_api_team_path(id: "1") - get '/api/teams/1/players' - assert_equal 'api/players#index', @response.body - assert_equal '/api/teams/1/players', api_team_players_path(:team_id => '1') + get "/api/teams/1/players" + assert_equal "api/players#index", @response.body + assert_equal "/api/teams/1/players", api_team_players_path(team_id: "1") - get '/api/teams/1/players/new' - assert_equal 'api/players#new', @response.body - assert_equal '/api/teams/1/players/new', new_api_team_player_path(:team_id => '1') + get "/api/teams/1/players/new" + assert_equal "api/players#new", @response.body + assert_equal "/api/teams/1/players/new", new_api_team_player_path(team_id: "1") - get '/api/players/2' - assert_equal 'api/players#show', @response.body - assert_equal '/api/players/2', api_player_path(:id => '2') + get "/api/players/2" + assert_equal "api/players#show", @response.body + assert_equal "/api/players/2", api_player_path(id: "2") - get '/api/players/2/edit' - assert_equal 'api/players#edit', @response.body - assert_equal '/api/players/2/edit', edit_api_player_path(:id => '2') + get "/api/players/2/edit" + assert_equal "api/players#edit", @response.body + assert_equal "/api/players/2/edit", edit_api_player_path(id: "2") - get '/api/teams/1/captain' - assert_equal 'api/captains#show', @response.body - assert_equal '/api/teams/1/captain', api_team_captain_path(:team_id => '1') + get "/api/teams/1/captain" + assert_equal "api/captains#show", @response.body + assert_equal "/api/teams/1/captain", api_team_captain_path(team_id: "1") - get '/api/teams/1/captain/new' - assert_equal 'api/captains#new', @response.body - assert_equal '/api/teams/1/captain/new', new_api_team_captain_path(:team_id => '1') + get "/api/teams/1/captain/new" + assert_equal "api/captains#new", @response.body + assert_equal "/api/teams/1/captain/new", new_api_team_captain_path(team_id: "1") - get '/api/teams/1/captain/edit' - assert_equal 'api/captains#edit', @response.body - assert_equal '/api/teams/1/captain/edit', edit_api_team_captain_path(:team_id => '1') + get "/api/teams/1/captain/edit" + assert_equal "api/captains#edit", @response.body + assert_equal "/api/teams/1/captain/edit", edit_api_team_captain_path(team_id: "1") - get '/threads' - assert_equal 'threads#index', @response.body - assert_equal '/threads', threads_path + get "/threads" + assert_equal "threads#index", @response.body + assert_equal "/threads", threads_path - get '/threads/new' - assert_equal 'threads#new', @response.body - assert_equal '/threads/new', new_thread_path + get "/threads/new" + assert_equal "threads#new", @response.body + assert_equal "/threads/new", new_thread_path - get '/threads/1' - assert_equal 'threads#show', @response.body - assert_equal '/threads/1', thread_path(:id => '1') + get "/threads/1" + assert_equal "threads#show", @response.body + assert_equal "/threads/1", thread_path(id: "1") - get '/threads/1/edit' - assert_equal 'threads#edit', @response.body - assert_equal '/threads/1/edit', edit_thread_path(:id => '1') + get "/threads/1/edit" + assert_equal "threads#edit", @response.body + assert_equal "/threads/1/edit", edit_thread_path(id: "1") - get '/threads/1/owner' - assert_equal 'owners#show', @response.body - assert_equal '/threads/1/owner', thread_owner_path(:thread_id => '1') + get "/threads/1/owner" + assert_equal "owners#show", @response.body + assert_equal "/threads/1/owner", thread_owner_path(thread_id: "1") - get '/threads/1/messages' - assert_equal 'messages#index', @response.body - assert_equal '/threads/1/messages', thread_messages_path(:thread_id => '1') + get "/threads/1/messages" + assert_equal "messages#index", @response.body + assert_equal "/threads/1/messages", thread_messages_path(thread_id: "1") - get '/threads/1/messages/new' - assert_equal 'messages#new', @response.body - assert_equal '/threads/1/messages/new', new_thread_message_path(:thread_id => '1') + get "/threads/1/messages/new" + assert_equal "messages#new", @response.body + assert_equal "/threads/1/messages/new", new_thread_message_path(thread_id: "1") - get '/messages/2' - assert_equal 'messages#show', @response.body - assert_equal '/messages/2', message_path(:id => '2') + get "/messages/2" + assert_equal "messages#show", @response.body + assert_equal "/messages/2", message_path(id: "2") - get '/messages/2/edit' - assert_equal 'messages#edit', @response.body - assert_equal '/messages/2/edit', edit_message_path(:id => '2') + get "/messages/2/edit" + assert_equal "messages#edit", @response.body + assert_equal "/messages/2/edit", edit_message_path(id: "2") - get '/messages/2/comments' - assert_equal 'comments#index', @response.body - assert_equal '/messages/2/comments', message_comments_path(:message_id => '2') + get "/messages/2/comments" + assert_equal "comments#index", @response.body + assert_equal "/messages/2/comments", message_comments_path(message_id: "2") - get '/messages/2/comments/new' - assert_equal 'comments#new', @response.body - assert_equal '/messages/2/comments/new', new_message_comment_path(:message_id => '2') + get "/messages/2/comments/new" + assert_equal "comments#new", @response.body + assert_equal "/messages/2/comments/new", new_message_comment_path(message_id: "2") - get '/comments/3' - assert_equal 'comments#show', @response.body - assert_equal '/comments/3', comment_path(:id => '3') + get "/comments/3" + assert_equal "comments#show", @response.body + assert_equal "/comments/3", comment_path(id: "3") - get '/comments/3/edit' - assert_equal 'comments#edit', @response.body - assert_equal '/comments/3/edit', edit_comment_path(:id => '3') + get "/comments/3/edit" + assert_equal "comments#edit", @response.body + assert_equal "/comments/3/edit", edit_comment_path(id: "3") - post '/comments/3/preview' - assert_equal 'comments#preview', @response.body - assert_equal '/comments/3/preview', preview_comment_path(:id => '3') + post "/comments/3/preview" + assert_equal "comments#preview", @response.body + assert_equal "/comments/3/preview", preview_comment_path(id: "3") end def test_shallow_nested_resources_inside_resource @@ -2082,33 +2137,33 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/membership/cards' - assert_equal 'cards#index', @response.body - assert_equal '/membership/cards', membership_cards_path + get "/membership/cards" + assert_equal "cards#index", @response.body + assert_equal "/membership/cards", membership_cards_path - get '/membership/cards/new' - assert_equal 'cards#new', @response.body - assert_equal '/membership/cards/new', new_membership_card_path + get "/membership/cards/new" + assert_equal "cards#new", @response.body + assert_equal "/membership/cards/new", new_membership_card_path - post '/membership/cards' - assert_equal 'cards#create', @response.body + post "/membership/cards" + assert_equal "cards#create", @response.body - get '/cards/1' - assert_equal 'cards#show', @response.body - assert_equal '/cards/1', card_path('1') + get "/cards/1" + assert_equal "cards#show", @response.body + assert_equal "/cards/1", card_path("1") - get '/cards/1/edit' - assert_equal 'cards#edit', @response.body - assert_equal '/cards/1/edit', edit_card_path('1') + get "/cards/1/edit" + assert_equal "cards#edit", @response.body + assert_equal "/cards/1/edit", edit_card_path("1") - put '/cards/1' - assert_equal 'cards#update', @response.body + put "/cards/1" + assert_equal "cards#update", @response.body - patch '/cards/1' - assert_equal 'cards#update', @response.body + patch "/cards/1" + assert_equal "cards#update", @response.body - delete '/cards/1' - assert_equal 'cards#destroy', @response.body + delete "/cards/1" + assert_equal "cards#destroy", @response.body end def test_shallow_deeply_nested_resources @@ -2120,13 +2175,13 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/comments/1' - assert_equal 'comments#show', @response.body + get "/comments/1" + assert_equal "comments#show", @response.body - assert_equal '/comments/1', comment_path('1') - assert_equal '/blogs/new', new_blog_path - assert_equal '/blogs/1/posts/new', new_blog_post_path(:blog_id => 1) - assert_equal '/blogs/1/posts/2/comments/new', new_blog_post_comment_path(:blog_id => 1, :post_id => 2) + assert_equal "/comments/1", comment_path("1") + assert_equal "/blogs/new", new_blog_path + assert_equal "/blogs/1/posts/new", new_blog_post_path(blog_id: 1) + assert_equal "/blogs/1/posts/2/comments/new", new_blog_post_comment_path(blog_id: 1, post_id: 2) end def test_direct_children_of_shallow_resources @@ -2138,22 +2193,22 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - post '/posts/1/comments' - assert_equal 'comments#create', @response.body - assert_equal '/posts/1/comments', post_comments_path('1') + post "/posts/1/comments" + assert_equal "comments#create", @response.body + assert_equal "/posts/1/comments", post_comments_path("1") - get '/posts/2/comments/new' - assert_equal 'comments#new', @response.body - assert_equal '/posts/2/comments/new', new_post_comment_path('2') + get "/posts/2/comments/new" + assert_equal "comments#new", @response.body + assert_equal "/posts/2/comments/new", new_post_comment_path("2") - get '/posts/1/comments' - assert_equal 'comments#index', @response.body - assert_equal '/posts/1/comments', post_comments_path('1') + get "/posts/1/comments" + assert_equal "comments#index", @response.body + assert_equal "/posts/1/comments", post_comments_path("1") end def test_shallow_nested_resources_within_scope draw do - scope '/hello' do + scope "/hello" do shallow do resources :notes do resources :trackbacks @@ -2162,120 +2217,120 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/hello/notes/1/trackbacks' - assert_equal 'trackbacks#index', @response.body - assert_equal '/hello/notes/1/trackbacks', note_trackbacks_path(:note_id => 1) + get "/hello/notes/1/trackbacks" + assert_equal "trackbacks#index", @response.body + assert_equal "/hello/notes/1/trackbacks", note_trackbacks_path(note_id: 1) - get '/hello/notes/1/edit' - assert_equal 'notes#edit', @response.body - assert_equal '/hello/notes/1/edit', edit_note_path(:id => '1') + get "/hello/notes/1/edit" + assert_equal "notes#edit", @response.body + assert_equal "/hello/notes/1/edit", edit_note_path(id: "1") - get '/hello/notes/1/trackbacks/new' - assert_equal 'trackbacks#new', @response.body - assert_equal '/hello/notes/1/trackbacks/new', new_note_trackback_path(:note_id => 1) + get "/hello/notes/1/trackbacks/new" + assert_equal "trackbacks#new", @response.body + assert_equal "/hello/notes/1/trackbacks/new", new_note_trackback_path(note_id: 1) - get '/hello/trackbacks/1' - assert_equal 'trackbacks#show', @response.body - assert_equal '/hello/trackbacks/1', trackback_path(:id => '1') + get "/hello/trackbacks/1" + assert_equal "trackbacks#show", @response.body + assert_equal "/hello/trackbacks/1", trackback_path(id: "1") - get '/hello/trackbacks/1/edit' - assert_equal 'trackbacks#edit', @response.body - assert_equal '/hello/trackbacks/1/edit', edit_trackback_path(:id => '1') + get "/hello/trackbacks/1/edit" + assert_equal "trackbacks#edit", @response.body + assert_equal "/hello/trackbacks/1/edit", edit_trackback_path(id: "1") - put '/hello/trackbacks/1' - assert_equal 'trackbacks#update', @response.body + put "/hello/trackbacks/1" + assert_equal "trackbacks#update", @response.body - post '/hello/notes/1/trackbacks' - assert_equal 'trackbacks#create', @response.body + post "/hello/notes/1/trackbacks" + assert_equal "trackbacks#create", @response.body - delete '/hello/trackbacks/1' - assert_equal 'trackbacks#destroy', @response.body + delete "/hello/trackbacks/1" + assert_equal "trackbacks#destroy", @response.body - get '/hello/notes' - assert_equal 'notes#index', @response.body + get "/hello/notes" + assert_equal "notes#index", @response.body - post '/hello/notes' - assert_equal 'notes#create', @response.body + post "/hello/notes" + assert_equal "notes#create", @response.body - get '/hello/notes/new' - assert_equal 'notes#new', @response.body - assert_equal '/hello/notes/new', new_note_path + get "/hello/notes/new" + assert_equal "notes#new", @response.body + assert_equal "/hello/notes/new", new_note_path - get '/hello/notes/1' - assert_equal 'notes#show', @response.body - assert_equal '/hello/notes/1', note_path(:id => 1) + get "/hello/notes/1" + assert_equal "notes#show", @response.body + assert_equal "/hello/notes/1", note_path(id: 1) - put '/hello/notes/1' - assert_equal 'notes#update', @response.body + put "/hello/notes/1" + assert_equal "notes#update", @response.body - delete '/hello/notes/1' - assert_equal 'notes#destroy', @response.body + delete "/hello/notes/1" + assert_equal "notes#destroy", @response.body end def test_shallow_option_nested_resources_within_scope draw do - scope '/hello' do - resources :notes, :shallow => true do + scope "/hello" do + resources :notes, shallow: true do resources :trackbacks end end end - get '/hello/notes/1/trackbacks' - assert_equal 'trackbacks#index', @response.body - assert_equal '/hello/notes/1/trackbacks', note_trackbacks_path(:note_id => 1) + get "/hello/notes/1/trackbacks" + assert_equal "trackbacks#index", @response.body + assert_equal "/hello/notes/1/trackbacks", note_trackbacks_path(note_id: 1) - get '/hello/notes/1/edit' - assert_equal 'notes#edit', @response.body - assert_equal '/hello/notes/1/edit', edit_note_path(:id => '1') + get "/hello/notes/1/edit" + assert_equal "notes#edit", @response.body + assert_equal "/hello/notes/1/edit", edit_note_path(id: "1") - get '/hello/notes/1/trackbacks/new' - assert_equal 'trackbacks#new', @response.body - assert_equal '/hello/notes/1/trackbacks/new', new_note_trackback_path(:note_id => 1) + get "/hello/notes/1/trackbacks/new" + assert_equal "trackbacks#new", @response.body + assert_equal "/hello/notes/1/trackbacks/new", new_note_trackback_path(note_id: 1) - get '/hello/trackbacks/1' - assert_equal 'trackbacks#show', @response.body - assert_equal '/hello/trackbacks/1', trackback_path(:id => '1') + get "/hello/trackbacks/1" + assert_equal "trackbacks#show", @response.body + assert_equal "/hello/trackbacks/1", trackback_path(id: "1") - get '/hello/trackbacks/1/edit' - assert_equal 'trackbacks#edit', @response.body - assert_equal '/hello/trackbacks/1/edit', edit_trackback_path(:id => '1') + get "/hello/trackbacks/1/edit" + assert_equal "trackbacks#edit", @response.body + assert_equal "/hello/trackbacks/1/edit", edit_trackback_path(id: "1") - put '/hello/trackbacks/1' - assert_equal 'trackbacks#update', @response.body + put "/hello/trackbacks/1" + assert_equal "trackbacks#update", @response.body - post '/hello/notes/1/trackbacks' - assert_equal 'trackbacks#create', @response.body + post "/hello/notes/1/trackbacks" + assert_equal "trackbacks#create", @response.body - delete '/hello/trackbacks/1' - assert_equal 'trackbacks#destroy', @response.body + delete "/hello/trackbacks/1" + assert_equal "trackbacks#destroy", @response.body - get '/hello/notes' - assert_equal 'notes#index', @response.body + get "/hello/notes" + assert_equal "notes#index", @response.body - post '/hello/notes' - assert_equal 'notes#create', @response.body + post "/hello/notes" + assert_equal "notes#create", @response.body - get '/hello/notes/new' - assert_equal 'notes#new', @response.body - assert_equal '/hello/notes/new', new_note_path + get "/hello/notes/new" + assert_equal "notes#new", @response.body + assert_equal "/hello/notes/new", new_note_path - get '/hello/notes/1' - assert_equal 'notes#show', @response.body - assert_equal '/hello/notes/1', note_path(:id => 1) + get "/hello/notes/1" + assert_equal "notes#show", @response.body + assert_equal "/hello/notes/1", note_path(id: 1) - put '/hello/notes/1' - assert_equal 'notes#update', @response.body + put "/hello/notes/1" + assert_equal "notes#update", @response.body - delete '/hello/notes/1' - assert_equal 'notes#destroy', @response.body + delete "/hello/notes/1" + assert_equal "notes#destroy", @response.body end def test_custom_resource_routes_are_scoped draw do resources :customers do - get :recent, :on => :collection - get "profile", :on => :member + get :recent, on: :collection + get "profile", on: :member get "secret/profile" => "customers#secret", :on => :member post "preview" => "customers#preview", :as => :another_preview, :on => :new resource :avatar do @@ -2283,11 +2338,11 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end resources :invoices do get "outstanding" => "invoices#outstanding", :on => :collection - get "overdue", :action => :overdue, :on => :collection + get "overdue", action: :overdue, on: :collection get "print" => "invoices#print", :as => :print, :on => :member post "preview" => "invoices#preview", :as => :preview, :on => :new end - resources :notes, :shallow => true do + resources :notes, shallow: true do get "preview" => "notes#preview", :as => :preview, :on => :new get "print" => "notes#print", :as => :print, :on => :member end @@ -2302,79 +2357,79 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - assert_equal '/customers/recent', recent_customers_path - assert_equal '/customers/1/profile', profile_customer_path(:id => '1') - assert_equal '/customers/1/secret/profile', secret_profile_customer_path(:id => '1') - assert_equal '/customers/new/preview', another_preview_new_customer_path - assert_equal '/customers/1/avatar/thumbnail.jpg', thumbnail_customer_avatar_path(:customer_id => '1', :format => :jpg) - assert_equal '/customers/1/invoices/outstanding', outstanding_customer_invoices_path(:customer_id => '1') - assert_equal '/customers/1/invoices/2/print', print_customer_invoice_path(:customer_id => '1', :id => '2') - assert_equal '/customers/1/invoices/new/preview', preview_new_customer_invoice_path(:customer_id => '1') - assert_equal '/customers/1/notes/new/preview', preview_new_customer_note_path(:customer_id => '1') - assert_equal '/notes/1/print', print_note_path(:id => '1') - assert_equal '/api/customers/recent', recent_api_customers_path - assert_equal '/api/customers/1/profile', profile_api_customer_path(:id => '1') - assert_equal '/api/customers/new/preview', preview_new_api_customer_path + assert_equal "/customers/recent", recent_customers_path + assert_equal "/customers/1/profile", profile_customer_path(id: "1") + assert_equal "/customers/1/secret/profile", secret_profile_customer_path(id: "1") + assert_equal "/customers/new/preview", another_preview_new_customer_path + assert_equal "/customers/1/avatar/thumbnail.jpg", thumbnail_customer_avatar_path(customer_id: "1", format: :jpg) + assert_equal "/customers/1/invoices/outstanding", outstanding_customer_invoices_path(customer_id: "1") + assert_equal "/customers/1/invoices/2/print", print_customer_invoice_path(customer_id: "1", id: "2") + assert_equal "/customers/1/invoices/new/preview", preview_new_customer_invoice_path(customer_id: "1") + assert_equal "/customers/1/notes/new/preview", preview_new_customer_note_path(customer_id: "1") + assert_equal "/notes/1/print", print_note_path(id: "1") + assert_equal "/api/customers/recent", recent_api_customers_path + assert_equal "/api/customers/1/profile", profile_api_customer_path(id: "1") + assert_equal "/api/customers/new/preview", preview_new_api_customer_path - get '/customers/1/invoices/overdue' - assert_equal 'invoices#overdue', @response.body + get "/customers/1/invoices/overdue" + assert_equal "invoices#overdue", @response.body - get '/customers/1/secret/profile' - assert_equal 'customers#secret', @response.body + get "/customers/1/secret/profile" + assert_equal "customers#secret", @response.body end def test_shallow_nested_routes_ignore_module draw do - scope :module => :api do - resources :errors, :shallow => true do + scope module: :api do + resources :errors, shallow: true do resources :notices end end end - get '/errors/1/notices' - assert_equal 'api/notices#index', @response.body - assert_equal '/errors/1/notices', error_notices_path(:error_id => '1') + get "/errors/1/notices" + assert_equal "api/notices#index", @response.body + assert_equal "/errors/1/notices", error_notices_path(error_id: "1") - get '/notices/1' - assert_equal 'api/notices#show', @response.body - assert_equal '/notices/1', notice_path(:id => '1') + get "/notices/1" + assert_equal "api/notices#show", @response.body + assert_equal "/notices/1", notice_path(id: "1") end def test_non_greedy_regexp draw do namespace :api do - scope(':version', :version => /.+/) do - resources :users, :id => /.+?/, :format => /json|xml/ + scope(":version", version: /.+/) do + resources :users, id: /.+?/, format: /json|xml/ end end end - get '/api/1.0/users' - assert_equal 'api/users#index', @response.body - assert_equal '/api/1.0/users', api_users_path(:version => '1.0') + get "/api/1.0/users" + assert_equal "api/users#index", @response.body + assert_equal "/api/1.0/users", api_users_path(version: "1.0") - get '/api/1.0/users.json' - assert_equal 'api/users#index', @response.body + get "/api/1.0/users.json" + assert_equal "api/users#index", @response.body assert_equal true, @request.format.json? - assert_equal '/api/1.0/users.json', api_users_path(:version => '1.0', :format => :json) + assert_equal "/api/1.0/users.json", api_users_path(version: "1.0", format: :json) - get '/api/1.0/users/first.last' - assert_equal 'api/users#show', @response.body - assert_equal 'first.last', @request.params[:id] - assert_equal '/api/1.0/users/first.last', api_user_path(:version => '1.0', :id => 'first.last') + get "/api/1.0/users/first.last" + assert_equal "api/users#show", @response.body + assert_equal "first.last", @request.params[:id] + assert_equal "/api/1.0/users/first.last", api_user_path(version: "1.0", id: "first.last") - get '/api/1.0/users/first.last.xml' - assert_equal 'api/users#show', @response.body - assert_equal 'first.last', @request.params[:id] + get "/api/1.0/users/first.last.xml" + assert_equal "api/users#show", @response.body + assert_equal "first.last", @request.params[:id] assert_equal true, @request.format.xml? - assert_equal '/api/1.0/users/first.last.xml', api_user_path(:version => '1.0', :id => 'first.last', :format => :xml) + assert_equal "/api/1.0/users/first.last.xml", api_user_path(version: "1.0", id: "first.last", format: :xml) end def test_match_without_via assert_raises(ArgumentError) do draw do - match '/foo/bar', :to => 'files#show' + match "/foo/bar", to: "files#show" end end end @@ -2382,17 +2437,17 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest def test_match_with_empty_via assert_raises(ArgumentError) do draw do - match '/foo/bar', :to => 'files#show', :via => [] + match "/foo/bar", to: "files#show", via: [] end end end def test_glob_parameter_accepts_regexp draw do - get '/:locale/*file.:format', :to => 'files#show', :file => /path\/to\/existing\/file/ + get "/:locale/*file.:format", to: "files#show", file: /path\/to\/existing\/file/ end - get '/en/path/to/existing/file.html' + get "/en/path/to/existing/file.html" assert_equal 200, @response.status end @@ -2401,8 +2456,8 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest resources :content end - get '/content' - assert_equal 'content#index', @response.body + get "/content" + assert_equal "content#index", @response.body end def test_url_generator_for_optional_prefix_dynamic_segment @@ -2410,15 +2465,15 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest get "(/:username)/followers" => "followers#index" end - get '/bob/followers' - assert_equal 'followers#index', @response.body - assert_equal 'http://www.example.com/bob/followers', - url_for(:controller => "followers", :action => "index", :username => "bob") + get "/bob/followers" + assert_equal "followers#index", @response.body + assert_equal "http://www.example.com/bob/followers", + url_for(controller: "followers", action: "index", username: "bob") - get '/followers' - assert_equal 'followers#index', @response.body - assert_equal 'http://www.example.com/followers', - url_for(:controller => "followers", :action => "index", :username => nil) + get "/followers" + assert_equal "followers#index", @response.body + assert_equal "http://www.example.com/followers", + url_for(controller: "followers", action: "index", username: nil) end def test_url_generator_for_optional_suffix_static_and_dynamic_segment @@ -2426,15 +2481,15 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest get "/groups(/user/:username)" => "groups#index" end - get '/groups/user/bob' - assert_equal 'groups#index', @response.body - assert_equal 'http://www.example.com/groups/user/bob', - url_for(:controller => "groups", :action => "index", :username => "bob") + get "/groups/user/bob" + assert_equal "groups#index", @response.body + assert_equal "http://www.example.com/groups/user/bob", + url_for(controller: "groups", action: "index", username: "bob") - get '/groups' - assert_equal 'groups#index', @response.body - assert_equal 'http://www.example.com/groups', - url_for(:controller => "groups", :action => "index", :username => nil) + get "/groups" + assert_equal "groups#index", @response.body + assert_equal "http://www.example.com/groups", + url_for(controller: "groups", action: "index", username: nil) end def test_url_generator_for_optional_prefix_static_and_dynamic_segment @@ -2442,66 +2497,66 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest get "(/user/:username)/photos" => "photos#index" end - get '/user/bob/photos' - assert_equal 'photos#index', @response.body - assert_equal 'http://www.example.com/user/bob/photos', - url_for(:controller => "photos", :action => "index", :username => "bob") + get "/user/bob/photos" + assert_equal "photos#index", @response.body + assert_equal "http://www.example.com/user/bob/photos", + url_for(controller: "photos", action: "index", username: "bob") - get '/photos' - assert_equal 'photos#index', @response.body - assert_equal 'http://www.example.com/photos', - url_for(:controller => "photos", :action => "index", :username => nil) + get "/photos" + assert_equal "photos#index", @response.body + assert_equal "http://www.example.com/photos", + url_for(controller: "photos", action: "index", username: nil) end def test_url_recognition_for_optional_static_segments draw do - scope '(groups)' do - scope '(discussions)' do + scope "(groups)" do + scope "(discussions)" do resources :messages end end end - get '/groups/discussions/messages' - assert_equal 'messages#index', @response.body + get "/groups/discussions/messages" + assert_equal "messages#index", @response.body - get '/groups/discussions/messages/1' - assert_equal 'messages#show', @response.body + get "/groups/discussions/messages/1" + assert_equal "messages#show", @response.body - get '/groups/messages' - assert_equal 'messages#index', @response.body + get "/groups/messages" + assert_equal "messages#index", @response.body - get '/groups/messages/1' - assert_equal 'messages#show', @response.body + get "/groups/messages/1" + assert_equal "messages#show", @response.body - get '/discussions/messages' - assert_equal 'messages#index', @response.body + get "/discussions/messages" + assert_equal "messages#index", @response.body - get '/discussions/messages/1' - assert_equal 'messages#show', @response.body + get "/discussions/messages/1" + assert_equal "messages#show", @response.body - get '/messages' - assert_equal 'messages#index', @response.body + get "/messages" + assert_equal "messages#index", @response.body - get '/messages/1' - assert_equal 'messages#show', @response.body + get "/messages/1" + assert_equal "messages#show", @response.body end def test_router_removes_invalid_conditions draw do - scope :constraints => { :id => /\d+/ } do - get '/tickets', :to => 'tickets#index', :as => :tickets + scope constraints: { id: /\d+/ } do + get "/tickets", to: "tickets#index", as: :tickets end end - get '/tickets' - assert_equal 'tickets#index', @response.body - assert_equal '/tickets', tickets_path + get "/tickets" + assert_equal "tickets#index", @response.body + assert_equal "/tickets", tickets_path end def test_constraints_are_merged_from_scope draw do - scope :constraints => { :id => /\d{4}/ } do + scope constraints: { id: /\d{4}/ } do resources :movies do resources :reviews resource :trailer @@ -2509,42 +2564,42 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/movies/0001' - assert_equal 'movies#show', @response.body - assert_equal '/movies/0001', movie_path(:id => '0001') + get "/movies/0001" + assert_equal "movies#show", @response.body + assert_equal "/movies/0001", movie_path(id: "0001") - get '/movies/00001' - assert_equal 'Not Found', @response.body - assert_raises(ActionController::UrlGenerationError){ movie_path(:id => '00001') } + get "/movies/00001" + assert_equal "Not Found", @response.body + assert_raises(ActionController::UrlGenerationError) { movie_path(id: "00001") } - get '/movies/0001/reviews' - assert_equal 'reviews#index', @response.body - assert_equal '/movies/0001/reviews', movie_reviews_path(:movie_id => '0001') + get "/movies/0001/reviews" + assert_equal "reviews#index", @response.body + assert_equal "/movies/0001/reviews", movie_reviews_path(movie_id: "0001") - get '/movies/00001/reviews' - assert_equal 'Not Found', @response.body - assert_raises(ActionController::UrlGenerationError){ movie_reviews_path(:movie_id => '00001') } + get "/movies/00001/reviews" + assert_equal "Not Found", @response.body + assert_raises(ActionController::UrlGenerationError) { movie_reviews_path(movie_id: "00001") } - get '/movies/0001/reviews/0001' - assert_equal 'reviews#show', @response.body - assert_equal '/movies/0001/reviews/0001', movie_review_path(:movie_id => '0001', :id => '0001') + get "/movies/0001/reviews/0001" + assert_equal "reviews#show", @response.body + assert_equal "/movies/0001/reviews/0001", movie_review_path(movie_id: "0001", id: "0001") - get '/movies/00001/reviews/0001' - assert_equal 'Not Found', @response.body - assert_raises(ActionController::UrlGenerationError){ movie_path(:movie_id => '00001', :id => '00001') } + get "/movies/00001/reviews/0001" + assert_equal "Not Found", @response.body + assert_raises(ActionController::UrlGenerationError) { movie_path(movie_id: "00001", id: "00001") } - get '/movies/0001/trailer' - assert_equal 'trailers#show', @response.body - assert_equal '/movies/0001/trailer', movie_trailer_path(:movie_id => '0001') + get "/movies/0001/trailer" + assert_equal "trailers#show", @response.body + assert_equal "/movies/0001/trailer", movie_trailer_path(movie_id: "0001") - get '/movies/00001/trailer' - assert_equal 'Not Found', @response.body - assert_raises(ActionController::UrlGenerationError){ movie_trailer_path(:movie_id => '00001') } + get "/movies/00001/trailer" + assert_equal "Not Found", @response.body + assert_raises(ActionController::UrlGenerationError) { movie_trailer_path(movie_id: "00001") } end def test_only_should_be_read_from_scope draw do - scope :only => [:index, :show] do + scope only: [:index, :show] do namespace :only do resources :clubs do resources :players @@ -2554,34 +2609,34 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/only/clubs' - assert_equal 'only/clubs#index', @response.body - assert_equal '/only/clubs', only_clubs_path + get "/only/clubs" + assert_equal "only/clubs#index", @response.body + assert_equal "/only/clubs", only_clubs_path - get '/only/clubs/1/edit' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { edit_only_club_path(:id => '1') } + get "/only/clubs/1/edit" + assert_equal "Not Found", @response.body + assert_raise(NoMethodError) { edit_only_club_path(id: "1") } - get '/only/clubs/1/players' - assert_equal 'only/players#index', @response.body - assert_equal '/only/clubs/1/players', only_club_players_path(:club_id => '1') + get "/only/clubs/1/players" + assert_equal "only/players#index", @response.body + assert_equal "/only/clubs/1/players", only_club_players_path(club_id: "1") - get '/only/clubs/1/players/2/edit' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { edit_only_club_player_path(:club_id => '1', :id => '2') } + get "/only/clubs/1/players/2/edit" + assert_equal "Not Found", @response.body + assert_raise(NoMethodError) { edit_only_club_player_path(club_id: "1", id: "2") } - get '/only/clubs/1/chairman' - assert_equal 'only/chairmen#show', @response.body - assert_equal '/only/clubs/1/chairman', only_club_chairman_path(:club_id => '1') + get "/only/clubs/1/chairman" + assert_equal "only/chairmen#show", @response.body + assert_equal "/only/clubs/1/chairman", only_club_chairman_path(club_id: "1") - get '/only/clubs/1/chairman/edit' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { edit_only_club_chairman_path(:club_id => '1') } + get "/only/clubs/1/chairman/edit" + assert_equal "Not Found", @response.body + assert_raise(NoMethodError) { edit_only_club_chairman_path(club_id: "1") } end def test_except_should_be_read_from_scope draw do - scope :except => [:new, :create, :edit, :update, :destroy] do + scope except: [:new, :create, :edit, :update, :destroy] do namespace :except do resources :clubs do resources :players @@ -2591,54 +2646,54 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/except/clubs' - assert_equal 'except/clubs#index', @response.body - assert_equal '/except/clubs', except_clubs_path + get "/except/clubs" + assert_equal "except/clubs#index", @response.body + assert_equal "/except/clubs", except_clubs_path - get '/except/clubs/1/edit' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { edit_except_club_path(:id => '1') } + get "/except/clubs/1/edit" + assert_equal "Not Found", @response.body + assert_raise(NoMethodError) { edit_except_club_path(id: "1") } - get '/except/clubs/1/players' - assert_equal 'except/players#index', @response.body - assert_equal '/except/clubs/1/players', except_club_players_path(:club_id => '1') + get "/except/clubs/1/players" + assert_equal "except/players#index", @response.body + assert_equal "/except/clubs/1/players", except_club_players_path(club_id: "1") - get '/except/clubs/1/players/2/edit' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { edit_except_club_player_path(:club_id => '1', :id => '2') } + get "/except/clubs/1/players/2/edit" + assert_equal "Not Found", @response.body + assert_raise(NoMethodError) { edit_except_club_player_path(club_id: "1", id: "2") } - get '/except/clubs/1/chairman' - assert_equal 'except/chairmen#show', @response.body - assert_equal '/except/clubs/1/chairman', except_club_chairman_path(:club_id => '1') + get "/except/clubs/1/chairman" + assert_equal "except/chairmen#show", @response.body + assert_equal "/except/clubs/1/chairman", except_club_chairman_path(club_id: "1") - get '/except/clubs/1/chairman/edit' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { edit_except_club_chairman_path(:club_id => '1') } + get "/except/clubs/1/chairman/edit" + assert_equal "Not Found", @response.body + assert_raise(NoMethodError) { edit_except_club_chairman_path(club_id: "1") } end def test_only_option_should_override_scope draw do - scope :only => :show do + scope only: :show do namespace :only do - resources :sectors, :only => :index + resources :sectors, only: :index end end end - get '/only/sectors' - assert_equal 'only/sectors#index', @response.body - assert_equal '/only/sectors', only_sectors_path + get "/only/sectors" + assert_equal "only/sectors#index", @response.body + assert_equal "/only/sectors", only_sectors_path - get '/only/sectors/1' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { only_sector_path(:id => '1') } + get "/only/sectors/1" + assert_equal "Not Found", @response.body + assert_raise(NoMethodError) { only_sector_path(id: "1") } end def test_only_option_should_not_inherit draw do - scope :only => :show do + scope only: :show do namespace :only do - resources :sectors, :only => :index do + resources :sectors, only: :index do resources :companies resource :leader end @@ -2646,38 +2701,38 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/only/sectors/1/companies/2' - assert_equal 'only/companies#show', @response.body - assert_equal '/only/sectors/1/companies/2', only_sector_company_path(:sector_id => '1', :id => '2') + get "/only/sectors/1/companies/2" + assert_equal "only/companies#show", @response.body + assert_equal "/only/sectors/1/companies/2", only_sector_company_path(sector_id: "1", id: "2") - get '/only/sectors/1/leader' - assert_equal 'only/leaders#show', @response.body - assert_equal '/only/sectors/1/leader', only_sector_leader_path(:sector_id => '1') + get "/only/sectors/1/leader" + assert_equal "only/leaders#show", @response.body + assert_equal "/only/sectors/1/leader", only_sector_leader_path(sector_id: "1") end def test_except_option_should_override_scope draw do - scope :except => :index do + scope except: :index do namespace :except do - resources :sectors, :except => [:show, :update, :destroy] + resources :sectors, except: [:show, :update, :destroy] end end end - get '/except/sectors' - assert_equal 'except/sectors#index', @response.body - assert_equal '/except/sectors', except_sectors_path + get "/except/sectors" + assert_equal "except/sectors#index", @response.body + assert_equal "/except/sectors", except_sectors_path - get '/except/sectors/1' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { except_sector_path(:id => '1') } + get "/except/sectors/1" + assert_equal "Not Found", @response.body + assert_raise(NoMethodError) { except_sector_path(id: "1") } end def test_except_option_should_not_inherit draw do - scope :except => :index do + scope except: :index do namespace :except do - resources :sectors, :except => [:show, :update, :destroy] do + resources :sectors, except: [:show, :update, :destroy] do resources :companies resource :leader end @@ -2685,62 +2740,62 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/except/sectors/1/companies/2' - assert_equal 'except/companies#show', @response.body - assert_equal '/except/sectors/1/companies/2', except_sector_company_path(:sector_id => '1', :id => '2') + get "/except/sectors/1/companies/2" + assert_equal "except/companies#show", @response.body + assert_equal "/except/sectors/1/companies/2", except_sector_company_path(sector_id: "1", id: "2") - get '/except/sectors/1/leader' - assert_equal 'except/leaders#show', @response.body - assert_equal '/except/sectors/1/leader', except_sector_leader_path(:sector_id => '1') + get "/except/sectors/1/leader" + assert_equal "except/leaders#show", @response.body + assert_equal "/except/sectors/1/leader", except_sector_leader_path(sector_id: "1") end def test_except_option_should_override_scoped_only draw do - scope :only => :show do + scope only: :show do namespace :only do - resources :sectors, :only => :index do - resources :managers, :except => [:show, :update, :destroy] + resources :sectors, only: :index do + resources :managers, except: [:show, :update, :destroy] end end end end - get '/only/sectors/1/managers' - assert_equal 'only/managers#index', @response.body - assert_equal '/only/sectors/1/managers', only_sector_managers_path(:sector_id => '1') + get "/only/sectors/1/managers" + assert_equal "only/managers#index", @response.body + assert_equal "/only/sectors/1/managers", only_sector_managers_path(sector_id: "1") - get '/only/sectors/1/managers/2' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { only_sector_manager_path(:sector_id => '1', :id => '2') } + get "/only/sectors/1/managers/2" + assert_equal "Not Found", @response.body + assert_raise(NoMethodError) { only_sector_manager_path(sector_id: "1", id: "2") } end def test_only_option_should_override_scoped_except draw do - scope :except => :index do + scope except: :index do namespace :except do - resources :sectors, :except => [:show, :update, :destroy] do - resources :managers, :only => :index + resources :sectors, except: [:show, :update, :destroy] do + resources :managers, only: :index end end end end - get '/except/sectors/1/managers' - assert_equal 'except/managers#index', @response.body - assert_equal '/except/sectors/1/managers', except_sector_managers_path(:sector_id => '1') + get "/except/sectors/1/managers" + assert_equal "except/managers#index", @response.body + assert_equal "/except/sectors/1/managers", except_sector_managers_path(sector_id: "1") - get '/except/sectors/1/managers/2' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { except_sector_manager_path(:sector_id => '1', :id => '2') } + get "/except/sectors/1/managers/2" + assert_equal "Not Found", @response.body + assert_raise(NoMethodError) { except_sector_manager_path(sector_id: "1", id: "2") } end def test_only_scope_should_override_parent_scope draw do - scope :only => :show do + scope only: :show do namespace :only do - resources :sectors, :only => :index do + resources :sectors, only: :index do resources :companies do - scope :only => :index do + scope only: :index do resources :divisions end end @@ -2749,22 +2804,22 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/only/sectors/1/companies/2/divisions' - assert_equal 'only/divisions#index', @response.body - assert_equal '/only/sectors/1/companies/2/divisions', only_sector_company_divisions_path(:sector_id => '1', :company_id => '2') + get "/only/sectors/1/companies/2/divisions" + assert_equal "only/divisions#index", @response.body + assert_equal "/only/sectors/1/companies/2/divisions", only_sector_company_divisions_path(sector_id: "1", company_id: "2") - get '/only/sectors/1/companies/2/divisions/3' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { only_sector_company_division_path(:sector_id => '1', :company_id => '2', :id => '3') } + get "/only/sectors/1/companies/2/divisions/3" + assert_equal "Not Found", @response.body + assert_raise(NoMethodError) { only_sector_company_division_path(sector_id: "1", company_id: "2", id: "3") } end def test_except_scope_should_override_parent_scope draw do - scope :except => :index do + scope except: :index do namespace :except do - resources :sectors, :except => [:show, :update, :destroy] do + resources :sectors, except: [:show, :update, :destroy] do resources :companies do - scope :except => [:show, :update, :destroy] do + scope except: [:show, :update, :destroy] do resources :divisions end end @@ -2773,22 +2828,22 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/except/sectors/1/companies/2/divisions' - assert_equal 'except/divisions#index', @response.body - assert_equal '/except/sectors/1/companies/2/divisions', except_sector_company_divisions_path(:sector_id => '1', :company_id => '2') + get "/except/sectors/1/companies/2/divisions" + assert_equal "except/divisions#index", @response.body + assert_equal "/except/sectors/1/companies/2/divisions", except_sector_company_divisions_path(sector_id: "1", company_id: "2") - get '/except/sectors/1/companies/2/divisions/3' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { except_sector_company_division_path(:sector_id => '1', :company_id => '2', :id => '3') } + get "/except/sectors/1/companies/2/divisions/3" + assert_equal "Not Found", @response.body + assert_raise(NoMethodError) { except_sector_company_division_path(sector_id: "1", company_id: "2", id: "3") } end def test_except_scope_should_override_parent_only_scope draw do - scope :only => :show do + scope only: :show do namespace :only do - resources :sectors, :only => :index do + resources :sectors, only: :index do resources :companies do - scope :except => [:show, :update, :destroy] do + scope except: [:show, :update, :destroy] do resources :departments end end @@ -2797,22 +2852,22 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/only/sectors/1/companies/2/departments' - assert_equal 'only/departments#index', @response.body - assert_equal '/only/sectors/1/companies/2/departments', only_sector_company_departments_path(:sector_id => '1', :company_id => '2') + get "/only/sectors/1/companies/2/departments" + assert_equal "only/departments#index", @response.body + assert_equal "/only/sectors/1/companies/2/departments", only_sector_company_departments_path(sector_id: "1", company_id: "2") - get '/only/sectors/1/companies/2/departments/3' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { only_sector_company_department_path(:sector_id => '1', :company_id => '2', :id => '3') } + get "/only/sectors/1/companies/2/departments/3" + assert_equal "Not Found", @response.body + assert_raise(NoMethodError) { only_sector_company_department_path(sector_id: "1", company_id: "2", id: "3") } end def test_only_scope_should_override_parent_except_scope draw do - scope :except => :index do + scope except: :index do namespace :except do - resources :sectors, :except => [:show, :update, :destroy] do + resources :sectors, except: [:show, :update, :destroy] do resources :companies do - scope :only => :index do + scope only: :index do resources :departments end end @@ -2821,13 +2876,13 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/except/sectors/1/companies/2/departments' - assert_equal 'except/departments#index', @response.body - assert_equal '/except/sectors/1/companies/2/departments', except_sector_company_departments_path(:sector_id => '1', :company_id => '2') + get "/except/sectors/1/companies/2/departments" + assert_equal "except/departments#index", @response.body + assert_equal "/except/sectors/1/companies/2/departments", except_sector_company_departments_path(sector_id: "1", company_id: "2") - get '/except/sectors/1/companies/2/departments/3' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { except_sector_company_department_path(:sector_id => '1', :company_id => '2', :id => '3') } + get "/except/sectors/1/companies/2/departments/3" + assert_equal "Not Found", @response.body + assert_raise(NoMethodError) { except_sector_company_department_path(sector_id: "1", company_id: "2", id: "3") } end def test_resources_are_not_pluralized @@ -2837,30 +2892,30 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/transport/taxis' - assert_equal 'transport/taxis#index', @response.body - assert_equal '/transport/taxis', transport_taxis_path + get "/transport/taxis" + assert_equal "transport/taxis#index", @response.body + assert_equal "/transport/taxis", transport_taxis_path - get '/transport/taxis/new' - assert_equal 'transport/taxis#new', @response.body - assert_equal '/transport/taxis/new', new_transport_taxi_path + get "/transport/taxis/new" + assert_equal "transport/taxis#new", @response.body + assert_equal "/transport/taxis/new", new_transport_taxi_path - post '/transport/taxis' - assert_equal 'transport/taxis#create', @response.body + post "/transport/taxis" + assert_equal "transport/taxis#create", @response.body - get '/transport/taxis/1' - assert_equal 'transport/taxis#show', @response.body - assert_equal '/transport/taxis/1', transport_taxi_path(:id => '1') + get "/transport/taxis/1" + assert_equal "transport/taxis#show", @response.body + assert_equal "/transport/taxis/1", transport_taxi_path(id: "1") - get '/transport/taxis/1/edit' - assert_equal 'transport/taxis#edit', @response.body - assert_equal '/transport/taxis/1/edit', edit_transport_taxi_path(:id => '1') + get "/transport/taxis/1/edit" + assert_equal "transport/taxis#edit", @response.body + assert_equal "/transport/taxis/1/edit", edit_transport_taxi_path(id: "1") - put '/transport/taxis/1' - assert_equal 'transport/taxis#update', @response.body + put "/transport/taxis/1" + assert_equal "transport/taxis#update", @response.body - delete '/transport/taxis/1' - assert_equal 'transport/taxis#destroy', @response.body + delete "/transport/taxis/1" + assert_equal "transport/taxis#destroy", @response.body end def test_singleton_resources_are_not_singularized @@ -2870,169 +2925,169 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/medical/taxis/new' - assert_equal 'medical/taxis#new', @response.body - assert_equal '/medical/taxis/new', new_medical_taxis_path + get "/medical/taxis/new" + assert_equal "medical/taxis#new", @response.body + assert_equal "/medical/taxis/new", new_medical_taxis_path - post '/medical/taxis' - assert_equal 'medical/taxis#create', @response.body + post "/medical/taxis" + assert_equal "medical/taxis#create", @response.body - get '/medical/taxis' - assert_equal 'medical/taxis#show', @response.body - assert_equal '/medical/taxis', medical_taxis_path + get "/medical/taxis" + assert_equal "medical/taxis#show", @response.body + assert_equal "/medical/taxis", medical_taxis_path - get '/medical/taxis/edit' - assert_equal 'medical/taxis#edit', @response.body - assert_equal '/medical/taxis/edit', edit_medical_taxis_path + get "/medical/taxis/edit" + assert_equal "medical/taxis#edit", @response.body + assert_equal "/medical/taxis/edit", edit_medical_taxis_path - put '/medical/taxis' - assert_equal 'medical/taxis#update', @response.body + put "/medical/taxis" + assert_equal "medical/taxis#update", @response.body - delete '/medical/taxis' - assert_equal 'medical/taxis#destroy', @response.body + delete "/medical/taxis" + assert_equal "medical/taxis#destroy", @response.body end def test_greedy_resource_id_regexp_doesnt_match_edit_and_custom_action draw do - resources :sections, :id => /.+/ do - get :preview, :on => :member + resources :sections, id: /.+/ do + get :preview, on: :member end end - get '/sections/1/edit' - assert_equal 'sections#edit', @response.body - assert_equal '/sections/1/edit', edit_section_path(:id => '1') + get "/sections/1/edit" + assert_equal "sections#edit", @response.body + assert_equal "/sections/1/edit", edit_section_path(id: "1") - get '/sections/1/preview' - assert_equal 'sections#preview', @response.body - assert_equal '/sections/1/preview', preview_section_path(:id => '1') + get "/sections/1/preview" + assert_equal "sections#preview", @response.body + assert_equal "/sections/1/preview", preview_section_path(id: "1") end def test_resource_constraints_are_pushed_to_scope draw do namespace :wiki do - resources :articles, :id => /[^\/]+/ do - resources :comments, :only => [:create, :new] + resources :articles, id: /[^\/]+/ do + resources :comments, only: [:create, :new] end end end - get '/wiki/articles/Ruby_on_Rails_3.0' - assert_equal 'wiki/articles#show', @response.body - assert_equal '/wiki/articles/Ruby_on_Rails_3.0', wiki_article_path(:id => 'Ruby_on_Rails_3.0') + get "/wiki/articles/Ruby_on_Rails_3.0" + assert_equal "wiki/articles#show", @response.body + assert_equal "/wiki/articles/Ruby_on_Rails_3.0", wiki_article_path(id: "Ruby_on_Rails_3.0") - get '/wiki/articles/Ruby_on_Rails_3.0/comments/new' - assert_equal 'wiki/comments#new', @response.body - assert_equal '/wiki/articles/Ruby_on_Rails_3.0/comments/new', new_wiki_article_comment_path(:article_id => 'Ruby_on_Rails_3.0') + get "/wiki/articles/Ruby_on_Rails_3.0/comments/new" + assert_equal "wiki/comments#new", @response.body + assert_equal "/wiki/articles/Ruby_on_Rails_3.0/comments/new", new_wiki_article_comment_path(article_id: "Ruby_on_Rails_3.0") - post '/wiki/articles/Ruby_on_Rails_3.0/comments' - assert_equal 'wiki/comments#create', @response.body - assert_equal '/wiki/articles/Ruby_on_Rails_3.0/comments', wiki_article_comments_path(:article_id => 'Ruby_on_Rails_3.0') + post "/wiki/articles/Ruby_on_Rails_3.0/comments" + assert_equal "wiki/comments#create", @response.body + assert_equal "/wiki/articles/Ruby_on_Rails_3.0/comments", wiki_article_comments_path(article_id: "Ruby_on_Rails_3.0") end def test_resources_path_can_be_a_symbol draw do - resources :wiki_pages, :path => :pages - resource :wiki_account, :path => :my_account + resources :wiki_pages, path: :pages + resource :wiki_account, path: :my_account end - get '/pages' - assert_equal 'wiki_pages#index', @response.body - assert_equal '/pages', wiki_pages_path + get "/pages" + assert_equal "wiki_pages#index", @response.body + assert_equal "/pages", wiki_pages_path - get '/pages/Ruby_on_Rails' - assert_equal 'wiki_pages#show', @response.body - assert_equal '/pages/Ruby_on_Rails', wiki_page_path(:id => 'Ruby_on_Rails') + get "/pages/Ruby_on_Rails" + assert_equal "wiki_pages#show", @response.body + assert_equal "/pages/Ruby_on_Rails", wiki_page_path(id: "Ruby_on_Rails") - get '/my_account' - assert_equal 'wiki_accounts#show', @response.body - assert_equal '/my_account', wiki_account_path + get "/my_account" + assert_equal "wiki_accounts#show", @response.body + assert_equal "/my_account", wiki_account_path end def test_redirect_https draw do - get 'secure', :to => redirect("/secure/login") + get "secure", to: redirect("/secure/login") end with_https do - get '/secure' - verify_redirect 'https://www.example.com/secure/login' + get "/secure" + verify_redirect "https://www.example.com/secure/login" end end def test_path_parameters_is_not_stale draw do - scope '/countries/:country', :constraints => lambda { |params, req| %w(all France).include?(params[:country]) } do - get '/', :to => 'countries#index' - get '/cities', :to => 'countries#cities' + scope "/countries/:country", constraints: lambda { |params, req| %w(all France).include?(params[:country]) } do + get "/", to: "countries#index" + get "/cities", to: "countries#cities" end - get '/countries/:country/(*other)', :to => redirect{ |params, req| params[:other] ? "/countries/all/#{params[:other]}" : '/countries/all' } + get "/countries/:country/(*other)", to: redirect { |params, req| params[:other] ? "/countries/all/#{params[:other]}" : "/countries/all" } end - get '/countries/France' - assert_equal 'countries#index', @response.body + get "/countries/France" + assert_equal "countries#index", @response.body - get '/countries/France/cities' - assert_equal 'countries#cities', @response.body + get "/countries/France/cities" + assert_equal "countries#cities", @response.body - get '/countries/UK' - verify_redirect 'http://www.example.com/countries/all' + get "/countries/UK" + verify_redirect "http://www.example.com/countries/all" - get '/countries/UK/cities' - verify_redirect 'http://www.example.com/countries/all/cities' + get "/countries/UK/cities" + verify_redirect "http://www.example.com/countries/all/cities" end def test_constraints_block_not_carried_to_following_routes draw do - scope '/italians' do - get '/writers', :to => 'italians#writers', :constraints => ::TestRoutingMapper::IpRestrictor - get '/sculptors', :to => 'italians#sculptors' - get '/painters/:painter', :to => 'italians#painters', :constraints => {:painter => /michelangelo/} + scope "/italians" do + get "/writers", to: "italians#writers", constraints: ::TestRoutingMapper::IpRestrictor + get "/sculptors", to: "italians#sculptors" + get "/painters/:painter", to: "italians#painters", constraints: { painter: /michelangelo/ } end end - get '/italians/writers' - assert_equal 'Not Found', @response.body + get "/italians/writers" + assert_equal "Not Found", @response.body - get '/italians/sculptors' - assert_equal 'italians#sculptors', @response.body + get "/italians/sculptors" + assert_equal "italians#sculptors", @response.body - get '/italians/painters/botticelli' - assert_equal 'Not Found', @response.body + get "/italians/painters/botticelli" + assert_equal "Not Found", @response.body - get '/italians/painters/michelangelo' - assert_equal 'italians#painters', @response.body + get "/italians/painters/michelangelo" + assert_equal "italians#painters", @response.body end def test_custom_resource_actions_defined_using_string draw do resources :customers do resources :invoices do - get "aged/:months", :on => :collection, :action => :aged, :as => :aged + get "aged/:months", on: :collection, action: :aged, as: :aged end - get "inactive", :on => :collection - post "deactivate", :on => :member - get "old", :on => :collection, :as => :stale + get "inactive", on: :collection + post "deactivate", on: :member + get "old", on: :collection, as: :stale end end - get '/customers/inactive' - assert_equal 'customers#inactive', @response.body - assert_equal '/customers/inactive', inactive_customers_path + get "/customers/inactive" + assert_equal "customers#inactive", @response.body + assert_equal "/customers/inactive", inactive_customers_path - post '/customers/1/deactivate' - assert_equal 'customers#deactivate', @response.body - assert_equal '/customers/1/deactivate', deactivate_customer_path(:id => '1') + post "/customers/1/deactivate" + assert_equal "customers#deactivate", @response.body + assert_equal "/customers/1/deactivate", deactivate_customer_path(id: "1") - get '/customers/old' - assert_equal 'customers#old', @response.body - assert_equal '/customers/old', stale_customers_path + get "/customers/old" + assert_equal "customers#old", @response.body + assert_equal "/customers/old", stale_customers_path - get '/customers/1/invoices/aged/3' - assert_equal 'invoices#aged', @response.body - assert_equal '/customers/1/invoices/aged/3', aged_customer_invoices_path(:customer_id => '1', :months => '3') + get "/customers/1/invoices/aged/3" + assert_equal "invoices#aged", @response.body + assert_equal "/customers/1/invoices/aged/3", aged_customer_invoices_path(customer_id: "1", months: "3") end def test_route_defined_in_resources_scope_level @@ -3042,43 +3097,43 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/customers/1/export' - assert_equal 'customers#export', @response.body - assert_equal '/customers/1/export', customer_export_path(:customer_id => '1') + get "/customers/1/export" + assert_equal "customers#export", @response.body + assert_equal "/customers/1/export", customer_export_path(customer_id: "1") end def test_named_character_classes_in_regexp_constraints draw do - get '/purchases/:token/:filename', - :to => 'purchases#fetch', - :token => /[[:alnum:]]{10}/, - :filename => /(.+)/, - :as => :purchase + get "/purchases/:token/:filename", + to: "purchases#fetch", + token: /[[:alnum:]]{10}/, + filename: /(.+)/, + as: :purchase end - get '/purchases/315004be7e/Ruby_on_Rails_3.pdf' - assert_equal 'purchases#fetch', @response.body - assert_equal '/purchases/315004be7e/Ruby_on_Rails_3.pdf', purchase_path(:token => '315004be7e', :filename => 'Ruby_on_Rails_3.pdf') + get "/purchases/315004be7e/Ruby_on_Rails_3.pdf" + assert_equal "purchases#fetch", @response.body + assert_equal "/purchases/315004be7e/Ruby_on_Rails_3.pdf", purchase_path(token: "315004be7e", filename: "Ruby_on_Rails_3.pdf") end def test_nested_resource_constraints draw do - resources :lists, :id => /([A-Za-z0-9]{25})|default/ do - resources :todos, :id => /\d+/ + resources :lists, id: /([A-Za-z0-9]{25})|default/ do + resources :todos, id: /\d+/ end end - get '/lists/01234012340123401234fffff' - assert_equal 'lists#show', @response.body - assert_equal '/lists/01234012340123401234fffff', list_path(:id => '01234012340123401234fffff') + get "/lists/01234012340123401234fffff" + assert_equal "lists#show", @response.body + assert_equal "/lists/01234012340123401234fffff", list_path(id: "01234012340123401234fffff") - get '/lists/01234012340123401234fffff/todos/1' - assert_equal 'todos#show', @response.body - assert_equal '/lists/01234012340123401234fffff/todos/1', list_todo_path(:list_id => '01234012340123401234fffff', :id => '1') + get "/lists/01234012340123401234fffff/todos/1" + assert_equal "todos#show", @response.body + assert_equal "/lists/01234012340123401234fffff/todos/1", list_todo_path(list_id: "01234012340123401234fffff", id: "1") - get '/lists/2/todos/1' - assert_equal 'Not Found', @response.body - assert_raises(ActionController::UrlGenerationError){ list_todo_path(:list_id => '2', :id => '1') } + get "/lists/2/todos/1" + assert_equal "Not Found", @response.body + assert_raises(ActionController::UrlGenerationError) { list_todo_path(list_id: "2", id: "1") } end def test_redirect_argument_error @@ -3101,10 +3156,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest def test_explicitly_avoiding_the_named_route draw do - scope :as => "routes" do - get "/c/:id", :as => :collision, :to => "collision#show" - get "/collision", :to => "collision#show" - get "/no_collision", :to => "collision#show", :as => nil + scope as: "routes" do + get "/c/:id", as: :collision, to: "collision#show" + get "/collision", to: "collision#show" + get "/no_collision", to: "collision#show", as: nil end end @@ -3113,55 +3168,49 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest def test_controller_name_with_leading_slash_raise_error assert_raise(ArgumentError) do - draw { get '/feeds/:service', :to => '/feeds#show' } + draw { get "/feeds/:service", to: "/feeds#show" } end assert_raise(ArgumentError) do - draw { get '/feeds/:service', :controller => '/feeds', :action => 'show' } + draw { get "/feeds/:service", controller: "/feeds", action: "show" } end assert_raise(ArgumentError) do - draw { get '/api/feeds/:service', :to => '/api/feeds#show' } + draw { get "/api/feeds/:service", to: "/api/feeds#show" } end assert_raise(ArgumentError) do - assert_deprecated do - draw { controller("/feeds") { get '/feeds/:service', :to => :show } } - end - end - - assert_raise(ArgumentError) do - draw { resources :feeds, :controller => '/feeds' } + draw { resources :feeds, controller: "/feeds" } end end def test_invalid_route_name_raises_error assert_raise(ArgumentError) do - draw { get '/products', :to => 'products#index', :as => 'products ' } + draw { get "/products", to: "products#index", as: "products " } end assert_raise(ArgumentError) do - draw { get '/products', :to => 'products#index', :as => ' products' } + draw { get "/products", to: "products#index", as: " products" } end assert_raise(ArgumentError) do - draw { get '/products', :to => 'products#index', :as => 'products!' } + draw { get "/products", to: "products#index", as: "products!" } end assert_raise(ArgumentError) do - draw { get '/products', :to => 'products#index', :as => 'products index' } + draw { get "/products", to: "products#index", as: "products index" } end assert_raise(ArgumentError) do - draw { get '/products', :to => 'products#index', :as => '1products' } + draw { get "/products", to: "products#index", as: "1products" } end end def test_duplicate_route_name_raises_error assert_raise(ArgumentError) do draw do - get '/collision', :to => 'collision#show', :as => 'collision' - get '/duplicate', :to => 'duplicate#show', :as => 'collision' + get "/collision", to: "collision#show", as: "collision" + get "/duplicate", to: "duplicate#show", as: "collision" end end end @@ -3170,15 +3219,15 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_raise(ArgumentError) do draw do resources :collisions - get '/collision', :to => 'collision#show', :as => 'collision' + get "/collision", to: "collision#show", as: "collision" end end end def test_nested_route_in_nested_resource draw do - resources :posts, :only => [:index, :show] do - resources :comments, :except => :destroy do + resources :posts, only: [:index, :show] do + resources :comments, except: :destroy do get "views" => "comments#views", :as => :views end end @@ -3186,124 +3235,124 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest get "/posts/1/comments/2/views" assert_equal "comments#views", @response.body - assert_equal "/posts/1/comments/2/views", post_comment_views_path(:post_id => '1', :comment_id => '2') + assert_equal "/posts/1/comments/2/views", post_comment_views_path(post_id: "1", comment_id: "2") end def test_root_in_deeply_nested_scope draw do - resources :posts, :only => [:index, :show] do + resources :posts, only: [:index, :show] do namespace :admin do - root :to => "index#index" + root to: "index#index" end end end get "/posts/1/admin" assert_equal "admin/index#index", @response.body - assert_equal "/posts/1/admin", post_admin_root_path(:post_id => '1') + assert_equal "/posts/1/admin", post_admin_root_path(post_id: "1") end def test_custom_param draw do - resources :profiles, :param => :username do - get :details, :on => :member + resources :profiles, param: :username do + get :details, on: :member resources :messages end end - get '/profiles/bob' - assert_equal 'profiles#show', @response.body - assert_equal 'bob', @request.params[:username] + get "/profiles/bob" + assert_equal "profiles#show", @response.body + assert_equal "bob", @request.params[:username] - get '/profiles/bob/details' - assert_equal 'bob', @request.params[:username] + get "/profiles/bob/details" + assert_equal "bob", @request.params[:username] - get '/profiles/bob/messages/34' - assert_equal 'bob', @request.params[:profile_username] - assert_equal '34', @request.params[:id] + get "/profiles/bob/messages/34" + assert_equal "bob", @request.params[:profile_username] + assert_equal "34", @request.params[:id] end def test_custom_param_constraint draw do - resources :profiles, :param => :username, :username => /[a-z]+/ do - get :details, :on => :member + resources :profiles, param: :username, username: /[a-z]+/ do + get :details, on: :member resources :messages end end - get '/profiles/bob1' + get "/profiles/bob1" assert_equal 404, @response.status - get '/profiles/bob1/details' + get "/profiles/bob1/details" assert_equal 404, @response.status - get '/profiles/bob1/messages/34' + get "/profiles/bob1/messages/34" assert_equal 404, @response.status end def test_shallow_custom_param draw do resources :orders do - constraints :download => /[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}/ do - resources :downloads, :param => :download, :shallow => true + constraints download: /[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}/ do + resources :downloads, param: :download, shallow: true end end end - get '/downloads/0c0c0b68-d24b-11e1-a861-001ff3fffe6f.zip' - assert_equal 'downloads#show', @response.body - assert_equal '0c0c0b68-d24b-11e1-a861-001ff3fffe6f', @request.params[:download] + get "/downloads/0c0c0b68-d24b-11e1-a861-001ff3fffe6f.zip" + assert_equal "downloads#show", @response.body + assert_equal "0c0c0b68-d24b-11e1-a861-001ff3fffe6f", @request.params[:download] end def test_action_from_path_is_not_frozen draw do - get 'search' => 'search' + get "search" => "search" end - get '/search' + get "/search" assert !@request.params[:action].frozen? end def test_multiple_positional_args_with_the_same_name draw do - get '/downloads/:id/:id.tar' => 'downloads#show', as: :download, format: false + get "/downloads/:id/:id.tar" => "downloads#show", as: :download, format: false end expected_params = { - controller: 'downloads', - action: 'show', - id: '1' + controller: "downloads", + action: "show", + id: "1" } - get '/downloads/1/1.tar' - assert_equal 'downloads#show', @response.body + get "/downloads/1/1.tar" + assert_equal "downloads#show", @response.body assert_equal expected_params, @request.path_parameters - assert_equal '/downloads/1/1.tar', download_path('1') - assert_equal '/downloads/1/1.tar', download_path('1', '1') + assert_equal "/downloads/1/1.tar", download_path("1") + assert_equal "/downloads/1/1.tar", download_path("1", "1") end def test_absolute_controller_namespace draw do namespace :foo do - get '/', to: '/bar#index', as: 'root' + get "/", to: "/bar#index", as: "root" end end - get '/foo' - assert_equal 'bar#index', @response.body - assert_equal '/foo', foo_root_path + get "/foo" + assert_equal "bar#index", @response.body + assert_equal "/foo", foo_root_path end def test_namespace_as_controller draw do namespace :foo do - get '/', to: '/bar#index', as: 'root' + get "/", to: "/bar#index", as: "root" end end - get '/foo' - assert_equal 'bar#index', @response.body - assert_equal '/foo', foo_root_path + get "/foo" + assert_equal "bar#index", @response.body + assert_equal "/foo", foo_root_path end def test_trailing_slash @@ -3311,63 +3360,63 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest resources :streams end - get '/streams' - assert @response.ok?, 'route without trailing slash should work' + get "/streams" + assert @response.ok?, "route without trailing slash should work" - get '/streams/' - assert @response.ok?, 'route with trailing slash should work' + get "/streams/" + assert @response.ok?, "route with trailing slash should work" - get '/streams?foobar' - assert @response.ok?, 'route without trailing slash and with QUERY_STRING should work' + get "/streams?foobar" + assert @response.ok?, "route without trailing slash and with QUERY_STRING should work" - get '/streams/?foobar' - assert @response.ok?, 'route with trailing slash and with QUERY_STRING should work' + get "/streams/?foobar" + assert @response.ok?, "route with trailing slash and with QUERY_STRING should work" end def test_route_with_dashes_in_path draw do - get '/contact-us', to: 'pages#contact_us' + get "/contact-us", to: "pages#contact_us" end - get '/contact-us' - assert_equal 'pages#contact_us', @response.body - assert_equal '/contact-us', contact_us_path + get "/contact-us" + assert_equal "pages#contact_us", @response.body + assert_equal "/contact-us", contact_us_path end def test_shorthand_route_with_dashes_in_path draw do - get '/about-us/index' + get "/about-us/index" end - get '/about-us/index' - assert_equal 'about_us#index', @response.body - assert_equal '/about-us/index', about_us_index_path + get "/about-us/index" + assert_equal "about_us#index", @response.body + assert_equal "/about-us/index", about_us_index_path end def test_resource_routes_with_dashes_in_path draw do resources :photos, only: [:show] do - get 'user-favorites', on: :collection - get 'preview-photo', on: :member - get 'summary-text' + get "user-favorites", on: :collection + get "preview-photo", on: :member + get "summary-text" end end - get '/photos/user-favorites' - assert_equal 'photos#user_favorites', @response.body - assert_equal '/photos/user-favorites', user_favorites_photos_path + get "/photos/user-favorites" + assert_equal "photos#user_favorites", @response.body + assert_equal "/photos/user-favorites", user_favorites_photos_path - get '/photos/1/preview-photo' - assert_equal 'photos#preview_photo', @response.body - assert_equal '/photos/1/preview-photo', preview_photo_photo_path('1') + get "/photos/1/preview-photo" + assert_equal "photos#preview_photo", @response.body + assert_equal "/photos/1/preview-photo", preview_photo_photo_path("1") - get '/photos/1/summary-text' - assert_equal 'photos#summary_text', @response.body - assert_equal '/photos/1/summary-text', photo_summary_text_path('1') + get "/photos/1/summary-text" + assert_equal "photos#summary_text", @response.body + assert_equal "/photos/1/summary-text", photo_summary_text_path("1") - get '/photos/1' - assert_equal 'photos#show', @response.body - assert_equal '/photos/1', photo_path('1') + get "/photos/1" + assert_equal "photos#show", @response.body + assert_equal "/photos/1", photo_path("1") end def test_shallow_path_inside_namespace_is_not_added_twice @@ -3381,208 +3430,208 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - get '/admin/posts/1/comments' - assert_equal 'admin/comments#index', @response.body - assert_equal '/admin/posts/1/comments', admin_post_comments_path('1') + get "/admin/posts/1/comments" + assert_equal "admin/comments#index", @response.body + assert_equal "/admin/posts/1/comments", admin_post_comments_path("1") end def test_mix_string_to_controller_action draw do - get '/projects', controller: 'project_files', - action: 'index', - to: 'comments#index' + get "/projects", controller: "project_files", + action: "index", + to: "comments#index" end - get '/projects' - assert_equal 'comments#index', @response.body + get "/projects" + assert_equal "comments#index", @response.body end def test_mix_string_to_controller draw do - get '/projects', controller: 'project_files', - to: 'comments#index' + get "/projects", controller: "project_files", + to: "comments#index" end - get '/projects' - assert_equal 'comments#index', @response.body + get "/projects" + assert_equal "comments#index", @response.body end def test_mix_string_to_action draw do - get '/projects', action: 'index', - to: 'comments#index' + get "/projects", action: "index", + to: "comments#index" end - get '/projects' - assert_equal 'comments#index', @response.body + get "/projects" + assert_equal "comments#index", @response.body end def test_shallow_path_and_prefix_are_not_added_to_non_shallow_routes draw do - scope shallow_path: 'projects', shallow_prefix: 'project' do + scope shallow_path: "projects", shallow_prefix: "project" do resources :projects do - resources :files, controller: 'project_files', shallow: true + resources :files, controller: "project_files", shallow: true end end end - get '/projects' - assert_equal 'projects#index', @response.body - assert_equal '/projects', projects_path + get "/projects" + assert_equal "projects#index", @response.body + assert_equal "/projects", projects_path - get '/projects/new' - assert_equal 'projects#new', @response.body - assert_equal '/projects/new', new_project_path + get "/projects/new" + assert_equal "projects#new", @response.body + assert_equal "/projects/new", new_project_path - post '/projects' - assert_equal 'projects#create', @response.body + post "/projects" + assert_equal "projects#create", @response.body - get '/projects/1' - assert_equal 'projects#show', @response.body - assert_equal '/projects/1', project_path('1') + get "/projects/1" + assert_equal "projects#show", @response.body + assert_equal "/projects/1", project_path("1") - get '/projects/1/edit' - assert_equal 'projects#edit', @response.body - assert_equal '/projects/1/edit', edit_project_path('1') + get "/projects/1/edit" + assert_equal "projects#edit", @response.body + assert_equal "/projects/1/edit", edit_project_path("1") - patch '/projects/1' - assert_equal 'projects#update', @response.body + patch "/projects/1" + assert_equal "projects#update", @response.body - delete '/projects/1' - assert_equal 'projects#destroy', @response.body + delete "/projects/1" + assert_equal "projects#destroy", @response.body - get '/projects/1/files' - assert_equal 'project_files#index', @response.body - assert_equal '/projects/1/files', project_files_path('1') + get "/projects/1/files" + assert_equal "project_files#index", @response.body + assert_equal "/projects/1/files", project_files_path("1") - get '/projects/1/files/new' - assert_equal 'project_files#new', @response.body - assert_equal '/projects/1/files/new', new_project_file_path('1') + get "/projects/1/files/new" + assert_equal "project_files#new", @response.body + assert_equal "/projects/1/files/new", new_project_file_path("1") - post '/projects/1/files' - assert_equal 'project_files#create', @response.body + post "/projects/1/files" + assert_equal "project_files#create", @response.body - get '/projects/files/2' - assert_equal 'project_files#show', @response.body - assert_equal '/projects/files/2', project_file_path('2') + get "/projects/files/2" + assert_equal "project_files#show", @response.body + assert_equal "/projects/files/2", project_file_path("2") - get '/projects/files/2/edit' - assert_equal 'project_files#edit', @response.body - assert_equal '/projects/files/2/edit', edit_project_file_path('2') + get "/projects/files/2/edit" + assert_equal "project_files#edit", @response.body + assert_equal "/projects/files/2/edit", edit_project_file_path("2") - patch '/projects/files/2' - assert_equal 'project_files#update', @response.body + patch "/projects/files/2" + assert_equal "project_files#update", @response.body - delete '/projects/files/2' - assert_equal 'project_files#destroy', @response.body + delete "/projects/files/2" + assert_equal "project_files#destroy", @response.body end def test_scope_path_is_copied_to_shallow_path draw do - scope path: 'foo' do + scope path: "foo" do resources :posts do resources :comments, shallow: true end end end - assert_equal '/foo/comments/1', comment_path('1') + assert_equal "/foo/comments/1", comment_path("1") end def test_scope_as_is_copied_to_shallow_prefix draw do - scope as: 'foo' do + scope as: "foo" do resources :posts do resources :comments, shallow: true end end end - assert_equal '/comments/1', foo_comment_path('1') + assert_equal "/comments/1", foo_comment_path("1") end def test_scope_shallow_prefix_is_not_overwritten_by_as draw do - scope as: 'foo', shallow_prefix: 'bar' do + scope as: "foo", shallow_prefix: "bar" do resources :posts do resources :comments, shallow: true end end end - assert_equal '/comments/1', bar_comment_path('1') + assert_equal "/comments/1", bar_comment_path("1") end def test_scope_shallow_path_is_not_overwritten_by_path draw do - scope path: 'foo', shallow_path: 'bar' do + scope path: "foo", shallow_path: "bar" do resources :posts do resources :comments, shallow: true end end end - assert_equal '/bar/comments/1', comment_path('1') + assert_equal "/bar/comments/1", comment_path("1") end def test_resource_where_as_is_empty draw do - resource :post, as: '' + resource :post, as: "" - scope 'post', as: 'post' do - resource :comment, as: '' + scope "post", as: "post" do + resource :comment, as: "" end end - assert_equal '/post/new', new_path - assert_equal '/post/comment/new', new_post_path + assert_equal "/post/new", new_path + assert_equal "/post/comment/new", new_post_path end def test_resources_where_as_is_empty draw do - resources :posts, as: '' + resources :posts, as: "" - scope 'posts', as: 'posts' do - resources :comments, as: '' + scope "posts", as: "posts" do + resources :comments, as: "" end end - assert_equal '/posts/new', new_path - assert_equal '/posts/comments/new', new_posts_path + assert_equal "/posts/new", new_path + assert_equal "/posts/comments/new", new_posts_path end def test_scope_where_as_is_empty draw do - scope 'post', as: '' do + scope "post", as: "" do resource :user resources :comments end end - assert_equal '/post/user/new', new_user_path - assert_equal '/post/comments/new', new_comment_path + assert_equal "/post/user/new", new_user_path + assert_equal "/post/comments/new", new_comment_path end def test_head_fetch_with_mount_on_root draw do - get '/home' => 'test#index' - mount lambda { |env| [200, {}, [env['REQUEST_METHOD']]] }, at: '/' + get "/home" => "test#index" + mount lambda { |env| [200, {}, [env["REQUEST_METHOD"]]] }, at: "/" end # HEAD request should match `get /home` rather than the # lower-precedence Rack app mounted at `/`. - head '/home' + head "/home" assert_response :ok - assert_equal 'test#index', @response.body + assert_equal "test#index", @response.body # But the Rack app can still respond to its own HEAD requests. - head '/foobar' + head "/foobar" assert_response :ok - assert_equal 'HEAD', @response.body + assert_equal "HEAD", @response.body end def test_passing_action_parameters_to_url_helpers_raises_error_if_parameters_are_not_permitted draw do - root :to => 'projects#index' + root to: "projects#index" end - params = ActionController::Parameters.new(id: '1') + params = ActionController::Parameters.new(id: "1") assert_raises ArgumentError do root_path(params) @@ -3591,19 +3640,77 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest def test_passing_action_parameters_to_url_helpers_is_allowed_if_parameters_are_permitted draw do - root :to => 'projects#index' + root to: "projects#index" end - params = ActionController::Parameters.new(id: '1') + params = ActionController::Parameters.new(id: "1") params.permit! - assert_equal '/?id=1', root_path(params) + assert_equal "/?id=1", root_path(params) + end + + def test_dynamic_controller_segments_are_deprecated + assert_deprecated do + draw do + get "/:controller", action: "index" + end + end + end + + def test_dynamic_action_segments_are_deprecated + assert_deprecated do + draw do + get "/pages/:action", controller: "pages" + end + end + end + + def test_multiple_roots + draw do + namespace :foo do + root "pages#index", constraints: { host: "www.example.com" } + root "admin/pages#index", constraints: { host: "admin.example.com" } + end + + root "pages#index", constraints: { host: "www.example.com" } + root "admin/pages#index", constraints: { host: "admin.example.com" } + end + + get "http://www.example.com/foo" + assert_equal "foo/pages#index", @response.body + + get "http://admin.example.com/foo" + assert_equal "foo/admin/pages#index", @response.body + + get "http://www.example.com/" + assert_equal "pages#index", @response.body + + get "http://admin.example.com/" + assert_equal "admin/pages#index", @response.body + end + + def test_multiple_namespaced_roots + draw do + namespace :foo do + root "test#index" + end + + root "test#index" + + namespace :bar do + root "test#index" + end + end + + assert_equal "/foo", foo_root_path + assert_equal "/", root_path + assert_equal "/bar", bar_root_path end private def draw(&block) self.class.stub_controllers do |routes| - routes.default_url_options = { host: 'www.example.com' } + routes.default_url_options = { host: "www.example.com" } routes.draw(&block) @app = RoutedRackApp.new routes end @@ -3631,7 +3738,7 @@ private def verify_redirect(url, status=301) assert_equal status, @response.status - assert_equal url, @response.headers['Location'] + assert_equal url, @response.headers["Location"] assert_equal expected_redirect_body(url), @response.body end @@ -3668,13 +3775,13 @@ class TestAltApp < ActionDispatch::IntegrationTest class XHeader def call(env) - [200, {"Content-Type" => "text/html"}, ["XHeader"]] + [200, { "Content-Type" => "text/html" }, ["XHeader"]] end end class AltApp def call(env) - [200, {"Content-Type" => "text/html"}, ["Alternative App"]] + [200, { "Content-Type" => "text/html" }, ["Alternative App"]] end end @@ -3684,7 +3791,7 @@ class TestAltApp < ActionDispatch::IntegrationTest end }.new AltRoutes.draw do - get "/" => TestAltApp::XHeader.new, :constraints => {:x_header => /HEADER/} + get "/" => TestAltApp::XHeader.new, :constraints => { x_header: /HEADER/ } get "/" => TestAltApp::AltApp.new end @@ -3712,7 +3819,7 @@ end class TestAppendingRoutes < ActionDispatch::IntegrationTest def simple_app(resp) - lambda { |e| [ 200, { 'Content-Type' => 'text/plain' }, [resp] ] } + lambda { |e| [ 200, { "Content-Type" => "text/plain" }, [resp] ] } end def setup @@ -3720,28 +3827,28 @@ class TestAppendingRoutes < ActionDispatch::IntegrationTest s = self routes = ActionDispatch::Routing::RouteSet.new routes.append do - get '/hello' => s.simple_app('fail') - get '/goodbye' => s.simple_app('goodbye') + get "/hello" => s.simple_app("fail") + get "/goodbye" => s.simple_app("goodbye") end routes.draw do - get '/hello' => s.simple_app('hello') + get "/hello" => s.simple_app("hello") end @app = self.class.build_app routes end def test_goodbye_should_be_available - get '/goodbye' - assert_equal 'goodbye', @response.body + get "/goodbye" + assert_equal "goodbye", @response.body end def test_hello_should_not_be_overwritten - get '/hello' - assert_equal 'hello', @response.body + get "/hello" + assert_equal "hello", @response.body end def test_missing_routes_are_still_missing - get '/random' + get "/random" assert_equal 404, @response.status end end @@ -3764,7 +3871,7 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest def test_missing_controller ex = assert_raises(ArgumentError) { draw do - get '/foo/bar', :action => :index + get "/foo/bar", action: :index end } assert_match(/Missing :controller/, ex.message) @@ -3773,7 +3880,7 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest def test_missing_controller_with_to ex = assert_raises(ArgumentError) { draw do - get '/foo/bar', :to => 'foo' + get "/foo/bar", to: "foo" end } assert_match(/Missing :controller/, ex.message) @@ -3782,7 +3889,7 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest def test_missing_action_on_hash ex = assert_raises(ArgumentError) { draw do - get '/foo/bar', :to => 'foo#' + get "/foo/bar", to: "foo#" end } assert_match(/Missing :action/, ex.message) @@ -3791,20 +3898,20 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest def test_valid_controller_options_inside_namespace draw do namespace :admin do - resources :storage_files, :controller => "storage_files" + resources :storage_files, controller: "storage_files" end end - get '/admin/storage_files' + get "/admin/storage_files" assert_equal "admin/storage_files#index", @response.body end def test_resources_with_valid_namespaced_controller_option draw do - resources :storage_files, :controller => 'admin/storage_files' + resources :storage_files, controller: "admin/storage_files" end - get '/storage_files' + get "/storage_files" assert_equal "admin/storage_files#index", @response.body end @@ -3812,7 +3919,7 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest e = assert_raise(ArgumentError) do draw do namespace :admin do - resources :storage_files, :controller => "StorageFiles" + resources :storage_files, controller: "StorageFiles" end end end @@ -3823,7 +3930,7 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest def test_warn_with_ruby_constant_syntax_namespaced_controller_option e = assert_raise(ArgumentError) do draw do - resources :storage_files, :controller => 'Admin::StorageFiles' + resources :storage_files, controller: "Admin::StorageFiles" end end @@ -3833,7 +3940,7 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest def test_warn_with_ruby_constant_syntax_no_colons e = assert_raise(ArgumentError) do draw do - resources :storage_files, :controller => 'Admin' + resources :storage_files, controller: "Admin" end end @@ -3851,7 +3958,7 @@ class TestDefaultScope < ActionDispatch::IntegrationTest end DefaultScopeRoutes = ActionDispatch::Routing::RouteSet.new - DefaultScopeRoutes.default_scope = {:module => :blog} + DefaultScopeRoutes.default_scope = { module: :blog } DefaultScopeRoutes.draw do resources :posts end @@ -3865,7 +3972,7 @@ class TestDefaultScope < ActionDispatch::IntegrationTest include DefaultScopeRoutes.url_helpers def test_default_scope - get '/posts' + get "/posts" assert_equal "blog/posts#index", @response.body end end @@ -3881,7 +3988,7 @@ class TestHttpMethods < ActionDispatch::IntegrationTest RFC5789 = %w(PATCH) def simple_app(response) - lambda { |env| [ 200, { 'Content-Type' => 'text/plain' }, [response] ] } + lambda { |env| [ 200, { "Content-Type" => "text/plain" }, [response] ] } end attr_reader :app @@ -3893,14 +4000,14 @@ class TestHttpMethods < ActionDispatch::IntegrationTest routes.draw do (RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC4791 + RFC5789).each do |method| - match '/' => s.simple_app(method), :via => method.underscore.to_sym + match "/" => s.simple_app(method), :via => method.underscore.to_sym end end end (RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC4791 + RFC5789).each do |method| test "request method #{method.underscore} can be matched" do - get '/', headers: { 'REQUEST_METHOD' => method } + get "/", headers: { "REQUEST_METHOD" => method } assert_equal method, @response.body end end @@ -3909,14 +4016,14 @@ end class TestUriPathEscaping < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| app.draw do - get '/:segment' => lambda { |env| - path_params = env['action_dispatch.request.path_parameters'] - [200, { 'Content-Type' => 'text/plain' }, [path_params[:segment]]] + get "/:segment" => lambda { |env| + path_params = env["action_dispatch.request.path_parameters"] + [200, { "Content-Type" => "text/plain" }, [path_params[:segment]]] }, :as => :segment - get '/*splat' => lambda { |env| - path_params = env['action_dispatch.request.path_parameters'] - [200, { 'Content-Type' => 'text/plain' }, [path_params[:splat]]] + get "/*splat" => lambda { |env| + path_params = env["action_dispatch.request.path_parameters"] + [200, { "Content-Type" => "text/plain" }, [path_params[:splat]]] }, :as => :splat end end @@ -3925,22 +4032,22 @@ class TestUriPathEscaping < ActionDispatch::IntegrationTest APP = build_app Routes def app; APP end - test 'escapes slash in generated path segment' do - assert_equal '/a%20b%2Fc+d', segment_path(:segment => 'a b/c+d') + test "escapes slash in generated path segment" do + assert_equal "/a%20b%2Fc+d", segment_path(segment: "a b/c+d") end - test 'unescapes recognized path segment' do - get '/a%20b%2Fc+d' - assert_equal 'a b/c+d', @response.body + test "unescapes recognized path segment" do + get "/a%20b%2Fc+d" + assert_equal "a b/c+d", @response.body end - test 'does not escape slash in generated path splat' do - assert_equal '/a%20b/c+d', splat_path(:splat => 'a b/c+d') + test "does not escape slash in generated path splat" do + assert_equal "/a%20b/c+d", splat_path(splat: "a b/c+d") end - test 'unescapes recognized path splat' do - get '/a%20b/c+d' - assert_equal 'a b/c+d', @response.body + test "unescapes recognized path splat" do + get "/a%20b/c+d" + assert_equal "a b/c+d", @response.body end end @@ -3948,7 +4055,7 @@ class TestUnicodePaths < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| app.draw do get "/ã»ã’" => lambda { |env| - [200, { 'Content-Type' => 'text/plain' }, []] + [200, { "Content-Type" => "text/plain" }, []] }, :as => :unicode_path end end @@ -3957,23 +4064,13 @@ class TestUnicodePaths < ActionDispatch::IntegrationTest APP = build_app Routes def app; APP end - test 'recognizes unicode path' do + test "recognizes unicode path" do get "/#{Rack::Utils.escape("ã»ã’")}" assert_equal "200", @response.code end end class TestMultipleNestedController < ActionDispatch::IntegrationTest - module ::Foo - module Bar - class BazController < ActionController::Base - def index - render :inline => "<%= url_for :controller => '/pooh', :action => 'index' %>" - end - end - end - end - Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| app.draw do namespace :foo do @@ -3985,7 +4082,18 @@ class TestMultipleNestedController < ActionDispatch::IntegrationTest end end - include Routes.url_helpers + module ::Foo + module Bar + class BazController < ActionController::Base + include Routes.url_helpers + + def index + render inline: "<%= url_for :controller => '/pooh', :action => 'index' %>" + end + end + end + end + APP = build_app Routes def app; APP end @@ -3998,7 +4106,7 @@ end class TestTildeAndMinusPaths < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| app.draw do - ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] } + ok = lambda { |env| [200, { "Content-Type" => "text/plain" }, []] } get "/~user" => ok get "/young-and-fine" => ok @@ -4009,25 +4117,24 @@ class TestTildeAndMinusPaths < ActionDispatch::IntegrationTest APP = build_app Routes def app; APP end - test 'recognizes tilde path' do + test "recognizes tilde path" do get "/~user" assert_equal "200", @response.code end - test 'recognizes minus path' do + test "recognizes minus path" do get "/young-and-fine" assert_equal "200", @response.code end - end class TestRedirectInterpolation < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| app.draw do - ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] } + ok = lambda { |env| [200, { "Content-Type" => "text/plain" }, []] } get "/foo/:id" => redirect("/foo/bar/%{id}") - get "/bar/:id" => redirect(:path => "/foo/bar/%{id}") + get "/bar/:id" => redirect(path: "/foo/bar/%{id}") get "/baz/:id" => redirect("/baz?id=%{id}&foo=?&bar=1#id-%{id}") get "/foo/bar/:id" => ok get "/baz" => ok @@ -4058,7 +4165,7 @@ class TestRedirectInterpolation < ActionDispatch::IntegrationTest private def verify_redirect(url, status=301) assert_equal status, @response.status - assert_equal url, @response.headers['Location'] + assert_equal url, @response.headers["Location"] assert_equal expected_redirect_body(url), @response.body end @@ -4070,9 +4177,9 @@ end class TestConstraintsAccessingParameters < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| app.draw do - ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] } + ok = lambda { |env| [200, { "Content-Type" => "text/plain" }, []] } - get "/:foo" => ok, :constraints => lambda { |r| r.params[:foo] == 'foo' } + get "/:foo" => ok, :constraints => lambda { |r| r.params[:foo] == "foo" } get "/:bar" => ok end end @@ -4090,9 +4197,9 @@ end class TestGlobRoutingMapper < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| app.draw do - ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] } + ok = lambda { |env| [200, { "Content-Type" => "text/plain" }, []] } - get "/*id" => redirect("/not_cars"), :constraints => {id: /dummy/} + get "/*id" => redirect("/not_cars"), :constraints => { id: /dummy/ } get "/cars" => ok end end @@ -4104,7 +4211,7 @@ class TestGlobRoutingMapper < ActionDispatch::IntegrationTest def test_glob_constraint get "/dummy" assert_equal "301", @response.code - assert_equal "/not_cars", @response.header['Location'].match('/[^/]+$')[0] + assert_equal "/not_cars", @response.header["Location"].match("/[^/]+$")[0] end def test_glob_constraint_skip_route @@ -4120,13 +4227,17 @@ end class TestOptimizedNamedRoutes < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| app.draw do - ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] } - get '/foo' => ok, as: :foo - get '/post(/:action(/:id))' => ok, as: :posts - get '/:foo/:foo_type/bars/:id' => ok, as: :bar - get '/projects/:id.:format' => ok, as: :project - get '/pages/:id' => ok, as: :page - get '/wiki/*page' => ok, as: :wiki + ok = lambda { |env| [200, { "Content-Type" => "text/plain" }, []] } + get "/foo" => ok, as: :foo + + ActiveSupport::Deprecation.silence do + get "/post(/:action(/:id))" => ok, as: :posts + end + + get "/:foo/:foo_type/bars/:id" => ok, as: :bar + get "/projects/:id.:format" => ok, as: :project + get "/pages/:id" => ok, as: :page + get "/wiki/*page" => ok, as: :wiki end end @@ -4134,51 +4245,51 @@ class TestOptimizedNamedRoutes < ActionDispatch::IntegrationTest APP = build_app Routes def app; APP end - test 'enabled when not mounted and default_url_options is empty' do + test "enabled when not mounted and default_url_options is empty" do assert Routes.url_helpers.optimize_routes_generation? end - test 'named route called as singleton method' do - assert_equal '/foo', Routes.url_helpers.foo_path + test "named route called as singleton method" do + assert_equal "/foo", Routes.url_helpers.foo_path end - test 'named route called on included module' do - assert_equal '/foo', foo_path + test "named route called on included module" do + assert_equal "/foo", foo_path end - test 'nested optional segments are removed' do - assert_equal '/post', Routes.url_helpers.posts_path - assert_equal '/post', posts_path + test "nested optional segments are removed" do + assert_equal "/post", Routes.url_helpers.posts_path + assert_equal "/post", posts_path end - test 'segments with same prefix are replaced correctly' do - assert_equal '/foo/baz/bars/1', Routes.url_helpers.bar_path('foo', 'baz', '1') - assert_equal '/foo/baz/bars/1', bar_path('foo', 'baz', '1') + test "segments with same prefix are replaced correctly" do + assert_equal "/foo/baz/bars/1", Routes.url_helpers.bar_path("foo", "baz", "1") + assert_equal "/foo/baz/bars/1", bar_path("foo", "baz", "1") end - test 'segments separated with a period are replaced correctly' do - assert_equal '/projects/1.json', Routes.url_helpers.project_path(1, :json) - assert_equal '/projects/1.json', project_path(1, :json) + test "segments separated with a period are replaced correctly" do + assert_equal "/projects/1.json", Routes.url_helpers.project_path(1, :json) + assert_equal "/projects/1.json", project_path(1, :json) end - test 'segments with question marks are escaped' do - assert_equal '/pages/foo%3Fbar', Routes.url_helpers.page_path('foo?bar') - assert_equal '/pages/foo%3Fbar', page_path('foo?bar') + test "segments with question marks are escaped" do + assert_equal "/pages/foo%3Fbar", Routes.url_helpers.page_path("foo?bar") + assert_equal "/pages/foo%3Fbar", page_path("foo?bar") end - test 'segments with slashes are escaped' do - assert_equal '/pages/foo%2Fbar', Routes.url_helpers.page_path('foo/bar') - assert_equal '/pages/foo%2Fbar', page_path('foo/bar') + test "segments with slashes are escaped" do + assert_equal "/pages/foo%2Fbar", Routes.url_helpers.page_path("foo/bar") + assert_equal "/pages/foo%2Fbar", page_path("foo/bar") end - test 'glob segments with question marks are escaped' do - assert_equal '/wiki/foo%3Fbar', Routes.url_helpers.wiki_path('foo?bar') - assert_equal '/wiki/foo%3Fbar', wiki_path('foo?bar') + test "glob segments with question marks are escaped" do + assert_equal "/wiki/foo%3Fbar", Routes.url_helpers.wiki_path("foo?bar") + assert_equal "/wiki/foo%3Fbar", wiki_path("foo?bar") end - test 'glob segments with slashes are not escaped' do - assert_equal '/wiki/foo/bar', Routes.url_helpers.wiki_path('foo/bar') - assert_equal '/wiki/foo/bar', wiki_path('foo/bar') + test "glob segments with slashes are not escaped" do + assert_equal "/wiki/foo/bar", Routes.url_helpers.wiki_path("foo/bar") + assert_equal "/wiki/foo/bar", wiki_path("foo/bar") end end @@ -4197,9 +4308,9 @@ class TestNamedRouteUrlHelpers < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| app.draw do - scope :module => "test_named_route_url_helpers" do - get "/categories/:id" => 'categories#show', :as => :category - get "/products/:id" => 'products#show', :as => :product + scope module: "test_named_route_url_helpers" do + get "/categories/:id" => "categories#show", :as => :category + get "/products/:id" => "products#show", :as => :product end end end @@ -4221,21 +4332,21 @@ end class TestUrlConstraints < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| app.draw do - ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] } + ok = lambda { |env| [200, { "Content-Type" => "text/plain" }, []] } - constraints :subdomain => 'admin' do - get '/' => ok, :as => :admin_root + constraints subdomain: "admin" do + get "/" => ok, :as => :admin_root end - scope :constraints => { :protocol => 'https://' } do - get '/' => ok, :as => :secure_root + scope constraints: { protocol: "https://" } do + get "/" => ok, :as => :secure_root end - get '/' => ok, :as => :alternate_root, :constraints => { :port => 8080 } + get "/" => ok, :as => :alternate_root, :constraints => { port: 8080 } - get '/search' => ok, :constraints => { :subdomain => false } + get "/search" => ok, :constraints => { subdomain: false } - get '/logs' => ok, :constraints => { :subdomain => true } + get "/logs" => ok, :constraints => { subdomain: true } end end @@ -4244,41 +4355,41 @@ class TestUrlConstraints < ActionDispatch::IntegrationTest def app; APP end test "constraints are copied to defaults when using constraints method" do - assert_equal 'http://admin.example.com/', admin_root_url + assert_equal "http://admin.example.com/", admin_root_url - get 'http://admin.example.com/' + get "http://admin.example.com/" assert_response :success end test "constraints are copied to defaults when using scope constraints hash" do - assert_equal 'https://www.example.com/', secure_root_url + assert_equal "https://www.example.com/", secure_root_url - get 'https://www.example.com/' + get "https://www.example.com/" assert_response :success end test "constraints are copied to defaults when using route constraints hash" do - assert_equal 'http://www.example.com:8080/', alternate_root_url + assert_equal "http://www.example.com:8080/", alternate_root_url - get 'http://www.example.com:8080/' + get "http://www.example.com:8080/" assert_response :success end test "false constraint expressions check for absence of values" do - get 'http://example.com/search' + get "http://example.com/search" assert_response :success - assert_equal 'http://example.com/search', search_url + assert_equal "http://example.com/search", search_url - get 'http://api.example.com/search' + get "http://api.example.com/search" assert_response :not_found end test "true constraint expressions check for presence of values" do - get 'http://api.example.com/logs' + get "http://api.example.com/logs" assert_response :success - assert_equal 'http://api.example.com/logs', logs_url + assert_equal "http://api.example.com/logs", logs_url - get 'http://example.com/logs' + get "http://example.com/logs" assert_response :not_found end end @@ -4293,10 +4404,16 @@ class TestInvalidUrls < ActionDispatch::IntegrationTest test "invalid UTF-8 encoding returns a 400 Bad Request" do with_routing do |set| set.draw do - get "/bar/:id", :to => redirect("/foo/show/%{id}") - get "/foo/show(/:id)", :to => "test_invalid_urls/foo#show" - get "/foo(/:action(/:id))", :controller => "test_invalid_urls/foo" - get "/:controller(/:action(/:id))" + get "/bar/:id", to: redirect("/foo/show/%{id}") + get "/foo/show(/:id)", to: "test_invalid_urls/foo#show" + + ok = lambda { |env| [200, { "Content-Type" => "text/plain" }, []] } + get "/foobar/:id", to: ok + + ActiveSupport::Deprecation.silence do + get "/foo(/:action(/:id))", controller: "test_invalid_urls/foo" + get "/:controller(/:action(/:id))" + end end get "/%E2%EF%BF%BD%A6" @@ -4310,6 +4427,9 @@ class TestInvalidUrls < ActionDispatch::IntegrationTest get "/bar/%E2%EF%BF%BD%A6" assert_response :bad_request + + get "/foobar/%E2%EF%BF%BD%A6" + assert_response :bad_request end end end @@ -4318,7 +4438,7 @@ class TestOptionalRootSegments < ActionDispatch::IntegrationTest stub_controllers do |routes| Routes = routes Routes.draw do - get '/(page/:page)', :to => 'pages#index', :as => :root + get "/(page/:page)", to: "pages#index", as: :root end end @@ -4330,27 +4450,27 @@ class TestOptionalRootSegments < ActionDispatch::IntegrationTest include Routes.url_helpers def test_optional_root_segments - get '/' - assert_equal 'pages#index', @response.body - assert_equal '/', root_path + get "/" + assert_equal "pages#index", @response.body + assert_equal "/", root_path - get '/page/1' - assert_equal 'pages#index', @response.body - assert_equal '1', @request.params[:page] - assert_equal '/page/1', root_path('1') - assert_equal '/page/1', root_path(:page => '1') + get "/page/1" + assert_equal "pages#index", @response.body + assert_equal "1", @request.params[:page] + assert_equal "/page/1", root_path("1") + assert_equal "/page/1", root_path(page: "1") end end class TestPortConstraints < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| app.draw do - ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] } + ok = lambda { |env| [200, { "Content-Type" => "text/plain" }, []] } - get '/integer', to: ok, constraints: { :port => 8080 } - get '/string', to: ok, constraints: { :port => '8080' } - get '/array', to: ok, constraints: { :port => [8080] } - get '/regexp', to: ok, constraints: { :port => /8080/ } + get "/integer", to: ok, constraints: { port: 8080 } + get "/string", to: ok, constraints: { port: "8080" } + get "/array", to: ok, constraints: { port: [8080] } + get "/regexp", to: ok, constraints: { port: /8080/ } end end @@ -4359,34 +4479,34 @@ class TestPortConstraints < ActionDispatch::IntegrationTest def app; APP end def test_integer_port_constraints - get 'http://www.example.com/integer' + get "http://www.example.com/integer" assert_response :not_found - get 'http://www.example.com:8080/integer' + get "http://www.example.com:8080/integer" assert_response :success end def test_string_port_constraints - get 'http://www.example.com/string' + get "http://www.example.com/string" assert_response :not_found - get 'http://www.example.com:8080/string' + get "http://www.example.com:8080/string" assert_response :success end def test_array_port_constraints - get 'http://www.example.com/array' + get "http://www.example.com/array" assert_response :not_found - get 'http://www.example.com:8080/array' + get "http://www.example.com:8080/array" assert_response :success end def test_regexp_port_constraints - get 'http://www.example.com/regexp' + get "http://www.example.com/regexp" assert_response :not_found - get 'http://www.example.com:8080/regexp' + get "http://www.example.com:8080/regexp" assert_response :success end end @@ -4394,12 +4514,12 @@ end class TestFormatConstraints < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| app.draw do - ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] } + ok = lambda { |env| [200, { "Content-Type" => "text/plain" }, []] } - get '/string', to: ok, constraints: { format: 'json' } - get '/regexp', to: ok, constraints: { format: /json/ } - get '/json_only', to: ok, format: true, constraints: { format: /json/ } - get '/xml_only', to: ok, format: 'xml' + get "/string", to: ok, constraints: { format: "json" } + get "/regexp", to: ok, constraints: { format: /json/ } + get "/json_only", to: ok, format: true, constraints: { format: /json/ } + get "/xml_only", to: ok, format: "xml" end end @@ -4408,46 +4528,46 @@ class TestFormatConstraints < ActionDispatch::IntegrationTest def app; APP end def test_string_format_constraints - get 'http://www.example.com/string' + get "http://www.example.com/string" assert_response :success - get 'http://www.example.com/string.json' + get "http://www.example.com/string.json" assert_response :success - get 'http://www.example.com/string.html' + get "http://www.example.com/string.html" assert_response :not_found end def test_regexp_format_constraints - get 'http://www.example.com/regexp' + get "http://www.example.com/regexp" assert_response :success - get 'http://www.example.com/regexp.json' + get "http://www.example.com/regexp.json" assert_response :success - get 'http://www.example.com/regexp.html' + get "http://www.example.com/regexp.html" assert_response :not_found end def test_enforce_with_format_true_with_constraint - get 'http://www.example.com/json_only.json' + get "http://www.example.com/json_only.json" assert_response :success - get 'http://www.example.com/json_only.html' + get "http://www.example.com/json_only.html" assert_response :not_found - get 'http://www.example.com/json_only' + get "http://www.example.com/json_only" assert_response :not_found end def test_enforce_with_string - get 'http://www.example.com/xml_only.xml' + get "http://www.example.com/xml_only.xml" assert_response :success - get 'http://www.example.com/xml_only' + get "http://www.example.com/xml_only" assert_response :success - get 'http://www.example.com/xml_only.json' + get "http://www.example.com/xml_only.json" assert_response :not_found end end @@ -4456,8 +4576,8 @@ class TestCallableConstraintValidation < ActionDispatch::IntegrationTest def test_constraint_with_object_not_callable assert_raises(ArgumentError) do ActionDispatch::Routing::RouteSet.new.draw do - ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] } - get '/test', to: ok, constraints: Object.new + ok = lambda { |env| [200, { "Content-Type" => "text/plain" }, []] } + get "/test", to: ok, constraints: Object.new end end end @@ -4467,8 +4587,8 @@ class TestRouteDefaults < ActionDispatch::IntegrationTest stub_controllers do |routes| Routes = routes Routes.draw do - resources :posts, bucket_type: 'post' - resources :projects, defaults: { bucket_type: 'project' } + resources :posts, bucket_type: "post" + resources :projects, defaults: { bucket_type: "project" } end end @@ -4481,14 +4601,14 @@ class TestRouteDefaults < ActionDispatch::IntegrationTest def test_route_options_are_required_for_url_for assert_raises(ActionController::UrlGenerationError) do - assert_equal '/posts/1', url_for(controller: 'posts', action: 'show', id: 1, only_path: true) + assert_equal "/posts/1", url_for(controller: "posts", action: "show", id: 1, only_path: true) end - assert_equal '/posts/1', url_for(controller: 'posts', action: 'show', id: 1, bucket_type: 'post', only_path: true) + assert_equal "/posts/1", url_for(controller: "posts", action: "show", id: 1, bucket_type: "post", only_path: true) end def test_route_defaults_are_not_required_for_url_for - assert_equal '/projects/1', url_for(controller: 'projects', action: 'show', id: 1, only_path: true) + assert_equal "/projects/1", url_for(controller: "projects", action: "show", id: 1, only_path: true) end end @@ -4496,9 +4616,9 @@ class TestRackAppRouteGeneration < ActionDispatch::IntegrationTest stub_controllers do |routes| Routes = routes Routes.draw do - rack_app = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] } - mount rack_app, at: '/account', as: 'account' - mount rack_app, at: '/:locale/account', as: 'localized_account' + rack_app = lambda { |env| [200, { "Content-Type" => "text/plain" }, []] } + mount rack_app, at: "/account", as: "account" + mount rack_app, at: "/:locale/account", as: "localized_account" end end @@ -4511,11 +4631,11 @@ class TestRackAppRouteGeneration < ActionDispatch::IntegrationTest def test_mounted_application_doesnt_match_unnamed_route assert_raise(ActionController::UrlGenerationError) do - assert_equal '/account?controller=products', url_for(controller: 'products', action: 'index', only_path: true) + assert_equal "/account?controller=products", url_for(controller: "products", action: "index", only_path: true) end assert_raise(ActionController::UrlGenerationError) do - assert_equal '/de/account?controller=products', url_for(controller: 'products', action: 'index', :locale => 'de', only_path: true) + assert_equal "/de/account?controller=products", url_for(controller: "products", action: "index", locale: "de", only_path: true) end end end @@ -4524,8 +4644,8 @@ class TestRedirectRouteGeneration < ActionDispatch::IntegrationTest stub_controllers do |routes| Routes = routes Routes.draw do - get '/account', to: redirect('/myaccount'), as: 'account' - get '/:locale/account', to: redirect('/%{locale}/myaccount'), as: 'localized_account' + get "/account", to: redirect("/myaccount"), as: "account" + get "/:locale/account", to: redirect("/%{locale}/myaccount"), as: "localized_account" end end @@ -4538,11 +4658,11 @@ class TestRedirectRouteGeneration < ActionDispatch::IntegrationTest def test_redirect_doesnt_match_unnamed_route assert_raise(ActionController::UrlGenerationError) do - assert_equal '/account?controller=products', url_for(controller: 'products', action: 'index', only_path: true) + assert_equal "/account?controller=products", url_for(controller: "products", action: "index", only_path: true) end assert_raise(ActionController::UrlGenerationError) do - assert_equal '/de/account?controller=products', url_for(controller: 'products', action: 'index', :locale => 'de', only_path: true) + assert_equal "/de/account?controller=products", url_for(controller: "products", action: "index", locale: "de", only_path: true) end end end @@ -4550,7 +4670,7 @@ end class TestUrlGenerationErrors < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| app.draw do - get "/products/:id" => 'products#show', :as => :product + get "/products/:id" => "products#show", :as => :product end end @@ -4559,29 +4679,32 @@ class TestUrlGenerationErrors < ActionDispatch::IntegrationTest include Routes.url_helpers - test "url helpers raise a helpful error message when generation fails" do - url, missing = { action: 'show', controller: 'products', id: nil }, [:id] - message = "No route matches #{url.inspect} missing required keys: #{missing.inspect}" + test "url helpers raise a 'missing keys' error for a nil param with optimized helpers" do + url, missing = { action: "show", controller: "products", id: nil }, [:id] + message = "No route matches #{url.inspect}, missing required keys: #{missing.inspect}" - # Optimized url helper - error = assert_raises(ActionController::UrlGenerationError){ product_path(nil) } + error = assert_raises(ActionController::UrlGenerationError) { product_path(nil) } assert_equal message, error.message + end - # Non-optimized url helper - error = assert_raises(ActionController::UrlGenerationError, message){ product_path(id: nil) } + test "url helpers raise a 'constraint failure' error for a nil param with non-optimized helpers" do + url, missing = { action: "show", controller: "products", id: nil }, [:id] + message = "No route matches #{url.inspect}, possible unmatched constraints: #{missing.inspect}" + + error = assert_raises(ActionController::UrlGenerationError, message) { product_path(id: nil) } assert_equal message, error.message end - test "url helpers raise message with mixed parameters when generation fails " do - url, missing = { action: 'show', controller: 'products', id: nil, "id"=>"url-tested"}, [:id] - message = "No route matches #{url.inspect} missing required keys: #{missing.inspect}" + test "url helpers raise message with mixed parameters when generation fails" do + url, missing = { action: "show", controller: "products", id: nil, "id"=>"url-tested" }, [:id] + message = "No route matches #{url.inspect}, possible unmatched constraints: #{missing.inspect}" # Optimized url helper - error = assert_raises(ActionController::UrlGenerationError){ product_path(nil, 'id'=>'url-tested') } + error = assert_raises(ActionController::UrlGenerationError) { product_path(nil, "id"=>"url-tested") } assert_equal message, error.message # Non-optimized url helper - error = assert_raises(ActionController::UrlGenerationError, message){ product_path(id: nil, 'id'=>'url-tested') } + error = assert_raises(ActionController::UrlGenerationError, message) { product_path(id: nil, "id"=>"url-tested") } assert_equal message, error.message end end @@ -4595,9 +4718,9 @@ class TestDefaultUrlOptions < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new Routes.draw do - default_url_options locale: 'en' - scope ':locale', format: false do - get '/posts/:year/:month/:day', to: 'posts#archive', as: 'archived_posts' + default_url_options locale: "en" + scope ":locale", format: false do + get "/posts/:year/:month/:day", to: "posts#archive", as: "archived_posts" end end @@ -4610,7 +4733,7 @@ class TestDefaultUrlOptions < ActionDispatch::IntegrationTest include Routes.url_helpers def test_positional_args_with_format_false - assert_equal '/en/posts/2014/12/13', archived_posts_path(2014, 12, 13) + assert_equal "/en/posts/2014/12/13", archived_posts_path(2014, 12, 13) end end @@ -4627,7 +4750,9 @@ class TestErrorsInController < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new Routes.draw do - get '/:controller(/:action)' + ActiveSupport::Deprecation.silence do + get "/:controller(/:action)" + end end APP = build_app Routes @@ -4637,20 +4762,20 @@ class TestErrorsInController < ActionDispatch::IntegrationTest end def test_legit_no_method_errors_are_not_caught - get '/posts/foo' + get "/posts/foo" assert_equal 500, response.status end def test_legit_name_errors_are_not_caught - get '/posts/bar' + get "/posts/bar" assert_equal 500, response.status end def test_legit_routing_not_found_responses - get '/posts/baz' + get "/posts/baz" assert_equal 404, response.status - get '/i_do_not_exist' + get "/i_do_not_exist" assert_equal 404, response.status end end @@ -4658,17 +4783,17 @@ end class TestPartialDynamicPathSegments < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new Routes.draw do - ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] } + ok = lambda { |env| [200, { "Content-Type" => "text/plain" }, []] } - get '/songs/song-:song', to: ok - get '/songs/:song-song', to: ok - get '/:artist/song-:song', to: ok - get '/:artist/:song-song', to: ok + get "/songs/song-:song", to: ok + get "/songs/:song-song", to: ok + get "/:artist/song-:song", to: ok + get "/:artist/:song-song", to: ok - get '/optional/songs(/song-:song)', to: ok - get '/optional/songs(/:song-song)', to: ok - get '/optional/:artist(/song-:song)', to: ok - get '/optional/:artist(/:song-song)', to: ok + get "/optional/songs(/song-:song)", to: ok + get "/optional/songs(/:song-song)", to: ok + get "/optional/:artist(/song-:song)", to: ok + get "/optional/:artist(/:song-song)", to: ok end APP = build_app Routes @@ -4678,42 +4803,113 @@ class TestPartialDynamicPathSegments < ActionDispatch::IntegrationTest end def test_paths_with_partial_dynamic_segments_are_recognised - get '/david-bowie/changes-song' + get "/david-bowie/changes-song" assert_equal 200, response.status - assert_params artist: 'david-bowie', song: 'changes' + assert_params artist: "david-bowie", song: "changes" - get '/david-bowie/song-changes' + get "/david-bowie/song-changes" assert_equal 200, response.status - assert_params artist: 'david-bowie', song: 'changes' + assert_params artist: "david-bowie", song: "changes" - get '/songs/song-changes' + get "/songs/song-changes" assert_equal 200, response.status - assert_params song: 'changes' + assert_params song: "changes" - get '/songs/changes-song' + get "/songs/changes-song" assert_equal 200, response.status - assert_params song: 'changes' + assert_params song: "changes" - get '/optional/songs/song-changes' + get "/optional/songs/song-changes" assert_equal 200, response.status - assert_params song: 'changes' + assert_params song: "changes" - get '/optional/songs/changes-song' + get "/optional/songs/changes-song" assert_equal 200, response.status - assert_params song: 'changes' + assert_params song: "changes" - get '/optional/david-bowie/changes-song' + get "/optional/david-bowie/changes-song" assert_equal 200, response.status - assert_params artist: 'david-bowie', song: 'changes' + assert_params artist: "david-bowie", song: "changes" - get '/optional/david-bowie/song-changes' + get "/optional/david-bowie/song-changes" assert_equal 200, response.status - assert_params artist: 'david-bowie', song: 'changes' + assert_params artist: "david-bowie", song: "changes" end private - def assert_params(params) - assert_equal(params, request.path_parameters) + def assert_params(params) + assert_equal(params, request.path_parameters) + end +end + +class TestPathParameters < ActionDispatch::IntegrationTest + Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| + app.draw do + scope module: "test_path_parameters" do + scope ":locale", locale: /en|ar/ do + root to: "home#index" + get "/about", to: "pages#about" + end + end + + ActiveSupport::Deprecation.silence do + get ":controller(/:action/(:id))" + end + end + end + + class HomeController < ActionController::Base + include Routes.url_helpers + + def index + render inline: "<%= root_path %>" + end + end + + class PagesController < ActionController::Base + include Routes.url_helpers + + def about + render inline: "<%= root_path(locale: :ar) %> | <%= url_for(locale: :ar) %>" + end + end + + APP = build_app Routes + def app; APP end + + def test_path_parameters_are_not_mutated + get "/en/about" + assert_equal "/ar | /ar/about", @response.body + end +end + +class TestInternalRoutingParams < ActionDispatch::IntegrationTest + Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| + app.draw do + get "/test_internal/:internal" => "internal#internal" + end + end + + class ::InternalController < ActionController::Base + def internal + head :ok + end + end + + APP = build_app Routes + + def app + APP + end + + def test_paths_with_partial_dynamic_segments_are_recognised + get "/test_internal/123" + assert_equal 200, response.status + + assert_equal( + { controller: "internal", action: "internal", internal: "123" }, + request.path_parameters + ) end end diff --git a/actionpack/test/dispatch/runner_test.rb b/actionpack/test/dispatch/runner_test.rb new file mode 100644 index 0000000000..969933c9ed --- /dev/null +++ b/actionpack/test/dispatch/runner_test.rb @@ -0,0 +1,18 @@ +require "abstract_unit" + +class RunnerTest < ActiveSupport::TestCase + test "runner preserves the setting of integration_session" do + runner = Class.new do + def before_setup + + end + end.new + + runner.extend(ActionDispatch::Integration::Runner) + runner.integration_session.host! "lvh.me" + + runner.before_setup + + assert_equal "lvh.me", runner.integration_session.host + end +end diff --git a/actionpack/test/dispatch/session/abstract_store_test.rb b/actionpack/test/dispatch/session/abstract_store_test.rb index d38d1bbce6..fd4d359cf8 100644 --- a/actionpack/test/dispatch/session/abstract_store_test.rb +++ b/actionpack/test/dispatch/session/abstract_store_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'action_dispatch/middleware/session/abstract_store' +require "abstract_unit" +require "action_dispatch/middleware/session/abstract_store" module ActionDispatch module Session @@ -37,7 +37,7 @@ module ActionDispatch assert @env session = Request::Session.find ActionDispatch::Request.new @env - session['foo'] = 'bar' + session["foo"] = "bar" as.call(@env) session1 = Request::Session.find ActionDispatch::Request.new @env @@ -47,10 +47,10 @@ module ActionDispatch end private - def app(&block) - @env = nil - lambda { |env| @env = env } - end + def app(&block) + @env = nil + lambda { |env| @env = env } + end end end end diff --git a/actionpack/test/dispatch/session/cache_store_test.rb b/actionpack/test/dispatch/session/cache_store_test.rb index dbb996973d..a60629a7ee 100644 --- a/actionpack/test/dispatch/session/cache_store_test.rb +++ b/actionpack/test/dispatch/session/cache_store_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'fixtures/session_autoload_test/session_autoload_test/foo' +require "abstract_unit" +require "fixtures/session_autoload_test/session_autoload_test/foo" class CacheStoreTest < ActionDispatch::IntegrationTest class TestController < ActionController::Base @@ -35,11 +35,11 @@ class CacheStoreTest < ActionDispatch::IntegrationTest def test_setting_and_getting_session_value with_test_route_set do - get '/set_session_value' + get "/set_session_value" assert_response :success - assert cookies['_session_id'] + assert cookies["_session_id"] - get '/get_session_value' + get "/get_session_value" assert_response :success assert_equal 'foo: "bar"', response.body end @@ -47,56 +47,56 @@ class CacheStoreTest < ActionDispatch::IntegrationTest def test_getting_nil_session_value with_test_route_set do - get '/get_session_value' + get "/get_session_value" assert_response :success - assert_equal 'foo: nil', response.body + assert_equal "foo: nil", response.body end end def test_getting_session_value_after_session_reset with_test_route_set do - get '/set_session_value' + get "/set_session_value" assert_response :success - assert cookies['_session_id'] - session_cookie = cookies.send(:hash_for)['_session_id'] + assert cookies["_session_id"] + session_cookie = cookies.send(:hash_for)["_session_id"] - get '/call_reset_session' + get "/call_reset_session" assert_response :success - assert_not_equal [], headers['Set-Cookie'] + assert_not_equal [], headers["Set-Cookie"] cookies << session_cookie # replace our new session_id with our old, pre-reset session_id - get '/get_session_value' + get "/get_session_value" assert_response :success - assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from cache" + assert_equal "foo: nil", response.body, "data for this session should have been obliterated from cache" end end def test_getting_from_nonexistent_session with_test_route_set do - get '/get_session_value' + get "/get_session_value" assert_response :success - assert_equal 'foo: nil', response.body - assert_nil cookies['_session_id'], "should only create session on write, not read" + assert_equal "foo: nil", response.body + assert_nil cookies["_session_id"], "should only create session on write, not read" end end def test_setting_session_value_after_session_reset with_test_route_set do - get '/set_session_value' + get "/set_session_value" assert_response :success - assert cookies['_session_id'] - session_id = cookies['_session_id'] + assert cookies["_session_id"] + session_id = cookies["_session_id"] - get '/call_reset_session' + get "/call_reset_session" assert_response :success - assert_not_equal [], headers['Set-Cookie'] + assert_not_equal [], headers["Set-Cookie"] - get '/get_session_value' + get "/get_session_value" assert_response :success - assert_equal 'foo: nil', response.body + assert_equal "foo: nil", response.body - get '/get_session_id' + get "/get_session_id" assert_response :success assert_not_equal session_id, response.body end @@ -104,12 +104,12 @@ class CacheStoreTest < ActionDispatch::IntegrationTest def test_getting_session_id with_test_route_set do - get '/set_session_value' + get "/set_session_value" assert_response :success - assert cookies['_session_id'] - session_id = cookies['_session_id'] + assert cookies["_session_id"] + session_id = cookies["_session_id"] - get '/get_session_id' + get "/get_session_id" assert_response :success assert_equal session_id, response.body, "should be able to read session id without accessing the session hash" end @@ -118,16 +118,16 @@ class CacheStoreTest < ActionDispatch::IntegrationTest def test_deserializes_unloaded_class with_test_route_set do with_autoload_path "session_autoload_test" do - get '/set_serialized_session_value' + get "/set_serialized_session_value" assert_response :success - assert cookies['_session_id'] + assert cookies["_session_id"] end with_autoload_path "session_autoload_test" do - get '/get_session_id' + get "/get_session_id" assert_response :success end with_autoload_path "session_autoload_test" do - get '/get_session_value' + get "/get_session_value" assert_response :success assert_equal 'foo: #<SessionAutoloadTest::Foo bar:"baz">', response.body, "should auto-load unloaded class" end @@ -136,27 +136,27 @@ class CacheStoreTest < ActionDispatch::IntegrationTest def test_doesnt_write_session_cookie_if_session_id_is_already_exists with_test_route_set do - get '/set_session_value' + get "/set_session_value" assert_response :success - assert cookies['_session_id'] + assert cookies["_session_id"] - get '/get_session_value' + 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_equal 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_equal nil, @cache.read("_session_id:0xhax") - cookies['_session_id'] = '0xhax' - get '/set_session_value' + 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_equal({'foo' => 'bar'}, @cache.read("_session_id:#{cookies['_session_id']}")) + assert_not_equal "0xhax", cookies["_session_id"] + assert_equal nil, @cache.read("_session_id:0xhax") + assert_equal({ "foo" => "bar" }, @cache.read("_session_id:#{cookies['_session_id']}")) end end @@ -164,12 +164,14 @@ class CacheStoreTest < ActionDispatch::IntegrationTest def with_test_route_set with_routing do |set| set.draw do - get ':action', :to => ::CacheStoreTest::TestController + ActiveSupport::Deprecation.silence do + get ":action", to: ::CacheStoreTest::TestController + end end @app = self.class.build_app(set) do |middleware| @cache = ActiveSupport::Cache::MemoryStore.new - middleware.use ActionDispatch::Session::CacheStore, :key => '_session_id', :cache => @cache + middleware.use ActionDispatch::Session::CacheStore, key: "_session_id", cache: @cache middleware.delete ActionDispatch::ShowExceptions end diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb index f07e215e3a..013d289c6d 100644 --- a/actionpack/test/dispatch/session/cookie_store_test.rb +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -1,14 +1,14 @@ -require 'abstract_unit' -require 'stringio' -require 'active_support/key_generator' +require "abstract_unit" +require "stringio" +require "active_support/key_generator" class CookieStoreTest < ActionDispatch::IntegrationTest - SessionKey = '_myapp_session' - SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33' + SessionKey = "_myapp_session" + SessionSecret = "b3c631c314c0bbca50c1b2843150fe33" Generator = ActiveSupport::LegacyKeyGenerator.new(SessionSecret) - Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, :digest => 'SHA1') - SignedBar = Verifier.generate(:foo => "bar", :session_id => SecureRandom.hex(16)) + Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, digest: "SHA1") + SignedBar = Verifier.generate(foo: "bar", session_id: SecureRandom.hex(16)) class TestController < ActionController::Base def no_session_access @@ -48,7 +48,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest end def raise_data_overflow - session[:foo] = 'bye!' * 1024 + session[:foo] = "bye!" * 1024 head :ok end @@ -65,17 +65,17 @@ class CookieStoreTest < ActionDispatch::IntegrationTest def test_setting_session_value with_test_route_set do - get '/set_session_value' + get "/set_session_value" assert_response :success assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly", - headers['Set-Cookie'] + headers["Set-Cookie"] end end def test_getting_session_value with_test_route_set do cookies[SessionKey] = SignedBar - get '/get_session_value' + get "/get_session_value" assert_response :success assert_equal 'foo: "bar"', response.body end @@ -84,12 +84,12 @@ class CookieStoreTest < ActionDispatch::IntegrationTest def test_getting_session_id with_test_route_set do cookies[SessionKey] = SignedBar - get '/persistent_session_id' + get "/persistent_session_id" assert_response :success assert_equal 32, response.body.size session_id = response.body - get '/get_session_id' + get "/get_session_id" assert_response :success assert_equal "id: #{session_id}", response.body, "should be able to read session id without accessing the session hash" end @@ -98,37 +98,37 @@ class CookieStoreTest < ActionDispatch::IntegrationTest def test_disregards_tampered_sessions with_test_route_set do cookies[SessionKey] = "BAh7BjoIZm9vIghiYXI%3D--123456780" - get '/get_session_value' + get "/get_session_value" assert_response :success - assert_equal 'foo: nil', response.body + assert_equal "foo: nil", response.body end end def test_does_not_set_secure_cookies_over_http - with_test_route_set(:secure => true) do - get '/set_session_value' + with_test_route_set(secure: true) do + get "/set_session_value" assert_response :success - assert_equal nil, headers['Set-Cookie'] + assert_equal nil, headers["Set-Cookie"] end end def test_properly_renew_cookies with_test_route_set do - get '/set_session_value' - get '/persistent_session_id' + get "/set_session_value" + get "/persistent_session_id" session_id = response.body - get '/renew_session_id' - get '/persistent_session_id' + get "/renew_session_id" + get "/persistent_session_id" assert_not_equal response.body, session_id end end def test_does_set_secure_cookies_over_https - with_test_route_set(:secure => true) do - get '/set_session_value', headers: { 'HTTPS' => 'on' } + with_test_route_set(secure: true) do + get "/set_session_value", headers: { "HTTPS" => "on" } assert_response :success assert_equal "_myapp_session=#{response.body}; path=/; secure; HttpOnly", - headers['Set-Cookie'] + headers["Set-Cookie"] end end @@ -139,9 +139,9 @@ class CookieStoreTest < ActionDispatch::IntegrationTest with_test_route_set do with_autoload_path "session_autoload_test" do cookies[SessionKey] = SignedSerializedCookie - get '/get_session_id' + get "/get_session_id" assert_response :success - assert_equal 'id: ce8b0752a6ab7c7af3cdb8a80e6b9e46', response.body, "should auto-load unloaded class" + assert_equal "id: ce8b0752a6ab7c7af3cdb8a80e6b9e46", response.body, "should auto-load unloaded class" end end end @@ -150,7 +150,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest with_test_route_set do with_autoload_path "session_autoload_test" do cookies[SessionKey] = SignedSerializedCookie - get '/get_session_value' + get "/get_session_value" assert_response :success assert_equal 'foo: #<SessionAutoloadTest::Foo bar:"baz">', response.body, "should auto-load unloaded class" end @@ -160,16 +160,16 @@ class CookieStoreTest < ActionDispatch::IntegrationTest def test_close_raises_when_data_overflows with_test_route_set do assert_raise(ActionDispatch::Cookies::CookieOverflow) { - get '/raise_data_overflow' + get "/raise_data_overflow" } end end def test_doesnt_write_session_cookie_if_session_is_not_accessed with_test_route_set do - get '/no_session_access' + get "/no_session_access" assert_response :success - assert_equal nil, headers['Set-Cookie'] + assert_equal nil, headers["Set-Cookie"] end end @@ -177,82 +177,82 @@ class CookieStoreTest < ActionDispatch::IntegrationTest with_test_route_set do cookies[SessionKey] = "BAh7BjoIZm9vIghiYXI%3D--" + "fef868465920f415f2c0652d6910d3af288a0367" - get '/no_session_access' + get "/no_session_access" assert_response :success - assert_equal nil, headers['Set-Cookie'] + assert_equal nil, headers["Set-Cookie"] end end def test_setting_session_value_after_session_reset with_test_route_set do - get '/set_session_value' + get "/set_session_value" assert_response :success session_payload = response.body assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly", - headers['Set-Cookie'] + headers["Set-Cookie"] - get '/call_reset_session' + get "/call_reset_session" assert_response :success - assert_not_equal [], headers['Set-Cookie'] + assert_not_equal [], headers["Set-Cookie"] assert_not_nil session_payload assert_not_equal session_payload, cookies[SessionKey] - get '/get_session_value' + get "/get_session_value" assert_response :success - assert_equal 'foo: nil', response.body + assert_equal "foo: nil", response.body end end def test_class_type_after_session_reset with_test_route_set do - get '/set_session_value' + get "/set_session_value" assert_response :success assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly", - headers['Set-Cookie'] + headers["Set-Cookie"] - get '/get_class_after_reset_session' + get "/get_class_after_reset_session" assert_response :success - assert_not_equal [], headers['Set-Cookie'] - assert_equal 'class: ActionDispatch::Request::Session', response.body + assert_not_equal [], headers["Set-Cookie"] + assert_equal "class: ActionDispatch::Request::Session", response.body end end def test_getting_from_nonexistent_session with_test_route_set do - get '/get_session_value' + get "/get_session_value" assert_response :success - assert_equal 'foo: nil', response.body - assert_nil headers['Set-Cookie'], "should only create session on write, not read" + assert_equal "foo: nil", response.body + assert_nil headers["Set-Cookie"], "should only create session on write, not read" end end def test_setting_session_value_after_session_clear with_test_route_set do - get '/set_session_value' + get "/set_session_value" assert_response :success assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly", - headers['Set-Cookie'] + headers["Set-Cookie"] - get '/call_session_clear' + get "/call_session_clear" assert_response :success - get '/get_session_value' + get "/get_session_value" assert_response :success - assert_equal 'foo: nil', response.body + assert_equal "foo: nil", response.body end end def test_persistent_session_id with_test_route_set do cookies[SessionKey] = SignedBar - get '/persistent_session_id' + get "/persistent_session_id" assert_response :success assert_equal 32, response.body.size session_id = response.body - get '/persistent_session_id' + get "/persistent_session_id" assert_equal session_id, response.body reset! - get '/persistent_session_id' + get "/persistent_session_id" assert_not_equal session_id, response.body end end @@ -271,7 +271,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest end def test_session_store_with_expire_after - with_test_route_set(:expire_after => 5.hours) do + with_test_route_set(expire_after: 5.hours) do # First request accesses the session time = Time.local(2008, 4, 24) cookie_body = nil @@ -281,12 +281,12 @@ class CookieStoreTest < ActionDispatch::IntegrationTest cookies[SessionKey] = SignedBar - get '/set_session_value' + get "/set_session_value" assert_response :success cookie_body = response.body assert_equal "_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; HttpOnly", - headers['Set-Cookie'] + headers["Set-Cookie"] end # Second request does not access the session @@ -294,41 +294,41 @@ class CookieStoreTest < ActionDispatch::IntegrationTest Time.stub :now, time do expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d %b %Y %H:%M:%S -0000") - get '/no_session_access' + get "/no_session_access" assert_response :success assert_equal "_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; HttpOnly", - headers['Set-Cookie'] + headers["Set-Cookie"] end end end def test_session_store_with_explicit_domain - with_test_route_set(:domain => "example.es") do - get '/set_session_value' - assert_match(/domain=example\.es/, headers['Set-Cookie']) - headers['Set-Cookie'] + with_test_route_set(domain: "example.es") do + get "/set_session_value" + assert_match(/domain=example\.es/, headers["Set-Cookie"]) + headers["Set-Cookie"] end end def test_session_store_without_domain with_test_route_set do - get '/set_session_value' - assert_no_match(/domain\=/, headers['Set-Cookie']) + get "/set_session_value" + assert_no_match(/domain\=/, headers["Set-Cookie"]) end end def test_session_store_with_nil_domain - with_test_route_set(:domain => nil) do - get '/set_session_value' - assert_no_match(/domain\=/, headers['Set-Cookie']) + with_test_route_set(domain: nil) do + get "/set_session_value" + assert_no_match(/domain\=/, headers["Set-Cookie"]) end end def test_session_store_with_all_domains - with_test_route_set(:domain => :all) do - get '/set_session_value' - assert_match(/domain=\.example\.com/, headers['Set-Cookie']) + with_test_route_set(domain: :all) do + get "/set_session_value" + assert_match(/domain=\.example\.com/, headers["Set-Cookie"]) end end @@ -345,10 +345,12 @@ class CookieStoreTest < ActionDispatch::IntegrationTest def with_test_route_set(options = {}) with_routing do |set| set.draw do - get ':action', :to => ::CookieStoreTest::TestController + ActiveSupport::Deprecation.silence do + get ":action", to: ::CookieStoreTest::TestController + end end - options = { :key => SessionKey }.merge!(options) + options = { key: SessionKey }.merge!(options) @app = self.class.build_app(set) do |middleware| middleware.use ActionDispatch::Session::CookieStore, options @@ -358,5 +360,4 @@ class CookieStoreTest < ActionDispatch::IntegrationTest yield end 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 3fed9bad4f..c2d0719b4e 100644 --- a/actionpack/test/dispatch/session/mem_cache_store_test.rb +++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'securerandom' +require "abstract_unit" +require "securerandom" # You need to start a memcached server inorder to run these tests class MemCacheStoreTest < ActionDispatch::IntegrationTest @@ -35,17 +35,17 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest end begin - require 'dalli' - ss = Dalli::Client.new('localhost:11211').stats - raise Dalli::DalliError unless ss['localhost:11211'] + require "dalli" + ss = Dalli::Client.new("localhost:11211").stats + raise Dalli::DalliError unless ss["localhost:11211"] def test_setting_and_getting_session_value with_test_route_set do - get '/set_session_value' + get "/set_session_value" assert_response :success - assert cookies['_session_id'] + assert cookies["_session_id"] - get '/get_session_value' + get "/get_session_value" assert_response :success assert_equal 'foo: "bar"', response.body end @@ -55,9 +55,9 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest def test_getting_nil_session_value with_test_route_set do - get '/get_session_value' + get "/get_session_value" assert_response :success - assert_equal 'foo: nil', response.body + assert_equal "foo: nil", response.body end rescue Dalli::RingError => ex skip ex.message, ex.backtrace @@ -65,20 +65,20 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest def test_getting_session_value_after_session_reset with_test_route_set do - get '/set_session_value' + get "/set_session_value" assert_response :success - assert cookies['_session_id'] - session_cookie = cookies.send(:hash_for)['_session_id'] + assert cookies["_session_id"] + session_cookie = cookies.send(:hash_for)["_session_id"] - get '/call_reset_session' + get "/call_reset_session" assert_response :success - assert_not_equal [], headers['Set-Cookie'] + assert_not_equal [], headers["Set-Cookie"] cookies << session_cookie # replace our new session_id with our old, pre-reset session_id - get '/get_session_value' + get "/get_session_value" assert_response :success - assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from memcached" + assert_equal "foo: nil", response.body, "data for this session should have been obliterated from memcached" end rescue Dalli::RingError => ex skip ex.message, ex.backtrace @@ -86,10 +86,10 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest def test_getting_from_nonexistent_session with_test_route_set do - get '/get_session_value' + get "/get_session_value" assert_response :success - assert_equal 'foo: nil', response.body - assert_nil cookies['_session_id'], "should only create session on write, not read" + assert_equal "foo: nil", response.body + assert_nil cookies["_session_id"], "should only create session on write, not read" end rescue Dalli::RingError => ex skip ex.message, ex.backtrace @@ -97,20 +97,20 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest def test_setting_session_value_after_session_reset with_test_route_set do - get '/set_session_value' + get "/set_session_value" assert_response :success - assert cookies['_session_id'] - session_id = cookies['_session_id'] + assert cookies["_session_id"] + session_id = cookies["_session_id"] - get '/call_reset_session' + get "/call_reset_session" assert_response :success - assert_not_equal [], headers['Set-Cookie'] + assert_not_equal [], headers["Set-Cookie"] - get '/get_session_value' + get "/get_session_value" assert_response :success - assert_equal 'foo: nil', response.body + assert_equal "foo: nil", response.body - get '/get_session_id' + get "/get_session_id" assert_response :success assert_not_equal session_id, response.body end @@ -120,12 +120,12 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest def test_getting_session_id with_test_route_set do - get '/set_session_value' + get "/set_session_value" assert_response :success - assert cookies['_session_id'] - session_id = cookies['_session_id'] + assert cookies["_session_id"] + session_id = cookies["_session_id"] - get '/get_session_id' + get "/get_session_id" assert_response :success assert_equal session_id, response.body, "should be able to read session id without accessing the session hash" end @@ -136,12 +136,12 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest def test_deserializes_unloaded_class with_test_route_set do with_autoload_path "session_autoload_test" do - get '/set_serialized_session_value' + get "/set_serialized_session_value" assert_response :success - assert cookies['_session_id'] + assert cookies["_session_id"] end with_autoload_path "session_autoload_test" do - get '/get_session_id' + get "/get_session_id" assert_response :success end end @@ -151,13 +151,13 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest def test_doesnt_write_session_cookie_if_session_id_is_already_exists with_test_route_set do - get '/set_session_value' + get "/set_session_value" assert_response :success - assert cookies['_session_id'] + assert cookies["_session_id"] - get '/get_session_value' + 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_equal 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 @@ -165,16 +165,16 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest def test_prevents_session_fixation with_test_route_set do - get '/get_session_value' + get "/get_session_value" assert_response :success - assert_equal 'foo: nil', response.body - session_id = cookies['_session_id'] + assert_equal "foo: nil", response.body + session_id = cookies["_session_id"] reset! - get '/set_session_value', params: { _session_id: session_id } + get "/set_session_value", params: { _session_id: session_id } assert_response :success - assert_not_equal session_id, cookies['_session_id'] + assert_not_equal session_id, cookies["_session_id"] end rescue Dalli::RingError => ex skip ex.message, ex.backtrace @@ -187,11 +187,13 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest def with_test_route_set with_routing do |set| set.draw do - get ':action', :to => ::MemCacheStoreTest::TestController + ActiveSupport::Deprecation.silence do + get ":action", to: ::MemCacheStoreTest::TestController + end end @app = self.class.build_app(set) do |middleware| - middleware.use ActionDispatch::Session::MemCacheStore, :key => '_session_id', :namespace => "mem_cache_store_test:#{SecureRandom.hex(10)}" + middleware.use ActionDispatch::Session::MemCacheStore, key: "_session_id", namespace: "mem_cache_store_test:#{SecureRandom.hex(10)}" middleware.delete ActionDispatch::ShowExceptions end diff --git a/actionpack/test/dispatch/session/test_session_test.rb b/actionpack/test/dispatch/session/test_session_test.rb index 3e61d123e3..0bf3a8b3ee 100644 --- a/actionpack/test/dispatch/session/test_session_test.rb +++ b/actionpack/test/dispatch/session/test_session_test.rb @@ -1,63 +1,63 @@ -require 'abstract_unit' -require 'stringio' +require "abstract_unit" +require "stringio" class ActionController::TestSessionTest < ActiveSupport::TestCase def test_initialize_with_values - session = ActionController::TestSession.new(one: 'one', two: 'two') - assert_equal('one', session[:one]) - assert_equal('two', session[:two]) + session = ActionController::TestSession.new(one: "one", two: "two") + assert_equal("one", session[:one]) + assert_equal("two", session[:two]) end def test_setting_session_item_sets_item session = ActionController::TestSession.new - session[:key] = 'value' - assert_equal('value', session[:key]) + session[:key] = "value" + assert_equal("value", session[:key]) end def test_calling_delete_removes_item_and_returns_its_value session = ActionController::TestSession.new - session[:key] = 'value' - assert_equal('value', session[:key]) - assert_equal('value', session.delete(:key)) + session[:key] = "value" + assert_equal("value", session[:key]) + assert_equal("value", session.delete(:key)) assert_nil(session[:key]) end def test_calling_update_with_params_passes_to_attributes session = ActionController::TestSession.new - session.update('key' => 'value') - assert_equal('value', session[:key]) + session.update("key" => "value") + assert_equal("value", session[:key]) end def test_clear_empties_session - session = ActionController::TestSession.new(one: 'one', two: 'two') + session = ActionController::TestSession.new(one: "one", two: "two") session.clear assert_nil(session[:one]) assert_nil(session[:two]) end def test_keys_and_values - session = ActionController::TestSession.new(one: '1', two: '2') + session = ActionController::TestSession.new(one: "1", two: "2") assert_equal %w(one two), session.keys assert_equal %w(1 2), session.values end def test_fetch_returns_default - session = ActionController::TestSession.new(one: '1') - assert_equal('2', session.fetch(:two, '2')) + session = ActionController::TestSession.new(one: "1") + assert_equal("2", session.fetch(:two, "2")) end def test_fetch_on_symbol_returns_value - session = ActionController::TestSession.new(one: '1') - assert_equal('1', session.fetch(:one)) + session = ActionController::TestSession.new(one: "1") + assert_equal("1", session.fetch(:one)) end def test_fetch_on_string_returns_value - session = ActionController::TestSession.new(one: '1') - assert_equal('1', session.fetch('one')) + session = ActionController::TestSession.new(one: "1") + assert_equal("1", session.fetch("one")) end def test_fetch_returns_block_value - session = ActionController::TestSession.new(one: '1') - assert_equal(2, session.fetch('2') { |key| key.to_i }) + session = ActionController::TestSession.new(one: "1") + assert_equal(2, session.fetch("2") { |key| key.to_i }) end end diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb index 14894d4b82..3513534d72 100644 --- a/actionpack/test/dispatch/show_exceptions_test.rb +++ b/actionpack/test/dispatch/show_exceptions_test.rb @@ -1,7 +1,6 @@ -require 'abstract_unit' +require "abstract_unit" class ShowExceptionsTest < ActionDispatch::IntegrationTest - class Boomer def call(env) req = ActionDispatch::Request.new(env) @@ -12,17 +11,17 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest begin raise StandardError.new rescue - raise ActionDispatch::ParamsParser::ParseError + raise ActionDispatch::Http::Parameters::ParseError end when "/method_not_allowed" - raise ActionController::MethodNotAllowed, 'PUT' + raise ActionController::MethodNotAllowed, "PUT" when "/unknown_http_method" raise ActionController::UnknownHttpMethod when "/not_found_original_exception" begin raise AbstractController::ActionNotFound.new rescue - raise ActionView::Template::Error.new('template') + raise ActionView::Template::Error.new("template") end else raise "puke!" @@ -35,30 +34,30 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest test "skip exceptions app if not showing exceptions" do @app = ProductionApp assert_raise RuntimeError do - get "/", headers: { 'action_dispatch.show_exceptions' => false } + get "/", headers: { "action_dispatch.show_exceptions" => false } end end test "rescue with error page" do @app = ProductionApp - get "/", headers: { 'action_dispatch.show_exceptions' => true } + get "/", headers: { "action_dispatch.show_exceptions" => true } assert_response 500 assert_equal "500 error fixture\n", body - get "/bad_params", headers: { 'action_dispatch.show_exceptions' => true } + get "/bad_params", headers: { "action_dispatch.show_exceptions" => true } assert_response 400 assert_equal "400 error fixture\n", body - get "/not_found", headers: { 'action_dispatch.show_exceptions' => true } + get "/not_found", headers: { "action_dispatch.show_exceptions" => true } assert_response 404 assert_equal "404 error fixture\n", body - get "/method_not_allowed", headers: { 'action_dispatch.show_exceptions' => true } + get "/method_not_allowed", headers: { "action_dispatch.show_exceptions" => true } assert_response 405 assert_equal "", body - get "/unknown_http_method", headers: { 'action_dispatch.show_exceptions' => true } + get "/unknown_http_method", headers: { "action_dispatch.show_exceptions" => true } assert_response 405 assert_equal "", body end @@ -69,11 +68,11 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest begin @app = ProductionApp - get "/", headers: { 'action_dispatch.show_exceptions' => true } + get "/", headers: { "action_dispatch.show_exceptions" => true } assert_response 500 assert_equal "500 localized error fixture\n", body - get "/not_found", headers: { 'action_dispatch.show_exceptions' => true } + get "/not_found", headers: { "action_dispatch.show_exceptions" => true } assert_response 404 assert_equal "404 error fixture\n", body ensure @@ -84,14 +83,14 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest test "sets the HTTP charset parameter" do @app = ProductionApp - get "/", headers: { 'action_dispatch.show_exceptions' => true } + get "/", headers: { "action_dispatch.show_exceptions" => true } assert_equal "text/html; charset=utf-8", response.headers["Content-Type"] end test "show registered original exception for wrapped exceptions" do @app = ProductionApp - get "/not_found_original_exception", headers: { 'action_dispatch.show_exceptions' => true } + get "/not_found_original_exception", headers: { "action_dispatch.show_exceptions" => true } assert_response 404 assert_match(/404 error/, body) end @@ -105,7 +104,7 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest end @app = ActionDispatch::ShowExceptions.new(Boomer.new, exceptions_app) - get "/not_found_original_exception", headers: { 'action_dispatch.show_exceptions' => true } + get "/not_found_original_exception", headers: { "action_dispatch.show_exceptions" => true } assert_response 404 assert_equal "YOU FAILED", body end @@ -116,7 +115,7 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest end @app = ActionDispatch::ShowExceptions.new(Boomer.new, exceptions_app) - get "/method_not_allowed", headers: { 'action_dispatch.show_exceptions' => true } + get "/method_not_allowed", headers: { "action_dispatch.show_exceptions" => true } assert_response 405 assert_equal "", body end @@ -124,12 +123,12 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest test "bad params exception is returned in the correct format" do @app = ProductionApp - get "/bad_params", headers: { 'action_dispatch.show_exceptions' => true } + get "/bad_params", headers: { "action_dispatch.show_exceptions" => true } assert_equal "text/html; charset=utf-8", response.headers["Content-Type"] assert_response 400 assert_match(/400 error/, body) - get "/bad_params.json", headers: { 'action_dispatch.show_exceptions' => true } + get "/bad_params.json", headers: { "action_dispatch.show_exceptions" => true } assert_equal "application/json; charset=utf-8", response.headers["Content-Type"] assert_response 400 assert_equal("{\"status\":400,\"error\":\"Bad Request\"}", body) diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb index c66a0e6a7a..e29ffa750c 100644 --- a/actionpack/test/dispatch/ssl_test.rb +++ b/actionpack/test/dispatch/ssl_test.rb @@ -1,37 +1,27 @@ -require 'abstract_unit' +require "abstract_unit" class SSLTest < ActionDispatch::IntegrationTest - HEADERS = Rack::Utils::HeaderHash.new 'Content-Type' => 'text/html' + HEADERS = Rack::Utils::HeaderHash.new "Content-Type" => "text/html" attr_accessor :app def build_app(headers: {}, ssl_options: {}) headers = HEADERS.merge(headers) - ActionDispatch::SSL.new lambda { |env| [200, headers, []] }, ssl_options + ActionDispatch::SSL.new lambda { |env| [200, headers, []] }, ssl_options.reverse_merge(hsts: { subdomains: true }) end end class RedirectSSLTest < SSLTest - - def assert_not_redirected(url, headers: {}, redirect: {}, deprecated_host: nil, - deprecated_port: nil) - - self.app = build_app ssl_options: { redirect: redirect, - host: deprecated_host, port: deprecated_port - } - + def assert_not_redirected(url, headers: {}, redirect: {}) + self.app = build_app ssl_options: { redirect: redirect } get url, headers: headers assert_response :ok end - def assert_redirected(redirect: {}, deprecated_host: nil, deprecated_port: nil, - from: 'http://a/b?c=d', to: from.sub('http', 'https')) - + def assert_redirected(redirect: {}, from: "http://a/b?c=d", to: from.sub("http", "https")) redirect = { status: 301, body: [] }.merge(redirect) - self.app = build_app ssl_options: { redirect: redirect, - host: deprecated_host, port: deprecated_port - } + self.app = build_app ssl_options: { redirect: redirect } get from assert_response redirect[:status] || 301 @@ -39,114 +29,124 @@ class RedirectSSLTest < SSLTest assert_equal redirect[:body].join, @response.body end - test 'https is not redirected' do - assert_not_redirected 'https://example.org' + def assert_post_redirected(redirect: {}, from: "http://a/b?c=d", + to: from.sub("http", "https")) + + self.app = build_app ssl_options: { redirect: redirect } + + post from + assert_response redirect[:status] || 307 + assert_redirected_to to + end + + test "exclude can avoid redirect" do + excluding = { exclude: -> request { request.path =~ /healthcheck/ } } + + assert_not_redirected "http://example.org/healthcheck", redirect: excluding + assert_redirected from: "http://example.org/", redirect: excluding end - test 'proxied https is not redirected' do - assert_not_redirected 'http://example.org', headers: { 'HTTP_X_FORWARDED_PROTO' => 'https' } + test "https is not redirected" do + assert_not_redirected "https://example.org" end - test 'http is redirected to https' do - assert_redirected + test "proxied https is not redirected" do + assert_not_redirected "http://example.org", headers: { "HTTP_X_FORWARDED_PROTO" => "https" } end - test 'redirect with non-301 status' do - assert_redirected redirect: { status: 307 } + test "http is redirected to https" do + assert_redirected end - test 'redirect with custom body' do - assert_redirected redirect: { body: ['foo'] } + test "http POST is redirected to https with status 307" do + assert_post_redirected end - test 'redirect to specific host' do - assert_redirected redirect: { host: 'ssl' }, to: 'https://ssl/b?c=d' + test "redirect with non-301 status" do + assert_redirected redirect: { status: 307 } end - test 'redirect to default port' do - assert_redirected redirect: { port: 443 } + test "redirect with custom body" do + assert_redirected redirect: { body: ["foo"] } end - test 'redirect to non-default port' do - assert_redirected redirect: { port: 8443 }, to: 'https://a:8443/b?c=d' + test "redirect to specific host" do + assert_redirected redirect: { host: "ssl" }, to: "https://ssl/b?c=d" end - test 'redirect to different host and non-default port' do - assert_redirected redirect: { host: 'ssl', port: 8443 }, to: 'https://ssl:8443/b?c=d' + test "redirect to default port" do + assert_redirected redirect: { port: 443 } end - test 'redirect to different host including port' do - assert_redirected redirect: { host: 'ssl:443' }, to: 'https://ssl:443/b?c=d' + test "redirect to non-default port" do + assert_redirected redirect: { port: 8443 }, to: "https://a:8443/b?c=d" end - test ':host is deprecated, moved within redirect: { host: … }' do - assert_deprecated do - assert_redirected deprecated_host: 'foo', to: 'https://foo/b?c=d' - end + test "redirect to different host and non-default port" do + assert_redirected redirect: { host: "ssl", port: 8443 }, to: "https://ssl:8443/b?c=d" end - test ':port is deprecated, moved within redirect: { port: … }' do - assert_deprecated do - assert_redirected deprecated_port: 1, to: 'https://a:1/b?c=d' - end + test "redirect to different host including port" do + assert_redirected redirect: { host: "ssl:443" }, to: "https://ssl:443/b?c=d" end - test 'no redirect with redirect set to false' do - assert_not_redirected 'http://example.org', redirect: false + test "no redirect with redirect set to false" do + assert_not_redirected "http://example.org", redirect: false end end class StrictTransportSecurityTest < SSLTest - EXPECTED = 'max-age=15552000' + EXPECTED = "max-age=15552000" + EXPECTED_WITH_SUBDOMAINS = "max-age=15552000; includeSubDomains" - def assert_hsts(expected, url: 'https://example.org', hsts: {}, headers: {}) + 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'] + assert_equal expected, response.headers["Strict-Transport-Security"] end - test 'enabled by default' do - assert_hsts EXPECTED + test "enabled by default" do + assert_hsts EXPECTED_WITH_SUBDOMAINS end - test 'not sent with http:// responses' do - assert_hsts nil, url: 'http://example.org' + test "not sent with http:// responses" do + assert_hsts nil, url: "http://example.org" end - test 'defers to app-provided header' do - assert_hsts 'app-provided', headers: { 'Strict-Transport-Security' => 'app-provided' } + test "defers to app-provided header" do + assert_hsts "app-provided", headers: { "Strict-Transport-Security" => "app-provided" } end - test 'hsts: true enables default settings' do - assert_hsts EXPECTED, hsts: true + test "hsts: true enables default settings" do + assert_hsts EXPECTED_WITH_SUBDOMAINS, hsts: true end - test 'hsts: false sets max-age to zero, clearing browser HSTS settings' do - assert_hsts 'max-age=0', hsts: false + test "hsts: false sets max-age to zero, clearing browser HSTS settings" do + assert_hsts "max-age=0; includeSubDomains", hsts: false end - test ':expires sets max-age' do - assert_hsts 'max-age=500', hsts: { expires: 500 } + test ":expires sets max-age" do + assert_hsts "max-age=500; includeSubDomains", hsts: { expires: 500 } end - test ':expires supports AS::Duration arguments' do - assert_hsts 'max-age=31557600', hsts: { expires: 1.year } + test ":expires supports AS::Duration arguments" do + assert_hsts "max-age=31557600; includeSubDomains", hsts: { expires: 1.year } end - test 'include subdomains' do + test "include subdomains" do assert_hsts "#{EXPECTED}; includeSubDomains", hsts: { subdomains: true } end - test 'exclude subdomains' do + test "exclude subdomains" do assert_hsts EXPECTED, hsts: { subdomains: false } end - test 'opt in to browser preload lists' do - assert_hsts "#{EXPECTED}; preload", hsts: { preload: true } + test "opt in to browser preload lists" do + assert_hsts "#{EXPECTED_WITH_SUBDOMAINS}; preload", hsts: { preload: true } end - test 'opt out of browser preload lists' do - assert_hsts EXPECTED, hsts: { preload: false } + test "opt out of browser preload lists" do + assert_hsts EXPECTED_WITH_SUBDOMAINS, hsts: { preload: false } end end @@ -155,60 +155,60 @@ class SecureCookiesTest < SSLTest def get(**options) self.app = build_app(**options) - super 'https://example.org' + super "https://example.org" end def assert_cookies(*expected) - assert_equal expected, response.headers['Set-Cookie'].split("\n") + assert_equal expected, response.headers["Set-Cookie"].split("\n") end def test_flag_cookies_as_secure - get headers: { 'Set-Cookie' => DEFAULT } - assert_cookies 'id=1; path=/; secure', 'token=abc; path=/; secure; HttpOnly' + get headers: { "Set-Cookie" => DEFAULT } + assert_cookies "id=1; path=/; secure", "token=abc; path=/; secure; HttpOnly" end def test_flag_cookies_as_secure_at_end_of_line - get headers: { 'Set-Cookie' => 'problem=def; path=/; HttpOnly; secure' } - assert_cookies 'problem=def; path=/; HttpOnly; secure' + get headers: { "Set-Cookie" => "problem=def; path=/; HttpOnly; secure" } + assert_cookies "problem=def; path=/; HttpOnly; secure" end def test_flag_cookies_as_secure_with_more_spaces_before - get headers: { 'Set-Cookie' => 'problem=def; path=/; HttpOnly; secure' } - assert_cookies 'problem=def; path=/; HttpOnly; secure' + get headers: { "Set-Cookie" => "problem=def; path=/; HttpOnly; secure" } + assert_cookies "problem=def; path=/; HttpOnly; secure" end def test_flag_cookies_as_secure_with_more_spaces_after - get headers: { 'Set-Cookie' => 'problem=def; path=/; secure; HttpOnly' } - assert_cookies 'problem=def; path=/; secure; HttpOnly' + get headers: { "Set-Cookie" => "problem=def; path=/; secure; HttpOnly" } + assert_cookies "problem=def; path=/; secure; HttpOnly" end def test_flag_cookies_as_secure_with_has_not_spaces_before - get headers: { 'Set-Cookie' => 'problem=def; path=/;secure; HttpOnly' } - assert_cookies 'problem=def; path=/;secure; HttpOnly' + get headers: { "Set-Cookie" => "problem=def; path=/;secure; HttpOnly" } + assert_cookies "problem=def; path=/;secure; HttpOnly" end def test_flag_cookies_as_secure_with_has_not_spaces_after - get headers: { 'Set-Cookie' => 'problem=def; path=/; secure;HttpOnly' } - assert_cookies 'problem=def; path=/; secure;HttpOnly' + get headers: { "Set-Cookie" => "problem=def; path=/; secure;HttpOnly" } + assert_cookies "problem=def; path=/; secure;HttpOnly" end def test_flag_cookies_as_secure_with_ignore_case - get headers: { 'Set-Cookie' => 'problem=def; path=/; Secure; HttpOnly' } - assert_cookies 'problem=def; path=/; Secure; HttpOnly' + get headers: { "Set-Cookie" => "problem=def; path=/; Secure; HttpOnly" } + assert_cookies "problem=def; path=/; Secure; HttpOnly" end def test_cookies_as_not_secure_with_secure_cookies_disabled - get headers: { 'Set-Cookie' => DEFAULT }, ssl_options: { secure_cookies: false } + get headers: { "Set-Cookie" => DEFAULT }, ssl_options: { secure_cookies: false } assert_cookies(*DEFAULT.split("\n")) end def test_no_cookies get - assert_nil response.headers['Set-Cookie'] + assert_nil response.headers["Set-Cookie"] end def test_keeps_original_headers_behavior - get headers: { 'Connection' => %w[close] } - assert_equal 'close', response.headers['Connection'] + get headers: { "Connection" => %w[close] } + assert_equal "close", response.headers["Connection"] end end diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb index ea8b5e904e..aca70e180c 100644 --- a/actionpack/test/dispatch/static_test.rb +++ b/actionpack/test/dispatch/static_test.rb @@ -1,9 +1,9 @@ -require 'abstract_unit' -require 'zlib' +require "abstract_unit" +require "zlib" module StaticTests DummyApp = lambda { |env| - [200, {"Content-Type" => "text/plain"}, ["Hello, World!"]] + [200, { "Content-Type" => "text/plain" }, ["Hello, World!"]] } def setup @@ -29,7 +29,7 @@ module StaticTests end def test_handles_urls_with_ascii_8bit - assert_equal "Hello, World!", get("/doorkeeper%E3E4".force_encoding('ASCII-8BIT')).body + assert_equal "Hello, World!", get("/doorkeeper%E3E4".force_encoding("ASCII-8BIT")).body end def test_handles_urls_with_ascii_8bit_on_win_31j @@ -37,23 +37,13 @@ module StaticTests Encoding.default_internal = "Windows-31J" Encoding.default_external = "Windows-31J" end - assert_equal "Hello, World!", get("/doorkeeper%E3E4".force_encoding('ASCII-8BIT')).body + assert_equal "Hello, World!", get("/doorkeeper%E3E4".force_encoding("ASCII-8BIT")).body end def test_handles_urls_with_null_byte assert_equal "Hello, World!", get("/doorkeeper%00").body end - def test_sets_cache_control - app = assert_deprecated do - ActionDispatch::Static.new(DummyApp, @root, "public, max-age=60") - end - response = Rack::MockRequest.new(app).request("GET", "/index.html") - - assert_html "/index.html", response - assert_equal "public, max-age=60", response.headers["Cache-Control"] - end - def test_serves_static_index_at_root assert_html "/index.html", get("/index.html") assert_html "/index.html", get("/index") @@ -82,7 +72,6 @@ module StaticTests assert_html "means hello in Japanese\n", get("/foo/#{Rack::Utils.escape("ã“ã‚“ã«ã¡ã¯.html")}") end - def test_serves_static_file_with_exclamation_mark_in_filename with_static_file "/foo/foo!bar.html" do |file| assert_html file, get("/foo/foo%21bar.html") @@ -148,65 +137,74 @@ module StaticTests def test_serves_gzip_files_when_header_set file_name = "/gzip/application-a71b3024f80aea3181c09774ca17e712.js" - response = get(file_name, 'HTTP_ACCEPT_ENCODING' => 'gzip') + response = get(file_name, "HTTP_ACCEPT_ENCODING" => "gzip") assert_gzip file_name, response - assert_equal 'application/javascript', response.headers['Content-Type'] - assert_equal 'Accept-Encoding', response.headers["Vary"] - assert_equal 'gzip', response.headers["Content-Encoding"] + assert_equal "application/javascript", response.headers["Content-Type"] + assert_equal "Accept-Encoding", response.headers["Vary"] + assert_equal "gzip", response.headers["Content-Encoding"] - response = get(file_name, 'HTTP_ACCEPT_ENCODING' => 'Gzip') + response = get(file_name, "HTTP_ACCEPT_ENCODING" => "Gzip") assert_gzip file_name, response - response = get(file_name, 'HTTP_ACCEPT_ENCODING' => 'GZIP') + response = get(file_name, "HTTP_ACCEPT_ENCODING" => "GZIP") assert_gzip file_name, response - response = get(file_name, 'HTTP_ACCEPT_ENCODING' => '') - assert_not_equal 'gzip', response.headers["Content-Encoding"] + response = get(file_name, "HTTP_ACCEPT_ENCODING" => "compress;q=0.5, gzip;q=1.0") + assert_gzip file_name, response + + response = get(file_name, "HTTP_ACCEPT_ENCODING" => "") + assert_not_equal "gzip", response.headers["Content-Encoding"] end def test_does_not_modify_path_info file_name = "/gzip/application-a71b3024f80aea3181c09774ca17e712.js" - env = {'PATH_INFO' => file_name, 'HTTP_ACCEPT_ENCODING' => 'gzip', "REQUEST_METHOD" => 'POST'} + env = { "PATH_INFO" => file_name, "HTTP_ACCEPT_ENCODING" => "gzip", "REQUEST_METHOD" => "POST" } @app.call(env) - assert_equal file_name, env['PATH_INFO'] + assert_equal file_name, env["PATH_INFO"] end def test_serves_gzip_with_propper_content_type_fallback file_name = "/gzip/foo.zoo" - response = get(file_name, 'HTTP_ACCEPT_ENCODING' => 'gzip') + response = get(file_name, "HTTP_ACCEPT_ENCODING" => "gzip") assert_gzip file_name, response default_response = get(file_name) # no gzip - assert_equal default_response.headers['Content-Type'], response.headers['Content-Type'] + assert_equal default_response.headers["Content-Type"], response.headers["Content-Type"] end def test_serves_gzip_files_with_not_modified file_name = "/gzip/application-a71b3024f80aea3181c09774ca17e712.js" 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) + 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_equal nil, response.headers["Content-Type"] + assert_equal nil, response.headers["Content-Encoding"] + assert_equal nil, response.headers["Vary"] end def test_serves_files_with_headers headers = { - "Access-Control-Allow-Origin" => 'http://rubyonrails.org', - "Cache-Control" => 'public, max-age=60', + "Access-Control-Allow-Origin" => "http://rubyonrails.org", + "Cache-Control" => "public, max-age=60", "X-Custom-Header" => "I'm a teapot" } app = ActionDispatch::Static.new(DummyApp, @root, headers: headers) response = Rack::MockRequest.new(app).request("GET", "/foo/bar.html") - assert_equal 'http://rubyonrails.org', response.headers["Access-Control-Allow-Origin"] - assert_equal 'public, max-age=60', response.headers["Cache-Control"] + assert_equal "http://rubyonrails.org", response.headers["Access-Control-Allow-Origin"] + assert_equal "public, max-age=60", response.headers["Cache-Control"] assert_equal "I'm a teapot", response.headers["X-Custom-Header"] end + def test_ignores_unknown_http_methods + app = ActionDispatch::Static.new(DummyApp, @root) + + assert_nothing_raised { Rack::MockRequest.new(app).request("BAD_METHOD", "/foo/bar.html") } + end + # Windows doesn't allow \ / : * ? " < > | in filenames - unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ + unless RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ def test_serves_static_file_with_colon with_static_file "/foo/foo:bar.html" do |file| assert_html file, get("/foo/foo%3Abar.html") @@ -258,7 +256,7 @@ class StaticTest < ActiveSupport::TestCase def setup super @root = "#{FIXTURE_LOAD_PATH}/public" - @app = ActionDispatch::Static.new(DummyApp, @root, headers: {'Cache-Control' => "public, max-age=60"}) + @app = ActionDispatch::Static.new(DummyApp, @root, headers: { "Cache-Control" => "public, max-age=60" }) end def public_path @@ -268,8 +266,8 @@ class StaticTest < ActiveSupport::TestCase include StaticTests def test_custom_handler_called_when_file_is_outside_root - filename = 'shared.html.erb' - assert File.exist?(File.join(@root, '..', filename)) + filename = "shared.html.erb" + assert File.exist?(File.join(@root, "..", filename)) env = { "REQUEST_METHOD"=>"GET", "REQUEST_PATH"=>"/..%2F#{filename}", @@ -294,14 +292,13 @@ class StaticTest < ActiveSupport::TestCase assert_html "/foo/other-index.html", get("/foo/") assert_html "/foo/other-index.html", get("/foo") end - end class StaticEncodingTest < StaticTest def setup super @root = "#{FIXTURE_LOAD_PATH}/公共" - @app = ActionDispatch::Static.new(DummyApp, @root, headers: {'Cache-Control' => "public, max-age=60"}) + @app = ActionDispatch::Static.new(DummyApp, @root, headers: { "Cache-Control" => "public, max-age=60" }) end def public_path diff --git a/actionpack/test/dispatch/test_request_test.rb b/actionpack/test/dispatch/test_request_test.rb index 51c469a61a..b479af781d 100644 --- a/actionpack/test/dispatch/test_request_test.rb +++ b/actionpack/test/dispatch/test_request_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class TestRequestTest < ActiveSupport::TestCase test "sane defaults" do @@ -33,24 +33,24 @@ class TestRequestTest < ActiveSupport::TestCase assert_equal nil, req.env["HTTP_COOKIE"] req.cookie_jar["user_name"] = "david" - assert_cookies({"user_name" => "david"}, req.cookie_jar) + assert_cookies({ "user_name" => "david" }, req.cookie_jar) req.cookie_jar["login"] = "XJ-122" - assert_cookies({"user_name" => "david", "login" => "XJ-122"}, req.cookie_jar) + assert_cookies({ "user_name" => "david", "login" => "XJ-122" }, req.cookie_jar) assert_nothing_raised do req.cookie_jar["login"] = nil - assert_cookies({"user_name" => "david", "login" => nil}, req.cookie_jar) + assert_cookies({ "user_name" => "david", "login" => nil }, req.cookie_jar) end req.cookie_jar.delete(:login) - assert_cookies({"user_name" => "david"}, req.cookie_jar) + assert_cookies({ "user_name" => "david" }, req.cookie_jar) req.cookie_jar.clear assert_cookies({}, req.cookie_jar) - req.cookie_jar.update(:user_name => "david") - assert_cookies({"user_name" => "david"}, req.cookie_jar) + req.cookie_jar.update(user_name: "david") + assert_cookies({ "user_name" => "david" }, req.cookie_jar) end test "does not complain when there is no application config" do @@ -60,32 +60,66 @@ class TestRequestTest < ActiveSupport::TestCase test "default remote address is 0.0.0.0" do req = ActionDispatch::TestRequest.create({}) - assert_equal '0.0.0.0', req.remote_addr + assert_equal "0.0.0.0", req.remote_addr end test "allows remote address to be overridden" do - req = ActionDispatch::TestRequest.create('REMOTE_ADDR' => '127.0.0.1') - assert_equal '127.0.0.1', req.remote_addr + req = ActionDispatch::TestRequest.create("REMOTE_ADDR" => "127.0.0.1") + assert_equal "127.0.0.1", req.remote_addr end test "default host is test.host" do req = ActionDispatch::TestRequest.create({}) - assert_equal 'test.host', req.host + assert_equal "test.host", req.host end test "allows host to be overridden" do - req = ActionDispatch::TestRequest.create('HTTP_HOST' => 'www.example.com') - assert_equal 'www.example.com', req.host + req = ActionDispatch::TestRequest.create("HTTP_HOST" => "www.example.com") + assert_equal "www.example.com", req.host end test "default user agent is 'Rails Testing'" do req = ActionDispatch::TestRequest.create({}) - assert_equal 'Rails Testing', req.user_agent + assert_equal "Rails Testing", req.user_agent end test "allows user agent to be overridden" do - req = ActionDispatch::TestRequest.create('HTTP_USER_AGENT' => 'GoogleBot') - assert_equal 'GoogleBot', req.user_agent + req = ActionDispatch::TestRequest.create("HTTP_USER_AGENT" => "GoogleBot") + assert_equal "GoogleBot", req.user_agent + end + + test "request_method getter and setter" do + req = ActionDispatch::TestRequest.create + req.request_method # to reproduce bug caused by memoization + req.request_method = "POST" + assert_equal "POST", req.request_method + end + + test "setter methods" do + req = ActionDispatch::TestRequest.create({}) + get = "GET" + + [ + "request_method=", "host=", "request_uri=", "path=", "if_modified_since=", "if_none_match=", + "remote_addr=", "user_agent=", "accept=" + ].each do |method| + req.send(method, get) + end + + req.port = 8080 + req.accept = "hello goodbye" + + assert_equal(get, req.get_header("REQUEST_METHOD")) + assert_equal(get, req.get_header("HTTP_HOST")) + assert_equal(8080, req.get_header("SERVER_PORT")) + assert_equal(get, req.get_header("REQUEST_URI")) + assert_equal(get, req.get_header("PATH_INFO")) + assert_equal(get, req.get_header("HTTP_IF_MODIFIED_SINCE")) + assert_equal(get, req.get_header("HTTP_IF_NONE_MATCH")) + assert_equal(get, req.get_header("REMOTE_ADDR")) + assert_equal(get, req.get_header("HTTP_USER_AGENT")) + assert_nil(req.get_header("action_dispatch.request.accepts")) + assert_equal("hello goodbye", req.get_header("HTTP_ACCEPT")) end private diff --git a/actionpack/test/dispatch/test_response_test.rb b/actionpack/test/dispatch/test_response_test.rb index a4f9d56a6a..98eafb5119 100644 --- a/actionpack/test/dispatch/test_response_test.rb +++ b/actionpack/test/dispatch/test_response_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class TestResponseTest < ActiveSupport::TestCase def assert_response_code_range(range, predicate) @@ -17,4 +17,12 @@ class TestResponseTest < ActiveSupport::TestCase assert_response_code_range 500..599, :server_error? assert_response_code_range 400..499, :client_error? end + + test "response parsing" do + response = ActionDispatch::TestResponse.create(200, {}, "") + assert_equal response.body, response.parsed_body + + response = ActionDispatch::TestResponse.create(200, { "Content-Type" => "application/json" }, '{ "foo": "fighters" }') + assert_equal({ "foo" => "fighters" }, response.parsed_body) + end end diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb index 55ebbd5143..60d0246a68 100644 --- a/actionpack/test/dispatch/uploaded_file_test.rb +++ b/actionpack/test/dispatch/uploaded_file_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module ActionDispatch class UploadedFileTest < ActiveSupport::TestCase @@ -9,81 +9,81 @@ module ActionDispatch end def test_original_filename - uf = Http::UploadedFile.new(:filename => 'foo', :tempfile => Object.new) - assert_equal 'foo', uf.original_filename + uf = Http::UploadedFile.new(filename: "foo", tempfile: Object.new) + assert_equal "foo", uf.original_filename end def test_filename_should_be_in_utf_8 - uf = Http::UploadedFile.new(:filename => 'foo', :tempfile => Object.new) + uf = Http::UploadedFile.new(filename: "foo", tempfile: Object.new) assert_equal "UTF-8", uf.original_filename.encoding.to_s end def test_filename_should_always_be_in_utf_8 - uf = Http::UploadedFile.new(:filename => 'foo'.encode(Encoding::SHIFT_JIS), - :tempfile => Object.new) + uf = Http::UploadedFile.new(filename: "foo".encode(Encoding::SHIFT_JIS), + tempfile: Object.new) assert_equal "UTF-8", uf.original_filename.encoding.to_s end def test_content_type - uf = Http::UploadedFile.new(:type => 'foo', :tempfile => Object.new) - assert_equal 'foo', uf.content_type + uf = Http::UploadedFile.new(type: "foo", tempfile: Object.new) + assert_equal "foo", uf.content_type end def test_headers - uf = Http::UploadedFile.new(:head => 'foo', :tempfile => Object.new) - assert_equal 'foo', uf.headers + uf = Http::UploadedFile.new(head: "foo", tempfile: Object.new) + assert_equal "foo", uf.headers end def test_tempfile - uf = Http::UploadedFile.new(:tempfile => 'foo') - assert_equal 'foo', uf.tempfile + uf = Http::UploadedFile.new(tempfile: "foo") + assert_equal "foo", uf.tempfile end def test_to_io_returns_the_tempfile tf = Object.new - uf = Http::UploadedFile.new(:tempfile => tf) + uf = Http::UploadedFile.new(tempfile: tf) assert_equal tf, uf.to_io end def test_delegates_path_to_tempfile - tf = Class.new { def path; 'thunderhorse' end } - uf = Http::UploadedFile.new(:tempfile => tf.new) - assert_equal 'thunderhorse', uf.path + tf = Class.new { def path; "thunderhorse" end } + uf = Http::UploadedFile.new(tempfile: tf.new) + assert_equal "thunderhorse", uf.path end def test_delegates_open_to_tempfile - tf = Class.new { def open; 'thunderhorse' end } - uf = Http::UploadedFile.new(:tempfile => tf.new) - assert_equal 'thunderhorse', uf.open + tf = Class.new { def open; "thunderhorse" end } + uf = Http::UploadedFile.new(tempfile: tf.new) + assert_equal "thunderhorse", uf.open end def test_delegates_close_to_tempfile - tf = Class.new { def close(unlink_now=false); 'thunderhorse' end } - uf = Http::UploadedFile.new(:tempfile => tf.new) - assert_equal 'thunderhorse', uf.close + tf = Class.new { def close(unlink_now=false); "thunderhorse" end } + uf = Http::UploadedFile.new(tempfile: tf.new) + assert_equal "thunderhorse", uf.close end def test_close_accepts_parameter tf = Class.new { def close(unlink_now=false); "thunderhorse: #{unlink_now}" end } - uf = Http::UploadedFile.new(:tempfile => tf.new) - assert_equal 'thunderhorse: true', uf.close(true) + uf = Http::UploadedFile.new(tempfile: tf.new) + assert_equal "thunderhorse: true", uf.close(true) end def test_delegates_read_to_tempfile - tf = Class.new { def read(length=nil, buffer=nil); 'thunderhorse' end } - uf = Http::UploadedFile.new(:tempfile => tf.new) - assert_equal 'thunderhorse', uf.read + tf = Class.new { def read(length=nil, buffer=nil); "thunderhorse" end } + uf = Http::UploadedFile.new(tempfile: tf.new) + assert_equal "thunderhorse", uf.read end def test_delegates_read_to_tempfile_with_params tf = Class.new { def read(length=nil, buffer=nil); [length, buffer] end } - uf = Http::UploadedFile.new(:tempfile => tf.new) + uf = Http::UploadedFile.new(tempfile: tf.new) assert_equal %w{ thunder horse }, uf.read(*%w{ thunder horse }) end def test_delegate_respects_respond_to? tf = Class.new { def read; yield end; private :read } - uf = Http::UploadedFile.new(:tempfile => tf.new) + uf = Http::UploadedFile.new(tempfile: tf.new) assert_raises(NoMethodError) do uf.read end @@ -91,15 +91,15 @@ module ActionDispatch def test_delegate_eof_to_tempfile tf = Class.new { def eof?; true end; } - uf = Http::UploadedFile.new(:tempfile => tf.new) + uf = Http::UploadedFile.new(tempfile: tf.new) assert uf.eof? end def test_respond_to? tf = Class.new { def read; yield end } - uf = Http::UploadedFile.new(:tempfile => tf.new) - assert uf.respond_to?(:headers), 'responds to headers' - assert uf.respond_to?(:read), 'responds to read' + uf = Http::UploadedFile.new(tempfile: tf.new) + assert uf.respond_to?(:headers), "responds to headers" + assert uf.respond_to?(:read), "responds to read" end end end diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb index 8c9782bb90..5d81fd6834 100644 --- a/actionpack/test/dispatch/url_generation_test.rb +++ b/actionpack/test/dispatch/url_generation_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module TestUrlGeneration class WithMountPoint < ActionDispatch::IntegrationTest @@ -13,11 +13,11 @@ module TestUrlGeneration end Routes.draw do - get "/foo", :to => "my_route_generating#index", :as => :foo + get "/foo", to: "my_route_generating#index", as: :foo resources :bars - mount MyRouteGeneratingController.action(:index), at: '/bar' + mount MyRouteGeneratingController.action(:index), at: "/bar" end APP = build_app Routes @@ -35,22 +35,22 @@ module TestUrlGeneration end test "accepting a :script_name option" do - assert_equal "/bar/foo", foo_path(:script_name => "/bar") + assert_equal "/bar/foo", foo_path(script_name: "/bar") end test "the request's SCRIPT_NAME takes precedence over the route" do - get "/foo", headers: { 'SCRIPT_NAME' => "/new", 'action_dispatch.routes' => Routes } + get "/foo", headers: { "SCRIPT_NAME" => "/new", "action_dispatch.routes" => Routes } assert_equal "/new/foo", response.body end test "the request's SCRIPT_NAME wraps the mounted app's" do - get '/new/bar/foo', headers: { 'SCRIPT_NAME' => '/new', 'PATH_INFO' => '/bar/foo', 'action_dispatch.routes' => Routes } + get "/new/bar/foo", headers: { "SCRIPT_NAME" => "/new", "PATH_INFO" => "/bar/foo", "action_dispatch.routes" => Routes } assert_equal "/new/bar/foo", response.body end test "handling http protocol with https set" do https! - assert_equal "http://www.example.com/foo", foo_url(:protocol => "http") + assert_equal "http://www.example.com/foo", foo_url(protocol: "http") end test "extracting protocol from host when protocol not present" do @@ -117,25 +117,23 @@ module TestUrlGeneration test "generating URLs with trailing slashes" do assert_equal "/bars.json", bars_path( trailing_slash: true, - format: 'json' + format: "json" ) end test "generating URLS with querystring and trailing slashes" do assert_equal "/bars.json?a=b", bars_path( trailing_slash: true, - a: 'b', - format: 'json' + a: "b", + format: "json" ) end test "generating URLS with empty querystring" do assert_equal "/bars.json", bars_path( a: {}, - format: 'json' + format: "json" ) end - end end - diff --git a/actionpack/test/fixtures/collection_cache/index.html.erb b/actionpack/test/fixtures/collection_cache/index.html.erb index 521b1450df..853e501ab4 100644 --- a/actionpack/test/fixtures/collection_cache/index.html.erb +++ b/actionpack/test/fixtures/collection_cache/index.html.erb @@ -1 +1 @@ -<%= render @customers %>
\ No newline at end of file +<%= render partial: 'customers/customer', collection: @customers, cached: true %> diff --git a/actionpack/test/fixtures/company.rb b/actionpack/test/fixtures/company.rb index f3ac3642fa..9f527acdd8 100644 --- a/actionpack/test/fixtures/company.rb +++ b/actionpack/test/fixtures/company.rb @@ -4,6 +4,6 @@ class Company < ActiveRecord::Base validates_presence_of :name def validate - errors.add('rating', 'rating should not be 2') if rating == 2 + errors.add("rating", "rating should not be 2") if rating == 2 end end diff --git a/actionpack/test/fixtures/functional_caching/fragment_cached_with_options.html.erb b/actionpack/test/fixtures/functional_caching/fragment_cached_with_options.html.erb index 01453323ef..951c761995 100644 --- a/actionpack/test/fixtures/functional_caching/fragment_cached_with_options.html.erb +++ b/actionpack/test/fixtures/functional_caching/fragment_cached_with_options.html.erb @@ -1,3 +1,3 @@ <body> -<%= cache 'with_options', skip_digest: true, expires_in: 1.minute do %><p>ERB</p><% end %> +<%= cache 'with_options', skip_digest: true, expires_in: 10 do %><p>ERB</p><% end %> </body> diff --git a/actionpack/test/fixtures/helpers/fun/games_helper.rb b/actionpack/test/fixtures/helpers/fun/games_helper.rb index 3b7adce086..2d5e50f5a5 100644 --- a/actionpack/test/fixtures/helpers/fun/games_helper.rb +++ b/actionpack/test/fixtures/helpers/fun/games_helper.rb @@ -2,4 +2,4 @@ module Fun module GamesHelper def stratego() "Iz guuut!" end end -end
\ No newline at end of file +end diff --git a/actionpack/test/fixtures/helpers/fun/pdf_helper.rb b/actionpack/test/fixtures/helpers/fun/pdf_helper.rb index 0171be8500..16057fd466 100644 --- a/actionpack/test/fixtures/helpers/fun/pdf_helper.rb +++ b/actionpack/test/fixtures/helpers/fun/pdf_helper.rb @@ -1,5 +1,5 @@ module Fun module PdfHelper - def foobar() 'baz' end + def foobar() "baz" end end end diff --git a/actionpack/test/fixtures/helpers/just_me_helper.rb b/actionpack/test/fixtures/helpers/just_me_helper.rb index b140a7b9b4..9b43fc6d49 100644 --- a/actionpack/test/fixtures/helpers/just_me_helper.rb +++ b/actionpack/test/fixtures/helpers/just_me_helper.rb @@ -1,3 +1,3 @@ module JustMeHelper def me() "mine!" end -end
\ No newline at end of file +end diff --git a/actionpack/test/fixtures/helpers/me_too_helper.rb b/actionpack/test/fixtures/helpers/me_too_helper.rb index ce56042143..8e312e7cd0 100644 --- a/actionpack/test/fixtures/helpers/me_too_helper.rb +++ b/actionpack/test/fixtures/helpers/me_too_helper.rb @@ -1,3 +1,3 @@ module MeTooHelper def me() "me too!" end -end
\ No newline at end of file +end diff --git a/actionpack/test/fixtures/helpers_typo/admin/users_helper.rb b/actionpack/test/fixtures/helpers_typo/admin/users_helper.rb index 7d2326e04d..64aa1a0476 100644 --- a/actionpack/test/fixtures/helpers_typo/admin/users_helper.rb +++ b/actionpack/test/fixtures/helpers_typo/admin/users_helper.rb @@ -2,4 +2,3 @@ module Admin module UsersHelpeR end end - diff --git a/actionpack/test/fixtures/implicit_render_test/empty_action_with_mobile_variant.html+mobile.erb b/actionpack/test/fixtures/implicit_render_test/empty_action_with_mobile_variant.html+mobile.erb new file mode 100644 index 0000000000..ded99ba52d --- /dev/null +++ b/actionpack/test/fixtures/implicit_render_test/empty_action_with_mobile_variant.html+mobile.erb @@ -0,0 +1 @@ +mobile diff --git a/actionpack/test/fixtures/implicit_render_test/empty_action_with_template.html.erb b/actionpack/test/fixtures/implicit_render_test/empty_action_with_template.html.erb new file mode 100644 index 0000000000..dd294f8cf6 --- /dev/null +++ b/actionpack/test/fixtures/implicit_render_test/empty_action_with_template.html.erb @@ -0,0 +1 @@ +<h1>Empty action rendered this implicitly.</h1> diff --git a/actionpack/test/fixtures/load_me.rb b/actionpack/test/fixtures/load_me.rb new file mode 100644 index 0000000000..e516512a4e --- /dev/null +++ b/actionpack/test/fixtures/load_me.rb @@ -0,0 +1,2 @@ +class LoadMe +end diff --git a/actionpack/test/fixtures/multipart/mona_lisa.jpg b/actionpack/test/fixtures/multipart/mona_lisa.jpg Binary files differdeleted file mode 100644 index 5cf3bef3d0..0000000000 --- a/actionpack/test/fixtures/multipart/mona_lisa.jpg +++ /dev/null diff --git a/actionpack/test/fixtures/multipart/ruby_on_rails.jpg b/actionpack/test/fixtures/multipart/ruby_on_rails.jpg Binary files differnew file mode 100644 index 0000000000..ed284ea0ba --- /dev/null +++ b/actionpack/test/fixtures/multipart/ruby_on_rails.jpg diff --git a/actionpack/test/fixtures/namespaced/implicit_render_test/hello_world.erb b/actionpack/test/fixtures/namespaced/implicit_render_test/hello_world.erb new file mode 100644 index 0000000000..cd0875583a --- /dev/null +++ b/actionpack/test/fixtures/namespaced/implicit_render_test/hello_world.erb @@ -0,0 +1 @@ +Hello world! diff --git a/actionpack/test/fixtures/respond_to/variant_with_implicit_rendering.html+mobile.erb b/actionpack/test/fixtures/respond_to/variant_with_implicit_template_rendering.html+mobile.erb index 317801ad30..317801ad30 100644 --- a/actionpack/test/fixtures/respond_to/variant_with_implicit_rendering.html+mobile.erb +++ b/actionpack/test/fixtures/respond_to/variant_with_implicit_template_rendering.html+mobile.erb diff --git a/actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb b/actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb index 4ee7a24561..cd0be6d1b5 100644 --- a/actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb +++ b/actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb @@ -1,6 +1,6 @@ module SessionAutoloadTest class Foo - def initialize(bar='baz') + def initialize(bar="baz") @bar = bar end def inspect diff --git a/actionpack/test/fixtures/test/with_implicit_template.erb b/actionpack/test/fixtures/test/with_implicit_template.erb new file mode 100644 index 0000000000..474488cd13 --- /dev/null +++ b/actionpack/test/fixtures/test/with_implicit_template.erb @@ -0,0 +1 @@ +Hello explicitly! diff --git a/actionpack/test/journey/gtg/builder_test.rb b/actionpack/test/journey/gtg/builder_test.rb index c1da374007..2b314cdd4e 100644 --- a/actionpack/test/journey/gtg/builder_test.rb +++ b/actionpack/test/journey/gtg/builder_test.rb @@ -1,28 +1,28 @@ -require 'abstract_unit' +require "abstract_unit" module ActionDispatch module Journey module GTG class TestBuilder < ActiveSupport::TestCase def test_following_states_multi - table = tt ['a|a'] - assert_equal 1, table.move([0], 'a').length + table = tt ["a|a"] + assert_equal 1, table.move([0], "a").length end def test_following_states_multi_regexp - table = tt [':a|b'] - assert_equal 1, table.move([0], 'fooo').length - assert_equal 2, table.move([0], 'b').length + table = tt [":a|b"] + assert_equal 1, table.move([0], "fooo").length + assert_equal 2, table.move([0], "b").length end def test_multi_path - table = tt ['/:a/d', '/b/c'] + table = tt ["/:a/d", "/b/c"] [ - [1, '/'], - [2, 'b'], - [2, '/'], - [1, 'c'], + [1, "/"], + [2, "b"], + [2, "/"], + [1, "c"], ].inject([0]) { |state, (exp, sym)| new = table.move(state, sym) assert_equal exp, new.length @@ -40,7 +40,7 @@ module ActionDispatch sim = NFA::Simulator.new table - match = sim.match '/articles/new' + match = sim.match "/articles/new" assert_equal 2, match.memos.length end @@ -54,25 +54,25 @@ module ActionDispatch sim = NFA::Simulator.new table - match = sim.match '/articles/new' + match = sim.match "/articles/new" assert_equal 2, match.memos.length end private - def ast strings - parser = Journey::Parser.new - asts = strings.map { |string| - memo = Object.new - ast = parser.parse string - ast.each { |n| n.memo = memo } - ast - } - Nodes::Or.new asts - end + def ast(strings) + parser = Journey::Parser.new + asts = strings.map { |string| + memo = Object.new + ast = parser.parse string + ast.each { |n| n.memo = memo } + ast + } + Nodes::Or.new asts + end - def tt strings - Builder.new(ast(strings)).transition_table - end + def tt(strings) + Builder.new(ast(strings)).transition_table + end end end end diff --git a/actionpack/test/journey/gtg/transition_table_test.rb b/actionpack/test/journey/gtg/transition_table_test.rb index b968780d8d..4c8b5032eb 100644 --- a/actionpack/test/journey/gtg/transition_table_test.rb +++ b/actionpack/test/journey/gtg/transition_table_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/json/decoding' +require "abstract_unit" +require "active_support/json/decoding" module ActionDispatch module Journey @@ -14,9 +14,9 @@ module ActionDispatch } json = ActiveSupport::JSON.decode table.to_json - assert json['regexp_states'] - assert json['string_states'] - assert json['accepting'] + assert json["regexp_states"] + assert json["string_states"] + assert json["accepting"] end if system("dot -V 2>/dev/null") @@ -34,26 +34,26 @@ module ActionDispatch end def test_simulate_gt - sim = simulator_for ['/foo', '/bar'] - assert_match sim, '/foo' + sim = simulator_for ["/foo", "/bar"] + assert_match sim, "/foo" end def test_simulate_gt_regexp - sim = simulator_for [':foo'] - assert_match sim, 'foo' + sim = simulator_for [":foo"] + assert_match sim, "foo" end def test_simulate_gt_regexp_mix - sim = simulator_for ['/get', '/:method/foo'] - assert_match sim, '/get' - assert_match sim, '/get/foo' + sim = simulator_for ["/get", "/:method/foo"] + assert_match sim, "/get" + assert_match sim, "/get/foo" end def test_simulate_optional - sim = simulator_for ['/foo(/bar)'] - assert_match sim, '/foo' - assert_match sim, '/foo/bar' - assert_no_match sim, '/foo/' + sim = simulator_for ["/foo(/bar)"] + assert_match sim, "/foo" + assert_match sim, "/foo/bar" + assert_no_match sim, "/foo/" end def test_match_data @@ -65,10 +65,10 @@ module ActionDispatch sim = GTG::Simulator.new tt - match = sim.match '/get' + match = sim.match "/get" assert_equal [paths.first], match.memos - match = sim.match '/get/foo' + match = sim.match "/get/foo" assert_equal [paths.last], match.memos end @@ -86,29 +86,29 @@ module ActionDispatch builder = GTG::Builder.new ast sim = GTG::Simulator.new builder.transition_table - match = sim.match '/articles/new' + match = sim.match "/articles/new" assert_equal [paths[1], paths[3]], match.memos end private - def asts paths - parser = Journey::Parser.new - paths.map { |x| - ast = parser.parse x - ast.each { |n| n.memo = ast} - ast - } - end + def asts(paths) + parser = Journey::Parser.new + paths.map { |x| + ast = parser.parse x + ast.each { |n| n.memo = ast } + ast + } + end - def tt paths - x = asts paths - builder = GTG::Builder.new Nodes::Or.new x - builder.transition_table - end + def tt(paths) + x = asts paths + builder = GTG::Builder.new Nodes::Or.new x + builder.transition_table + end - def simulator_for paths - GTG::Simulator.new tt(paths) - end + def simulator_for(paths) + GTG::Simulator.new tt(paths) + end end end end diff --git a/actionpack/test/journey/nfa/simulator_test.rb b/actionpack/test/journey/nfa/simulator_test.rb index 673a491fe5..183c892a53 100644 --- a/actionpack/test/journey/nfa/simulator_test.rb +++ b/actionpack/test/journey/nfa/simulator_test.rb @@ -1,47 +1,47 @@ -require 'abstract_unit' +require "abstract_unit" module ActionDispatch module Journey module NFA class TestSimulator < ActiveSupport::TestCase def test_simulate_simple - sim = simulator_for ['/foo'] - assert_match sim, '/foo' + sim = simulator_for ["/foo"] + assert_match sim, "/foo" end def test_simulate_simple_no_match - sim = simulator_for ['/foo'] - assert_no_match sim, 'foo' + sim = simulator_for ["/foo"] + assert_no_match sim, "foo" end def test_simulate_simple_no_match_too_long - sim = simulator_for ['/foo'] - assert_no_match sim, '/foo/bar' + sim = simulator_for ["/foo"] + assert_no_match sim, "/foo/bar" end def test_simulate_simple_no_match_wrong_string - sim = simulator_for ['/foo'] - assert_no_match sim, '/bar' + sim = simulator_for ["/foo"] + assert_no_match sim, "/bar" end def test_simulate_regex - sim = simulator_for ['/:foo/bar'] - assert_match sim, '/bar/bar' - assert_match sim, '/foo/bar' + sim = simulator_for ["/:foo/bar"] + assert_match sim, "/bar/bar" + assert_match sim, "/foo/bar" end def test_simulate_or - sim = simulator_for ['/foo', '/bar'] - assert_match sim, '/bar' - assert_match sim, '/foo' - assert_no_match sim, '/baz' + sim = simulator_for ["/foo", "/bar"] + assert_match sim, "/bar" + assert_match sim, "/foo" + assert_no_match sim, "/baz" end def test_simulate_optional - sim = simulator_for ['/foo(/bar)'] - assert_match sim, '/foo' - assert_match sim, '/foo/bar' - assert_no_match sim, '/foo/' + sim = simulator_for ["/foo(/bar)"] + assert_match sim, "/foo" + assert_match sim, "/foo/bar" + assert_no_match sim, "/foo/" end def test_matchdata_has_memos @@ -49,7 +49,7 @@ module ActionDispatch parser = Journey::Parser.new asts = paths.map { |x| ast = parser.parse x - ast.each { |n| n.memo = ast} + ast.each { |n| n.memo = ast } ast } @@ -59,17 +59,17 @@ module ActionDispatch sim = Simulator.new builder.transition_table - md = sim.match '/foo' + md = sim.match "/foo" assert_equal [expected], md.memos end def test_matchdata_memos_on_merge parser = Journey::Parser.new routes = [ - '/articles(.:format)', - '/articles/new(.:format)', - '/articles/:id/edit(.:format)', - '/articles/:id(.:format)', + "/articles(.:format)", + "/articles/new(.:format)", + "/articles/:id/edit(.:format)", + "/articles/:id(.:format)", ].map { |path| ast = parser.parse path ast.each { |n| n.memo = ast } @@ -82,11 +82,11 @@ module ActionDispatch nfa = Journey::NFA::Builder.new ast sim = Simulator.new nfa.transition_table - md = sim.match '/articles' + md = sim.match "/articles" assert_equal [asts.first], md.memos end - def simulator_for paths + def simulator_for(paths) parser = Journey::Parser.new asts = paths.map { |x| parser.parse x } builder = Builder.new Nodes::Or.new asts diff --git a/actionpack/test/journey/nfa/transition_table_test.rb b/actionpack/test/journey/nfa/transition_table_test.rb index 1248082c03..f3cf36a064 100644 --- a/actionpack/test/journey/nfa/transition_table_test.rb +++ b/actionpack/test/journey/nfa/transition_table_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module ActionDispatch module Journey @@ -9,63 +9,63 @@ module ActionDispatch end def test_eclosure - table = tt '/' + table = tt "/" assert_equal [0], table.eclosure(0) - table = tt ':a|:b' + table = tt ":a|:b" assert_equal 3, table.eclosure(0).length - table = tt '(:a|:b)' + table = tt "(:a|:b)" assert_equal 5, table.eclosure(0).length assert_equal 5, table.eclosure([0]).length end def test_following_states_one - table = tt '/' + table = tt "/" - assert_equal [1], table.following_states(0, '/') - assert_equal [1], table.following_states([0], '/') + assert_equal [1], table.following_states(0, "/") + assert_equal [1], table.following_states([0], "/") end def test_following_states_group - table = tt 'a|b' + table = tt "a|b" states = table.eclosure 0 - assert_equal 1, table.following_states(states, 'a').length - assert_equal 1, table.following_states(states, 'b').length + assert_equal 1, table.following_states(states, "a").length + assert_equal 1, table.following_states(states, "b").length end def test_following_states_multi - table = tt 'a|a' + table = tt "a|a" states = table.eclosure 0 - assert_equal 2, table.following_states(states, 'a').length - assert_equal 0, table.following_states(states, 'b').length + assert_equal 2, table.following_states(states, "a").length + assert_equal 0, table.following_states(states, "b").length end def test_following_states_regexp - table = tt 'a|:a' + table = tt "a|:a" states = table.eclosure 0 - assert_equal 1, table.following_states(states, 'a').length + assert_equal 1, table.following_states(states, "a").length assert_equal 1, table.following_states(states, /[^\.\/\?]+/).length - assert_equal 0, table.following_states(states, 'b').length + assert_equal 0, table.following_states(states, "b").length end def test_alphabet - table = tt 'a|:a' - assert_equal [/[^\.\/\?]+/, 'a'], table.alphabet + table = tt "a|:a" + assert_equal [/[^\.\/\?]+/, "a"], table.alphabet - table = tt 'a|a' - assert_equal ['a'], table.alphabet + table = tt "a|a" + assert_equal ["a"], table.alphabet end private - def tt string - ast = @parser.parse string - builder = Builder.new ast - builder.transition_table - end + def tt(string) + ast = @parser.parse string + builder = Builder.new ast + builder.transition_table + end end end end diff --git a/actionpack/test/journey/nodes/symbol_test.rb b/actionpack/test/journey/nodes/symbol_test.rb index adf85b860c..baf60f40b8 100644 --- a/actionpack/test/journey/nodes/symbol_test.rb +++ b/actionpack/test/journey/nodes/symbol_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module ActionDispatch module Journey diff --git a/actionpack/test/journey/path/pattern_test.rb b/actionpack/test/journey/path/pattern_test.rb index 72858f5eda..d61a8c023a 100644 --- a/actionpack/test/journey/path/pattern_test.rb +++ b/actionpack/test/journey/path/pattern_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module ActionDispatch module Journey @@ -8,22 +8,22 @@ module ActionDispatch x = /.+/ { - '/:controller(/:action)' => %r{\A/(#{x})(?:/([^/.?]+))?\Z}, - '/:controller/foo' => %r{\A/(#{x})/foo\Z}, - '/:controller/:action' => %r{\A/(#{x})/([^/.?]+)\Z}, - '/:controller' => %r{\A/(#{x})\Z}, - '/:controller(/:action(/:id))' => %r{\A/(#{x})(?:/([^/.?]+)(?:/([^/.?]+))?)?\Z}, - '/:controller/:action.xml' => %r{\A/(#{x})/([^/.?]+)\.xml\Z}, - '/:controller.:format' => %r{\A/(#{x})\.([^/.?]+)\Z}, - '/:controller(.:format)' => %r{\A/(#{x})(?:\.([^/.?]+))?\Z}, - '/:controller/*foo' => %r{\A/(#{x})/(.+)\Z}, - '/:controller/*foo/bar' => %r{\A/(#{x})/(.+)/bar\Z}, - '/:foo|*bar' => %r{\A/(?:([^/.?]+)|(.+))\Z}, + "/:controller(/:action)" => %r{\A/(#{x})(?:/([^/.?]+))?\Z}, + "/:controller/foo" => %r{\A/(#{x})/foo\Z}, + "/:controller/:action" => %r{\A/(#{x})/([^/.?]+)\Z}, + "/:controller" => %r{\A/(#{x})\Z}, + "/:controller(/:action(/:id))" => %r{\A/(#{x})(?:/([^/.?]+)(?:/([^/.?]+))?)?\Z}, + "/:controller/:action.xml" => %r{\A/(#{x})/([^/.?]+)\.xml\Z}, + "/:controller.:format" => %r{\A/(#{x})\.([^/.?]+)\Z}, + "/:controller(.:format)" => %r{\A/(#{x})(?:\.([^/.?]+))?\Z}, + "/:controller/*foo" => %r{\A/(#{x})/(.+)\Z}, + "/:controller/*foo/bar" => %r{\A/(#{x})/(.+)/bar\Z}, + "/:foo|*bar" => %r{\A/(?:([^/.?]+)|(.+))\Z}, }.each do |path, expected| define_method(:"test_to_regexp_#{path}") do path = Pattern.build( path, - { :controller => /.+/ }, + { controller: /.+/ }, SEPARATORS, true ) @@ -32,22 +32,22 @@ module ActionDispatch end { - '/:controller(/:action)' => %r{\A/(#{x})(?:/([^/.?]+))?}, - '/:controller/foo' => %r{\A/(#{x})/foo}, - '/:controller/:action' => %r{\A/(#{x})/([^/.?]+)}, - '/:controller' => %r{\A/(#{x})}, - '/:controller(/:action(/:id))' => %r{\A/(#{x})(?:/([^/.?]+)(?:/([^/.?]+))?)?}, - '/:controller/:action.xml' => %r{\A/(#{x})/([^/.?]+)\.xml}, - '/:controller.:format' => %r{\A/(#{x})\.([^/.?]+)}, - '/:controller(.:format)' => %r{\A/(#{x})(?:\.([^/.?]+))?}, - '/:controller/*foo' => %r{\A/(#{x})/(.+)}, - '/:controller/*foo/bar' => %r{\A/(#{x})/(.+)/bar}, - '/:foo|*bar' => %r{\A/(?:([^/.?]+)|(.+))}, + "/:controller(/:action)" => %r{\A/(#{x})(?:/([^/.?]+))?}, + "/:controller/foo" => %r{\A/(#{x})/foo}, + "/:controller/:action" => %r{\A/(#{x})/([^/.?]+)}, + "/:controller" => %r{\A/(#{x})}, + "/:controller(/:action(/:id))" => %r{\A/(#{x})(?:/([^/.?]+)(?:/([^/.?]+))?)?}, + "/:controller/:action.xml" => %r{\A/(#{x})/([^/.?]+)\.xml}, + "/:controller.:format" => %r{\A/(#{x})\.([^/.?]+)}, + "/:controller(.:format)" => %r{\A/(#{x})(?:\.([^/.?]+))?}, + "/:controller/*foo" => %r{\A/(#{x})/(.+)}, + "/:controller/*foo/bar" => %r{\A/(#{x})/(.+)/bar}, + "/:foo|*bar" => %r{\A/(?:([^/.?]+)|(.+))}, }.each do |path, expected| define_method(:"test_to_non_anchored_regexp_#{path}") do path = Pattern.build( path, - { :controller => /.+/ }, + { controller: /.+/ }, SEPARATORS, false ) @@ -56,21 +56,21 @@ module ActionDispatch end { - '/:controller(/:action)' => %w{ controller action }, - '/:controller/foo' => %w{ controller }, - '/:controller/:action' => %w{ controller action }, - '/:controller' => %w{ controller }, - '/:controller(/:action(/:id))' => %w{ controller action id }, - '/:controller/:action.xml' => %w{ controller action }, - '/:controller.:format' => %w{ controller format }, - '/:controller(.:format)' => %w{ controller format }, - '/:controller/*foo' => %w{ controller foo }, - '/:controller/*foo/bar' => %w{ controller foo }, + "/:controller(/:action)" => %w{ controller action }, + "/:controller/foo" => %w{ controller }, + "/:controller/:action" => %w{ controller action }, + "/:controller" => %w{ controller }, + "/:controller(/:action(/:id))" => %w{ controller action id }, + "/:controller/:action.xml" => %w{ controller action }, + "/:controller.:format" => %w{ controller format }, + "/:controller(.:format)" => %w{ controller format }, + "/:controller/*foo" => %w{ controller foo }, + "/:controller/*foo/bar" => %w{ controller foo }, }.each do |path, expected| define_method(:"test_names_#{path}") do path = Pattern.build( path, - { :controller => /.+/ }, + { controller: /.+/ }, SEPARATORS, true ) @@ -80,8 +80,8 @@ module ActionDispatch def test_to_regexp_with_extended_group path = Pattern.build( - '/page/:name', - { :name => / + "/page/:name", + { name: / #ROFL (tender|love #MAO @@ -89,16 +89,16 @@ module ActionDispatch SEPARATORS, true ) - assert_match(path, '/page/tender') - assert_match(path, '/page/love') - assert_no_match(path, '/page/loving') + assert_match(path, "/page/tender") + assert_match(path, "/page/love") + assert_no_match(path, "/page/loving") end def test_optional_names [ - ['/:foo(/:bar(/:baz))', %w{ bar baz }], - ['/:foo(/:bar)', %w{ bar }], - ['/:foo(/:bar)/:lol(/:baz)', %w{ bar baz }], + ["/:foo(/:bar(/:baz))", %w{ bar baz }], + ["/:foo(/:bar)", %w{ bar }], + ["/:foo(/:bar)/:lol(/:baz)", %w{ bar baz }], ].each do |pattern, list| path = Pattern.from_string pattern assert_equal list.sort, path.optional_names.sort @@ -107,31 +107,31 @@ module ActionDispatch def test_to_regexp_match_non_optional path = Pattern.build( - '/:name', - { :name => /\d+/ }, + "/:name", + { name: /\d+/ }, SEPARATORS, true ) - assert_match(path, '/123') - assert_no_match(path, '/') + assert_match(path, "/123") + assert_no_match(path, "/") end def test_to_regexp_with_group path = Pattern.build( - '/page/:name', - { :name => /(tender|love)/ }, + "/page/:name", + { name: /(tender|love)/ }, SEPARATORS, true ) - assert_match(path, '/page/tender') - assert_match(path, '/page/love') - assert_no_match(path, '/page/loving') + assert_match(path, "/page/tender") + assert_match(path, "/page/love") + assert_no_match(path, "/page/loving") end def test_ast_sets_regular_expressions - requirements = { :name => /(tender|love)/, :value => /./ } + requirements = { name: /(tender|love)/, value: /./ } path = Pattern.build( - '/page/:name/:value', + "/page/:name/:value", requirements, SEPARATORS, true @@ -146,26 +146,26 @@ module ActionDispatch def test_match_data_with_group path = Pattern.build( - '/page/:name', - { :name => /(tender|love)/ }, + "/page/:name", + { name: /(tender|love)/ }, SEPARATORS, true ) - match = path.match '/page/tender' - assert_equal 'tender', match[1] + match = path.match "/page/tender" + assert_equal "tender", match[1] assert_equal 2, match.length end def test_match_data_with_multi_group path = Pattern.build( - '/page/:name/:id', - { :name => /t(((ender|love)))()/ }, + "/page/:name/:id", + { name: /t(((ender|love)))()/ }, SEPARATORS, true ) - match = path.match '/page/tender/10' - assert_equal 'tender', match[1] - assert_equal '10', match[2] + match = path.match "/page/tender/10" + assert_equal "tender", match[1] + assert_equal "10", match[2] assert_equal 3, match.length assert_equal %w{ tender 10 }, match.captures end @@ -173,8 +173,8 @@ module ActionDispatch def test_star_with_custom_re z = /\d+/ path = Pattern.build( - '/page/*foo', - { :foo => z }, + "/page/*foo", + { foo: z }, SEPARATORS, true ) @@ -183,76 +183,76 @@ module ActionDispatch def test_insensitive_regexp_with_group path = Pattern.build( - '/page/:name/aaron', - { :name => /(tender|love)/i }, + "/page/:name/aaron", + { name: /(tender|love)/i }, SEPARATORS, true ) - assert_match(path, '/page/TENDER/aaron') - assert_match(path, '/page/loVE/aaron') - assert_no_match(path, '/page/loVE/AAron') + assert_match(path, "/page/TENDER/aaron") + assert_match(path, "/page/loVE/aaron") + assert_no_match(path, "/page/loVE/AAron") end def test_to_regexp_with_strexp - path = Pattern.build('/:controller', { }, SEPARATORS, true) + path = Pattern.build("/:controller", {}, SEPARATORS, true) x = %r{\A/([^/.?]+)\Z} assert_equal(x.source, path.source) end def test_to_regexp_defaults - path = Pattern.from_string '/:controller(/:action(/:id))' + path = Pattern.from_string "/:controller(/:action(/:id))" expected = %r{\A/([^/.?]+)(?:/([^/.?]+)(?:/([^/.?]+))?)?\Z} assert_equal expected, path.to_regexp end def test_failed_match - path = Pattern.from_string '/:controller(/:action(/:id(.:format)))' - uri = 'content' + path = Pattern.from_string "/:controller(/:action(/:id(.:format)))" + uri = "content" assert_not path =~ uri end def test_match_controller - path = Pattern.from_string '/:controller(/:action(/:id(.:format)))' - uri = '/content' + path = Pattern.from_string "/:controller(/:action(/:id(.:format)))" + uri = "/content" match = path =~ uri assert_equal %w{ controller action id format }, match.names - assert_equal 'content', match[1] + assert_equal "content", match[1] assert_nil match[2] assert_nil match[3] assert_nil match[4] end def test_match_controller_action - path = Pattern.from_string '/:controller(/:action(/:id(.:format)))' - uri = '/content/list' + path = Pattern.from_string "/:controller(/:action(/:id(.:format)))" + uri = "/content/list" match = path =~ uri assert_equal %w{ controller action id format }, match.names - assert_equal 'content', match[1] - assert_equal 'list', match[2] + assert_equal "content", match[1] + assert_equal "list", match[2] assert_nil match[3] assert_nil match[4] end def test_match_controller_action_id - path = Pattern.from_string '/:controller(/:action(/:id(.:format)))' - uri = '/content/list/10' + path = Pattern.from_string "/:controller(/:action(/:id(.:format)))" + uri = "/content/list/10" match = path =~ uri assert_equal %w{ controller action id format }, match.names - assert_equal 'content', match[1] - assert_equal 'list', match[2] - assert_equal '10', match[3] + assert_equal "content", match[1] + assert_equal "list", match[2] + assert_equal "10", match[3] assert_nil match[4] end def test_match_literal path = Path::Pattern.from_string "/books(/:action(.:format))" - uri = '/books' + uri = "/books" match = path =~ uri assert_equal %w{ action format }, match.names assert_nil match[1] @@ -262,21 +262,21 @@ module ActionDispatch def test_match_literal_with_action path = Path::Pattern.from_string "/books(/:action(.:format))" - uri = '/books/list' + uri = "/books/list" match = path =~ uri assert_equal %w{ action format }, match.names - assert_equal 'list', match[1] + assert_equal "list", match[1] assert_nil match[2] end def test_match_literal_with_action_and_format path = Path::Pattern.from_string "/books(/:action(.:format))" - uri = '/books/list.rss' + uri = "/books/list.rss" match = path =~ uri assert_equal %w{ action format }, match.names - assert_equal 'list', match[1] - assert_equal 'rss', match[2] + assert_equal "list", match[1] + assert_equal "rss", match[2] end end end diff --git a/actionpack/test/journey/route/definition/parser_test.rb b/actionpack/test/journey/route/definition/parser_test.rb index d7d7172a40..8c6e3c0371 100644 --- a/actionpack/test/journey/route/definition/parser_test.rb +++ b/actionpack/test/journey/route/definition/parser_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module ActionDispatch module Journey @@ -9,88 +9,88 @@ module ActionDispatch end def test_slash - assert_equal :SLASH, @parser.parse('/').type - assert_round_trip '/' + assert_equal :SLASH, @parser.parse("/").type + assert_round_trip "/" end def test_segment - assert_round_trip '/foo' + assert_round_trip "/foo" end def test_segments - assert_round_trip '/foo/bar' + assert_round_trip "/foo/bar" end def test_segment_symbol - assert_round_trip '/foo/:id' + assert_round_trip "/foo/:id" end def test_symbol - assert_round_trip '/:foo' + assert_round_trip "/:foo" end def test_group - assert_round_trip '(/:foo)' + assert_round_trip "(/:foo)" end def test_groups - assert_round_trip '(/:foo)(/:bar)' + assert_round_trip "(/:foo)(/:bar)" end def test_nested_groups - assert_round_trip '(/:foo(/:bar))' + assert_round_trip "(/:foo(/:bar))" end def test_dot_symbol - assert_round_trip('.:format') + assert_round_trip(".:format") end def test_dot_literal - assert_round_trip('.xml') + assert_round_trip(".xml") end def test_segment_dot - assert_round_trip('/foo.:bar') + assert_round_trip("/foo.:bar") end def test_segment_group_dot - assert_round_trip('/foo(.:bar)') + assert_round_trip("/foo(.:bar)") end def test_segment_group - assert_round_trip('/foo(/:action)') + assert_round_trip("/foo(/:action)") end def test_segment_groups - assert_round_trip('/foo(/:action)(/:bar)') + assert_round_trip("/foo(/:action)(/:bar)") end def test_segment_nested_groups - assert_round_trip('/foo(/:action(/:bar))') + assert_round_trip("/foo(/:action(/:bar))") end def test_group_followed_by_path - assert_round_trip('/foo(/:action)/:bar') + assert_round_trip("/foo(/:action)/:bar") end def test_star - assert_round_trip('*foo') - assert_round_trip('/*foo') - assert_round_trip('/bar/*foo') - assert_round_trip('/bar/(*foo)') + assert_round_trip("*foo") + assert_round_trip("/*foo") + assert_round_trip("/bar/*foo") + assert_round_trip("/bar/(*foo)") end def test_or - assert_round_trip('a|b') - assert_round_trip('a|b|c') - assert_round_trip('(a|b)|c') - assert_round_trip('a|(b|c)') - assert_round_trip('*a|(b|c)') - assert_round_trip('*a|:b|c') + assert_round_trip("a|b") + assert_round_trip("a|b|c") + assert_round_trip("(a|b)|c") + assert_round_trip("a|(b|c)") + assert_round_trip("*a|(b|c)") + assert_round_trip("*a|:b|c") end def test_arbitrary - assert_round_trip('/bar/*foo#') + assert_round_trip("/bar/*foo#") end def test_literal_dot_paren @@ -101,7 +101,7 @@ module ActionDispatch assert_round_trip "/(:locale)(.:format)" end - def assert_round_trip str + def assert_round_trip(str) assert_equal str, @parser.parse(str).to_s end end diff --git a/actionpack/test/journey/route/definition/scanner_test.rb b/actionpack/test/journey/route/definition/scanner_test.rb index 7a510f1e07..98578ddbf1 100644 --- a/actionpack/test/journey/route/definition/scanner_test.rb +++ b/actionpack/test/journey/route/definition/scanner_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module ActionDispatch module Journey @@ -11,44 +11,44 @@ module ActionDispatch # /page/:id(/:action)(.:format) def test_tokens [ - ['/', [[:SLASH, '/']]], - ['*omg', [[:STAR, '*omg']]], - ['/page', [[:SLASH, '/'], [:LITERAL, 'page']]], - ['/page!', [[:SLASH, '/'], [:LITERAL, 'page!']]], - ['/page$', [[:SLASH, '/'], [:LITERAL, 'page$']]], - ['/page&', [[:SLASH, '/'], [:LITERAL, 'page&']]], - ["/page'", [[:SLASH, '/'], [:LITERAL, "page'"]]], - ['/page*', [[:SLASH, '/'], [:LITERAL, 'page*']]], - ['/page+', [[:SLASH, '/'], [:LITERAL, 'page+']]], - ['/page,', [[:SLASH, '/'], [:LITERAL, 'page,']]], - ['/page;', [[:SLASH, '/'], [:LITERAL, 'page;']]], - ['/page=', [[:SLASH, '/'], [:LITERAL, 'page=']]], - ['/page@', [[:SLASH, '/'], [:LITERAL, 'page@']]], - ['/page\:', [[:SLASH, '/'], [:LITERAL, 'page:']]], - ['/page\(', [[:SLASH, '/'], [:LITERAL, 'page(']]], - ['/page\)', [[:SLASH, '/'], [:LITERAL, 'page)']]], - ['/~page', [[:SLASH, '/'], [:LITERAL, '~page']]], - ['/pa-ge', [[:SLASH, '/'], [:LITERAL, 'pa-ge']]], - ['/:page', [[:SLASH, '/'], [:SYMBOL, ':page']]], - ['/(:page)', [ - [:SLASH, '/'], - [:LPAREN, '('], - [:SYMBOL, ':page'], - [:RPAREN, ')'], + ["/", [[:SLASH, "/"]]], + ["*omg", [[:STAR, "*omg"]]], + ["/page", [[:SLASH, "/"], [:LITERAL, "page"]]], + ["/page!", [[:SLASH, "/"], [:LITERAL, "page!"]]], + ["/page$", [[:SLASH, "/"], [:LITERAL, "page$"]]], + ["/page&", [[:SLASH, "/"], [:LITERAL, "page&"]]], + ["/page'", [[:SLASH, "/"], [:LITERAL, "page'"]]], + ["/page*", [[:SLASH, "/"], [:LITERAL, "page*"]]], + ["/page+", [[:SLASH, "/"], [:LITERAL, "page+"]]], + ["/page,", [[:SLASH, "/"], [:LITERAL, "page,"]]], + ["/page;", [[:SLASH, "/"], [:LITERAL, "page;"]]], + ["/page=", [[:SLASH, "/"], [:LITERAL, "page="]]], + ["/page@", [[:SLASH, "/"], [:LITERAL, "page@"]]], + ['/page\:', [[:SLASH, "/"], [:LITERAL, "page:"]]], + ['/page\(', [[:SLASH, "/"], [:LITERAL, "page("]]], + ['/page\)', [[:SLASH, "/"], [:LITERAL, "page)"]]], + ["/~page", [[:SLASH, "/"], [:LITERAL, "~page"]]], + ["/pa-ge", [[:SLASH, "/"], [:LITERAL, "pa-ge"]]], + ["/:page", [[:SLASH, "/"], [:SYMBOL, ":page"]]], + ["/(:page)", [ + [:SLASH, "/"], + [:LPAREN, "("], + [:SYMBOL, ":page"], + [:RPAREN, ")"], ]], - ['(/:action)', [ - [:LPAREN, '('], - [:SLASH, '/'], - [:SYMBOL, ':action'], - [:RPAREN, ')'], + ["(/:action)", [ + [:LPAREN, "("], + [:SLASH, "/"], + [:SYMBOL, ":action"], + [:RPAREN, ")"], ]], - ['(())', [[:LPAREN, '('], - [:LPAREN, '('], [:RPAREN, ')'], [:RPAREN, ')']]], - ['(.:format)', [ - [:LPAREN, '('], - [:DOT, '.'], - [:SYMBOL, ':format'], - [:RPAREN, ')'], + ["(())", [[:LPAREN, "("], + [:LPAREN, "("], [:RPAREN, ")"], [:RPAREN, ")"]]], + ["(.:format)", [ + [:LPAREN, "("], + [:DOT, "."], + [:SYMBOL, ":format"], + [:RPAREN, ")"], ]], ].each do |str, expected| @scanner.scan_setup str @@ -56,7 +56,7 @@ module ActionDispatch end end - def assert_tokens tokens, scanner + def assert_tokens(tokens, scanner) toks = [] while tok = scanner.next_token toks << tok diff --git a/actionpack/test/journey/route_test.rb b/actionpack/test/journey/route_test.rb index 22c3b8113d..b6414fd101 100644 --- a/actionpack/test/journey/route_test.rb +++ b/actionpack/test/journey/route_test.rb @@ -1,11 +1,11 @@ -require 'abstract_unit' +require "abstract_unit" module ActionDispatch module Journey class TestRoute < ActiveSupport::TestCase def test_initialize app = Object.new - path = Path::Pattern.from_string '/:controller(/:action(/:id(.:format)))' + path = Path::Pattern.from_string "/:controller(/:action(/:id(.:format)))" defaults = {} route = Route.build("name", app, path, {}, [], defaults) @@ -16,7 +16,7 @@ module ActionDispatch def test_route_adds_itself_as_memo app = Object.new - path = Path::Pattern.from_string '/:controller(/:action(/:id(.:format)))' + path = Path::Pattern.from_string "/:controller(/:action(/:id(.:format)))" defaults = {} route = Route.build("name", app, path, {}, [], defaults) @@ -26,71 +26,69 @@ module ActionDispatch end def test_path_requirements_override_defaults - path = Path::Pattern.build(':name', { name: /love/ }, '/', true) - defaults = { name: 'tender' } - route = Route.build('name', nil, path, {}, [], defaults) + path = Path::Pattern.build(":name", { name: /love/ }, "/", true) + defaults = { name: "tender" } + route = Route.build("name", nil, path, {}, [], defaults) assert_equal(/love/, route.requirements[:name]) end def test_ip_address - path = Path::Pattern.from_string '/messages/:id(.:format)' - route = Route.build("name", nil, path, {:ip => '192.168.1.1'}, [], - { :controller => 'foo', :action => 'bar' }) - assert_equal '192.168.1.1', route.ip + path = Path::Pattern.from_string "/messages/:id(.:format)" + route = Route.build("name", nil, path, { ip: "192.168.1.1" }, [], + controller: "foo", action: "bar") + assert_equal "192.168.1.1", route.ip end def test_default_ip - path = Path::Pattern.from_string '/messages/:id(.:format)' + path = Path::Pattern.from_string "/messages/:id(.:format)" route = Route.build("name", nil, path, {}, [], - { :controller => 'foo', :action => 'bar' }) + controller: "foo", action: "bar") assert_equal(//, route.ip) end def test_format_with_star - path = Path::Pattern.from_string '/:controller/*extra' + path = Path::Pattern.from_string "/:controller/*extra" route = Route.build("name", nil, path, {}, [], - { :controller => 'foo', :action => 'bar' }) - assert_equal '/foo/himom', route.format({ - :controller => 'foo', - :extra => 'himom', - }) + controller: "foo", action: "bar") + assert_equal "/foo/himom", route.format( + controller: "foo", + extra: "himom") end def test_connects_all_match - path = Path::Pattern.from_string '/:controller(/:action(/:id(.:format)))' - route = Route.build("name", nil, path, {:action => 'bar'}, [], { :controller => 'foo' }) - - assert_equal '/foo/bar/10', route.format({ - :controller => 'foo', - :action => 'bar', - :id => 10 - }) + path = Path::Pattern.from_string "/:controller(/:action(/:id(.:format)))" + route = Route.build("name", nil, path, { action: "bar" }, [], controller: "foo") + + assert_equal "/foo/bar/10", route.format( + controller: "foo", + action: "bar", + id: 10) end def test_extras_are_not_included_if_optional - path = Path::Pattern.from_string '/page/:id(/:action)' - route = Route.build("name", nil, path, { }, [], { :action => 'show' }) + path = Path::Pattern.from_string "/page/:id(/:action)" + route = Route.build("name", nil, path, {}, [], action: "show") - assert_equal '/page/10', route.format({ :id => 10 }) + assert_equal "/page/10", route.format(id: 10) end def test_extras_are_not_included_if_optional_with_parameter - path = Path::Pattern.from_string '(/sections/:section)/pages/:id' - route = Route.build("name", nil, path, { }, [], { :action => 'show' }) + path = Path::Pattern.from_string "(/sections/:section)/pages/:id" + route = Route.build("name", nil, path, {}, [], action: "show") - assert_equal '/pages/10', route.format({:id => 10}) + assert_equal "/pages/10", route.format(id: 10) end def test_extras_are_not_included_if_optional_parameter_is_nil - path = Path::Pattern.from_string '(/sections/:section)/pages/:id' - route = Route.build("name", nil, path, { }, [], { :action => 'show' }) + path = Path::Pattern.from_string "(/sections/:section)/pages/:id" + route = Route.build("name", nil, path, {}, [], action: "show") - assert_equal '/pages/10', route.format({:id => 10, :section => nil}) + assert_equal "/pages/10", route.format(id: 10, section: nil) end def test_score constraints = {} - defaults = {:controller=>"pages", :action=>"show"} + defaults = { controller: "pages", action: "show" } path = Path::Pattern.from_string "/page/:id(/:action)(.:format)" specific = Route.build "name", nil, path, constraints, [:controller, :action], defaults @@ -98,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: 20, controller: "pages", action: "show" } routes = [specific, generic] diff --git a/actionpack/test/journey/router/utils_test.rb b/actionpack/test/journey/router/utils_test.rb index 2b505f081e..b77bf6628a 100644 --- a/actionpack/test/journey/router/utils_test.rb +++ b/actionpack/test/journey/router/utils_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module ActionDispatch module Journey diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb index 15d51e5d6c..7b5916eb72 100644 --- a/actionpack/test/journey/router_test.rb +++ b/actionpack/test/journey/router_test.rb @@ -1,9 +1,9 @@ -require 'abstract_unit' +require "abstract_unit" module ActionDispatch module Journey class TestRouter < ActiveSupport::TestCase - attr_reader :routes, :mapper + attr_reader :mapper, :routes, :route_set, :router def setup @app = Routing::RouteSet::Dispatcher.new({}) @@ -15,84 +15,83 @@ module ActionDispatch end def test_dashes - mapper.get '/foo-bar-baz', to: 'foo#bar' + get "/foo-bar-baz", to: "foo#bar" - env = rails_env 'PATH_INFO' => '/foo-bar-baz' + env = rails_env "PATH_INFO" => "/foo-bar-baz" called = false - @router.recognize(env) do |r, params| + router.recognize(env) do |r, params| called = true end assert called end def test_unicode - mapper.get '/ã»ã’', to: 'foo#bar' + get "/ã»ã’", to: "foo#bar" #match the escaped version of /ã»ã’ - env = rails_env 'PATH_INFO' => '/%E3%81%BB%E3%81%92' + env = rails_env "PATH_INFO" => "/%E3%81%BB%E3%81%92" called = false - @router.recognize(env) do |r, params| + router.recognize(env) do |r, params| called = true end assert called end def test_regexp_first_precedence - mapper.get "/whois/:domain", :domain => /\w+\.[\w\.]+/, to: "foo#bar" - mapper.get "/whois/:id(.:format)", to: "foo#baz" + get "/whois/:domain", domain: /\w+\.[\w\.]+/, to: "foo#bar" + get "/whois/:id(.:format)", to: "foo#baz" - env = rails_env 'PATH_INFO' => '/whois/example.com' + env = rails_env "PATH_INFO" => "/whois/example.com" list = [] - @router.recognize(env) do |r, params| + router.recognize(env) do |r, params| list << r end assert_equal 2, list.length r = list.first - assert_equal '/whois/:domain(.:format)', r.path.spec.to_s + assert_equal "/whois/:domain(.:format)", r.path.spec.to_s end def test_required_parts_verified_are_anchored - mapper.get "/foo/:id", :id => /\d/, anchor: false, to: "foo#bar" + get "/foo/:id", id: /\d/, anchor: false, to: "foo#bar" assert_raises(ActionController::UrlGenerationError) do - @formatter.generate(nil, { :controller => "foo", :action => "bar", :id => '10' }, { }) + @formatter.generate(nil, { controller: "foo", action: "bar", id: "10" }, {}) end end def test_required_parts_are_verified_when_building - mapper.get "/foo/:id", :id => /\d+/, anchor: false, to: "foo#bar" + get "/foo/:id", id: /\d+/, anchor: false, to: "foo#bar" - path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar", :id => '10' }, { }) - assert_equal '/foo/10', path + path, _ = @formatter.generate(nil, { controller: "foo", action: "bar", id: "10" }, {}) + assert_equal "/foo/10", path assert_raises(ActionController::UrlGenerationError) do - @formatter.generate(nil, { :id => 'aa' }, { }) + @formatter.generate(nil, { id: "aa" }, {}) end end def test_only_required_parts_are_verified - mapper.get "/foo(/:id)", :id => /\d/, :to => "foo#bar" + get "/foo(/:id)", id: /\d/, to: "foo#bar" - path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar", :id => '10' }, { }) - assert_equal '/foo/10', path + path, _ = @formatter.generate(nil, { controller: "foo", action: "bar", id: "10" }, {}) + assert_equal "/foo/10", path - path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar" }, { }) - assert_equal '/foo', path + path, _ = @formatter.generate(nil, { controller: "foo", action: "bar" }, {}) + assert_equal "/foo", path - path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar", :id => 'aa' }, { }) - assert_equal '/foo/aa', path + path, _ = @formatter.generate(nil, { controller: "foo", action: "bar", id: "aa" }, {}) + assert_equal "/foo/aa", path end def test_knows_what_parts_are_missing_from_named_route route_name = "gorby_thunderhorse" - mapper = ActionDispatch::Routing::Mapper.new @route_set - mapper.get "/foo/:id", :as => route_name, :id => /\d+/, :to => "foo#bar" + get "/foo/:id", as: route_name, id: /\d+/, to: "foo#bar" error = assert_raises(ActionController::UrlGenerationError) do - @formatter.generate(route_name, { }, { }) + @formatter.generate(route_name, {}, {}) end assert_match(/missing required keys: \[:id\]/, error.message) @@ -102,69 +101,66 @@ module ActionDispatch route_name = "gorby_thunderhorse" error = assert_raises(ActionController::UrlGenerationError) do - @formatter.generate(route_name, { }, { }) + @formatter.generate(route_name, {}, {}) end assert_no_match(/missing required keys: \[\]/, error.message) end def test_X_Cascade - mapper.get "/messages(.:format)", to: "foo#bar" - resp = @router.serve(rails_env({ 'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/lol' })) - assert_equal ['Not Found'], resp.last - assert_equal 'pass', resp[1]['X-Cascade'] + get "/messages(.:format)", to: "foo#bar" + resp = router.serve(rails_env("REQUEST_METHOD" => "GET", "PATH_INFO" => "/lol")) + assert_equal ["Not Found"], resp.last + assert_equal "pass", resp[1]["X-Cascade"] assert_equal 404, resp.first end def test_clear_trailing_slash_from_script_name_on_root_unanchored_routes - route_set = Routing::RouteSet.new - mapper = Routing::Mapper.new route_set + app = lambda { |env| [200, {}, ["success!"]] } + get "/weblog", to: app - app = lambda { |env| [200, {}, ['success!']] } - mapper.get '/weblog', :to => app - - env = rack_env('SCRIPT_NAME' => '', 'PATH_INFO' => '/weblog') + env = rack_env("SCRIPT_NAME" => "", "PATH_INFO" => "/weblog") resp = route_set.call env - assert_equal ['success!'], resp.last - assert_equal '', env['SCRIPT_NAME'] + assert_equal ["success!"], resp.last + assert_equal "", env["SCRIPT_NAME"] end def test_defaults_merge_correctly - mapper.get '/foo(/:id)', to: "foo#bar", id: nil + get "/foo(/:id)", to: "foo#bar", id: nil - env = rails_env 'PATH_INFO' => '/foo/10' - @router.recognize(env) do |r, params| - assert_equal({:id => '10', :controller => "foo", :action => "bar"}, params) + env = rails_env "PATH_INFO" => "/foo/10" + router.recognize(env) do |r, params| + assert_equal({ id: "10", controller: "foo", action: "bar" }, params) end - env = rails_env 'PATH_INFO' => '/foo' - @router.recognize(env) do |r, params| - assert_equal({:id => nil, :controller => "foo", :action => "bar"}, params) + env = rails_env "PATH_INFO" => "/foo" + router.recognize(env) do |r, params| + assert_equal({ id: nil, controller: "foo", action: "bar" }, params) end end def test_recognize_with_unbound_regexp - mapper.get "/foo", anchor: false, to: "foo#bar" + get "/foo", anchor: false, to: "foo#bar" - env = rails_env 'PATH_INFO' => '/foo/bar' + env = rails_env "PATH_INFO" => "/foo/bar" - @router.recognize(env) { |*_| } + router.recognize(env) { |*_| } - assert_equal '/foo', env.env['SCRIPT_NAME'] - assert_equal '/bar', env.env['PATH_INFO'] + assert_equal "/foo", env.env["SCRIPT_NAME"] + assert_equal "/bar", env.env["PATH_INFO"] end def test_bound_regexp_keeps_path_info - mapper.get "/foo", to: "foo#bar" + get "/foo", to: "foo#bar" - env = rails_env 'PATH_INFO' => '/foo' + env = rails_env "PATH_INFO" => "/foo" - before = env.env['SCRIPT_NAME'] + before = env.env["SCRIPT_NAME"] - @router.recognize(env) { |*_| } + router.recognize(env) { |*_| } - assert_equal before, env.env['SCRIPT_NAME'] - assert_equal '/foo', env.env['PATH_INFO'] + assert_equal before, env.env["SCRIPT_NAME"] + assert_equal "/foo", env.env["PATH_INFO"] end def test_path_not_found @@ -174,60 +170,60 @@ module ActionDispatch "/messages/:id/edit(.:format)", "/messages/:id(.:format)" ].each do |path| - mapper.get path, to: "foo#bar" + get path, to: "foo#bar" end - env = rails_env 'PATH_INFO' => '/messages/unknown/path' + env = rails_env "PATH_INFO" => "/messages/unknown/path" yielded = false - @router.recognize(env) do |*whatever| + router.recognize(env) do |*whatever| yielded = true end assert_not yielded end def test_required_part_in_recall - mapper.get "/messages/:a/:b", to: "foo#bar" + get "/messages/:a/:b", to: "foo#bar" - path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar", :a => 'a' }, { :b => 'b' }) + path, _ = @formatter.generate(nil, { controller: "foo", action: "bar", a: "a" }, b: "b") assert_equal "/messages/a/b", path end def test_splat_in_recall - mapper.get "/*path", to: "foo#bar" + get "/*path", to: "foo#bar" - path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar" }, { :path => 'b' }) + path, _ = @formatter.generate(nil, { controller: "foo", action: "bar" }, path: "b") assert_equal "/b", path end def test_recall_should_be_used_when_scoring - mapper.get "/messages/:action(/:id(.:format))", to: 'foo#bar' - mapper.get "/messages/:id(.:format)", to: 'bar#baz' + get "/messages/:action(/:id(.:format))", to: "foo#bar" + get "/messages/:id(.:format)", to: "bar#baz" - path, _ = @formatter.generate(nil, { :controller => "foo", :id => 10 }, { :action => 'index' }) + path, _ = @formatter.generate(nil, { controller: "foo", id: 10 }, action: "index") assert_equal "/messages/index/10", path end def test_nil_path_parts_are_ignored - mapper.get "/:controller(/:action(.:format))", to: "tasks#lol" + get "/:controller(/:action(.:format))", to: "tasks#lol" - params = { :controller => "tasks", :format => nil } - extras = { :action => 'lol' } + params = { controller: "tasks", format: nil } + extras = { action: "lol" } path, _ = @formatter.generate(nil, params, extras) - assert_equal '/tasks', path + assert_equal "/tasks", path end def test_generate_slash params = [ [:controller, "tasks"], [:action, "show"] ] - mapper.get "/", Hash[params] + get "/", Hash[params] path, _ = @formatter.generate(nil, Hash[params], {}) - assert_equal '/', path + assert_equal "/", path end def test_generate_calls_param_proc - mapper.get '/:controller(/:action)', to: "foo#bar" + get "/:controller(/:action)", to: "foo#bar" parameterized = [] params = [ [:controller, "tasks"], @@ -243,65 +239,65 @@ module ActionDispatch end def test_generate_id - mapper.get '/:controller(/:action)', to: 'foo#bar' + get "/:controller(/:action)", to: "foo#bar" path, params = @formatter.generate( - nil, {:id=>1, :controller=>"tasks", :action=>"show"}, {}) - assert_equal '/tasks/show', path - assert_equal({:id => 1}, params) + nil, { id: 1, controller: "tasks", action: "show" }, {}) + assert_equal "/tasks/show", path + assert_equal({ id: 1 }, params) end def test_generate_escapes - mapper.get '/:controller(/:action)', to: "foo#bar" + get "/:controller(/:action)", to: "foo#bar" path, _ = @formatter.generate(nil, - { :controller => "tasks", - :action => "a/b c+d", + { controller: "tasks", + action: "a/b c+d", }, {}) - assert_equal '/tasks/a%2Fb%20c+d', path + assert_equal "/tasks/a%2Fb%20c+d", path end def test_generate_escapes_with_namespaced_controller - mapper.get '/:controller(/:action)', to: "foo#bar" + get "/:controller(/:action)", to: "foo#bar" path, _ = @formatter.generate( - nil, { :controller => "admin/tasks", - :action => "a/b c+d", + nil, { controller: "admin/tasks", + action: "a/b c+d", }, {}) - assert_equal '/admin/tasks/a%2Fb%20c+d', path + assert_equal "/admin/tasks/a%2Fb%20c+d", path end def test_generate_extra_params - mapper.get '/:controller(/:action)', to: "foo#bar" + get "/:controller(/:action)", to: "foo#bar" path, params = @formatter.generate( - nil, { :id => 1, - :controller => "tasks", - :action => "show", - :relative_url_root => nil + nil, { id: 1, + controller: "tasks", + action: "show", + relative_url_root: nil }, {}) - assert_equal '/tasks/show', path - assert_equal({:id => 1, :relative_url_root => nil}, params) + assert_equal "/tasks/show", path + assert_equal({ id: 1, relative_url_root: nil }, params) end def test_generate_missing_keys_no_matches_different_format_keys - mapper.get '/:controller/:action/:name', to: "foo#bar" + get "/:controller/:action/:name", to: "foo#bar" primarty_parameters = { - :id => 1, - :controller => "tasks", - :action => "show", - :relative_url_root => nil + id: 1, + controller: "tasks", + action: "show", + relative_url_root: nil } redirection_parameters = { - 'action'=>'show', + "action"=>"show", } - missing_key = 'name' + missing_key = "name" missing_parameters ={ missing_key => "task_1" } request_parameters = primarty_parameters.merge(redirection_parameters).merge(missing_parameters) - message = "No route matches #{Hash[request_parameters.sort_by{|k,v|k.to_s}].inspect} missing required keys: #{[missing_key.to_sym].inspect}" + message = "No route matches #{Hash[request_parameters.sort_by { |k,v|k.to_s }].inspect}, missing required keys: #{[missing_key.to_sym].inspect}" error = assert_raises(ActionController::UrlGenerationError) do @formatter.generate( @@ -311,42 +307,42 @@ module ActionDispatch end def test_generate_uses_recall_if_needed - mapper.get '/:controller(/:action(/:id))', to: "foo#bar" + get "/:controller(/:action(/:id))", to: "foo#bar" path, params = @formatter.generate( nil, - {:controller =>"tasks", :id => 10}, - {:action =>"index"}) - assert_equal '/tasks/index/10', path + { controller: "tasks", id: 10 }, + action: "index") + assert_equal "/tasks/index/10", path assert_equal({}, params) end def test_generate_with_name - mapper.get '/:controller(/:action)', to: 'foo#bar', as: 'tasks' + get "/:controller(/:action)", to: "foo#bar", as: "tasks" path, params = @formatter.generate( "tasks", - {:controller=>"tasks"}, - {:controller=>"tasks", :action=>"index"}) - assert_equal '/tasks', path + { controller: "tasks" }, + controller: "tasks", action: "index") + assert_equal "/tasks", path assert_equal({}, params) end { - '/content' => { :controller => 'content' }, - '/content/list' => { :controller => 'content', :action => 'list' }, - '/content/show/10' => { :controller => 'content', :action => 'show', :id => "10" }, + "/content" => { controller: "content" }, + "/content/list" => { controller: "content", action: "list" }, + "/content/show/10" => { controller: "content", action: "show", id: "10" }, }.each do |request_path, expected| define_method("test_recognize_#{expected.keys.map(&:to_s).join('_')}") do - mapper.get "/:controller(/:action(/:id))", to: 'foo#bar' + get "/:controller(/:action(/:id))", to: "foo#bar" route = @routes.first - env = rails_env 'PATH_INFO' => request_path + env = rails_env "PATH_INFO" => request_path called = false - @router.recognize(env) do |r, params| + router.recognize(env) do |r, params| assert_equal route, r - assert_equal({ :action => "bar" }.merge(expected), params) + assert_equal({ action: "bar" }.merge(expected), params) called = true end @@ -355,19 +351,19 @@ module ActionDispatch end { - :segment => ['/a%2Fb%20c+d/splat', { :segment => 'a/b c+d', :splat => 'splat' }], - :splat => ['/segment/a/b%20c+d', { :segment => 'segment', :splat => 'a/b c+d' }] + segment: ["/a%2Fb%20c+d/splat", { segment: "a/b c+d", splat: "splat" }], + splat: ["/segment/a/b%20c+d", { segment: "segment", splat: "a/b c+d" }] }.each do |name, (request_path, expected)| define_method("test_recognize_#{name}") do - mapper.get '/:segment/*splat', to: 'foo#bar' + get "/:segment/*splat", to: "foo#bar" - env = rails_env 'PATH_INFO' => request_path + env = rails_env "PATH_INFO" => request_path called = false route = @routes.first - @router.recognize(env) do |r, params| + router.recognize(env) do |r, params| assert_equal route, r - assert_equal(expected.merge(:controller=>"foo", :action=>"bar"), params) + assert_equal(expected.merge(controller: "foo", action: "bar"), params) called = true end @@ -376,18 +372,18 @@ module ActionDispatch end def test_namespaced_controller - mapper.get "/:controller(/:action(/:id))", { :controller => /.+?/ } + get "/:controller(/:action(/:id))", controller: /.+?/ route = @routes.first - env = rails_env 'PATH_INFO' => '/admin/users/show/10' + env = rails_env "PATH_INFO" => "/admin/users/show/10" called = false expected = { - :controller => 'admin/users', - :action => 'show', - :id => '10' + controller: "admin/users", + action: "show", + id: "10" } - @router.recognize(env) do |r, params| + router.recognize(env) do |r, params| assert_equal route, r assert_equal(expected, params) called = true @@ -396,13 +392,13 @@ module ActionDispatch end def test_recognize_literal - mapper.get "/books(/:action(.:format))", controller: "books" + get "/books(/:action(.:format))", controller: "books" route = @routes.first - env = rails_env 'PATH_INFO' => '/books/list.rss' - expected = { :controller => 'books', :action => 'list', :format => 'rss' } + env = rails_env "PATH_INFO" => "/books/list.rss" + expected = { controller: "books", action: "list", format: "rss" } called = false - @router.recognize(env) do |r, params| + router.recognize(env) do |r, params| assert_equal route, r assert_equal(expected, params) called = true @@ -412,15 +408,15 @@ module ActionDispatch end def test_recognize_head_route - mapper.match "/books(/:action(.:format))", via: 'head', to: 'foo#bar' + match "/books(/:action(.:format))", via: "head", to: "foo#bar" env = rails_env( - 'PATH_INFO' => '/books/list.rss', - 'REQUEST_METHOD' => 'HEAD' + "PATH_INFO" => "/books/list.rss", + "REQUEST_METHOD" => "HEAD" ) called = false - @router.recognize(env) do |r, params| + router.recognize(env) do |r, params| called = true end @@ -428,13 +424,13 @@ module ActionDispatch end def test_recognize_head_request_as_get_route - mapper.get "/books(/:action(.:format))", to: 'foo#bar' + get "/books(/:action(.:format))", to: "foo#bar" - env = rails_env 'PATH_INFO' => '/books/list.rss', + env = rails_env "PATH_INFO" => "/books/list.rss", "REQUEST_METHOD" => "HEAD" called = false - @router.recognize(env) do |r, params| + router.recognize(env) do |r, params| called = true end @@ -442,13 +438,13 @@ module ActionDispatch end def test_recognize_cares_about_get_verbs - mapper.match "/books(/:action(.:format))", to: "foo#bar", via: :get + match "/books(/:action(.:format))", to: "foo#bar", via: :get - env = rails_env 'PATH_INFO' => '/books/list.rss', + env = rails_env "PATH_INFO" => "/books/list.rss", "REQUEST_METHOD" => "POST" called = false - @router.recognize(env) do |r, params| + router.recognize(env) do |r, params| called = true end @@ -456,13 +452,13 @@ module ActionDispatch end def test_recognize_cares_about_post_verbs - mapper.match "/books(/:action(.:format))", to: "foo#bar", via: :post + match "/books(/:action(.:format))", to: "foo#bar", via: :post - env = rails_env 'PATH_INFO' => '/books/list.rss', + env = rails_env "PATH_INFO" => "/books/list.rss", "REQUEST_METHOD" => "POST" called = false - @router.recognize(env) do |r, params| + router.recognize(env) do |r, params| called = true end @@ -470,25 +466,25 @@ module ActionDispatch end def test_multi_verb_recognition - mapper.match "/books(/:action(.:format))", to: "foo#bar", via: [:post, :get] + match "/books(/:action(.:format))", to: "foo#bar", via: [:post, :get] %w( POST GET ).each do |verb| - env = rails_env 'PATH_INFO' => '/books/list.rss', + env = rails_env "PATH_INFO" => "/books/list.rss", "REQUEST_METHOD" => verb called = false - @router.recognize(env) do |r, params| + router.recognize(env) do |r, params| called = true end assert called end - env = rails_env 'PATH_INFO' => '/books/list.rss', - "REQUEST_METHOD" => 'PUT' + env = rails_env "PATH_INFO" => "/books/list.rss", + "REQUEST_METHOD" => "PUT" called = false - @router.recognize(env) do |r, params| + router.recognize(env) do |r, params| called = true end @@ -497,29 +493,41 @@ module ActionDispatch private - def rails_env env, klass = ActionDispatch::Request - klass.new(rack_env(env)) - end - - def rack_env env - { - "rack.version" => [1, 1], - "rack.input" => StringIO.new, - "rack.errors" => StringIO.new, - "rack.multithread" => true, - "rack.multiprocess" => true, - "rack.run_once" => false, - "REQUEST_METHOD" => "GET", - "SERVER_NAME" => "example.org", - "SERVER_PORT" => "80", - "QUERY_STRING" => "", - "PATH_INFO" => "/content", - "rack.url_scheme" => "http", - "HTTPS" => "off", - "SCRIPT_NAME" => "", - "CONTENT_LENGTH" => "0" - }.merge env - end + def get(*args) + ActiveSupport::Deprecation.silence do + mapper.get(*args) + end + end + + def match(*args) + ActiveSupport::Deprecation.silence do + mapper.match(*args) + end + end + + def rails_env(env, klass = ActionDispatch::Request) + klass.new(rack_env(env)) + end + + def rack_env(env) + { + "rack.version" => [1, 1], + "rack.input" => StringIO.new, + "rack.errors" => StringIO.new, + "rack.multithread" => true, + "rack.multiprocess" => true, + "rack.run_once" => false, + "REQUEST_METHOD" => "GET", + "SERVER_NAME" => "example.org", + "SERVER_PORT" => "80", + "QUERY_STRING" => "", + "PATH_INFO" => "/content", + "rack.url_scheme" => "http", + "HTTPS" => "off", + "SCRIPT_NAME" => "", + "CONTENT_LENGTH" => "0" + }.merge env + end end end end diff --git a/actionpack/test/journey/routes_test.rb b/actionpack/test/journey/routes_test.rb index f8293dfc5f..ca735ea022 100644 --- a/actionpack/test/journey/routes_test.rb +++ b/actionpack/test/journey/routes_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module ActionDispatch module Journey @@ -14,7 +14,7 @@ module ActionDispatch end def test_clear - mapper.get "/foo(/:id)", to: "foo#bar", as: 'aaron' + mapper.get "/foo(/:id)", to: "foo#bar", as: "aaron" assert_not_predicate routes, :empty? assert_equal 1, routes.length @@ -24,35 +24,35 @@ module ActionDispatch end def test_ast - mapper.get "/foo(/:id)", to: "foo#bar", as: 'aaron' + mapper.get "/foo(/:id)", to: "foo#bar", as: "aaron" ast = routes.ast - mapper.get "/foo(/:id)", to: "foo#bar", as: 'gorby' + mapper.get "/foo(/:id)", to: "foo#bar", as: "gorby" assert_not_equal ast, routes.ast end def test_simulator_changes - mapper.get "/foo(/:id)", to: "foo#bar", as: 'aaron' + mapper.get "/foo(/:id)", to: "foo#bar", as: "aaron" sim = routes.simulator - mapper.get "/foo(/:id)", to: "foo#bar", as: 'gorby' + mapper.get "/foo(/:id)", to: "foo#bar", as: "gorby" assert_not_equal sim, routes.simulator end def test_partition_route - mapper.get "/foo(/:id)", to: "foo#bar", as: 'aaron' + mapper.get "/foo(/:id)", to: "foo#bar", as: "aaron" assert_equal 1, @routes.anchored_routes.length assert_predicate @routes.custom_routes, :empty? - mapper.get "/hello/:who", to: "foo#bar", as: 'bar', who: /\d/ + mapper.get "/hello/:who", to: "foo#bar", as: "bar", who: /\d/ assert_equal 1, @routes.custom_routes.length assert_equal 1, @routes.anchored_routes.length end def test_first_name_wins - mapper.get "/hello", to: "foo#bar", as: 'aaron' + mapper.get "/hello", to: "foo#bar", as: "aaron" assert_raise(ArgumentError) do - mapper.get "/aaron", to: "foo#bar", as: 'aaron' + mapper.get "/aaron", to: "foo#bar", as: "aaron" end end end diff --git a/actionpack/test/routing/helper_test.rb b/actionpack/test/routing/helper_test.rb index 0028aaa629..0debacedf7 100644 --- a/actionpack/test/routing/helper_test.rb +++ b/actionpack/test/routing/helper_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module ActionDispatch module Routing diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index bebe78c360..67bd9b5c8f 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,276 +1,120 @@ -## Rails 5.0.0.beta2 (February 01, 2016) ## +* Removed deprecated `#original_exception` in `ActionView::Template::Error`. -* Fix stripping the digest from the automatically generated img tag alt - attribute when assets are handled by Sprockets >=3.0. - - *Bart de Water* - -* Create a new `ActiveSupport::SafeBuffer` instance when `content_for` is flushed. - - Fixes #19890 - - *Yoong Kang Lim* - -* Fix `collection_radio_buttons` hidden_field name and make it appear - before the actual input radio tags to make the real value override - the hidden when passed. - - Fixes #22773. - - *Santiago Pastorino* - -* `ActionView::TestCase::Controller#params` returns an instance of - `ActionController::Parameters`. - - *Justin Coyne* - -* Fix regression in `submit_tag` when a symbol is used as label argument. - - *Yuuji Yaginuma* - - -## Rails 5.0.0.beta1 (December 18, 2015) ## - -* `I18n.translate` helper will wrap the missing translation keys - in a <span> tag only if `debug_missing_translation` configuration - be true. Default value is `true`. For example in `application.rb`: - - # in order to turn off missing key wrapping - config.action_view.debug_missing_translation = false - - *Sameer Rahmani* - -* Respect value of `:object` if `:object` is false when rendering. - - Fixes #22260. - - *Yuichiro Kaneko* - -* Generate `week_field` input values using a 1-based index and not a 0-based index - as per the W3 spec: http://www.w3.org/TR/html-markup/datatypes.html#form.data.week - - *Christoph Geschwind* - -* Allow `host` option in `javascript_include_tag` and `stylesheet_link_tag` helpers - - *Grzegorz Witek* - -* Restrict `url_for :back` to valid, non-JavaScript URLs. GH#14444 - - *Damien Burke* - -* Allow `date_select` helper selected option to accept hash like the default options. - - *Lecky Lao* - -* Collection input propagates input's `id` to the label's `for` attribute when - using html options as the last element of collection. - - *Vasiliy Ermolovich* - -* Add a `hidden_field` on the `collection_radio_buttons` to avoid raising an error - when the only input on the form is the `collection_radio_buttons`. - - *Mauro George* - -* `url_for` does not modify its arguments when generating polymorphic URLs. - - *Bernerd Schaefer* - -* `number_to_currency` and `number_with_delimiter` now accept a custom `delimiter_pattern` option - to handle placement of delimiter, to support currency formats like INR. - - Example: - - number_to_currency(1230000, delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/, unit: '₹', format: "%u %n") - # => '₹ 12,30,000.00' - - *Vipul A M* - -* Make `disable_with` the default behavior for submit tags. Disables the - button on submit to prevent double submits. - - *Justin Schiff* - -* Add a break_sequence option to word_wrap so you can specify a custom break. + *Rafael Mendonça França* - *Mauricio Gomez* +* Render now accepts any keys for locals, including reserved words -* Add wildcard matching to explicit dependencies. + Only locals with valid variable names get set directly. Others + will still be available in local_assigns. - Turns: + Example of render with reserved words: ```erb - <% # Template Dependency: recordings/threads/events/subscribers_changed %> - <% # Template Dependency: recordings/threads/events/completed %> - <% # Template Dependency: recordings/threads/events/uncompleted %> - ``` - - Into: + <%= render "example", class: "text-center", message: "Hello world!" %> - ```erb - <% # Template Dependency: recordings/threads/events/* %> + <!-- _example.html.erb: --> + <%= tag.div class: local_assigns[:class] do %> + <p><%= message %></p> + <% end %> ``` - *Kasper Timm Hansen* - -* Allow defining explicit collection caching using a `# Template Collection: ...` - directive inside templates. + *Peter Schilling*, *Matthew Draper* - *Dov Murik* +* Show cache hits and misses when rendering partials. -* Asset helpers raise `ArgumentError` when `nil` is passed as a source. + Partials using the `cache` helper will show whether a render hit or missed + the cache: - *Anton Kolomiychuk* - -* Always attach the template digest to the cache key for collection caching - even when `virtual_path` is not available from the view context. - Which could happen if the rendering was done directly in the controller - and not in a template. - - Fixes #20535. - - *Roque Pinel* - -* Improve detection of partial templates eligible for collection caching, - now allowing multi-line comments at the beginning of the template file. - - *Dov Murik* - -* Raise an `ArgumentError` when a false value for `include_blank` is passed to a - required select field (to comply with the HTML5 spec). - - *Grey Baker* - -* Do not put partial name to `local_assigns` when rendering without - an object or a collection. - - *Henrik Nygren* - -* Remove `:rescue_format` option for `translate` helper since it's no longer - supported by I18n. - - *Bernard Potocki* - -* `translate` should handle `raise` flag correctly in case of both main and default - translation is missing. - - Fixes #19967. - - *Bernard Potocki* - -* Load the `default_form_builder` from the controller on initialization, which overrides - the global config if it is present. - - *Kevin McPhillips* - -* Accept lambda as `child_index` option in `fields_for` method. - - *Karol Galanciak* - -* `translate` allows `default: [[]]` again for a default value of `[]`. - - Fixes #19640. - - *Adam Prescott* - -* `translate` should accept nils as members of the `:default` - parameter without raising a translation missing error. - - Fixes #19419. + ``` + Rendered messages/_message.html.erb in 1.2 ms [cache hit] + Rendered recordings/threads/_thread.html.erb in 1.5 ms [cache miss] + ``` - *Justin Coyne* + This removes the need for the old fragment cache logging: -* `number_to_percentage` does not crash with `Float::NAN` or `Float::INFINITY` - as input when `precision: 0` is used. + ``` + Read fragment views/v1/2914079/v1/2914079/recordings/70182313-20160225015037000000/d0bdf2974e1ef6d31685c3b392ad0b74 (0.6ms) + Rendered messages/_message.html.erb in 1.2 ms [cache hit] + Write fragment views/v1/2914079/v1/2914079/recordings/70182313-20160225015037000000/3b4e249ac9d168c617e32e84b99218b5 (1.1ms) + Rendered recordings/threads/_thread.html.erb in 1.5 ms [cache miss] + ``` - Fixes #19227. + Though that full output can be reenabled with + `config.action_controller.enable_fragment_cache_logging = true`. - *Yves Senn* + *Stan Lo* -* Fixed the translation helper method to accept different default values types - besides String. +* Changed partial rendering with a collection to allow collections which + implement `to_a`. - *Ulisses Almeida* + Extracting the collection option had an optimization to avoid unnecessary + queries of ActiveRecord Relations by calling `#to_ary` on the given + collection. Instances of `Enumerator` or `Enumerable` are valid + collections, but they do not implement `#to_ary`. By changing this to + `#to_a`, they will now be extracted and rendered as expected. -* Collection rendering automatically caches and fetches multiple partials. + *Steven Harman* - Collections rendered as: +* New syntax for tag helpers. Avoid positional parameters and support HTML5 by default. + Example usage of tag helpers before: ```ruby - <%= render @notifications %> - <%= render partial: 'notifications/notification', collection: @notifications, as: :notification %> + tag(:br, nil, true) + content_tag(:div, content_tag(:p, "Hello world!"), class: "strong") + + <%= content_tag :div, class: "strong" do -%> + Hello world! + <% end -%> ``` - will now read several partials from cache at once, if the template starts with a cache call: + Example usage of tag helpers after: ```ruby - # notifications/_notification.html.erb - <% cache notification do %> - <%# ... %> + tag.br + tag.div tag.p("Hello world!"), class: "strong" + + <%= tag.div class: "strong" do %> + Hello world! <% end %> ``` - *Kasper Timm Hansen* - -* Fixed a dependency tracker bug that caused template dependencies not - count layouts as dependencies for partials. + *Marek Kirejczyk*, *Kasper Timm Hansen* - *Juho Leinonen* +* Change `datetime_field` and `datetime_field_tag` to generate `datetime-local` fields. -* Extracted `ActionView::Helpers::RecordTagHelper` to external gem - (`record_tag_helper`) and added removal notices. + As a new specification of the HTML 5 the text field type `datetime` will no longer exist + and it is recommended to use `datetime-local`. + Ref: https://html.spec.whatwg.org/multipage/forms.html#local-date-and-time-state-(type=datetime-local) - *Todd Bealmear* + *Herminio Torres* -* Allow to pass a string value to `size` option in `image_tag` and `video_tag`. +* Raw template handler (which is also the default template handler in Rails 5) now outputs + HTML-safe strings. - This makes the behavior more consistent with `width` or `height` options. + In Rails 5 the default template handler was changed to the raw template handler. Because + the ERB template handler escaped strings by default this broke some applications that + expected plain JS or HTML files to be rendered unescaped. This fixes the issue caused + by changing the default handler by changing the Raw template handler to output HTML-safe + strings. - *Mehdi Lahmam* + *Eileen M. Uchitelle* -* Partial template name does no more have to be a valid Ruby identifier. - - There used to be a naming rule that the partial name should start with - underscore, and should be followed by any combination of letters, numbers - and underscores. - But now we can give our partials any name starting with underscore, such as - _ðŸ”.html.erb. - - *Akira Matsuda* - -* Change the default template handler from `ERB` to `Raw`. - - Files without a template handler in their extension will be rendered using the raw - handler instead of ERB. - - *Rafael Mendonça França* +* `select_tag`'s `include_blank` option for generation for blank option tag, now adds an empty space label, + when the value as well as content for option tag are empty, so that we conform with html specification. + Ref: https://www.w3.org/TR/html5/forms.html#the-option-element. -* Remove deprecated `AbstractController::Base::parent_prefixes`. + Generation of option before: - *Rafael Mendonça França* - -* Default translations that have a lower precedence than a html safe default, - but are not themselves safe, should not be marked as html_safe. - - *Justin Coyne* - -* Make possible to use blocks with short version of `render "partial"` helper. - - *Nikolay Shebanov* - -* Add a `hidden_field` on the `file_field` to avoid raising an error when the only - input on the form is the `file_field`. - - *Mauro George* - -* Add an explicit error message, in `ActionView::PartialRenderer` for partial - `rendering`, when the value of option `as` has invalid characters. + ```html + <option value=""></option> + ``` - *Angelo Capilleri* + Generation of option after: -* Allow entries without a link tag in `AtomFeedHelper`. + ```html + <option value="" label=" "></option> + ``` - *Daniel Gomez de Souza* + *Vipul A M* -Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/actionview/CHANGELOG.md) for previous changes. +Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/actionview/CHANGELOG.md) for previous changes. diff --git a/actionview/Rakefile b/actionview/Rakefile index d41030c650..6c3fc59b0a 100644 --- a/actionview/Rakefile +++ b/actionview/Rakefile @@ -1,44 +1,43 @@ -require 'rake/testtask' +require "rake/testtask" desc "Default Task" -task :default => :test +task default: :test task :package -task "package:clean" # Run the unit tests desc "Run all unit tests" -task :test => ["test:template", "test:integration:action_pack", "test:integration:active_record"] +task test: ["test:template", "test:integration:action_pack", "test:integration:active_record"] namespace :test do task :isolated do Dir.glob("test/{actionpack,activerecord,template}/**/*_test.rb").all? do |file| - sh(Gem.ruby, '-w', '-Ilib:test', file) - end or raise "Failures" + sh(Gem.ruby, "-w", "-Ilib:test", file) + end || raise("Failures") end Rake::TestTask.new(:template) do |t| - t.libs << 'test' - t.test_files = Dir.glob('test/template/**/*_test.rb') + t.libs << "test" + t.test_files = Dir.glob("test/template/**/*_test.rb") t.warning = true t.verbose = true t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end namespace :integration do - desc 'ActiveRecord Integration Tests' + desc "ActiveRecord Integration Tests" Rake::TestTask.new(:active_record) do |t| - t.libs << 'test' + t.libs << "test" t.test_files = Dir.glob("test/activerecord/*_test.rb") t.warning = true t.verbose = true t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end - desc 'ActionPack Integration Tests' + desc "ActionPack Integration Tests" Rake::TestTask.new(:action_pack) do |t| - t.libs << 'test' + t.libs << "test" t.test_files = Dir.glob("test/actionpack/**/*_test.rb") t.warning = true t.verbose = true @@ -48,7 +47,7 @@ namespace :test do end task :lines do - load File.expand_path('..', File.dirname(__FILE__)) + '/tools/line_statistics' + load File.expand_path("..", File.dirname(__FILE__)) + "/tools/line_statistics" files = FileList["lib/**/*.rb"] CodeTools::LineStatistics.new(files).print_loc end diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec index 612e94021d..7bfdfbe29a 100644 --- a/actionview/actionview.gemspec +++ b/actionview/actionview.gemspec @@ -2,30 +2,30 @@ version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY - s.name = 'actionview' + s.name = "actionview" s.version = version - s.summary = 'Rendering framework putting the V in MVC (part of Rails).' - s.description = 'Simple, battle-tested conventions and helpers for building web pages.' + s.summary = "Rendering framework putting the V in MVC (part of Rails)." + s.description = "Simple, battle-tested conventions and helpers for building web pages." - s.required_ruby_version = '>= 2.2.2' + s.required_ruby_version = ">= 2.2.2" - s.license = 'MIT' + s.license = "MIT" - s.author = 'David Heinemeier Hansson' - s.email = 'david@loudthinking.com' - s.homepage = 'http://www.rubyonrails.org' + s.author = "David Heinemeier Hansson" + s.email = "david@loudthinking.com" + s.homepage = "http://rubyonrails.org" - s.files = Dir['CHANGELOG.md', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*'] - s.require_path = 'lib' - s.requirements << 'none' + s.files = Dir["CHANGELOG.md", "README.rdoc", "MIT-LICENSE", "lib/**/*"] + s.require_path = "lib" + s.requirements << "none" - s.add_dependency 'activesupport', version + 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 'rails-dom-testing', '~> 1.0', '>= 1.0.5' + 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 "rails-dom-testing", "~> 2.0" - s.add_development_dependency 'actionpack', version - s.add_development_dependency 'activemodel', version + s.add_development_dependency "actionpack", version + s.add_development_dependency "activemodel", version end diff --git a/actionview/bin/test b/actionview/bin/test index 404cabba51..84a05bba08 100755 --- a/actionview/bin/test +++ b/actionview/bin/test @@ -1,4 +1,6 @@ #!/usr/bin/env ruby -COMPONENT_ROOT = File.expand_path("../../", __FILE__) + +COMPONENT_ROOT = File.expand_path("..", __dir__) require File.expand_path("../tools/test", COMPONENT_ROOT) + exit Minitest.run(ARGV) diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb index 0a87500a52..ba6755be82 100644 --- a/actionview/lib/action_view.rb +++ b/actionview/lib/action_view.rb @@ -21,9 +21,9 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ -require 'active_support' -require 'active_support/rails' -require 'action_view/version' +require "active_support" +require "active_support/rails" +require "action_view/version" module ActionView extend ActiveSupport::Autoload @@ -89,7 +89,7 @@ module ActionView end end -require 'active_support/core_ext/string/output_safety' +require "active_support/core_ext/string/output_safety" ActiveSupport.on_load(:i18n) do I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en.yml" diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb index ad1cb1a4be..b7c05fdb88 100644 --- a/actionview/lib/action_view/base.rb +++ b/actionview/lib/action_view/base.rb @@ -1,11 +1,11 @@ -require 'active_support/core_ext/module/attr_internal' -require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/ordered_options' -require 'action_view/log_subscriber' -require 'action_view/helpers' -require 'action_view/context' -require 'action_view/template' -require 'action_view/lookup_context' +require "active_support/core_ext/module/attr_internal" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/ordered_options" +require "action_view/log_subscriber" +require "action_view/helpers" +require "action_view/context" +require "action_view/template" +require "action_view/lookup_context" module ActionView #:nodoc: # = Action View Base @@ -17,7 +17,7 @@ module ActionView #:nodoc: # # == ERB # - # You trigger ERB by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the + # You trigger ERB by using embeddings such as <tt><% %></tt>, <tt><% -%></tt>, and <tt><%= %></tt>. The <tt><%= %></tt> tag set is used when you want output. Consider the # following loop for names: # # <b>Names of all the people</b> @@ -25,7 +25,7 @@ module ActionView #:nodoc: # Name: <%= person.name %><br/> # <% end %> # - # The loop is setup in regular embedding tags <% %> and the name is written using the output embedding tag <%= %>. Note that this + # The loop is setup in regular embedding tags <tt><% %></tt>, and the name is written using the output embedding tag <tt><%= %></tt>. Note that this # is not just a usage suggestion. Regular output functions like print or puts won't work with ERB templates. So this would be wrong: # # <%# WRONG %> @@ -33,9 +33,9 @@ module ActionView #:nodoc: # # If you absolutely must write from within a function use +concat+. # - # When on a line that only contains whitespaces except for the tag, <% %> suppress leading and trailing whitespace, - # including the trailing newline. <% %> and <%- -%> are the same. - # Note however that <%= %> and <%= -%> are different: only the latter removes trailing whitespaces. + # When on a line that only contains whitespaces except for the tag, <tt><% %></tt> suppresses leading and trailing whitespace, + # including the trailing newline. <tt><% %></tt> and <tt><%- -%></tt> are the same. + # Note however that <tt><%= %></tt> and <tt><%= -%></tt> are different: only the latter removes trailing whitespaces. # # === Using sub templates # @@ -110,7 +110,7 @@ module ActionView #:nodoc: # <p>A product of Danish Design during the Winter of '79...</p> # </div> # - # A full-length RSS example actually used on Basecamp: + # Here is a full-length RSS example actually used on Basecamp: # # xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do # xml.channel do @@ -141,7 +141,7 @@ module ActionView #:nodoc: # Specify the proc used to decorate input tags that refer to attributes with errors. cattr_accessor :field_error_proc - @@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe } + @@field_error_proc = Proc.new { |html_tag, instance| "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe } # How to complete the streaming when an exception occurs. # This is our best guess: first try to close the attribute, then the tag. @@ -169,7 +169,7 @@ module ActionView #:nodoc: class_attribute :logger class << self - delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB' + delegate :erb_trim_mode=, to: "ActionView::Template::Handlers::ERB" def cache_template_loading ActionView::Resolver.caching? @@ -187,8 +187,8 @@ module ActionView #:nodoc: attr_accessor :view_renderer attr_internal :config, :assigns - delegate :lookup_context, :to => :view_renderer - delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, :to => :lookup_context + delegate :lookup_context, to: :view_renderer + delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, to: :lookup_context def assign(new_assigns) # :nodoc: @_assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) } diff --git a/actionview/lib/action_view/buffers.rb b/actionview/lib/action_view/buffers.rb index be5d86b1dc..089daa6d60 100644 --- a/actionview/lib/action_view/buffers.rb +++ b/actionview/lib/action_view/buffers.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/string/output_safety' +require "active_support/core_ext/string/output_safety" module ActionView class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc: diff --git a/actionview/lib/action_view/dependency_tracker.rb b/actionview/lib/action_view/dependency_tracker.rb index 7731773040..451eeec9d6 100644 --- a/actionview/lib/action_view/dependency_tracker.rb +++ b/actionview/lib/action_view/dependency_tracker.rb @@ -1,5 +1,5 @@ -require 'concurrent/map' -require 'action_view/path_set' +require "concurrent/map" +require "action_view/path_set" module ActionView class DependencyTracker # :nodoc: @@ -105,7 +105,6 @@ module ActionView attr_reader :name, :template private :name, :template - private def source template.source @@ -142,7 +141,7 @@ module ActionView def add_static_dependency(dependencies, dependency) if dependency - if dependency.include?('/') + if dependency.include?("/") dependencies << dependency else dependencies << "#{directory}/#{dependency}" @@ -163,7 +162,7 @@ module ActionView def explicit_dependencies dependencies = source.scan(EXPLICIT_DEPENDENCY).flatten.uniq - wildcards, explicits = dependencies.partition { |dependency| dependency[-1] == '*' } + wildcards, explicits = dependencies.partition { |dependency| dependency[-1] == "*" } (explicits + resolve_directories(wildcards)).uniq end diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb index 657026fa14..0658d8601d 100644 --- a/actionview/lib/action_view/digestor.rb +++ b/actionview/lib/action_view/digestor.rb @@ -1,17 +1,14 @@ -require 'concurrent/map' -require 'action_view/dependency_tracker' -require 'monitor' +require "concurrent/map" +require "action_view/dependency_tracker" +require "monitor" module ActionView class Digestor - cattr_reader(:cache) - @@cache = Concurrent::Map.new - @@digest_monitor = Monitor.new - - class PerRequestDigestCacheExpiry < Struct.new(:app) # :nodoc: - def call(env) - ActionView::Digestor.cache.clear - app.call(env) + @@digest_mutex = Mutex.new + + module PerExecutionDigestCacheExpiry + def self.before(target) + ActionView::LookupContext::DetailsKey.clear end end @@ -21,112 +18,107 @@ module ActionView # * <tt>name</tt> - Template name # * <tt>finder</tt> - An instance of <tt>ActionView::LookupContext</tt> # * <tt>dependencies</tt> - An array of dependent views - # * <tt>partial</tt> - Specifies whether the template is a partial - def digest(name:, finder:, **options) - options.assert_valid_keys(:dependencies, :partial) - - cache_key = ([ name, finder.details_key.hash ].compact + Array.wrap(options[:dependencies])).join('.') + def digest(name:, finder:, dependencies: []) + dependencies ||= [] + cache_key = [ name, finder.rendered_format, dependencies ].flatten.compact.join(".") # this is a correctly done double-checked locking idiom # (Concurrent::Map's lookups have volatile semantics) - @@cache[cache_key] || @@digest_monitor.synchronize do - @@cache.fetch(cache_key) do # re-check under lock - compute_and_store_digest(cache_key, name, finder, options) + finder.digest_cache[cache_key] || @@digest_mutex.synchronize do + finder.digest_cache.fetch(cache_key) do # re-check under lock + partial = name.include?("/_") + root = tree(name, finder, partial) + dependencies.each do |injected_dep| + root.children << Injected.new(injected_dep, nil, nil) + end + finder.digest_cache[cache_key] = root.digest(finder) end end end - private - def compute_and_store_digest(cache_key, name, finder, options) # called under @@digest_monitor lock - klass = if options[:partial] || name.include?("/_") - # Prevent re-entry or else recursive templates will blow the stack. - # There is no need to worry about other threads seeing the +false+ value, - # as they will then have to wait for this thread to let go of the @@digest_monitor lock. - pre_stored = @@cache.put_if_absent(cache_key, false).nil? # put_if_absent returns nil on insertion - PartialDigestor - else - Digestor - end - - @@cache[cache_key] = stored_digest = klass.new(name, finder, options).digest - ensure - # something went wrong or ActionView::Resolver.caching? is false, make sure not to corrupt the @@cache - @@cache.delete_pair(cache_key, false) if pre_stored && !stored_digest - end - end + def logger + ActionView::Base.logger || NullLogger + end - attr_reader :name, :finder, :options + # Create a dependency tree for template named +name+. + def tree(name, finder, partial = false, seen = {}) + logical_name = name.gsub(%r|/_|, "/") - def initialize(name, finder, options = {}) - @name, @finder = name, finder - @options = options - end + options = {} + options[:formats] = [finder.rendered_format] if finder.rendered_format - def digest - Digest::MD5.hexdigest("#{source}-#{dependency_digest}").tap do |digest| - logger.debug " Cache digest for #{template.inspect}: #{digest}" - end - rescue ActionView::MissingTemplate - logger.error " Couldn't find template for digesting: #{name}" - '' - end + if template = finder.disable_cache { finder.find_all(logical_name, [], partial, [], options).first } + finder.rendered_format ||= template.formats.first - def dependencies - DependencyTracker.find_dependencies(name, template, finder.view_paths) - rescue ActionView::MissingTemplate - logger.error " '#{name}' file doesn't exist, so no dependencies" - [] - end + if node = seen[template.identifier] # handle cycles in the tree + node + else + node = seen[template.identifier] = Node.create(name, logical_name, template, partial) - def nested_dependencies - dependencies.collect do |dependency| - dependencies = PartialDigestor.new(dependency, finder).nested_dependencies - dependencies.any? ? { dependency => dependencies } : dependency + deps = DependencyTracker.find_dependencies(name, template, finder.view_paths) + deps.uniq { |n| n.gsub(%r|/_|, "/") }.each do |dep_file| + node.children << tree(dep_file, finder, true, seen) + end + node + end + else + logger.error " '#{name}' file doesn't exist, so no dependencies" + logger.error " Couldn't find template for digesting: #{name}" + seen[name] ||= Missing.new(name, logical_name, nil) + end end end - private - class NullLogger - def self.debug(_); end - def self.error(_); end - end + class Node + attr_reader :name, :logical_name, :template, :children - def logger - ActionView::Base.logger || NullLogger + def self.create(name, logical_name, template, partial) + klass = partial ? Partial : Node + klass.new(name, logical_name, template, []) end - def logical_name - name.gsub(%r|/_|, "/") + def initialize(name, logical_name, template, children = []) + @name = name + @logical_name = logical_name + @template = template + @children = children end - def partial? - false + def digest(finder, stack = []) + Digest::MD5.hexdigest("#{template.source}-#{dependency_digest(finder, stack)}") end - def template - @template ||= finder.disable_cache { finder.find(logical_name, [], partial?) } + def dependency_digest(finder, stack) + children.map do |node| + if stack.include?(node) + false + else + finder.digest_cache[node.name] ||= begin + stack.push node + node.digest(finder, stack).tap { stack.pop } + end + end + end.join("-") end - def source - template.source + def to_dep_map + children.any? ? { name => children.map(&:to_dep_map) } : name end + end - def dependency_digest - template_digests = dependencies.collect do |template_name| - Digestor.digest(name: template_name, finder: finder, partial: true) - end + class Partial < Node; end - (template_digests + injected_dependencies).join("-") - end + class Missing < Node + def digest(finder, _ = []) "" end + end - def injected_dependencies - Array.wrap(options[:dependencies]) - end - end + class Injected < Node + def digest(finder, _ = []) name end + end - class PartialDigestor < Digestor # :nodoc: - def partial? - true + class NullLogger + def self.debug(_); end + def self.error(_); end end end end diff --git a/actionview/lib/action_view/flows.rb b/actionview/lib/action_view/flows.rb index bc61920848..16874c1194 100644 --- a/actionview/lib/action_view/flows.rb +++ b/actionview/lib/action_view/flows.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/string/output_safety' +require "active_support/core_ext/string/output_safety" module ActionView class OutputFlow #:nodoc: @@ -23,7 +23,6 @@ module ActionView @content[key] << value end alias_method :append!, :append - end class StreamingFlow < OutputFlow #:nodoc: @@ -37,9 +36,8 @@ module ActionView end # Try to get stored content. If the content - # is not available and we are inside the layout - # fiber, we set that we are waiting for the given - # key and yield. + # is not available and we're inside the layout fiber, + # then it will begin waiting for the given key and yield. def get(key) return super if @content.key?(key) @@ -60,8 +58,8 @@ module ActionView end # Appends the contents for the given key. This is called - # by provides and resumes back to the fiber if it is - # the key it is waiting for. + # by providing and resuming back to the fiber, + # if that's the key it's waiting for. def append!(key, value) super @fiber.resume if @waiting_for == key @@ -69,8 +67,8 @@ module ActionView private - def inside_fiber? - Fiber.current.object_id != @root - end + def inside_fiber? + Fiber.current.object_id != @root + end end end diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb index bb5c96cb39..5fc4f3f1b9 100644 --- a/actionview/lib/action_view/gem_version.rb +++ b/actionview/lib/action_view/gem_version.rb @@ -6,9 +6,9 @@ module ActionView module VERSION MAJOR = 5 - MINOR = 0 + MINOR = 1 TINY = 0 - PRE = "beta2" + PRE = "alpha" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actionview/lib/action_view/helpers.rb b/actionview/lib/action_view/helpers.rb index 787e9d67b2..c1b4b4f84b 100644 --- a/actionview/lib/action_view/helpers.rb +++ b/actionview/lib/action_view/helpers.rb @@ -1,4 +1,4 @@ -require 'active_support/benchmarkable' +require "active_support/benchmarkable" module ActionView #:nodoc: module Helpers #:nodoc: diff --git a/actionview/lib/action_view/helpers/active_model_helper.rb b/actionview/lib/action_view/helpers/active_model_helper.rb index d5222e3616..4bb5788a16 100644 --- a/actionview/lib/action_view/helpers/active_model_helper.rb +++ b/actionview/lib/action_view/helpers/active_model_helper.rb @@ -1,5 +1,5 @@ -require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/core_ext/enumerable' +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/enumerable" module ActionView # = Active Model Helpers @@ -37,13 +37,13 @@ module ActionView private - def object_has_errors? - object.respond_to?(:errors) && object.errors.respond_to?(:[]) && error_message.present? - end + def object_has_errors? + object.respond_to?(:errors) && object.errors.respond_to?(:[]) && error_message.present? + end - def tag_generate_errors?(options) - options['type'] != 'hidden' - end + def tag_generate_errors?(options) + options["type"] != "hidden" + end end end end diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index 413c35954c..39fa11ccfd 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -1,7 +1,7 @@ -require 'active_support/core_ext/array/extract_options' -require 'active_support/core_ext/hash/keys' -require 'action_view/helpers/asset_url_helper' -require 'action_view/helpers/tag_helper' +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/hash/keys" +require "action_view/helpers/asset_url_helper" +require "action_view/helpers/tag_helper" module ActionView # = Action View Asset Tag Helpers @@ -55,7 +55,7 @@ module ActionView # # => <script src="http://www.example.com/xmlhr.js"></script> def javascript_include_tag(*sources) options = sources.extract_options!.stringify_keys - path_options = options.extract!('protocol', 'extname', 'host').symbolize_keys + path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys sources.uniq.map { |source| tag_options = { "src" => path_to_javascript(source, path_options) @@ -91,8 +91,7 @@ module ActionView # # <link href="/css/stylish.css" media="screen" rel="stylesheet" /> def stylesheet_link_tag(*sources) options = sources.extract_options!.stringify_keys - path_options = options.extract!('protocol', 'host').symbolize_keys - + path_options = options.extract!("protocol", "host", "skip_pipeline").symbolize_keys sources.uniq.map { |source| tag_options = { "rel" => "stylesheet", @@ -138,7 +137,7 @@ module ActionView "rel" => tag_options[:rel] || "alternate", "type" => tag_options[:type] || Template::Types[type].to_s, "title" => tag_options[:title] || type.to_s.upcase, - "href" => url_options.is_a?(Hash) ? url_for(url_options.merge(:only_path => false)) : url_options + "href" => url_options.is_a?(Hash) ? url_for(url_options.merge(only_path: false)) : url_options ) end @@ -169,11 +168,11 @@ module ActionView # # favicon_link_tag 'mb-icon.png', rel: 'apple-touch-icon', type: 'image/png' # # => <link href="/assets/mb-icon.png" rel="apple-touch-icon" type="image/png" /> - def favicon_link_tag(source='favicon.ico', options={}) - tag('link', { - :rel => 'shortcut icon', - :type => 'image/x-icon', - :href => path_to_image(source) + def favicon_link_tag(source="favicon.ico", options={}) + tag("link", { + rel: "shortcut icon", + type: "image/x-icon", + href: path_to_image(source, skip_pipeline: options.delete(:skip_pipeline)) }.merge!(options.symbolize_keys)) end @@ -211,10 +210,10 @@ module ActionView options = options.symbolize_keys check_for_image_tag_errors(options) - src = options[:src] = path_to_image(source) + src = options[:src] = path_to_image(source, skip_pipeline: options.delete(:skip_pipeline)) - unless src =~ /^(?:cid|data):/ || src.blank? - options[:alt] = options.fetch(:alt){ image_alt(src) } + unless src.start_with?("cid:") || src.start_with?("data:") || src.blank? + options[:alt] = options.fetch(:alt) { image_alt(src) } end options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size] @@ -239,7 +238,7 @@ module ActionView # image_alt('underscored_file_name.png') # # => Underscored file name def image_alt(src) - File.basename(src, '.*'.freeze).sub(/-[[:xdigit:]]{32,64}\z/, ''.freeze).tr('-_'.freeze, ' '.freeze).capitalize + File.basename(src, ".*".freeze).sub(/-[[:xdigit:]]{32,64}\z/, "".freeze).tr("-_".freeze, " ".freeze).capitalize end # Returns an HTML video tag for the +sources+. If +sources+ is a string, @@ -257,6 +256,8 @@ module ActionView # * <tt>:size</tt> - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes # width="30" and height="45", and "50" becomes width="50" and height="50". # <tt>:size</tt> will be ignored if the value is not in the correct format. + # * <tt>:poster_skip_pipeline</tt> will bypass the asset pipeline when using + # the <tt>:poster</tt> option instead using an asset in the public folder. # # ==== Examples # @@ -264,10 +265,12 @@ module ActionView # # => <video src="/videos/trailer"></video> # video_tag("trailer.ogg") # # => <video src="/videos/trailer.ogg"></video> - # video_tag("trailer.ogg", controls: true, autobuffer: true) - # # => <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" ></video> + # video_tag("trailer.ogg", controls: true, preload: 'none') + # # => <video preload="none" controls="controls" src="/videos/trailer.ogg" ></video> # video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png") # # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png"></video> + # video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png", poster_skip_pipeline: true) + # # => <video src="/videos/trailer.m4v" width="16" height="10" poster="screenshot.png"></video> # video_tag("/trailers/hd.avi", size: "16x16") # # => <video src="/trailers/hd.avi" width="16" height="16"></video> # video_tag("/trailers/hd.avi", size: "16") @@ -281,9 +284,12 @@ module ActionView # video_tag(["trailer.ogg", "trailer.flv"], size: "160x120") # # => <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video> def video_tag(*sources) - multiple_sources_tag('video', sources) do |options| - options[:poster] = path_to_image(options[:poster]) if options[:poster] - options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size] + options = sources.extract_options!.symbolize_keys + public_poster_folder = options.delete(:poster_skip_pipeline) + sources << options + multiple_sources_tag_builder("video", sources) do |tag_options| + tag_options[:poster] = path_to_image(tag_options[:poster], skip_pipeline: public_poster_folder) if tag_options[:poster] + tag_options[:width], tag_options[:height] = extract_dimensions(tag_options.delete(:size)) if tag_options[:size] end end @@ -300,31 +306,32 @@ module ActionView # audio_tag("sound.wav", "sound.mid") # # => <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio> def audio_tag(*sources) - multiple_sources_tag('audio', sources) + multiple_sources_tag_builder("audio", sources) end private - def multiple_sources_tag(type, sources) - options = sources.extract_options!.symbolize_keys + def multiple_sources_tag_builder(type, sources) + options = sources.extract_options!.symbolize_keys + skip_pipeline = options.delete(:skip_pipeline) sources.flatten! yield options if block_given? if sources.size > 1 content_tag(type, options) do - safe_join sources.map { |source| tag("source", :src => send("path_to_#{type}", source)) } + safe_join sources.map { |source| tag("source", src: send("path_to_#{type}", source, skip_pipeline: skip_pipeline)) } end else - options[:src] = send("path_to_#{type}", sources.first) + options[:src] = send("path_to_#{type}", sources.first, skip_pipeline: skip_pipeline) content_tag(type, nil, options) end end def extract_dimensions(size) size = size.to_s - if size =~ %r{\A\d+x\d+\z} - size.split('x') - elsif size =~ %r{\A\d+\z} + if /\A\d+x\d+\z/.match?(size) + size.split("x") + elsif /\A\d+\z/.match?(size) [size, size] end end diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb index 717b326740..fab49e402b 100644 --- a/actionview/lib/action_view/helpers/asset_url_helper.rb +++ b/actionview/lib/action_view/helpers/asset_url_helper.rb @@ -1,4 +1,4 @@ -require 'zlib' +require "zlib" module ActionView # = Action View Asset URL Helpers @@ -36,7 +36,7 @@ module ActionView # some asset downloads to wait for previous assets to finish before they can # begin. You can use the <tt>%d</tt> wildcard in the +asset_host+ to # distribute the requests over four hosts. For example, - # <tt>assets%d.example.com<tt> will spread the asset requests over + # <tt>assets%d.example.com</tt> will spread the asset requests over # "assets0.example.com", ..., "assets3.example.com". # # image_tag("rails.png") @@ -117,31 +117,86 @@ module ActionView module AssetUrlHelper URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//}i - # Computes the path to asset in public directory. If :type - # options is set, a file extension will be appended and scoped - # to the corresponding public directory. + # This is the entry point for all assets. + # When using the asset pipeline (i.e. sprockets and sprockets-rails), the + # behavior is "enhanced". You can bypass the asset pipeline by passing in + # <tt>skip_pipeline: true</tt> to the options. # # All other asset *_path helpers delegate through this method. # - # asset_path "application.js" # => /assets/application.js - # asset_path "application", type: :javascript # => /assets/application.js - # asset_path "application", type: :stylesheet # => /assets/application.css - # asset_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js + # === With the asset pipeline + # + # All options passed to +asset_path+ will be passed to +compute_asset_path+ + # which is implemented by sprockets-rails. + # + # asset_path("application.js") # => "/assets/application-60aa4fdc5cea14baf5400fba1abf4f2a46a5166bad4772b1effe341570f07de9.js" + # + # === Without the asset pipeline (<tt>skip_pipeline: true</tt>) + # + # Accepts a <tt>type</tt> option that can specify the asset's extension. No error + # checking is done to verify the source passed into +asset_path+ is valid + # and that the file exists on disk. + # + # asset_path("application.js", skip_pipeline: true) # => "application.js" + # asset_path("filedoesnotexist.png", skip_pipeline: true) # => "filedoesnotexist.png" + # asset_path("application", type: :javascript, skip_pipeline: true) # => "/javascripts/application.js" + # asset_path("application", type: :stylesheet, skip_pipeline: true) # => "/stylesheets/application.css" + # + # === Options applying to all assets + # + # Below lists scenarios that apply to +asset_path+ whether or not you're + # using the asset pipeline. + # + # - All fully qualified urls are returned immediately. This bypasses the + # asset pipeline and all other behavior described. + # + # asset_path("http://www.example.com/js/xmlhr.js") # => "http://www.example.com/js/xmlhr.js" + # + # - All assets that begin with a forward slash are assumed to be full + # urls and will not be expanded. This will bypass the asset pipeline. + # + # asset_path("/foo.png") # => "/foo.png" + # + # - All blank strings will be returned immediately. This bypasses the + # asset pipeline and all other behavior described. + # + # asset_path("") # => "" + # + # - If <tt>config.relative_url_root</tt> is specified, all assets will have that + # root prepended. + # + # Rails.application.config.relative_url_root = "bar" + # asset_path("foo.js", skip_pipeline: true) # => "bar/foo.js" + # + # - A different asset host can be specified via <tt>config.action_controller.asset_host</tt> + # this is commonly used in conjunction with a CDN. + # + # Rails.application.config.action_controller.asset_host = "assets.example.com" + # asset_path("foo.js", skip_pipeline: true) # => "http://assets.example.com/foo.js" + # + # - An extension name can be specified manually with <tt>extname</tt>. + # + # asset_path("foo", skip_pipeline: true, extname: ".js") # => "/foo.js" + # asset_path("foo.css", skip_pipeline: true, extname: ".js") # => "/foo.css.js" def asset_path(source, options = {}) raise ArgumentError, "nil is not a valid asset source" if source.nil? source = source.to_s - return "" unless source.present? - return source if source =~ URI_REGEXP + return "" if source.blank? + return source if URI_REGEXP.match?(source) - tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, ''.freeze) + tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, "".freeze) if extname = compute_asset_extname(source, options) source = "#{source}#{extname}" end if source[0] != ?/ - source = compute_asset_path(source, options) + if options[:skip_pipeline] + source = public_compute_asset_path(source, options) + else + source = compute_asset_path(source, options) + end end relative_url_root = defined?(config.relative_url_root) && config.relative_url_root @@ -168,13 +223,13 @@ module ActionView # asset_url "application.js", host: "http://cdn.example.com" # => http://cdn.example.com/assets/application.js # def asset_url(source, options = {}) - path_to_asset(source, options.merge(:protocol => :request)) + path_to_asset(source, options.merge(protocol: :request)) end alias_method :url_to_asset, :asset_url # aliased to avoid conflicts with an asset_url named route ASSET_EXTENSIONS = { - javascript: '.js', - stylesheet: '.css' + javascript: ".js", + stylesheet: ".css" } # Compute extname to append to asset path. Returns nil if @@ -187,12 +242,12 @@ module ActionView # Maps asset types to public directory. ASSET_PUBLIC_DIRECTORIES = { - audio: '/audios', - font: '/fonts', - image: '/images', - javascript: '/javascripts', - stylesheet: '/stylesheets', - video: '/videos' + audio: "/audios", + font: "/fonts", + image: "/images", + javascript: "/javascripts", + stylesheet: "/stylesheets", + video: "/videos" } # Computes asset path to public directory. Plugins and @@ -202,6 +257,7 @@ module ActionView dir = ASSET_PUBLIC_DIRECTORIES[options[:type]] || "" File.join(dir, source) end + alias :public_compute_asset_path :compute_asset_path # Pick an asset host for this source. Returns +nil+ if no host is set, # the host if no wildcard is set, the host interpolated with the @@ -213,19 +269,21 @@ module ActionView host = options[:host] host ||= config.asset_host if defined? config.asset_host - if host.respond_to?(:call) - arity = host.respond_to?(:arity) ? host.arity : host.method(:call).arity - args = [source] - args << request if request && (arity > 1 || arity < 0) - host = host.call(*args) - elsif host =~ /%d/ - host = host % (Zlib.crc32(source) % 4) + if host + if host.respond_to?(:call) + arity = host.respond_to?(:arity) ? host.arity : host.method(:call).arity + args = [source] + args << request if request && (arity > 1 || arity < 0) + host = host.call(*args) + elsif host.include?("%d") + host = host % (Zlib.crc32(source) % 4) + end end host ||= request.base_url if request && options[:protocol] == :request return unless host - if host =~ URI_REGEXP + if URI_REGEXP.match?(host) host else protocol = options[:protocol] || config.default_asset_host_protocol || (request ? :request : :relative) @@ -251,7 +309,7 @@ module ActionView # javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr # javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js def javascript_path(source, options = {}) - path_to_asset(source, {type: :javascript}.merge!(options)) + path_to_asset(source, { type: :javascript }.merge!(options)) end alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route @@ -263,7 +321,7 @@ module ActionView # javascript_url "js/xmlhr.js", host: "http://stage.example.com" # => http://stage.example.com/assets/dir/xmlhr.js # def javascript_url(source, options = {}) - url_to_asset(source, {type: :javascript}.merge!(options)) + url_to_asset(source, { type: :javascript }.merge!(options)) end alias_method :url_to_javascript, :javascript_url # aliased to avoid conflicts with a javascript_url named route @@ -278,7 +336,7 @@ module ActionView # stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style # stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css def stylesheet_path(source, options = {}) - path_to_asset(source, {type: :stylesheet}.merge!(options)) + path_to_asset(source, { type: :stylesheet }.merge!(options)) end alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route @@ -290,7 +348,7 @@ module ActionView # stylesheet_url "css/style.css", host: "http://stage.example.com" # => http://stage.example.com/css/style.css # def stylesheet_url(source, options = {}) - url_to_asset(source, {type: :stylesheet}.merge!(options)) + url_to_asset(source, { type: :stylesheet }.merge!(options)) end alias_method :url_to_stylesheet, :stylesheet_url # aliased to avoid conflicts with a stylesheet_url named route @@ -308,7 +366,7 @@ module ActionView # The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and # plugin authors are encouraged to do so. def image_path(source, options = {}) - path_to_asset(source, {type: :image}.merge!(options)) + path_to_asset(source, { type: :image }.merge!(options)) end alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route @@ -320,7 +378,7 @@ module ActionView # image_url "edit.png", host: "http://stage.example.com" # => http://stage.example.com/edit.png # def image_url(source, options = {}) - url_to_asset(source, {type: :image}.merge!(options)) + url_to_asset(source, { type: :image }.merge!(options)) end alias_method :url_to_image, :image_url # aliased to avoid conflicts with an image_url named route @@ -334,7 +392,7 @@ module ActionView # video_path("/trailers/hd.avi") # => /trailers/hd.avi # video_path("http://www.example.com/vid/hd.avi") # => http://www.example.com/vid/hd.avi def video_path(source, options = {}) - path_to_asset(source, {type: :video}.merge!(options)) + path_to_asset(source, { type: :video }.merge!(options)) end alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route @@ -346,7 +404,7 @@ module ActionView # video_url "hd.avi", host: "http://stage.example.com" # => http://stage.example.com/hd.avi # def video_url(source, options = {}) - url_to_asset(source, {type: :video}.merge!(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 @@ -360,7 +418,7 @@ module ActionView # audio_path("/sounds/horse.wav") # => /sounds/horse.wav # audio_path("http://www.example.com/sounds/horse.wav") # => http://www.example.com/sounds/horse.wav def audio_path(source, options = {}) - path_to_asset(source, {type: :audio}.merge!(options)) + path_to_asset(source, { type: :audio }.merge!(options)) end alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route @@ -372,7 +430,7 @@ module ActionView # audio_url "horse.wav", host: "http://stage.example.com" # => http://stage.example.com/horse.wav # def audio_url(source, options = {}) - url_to_asset(source, {type: :audio}.merge!(options)) + url_to_asset(source, { type: :audio }.merge!(options)) end alias_method :url_to_audio, :audio_url # aliased to avoid conflicts with an audio_url named route @@ -385,7 +443,7 @@ module ActionView # font_path("/dir/font.ttf") # => /dir/font.ttf # font_path("http://www.example.com/dir/font.ttf") # => http://www.example.com/dir/font.ttf def font_path(source, options = {}) - path_to_asset(source, {type: :font}.merge!(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 @@ -397,7 +455,7 @@ module ActionView # font_url "font.ttf", host: "http://stage.example.com" # => http://stage.example.com/font.ttf # def font_url(source, options = {}) - url_to_asset(source, {type: :font}.merge!(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 end diff --git a/actionview/lib/action_view/helpers/atom_feed_helper.rb b/actionview/lib/action_view/helpers/atom_feed_helper.rb index dba70e284e..09d243c46d 100644 --- a/actionview/lib/action_view/helpers/atom_feed_helper.rb +++ b/actionview/lib/action_view/helpers/atom_feed_helper.rb @@ -1,4 +1,4 @@ -require 'set' +require "set" module ActionView # = Action View Atom Feed Helpers @@ -112,13 +112,13 @@ module ActionView end end - feed_opts = {"xml:lang" => options[:language] || "en-US", "xmlns" => 'http://www.w3.org/2005/Atom'} - feed_opts.merge!(options).reject!{|k,v| !k.to_s.match(/^xml/)} + feed_opts = { "xml:lang" => options[:language] || "en-US", "xmlns" => "http://www.w3.org/2005/Atom" } + feed_opts.merge!(options).reject! { |k,v| !k.to_s.match(/^xml/) } xml.feed(feed_opts) do xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}") - xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:root_url] || (request.protocol + request.host_with_port)) - xml.link(:rel => 'self', :type => 'application/atom+xml', :href => options[:url] || request.url) + xml.link(rel: "alternate", type: "text/html", href: options[:root_url] || (request.protocol + request.host_with_port)) + xml.link(rel: "self", type: "application/atom+xml", href: options[:url] || request.url) yield AtomFeedBuilder.new(xml, self, options) end @@ -132,13 +132,13 @@ module ActionView end private - # Delegate to xml builder, first wrapping the element in a xhtml + # Delegate to xml builder, first wrapping the element in an xhtml # namespaced div element if the method and arguments indicate # that an xhtml_block? is desired. def method_missing(method, *arguments, &block) if xhtml_block?(method, arguments) @xml.__send__(method, *arguments) do - @xml.div(:xmlns => 'http://www.w3.org/1999/xhtml') do |xhtml| + @xml.div(xmlns: "http://www.w3.org/1999/xhtml") do |xhtml| block.call(xhtml) end end @@ -153,7 +153,7 @@ module ActionView def xhtml_block?(method, arguments) if XHTML_TAG_NAMES.include?(method.to_s) last = arguments.last - last.is_a?(Hash) && last[:type].to_s == 'xhtml' + last.is_a?(Hash) && last[:type].to_s == "xhtml" end end end @@ -189,16 +189,15 @@ module ActionView @xml.updated((options[:updated] || record.updated_at).xmlschema) end - type = options.fetch(:type, 'text/html') + type = options.fetch(:type, "text/html") url = options.fetch(:url) { @view.polymorphic_url(record) } - @xml.link(:rel => 'alternate', :type => type, :href => url) if url + @xml.link(rel: "alternate", type: type, href: url) if url yield AtomBuilder.new(@xml) end end end - end end end diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb index 401f398721..5258a01144 100644 --- a/actionview/lib/action_view/helpers/cache_helper.rb +++ b/actionview/lib/action_view/helpers/cache_helper.rb @@ -41,11 +41,11 @@ module ActionView # # ==== \Template digest # - # The template digest that's added to the cache key is computed by taking an md5 of the + # The template digest that's added to the cache key is computed by taking an MD5 of the # contents of the entire template file. This ensures that your caches will automatically # expire when you change the template file. # - # Note that the md5 is taken of the entire template file, not just what's within the + # Note that the MD5 is taken of the entire template file, not just what's within the # cache do/end call. So it's possible that changing something outside of that call will # still expire the cache. # @@ -69,11 +69,11 @@ module ActionView # render 'comments/comments' # render('comments/comments') # - # render "header" => render("comments/header") + # render "header" translates to render("comments/header") # - # render(@topic) => render("topics/topic") - # render(topics) => render("topics/topic") - # render(message.topics) => render("topics/topic") + # render(@topic) translates to render("topics/topic") + # render(topics) translates to render("topics/topic") + # render(message.topics) translates to render("topics/topic") # # It's not possible to derive all render calls like that, though. # Here are a few examples of things that can't be derived: @@ -118,7 +118,7 @@ module ActionView # # If you use a helper method, for example, inside a cached block and # you then update that helper, you'll have to bump the cache as well. - # It doesn't really matter how you do it, but the md5 of the template file + # It doesn't really matter how you do it, but the MD5 of the template file # must change. One recommendation is to simply be explicit in a comment, like: # # <%# Helper Dependency Updated: May 6, 2012 at 6pm %> @@ -126,44 +126,38 @@ module ActionView # # Now all you have to do is change that timestamp when the helper method changes. # - # === Automatic Collection Caching + # === Collection Caching # - # When rendering collections such as: + # When rendering a collection of objects that each use the same partial, a `cached` + # option can be passed. # - # <%= render @notifications %> - # <%= render partial: 'notifications/notification', collection: @notifications %> + # For collections rendered such: # - # If the notifications/_notification partial starts with a cache call as: + # <%= render partial: 'projects/project', collection: @projects, cached: true %> # - # <% cache notification do %> - # <%= notification.name %> - # <% end %> - # - # The collection can then automatically use any cached renders for that - # template by reading them at once instead of one by one. + # The `cached: true` will make Action View's rendering read several templates + # from cache at once instead of one call per template. # - # See ActionView::Template::Handlers::ERB.resource_cache_call_pattern for - # more information on what cache calls make a template eligible for this - # collection caching. + # Templates in the collection not already cached are written to cache. # - # The automatic cache multi read can be turned off like so: + # Works great alongside individual template fragment caching. + # For instance if the template the collection renders is cached like: # - # <%= render @notifications, cache: false %> + # # projects/_project.html.erb + # <% cache project do %> + # <%# ... %> + # <% end %> # - # === Explicit Collection Caching + # Any collection renders will find those cached templates when attempting + # to read multiple templates at once. # - # If the partial template doesn't start with a clean cache call as - # mentioned above, you can still benefit from collection caching by - # adding a special comment format anywhere in the template, like: + # If your collection cache depends on multiple sources (try to avoid this to keep things simple), + # you can name all these dependencies as part of a block that returns an array: # - # <%# Template Collection: notification %> - # <% my_helper_that_calls_cache(some_arg, notification) do %> - # <%= notification.name %> - # <% end %> + # <%= render partial: 'projects/project', collection: @projects, cached: -> project { [ project, current_user ] } %> # - # The pattern used to match these is <tt>/# Template Collection: (\S+)/</tt>, - # so it's important that you type it out just so. - # You can only declare one collection in a partial template file. + # This will include both records as part of the cache key and updating either of them will + # expire the cache. def cache(name = {}, options = {}, &block) if controller.respond_to?(:perform_caching) && controller.perform_caching name_options = options.slice(:skip_digest, :virtual_path) @@ -217,6 +211,8 @@ module ActionView end end + attr_reader :cache_hit # :nodoc: + private def fragment_name_with_digest(name, virtual_path) #:nodoc: @@ -230,9 +226,14 @@ module ActionView end end - # TODO: Create an object that has caching read/write on it def fragment_for(name = {}, options = nil, &block) #:nodoc: - read_fragment_for(name, options) || write_fragment_for(name, options, &block) + if content = read_fragment_for(name, options) + @cache_hit = true + content + else + @cache_hit = false + write_fragment_for(name, options, &block) + end end def read_fragment_for(name, options) #:nodoc: diff --git a/actionview/lib/action_view/helpers/capture_helper.rb b/actionview/lib/action_view/helpers/capture_helper.rb index df8d0affd0..719592b5c5 100644 --- a/actionview/lib/action_view/helpers/capture_helper.rb +++ b/actionview/lib/action_view/helpers/capture_helper.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/string/output_safety' +require "active_support/core_ext/string/output_safety" module ActionView # = Action View Capture Helper @@ -37,7 +37,7 @@ module ActionView def capture(*args) value = nil buffer = with_output_buffer { value = yield(*args) } - if string = buffer.presence || value and string.is_a?(String) + if (string = buffer.presence || value) && string.is_a?(String) ERB::Util.html_escape string end end diff --git a/actionview/lib/action_view/helpers/controller_helper.rb b/actionview/lib/action_view/helpers/controller_helper.rb index 3569fba8c6..e86cdca4e4 100644 --- a/actionview/lib/action_view/helpers/controller_helper.rb +++ b/actionview/lib/action_view/helpers/controller_helper.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/module/attr_internal' +require "active_support/core_ext/module/attr_internal" module ActionView module Helpers @@ -8,7 +8,7 @@ module ActionView attr_internal :controller, :request delegate :request_forgery_protection_token, :params, :session, :cookies, :response, :headers, - :flash, :action_name, :controller_name, :controller_path, :to => :controller + :flash, :action_name, :controller_name, :controller_path, to: :controller def assign_controller(controller) if @_controller = controller diff --git a/actionview/lib/action_view/helpers/csrf_helper.rb b/actionview/lib/action_view/helpers/csrf_helper.rb index 5af92c4ff2..2a15d2aa5a 100644 --- a/actionview/lib/action_view/helpers/csrf_helper.rb +++ b/actionview/lib/action_view/helpers/csrf_helper.rb @@ -14,14 +14,14 @@ module ActionView # # You don't need to use these tags for regular forms as they generate their own hidden fields. # - # For AJAX requests other than GETs, extract the "csrf-token" from the meta-tag and send as the + # For AJAX requests other than GETs, extract the "csrf-token" from the meta-tag and send as the # "X-CSRF-Token" HTTP header. If you are using jQuery with jquery-rails this happens automatically. # def csrf_meta_tags if protect_against_forgery? [ - tag('meta', :name => 'csrf-param', :content => request_forgery_protection_token), - tag('meta', :name => 'csrf-token', :content => form_authenticity_token) + tag("meta", name: "csrf-param", content: request_forgery_protection_token), + tag("meta", name: "csrf-token", content: form_authenticity_token) ].join("\n").html_safe end end diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb index 233e613e97..ea7259a25d 100644 --- a/actionview/lib/action_view/helpers/date_helper.rb +++ b/actionview/lib/action_view/helpers/date_helper.rb @@ -1,9 +1,10 @@ -require 'date' -require 'action_view/helpers/tag_helper' -require 'active_support/core_ext/array/extract_options' -require 'active_support/core_ext/date/conversions' -require 'active_support/core_ext/hash/slice' -require 'active_support/core_ext/object/with_options' +require "date" +require "action_view/helpers/tag_helper" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/date/conversions" +require "active_support/core_ext/hash/slice" +require "active_support/core_ext/object/acts_like" +require "active_support/core_ext/object/with_options" module ActionView module Helpers @@ -100,60 +101,60 @@ module ActionView distance_in_minutes = ((to_time - from_time)/60.0).round distance_in_seconds = (to_time - from_time).round - I18n.with_options :locale => options[:locale], :scope => options[:scope] do |locale| + I18n.with_options locale: options[:locale], scope: options[:scope] do |locale| case distance_in_minutes - when 0..1 - return distance_in_minutes == 0 ? - locale.t(:less_than_x_minutes, :count => 1) : - locale.t(:x_minutes, :count => distance_in_minutes) unless options[:include_seconds] - - case distance_in_seconds - when 0..4 then locale.t :less_than_x_seconds, :count => 5 - when 5..9 then locale.t :less_than_x_seconds, :count => 10 - when 10..19 then locale.t :less_than_x_seconds, :count => 20 - when 20..39 then locale.t :half_a_minute - when 40..59 then locale.t :less_than_x_minutes, :count => 1 - else locale.t :x_minutes, :count => 1 - end - - when 2...45 then locale.t :x_minutes, :count => distance_in_minutes - when 45...90 then locale.t :about_x_hours, :count => 1 + when 0..1 + return distance_in_minutes == 0 ? + locale.t(:less_than_x_minutes, count: 1) : + locale.t(:x_minutes, count: distance_in_minutes) unless options[:include_seconds] + + case distance_in_seconds + when 0..4 then locale.t :less_than_x_seconds, count: 5 + when 5..9 then locale.t :less_than_x_seconds, count: 10 + when 10..19 then locale.t :less_than_x_seconds, count: 20 + when 20..39 then locale.t :half_a_minute + when 40..59 then locale.t :less_than_x_minutes, count: 1 + else locale.t :x_minutes, count: 1 + end + + when 2...45 then locale.t :x_minutes, count: distance_in_minutes + when 45...90 then locale.t :about_x_hours, count: 1 # 90 mins up to 24 hours - when 90...1440 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round + when 90...1440 then locale.t :about_x_hours, count: (distance_in_minutes.to_f / 60.0).round # 24 hours up to 42 hours - when 1440...2520 then locale.t :x_days, :count => 1 + when 1440...2520 then locale.t :x_days, count: 1 # 42 hours up to 30 days - when 2520...43200 then locale.t :x_days, :count => (distance_in_minutes.to_f / 1440.0).round + when 2520...43200 then locale.t :x_days, count: (distance_in_minutes.to_f / 1440.0).round # 30 days up to 60 days - when 43200...86400 then locale.t :about_x_months, :count => (distance_in_minutes.to_f / 43200.0).round + when 43200...86400 then locale.t :about_x_months, count: (distance_in_minutes.to_f / 43200.0).round # 60 days up to 365 days - when 86400...525600 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round + when 86400...525600 then locale.t :x_months, count: (distance_in_minutes.to_f / 43200.0).round else - if from_time.acts_like?(:time) && to_time.acts_like?(:time) - fyear = from_time.year - fyear += 1 if from_time.month >= 3 - tyear = to_time.year - tyear -= 1 if to_time.month < 3 - leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count{|x| Date.leap?(x)} - minute_offset_for_leap_year = leap_years * 1440 - # Discount the leap year days when calculating year distance. - # e.g. if there are 20 leap year days between 2 dates having the same day - # and month then the based on 365 days calculation - # the distance in years will come out to over 80 years when in written - # English it would read better as about 80 years. - minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year - else - minutes_with_offset = distance_in_minutes - end - remainder = (minutes_with_offset % MINUTES_IN_YEAR) - distance_in_years = (minutes_with_offset.div MINUTES_IN_YEAR) - if remainder < MINUTES_IN_QUARTER_YEAR - locale.t(:about_x_years, :count => distance_in_years) - elsif remainder < MINUTES_IN_THREE_QUARTERS_YEAR - locale.t(:over_x_years, :count => distance_in_years) - else - locale.t(:almost_x_years, :count => distance_in_years + 1) - end + if from_time.acts_like?(:time) && to_time.acts_like?(:time) + fyear = from_time.year + fyear += 1 if from_time.month >= 3 + tyear = to_time.year + tyear -= 1 if to_time.month < 3 + leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count { |x| Date.leap?(x) } + minute_offset_for_leap_year = leap_years * 1440 + # Discount the leap year days when calculating year distance. + # e.g. if there are 20 leap year days between 2 dates having the same day + # and month then the based on 365 days calculation + # the distance in years will come out to over 80 years when in written + # English it would read better as about 80 years. + minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year + else + minutes_with_offset = distance_in_minutes + end + remainder = (minutes_with_offset % MINUTES_IN_YEAR) + distance_in_years = (minutes_with_offset.div MINUTES_IN_YEAR) + if remainder < MINUTES_IN_QUARTER_YEAR + locale.t(:about_x_years, count: distance_in_years) + elsif remainder < MINUTES_IN_THREE_QUARTERS_YEAR + locale.t(:over_x_years, count: distance_in_years) + else + locale.t(:almost_x_years, count: distance_in_years + 1) + end end end end @@ -226,8 +227,10 @@ module ActionView # for <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt> and <tt>:second</tt>. # Setting this option prepends a select option with a generic prompt (Day, Month, Year, Hour, Minute, Seconds) # or the given prompt string. - # * <tt>:with_css_classes</tt> - Set to true if you want assign different styles for 'select' tags. This option - # automatically set classes 'year', 'month', 'day', 'hour', 'minute' and 'second' for your 'select' tags. + # * <tt>:with_css_classes</tt> - Set to true or a hash of strings. Use true if you want to assign generic styles for + # select tags. This automatically set classes 'year', 'month', 'day', 'hour', 'minute' and 'second'. A hash of + # strings for <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt>, <tt>:second</tt> + # will extend the select type with the given value. Use +html_options+ to modify every select tag in the set. # * <tt>:use_hidden</tt> - Set to true if you only want to generate hidden input tags. # # If anything is passed in the +html_options+ hash it will be applied to every select tag in the set. @@ -264,7 +267,7 @@ module ActionView # date_select("article", "written_on", default: 3.days.from_now) # # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute - # # which is set in the form with todays date, regardless of the value in the Active Record object. + # # which is set in the form with today's date, regardless of the value in the Active Record object. # date_select("article", "written_on", selected: Date.today) # # # Generates a date select that when POSTed is stored in the credit_card variable, in the bill_due attribute @@ -300,7 +303,7 @@ module ActionView # # the sunrise attribute. # time_select("article", "start_time", include_seconds: true) # - # # You can set the <tt>:minute_step</tt> to 15 which will give you: 00, 15, 30 and 45. + # # You can set the <tt>:minute_step</tt> to 15 which will give you: 00, 15, 30, and 45. # time_select 'game', 'game_time', {minute_step: 15} # # # Creates a time select tag with a custom prompt. Use <tt>prompt: true</tt> for generic prompts. @@ -679,19 +682,19 @@ module ActionView def time_tag(date_or_time, *args, &block) options = args.extract_options! format = options.delete(:format) || :long - content = args.first || I18n.l(date_or_time, :format => format) + content = args.first || I18n.l(date_or_time, format: format) datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.iso8601 - content_tag("time".freeze, content, options.reverse_merge(:datetime => datetime), &block) + content_tag("time".freeze, content, options.reverse_merge(datetime: datetime), &block) end end class DateTimeSelector #:nodoc: include ActionView::Helpers::TagHelper - DEFAULT_PREFIX = 'date'.freeze + DEFAULT_PREFIX = "date".freeze POSITION = { - :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 + year: 1, month: 2, day: 3, hour: 4, minute: 5, second: 6 }.freeze AMPM_TRANSLATION = Hash[ @@ -707,8 +710,8 @@ module ActionView @options = options.dup @html_options = html_options.dup @datetime = datetime - @options[:datetime_separator] ||= ' — ' - @options[:time_separator] ||= ' : ' + @options[:datetime_separator] ||= " — " + @options[:time_separator] ||= " : " end def select_datetime @@ -778,7 +781,7 @@ module ActionView if @options[:use_hidden] || @options[:discard_minute] build_hidden(:minute, min) else - build_options_and_select(:minute, min, :step => @options[:minute_step]) + build_options_and_select(:minute, min, step: @options[:minute_step]) end end @@ -798,7 +801,7 @@ module ActionView if @options[:use_hidden] || @options[:discard_day] build_hidden(:day, day || 1) else - build_options_and_select(:day, day, :start => 1, :end => 31, :leading_zeros => false, :use_two_digit_numbers => @options[:use_two_digit_numbers]) + build_options_and_select(:day, day, start: 1, end: 31, leading_zeros: false, use_two_digit_numbers: @options[:use_two_digit_numbers]) end end @@ -808,7 +811,7 @@ module ActionView else month_options = [] 1.upto(12) do |month_number| - options = { :value => month_number } + options = { value: month_number } options[:selected] = "selected" if month == month_number month_options << content_tag("option".freeze, month_name(month_number), options) + "\n" end @@ -818,7 +821,7 @@ module ActionView def select_year if !@datetime || @datetime == 0 - val = '1' + val = "1" middle_year = Date.today.year else val = middle_year = year @@ -858,7 +861,7 @@ module ActionView # valid. Otherwise, February 31st or February 29th, 2011 can be selected, which are invalid. def set_day_if_discarded if @datetime && @options[:discard_day] - @datetime = @datetime.change(:day => 1) + @datetime = @datetime.change(day: 1) end end @@ -883,7 +886,7 @@ module ActionView # "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] def translated_month_names key = @options[:use_short_month] ? :'date.abbr_month_names' : :'date.month_names' - I18n.translate(key, :locale => @options[:locale]) + I18n.translate(key, locale: @options[:locale]) end # Looks up month names by number (1-based): @@ -911,11 +914,11 @@ module ActionView if @options[:use_month_numbers] number elsif @options[:use_two_digit_numbers] - '%02d' % number + "%02d" % number elsif @options[:add_month_numbers] "#{number} - #{month_names[number]}" elsif format_string = @options[:month_format_string] - format_string % {number: number, name: month_names[number]} + format_string % { number: number, name: month_names[number] } else month_names[number] end @@ -926,7 +929,7 @@ module ActionView end def translated_date_order - date_order = I18n.translate(:'date.order', :locale => @options[:locale], :default => []) + date_order = I18n.translate(:'date.order', locale: @options[:locale], default: []) date_order = date_order.map(&:to_sym) forbidden_elements = date_order - [:year, :month, :day] @@ -973,7 +976,7 @@ module ActionView select_options = [] start.step(stop, step) do |i| value = leading_zeros ? sprintf("%02d", i) : i - tag_options = { :value => value } + tag_options = { value: value } tag_options[:selected] = "selected" if selected == i text = options[:use_two_digit_numbers] ? sprintf("%02d", i) : value text = options[:ampm] ? AMPM_TRANSLATION[i] : text @@ -990,35 +993,51 @@ module ActionView # </select>" def build_select(type, select_options_as_html) select_options = { - :id => input_id_from_type(type), - :name => input_name_from_type(type) + id: input_id_from_type(type), + name: input_name_from_type(type) }.merge!(@html_options) - select_options[:disabled] = 'disabled' if @options[:disabled] - select_options[:class] = [select_options[:class], type].compact.join(' ') if @options[:with_css_classes] + select_options[:disabled] = "disabled" if @options[:disabled] + select_options[:class] = css_class_attribute(type, select_options[:class], @options[:with_css_classes]) if @options[:with_css_classes] select_html = "\n" - select_html << content_tag("option".freeze, '', :value => '') + "\n" if @options[:include_blank] + select_html << content_tag("option".freeze, "", value: "") + "\n" if @options[:include_blank] select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt] select_html << select_options_as_html (content_tag("select".freeze, select_html.html_safe, select_options) + "\n").html_safe end + # Builds the css class value for the select element + # css_class_attribute(:year, 'date optional', { year: 'my-year' }) + # => "date optional my-year" + def css_class_attribute(type, html_options_class, options) # :nodoc: + css_class = \ + case options + when Hash + options[type.to_sym] + else + type + end + + [html_options_class, css_class].compact.join(" ") + end + # Builds a prompt option tag with supplied options or from default options. # prompt_option_tag(:month, prompt: 'Select month') # => "<option value="">Select month</option>" def prompt_option_tag(type, options) - prompt = case options + prompt = \ + case options when Hash - default_options = {:year => false, :month => false, :day => false, :hour => false, :minute => false, :second => false} + default_options = { year: false, month: false, day: false, hour: false, minute: false, second: false } default_options.merge!(options)[type.to_sym] when String options else - I18n.translate(:"datetime.prompts.#{type}", :locale => @options[:locale]) - end + I18n.translate(:"datetime.prompts.#{type}", locale: @options[:locale]) + end - prompt ? content_tag("option".freeze, prompt, :value => '') : '' + prompt ? content_tag("option".freeze, prompt, value: "") : "" end # Builds hidden input tag for date part and value. @@ -1026,12 +1045,12 @@ module ActionView # => "<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="2008" />" def build_hidden(type, value) select_options = { - :type => "hidden", - :id => input_id_from_type(type), - :name => input_name_from_type(type), - :value => value + type: "hidden", + id: input_id_from_type(type), + name: input_name_from_type(type), + value: value }.merge!(@html_options.slice(:disabled)) - select_options[:disabled] = 'disabled' if @options[:disabled] + select_options[:disabled] = "disabled" if @options[:disabled] tag(:input, select_options) + "\n".html_safe end @@ -1042,7 +1061,7 @@ module ActionView prefix = @options[:prefix] || ActionView::Helpers::DateTimeSelector::DEFAULT_PREFIX prefix += "[#{@options[:index]}]" if @options.has_key?(:index) - field_name = @options[:field_name] || type + field_name = @options[:field_name] || type.to_s if @options[:include_position] field_name += "(#{ActionView::Helpers::DateTimeSelector::POSITION[type]}i)" end @@ -1053,8 +1072,8 @@ module ActionView # Returns the id attribute for the input tag. # => "post_written_on_1i" def input_id_from_type(type) - id = input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '') - id = @options[:namespace] + '_' + id if @options[:namespace] + id = input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, "_").gsub(/[\]\)]/, "") + id = @options[:namespace] + "_" + id if @options[:namespace] id end @@ -1062,7 +1081,7 @@ module ActionView # Given an ordering of datetime components, create the selection HTML # and join them with their appropriate separators. def build_selects_from_types(order) - select = '' + select = "" first_visible = order.find { |type| !@options[:"discard_#{type}"] } order.reverse_each do |type| separator = separator(type) unless type == first_visible # don't add before first visible field @@ -1076,12 +1095,12 @@ module ActionView return "" if @options[:use_hidden] case type - when :year, :month, :day - @options[:"discard_#{type}"] ? "" : @options[:date_separator] - when :hour - (@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator] - when :minute, :second - @options[:"discard_#{type}"] ? "" : @options[:time_separator] + when :year, :month, :day + @options[:"discard_#{type}"] ? "" : @options[:date_separator] + when :hour + (@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator] + when :minute, :second + @options[:"discard_#{type}"] ? "" : @options[:time_separator] end end end diff --git a/actionview/lib/action_view/helpers/debug_helper.rb b/actionview/lib/action_view/helpers/debug_helper.rb index e9dccbad1c..f61ca2c9c2 100644 --- a/actionview/lib/action_view/helpers/debug_helper.rb +++ b/actionview/lib/action_view/helpers/debug_helper.rb @@ -4,7 +4,6 @@ module ActionView # Provides a set of methods for making it easier to debug Rails objects. module Helpers module DebugHelper - include TagHelper # Returns a YAML representation of +object+ wrapped with <pre> and </pre>. @@ -25,10 +24,10 @@ module ActionView def debug(object) Marshal::dump(object) object = ERB::Util.html_escape(object.to_yaml) - content_tag(:pre, object, :class => "debug_dump") + content_tag(:pre, object, class: "debug_dump") rescue # errors from Marshal or YAML # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback - content_tag(:code, object.inspect, :class => "debug_dump") + content_tag(:code, object.inspect, class: "debug_dump") end end end diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb index c1015ffe89..124a14f1d9 100644 --- a/actionview/lib/action_view/helpers/form_helper.rb +++ b/actionview/lib/action_view/helpers/form_helper.rb @@ -1,14 +1,14 @@ -require 'cgi' -require 'action_view/helpers/date_helper' -require 'action_view/helpers/tag_helper' -require 'action_view/helpers/form_tag_helper' -require 'action_view/helpers/active_model_helper' -require 'action_view/model_naming' -require 'action_view/record_identifier' -require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/core_ext/hash/slice' -require 'active_support/core_ext/string/output_safety' -require 'active_support/core_ext/string/inflections' +require "cgi" +require "action_view/helpers/date_helper" +require "action_view/helpers/tag_helper" +require "action_view/helpers/form_tag_helper" +require "action_view/helpers/active_model_helper" +require "action_view/model_naming" +require "action_view/record_identifier" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/hash/slice" +require "active_support/core_ext/string/output_safety" +require "active_support/core_ext/string/inflections" module ActionView # = Action View Form Helpers @@ -467,10 +467,10 @@ module ActionView ) options[:url] ||= if options.key?(:format) - polymorphic_path(record, format: options.delete(:format)) - else - polymorphic_path(record, {}) - end + polymorphic_path(record, format: options.delete(:format)) + else + polymorphic_path(record, {}) + end end private :apply_form_for_options! @@ -860,24 +860,6 @@ module ActionView # # file_field(:attachment, :file, class: 'file_input') # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" /> - # - # ==== Gotcha - # - # The HTML specification says that when a file field is empty, web browsers - # do not send any value to the server. Unfortunately this introduces a - # gotcha: if a +User+ model has an +avatar+ field, and no file is selected, - # then the +avatar+ parameter is empty. Thus, any mass-assignment idiom like - # - # @user.update(params[:user]) - # - # wouldn't update the +avatar+ field. - # - # To prevent this, the helper generates an auxiliary hidden field before - # every file field. The hidden field has the same name as the file one and - # a blank value. - # - # In case you don't want the helper to generate this hidden field you can - # specify the <tt>include_hidden: false</tt> option. def file_field(object_name, method, options = {}) Tags::FileField.new(object_name, method, self, options).render end @@ -983,6 +965,7 @@ module ActionView # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" /> # # <input type="radio" id="post_category_java" name="post[category]" value="java" /> # + # # Let's say that @user.receive_newsletter returns "no": # radio_button("user", "receive_newsletter", "yes") # radio_button("user", "receive_newsletter", "no") # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" /> @@ -1066,7 +1049,7 @@ module ActionView # Returns a text_field of type "time". # # The default value is generated by trying to call +strftime+ with "%T.%L" - # on the objects's value. It is still possible to override that + # on the object's value. It is still possible to override that # by passing the "value" option. # # === Options @@ -1092,38 +1075,9 @@ module ActionView Tags::TimeField.new(object_name, method, self, options).render end - # Returns a text_field of type "datetime". - # - # datetime_field("user", "born_on") - # # => <input id="user_born_on" name="user[born_on]" type="datetime" /> - # - # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T.%L%z" - # on the object's value, which makes it behave as expected for instances - # of DateTime and ActiveSupport::TimeWithZone. - # - # @user.born_on = Date.new(1984, 1, 12) - # datetime_field("user", "born_on") - # # => <input id="user_born_on" name="user[born_on]" type="datetime" value="1984-01-12T00:00:00.000+0000" /> - # - # You can create values for the "min" and "max" attributes by passing - # instances of Date or Time to the options hash. - # - # datetime_field("user", "born_on", min: Date.today) - # # => <input id="user_born_on" name="user[born_on]" type="datetime" min="2014-05-20T00:00:00.000+0000" /> - # - # Alternatively, you can pass a String formatted as an ISO8601 datetime - # with UTC offset as the values for "min" and "max." - # - # datetime_field("user", "born_on", min: "2014-05-20T00:00:00+0000") - # # => <input id="user_born_on" name="user[born_on]" type="datetime" min="2014-05-20T00:00:00.000+0000" /> - # - def datetime_field(object_name, method, options = {}) - Tags::DatetimeField.new(object_name, method, self, options).render - end - # Returns a text_field of type "datetime-local". # - # datetime_local_field("user", "born_on") + # datetime_field("user", "born_on") # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" /> # # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T" @@ -1131,25 +1085,27 @@ module ActionView # of DateTime and ActiveSupport::TimeWithZone. # # @user.born_on = Date.new(1984, 1, 12) - # datetime_local_field("user", "born_on") + # datetime_field("user", "born_on") # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" value="1984-01-12T00:00:00" /> # # You can create values for the "min" and "max" attributes by passing # instances of Date or Time to the options hash. # - # datetime_local_field("user", "born_on", min: Date.today) + # datetime_field("user", "born_on", min: Date.today) # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" /> # # Alternatively, you can pass a String formatted as an ISO8601 datetime as # the values for "min" and "max." # - # datetime_local_field("user", "born_on", min: "2014-05-20T00:00:00") + # datetime_field("user", "born_on", min: "2014-05-20T00:00:00") # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" /> # - def datetime_local_field(object_name, method, options = {}) + def datetime_field(object_name, method, options = {}) Tags::DatetimeLocalField.new(object_name, method, self, options).render end + alias datetime_local_field datetime_field + # Returns a text_field of type "month". # # month_field("user", "born_on") @@ -1315,7 +1271,7 @@ module ActionView end def self._to_partial_path - @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, '') + @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, "") end def to_partial_path @@ -1331,7 +1287,7 @@ module ActionView @object_name, @object, @template, @options = object_name, object, template, options @default_options = @options ? @options.slice(:index, :namespace) : {} if @object_name.to_s.match(/\[\]$/) - if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param) + if (object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}")) && object.respond_to?(:to_param) @auto_index = object.to_param else raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}" @@ -1618,13 +1574,13 @@ module ActionView end record_name = if index - "#{object_name}[#{index}][#{record_name}]" - elsif record_name.to_s.end_with?('[]') - record_name = record_name.to_s.sub(/(.*)\[\]$/, "[\\1][#{record_object.id}]") - "#{object_name}#{record_name}" - else - "#{object_name}[#{record_name}]" - end + "#{object_name}[#{index}][#{record_name}]" + elsif record_name.to_s.end_with?("[]") + record_name = record_name.to_s.sub(/(.*)\[\]$/, "[\\1][#{record_object.id}]") + "#{object_name}#{record_name}" + else + "#{object_name}[#{record_name}]" + end fields_options[:child_index] = index @template.fields_for(record_name, record_object, fields_options, &block) @@ -1756,7 +1712,7 @@ module ActionView # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" /> # # <input type="radio" id="post_category_java" name="post[category]" value="java" /> # - # # Let's say that @user.category returns "no": + # # Let's say that @user.receive_newsletter returns "no": # radio_button("receive_newsletter", "yes") # radio_button("receive_newsletter", "no") # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" /> @@ -1922,8 +1878,6 @@ module ActionView @object_name.to_s.humanize end - model = model.downcase - defaults = [] defaults << :"helpers.submit.#{object_name}.#{key}" defaults << :"helpers.submit.#{key}" diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb index 430051379d..07d4310a4e 100644 --- a/actionview/lib/action_view/helpers/form_options_helper.rb +++ b/actionview/lib/action_view/helpers/form_options_helper.rb @@ -1,9 +1,9 @@ -require 'cgi' -require 'erb' -require 'action_view/helpers/form_helper' -require 'active_support/core_ext/string/output_safety' -require 'active_support/core_ext/array/extract_options' -require 'active_support/core_ext/array/wrap' +require "cgi" +require "erb" +require "action_view/helpers/form_helper" +require "active_support/core_ext/string/output_safety" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/array/wrap" module ActionView # = Action View Form Option Helpers @@ -268,10 +268,11 @@ module ActionView # for more information.) # # You can also supply an array of ActiveSupport::TimeZone objects - # as +priority_zones+, so that they will be listed above the rest of the - # (long) list. (You can use ActiveSupport::TimeZone.us_zones as a convenience - # for obtaining a list of the US time zones, or a Regexp to select the zones - # of your choice) + # as +priority_zones+ so that they will be listed above the rest of the + # (long) list. You can use ActiveSupport::TimeZone.us_zones for a list + # of US time zones, ActiveSupport::TimeZone.country_zones(country_code) + # for another country's time zones, or a Regexp to select the zones of + # your choice. # # Finally, this method supports a <tt>:default</tt> option, which selects # a default ActiveSupport::TimeZone if the object's time zone is +nil+. @@ -362,7 +363,7 @@ module ActionView html_attributes[:disabled] ||= disabled && option_value_selected?(value, disabled) html_attributes[:value] = value - content_tag_string(:option, text, html_attributes) + tag_builder.content_tag_string(:option, text, html_attributes) end.join("\n").html_safe end @@ -577,7 +578,7 @@ module ActionView end zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected) - zone_options.safe_concat content_tag("option".freeze, '-------------', value: '', disabled: true) + zone_options.safe_concat content_tag("option".freeze, "-------------", value: "", disabled: true) zone_options.safe_concat "\n" zones = zones - priority_zones @@ -650,12 +651,12 @@ module ActionView # The HTML specification says when nothing is select on a collection of radio buttons # web browsers do not send any value to server. # Unfortunately this introduces a gotcha: - # if a +User+ model has a +category_id+ field, and in the form none category is selected no +category_id+ parameter is sent. So, - # any strong parameters idiom like + # if a +User+ model has a +category_id+ field and in the form no category is selected, no +category_id+ parameter is sent. So, + # any strong parameters idiom like: # # params.require(:user).permit(...) # - # will raise an error since no +{user: ...}+ will be present. + # will raise an error since no <tt>{user: ...}</tt> will be present. # # To prevent this the helper generates an auxiliary hidden field before # every collection of radio buttons. The hidden field has the same name as collection radio button and blank value. @@ -799,7 +800,7 @@ module ActionView end def prompt_text(prompt) - prompt.kind_of?(String) ? prompt : I18n.translate('helpers.select.prompt', default: 'Please select') + prompt.kind_of?(String) ? prompt : I18n.translate("helpers.select.prompt", default: "Please select") 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 55dac74d00..7bd473507b 100644 --- a/actionview/lib/action_view/helpers/form_tag_helper.rb +++ b/actionview/lib/action_view/helpers/form_tag_helper.rb @@ -1,7 +1,7 @@ -require 'cgi' -require 'action_view/helpers/tag_helper' -require 'active_support/core_ext/string/output_safety' -require 'active_support/core_ext/module/attribute_accessors' +require "cgi" +require "action_view/helpers/tag_helper" +require "active_support/core_ext/string/output_safety" +require "active_support/core_ext/module/attribute_accessors" module ActionView # = Action View Form Tag Helpers @@ -134,18 +134,20 @@ module ActionView if options.include?(:include_blank) include_blank = options.delete(:include_blank) + options_for_blank_options_tag = { value: "" } if include_blank == true - include_blank = '' + include_blank = "" + options_for_blank_options_tag[:label] = " " end if include_blank - option_tags = content_tag("option".freeze, include_blank, value: '').safe_concat(option_tags) + option_tags = content_tag("option".freeze, include_blank, options_for_blank_options_tag).safe_concat(option_tags) end end if prompt = options.delete(:prompt) - option_tags = content_tag("option".freeze, prompt, value: '').safe_concat(option_tags) + option_tags = content_tag("option".freeze, prompt, value: "").safe_concat(option_tags) end content_tag "select".freeze, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys) @@ -516,12 +518,12 @@ module ActionView options ||= {} end - options = { 'name' => 'button', 'type' => 'submit' }.merge!(options.stringify_keys) + options = { "name" => "button", "type" => "submit" }.merge!(options.stringify_keys) if block_given? content_tag :button, options, &block else - content_tag :button, content_or_options || 'Button', options + content_tag :button, content_or_options || "Button", options end end @@ -683,7 +685,7 @@ module ActionView text_field_tag(name, value, options.merge(type: :time)) end - # Creates a text field of type "datetime". + # Creates a text field of type "datetime-local". # # === Options # * <tt>:min</tt> - The minimum acceptable value. @@ -691,19 +693,10 @@ module ActionView # * <tt>:step</tt> - The acceptable value granularity. # * Otherwise accepts the same options as text_field_tag. def datetime_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.merge(type: :datetime)) + text_field_tag(name, value, options.merge(type: "datetime-local")) end - # Creates a text field of type "datetime-local". - # - # === Options - # * <tt>:min</tt> - The minimum acceptable value. - # * <tt>:max</tt> - The maximum acceptable value. - # * <tt>:step</tt> - The acceptable value granularity. - # * Otherwise accepts the same options as text_field_tag. - def datetime_local_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.merge(type: 'datetime-local')) - end + alias datetime_local_field_tag datetime_field_tag # Creates a text field of type "month". # @@ -862,13 +855,14 @@ module ActionView def extra_tags_for_form(html_options) authenticity_token = html_options.delete("authenticity_token") - method = html_options.delete("method").to_s + method = html_options.delete("method").to_s.downcase - method_tag = case method - when /^get$/i # must be case-insensitive, but can't use downcase as might be nil + method_tag = \ + case method + when "get" html_options["method"] = "get" - '' - when /^post$/i, "", nil + "" + when "post", "" html_options["method"] = "post" token_tag(authenticity_token, form_options: { action: html_options["action"], @@ -880,7 +874,7 @@ module ActionView action: html_options["action"], method: method }) - end + end if html_options.delete("enforce_utf8") { true } utf8_enforcer_tag + method_tag @@ -902,7 +896,7 @@ module ActionView # see http://www.w3.org/TR/html4/types.html#type-name def sanitize_to_id(name) - name.to_s.delete(']').tr('^-a-zA-Z0-9:.', "_") + name.to_s.delete("]").tr("^-a-zA-Z0-9:.", "_") end end end diff --git a/actionview/lib/action_view/helpers/javascript_helper.rb b/actionview/lib/action_view/helpers/javascript_helper.rb index ed7e882c94..22e1e74ad6 100644 --- a/actionview/lib/action_view/helpers/javascript_helper.rb +++ b/actionview/lib/action_view/helpers/javascript_helper.rb @@ -1,11 +1,11 @@ -require 'action_view/helpers/tag_helper' +require "action_view/helpers/tag_helper" module ActionView module Helpers module JavaScriptHelper JS_ESCAPE_MAP = { '\\' => '\\\\', - '</' => '<\/', + "</" => '<\/', "\r\n" => '\n', "\n" => '\n', "\r" => '\n', @@ -13,8 +13,8 @@ module ActionView "'" => "\\'" } - JS_ESCAPE_MAP["\342\200\250".force_encoding(Encoding::UTF_8).encode!] = '
' - JS_ESCAPE_MAP["\342\200\251".force_encoding(Encoding::UTF_8).encode!] = '
' + JS_ESCAPE_MAP["\342\200\250".force_encoding(Encoding::UTF_8).encode!] = "
" + JS_ESCAPE_MAP["\342\200\251".force_encoding(Encoding::UTF_8).encode!] = "
" # Escapes carriage returns and single and double quotes for JavaScript segments. # @@ -24,10 +24,10 @@ module ActionView # $('some_element').replaceWith('<%= j render 'some/element_template' %>'); def escape_javascript(javascript) if javascript - result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) {|match| JS_ESCAPE_MAP[match] } + result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) { |match| JS_ESCAPE_MAP[match] } javascript.html_safe? ? result.html_safe : result else - '' + "" end end diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb index 161aa031c6..75b898c3e9 100644 --- a/actionview/lib/action_view/helpers/number_helper.rb +++ b/actionview/lib/action_view/helpers/number_helper.rb @@ -1,11 +1,10 @@ -require 'active_support/core_ext/hash/keys' -require 'active_support/core_ext/string/output_safety' -require 'active_support/number_helper' +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/string/output_safety" +require "active_support/number_helper" module ActionView # = Action View Number Helpers module Helpers #:nodoc: - # Provides methods for converting numbers into formatted strings. # Methods are provided for phone numbers, currency, percentage, # precision, positional notation, file size and pretty printing. @@ -13,7 +12,6 @@ module ActionView # Most methods expect a +number+ argument, and will return it # unchanged if can't be converted into a valid number. module NumberHelper - # Raised when argument +number+ param given to the helpers is invalid and # the option :raise is set to +true+. class InvalidNumberError < StandardError @@ -23,7 +21,7 @@ module ActionView end end - # Formats a +number+ into a US phone number (e.g., (555) + # Formats a +number+ into a phone number (US by default e.g., (555) # 123-9876). You can customize the format in the +options+ hash. # # ==== Options @@ -35,6 +33,8 @@ module ActionView # end of the generated number. # * <tt>:country_code</tt> - Sets the country code for the phone # number. + # * <tt>:pattern</tt> - Specifies how the number is divided into three + # groups with the custom regexp to override the default format. # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when # the argument is invalid. # @@ -52,6 +52,11 @@ module ActionView # # number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: ".") # # => +1.123.555.1234 x 1343 + # + # 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})$/)) + # # => "133-1234-5678" def number_to_phone(number, options = {}) return unless number options = options.symbolize_keys @@ -256,8 +261,6 @@ module ActionView # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes # insignificant zeros after the decimal separator (defaults to # +true+) - # * <tt>:prefix</tt> - If +:si+ formats the number using the SI - # prefix (defaults to :binary) # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when # the argument is invalid. # @@ -388,53 +391,53 @@ module ActionView private - def delegate_number_helper_method(method, number, options) - return unless number - options = escape_unsafe_options(options.symbolize_keys) + def delegate_number_helper_method(method, number, options) + return unless number + options = escape_unsafe_options(options.symbolize_keys) - wrap_with_output_safety_handling(number, options.delete(:raise)) { - ActiveSupport::NumberHelper.public_send(method, number, options) - } - end + wrap_with_output_safety_handling(number, options.delete(:raise)) { + ActiveSupport::NumberHelper.public_send(method, number, options) + } + end - def escape_unsafe_options(options) - options[:format] = ERB::Util.html_escape(options[:format]) if options[:format] - options[:negative_format] = ERB::Util.html_escape(options[:negative_format]) if options[:negative_format] - options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator] - options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter] - options[:unit] = ERB::Util.html_escape(options[:unit]) if options[:unit] && !options[:unit].html_safe? - options[:units] = escape_units(options[:units]) if options[:units] && Hash === options[:units] - options - end + def escape_unsafe_options(options) + options[:format] = ERB::Util.html_escape(options[:format]) if options[:format] + options[:negative_format] = ERB::Util.html_escape(options[:negative_format]) if options[:negative_format] + options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator] + options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter] + options[:unit] = ERB::Util.html_escape(options[:unit]) if options[:unit] && !options[:unit].html_safe? + options[:units] = escape_units(options[:units]) if options[:units] && Hash === options[:units] + options + end - def escape_units(units) - Hash[units.map do |k, v| - [k, ERB::Util.html_escape(v)] - end] - end + def escape_units(units) + Hash[units.map do |k, v| + [k, ERB::Util.html_escape(v)] + end] + end - def wrap_with_output_safety_handling(number, raise_on_invalid, &block) - valid_float = valid_float?(number) - raise InvalidNumberError, number if raise_on_invalid && !valid_float + def wrap_with_output_safety_handling(number, raise_on_invalid, &block) + valid_float = valid_float?(number) + raise InvalidNumberError, number if raise_on_invalid && !valid_float - formatted_number = yield + formatted_number = yield - if valid_float || number.html_safe? - formatted_number.html_safe - else - formatted_number + if valid_float || number.html_safe? + formatted_number.html_safe + else + formatted_number + end end - end - def valid_float?(number) - !parse_float(number, false).nil? - end + def valid_float?(number) + !parse_float(number, false).nil? + end - def parse_float(number, raise_error) - Float(number) - rescue ArgumentError, TypeError - raise InvalidNumberError, number if raise_error - end + def parse_float(number, raise_error) + Float(number) + rescue ArgumentError, TypeError + raise InvalidNumberError, number if raise_error + end end end end diff --git a/actionview/lib/action_view/helpers/output_safety_helper.rb b/actionview/lib/action_view/helpers/output_safety_helper.rb index c0fc3b820f..8e63e59fac 100644 --- a/actionview/lib/action_view/helpers/output_safety_helper.rb +++ b/actionview/lib/action_view/helpers/output_safety_helper.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/string/output_safety' +require "active_support/core_ext/string/output_safety" module ActionView #:nodoc: # = Action View Raw Output Helper @@ -33,6 +33,36 @@ module ActionView #:nodoc: array.flatten.map! { |i| ERB::Util.unwrapped_html_escape(i) }.join(sep).html_safe end + + # Converts the array to a comma-separated sentence where the last element is + # joined by the connector word. This is the html_safe-aware version of + # ActiveSupport's {Array#to_sentence}[http://api.rubyonrails.org/classes/Array.html#method-i-to_sentence]. + # + def to_sentence(array, options = {}) + options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale) + + default_connectors = { + words_connector: ", ", + two_words_connector: " and ", + last_word_connector: ", and " + } + if defined?(I18n) + i18n_connectors = I18n.translate(:'support.array', locale: options[:locale], default: {}) + default_connectors.merge!(i18n_connectors) + end + options = default_connectors.merge!(options) + + case array.length + when 0 + "".html_safe + when 1 + ERB::Util.html_escape(array[0]) + 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]]) + end + end end end end diff --git a/actionview/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb index c98f2d74a8..7d7f2393ff 100644 --- a/actionview/lib/action_view/helpers/rendering_helper.rb +++ b/actionview/lib/action_view/helpers/rendering_helper.rb @@ -27,12 +27,12 @@ module ActionView case options when Hash if block_given? - view_renderer.render_partial(self, options.merge(:partial => options[:layout]), &block) + view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block) else view_renderer.render(self, options) end else - view_renderer.render_partial(self, :partial => options, :locals => locals, &block) + view_renderer.render_partial(self, partial: options, locals: locals, &block) end end diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb index 191a881de0..3d6ff598ee 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper.rb @@ -1,5 +1,5 @@ -require 'active_support/core_ext/object/try' -require 'rails-html-sanitizer' +require "active_support/core_ext/object/try" +require "rails-html-sanitizer" module ActionView # = Action View Sanitize Helpers @@ -120,7 +120,7 @@ module ActionView attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer # Vendors the full, link and white list sanitizers. - # Provided strictly for compatibility and can be removed in Rails 5. + # Provided strictly for compatibility and can be removed in Rails 5.1. def sanitizer_vendor Rails::Html::Sanitizer end diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb index 2562504896..306b71c85e 100644 --- a/actionview/lib/action_view/helpers/tag_helper.rb +++ b/actionview/lib/action_view/helpers/tag_helper.rb @@ -1,39 +1,207 @@ -require 'active_support/core_ext/string/output_safety' -require 'set' +# frozen-string-literal: true + +require "active_support/core_ext/string/output_safety" +require "set" module ActionView # = Action View Tag Helpers module Helpers #:nodoc: - # Provides methods to generate HTML tags programmatically when you can't use - # a Builder. By default, they output XHTML compliant tags. + # Provides methods to generate HTML tags programmatically both as a modern + # HTML5 compliant builder style and legacy XHTML compliant tags. module TagHelper extend ActiveSupport::Concern include CaptureHelper include OutputSafetyHelper - BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked autobuffer - autoplay controls loop selected hidden scoped async - defer reversed ismap seamless muted required - autofocus novalidate formnovalidate open pubdate - itemscope allowfullscreen default inert sortable - truespeed typemustmatch).to_set + BOOLEAN_ATTRIBUTES = %w(allowfullscreen async autofocus autoplay checked + compact controls declare default defaultchecked + defaultmuted defaultselected defer disabled + enabled formnovalidate hidden indeterminate inert + ismap itemscope loop multiple muted nohref + noresize noshade novalidate nowrap open + pauseonexit readonly required reversed scoped + seamless selected sortable truespeed typemustmatch + visible).to_set BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym)) - TAG_PREFIXES = ['aria', 'data', :aria, :data].to_set + TAG_PREFIXES = ["aria", "data", :aria, :data].to_set - PRE_CONTENT_STRINGS = Hash.new { "".freeze } + PRE_CONTENT_STRINGS = Hash.new { "" } PRE_CONTENT_STRINGS[:textarea] = "\n" PRE_CONTENT_STRINGS["textarea"] = "\n" + class TagBuilder #:nodoc: + include CaptureHelper + include OutputSafetyHelper + + VOID_ELEMENTS = %i(area base br col embed hr img input keygen link meta param source track wbr).to_set + + def initialize(view_context) + @view_context = view_context + end + + def tag_string(name, content = nil, escape_attributes: true, **options, &block) + content = @view_context.capture(self, &block) if block_given? + if VOID_ELEMENTS.include?(name) && content.nil? + "<#{name.to_s.dasherize}#{tag_options(options, escape_attributes)}>".html_safe + else + content_tag_string(name.to_s.dasherize, content || "", options, escape_attributes) + end + end + + def content_tag_string(name, content, options, escape = true) + tag_options = tag_options(options, escape) if options + content = ERB::Util.unwrapped_html_escape(content) if escape + "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe + end + + def tag_options(options, escape = true) + return if options.blank? + output = "".dup + sep = " " + options.each_pair do |key, value| + if TAG_PREFIXES.include?(key) && value.is_a?(Hash) + value.each_pair do |k, v| + next if v.nil? + output << sep + output << prefix_tag_option(key, k, v, escape) + end + elsif BOOLEAN_ATTRIBUTES.include?(key) + if value + output << sep + output << boolean_tag_option(key) + end + elsif !value.nil? + output << sep + output << tag_option(key, value, escape) + end + end + output unless output.empty? + end - # Returns an empty HTML tag of type +name+ which by default is XHTML + def boolean_tag_option(key) + %(#{key}="#{key}") + end + + def tag_option(key, value, escape) + if value.is_a?(Array) + value = escape ? safe_join(value, " ".freeze) : value.join(" ".freeze) + else + value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s + end + %(#{key}="#{value.gsub('"'.freeze, '"'.freeze)}") + end + + private + def prefix_tag_option(prefix, key, value, escape) + key = "#{prefix}-#{key.to_s.dasherize}" + unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal) + value = value.to_json + end + tag_option(key, value, escape) + end + + def respond_to_missing?(*args) + true + end + + def method_missing(called, *args, &block) + tag_string(called, *args, &block) + end + end + + # Returns an HTML tag. + # + # === Building HTML tags + # + # Builds HTML5 compliant tags with a tag proxy. Every tag can be built with: + # + # tag.<tag name>(optional content, options) + # + # where tag name can be e.g. br, div, section, article, or any tag really. + # + # ==== Passing content + # + # Tags can pass content to embed within it: + # + # tag.h1 'All titles fit to print' # => <h1>All titles fit to print</h1> + # + # tag.div tag.p('Hello world!') # => <div><p>Hello world!</p></div> + # + # Content can also be captured with a block, which is useful in templates: + # + # <%= tag.p do %> + # The next great American novel starts here. + # <% end %> + # # => <p>The next great American novel starts here.</p> + # + # ==== Options + # + # Use symbol keyed options to add attributes to the generated tag. + # + # tag.section class: %w( kitties puppies ) + # # => <section class="kitties puppies"></section> + # + # tag.section id: dom_id(@post) + # # => <section id="<generated dom id>"></section> + # + # Pass +true+ for any attributes that can render with no values, like +disabled+ and +readonly+. + # + # tag.input type: 'text', disabled: true + # # => <input type="text" disabled="disabled"> + # + # HTML5 <tt>data-*</tt> attributes can be set with a single +data+ key + # pointing to a hash of sub-attributes. + # + # To play nicely with JavaScript conventions, sub-attributes are dasherized. + # + # tag.article data: { user_id: 123 } + # # => <article data-user-id="123"></article> + # + # Thus <tt>data-user-id</tt> can be accessed as <tt>dataset.userId</tt>. + # + # Data attribute values are encoded to JSON, with the exception of strings, symbols and + # BigDecimals. + # This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt> + # from 1.4.3. + # + # tag.div data: { city_state: %w( Chigaco IL ) } + # # => <div data-city-state="["Chicago","IL"]"></div> + # + # The generated attributes are escaped by default. This can be disabled using + # +escape_attributes+. + # + # tag.img src: 'open & shut.png' + # # => <img src="open & shut.png"> + # + # tag.img src: 'open & shut.png', escape_attributes: false + # # => <img src="open & shut.png"> + # + # The tag builder respects + # {HTML5 void elements}[https://www.w3.org/TR/html5/syntax.html#void-elements] + # if no content is passed, and omits closing tags for those elements. + # + # # A standard element: + # tag.div # => <div></div> + # + # # A void element: + # tag.br # => <br> + # + # === Legacy syntax + # + # The following format is for legacy syntax support. It will be deprecated in future versions of Rails. + # + # tag(name, options = nil, open = false, escape = true) + # + # It returns an empty HTML tag of type +name+ which by default is XHTML # compliant. Set +open+ to true to create an open tag compatible # with HTML 4.0 and below. Add HTML attributes by passing an attributes # hash to +options+. Set +escape+ to false to disable attribute value # escaping. # # ==== Options + # # You can use symbols or strings for the attribute names. # # Use +true+ with boolean attributes that can render with no value, like @@ -42,16 +210,8 @@ module ActionView # HTML5 <tt>data-*</tt> attributes can be set with a single +data+ key # pointing to a hash of sub-attributes. # - # To play nicely with JavaScript conventions sub-attributes are dasherized. - # For example, a key +user_id+ would render as <tt>data-user-id</tt> and - # thus accessed as <tt>dataset.userId</tt>. - # - # Values are encoded to JSON, with the exception of strings, symbols and - # BigDecimals. - # This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt> - # from 1.4.3. - # # ==== Examples + # # tag("br") # # => <br /> # @@ -72,8 +232,12 @@ module ActionView # # tag("div", data: {name: 'Stephen', city_state: %w(Chicago IL)}) # # => <div data-name="Stephen" data-city-state="["Chicago","IL"]" /> - def tag(name, options = nil, open = false, escape = true) - "<#{name}#{tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe + def tag(name = nil, options = nil, open = false, escape = true) + if name.nil? + tag_builder + else + "<#{name}#{tag_builder.tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe + end end # Returns an HTML block tag of type +name+ surrounding the +content+. Add @@ -81,6 +245,7 @@ module ActionView # Instead of passing the content as an argument, you can also use a block # in which case, you pass your +options+ as the second parameter. # Set escape to false to disable attribute value escaping. + # Note: this is legacy syntax, see +tag+ method description for details. # # ==== Options # The +options+ hash can be used with attributes with no value like (<tt>disabled</tt> and @@ -104,9 +269,9 @@ module ActionView def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block) if block_given? options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash) - content_tag_string(name, capture(&block), options, escape) + tag_builder.content_tag_string(name, capture(&block), options, escape) else - content_tag_string(name, content_or_options_with_block, options, escape) + tag_builder.content_tag_string(name, content_or_options_with_block, options, escape) end end @@ -124,7 +289,7 @@ module ActionView # cdata_section("hello]]>world") # # => <![CDATA[hello]]]]><![CDATA[>world]]> def cdata_section(content) - splitted = content.to_s.gsub(/\]\]\>/, ']]]]><![CDATA[>') + splitted = content.to_s.gsub(/\]\]\>/, "]]]]><![CDATA[>") "<![CDATA[#{splitted}]]>".html_safe end @@ -140,55 +305,8 @@ module ActionView end private - - def content_tag_string(name, content, options, escape = true) - tag_options = tag_options(options, escape) if options - content = ERB::Util.unwrapped_html_escape(content) if escape - "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe - end - - def tag_options(options, escape = true) - return if options.blank? - output = "" - sep = " ".freeze - options.each_pair do |key, value| - if TAG_PREFIXES.include?(key) && value.is_a?(Hash) - value.each_pair do |k, v| - output << sep - output << prefix_tag_option(key, k, v, escape) - end - elsif BOOLEAN_ATTRIBUTES.include?(key) - if value - output << sep - output << boolean_tag_option(key) - end - elsif !value.nil? - output << sep - output << tag_option(key, value, escape) - end - end - output unless output.empty? - end - - def prefix_tag_option(prefix, key, value, escape) - key = "#{prefix}-#{key.to_s.dasherize}" - unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal) - value = value.to_json - end - tag_option(key, value, escape) - end - - def boolean_tag_option(key) - %(#{key}="#{key}") - end - - def tag_option(key, value, escape) - if value.is_a?(Array) - value = escape ? safe_join(value, " ".freeze) : value.join(" ".freeze) - else - value = escape ? ERB::Util.unwrapped_html_escape(value) : value - end - %(#{key}="#{value}") + def tag_builder + @tag_builder ||= TagBuilder.new(self) end end end diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb index d57f26ba4f..e3e3c8b109 100644 --- a/actionview/lib/action_view/helpers/tags/base.rb +++ b/actionview/lib/action_view/helpers/tags/base.rb @@ -24,136 +24,136 @@ module ActionView private - def value(object) - object.public_send @method_name if object - end + def value(object) + object.public_send @method_name if object + end - def value_before_type_cast(object) - unless object.nil? - method_before_type_cast = @method_name + "_before_type_cast" + def value_before_type_cast(object) + unless object.nil? + method_before_type_cast = @method_name + "_before_type_cast" - if value_came_from_user?(object) && object.respond_to?(method_before_type_cast) - object.public_send(method_before_type_cast) - else - value(object) + if value_came_from_user?(object) && object.respond_to?(method_before_type_cast) + object.public_send(method_before_type_cast) + else + value(object) + end end end - end - def value_came_from_user?(object) - method_name = "#{@method_name}_came_from_user?" - !object.respond_to?(method_name) || object.public_send(method_name) - end + def value_came_from_user?(object) + method_name = "#{@method_name}_came_from_user?" + !object.respond_to?(method_name) || object.public_send(method_name) + end - def retrieve_object(object) - if object - object - elsif @template_object.instance_variable_defined?("@#{@object_name}") - @template_object.instance_variable_get("@#{@object_name}") + def retrieve_object(object) + if object + object + elsif @template_object.instance_variable_defined?("@#{@object_name}") + @template_object.instance_variable_get("@#{@object_name}") + end + rescue NameError + # As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil. + nil end - rescue NameError - # As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil. - nil - end - def retrieve_autoindex(pre_match) - object = self.object || @template_object.instance_variable_get("@#{pre_match}") - if object && object.respond_to?(:to_param) - object.to_param - else - raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}" + def retrieve_autoindex(pre_match) + object = self.object || @template_object.instance_variable_get("@#{pre_match}") + if object && object.respond_to?(:to_param) + object.to_param + else + raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}" + end end - end - def add_default_name_and_id_for_value(tag_value, options) - if tag_value.nil? - add_default_name_and_id(options) - else - specified_id = options["id"] - add_default_name_and_id(options) + def add_default_name_and_id_for_value(tag_value, options) + if tag_value.nil? + add_default_name_and_id(options) + else + specified_id = options["id"] + add_default_name_and_id(options) - if specified_id.blank? && options["id"].present? - options["id"] += "_#{sanitized_value(tag_value)}" + if specified_id.blank? && options["id"].present? + options["id"] += "_#{sanitized_value(tag_value)}" + end end end - end - 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 + 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 + end end - end - def tag_name(multiple = false, index = nil) - # a little duplication to construct less strings - if index - "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}" - else - "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}" + def tag_name(multiple = false, index = nil) + # a little duplication to construct less strings + if index + "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}" + else + "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}" + end end - end - def tag_id(index = nil) - # a little duplication to construct less strings - if index - "#{sanitized_object_name}_#{index}_#{sanitized_method_name}" - else - "#{sanitized_object_name}_#{sanitized_method_name}" + def tag_id(index = nil) + # a little duplication to construct less strings + if index + "#{sanitized_object_name}_#{index}_#{sanitized_method_name}" + else + "#{sanitized_object_name}_#{sanitized_method_name}" + end end - end - def sanitized_object_name - @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "") - end + def sanitized_object_name + @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "") + end - def sanitized_method_name - @sanitized_method_name ||= @method_name.sub(/\?$/,"") - end + def sanitized_method_name + @sanitized_method_name ||= @method_name.sub(/\?$/,"") + end - def sanitized_value(value) - value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase - end + def sanitized_value(value) + value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase + end - def select_content_tag(option_tags, options, html_options) - html_options = html_options.stringify_keys - add_default_name_and_id(html_options) + def select_content_tag(option_tags, options, html_options) + html_options = html_options.stringify_keys + add_default_name_and_id(html_options) - if placeholder_required?(html_options) - raise ArgumentError, "include_blank cannot be false for a required field." if options[:include_blank] == false - options[:include_blank] ||= true unless options[:prompt] - end + if placeholder_required?(html_options) + raise ArgumentError, "include_blank cannot be false for a required field." if options[:include_blank] == false + options[:include_blank] ||= true unless options[:prompt] + end - value = options.fetch(:selected) { value(object) } - select = content_tag("select", add_options(option_tags, options, value), html_options) + value = options.fetch(:selected) { value(object) } + select = content_tag("select", add_options(option_tags, options, value), html_options) - if html_options["multiple"] && options.fetch(:include_hidden, true) - tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select - else - select + if html_options["multiple"] && options.fetch(:include_hidden, true) + tag("input", disabled: html_options["disabled"], name: html_options["name"], type: "hidden", value: "") + select + else + select + end end - end - def placeholder_required?(html_options) - # See https://html.spec.whatwg.org/multipage/forms.html#attr-select-required - html_options["required"] && !html_options["multiple"] && html_options.fetch("size", 1).to_i == 1 - end - - def add_options(option_tags, options, value = nil) - if options[:include_blank] - option_tags = content_tag_string('option', options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, :value => '') + "\n" + option_tags + def placeholder_required?(html_options) + # See https://html.spec.whatwg.org/multipage/forms.html#attr-select-required + html_options["required"] && !html_options["multiple"] && html_options.fetch("size", 1).to_i == 1 end - if value.blank? && options[:prompt] - option_tags = content_tag_string('option', prompt_text(options[:prompt]), :value => '') + "\n" + option_tags + + def add_options(option_tags, options, value = nil) + if options[:include_blank] + option_tags = tag_builder.content_tag_string("option", options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, value: "") + "\n" + option_tags + end + if value.blank? && options[:prompt] + option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), value: "") + "\n" + option_tags + end + option_tags end - option_tags - end - def name_and_id_index(options) - options.key?("index") ? options.delete("index") || "" : @auto_index - end + def name_and_id_index(options) + options.key?("index") ? options.delete("index") || "" : @auto_index + end end end end diff --git a/actionview/lib/action_view/helpers/tags/check_box.rb b/actionview/lib/action_view/helpers/tags/check_box.rb index 6d51f2629a..02f87fc89f 100644 --- a/actionview/lib/action_view/helpers/tags/check_box.rb +++ b/actionview/lib/action_view/helpers/tags/check_box.rb @@ -1,4 +1,4 @@ -require 'action_view/helpers/tags/checkable' +require "action_view/helpers/tags/checkable" module ActionView module Helpers @@ -38,26 +38,26 @@ module ActionView private - def checked?(value) - case value - when TrueClass, FalseClass - value == !!@checked_value - when NilClass - false - when String - value == @checked_value - else - if value.respond_to?(:include?) - value.include?(@checked_value) + def checked?(value) + case value + when TrueClass, FalseClass + value == !!@checked_value + when NilClass + false + when String + value == @checked_value else - value.to_i == @checked_value.to_i + if value.respond_to?(:include?) + value.include?(@checked_value) + else + value.to_i == @checked_value.to_i + end end end - end - def hidden_field_for_checkbox(options) - @unchecked_value ? tag("input", options.slice("name", "disabled", "form").merge!("type" => "hidden", "value" => @unchecked_value)) : "".html_safe - end + def hidden_field_for_checkbox(options) + @unchecked_value ? tag("input", options.slice("name", "disabled", "form").merge!("type" => "hidden", "value" => @unchecked_value)) : "".html_safe + end 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 3dda47a458..2a6bf49567 100644 --- a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb +++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb @@ -1,4 +1,4 @@ -require 'action_view/helpers/tags/collection_helpers' +require "action_view/helpers/tags/collection_helpers" module ActionView module Helpers @@ -20,13 +20,13 @@ module ActionView private - def render_component(builder) - builder.check_box + builder.label - end + def render_component(builder) + builder.check_box + builder.label + end - def hidden_field_name #:nodoc: - "#{super}[]" - end + def hidden_field_name #:nodoc: + "#{super}[]" + end end 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 fb51460c8e..36575b2fd0 100644 --- a/actionview/lib/action_view/helpers/tags/collection_helpers.rb +++ b/actionview/lib/action_view/helpers/tags/collection_helpers.rb @@ -36,81 +36,81 @@ module ActionView private - def instantiate_builder(builder_class, item, value, text, html_options) - builder_class.new(@template_object, @object_name, @method_name, item, - sanitize_attribute_name(value), text, value, html_options) - end - - # Generate default options for collection helpers, such as :checked and - # :disabled. - def default_html_options_for_collection(item, value) #:nodoc: - html_options = @html_options.dup - - [:checked, :selected, :disabled, :readonly].each do |option| - current_value = @options[option] - next if current_value.nil? + def instantiate_builder(builder_class, item, value, text, html_options) + builder_class.new(@template_object, @object_name, @method_name, item, + sanitize_attribute_name(value), text, value, html_options) + end - accept = if current_value.respond_to?(:call) - current_value.call(item) - else - Array(current_value).map(&:to_s).include?(value.to_s) + # Generate default options for collection helpers, such as :checked and + # :disabled. + def default_html_options_for_collection(item, value) #:nodoc: + html_options = @html_options.dup + + [:checked, :selected, :disabled, :readonly].each do |option| + current_value = @options[option] + next if current_value.nil? + + accept = if current_value.respond_to?(:call) + current_value.call(item) + else + Array(current_value).map(&:to_s).include?(value.to_s) + end + + if accept + html_options[option] = true + elsif option == :checked + html_options[option] = false + end end - if accept - html_options[option] = true - elsif option == :checked - html_options[option] = false - end + html_options[:object] = @object + html_options end - html_options[:object] = @object - html_options - end + def sanitize_attribute_name(value) #:nodoc: + "#{sanitized_method_name}_#{sanitized_value(value)}" + end - def sanitize_attribute_name(value) #:nodoc: - "#{sanitized_method_name}_#{sanitized_value(value)}" - end + def render_collection #:nodoc: + @collection.map do |item| + value = value_for_collection(item, @value_method) + text = value_for_collection(item, @text_method) + default_html_options = default_html_options_for_collection(item, value) + additional_html_options = option_html_attributes(item) - def render_collection #:nodoc: - @collection.map do |item| - value = value_for_collection(item, @value_method) - text = value_for_collection(item, @text_method) - default_html_options = default_html_options_for_collection(item, value) - additional_html_options = option_html_attributes(item) + yield item, value, text, default_html_options.merge(additional_html_options) + end.join.html_safe + end - yield item, value, text, default_html_options.merge(additional_html_options) - end.join.html_safe - end + def render_collection_for(builder_class, &block) #:nodoc: + 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) - def render_collection_for(builder_class, &block) #:nodoc: - 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) + if block_given? + @template_object.capture(builder, &block) + else + render_component(builder) + end + end - if block_given? - @template_object.capture(builder, &block) + # Prepend a hidden field to make sure something will be sent back to the + # server if all radio buttons are unchecked. + if options.fetch("include_hidden", true) + hidden_field + rendered_collection else - render_component(builder) + rendered_collection end end - # Prepend a hidden field to make sure something will be sent back to the - # server if all radio buttons are unchecked. - if options.fetch('include_hidden', true) - hidden_field + rendered_collection - else - rendered_collection + def hidden_field #:nodoc: + hidden_name = @html_options[:name] || hidden_field_name + @template_object.hidden_field_tag(hidden_name, "", id: nil) end - end - def hidden_field #:nodoc: - hidden_name = @html_options[:name] || hidden_field_name - @template_object.hidden_field_tag(hidden_name, "", id: nil) - end - - def hidden_field_name #:nodoc: - "#{tag_name(false, @options[:index])}" - end + def hidden_field_name #:nodoc: + "#{tag_name(false, @options[:index])}" + end end end end diff --git a/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb b/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb index 21aaf122f8..eed7941cd6 100644 --- a/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb +++ b/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb @@ -1,4 +1,4 @@ -require 'action_view/helpers/tags/collection_helpers' +require "action_view/helpers/tags/collection_helpers" module ActionView module Helpers diff --git a/actionview/lib/action_view/helpers/tags/collection_select.rb b/actionview/lib/action_view/helpers/tags/collection_select.rb index 6cb2b2e0d3..4365c714eb 100644 --- a/actionview/lib/action_view/helpers/tags/collection_select.rb +++ b/actionview/lib/action_view/helpers/tags/collection_select.rb @@ -13,8 +13,8 @@ module ActionView def render option_tags_options = { - :selected => @options.fetch(:selected) { value(@object) }, - :disabled => @options[:disabled] + selected: @options.fetch(:selected) { value(@object) }, + disabled: @options[:disabled] } select_content_tag( diff --git a/actionview/lib/action_view/helpers/tags/date_select.rb b/actionview/lib/action_view/helpers/tags/date_select.rb index 0c4ac40070..006605885a 100644 --- a/actionview/lib/action_view/helpers/tags/date_select.rb +++ b/actionview/lib/action_view/helpers/tags/date_select.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/time/calculations' +require "active_support/core_ext/time/calculations" module ActionView module Helpers @@ -22,50 +22,50 @@ module ActionView private - def select_type - self.class.select_type - end + def select_type + self.class.select_type + end - def datetime_selector(options, html_options) - datetime = options.fetch(:selected) { value(object) || default_datetime(options) } - @auto_index ||= nil + def datetime_selector(options, html_options) + datetime = options.fetch(:selected) { value(object) || default_datetime(options) } + @auto_index ||= nil - options = options.dup - options[:field_name] = @method_name - options[:include_position] = true - options[:prefix] ||= @object_name - options[:index] = @auto_index if @auto_index && !options.has_key?(:index) + options = options.dup + options[:field_name] = @method_name + options[:include_position] = true + options[:prefix] ||= @object_name + options[:index] = @auto_index if @auto_index && !options.has_key?(:index) - DateTimeSelector.new(datetime, options, html_options) - end + DateTimeSelector.new(datetime, options, html_options) + end - def default_datetime(options) - return if options[:include_blank] || options[:prompt] + def default_datetime(options) + return if options[:include_blank] || options[:prompt] - case options[:default] - when nil - Time.current - when Date, Time - options[:default] - else - default = options[:default].dup + case options[:default] + when nil + Time.current + when Date, Time + options[:default] + else + default = options[:default].dup - # Rename :minute and :second to :min and :sec - default[:min] ||= default[:minute] - default[:sec] ||= default[:second] + # Rename :minute and :second to :min and :sec + default[:min] ||= default[:minute] + default[:sec] ||= default[:second] - time = Time.current + time = Time.current - [:year, :month, :day, :hour, :min, :sec].each do |key| - default[key] ||= time.send(key) - end + [:year, :month, :day, :hour, :min, :sec].each do |key| + default[key] ||= time.send(key) + end - Time.utc( - default[:year], default[:month], default[:day], - default[:hour], default[:min], default[:sec] - ) + Time.utc( + default[:year], default[:month], default[:day], + default[:hour], default[:min], default[:sec] + ) + end end - end end end end diff --git a/actionview/lib/action_view/helpers/tags/datetime_field.rb b/actionview/lib/action_view/helpers/tags/datetime_field.rb index b2cee9d198..b3940c7e44 100644 --- a/actionview/lib/action_view/helpers/tags/datetime_field.rb +++ b/actionview/lib/action_view/helpers/tags/datetime_field.rb @@ -14,7 +14,7 @@ module ActionView private def format_date(value) - value.try(:strftime, "%Y-%m-%dT%T.%L%z") + raise NotImplementedError end def datetime_value(value) diff --git a/actionview/lib/action_view/helpers/tags/file_field.rb b/actionview/lib/action_view/helpers/tags/file_field.rb index e6a1d9c62d..476b820d84 100644 --- a/actionview/lib/action_view/helpers/tags/file_field.rb +++ b/actionview/lib/action_view/helpers/tags/file_field.rb @@ -2,21 +2,6 @@ module ActionView module Helpers module Tags # :nodoc: class FileField < TextField # :nodoc: - - def render - options = @options.stringify_keys - - if options.fetch("include_hidden", true) - add_default_name_and_id(options) - options[:type] = "file" - tag("input", name: options["name"], type: "hidden", value: "") + tag("input", options) - else - options.delete("include_hidden") - @options = options - - super - end - end end end end diff --git a/actionview/lib/action_view/helpers/tags/grouped_collection_select.rb b/actionview/lib/action_view/helpers/tags/grouped_collection_select.rb index 2ed4712dac..20e312dd0f 100644 --- a/actionview/lib/action_view/helpers/tags/grouped_collection_select.rb +++ b/actionview/lib/action_view/helpers/tags/grouped_collection_select.rb @@ -15,8 +15,8 @@ module ActionView def render option_tags_options = { - :selected => @options.fetch(:selected) { value(@object) }, - :disabled => @options[:disabled] + selected: @options.fetch(:selected) { value(@object) }, + disabled: @options[:disabled] } select_content_tag( diff --git a/actionview/lib/action_view/helpers/tags/password_field.rb b/actionview/lib/action_view/helpers/tags/password_field.rb index 6099fa6f19..444ef65074 100644 --- a/actionview/lib/action_view/helpers/tags/password_field.rb +++ b/actionview/lib/action_view/helpers/tags/password_field.rb @@ -3,7 +3,7 @@ module ActionView module Tags # :nodoc: class PasswordField < TextField # :nodoc: def render - @options = {:value => nil}.merge!(@options) + @options = { value: nil }.merge!(@options) super end end diff --git a/actionview/lib/action_view/helpers/tags/radio_button.rb b/actionview/lib/action_view/helpers/tags/radio_button.rb index 4849c537a5..43dbd32083 100644 --- a/actionview/lib/action_view/helpers/tags/radio_button.rb +++ b/actionview/lib/action_view/helpers/tags/radio_button.rb @@ -1,4 +1,4 @@ -require 'action_view/helpers/tags/checkable' +require "action_view/helpers/tags/checkable" module ActionView module Helpers @@ -22,9 +22,9 @@ module ActionView private - def checked?(value) - value.to_s == @tag_value.to_s - end + def checked?(value) + value.to_s == @tag_value.to_s + end end end end diff --git a/actionview/lib/action_view/helpers/tags/select.rb b/actionview/lib/action_view/helpers/tags/select.rb index 180900cc8d..667c7e945a 100644 --- a/actionview/lib/action_view/helpers/tags/select.rb +++ b/actionview/lib/action_view/helpers/tags/select.rb @@ -13,8 +13,8 @@ module ActionView def render option_tags_options = { - :selected => @options.fetch(:selected) { value(@object) }, - :disabled => @options[:disabled] + selected: @options.fetch(:selected) { value(@object) }, + disabled: @options[:disabled] } option_tags = if grouped_choices? @@ -28,13 +28,13 @@ module ActionView private - # Grouped choices look like this: - # - # [nil, []] - # { nil => [] } - def grouped_choices? - !@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last - end + # Grouped choices look like this: + # + # [nil, []] + # { nil => [] } + def grouped_choices? + !@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last + end end end end diff --git a/actionview/lib/action_view/helpers/tags/text_area.rb b/actionview/lib/action_view/helpers/tags/text_area.rb index 69038c1498..31e3a9e9b1 100644 --- a/actionview/lib/action_view/helpers/tags/text_area.rb +++ b/actionview/lib/action_view/helpers/tags/text_area.rb @@ -1,4 +1,4 @@ -require 'action_view/helpers/tags/placeholderable' +require "action_view/helpers/tags/placeholderable" module ActionView module Helpers diff --git a/actionview/lib/action_view/helpers/tags/text_field.rb b/actionview/lib/action_view/helpers/tags/text_field.rb index 5c576a20ca..4306c3543d 100644 --- a/actionview/lib/action_view/helpers/tags/text_field.rb +++ b/actionview/lib/action_view/helpers/tags/text_field.rb @@ -1,4 +1,4 @@ -require 'action_view/helpers/tags/placeholderable' +require "action_view/helpers/tags/placeholderable" module ActionView module Helpers @@ -23,9 +23,9 @@ module ActionView private - def field_type - self.class.field_type - end + def field_type + self.class.field_type + end end end end diff --git a/actionview/lib/action_view/helpers/tags/translator.rb b/actionview/lib/action_view/helpers/tags/translator.rb index 8b6655481d..62b1df81c6 100644 --- a/actionview/lib/action_view/helpers/tags/translator.rb +++ b/actionview/lib/action_view/helpers/tags/translator.rb @@ -16,24 +16,24 @@ module ActionView protected - attr_reader :object_name, :method_and_value, :scope, :model + attr_reader :object_name, :method_and_value, :scope, :model private - def i18n_default - if model - key = model.model_name.i18n_key - ["#{key}.#{method_and_value}".to_sym, ""] - else - "" + def i18n_default + if model + key = model.model_name.i18n_key + ["#{key}.#{method_and_value}".to_sym, ""] + else + "" + end end - end - def human_attribute_name - if model && model.class.respond_to?(:human_attribute_name) - model.class.human_attribute_name(method_and_value) + def human_attribute_name + if model && model.class.respond_to?(:human_attribute_name) + model.class.human_attribute_name(method_and_value) + end end - end end end end diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb index 58ce042f12..bd3371ccc8 100644 --- a/actionview/lib/action_view/helpers/text_helper.rb +++ b/actionview/lib/action_view/helpers/text_helper.rb @@ -1,5 +1,5 @@ -require 'active_support/core_ext/string/filters' -require 'active_support/core_ext/array/extract_options' +require "active_support/core_ext/string/filters" +require "active_support/core_ext/array/extract_options" module ActionView # = Action View Text Helpers @@ -135,7 +135,7 @@ module ActionView else match = Array(phrases).map do |p| Regexp === p ? p.to_s : Regexp.escape(p) - end.join('|') + end.join("|") if block_given? text.gsub(/(#{match})(?![^<]*?>)/i) { |found| yield found } @@ -225,14 +225,7 @@ module ActionView # # pluralize(2, 'Person', locale: :de) # # => 2 Personen - def pluralize(count, singular, deprecated_plural = nil, plural: nil, locale: I18n.locale) - if deprecated_plural - ActiveSupport::Deprecation.warn("Passing plural as a positional argument " \ - "is deprecated and will be removed in Rails 5.1. Use e.g. " \ - "pluralize(1, 'person', plural: 'people') instead.") - plural ||= deprecated_plural - end - + def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale) word = if (count == 1 || count =~ /^1(\.0+)?$/) singular else @@ -269,10 +262,11 @@ module ActionView end # Returns +text+ transformed into HTML using simple formatting rules. - # Two or more consecutive newlines(<tt>\n\n</tt>) are considered as a - # paragraph and wrapped in <tt><p></tt> tags. One newline (<tt>\n</tt>) is - # considered as a linebreak and a <tt><br /></tt> tag is appended. This - # method does not remove the newlines from the +text+. + # Two or more consecutive newlines(<tt>\n\n</tt> or <tt>\r\n\r\n</tt>) are + # considered a paragraph and wrapped in <tt><p></tt> tags. One newline + # (<tt>\n</tt> or <tt>\r\n</tt>) is considered a linebreak and a + # <tt><br /></tt> tag is appended. This method does not remove the + # newlines from the +text+. # # You can pass any HTML attributes into <tt>html_options</tt>. These # will be added to all created paragraphs. @@ -357,7 +351,7 @@ module ActionView # <% end %> def cycle(first_value, *values) options = values.extract_options! - name = options.fetch(:name, 'default') + name = options.fetch(:name, "default") values.unshift(*first_value) @@ -431,17 +425,17 @@ module ActionView private - def next_index - step_index(1) - end + def next_index + step_index(1) + end - def previous_index - step_index(-1) - end + def previous_index + step_index(-1) + end - def step_index(n) - (@index + n) % @values.size - end + def step_index(n) + (@index + n) % @values.size + end end private diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb index 152e1b1211..47ed41a129 100644 --- a/actionview/lib/action_view/helpers/translation_helper.rb +++ b/actionview/lib/action_view/helpers/translation_helper.rb @@ -1,6 +1,6 @@ -require 'action_view/helpers/tag_helper' -require 'active_support/core_ext/string/access' -require 'i18n/exceptions' +require "action_view/helpers/tag_helper" +require "active_support/core_ext/string/access" +require "i18n/exceptions" module ActionView # = Action View Translation Helpers @@ -100,12 +100,12 @@ module ActionView interpolations = options.except(:default, :scope) if interpolations.any? - title << ", " << interpolations.map { |k, v| "#{k}: #{ERB::Util.html_escape(v)}" }.join(', ') + title << ", " << interpolations.map { |k, v| "#{k}: #{ERB::Util.html_escape(v)}" }.join(", ") end return title unless ActionView::Base.debug_missing_translation - content_tag('span', keys.last.to_s.titleize, class: 'translation_missing', title: title) + content_tag("span", keys.last.to_s.titleize, class: "translation_missing", title: title) end end alias :t :translate @@ -133,7 +133,7 @@ module ActionView end def html_safe_translation_key?(key) - key.to_s =~ /(\b|_|\.)html$/ + /(\b|_|\.)html$/.match?(key.to_s) end end end diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb index 3a4561a083..36b7515173 100644 --- a/actionview/lib/action_view/helpers/url_helper.rb +++ b/actionview/lib/action_view/helpers/url_helper.rb @@ -1,7 +1,7 @@ -require 'action_view/helpers/javascript_helper' -require 'active_support/core_ext/array/access' -require 'active_support/core_ext/hash/keys' -require 'active_support/core_ext/string/output_safety' +require "action_view/helpers/javascript_helper" +require "active_support/core_ext/array/access" +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/string/output_safety" module ActionView # = Action View URL Helpers @@ -41,14 +41,14 @@ module ActionView end def _back_url # :nodoc: - _filtered_referrer || 'javascript:history.back()' + _filtered_referrer || "javascript:history.back()" end protected :_back_url def _filtered_referrer # :nodoc: if controller.respond_to?(:request) referrer = controller.request.env["HTTP_REFERER"] - if referrer && URI(referrer).scheme != 'javascript' + if referrer && URI(referrer).scheme != "javascript" referrer end end @@ -298,42 +298,43 @@ module ActionView html_options = html_options.stringify_keys url = options.is_a?(String) ? options : url_for(options) - remote = html_options.delete('remote') - params = html_options.delete('params') + remote = html_options.delete("remote") + params = html_options.delete("params") - method = html_options.delete('method').to_s - method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : ''.html_safe + method = html_options.delete("method").to_s + method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : "".freeze.html_safe - form_method = method == 'get' ? 'get' : 'post' - form_options = html_options.delete('form') || {} - form_options[:class] ||= html_options.delete('form_class') || 'button_to' + form_method = method == "get" ? "get" : "post" + form_options = html_options.delete("form") || {} + form_options[:class] ||= html_options.delete("form_class") || "button_to" form_options[:method] = form_method form_options[:action] = url form_options[:'data-remote'] = true if remote - request_token_tag = if form_method == 'post' - token_tag(nil, form_options: form_options) + request_token_tag = if form_method == "post" + request_method = method.empty? ? "post" : method + token_tag(nil, form_options: { action: url, method: request_method }) else - '' + "".freeze end html_options = convert_options_to_data_attributes(options, html_options) - html_options['type'] = 'submit' + html_options["type"] = "submit" button = if block_given? - content_tag('button', html_options, &block) + content_tag("button", html_options, &block) else - html_options['value'] = name || url - tag('input', html_options) + html_options["value"] = name || url + tag("input", html_options) end inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag) if params - params.each do |param_name, value| - inner_tags.safe_concat tag(:input, type: "hidden", name: param_name, value: value.to_param) + to_form_params(params).each do |param| + inner_tags.safe_concat tag(:input, type: "hidden", name: param[:name], value: param[:value]) end end - content_tag('form', inner_tags, form_options) + content_tag("form", inner_tags, form_options) end # Creates a link tag of the given +name+ using a URL created by the set of @@ -480,7 +481,7 @@ module ActionView option = html_options.delete(item).presence || next "#{item.dasherize}=#{ERB::Util.url_encode(option)}" }.compact - extras = extras.empty? ? '' : '?' + extras.join('&') + extras = extras.empty? ? "".freeze : "?" + extras.join("&") encoded_email_address = ERB::Util.url_encode(email_address).gsub("%40", "@") html_options["href"] = "mailto:#{encoded_email_address}#{extras}" @@ -547,7 +548,9 @@ module ActionView request_uri = url_string.index("?") ? request.fullpath : request.path request_uri = URI.parser.unescape(request_uri).force_encoding(Encoding::BINARY) - if url_string =~ /^\w+:\/\// + url_string.chomp!("/") if url_string.start_with?("/") && url_string != "/" + + if %r{^\w+://}.match?(url_string) url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}" else url_string == request_uri @@ -558,29 +561,29 @@ module ActionView def convert_options_to_data_attributes(options, html_options) if html_options html_options = html_options.stringify_keys - html_options['data-remote'] = 'true' if link_to_remote_options?(options) || link_to_remote_options?(html_options) + html_options["data-remote"] = "true".freeze if link_to_remote_options?(options) || link_to_remote_options?(html_options) - method = html_options.delete('method') + method = html_options.delete("method".freeze) add_method_to_attributes!(html_options, method) if method html_options else - link_to_remote_options?(options) ? {'data-remote' => 'true'} : {} + link_to_remote_options?(options) ? { "data-remote" => "true".freeze } : {} end end def link_to_remote_options?(options) if options.is_a?(Hash) - options.delete('remote') || options.delete(:remote) + options.delete("remote".freeze) || options.delete(:remote) end end def add_method_to_attributes!(html_options, method) - if method && method.to_s.downcase != "get" && html_options["rel"] !~ /nofollow/ - html_options["rel"] = "#{html_options["rel"]} nofollow".lstrip + if method && method.to_s.downcase != "get".freeze && html_options["rel".freeze] !~ /nofollow/ + html_options["rel".freeze] = "#{html_options["rel".freeze]} nofollow".lstrip end - html_options["data-method"] = method + html_options["data-method".freeze] = method end def token_tag(token=nil, form_options: {}) @@ -588,12 +591,59 @@ module ActionView token ||= form_authenticity_token(form_options: form_options) tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token) else - '' + "".freeze end end def method_tag(method) - tag('input', type: 'hidden', name: '_method', value: method.to_s) + tag("input", type: "hidden", name: "_method", value: method.to_s) + end + + # Returns an array of hashes each containing :name and :value keys + # suitable for use as the names and values of form input fields: + # + # to_form_params(name: 'David', nationality: 'Danish') + # # => [{name: :name, value: 'David'}, {name: 'nationality', value: 'Danish'}] + # + # to_form_params(country: {name: 'Denmark'}) + # # => [{name: 'country[name]', value: 'Denmark'}] + # + # to_form_params(countries: ['Denmark', 'Sweden']}) + # # => [{name: 'countries[]', value: 'Denmark'}, {name: 'countries[]', value: 'Sweden'}] + # + # An optional namespace can be passed to enclose key names: + # + # to_form_params({ name: 'Denmark' }, 'country') + # # => [{name: 'country[name]', value: 'Denmark'}] + def to_form_params(attribute, namespace = nil) # :nodoc: + attribute = if attribute.respond_to?(:permitted?) + unless attribute.permitted? + raise ArgumentError, "Attempting to generate a buttom from non-sanitized request parameters!" \ + " Whitelist and sanitize passed parameters to be secure." + end + + attribute.to_h + else + attribute + end + + params = [] + case attribute + when Hash + attribute.each do |key, value| + prefix = namespace ? "#{namespace}[#{key}]" : key + params.push(*to_form_params(value, prefix)) + end + when Array + array_prefix = "#{namespace}[]" + attribute.each do |value| + params.push(*to_form_params(value, array_prefix)) + end + else + params << { name: namespace, value: attribute.to_param } + end + + params.sort_by { |pair| pair[:name] } end end end diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb index a74a5e05f3..e8abfeac52 100644 --- a/actionview/lib/action_view/layouts.rb +++ b/actionview/lib/action_view/layouts.rb @@ -204,7 +204,7 @@ module ActionView include ActionView::Rendering included do - class_attribute :_layout, :_layout_conditions, :instance_accessor => false + class_attribute :_layout, :_layout_conditions, instance_accessor: false self._layout = nil self._layout_conditions = {} _write_layout_method @@ -223,36 +223,39 @@ module ActionView module LayoutConditions # :nodoc: private - # Determines whether the current action has a layout definition by - # checking the action name against the :only and :except conditions - # set by the <tt>layout</tt> method. - # - # ==== Returns - # * <tt>Boolean</tt> - True if the action has a layout definition, false otherwise. - def _conditional_layout? - return unless super - - conditions = _layout_conditions - - if only = conditions[:only] - only.include?(action_name) - elsif except = conditions[:except] - !except.include?(action_name) - else - true + # Determines whether the current action has a layout definition by + # checking the action name against the :only and :except conditions + # set by the <tt>layout</tt> method. + # + # ==== Returns + # * <tt>Boolean</tt> - True if the action has a layout definition, false otherwise. + def _conditional_layout? + return unless super + + conditions = _layout_conditions + + if only = conditions[:only] + only.include?(action_name) + elsif except = conditions[:except] + !except.include?(action_name) + else + true + end end - end end # Specify the layout to use for this class. # # If the specified layout is a: # String:: the String is the template name - # Symbol:: call the method specified by the symbol, which will return the template name + # Symbol:: call the method specified by the symbol + # Proc:: call the passed Proc # false:: There is no layout # true:: raise an ArgumentError # nil:: Force default layout behavior with inheritance # + # Return value of Proc & Symbol arguments should be String, false, true or nil + # with the same meaning as described above. # ==== Parameters # * <tt>layout</tt> - The layout to use. # @@ -262,7 +265,7 @@ module ActionView def layout(layout, conditions = {}) include LayoutConditions unless conditions.empty? - conditions.each {|k, v| conditions[k] = Array(v).map(&:to_s) } + conditions.each { |k, v| conditions[k] = Array(v).map(&:to_s) } self._layout_conditions = conditions self._layout = layout @@ -276,7 +279,7 @@ module ActionView def _write_layout_method # :nodoc: remove_possible_method(:_layout) - prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"] + prefixes = /\blayouts/.match?(_implied_layout_name) ? [] : ["layouts"] default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}, false, [], { formats: formats }).first || super" name_clause = if name default_behavior @@ -286,7 +289,8 @@ module ActionView RUBY end - layout_definition = case _layout + layout_definition = \ + case _layout when String _layout.inspect when Symbol @@ -313,7 +317,7 @@ module ActionView raise ArgumentError, "Layouts must be specified as a String, Symbol, Proc, false, or nil" when nil name_clause - end + end self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 def _layout(formats) @@ -329,14 +333,14 @@ module ActionView private - # If no layout is supplied, look for a template named the return - # value of this method. - # - # ==== Returns - # * <tt>String</tt> - A template name - def _implied_layout_name # :nodoc: - controller_path - end + # If no layout is supplied, look for a template named the return + # value of this method. + # + # ==== Returns + # * <tt>String</tt> - A template name + def _implied_layout_name # :nodoc: + controller_path + end end def _normalize_options(options) # :nodoc: @@ -421,7 +425,7 @@ module ActionView end def _include_layout?(options) - (options.keys & [:body, :text, :plain, :html, :inline, :partial]).empty? || options.key?(:layout) + (options.keys & [:body, :plain, :html, :inline, :partial]).empty? || options.key?(:layout) end end end diff --git a/actionview/lib/action_view/log_subscriber.rb b/actionview/lib/action_view/log_subscriber.rb index 9047dbdd85..c9f308c2a2 100644 --- a/actionview/lib/action_view/log_subscriber.rb +++ b/actionview/lib/action_view/log_subscriber.rb @@ -1,4 +1,4 @@ -require 'active_support/log_subscriber' +require "active_support/log_subscriber" module ActionView # = Action View Log Subscriber @@ -19,8 +19,33 @@ module ActionView message << " (#{event.duration.round(1)}ms)" end end - alias :render_partial :render_template - alias :render_collection :render_template + + def render_partial(event) + info do + message = " Rendered #{from_rails_root(event.payload[:identifier])}" + message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout] + message << " (#{event.duration.round(1)}ms)" + message << " #{cache_message(event.payload)}" if event.payload.key?(:cache_hit) + message + end + end + + def render_collection(event) + identifier = event.payload[:identifier] || "templates" + + info do + " Rendered collection of #{from_rails_root(identifier)}" \ + " #{render_count(event.payload)} (#{event.duration.round(1)}ms)" + end + end + + def start(name, id, payload) + if name == "render_template.action_view" + log_rendering_start(payload) + end + + super + end def logger ActionView::Base.logger @@ -28,7 +53,7 @@ module ActionView protected - EMPTY = '' + EMPTY = "" def from_rails_root(string) string = string.sub(rails_root, EMPTY) string.sub!(VIEWS_PATTERN, EMPTY) @@ -38,6 +63,32 @@ module ActionView def rails_root @root ||= "#{Rails.root}/" end + + def render_count(payload) + if payload[:cache_hits] + "[#{payload[:cache_hits]} / #{payload[:count]} cache hits]" + else + "[#{payload[:count]} times]" + end + end + + def cache_message(payload) + if payload[:cache_hit] + "[cache hit]" + else + "[cache miss]" + end + end + + private + + def log_rendering_start(payload) + info do + message = " Rendering #{from_rails_root(payload[:identifier])}" + message << " within #{from_rails_root(payload[:layout])}" if payload[:layout] + message + end + end end end diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb index 126f289f55..9d6c762cc4 100644 --- a/actionview/lib/action_view/lookup_context.rb +++ b/actionview/lib/action_view/lookup_context.rb @@ -1,7 +1,7 @@ -require 'concurrent/map' -require 'active_support/core_ext/module/remove_method' -require 'active_support/core_ext/module/attribute_accessors' -require 'action_view/template/resolver' +require "concurrent/map" +require "active_support/core_ext/module/remove_method" +require "active_support/core_ext/module/attribute_accessors" +require "action_view/template/resolver" module ActionView # = Action View Lookup Context @@ -21,7 +21,7 @@ module ActionView self.registered_details = [] def self.register_detail(name, &block) - self.registered_details << name + registered_details << name Accessors::DEFAULT_PROCS[name] = block Accessors.send :define_method, :"default_#{name}", &block @@ -55,9 +55,7 @@ module ActionView class DetailsKey #:nodoc: alias :eql? :equal? - alias :object_hash :hash - attr_reader :hash @details_keys = Concurrent::Map.new def self.get(details) @@ -65,15 +63,15 @@ module ActionView details = details.dup details[:formats] &= Template::Types.symbols end - @details_keys[details] ||= new + @details_keys[details] ||= Concurrent::Map.new end def self.clear @details_keys.clear end - def initialize - @hash = object_hash + def self.digest_caches + @details_keys.values end end @@ -132,6 +130,11 @@ module ActionView end alias :template_exists? :exists? + def any?(name, prefixes = [], partial = false) + @view_paths.exists?(*args_for_any(name, prefixes, partial)) + end + alias :any_templates? :any? + # Adds fallbacks to the view paths. Useful in cases when you are rendering # a :file. def with_fallbacks @@ -168,18 +171,44 @@ module ActionView [user_details, details_key] end + def args_for_any(name, prefixes, partial) # :nodoc: + 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: + @detail_args_for_any ||= begin + details = {} + + registered_details.each do |k| + if k == :variants + details[k] = :any + else + details[k] = Accessors::DEFAULT_PROCS[k].call + end + end + + if @cache + [details, DetailsKey.get(details)] + else + [details, nil] + end + end + end + # 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: prefixes = prefixes.presence - parts = name.to_s.split('/'.freeze) + parts = name.to_s.split("/".freeze) parts.shift if parts.first.empty? name = parts.pop return name, prefixes || [""] if parts.empty? - parts = parts.join('/'.freeze) + parts = parts.join("/".freeze) prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts] return name, prefixes @@ -200,6 +229,10 @@ module ActionView self.view_paths = view_paths end + def digest_cache + details_key + end + def initialize_details(target, details) registered_details.each do |k| target[k] = details[k] || Accessors::DEFAULT_PROCS[k].call diff --git a/actionview/lib/action_view/path_set.rb b/actionview/lib/action_view/path_set.rb index f68d2a77ed..6688519ffd 100644 --- a/actionview/lib/action_view/path_set.rb +++ b/actionview/lib/action_view/path_set.rb @@ -69,30 +69,30 @@ module ActionView #:nodoc: private - def _find_all(path, prefixes, args, outside_app) - prefixes = [prefixes] if String === prefixes - prefixes.each do |prefix| - paths.each do |resolver| - if outside_app - templates = resolver.find_all_anywhere(path, prefix, *args) - else - templates = resolver.find_all(path, prefix, *args) + def _find_all(path, prefixes, args, outside_app) + prefixes = [prefixes] if String === prefixes + prefixes.each do |prefix| + paths.each do |resolver| + if outside_app + templates = resolver.find_all_anywhere(path, prefix, *args) + else + templates = resolver.find_all(path, prefix, *args) + end + return templates unless templates.empty? end - return templates unless templates.empty? end + [] end - [] - end - def typecast(paths) - paths.map do |path| - case path - when Pathname, String - OptimizedFileSystemResolver.new path.to_s - else - path + def typecast(paths) + paths.map do |path| + case path + when Pathname, String + OptimizedFileSystemResolver.new path.to_s + else + path + end end end - end end end diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb index 59d869d92d..42795ca2c7 100644 --- a/actionview/lib/action_view/railtie.rb +++ b/actionview/lib/action_view/railtie.rb @@ -37,16 +37,10 @@ module ActionView end end - initializer "action_view.collection_caching" do |app| - ActiveSupport.on_load(:action_controller) do - PartialRenderer.collection_cache = app.config.action_controller.cache_store - end - end - initializer "action_view.per_request_digest_cache" do |app| ActiveSupport.on_load(:action_view) do if app.config.consider_all_requests_local - app.middleware.use ActionView::Digestor::PerRequestDigestCacheExpiry + app.executor.to_run ActionView::Digestor::PerExecutionDigestCacheExpiry end end end @@ -57,9 +51,13 @@ module ActionView end end + initializer "action_view.collection_caching", after: "action_controller.set_configs" do |app| + PartialRenderer.collection_cache = app.config.action_controller.cache_store + end + rake_tasks do |app| unless app.config.api_only - load "action_view/tasks/dependencies.rake" + load "action_view/tasks/cache_digests.rake" end end end diff --git a/actionview/lib/action_view/record_identifier.rb b/actionview/lib/action_view/record_identifier.rb index 4a2547b0fb..b39acfa0b5 100644 --- a/actionview/lib/action_view/record_identifier.rb +++ b/actionview/lib/action_view/record_identifier.rb @@ -1,5 +1,5 @@ -require 'active_support/core_ext/module' -require 'action_view/model_naming' +require "active_support/core_ext/module" +require "action_view/model_naming" module ActionView # RecordIdentifier encapsulates methods used by various ActionView helpers @@ -57,8 +57,8 @@ module ActionView include ModelNaming - JOIN = '_'.freeze - NEW = 'new'.freeze + JOIN = "_".freeze + NEW = "new".freeze # The DOM class convention is to use the singular form of an object or class. # diff --git a/actionview/lib/action_view/renderer/abstract_renderer.rb b/actionview/lib/action_view/renderer/abstract_renderer.rb index aa77a77acf..3c85be49cd 100644 --- a/actionview/lib/action_view/renderer/abstract_renderer.rb +++ b/actionview/lib/action_view/renderer/abstract_renderer.rb @@ -15,7 +15,7 @@ module ActionView # that new object is called in turn. This abstracts the setup and rendering # into a separate classes for partials and templates. class AbstractRenderer #:nodoc: - delegate :find_template, :find_file, :template_exists?, :with_fallbacks, :with_layout_format, :formats, :to => :@lookup_context + delegate :find_template, :find_file, :template_exists?, :any_templates?, :with_fallbacks, :with_layout_format, :formats, to: :@lookup_context def initialize(lookup_context) @lookup_context = lookup_context @@ -27,23 +27,27 @@ module ActionView protected - def extract_details(options) - @lookup_context.registered_details.each_with_object({}) do |key, details| - value = options[key] + def extract_details(options) + @lookup_context.registered_details.each_with_object({}) do |key, details| + value = options[key] - details[key] = Array(value) if value + details[key] = Array(value) if value + end end - end - def instrument(name, options={}) - ActiveSupport::Notifications.instrument("render_#{name}.action_view", options){ yield } - end + def instrument(name, **options) + options[:identifier] ||= (@template && @template.identifier) || @path + + ActiveSupport::Notifications.instrument("render_#{name}.action_view", options) do |payload| + yield payload + end + end - def prepend_formats(formats) - formats = Array(formats) - return if formats.empty? || @lookup_context.html_fallback_for_js + def prepend_formats(formats) + formats = Array(formats) + return if formats.empty? || @lookup_context.html_fallback_for_js - @lookup_context.formats = formats | @lookup_context.formats - end + @lookup_context.formats = formats | @lookup_context.formats + end end end diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb index a9bd257e76..c900da3277 100644 --- a/actionview/lib/action_view/renderer/partial_renderer.rb +++ b/actionview/lib/action_view/renderer/partial_renderer.rb @@ -1,5 +1,5 @@ -require 'action_view/renderer/partial_renderer/collection_caching' -require 'concurrent/map' +require "concurrent/map" +require "action_view/renderer/partial_renderer/collection_caching" module ActionView class PartialIteration @@ -294,7 +294,7 @@ module ActionView def render(context, options, block) setup(context, options, block) - identifier = (@template = find_partial) ? @template.identifier : @path + @template = find_partial @lookup_context.rendered_format ||= begin if @template && @template.formats.present? @@ -305,252 +305,246 @@ module ActionView end if @collection - instrument(:collection, :identifier => identifier || "collection", :count => @collection.size) do - render_collection - end + render_collection else - instrument(:partial, :identifier => identifier) do - render_partial - end + render_partial end end private - def render_collection - return nil if @collection.blank? + def render_collection + instrument(:collection, count: @collection.size) do |payload| + return nil if @collection.blank? - if @options.key?(:spacer_template) - spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals) + if @options.key?(:spacer_template) + spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals) + end + + cache_collection_render(payload) do + @template ? collection_with_template : collection_without_template + end.join(spacer).html_safe + end end - cache_collection_render do - @template ? collection_with_template : collection_without_template - end.join(spacer).html_safe - end + def render_partial + instrument(:partial) do |payload| + view, locals, block = @view, @locals, @block + object, as = @object, @variable - def render_partial - view, locals, block = @view, @locals, @block - object, as = @object, @variable + if !block && (layout = @options[:layout]) + layout = find_template(layout.to_s, @template_keys) + end - if !block && (layout = @options[:layout]) - layout = find_template(layout.to_s, @template_keys) - end + object = locals[as] if object.nil? # Respect object when object is false + locals[as] = object if @has_object - object = locals[as] if object.nil? # Respect object when object is false - locals[as] = object if @has_object + content = @template.render(view, locals) do |*name| + view._layout_for(*name, &block) + end - content = @template.render(view, locals) do |*name| - view._layout_for(*name, &block) + content = layout.render(view, locals) { content } if layout + payload[:cache_hit] = view.cache_hit + content + end end - content = layout.render(view, locals){ content } if layout - content - end + # Sets up instance variables needed for rendering a partial. This method + # finds the options and details and extracts them. The method also contains + # logic that handles the type of object passed in as the partial. + # + # If +options[:partial]+ is a string, then the +@path+ instance variable is + # set to that string. Otherwise, the +options[:partial]+ object must + # respond to +to_partial_path+ in order to setup the path. + def setup(context, options, block) + @view = context + @options = options + @block = block + + @locals = options[:locals] || {} + @details = extract_details(options) + + prepend_formats(options[:formats]) + + partial = options[:partial] + + if String === partial + @has_object = options.key?(:object) + @object = options[:object] + @collection = collection_from_options + @path = partial + else + @has_object = true + @object = partial + @collection = collection_from_object || collection_from_options + + if @collection + paths = @collection_data = @collection.map { |o| partial_path(o) } + @path = paths.uniq.one? ? paths.first : nil + else + @path = partial_path + end + end - # Sets up instance variables needed for rendering a partial. This method - # finds the options and details and extracts them. The method also contains - # logic that handles the type of object passed in as the partial. - # - # If +options[:partial]+ is a string, then the +@path+ instance variable is - # set to that string. Otherwise, the +options[:partial]+ object must - # respond to +to_partial_path+ in order to setup the path. - def setup(context, options, block) - @view = context - @options = options - @block = block - - @locals = options[:locals] || {} - @details = extract_details(options) - - prepend_formats(options[:formats]) - - partial = options[:partial] - - if String === partial - @has_object = options.key?(:object) - @object = options[:object] - @collection = collection_from_options - @path = partial - else - @has_object = true - @object = partial - @collection = collection_from_object || collection_from_options + if as = options[:as] + raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s) + as = as.to_sym + end - if @collection - paths = @collection_data = @collection.map { |o| partial_path(o) } - @path = paths.uniq.one? ? paths.first : nil + if @path + @variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as) + @template_keys = retrieve_template_keys else - @path = partial_path + paths.map! { |path| retrieve_variable(path, as).unshift(path) } end - end - if as = options[:as] - raise_invalid_option_as(as) unless as.to_s =~ /\A[a-z_]\w*\z/ - as = as.to_sym + self end - if @path - @variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as) - @template_keys = retrieve_template_keys - else - paths.map! { |path| retrieve_variable(path, as).unshift(path) } + def collection_from_options + if @options.key?(:collection) + collection = @options[:collection] + collection ? collection.to_a : [] + end end - self - end - - def collection_from_options - if @options.key?(:collection) - collection = @options[:collection] - collection.respond_to?(:to_ary) ? collection.to_ary : [] + def collection_from_object + @object.to_ary if @object.respond_to?(:to_ary) end - end - - def collection_from_object - @object.to_ary if @object.respond_to?(:to_ary) - end - - def find_partial - find_template(@path, @template_keys) if @path - end - def find_template(path, locals) - prefixes = path.include?(?/) ? [] : @lookup_context.prefixes - @lookup_context.find_template(path, prefixes, true, locals, @details) - end - - def collection_with_template - view, locals, template = @view, @locals, @template - as, counter, iteration = @variable, @variable_counter, @variable_iteration + def find_partial + find_template(@path, @template_keys) if @path + end - if layout = @options[:layout] - layout = find_template(layout, @template_keys) + def find_template(path, locals) + prefixes = path.include?(?/) ? [] : @lookup_context.prefixes + @lookup_context.find_template(path, prefixes, true, locals, @details) end - collection_cache_eligible = automatic_cache_eligible? + def collection_with_template + view, locals, template = @view, @locals, @template + as, counter, iteration = @variable, @variable_counter, @variable_iteration - partial_iteration = PartialIteration.new(@collection.size) - locals[iteration] = partial_iteration + if layout = @options[:layout] + layout = find_template(layout, @template_keys) + end - @collection.map do |object| - locals[as] = object - locals[counter] = partial_iteration.index + partial_iteration = PartialIteration.new(@collection.size) + locals[iteration] = partial_iteration - content = template.render(view, locals) + @collection.map do |object| + locals[as] = object + locals[counter] = partial_iteration.index - if collection_cache_eligible - collection_cache_rendered_partial(content, object) + content = template.render(view, locals) + content = layout.render(view, locals) { content } if layout + partial_iteration.iterate! + content end - - content = layout.render(view, locals) { content } if layout - partial_iteration.iterate! - content end - end - def collection_without_template - view, locals, collection_data = @view, @locals, @collection_data - cache = {} - keys = @locals.keys + def collection_without_template + view, locals, collection_data = @view, @locals, @collection_data + cache = {} + keys = @locals.keys - partial_iteration = PartialIteration.new(@collection.size) + partial_iteration = PartialIteration.new(@collection.size) - @collection.map do |object| - index = partial_iteration.index - path, as, counter, iteration = collection_data[index] + @collection.map do |object| + index = partial_iteration.index + path, as, counter, iteration = collection_data[index] - locals[as] = object - locals[counter] = index - locals[iteration] = partial_iteration + locals[as] = object + locals[counter] = index + locals[iteration] = partial_iteration - template = (cache[path] ||= find_template(path, keys + [as, counter])) - content = template.render(view, locals) - partial_iteration.iterate! - content + template = (cache[path] ||= find_template(path, keys + [as, counter])) + content = template.render(view, locals) + partial_iteration.iterate! + content + end end - end - # Obtains the path to where the object's partial is located. If the object - # responds to +to_partial_path+, then +to_partial_path+ will be called and - # will provide the path. If the object does not respond to +to_partial_path+, - # then an +ArgumentError+ is raised. - # - # If +prefix_partial_path_with_controller_namespace+ is true, then this - # method will prefix the partial paths with a namespace. - def partial_path(object = @object) - object = object.to_model if object.respond_to?(:to_model) - - path = if object.respond_to?(:to_partial_path) - object.to_partial_path - else - raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.") + # Obtains the path to where the object's partial is located. If the object + # responds to +to_partial_path+, then +to_partial_path+ will be called and + # will provide the path. If the object does not respond to +to_partial_path+, + # then an +ArgumentError+ is raised. + # + # If +prefix_partial_path_with_controller_namespace+ is true, then this + # method will prefix the partial paths with a namespace. + def partial_path(object = @object) + object = object.to_model if object.respond_to?(:to_model) + + path = if object.respond_to?(:to_partial_path) + object.to_partial_path + else + raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.") + end + + if @view.prefix_partial_path_with_controller_namespace + prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup) + else + path + end end - if @view.prefix_partial_path_with_controller_namespace - prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup) - else - path + def prefixed_partial_names + @prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix] end - end - def prefixed_partial_names - @prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix] - end + def merge_prefix_into_object_path(prefix, object_path) + if prefix.include?(?/) && object_path.include?(?/) + prefixes = [] + prefix_array = File.dirname(prefix).split("/") + object_path_array = object_path.split("/")[0..-3] # skip model dir & partial - def merge_prefix_into_object_path(prefix, object_path) - if prefix.include?(?/) && object_path.include?(?/) - prefixes = [] - prefix_array = File.dirname(prefix).split('/') - object_path_array = object_path.split('/')[0..-3] # skip model dir & partial + prefix_array.each_with_index do |dir, index| + break if dir == object_path_array[index] + prefixes << dir + end - prefix_array.each_with_index do |dir, index| - break if dir == object_path_array[index] - prefixes << dir + (prefixes << object_path).join("/") + else + object_path end - - (prefixes << object_path).join("/") - else - object_path end - end - def retrieve_template_keys - keys = @locals.keys - keys << @variable if @has_object || @collection - if @collection - keys << @variable_counter - keys << @variable_iteration + def retrieve_template_keys + keys = @locals.keys + keys << @variable if @has_object || @collection + if @collection + keys << @variable_counter + keys << @variable_iteration + end + keys end - keys - end - def retrieve_variable(path, as) - variable = as || begin - base = path[-1] == "/".freeze ? "".freeze : File.basename(path) - raise_invalid_identifier(path) unless base =~ /\A_?(.*)(?:\.\w+)*\z/ - $1.to_sym - end - if @collection - variable_counter = :"#{variable}_counter" - variable_iteration = :"#{variable}_iteration" + def retrieve_variable(path, as) + variable = as || begin + base = path[-1] == "/".freeze ? "".freeze : File.basename(path) + raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/ + $1.to_sym + end + if @collection + variable_counter = :"#{variable}_counter" + variable_iteration = :"#{variable}_iteration" + end + [variable, variable_counter, variable_iteration] end - [variable, variable_counter, variable_iteration] - end - IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " + - "make sure your partial name starts with underscore." + 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, " + - "and is followed by any combination of letters, numbers and underscores." + 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) - raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path)) - end + def raise_invalid_identifier(path) + raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path)) + end - def raise_invalid_option_as(as) - raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as)) - end + def raise_invalid_option_as(as) + raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as)) + end end end diff --git a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb index 4860f00243..1fbe209200 100644 --- a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb +++ b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/object/try' - module ActionView module CollectionCaching # :nodoc: extend ActiveSupport::Concern @@ -11,37 +9,28 @@ module ActionView end private - def cache_collection_render - return yield unless cache_collection? + def cache_collection_render(instrumentation_payload) + return yield unless @options[:cached] keyed_collection = collection_by_cache_keys - cached_partials = collection_cache.read_multi(*keyed_collection.keys) + cached_partials = collection_cache.read_multi(*keyed_collection.keys) + instrumentation_payload[:cache_hits] = cached_partials.size @collection = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values rendered_partials = @collection.empty? ? [] : yield index = 0 - keyed_collection.map do |cache_key, _| - cached_partials.fetch(cache_key) do - rendered_partials[index].tap { index += 1 } - end + fetch_or_cache_partial(cached_partials, order_by: keyed_collection.each_key) do + rendered_partials[index].tap { index += 1 } end end - def cache_collection? - @options.fetch(:cache, automatic_cache_eligible?) - end - - def automatic_cache_eligible? - @template && @template.eligible_for_collection_caching?(as: @options[:as]) - end - def callable_cache_key? - @options[:cache].respond_to?(:call) + @options[:cached].respond_to?(:call) end def collection_by_cache_keys - seed = callable_cache_key? ? @options[:cache] : ->(i) { i } + seed = callable_cache_key? ? @options[:cached] : ->(i) { i } @collection.each_with_object({}) do |item, hash| hash[expanded_cache_key(seed.call(item))] = item @@ -53,10 +42,13 @@ module ActionView key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0. end - def collection_cache_rendered_partial(rendered_partial, key_by) - if callable_cache_key? - key = expanded_cache_key(@options[:cache].call(key_by)) - collection_cache.write(key, rendered_partial, @options[:cache_options]) + def fetch_or_cache_partial(cached_partials, order_by:) + order_by.map do |cache_key| + cached_partials.fetch(cache_key) do + yield.tap do |rendered_partial| + collection_cache.write(cache_key, rendered_partial) + end + end end end end diff --git a/actionview/lib/action_view/renderer/streaming_template_renderer.rb b/actionview/lib/action_view/renderer/streaming_template_renderer.rb index f38e2764d0..2434250b2d 100644 --- a/actionview/lib/action_view/renderer/streaming_template_renderer.rb +++ b/actionview/lib/action_view/renderer/streaming_template_renderer.rb @@ -1,4 +1,4 @@ -require 'fiber' +require "fiber" module ActionView # == TODO @@ -27,17 +27,17 @@ 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: - logger = ActionView::Base.logger - return unless logger + # 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: + logger = ActionView::Base.logger + return unless logger - message = "\n#{exception.class} (#{exception.message}):\n" - message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) - message << " " << exception.backtrace.join("\n ") - logger.fatal("#{message}\n\n") - end + message = "\n#{exception.class} (#{exception.message}):\n" + message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) + message << " " << exception.backtrace.join("\n ") + logger.fatal("#{message}\n\n") + end end # For streaming, instead of rendering a given a template, we return a Body @@ -56,48 +56,48 @@ module ActionView private - def delayed_render(buffer, template, layout, view, locals) - # Wrap the given buffer in the StreamingBuffer and pass it to the - # underlying template handler. Now, every time something is concatenated - # to the buffer, it is not appended to an array, but streamed straight - # to the client. - output = ActionView::StreamingBuffer.new(buffer) - yielder = lambda { |*name| view._layout_for(*name) } - - instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do - fiber = Fiber.new do - if layout - layout.render(view, locals, output, &yielder) - else - # If you don't have a layout, just render the thing - # and concatenate the final result. This is the same - # as a layout with just <%= yield %> - output.safe_concat view._layout_for + def delayed_render(buffer, template, layout, view, locals) + # Wrap the given buffer in the StreamingBuffer and pass it to the + # underlying template handler. Now, every time something is concatenated + # to the buffer, it is not appended to an array, but streamed straight + # to the client. + output = ActionView::StreamingBuffer.new(buffer) + yielder = lambda { |*name| view._layout_for(*name) } + + instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do + fiber = Fiber.new do + if layout + layout.render(view, locals, output, &yielder) + else + # If you don't have a layout, just render the thing + # and concatenate the final result. This is the same + # as a layout with just <%= yield %> + output.safe_concat view._layout_for + end end - end - # Set the view flow to support streaming. It will be aware - # when to stop rendering the layout because it needs to search - # something in the template and vice-versa. - view.view_flow = StreamingFlow.new(view, fiber) + # Set the view flow to support streaming. It will be aware + # when to stop rendering the layout because it needs to search + # something in the template and vice-versa. + view.view_flow = StreamingFlow.new(view, fiber) - # Yo! Start the fiber! - fiber.resume + # Yo! Start the fiber! + fiber.resume - # If the fiber is still alive, it means we need something - # from the template, so start rendering it. If not, it means - # the layout exited without requiring anything from the template. - if fiber.alive? - content = template.render(view, locals, &yielder) + # If the fiber is still alive, it means we need something + # from the template, so start rendering it. If not, it means + # the layout exited without requiring anything from the template. + if fiber.alive? + content = template.render(view, locals, &yielder) - # Once rendering the template is done, sets its content in the :layout key. - view.view_flow.set(:layout, content) + # Once rendering the template is done, sets its content in the :layout key. + view.view_flow.set(:layout, content) - # In case the layout continues yielding, we need to resume - # the fiber until all yields are handled. - fiber.resume while fiber.alive? + # In case the layout continues yielding, we need to resume + # the fiber until all yields are handled. + fiber.resume while fiber.alive? + end end end - end end end diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb index 9d15bbfca7..f40bf8f6e2 100644 --- a/actionview/lib/action_view/renderer/template_renderer.rb +++ b/actionview/lib/action_view/renderer/template_renderer.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/object/try' +require "active_support/core_ext/object/try" module ActionView class TemplateRenderer < AbstractRenderer #:nodoc: @@ -16,87 +16,85 @@ module ActionView private - # Determine the template to be rendered using the given options. - def determine_template(options) - keys = options.has_key?(:locals) ? options[:locals].keys : [] + # Determine the template to be rendered using the given options. + def determine_template(options) + keys = options.has_key?(:locals) ? options[:locals].keys : [] - if options.key?(:body) - Template::Text.new(options[:body]) - elsif options.key?(:text) - Template::Text.new(options[:text], formats.first) - elsif options.key?(:plain) - Template::Text.new(options[:plain]) - elsif options.key?(:html) - Template::HTML.new(options[:html], formats.first) - elsif options.key?(:file) - with_fallbacks { find_file(options[:file], nil, false, keys, @details) } - elsif options.key?(:inline) - handler = Template.handler_for_extension(options[:type] || "erb") - Template.new(options[:inline], "inline template", handler, :locals => keys) - elsif options.key?(:template) - if options[:template].respond_to?(:render) - options[:template] + if options.key?(:body) + Template::Text.new(options[:body]) + elsif options.key?(:plain) + Template::Text.new(options[:plain]) + elsif options.key?(:html) + Template::HTML.new(options[:html], formats.first) + elsif options.key?(:file) + with_fallbacks { find_file(options[:file], nil, false, keys, @details) } + elsif options.key?(:inline) + handler = Template.handler_for_extension(options[:type] || "erb") + Template.new(options[:inline], "inline template", handler, locals: keys) + elsif options.key?(:template) + if options[:template].respond_to?(:render) + options[:template] + else + find_template(options[:template], options[:prefixes], false, keys, @details) + end else - find_template(options[:template], options[:prefixes], false, keys, @details) + raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :html or :body option." end - else - raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :html, :text or :body option." end - end - # Renders the given template. A string representing the layout can be - # supplied as well. - def render_template(template, layout_name = nil, locals = nil) #:nodoc: - view, locals = @view, locals || {} + # Renders the given template. A string representing the layout can be + # supplied as well. + def render_template(template, layout_name = nil, locals = nil) #:nodoc: + view, locals = @view, locals || {} - render_with_layout(layout_name, locals) do |layout| - instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do - template.render(view, locals) { |*name| view._layout_for(*name) } + render_with_layout(layout_name, locals) do |layout| + instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do + template.render(view, locals) { |*name| view._layout_for(*name) } + end end end - end - def render_with_layout(path, locals) #:nodoc: - layout = path && find_layout(path, locals.keys, [formats.first]) - content = yield(layout) + def render_with_layout(path, locals) #:nodoc: + layout = path && find_layout(path, locals.keys, [formats.first]) + content = yield(layout) - if layout - view = @view - view.view_flow.set(:layout, content) - layout.render(view, locals){ |*name| view._layout_for(*name) } - else - content + if layout + view = @view + view.view_flow.set(:layout, content) + layout.render(view, locals) { |*name| view._layout_for(*name) } + else + content + end end - end - # This is the method which actually finds the layout using details in the lookup - # context object. If no layout is found, it checks if at least a layout with - # the given name exists across all details before raising the error. - def find_layout(layout, keys, formats) - resolve_layout(layout, keys, formats) - end + # This is the method which actually finds the layout using details in the lookup + # context object. If no layout is found, it checks if at least a layout with + # the given name exists across all details before raising the error. + def find_layout(layout, keys, formats) + resolve_layout(layout, keys, formats) + end - def resolve_layout(layout, keys, formats) - details = @details.dup - details[:formats] = formats + def resolve_layout(layout, keys, formats) + details = @details.dup + details[:formats] = formats - case layout - when String - begin - if layout =~ /^\// - with_fallbacks { find_template(layout, nil, false, keys, details) } - else - find_template(layout, nil, false, keys, details) + case layout + when String + begin + if layout.start_with?("/") + with_fallbacks { find_template(layout, nil, false, [], details) } + else + find_template(layout, nil, false, [], details) + end + rescue ActionView::MissingTemplate + all_details = @details.merge(formats: @lookup_context.default_formats) + raise unless template_exists?(layout, nil, false, [], all_details) end - rescue ActionView::MissingTemplate - all_details = @details.merge(:formats => @lookup_context.default_formats) - raise unless template_exists?(layout, nil, false, keys, all_details) + when Proc + resolve_layout(layout.call(formats), keys, formats) + else + layout end - when Proc - resolve_layout(layout.call(formats), keys, formats) - else - layout end - end end end diff --git a/actionview/lib/action_view/routing_url_for.rb b/actionview/lib/action_view/routing_url_for.rb index 45e78d1ad9..669cffab1a 100644 --- a/actionview/lib/action_view/routing_url_for.rb +++ b/actionview/lib/action_view/routing_url_for.rb @@ -1,8 +1,7 @@ -require 'action_dispatch/routing/polymorphic_routes' +require "action_dispatch/routing/polymorphic_routes" module ActionView module RoutingUrlFor - # Returns the URL for the set of +options+ provided. This takes the # same options as +url_for+ in Action Controller (see the # documentation for <tt>ActionController::Base#url_for</tt>). Note that by default diff --git a/actionview/lib/action_view/tasks/cache_digests.rake b/actionview/lib/action_view/tasks/cache_digests.rake new file mode 100644 index 0000000000..d30b3f7797 --- /dev/null +++ b/actionview/lib/action_view/tasks/cache_digests.rake @@ -0,0 +1,23 @@ +namespace :cache_digests do + desc "Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html)" + task nested_dependencies: :environment do + abort "You must provide TEMPLATE for the task to run" unless ENV["TEMPLATE"].present? + puts JSON.pretty_generate ActionView::Digestor.tree(CacheDigests.template_name, CacheDigests.finder).children.map(&:to_dep_map) + end + + desc "Lookup first-level dependencies for TEMPLATE (like messages/show or comments/_comment.html)" + task dependencies: :environment do + abort "You must provide TEMPLATE for the task to run" unless ENV["TEMPLATE"].present? + puts JSON.pretty_generate ActionView::Digestor.tree(CacheDigests.template_name, CacheDigests.finder).children.map(&:name) + end + + class CacheDigests + def self.template_name + ENV["TEMPLATE"].split(".", 2).first + end + + def self.finder + ApplicationController.new.lookup_context + end + end +end diff --git a/actionview/lib/action_view/tasks/dependencies.rake b/actionview/lib/action_view/tasks/dependencies.rake deleted file mode 100644 index 9932ff8b6d..0000000000 --- a/actionview/lib/action_view/tasks/dependencies.rake +++ /dev/null @@ -1,23 +0,0 @@ -namespace :cache_digests do - desc 'Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html)' - task :nested_dependencies => :environment do - abort 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present? - puts JSON.pretty_generate ActionView::Digestor.new(CacheDigests.template_name, CacheDigests.finder).nested_dependencies - end - - desc 'Lookup first-level dependencies for TEMPLATE (like messages/show or comments/_comment.html)' - task :dependencies => :environment do - abort 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present? - puts JSON.pretty_generate ActionView::Digestor.new(CacheDigests.template_name, CacheDigests.finder).dependencies - end - - class CacheDigests - def self.template_name - ENV['TEMPLATE'].split('.', 2).first - end - - def self.finder - ApplicationController.new.lookup_context - end - end -end diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb index 15fc2b71a3..98571cf55e 100644 --- a/actionview/lib/action_view/template.rb +++ b/actionview/lib/action_view/template.rb @@ -1,6 +1,6 @@ -require 'active_support/core_ext/object/try' -require 'active_support/core_ext/kernel/singleton_class' -require 'thread' +require "active_support/core_ext/object/try" +require "active_support/core_ext/kernel/singleton_class" +require "thread" module ActionView # = Action View Template @@ -65,8 +65,7 @@ module ActionView # If you want to provide an alternate mechanism for # specifying encodings (like ERB does via <%# encoding: ... %>), # you may indicate that you will handle encodings yourself - # by implementing <tt>self.handles_encoding?</tt> - # on your handler. + # by implementing <tt>handles_encoding?</tt> on your handler. # # If you do, Rails will not try to encode the String # into the default_internal, passing you the unaltered @@ -130,7 +129,6 @@ module ActionView @source = source @identifier = identifier @handler = handler - @cache_name = extract_resource_cache_name @compiled = false @original_encoding = nil @locals = details[:locals] || [] @@ -154,7 +152,7 @@ module ActionView # we use a bang in this instrumentation because you don't want to # consume this in production. This is only slow if it's being listened to. def render(view, locals, buffer=nil, &block) - instrument("!render_template".freeze) do + instrument_render_template do compile!(view) view.send(method_name, locals, buffer, &block) end @@ -166,10 +164,6 @@ module ActionView @type ||= Types[@formats.first] if @formats.first end - def eligible_for_collection_caching?(as: nil) - @cache_name == (as || inferred_cache_name).to_s - end - # Receives a view object and return a template similar to self by using @virtual_path. # # This method is useful if you have a template object but it does not contain its source @@ -185,12 +179,12 @@ module ActionView name = pieces.pop partial = !!name.sub!(/^_/, "") lookup.disable_cache do - lookup.find_template(name, [ pieces.join('/') ], partial, @locals) + lookup.find_template(name, [ pieces.join("/") ], partial, @locals) end end def inspect - @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", ''.freeze) : identifier + @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", "".freeze) : identifier end # This method is responsible for properly setting the encoding of the @@ -209,7 +203,7 @@ module ActionView # Look for # encoding: *. If we find one, we'll encode the # String in that encoding, otherwise, we'll use the # default external encoding. - if source.sub!(/\A#{ENCODING_FLAG}/, '') + if source.sub!(/\A#{ENCODING_FLAG}/, "") encoding = magic_encoding = $1 else encoding = Encoding.default_external @@ -330,48 +324,39 @@ module ActionView end def locals_code #:nodoc: + # 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/) + # Double assign to suppress the dreaded 'assigned but unused variable' warning - @locals.each_with_object('') { |key, code| code << "#{key} = #{key} = local_assigns[:#{key}];" } + locals.each_with_object("") { |key, code| code << "#{key} = #{key} = local_assigns[:#{key}];" } end def method_name #:nodoc: @method_name ||= begin m = "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}" - m.tr!('-'.freeze, '_'.freeze) + m.tr!("-".freeze, "_".freeze) m end end def identifier_method_name #:nodoc: - inspect.tr('^a-z_'.freeze, '_'.freeze) + inspect.tr("^a-z_".freeze, "_".freeze) end def instrument(action, &block) - payload = { virtual_path: @virtual_path, identifier: @identifier } - case action - when "!render_template".freeze - ActiveSupport::Notifications.instrument("!render_template.action_view".freeze, payload, &block) - else - ActiveSupport::Notifications.instrument("#{action}.action_view".freeze, payload, &block) - end + ActiveSupport::Notifications.instrument("#{action}.action_view".freeze, instrument_payload, &block) end - EXPLICIT_COLLECTION = /# Template Collection: (?<resource_name>\w+)/ + private - def extract_resource_cache_name - if match = @source.match(EXPLICIT_COLLECTION) || resource_cache_call_match - match[:resource_name] - end - end - - def resource_cache_call_match - if @handler.respond_to?(:resource_cache_call_pattern) - @source.match(@handler.resource_cache_call_pattern) - end + def instrument_render_template(&block) + ActiveSupport::Notifications.instrument("!render_template.action_view".freeze, instrument_payload, &block) end - def inferred_cache_name - @inferred_cache_name ||= @virtual_path.split('/'.freeze).last.sub('_'.freeze, ''.freeze) + def instrument_payload + { virtual_path: @virtual_path, identifier: @identifier } end end end diff --git a/actionview/lib/action_view/template/error.rb b/actionview/lib/action_view/template/error.rb index ccee785d3e..cc90477190 100644 --- a/actionview/lib/action_view/template/error.rb +++ b/actionview/lib/action_view/template/error.rb @@ -35,10 +35,10 @@ module ActionView prefixes = Array(prefixes) template_type = if partial "partial" - elsif path =~ /layouts/i - 'layout' + elsif /layouts/i.match?(path) + "layout" else - 'template' + "template" end if partial && path.present? @@ -62,23 +62,13 @@ module ActionView # Override to prevent #cause resetting during re-raise. attr_reader :cause - def initialize(template, 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(template) super($!.message) set_backtrace($!.backtrace) @cause = $! @template, @sub_templates = template, nil end - def original_exception - ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller) - cause - end - def file_name @template.identifier end @@ -130,18 +120,18 @@ module ActionView if line_number "on line ##{line_number} of " else - 'in ' + "in " end + file_name end def formatted_code_for(source_code, line_counter, indent, output) - start_value = (output == :html) ? {} : "" + start_value = (output == :html) ? {} : [] source_code.inject(start_value) do |result, line| line_counter += 1 if output == :html result.update(line_counter.to_s => "%#{indent}s %s\n" % ["", line]) else - result << "%#{indent}s: %s\n" % [line_counter, line] + result << "%#{indent}s: %s" % [line_counter, line] end end end diff --git a/actionview/lib/action_view/template/handlers.rb b/actionview/lib/action_view/template/handlers.rb index ad4c353608..f4301f6f07 100644 --- a/actionview/lib/action_view/template/handlers.rb +++ b/actionview/lib/action_view/template/handlers.rb @@ -2,10 +2,10 @@ module ActionView #:nodoc: # = Action View Template Handlers class Template module Handlers #:nodoc: - autoload :Raw, 'action_view/template/handlers/raw' - autoload :ERB, 'action_view/template/handlers/erb' - autoload :Html, 'action_view/template/handlers/html' - autoload :Builder, 'action_view/template/handlers/builder' + autoload :Raw, "action_view/template/handlers/raw" + autoload :ERB, "action_view/template/handlers/erb" + autoload :Html, "action_view/template/handlers/html" + autoload :Builder, "action_view/template/handlers/builder" def self.extended(base) base.register_default_template_handler :raw, Raw.new diff --git a/actionview/lib/action_view/template/handlers/builder.rb b/actionview/lib/action_view/template/handlers/builder.rb index d90b0c6378..e08a5b5db8 100644 --- a/actionview/lib/action_view/template/handlers/builder.rb +++ b/actionview/lib/action_view/template/handlers/builder.rb @@ -15,12 +15,12 @@ module ActionView protected - def require_engine - @required ||= begin - require "builder" - true + def require_engine + @required ||= begin + require "builder" + true + end end - end end end end diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb index 1f8459c24b..5d047a6991 100644 --- a/actionview/lib/action_view/template/handlers/erb.rb +++ b/actionview/lib/action_view/template/handlers/erb.rb @@ -1,4 +1,4 @@ -require 'erubis' +require "erubis" module ActionView class Template @@ -28,7 +28,7 @@ module ActionView # We override to always treat <%== as escaped. def add_expr(src, code, indicator) case indicator - when '==' + when "==" add_expr_escaped(src, code) else super @@ -39,16 +39,16 @@ module ActionView def add_expr_literal(src, code) flush_newline_if_pending(src) - if code =~ BLOCK_EXPR - src << '@output_buffer.append= ' << code + if BLOCK_EXPR.match?(code) + src << "@output_buffer.append= " << code else - src << '@output_buffer.append=(' << code << ');' + src << "@output_buffer.append=(" << code << ");" end end def add_expr_escaped(src, code) flush_newline_if_pending(src) - if code =~ BLOCK_EXPR + if BLOCK_EXPR.match?(code) src << "@output_buffer.safe_expr_append= " << code else src << "@output_buffer.safe_expr_append=(" << code << ");" @@ -62,7 +62,7 @@ module ActionView def add_postamble(src) flush_newline_if_pending(src) - src << '@output_buffer.to_s' + src << "@output_buffer.to_s" end def flush_newline_if_pending(src) @@ -77,7 +77,7 @@ module ActionView # Specify trim mode for the ERB compiler. Defaults to '-'. # See ERB documentation for suitable values. class_attribute :erb_trim_mode - self.erb_trim_mode = '-' + self.erb_trim_mode = "-" # Default implementation used. class_attribute :erb_implementation @@ -108,7 +108,7 @@ module ActionView # expression template_source = template.source.dup.force_encoding(Encoding::ASCII_8BIT) - erb = template_source.gsub(ENCODING_TAG, '') + erb = template_source.gsub(ENCODING_TAG, "") encoding = $2 erb.force_encoding valid_encoding(template.source.dup, encoding) @@ -118,36 +118,11 @@ module ActionView self.class.erb_implementation.new( erb, - :escape => (self.class.escape_whitelist.include? template.type), - :trim => (self.class.erb_trim_mode == "-") + escape: (self.class.escape_whitelist.include? template.type), + trim: (self.class.erb_trim_mode == "-") ).src end - # Returns Regexp to extract a cached resource's name from a cache call at the - # first line of a template. - # The extracted cache name is captured as :resource_name. - # - # <% cache notification do %> # => notification - # - # The pattern should support templates with a beginning comment: - # - # <%# Still extractable even though there's a comment %> - # <% cache notification do %> # => notification - # - # But fail to extract a name if a resource association is cached. - # - # <% cache notification.event do %> # => nil - def resource_cache_call_pattern - /\A - (?:<%\#.*%>)* # optional initial comment - \s* # followed by optional spaces or newlines - <%\s*cache[\(\s] # followed by an ERB call to cache - \s* # followed by optional spaces or newlines - (?<resource_name>\w+) # capture the cache call argument as :resource_name - [\s\)] # followed by a space or close paren - /xm - end - private def valid_encoding(string, encoding) diff --git a/actionview/lib/action_view/template/handlers/raw.rb b/actionview/lib/action_view/template/handlers/raw.rb index 760f517431..e7519e94f9 100644 --- a/actionview/lib/action_view/template/handlers/raw.rb +++ b/actionview/lib/action_view/template/handlers/raw.rb @@ -2,7 +2,7 @@ module ActionView module Template::Handlers class Raw def call(template) - "#{template.source.inspect};" + "#{template.source.inspect}.html_safe;" end end end diff --git a/actionview/lib/action_view/template/html.rb b/actionview/lib/action_view/template/html.rb index 0321f819b5..0ffae10432 100644 --- a/actionview/lib/action_view/template/html.rb +++ b/actionview/lib/action_view/template/html.rb @@ -11,12 +11,10 @@ module ActionView #:nodoc: end def identifier - 'html template' + "html template" end - def inspect - 'html template' - end + alias_method :inspect, :identifier def to_str ERB::Util.h(@string) diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb index 8a675cd521..5a2948d5a9 100644 --- a/actionview/lib/action_view/template/resolver.rb +++ b/actionview/lib/action_view/template/resolver.rb @@ -37,15 +37,15 @@ module ActionView class Cache #:nodoc: class SmallCache < Concurrent::Map def initialize(options = {}) - super(options.merge(:initial_capacity => 2)) + super(options.merge(initial_capacity: 2)) end end # preallocate all the default blocks for performance/memory consumption reasons - PARTIAL_BLOCK = lambda {|cache, partial| cache[partial] = SmallCache.new} - PREFIX_BLOCK = lambda {|cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK)} - NAME_BLOCK = lambda {|cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK)} - KEY_BLOCK = lambda {|cache, key| cache[key] = SmallCache.new(&NAME_BLOCK)} + PARTIAL_BLOCK = lambda { |cache, partial| cache[partial] = SmallCache.new } + PREFIX_BLOCK = lambda { |cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK) } + NAME_BLOCK = lambda { |cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK) } + KEY_BLOCK = lambda { |cache, key| cache[key] = SmallCache.new(&NAME_BLOCK) } # usually a majority of template look ups return nothing, use this canonical preallocated array to save memory NO_TEMPLATES = [].freeze @@ -55,6 +55,10 @@ module ActionView @query_cache = SmallCache.new end + def inspect + "#<#{self.class.name}:0x#{(object_id << 1).to_s(16)} keys=#{@data.size} queries=#{@query_cache.size}>" + end + # Cache the templates returned by the block def cache(key, name, prefix, partial, locals) if Resolver.caching? @@ -84,24 +88,41 @@ module ActionView @query_cache.clear end - private + # Get the cache size. Do not call this + # method. This method is not guaranteed to be here ever. + def size # :nodoc: + size = 0 + @data.each_value do |v1| + v1.each_value do |v2| + v2.each_value do |v3| + v3.each_value do |v4| + size += v4.size + end + end + end + end - def canonical_no_templates(templates) - templates.empty? ? NO_TEMPLATES : templates + size + @query_cache.size end - def templates_have_changed?(cached_templates, fresh_templates) - # if either the old or new template list is empty, we don't need to (and can't) - # compare modification times, and instead just check whether the lists are different - if cached_templates.blank? || fresh_templates.blank? - return fresh_templates.blank? != cached_templates.blank? + private + + def canonical_no_templates(templates) + templates.empty? ? NO_TEMPLATES : templates end - cached_templates_max_updated_at = cached_templates.map(&:updated_at).max + def templates_have_changed?(cached_templates, fresh_templates) + # if either the old or new template list is empty, we don't need to (and can't) + # compare modification times, and instead just check whether the lists are different + if cached_templates.blank? || fresh_templates.blank? + return fresh_templates.blank? != cached_templates.blank? + end - # if a template has changed, it will be now be newer than all the cached templates - fresh_templates.any? { |t| t.updated_at > cached_templates_max_updated_at } - end + cached_templates_max_updated_at = cached_templates.map(&:updated_at).max + + # if a template has changed, it will be now be newer than all the cached templates + fresh_templates.any? { |t| t.updated_at > cached_templates_max_updated_at } + end end cattr_accessor :caching @@ -183,7 +204,7 @@ module ActionView # An abstract class that implements a Resolver with path semantics. class PathResolver < Resolver #:nodoc: - EXTENSIONS = { :locale => ".", :formats => ".", :variants => "+", :handlers => "." } + EXTENSIONS = { locale: ".", formats: ".", variants: "+", handlers: "." } DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}" def initialize(pattern=nil) @@ -193,89 +214,93 @@ module ActionView private - def find_templates(name, prefix, partial, details, outside_app_allowed = false) - path = Path.build(name, prefix, partial) - query(path, details, details[:formats], outside_app_allowed) - end + def find_templates(name, prefix, partial, details, outside_app_allowed = false) + path = Path.build(name, prefix, partial) + query(path, details, details[:formats], outside_app_allowed) + end - def query(path, details, formats, outside_app_allowed) - query = build_query(path, details) + def query(path, details, formats, outside_app_allowed) + query = build_query(path, details) - template_paths = find_template_paths(query) - template_paths = reject_files_external_to_app(template_paths) unless outside_app_allowed + template_paths = find_template_paths(query) + 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) - contents = File.binread(template) + template_paths.map do |template| + handler, format, variant = extract_handler_and_format_and_variant(template, formats) + contents = File.binread(template) - Template.new(contents, File.expand_path(template), handler, - :virtual_path => path.virtual, - :format => format, - :variant => variant, - :updated_at => mtime(template) - ) + Template.new(contents, File.expand_path(template), handler, + virtual_path: path.virtual, + format: format, + variant: variant, + updated_at: mtime(template) + ) + end end - end - def reject_files_external_to_app(files) - files.reject { |filename| !inside_path?(@path, filename) } - end + def reject_files_external_to_app(files) + files.reject { |filename| !inside_path?(@path, filename) } + end + + def find_template_paths(query) + Dir[query].uniq.reject do |filename| + File.directory?(filename) || + # deals with case-insensitive file systems. + !File.fnmatch(query, filename, File::FNM_EXTGLOB) + end + end - def find_template_paths(query) - Dir[query].reject do |filename| - File.directory?(filename) || - # deals with case-insensitive file systems. - !File.fnmatch(query, filename, File::FNM_EXTGLOB) + def inside_path?(path, filename) + filename = File.expand_path(filename) + path = File.join(path, "") + filename.start_with?(path) end - end - def inside_path?(path, filename) - filename = File.expand_path(filename) - path = File.join(path, '') - filename.start_with?(path) - end + # Helper for building query glob string based on resolver's pattern. + def build_query(path, details) + query = @pattern.dup - # Helper for building query glob string based on resolver's pattern. - def build_query(path, details) - query = @pattern.dup + prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1" + query.gsub!(/:prefix(\/)?/, prefix) - prefix = path.prefix.empty? ? '' : "#{escape_entry(path.prefix)}\\1" - query.gsub!(/:prefix(\/)?/, prefix) + partial = escape_entry(path.partial? ? "_#{path.name}" : path.name) + query.gsub!(/:action/, partial) - partial = escape_entry(path.partial? ? "_#{path.name}" : path.name) - query.gsub!(/:action/, partial) + details.each do |ext, candidates| + if ext == :variants && candidates == :any + query.gsub!(/:#{ext}/, "*") + else + query.gsub!(/:#{ext}/, "{#{candidates.compact.uniq.join(',')}}") + end + end - details.each do |ext, variants| - query.gsub!(/:#{ext}/, "{#{variants.compact.uniq.join(',')}}") + File.expand_path(query, @path) end - File.expand_path(query, @path) - end - - def escape_entry(entry) - entry.gsub(/[*?{}\[\]]/, '\\\\\\&'.freeze) - end + def escape_entry(entry) + entry.gsub(/[*?{}\[\]]/, '\\\\\\&'.freeze) + end - # Returns the file mtime from the filesystem. - def mtime(p) - File.mtime(p) - end + # Returns the file mtime from the filesystem. + def mtime(p) + File.mtime(p) + end - # 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) - pieces = File.basename(path).split('.'.freeze) - pieces.shift + # 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) + pieces = File.basename(path).split(".".freeze) + pieces.shift - extension = pieces.pop + extension = pieces.pop - handler = Template.handler_for_extension(extension) - format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last - format &&= Template::Types[format] + handler = Template.handler_for_extension(extension) + format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last + format &&= Template::Types[format] - [handler, format, variant] - end + [handler, format, variant] + end end # A resolver that loads files from the filesystem. It allows setting your own @@ -340,7 +365,11 @@ module ActionView query = escape_entry(File.join(@path, path)) exts = EXTENSIONS.map do |ext, prefix| - "{#{details[ext].compact.uniq.map { |e| "#{prefix}#{e}," }.join}}" + if ext == :variants && details[ext] == :any + "{#{prefix}*,}" + else + "{#{details[ext].compact.uniq.map { |e| "#{prefix}#{e}," }.join}}" + end end.join query + exts diff --git a/actionview/lib/action_view/template/text.rb b/actionview/lib/action_view/template/text.rb index 04f5b8d17a..e8d4e18f04 100644 --- a/actionview/lib/action_view/template/text.rb +++ b/actionview/lib/action_view/template/text.rb @@ -11,12 +11,10 @@ module ActionView #:nodoc: end def identifier - 'text template' + "text template" end - def inspect - 'text template' - end + alias_method :inspect, :identifier def to_str @string diff --git a/actionview/lib/action_view/template/types.rb b/actionview/lib/action_view/template/types.rb index c233d06ccb..21959a3798 100644 --- a/actionview/lib/action_view/template/types.rb +++ b/actionview/lib/action_view/template/types.rb @@ -1,5 +1,4 @@ -require 'set' -require 'active_support/core_ext/module/attribute_accessors' +require "active_support/core_ext/module/attribute_accessors" module ActionView class Template diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb index 120962b5aa..3eb1ac0826 100644 --- a/actionview/lib/action_view/test_case.rb +++ b/actionview/lib/action_view/test_case.rb @@ -1,9 +1,9 @@ -require 'active_support/core_ext/module/remove_method' -require 'action_controller' -require 'action_controller/test_case' -require 'action_view' +require "active_support/core_ext/module/remove_method" +require "action_controller" +require "action_controller/test_case" +require "action_view" -require 'rails-dom-testing' +require "rails-dom-testing" module ActionView # = Action View Test Case @@ -24,10 +24,10 @@ module ActionView def initialize super self.class.controller_path = "" - @request = ActionController::TestRequest.create + @request = ActionController::TestRequest.create(self.class) @response = ActionDispatch::TestResponse.new - @request.env.delete('PATH_INFO') + @request.env.delete("PATH_INFO") @params = ActionController::Parameters.new end end @@ -49,7 +49,7 @@ module ActionView include ActiveSupport::Testing::ConstantLookup - delegate :lookup_context, :to => :controller + delegate :lookup_context, to: :controller attr_accessor :controller, :output_buffer, :rendered module ClassMethods @@ -96,7 +96,6 @@ module ActionView helper(helper_class) if helper_class include _helpers end - end def setup_with_controller @@ -104,8 +103,8 @@ module ActionView @request = @controller.request # empty string ensures buffer has UTF-8 encoding as # new without arguments returns ASCII-8BIT encoded buffer like String#new - @output_buffer = ActiveSupport::SafeBuffer.new '' - @rendered = '' + @output_buffer = ActiveSupport::SafeBuffer.new "" + @rendered = "" make_test_case_available_to_view! say_no_to_protect_against_forgery! @@ -146,13 +145,14 @@ module ActionView def view_rendered?(view, expected_locals) locals_for(view).any? do |actual_locals| - expected_locals.all? {|key, value| value == actual_locals[key] } + expected_locals.all? { |key, value| value == actual_locals[key] } end end end included do setup :setup_with_controller + ActiveSupport.run_load_hooks(:action_view_test_case, self) end private diff --git a/actionview/lib/action_view/testing/resolvers.rb b/actionview/lib/action_view/testing/resolvers.rb index 2664aca991..f072664535 100644 --- a/actionview/lib/action_view/testing/resolvers.rb +++ b/actionview/lib/action_view/testing/resolvers.rb @@ -1,4 +1,4 @@ -require 'action_view/template/resolver' +require "action_view/template/resolver" module ActionView #:nodoc: # Use FixtureResolver in your tests to simulate the presence of files on the @@ -14,7 +14,7 @@ module ActionView #:nodoc: end def to_s - @hash.keys.join(', ') + @hash.keys.join(", ") end private @@ -22,32 +22,31 @@ module ActionView #:nodoc: def query(path, exts, formats, _) query = "" EXTENSIONS.each_key do |ext| - query << '(' << exts[ext].map {|e| e && Regexp.escape(".#{e}") }.join('|') << '|)' + 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 _path =~ query + 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 + 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 } + 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) - [ActionView::Template.new("Template generated by Null Resolver", path.virtual, handler, :virtual_path => path.virtual, :format => format, :variant => variant)] + [ActionView::Template.new("Template generated by Null Resolver", path.virtual, handler, virtual_path: path.virtual, format: format, variant: variant)] end end end - diff --git a/actionview/lib/action_view/version.rb b/actionview/lib/action_view/version.rb index f55d3fdaef..315404864d 100644 --- a/actionview/lib/action_view/version.rb +++ b/actionview/lib/action_view/version.rb @@ -1,4 +1,4 @@ -require_relative 'gem_version' +require_relative "gem_version" module ActionView # Returns the version of the currently loaded ActionView as a <tt>Gem::Version</tt> diff --git a/actionview/lib/action_view/view_paths.rb b/actionview/lib/action_view/view_paths.rb index 37722013ce..b5cde5b43f 100644 --- a/actionview/lib/action_view/view_paths.rb +++ b/actionview/lib/action_view/view_paths.rb @@ -1,5 +1,3 @@ -require 'action_view/base' - module ActionView module ViewPaths extend ActiveSupport::Concern @@ -10,8 +8,8 @@ module ActionView self._view_paths.freeze end - delegate :template_exists?, :view_paths, :formats, :formats=, - :locale, :locale=, :to => :lookup_context + delegate :template_exists?, :any_templates?, :view_paths, :formats, :formats=, + :locale, :locale=, to: :lookup_context module ClassMethods def _prefixes # :nodoc: @@ -24,11 +22,11 @@ module ActionView private - # Override this method in your controller if you want to change paths prefixes for finding views. - # Prefixes defined here will still be added to parents' <tt>._prefixes</tt>. - def local_prefixes - [controller_path] - end + # Override this method in your controller if you want to change paths prefixes for finding views. + # Prefixes defined here will still be added to parents' <tt>._prefixes</tt>. + def local_prefixes + [controller_path] + end end # The prefixes used in render "foo" shortcuts. @@ -45,7 +43,7 @@ module ActionView end def details_for_lookup - { } + {} end def append_view_path(path) diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb index 79173f730f..88c7189d22 100644 --- a/actionview/test/abstract_unit.rb +++ b/actionview/test/abstract_unit.rb @@ -1,12 +1,10 @@ -require File.expand_path('../../../load_paths', __FILE__) +$:.unshift(File.dirname(__FILE__) + "/lib") +$:.unshift(File.dirname(__FILE__) + "/fixtures/helpers") +$:.unshift(File.dirname(__FILE__) + "/fixtures/alternate_helpers") -$:.unshift(File.dirname(__FILE__) + '/lib') -$:.unshift(File.dirname(__FILE__) + '/fixtures/helpers') -$:.unshift(File.dirname(__FILE__) + '/fixtures/alternate_helpers') +ENV["TMPDIR"] = File.join(File.dirname(__FILE__), "tmp") -ENV['TMPDIR'] = File.join(File.dirname(__FILE__), 'tmp') - -require 'active_support/core_ext/kernel/reporting' +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 @@ -15,16 +13,16 @@ silence_warnings do Encoding.default_external = "UTF-8" end -require 'active_support/testing/autorun' -require 'active_support/testing/method_call_assertions' -require 'action_controller' -require 'action_view' -require 'action_view/testing/resolvers' -require 'active_support/dependencies' -require 'active_model' -require 'active_record' +require "active_support/testing/autorun" +require "active_support/testing/method_call_assertions" +require "action_controller" +require "action_view" +require "action_view/testing/resolvers" +require "active_support/dependencies" +require "active_model" +require "active_record" -require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late +require "pp" # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late module Rails class << self @@ -45,11 +43,11 @@ ActiveSupport::Deprecation.debug = true I18n.enforce_available_locales = false # Register danish language for testing -I18n.backend.store_translations 'da', {} -I18n.backend.store_translations 'pt-BR', {} +I18n.backend.store_translations "da", {} +I18n.backend.store_translations "pt-BR", {} ORIGINAL_LOCALES = I18n.available_locales.map(&:to_s).sort -FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures') +FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), "fixtures") module RenderERBUtils def view @@ -95,12 +93,14 @@ module ActionDispatch super return if DrawOnce.drew - SharedTestRoutes.draw do - get ':controller(/:action)' - end + ActiveSupport::Deprecation.silence do + SharedTestRoutes.draw do + get ":controller(/:action)" + end - ActionDispatch::IntegrationTest.app.routes.draw do - get ':controller(/:action)' + ActionDispatch::IntegrationTest.app.routes.draw do + get ":controller(/:action)" + end end DrawOnce.drew = true @@ -137,7 +137,7 @@ class BasicController config.assets_dir = public_dir config.javascripts_dir = "#{public_dir}/javascripts" config.stylesheets_dir = "#{public_dir}/stylesheets" - config.assets = ActiveSupport::InheritableOptions.new({ :prefix => "assets" }) + config.assets = ActiveSupport::InheritableOptions.new(prefix: "assets") config end end @@ -164,13 +164,13 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase # simply return the controller#action as Rack::Body. class StubDispatcher < ::ActionDispatch::Routing::RouteSet::Dispatcher protected - def controller_reference(controller_param) - controller_param - end + def controller_reference(controller_param) + controller_param + end - def dispatch(controller, action, env) - [200, {'Content-Type' => 'text/html'}, ["#{controller}##{action}"]] - end + def dispatch(controller, action, env) + [200, { "Content-Type" => "text/html" }, ["#{controller}##{action}"]] + end end def self.stub_controllers @@ -204,7 +204,7 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase ActiveSupport::Dependencies.autoload_paths << path yield ensure - ActiveSupport::Dependencies.autoload_paths.reject! {|p| p == path} + ActiveSupport::Dependencies.autoload_paths.reject! { |p| p == path } ActiveSupport::Dependencies.clear end end @@ -263,20 +263,20 @@ end module ActionDispatch class DebugExceptions private - remove_method :stderr_logger - # Silence logger - def stderr_logger - nil - end + remove_method :stderr_logger + # Silence logger + def stderr_logger + nil + end end end # Skips the current run on Rubinius using Minitest::Assertions#skip -def rubinius_skip(message = '') - skip message if RUBY_ENGINE == 'rbx' +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 = '') +def jruby_skip(message = "") skip message if defined?(JRUBY_VERSION) end diff --git a/actionview/test/actionpack/abstract/abstract_controller_test.rb b/actionview/test/actionpack/abstract/abstract_controller_test.rb index 490932fef0..a2cd3deb58 100644 --- a/actionview/test/actionpack/abstract/abstract_controller_test.rb +++ b/actionview/test/actionpack/abstract/abstract_controller_test.rb @@ -1,9 +1,8 @@ -require 'abstract_unit' -require 'set' +require "abstract_unit" +require "set" module AbstractController module Testing - # Test basic dispatching. # ==== # * Call process @@ -38,7 +37,7 @@ module AbstractController def render(options = {}) if options.is_a?(String) - options = {:_template_name => options} + options = { _template_name: options } end super end @@ -65,11 +64,11 @@ module AbstractController end def rendering_to_body - self.response_body = render_to_body :template => "naked_render" + self.response_body = render_to_body template: "naked_render" end def rendering_to_string - self.response_body = render_to_string :template => "naked_render" + self.response_body = render_to_string template: "naked_render" end end @@ -114,13 +113,13 @@ module AbstractController # * self._prefix is used when defined class PrefixedViews < RenderingController private - def self.prefix - name.underscore - end + def self.prefix + name.underscore + end - def _prefixes - [self.class.prefix] - end + def _prefixes + [self.class.prefix] + end end class Me3 < PrefixedViews @@ -189,19 +188,19 @@ module AbstractController include ActionView::Layouts private - def self.layout(formats) - find_template(name.underscore, {:formats => formats}, :_prefixes => ["layouts"]) - rescue ActionView::MissingTemplate - begin - find_template("application", {:formats => formats}, :_prefixes => ["layouts"]) + def self.layout(formats) + find_template(name.underscore, { formats: formats }, _prefixes: ["layouts"]) rescue ActionView::MissingTemplate + begin + find_template("application", { formats: formats }, _prefixes: ["layouts"]) + rescue ActionView::MissingTemplate + end end - end - def render_to_body(options = {}) - options[:_layout] = options[:layout] || _default_layout({}) - super - end + def render_to_body(options = {}) + options[:_layout] = options[:layout] || _default_layout({}) + super + end end class Me4 < WithLayouts @@ -228,7 +227,7 @@ module AbstractController end class ActionMissingRespondToActionController < AbstractController::Base - # No actions + # No actions private def action_missing(action_name) self.response_body = "success" @@ -248,7 +247,6 @@ module AbstractController end class TestRespondToAction < ActiveSupport::TestCase - def assert_dispatch(klass, body = "success", action = :index) controller = klass.new controller.process(action) @@ -277,18 +275,16 @@ module AbstractController end class Me6 < AbstractController::Base - self.action_methods + action_methods def index end end class TestActionMethodsReloading < ActiveSupport::TestCase - test "action_methods should be reloaded after defining a new method" do assert_equal Set.new(["index"]), Me6.action_methods end end - end end diff --git a/actionview/test/actionpack/abstract/helper_test.rb b/actionview/test/actionpack/abstract/helper_test.rb index 7d346e917d..5a2f0839e5 100644 --- a/actionview/test/actionpack/abstract/helper_test.rb +++ b/actionview/test/actionpack/abstract/helper_test.rb @@ -1,17 +1,16 @@ -require 'abstract_unit' +require "abstract_unit" -ActionController::Base.helpers_path = File.expand_path('../../../fixtures/helpers', __FILE__) +ActionController::Base.helpers_path = File.expand_path("../../../fixtures/helpers", __FILE__) module AbstractController module Testing - class ControllerWithHelpers < AbstractController::Base include AbstractController::Helpers include AbstractController::Rendering include ActionView::Rendering def with_module - render :inline => "Module <%= included_method %>" + render inline: "Module <%= included_method %>" end end @@ -31,11 +30,11 @@ module AbstractController helper :abc def with_block - render :inline => "Hello <%= helpery_test %>" + render inline: "Hello <%= helpery_test %>" end def with_symbol - render :inline => "I respond to bare_a: <%= respond_to?(:bare_a) %>" + render inline: "I respond to bare_a: <%= respond_to?(:bare_a) %>" end end @@ -52,7 +51,7 @@ module AbstractController class AbstractInvalidHelpers < AbstractHelpers include ActionController::Helpers - path = File.expand_path('../../../fixtures/helpers_missing', __FILE__) + path = File.expand_path("../../../fixtures/helpers_missing", __FILE__) $:.unshift(path) self.helpers_path = path end diff --git a/actionview/test/actionpack/abstract/layouts_test.rb b/actionview/test/actionpack/abstract/layouts_test.rb index 80bc665b0a..4ece992597 100644 --- a/actionview/test/actionpack/abstract/layouts_test.rb +++ b/actionview/test/actionpack/abstract/layouts_test.rb @@ -1,8 +1,7 @@ -require 'abstract_unit' +require "abstract_unit" module AbstractControllerTests module Layouts - # Base controller for these tests class Base < AbstractController::Base include AbstractController::Rendering @@ -12,7 +11,9 @@ module AbstractControllerTests abstract! self.view_paths = [ActionView::FixtureResolver.new( + "some/template.erb" => "hello <%= foo %> bar", "layouts/hello.erb" => "With String <%= yield %>", + "layouts/hello_locals.erb" => "With String <%= yield %>", "layouts/hello_override.erb" => "With Override <%= yield %>", "layouts/overwrite.erb" => "Overwrite <%= yield %>", "layouts/with_false_layout.erb" => "False Layout <%= yield %>", @@ -28,7 +29,15 @@ module AbstractControllerTests self.view_paths = [] def index - render :template => ActionView::Template::Text.new("Hello blank!") + render template: ActionView::Template::Text.new("Hello blank!") + end + end + + class WithStringLocals < Base + layout "hello_locals" + + def index + render template: "some/template", locals: { foo: "less than 3" } end end @@ -36,19 +45,23 @@ module AbstractControllerTests layout "hello" def index - render :template => ActionView::Template::Text.new("Hello string!") + render template: ActionView::Template::Text.new("Hello string!") + end + + def action_has_layout_false + render template: ActionView::Template::Text.new("Hello string!") end def overwrite_default - render :template => ActionView::Template::Text.new("Hello string!"), :layout => :default + render template: ActionView::Template::Text.new("Hello string!"), layout: :default end def overwrite_false - render :template => ActionView::Template::Text.new("Hello string!"), :layout => false + render template: ActionView::Template::Text.new("Hello string!"), layout: false end def overwrite_string - render :template => ActionView::Template::Text.new("Hello string!"), :layout => "overwrite" + render template: ActionView::Template::Text.new("Hello string!"), layout: "overwrite" end def overwrite_skip @@ -78,11 +91,11 @@ module AbstractControllerTests layout proc { "overwrite" } def index - render :template => ActionView::Template::Text.new("Hello proc!") + render template: ActionView::Template::Text.new("Hello proc!") end end - class WithProcReturningNil < Base + class WithProcReturningNil < WithString layout proc { nil } def index @@ -90,11 +103,19 @@ module AbstractControllerTests end end + class WithProcReturningFalse < WithString + layout proc { false } + + def index + render template: ActionView::Template::Text.new("Hello false!") + end + end + class WithZeroArityProc < Base layout proc { "overwrite" } def index - render :template => ActionView::Template::Text.new("Hello zero arity proc!") + render template: ActionView::Template::Text.new("Hello zero arity proc!") end end @@ -107,7 +128,7 @@ module AbstractControllerTests } def index - render :template => ActionView::Template::Text.new("Hello again zero arity proc!") + render template: ActionView::Template::Text.new("Hello again zero arity proc!") end end @@ -115,7 +136,7 @@ module AbstractControllerTests layout :hello def index - render :template => ActionView::Template::Text.new("Hello symbol!") + render template: ActionView::Template::Text.new("Hello symbol!") end private def hello @@ -127,7 +148,7 @@ module AbstractControllerTests layout :nilz def index - render :template => ActionView::Template::Text.new("Hello nilz!") + render template: ActionView::Template::Text.new("Hello nilz!") end def nilz() end @@ -137,7 +158,7 @@ module AbstractControllerTests layout :objekt def index - render :template => ActionView::Template::Text.new("Hello nilz!") + render template: ActionView::Template::Text.new("Hello nilz!") end def objekt @@ -149,7 +170,7 @@ module AbstractControllerTests layout :no_method def index - render :template => ActionView::Template::Text.new("Hello boom!") + render template: ActionView::Template::Text.new("Hello boom!") end end @@ -157,7 +178,7 @@ module AbstractControllerTests layout "missing" def index - render :template => ActionView::Template::Text.new("Hello missing!") + render template: ActionView::Template::Text.new("Hello missing!") end end @@ -165,7 +186,7 @@ module AbstractControllerTests layout false def index - render :template => ActionView::Template::Text.new("Hello false!") + render template: ActionView::Template::Text.new("Hello false!") end end @@ -173,34 +194,81 @@ module AbstractControllerTests layout nil def index - render :template => ActionView::Template::Text.new("Hello nil!") + render template: ActionView::Template::Text.new("Hello nil!") end end class WithOnlyConditional < WithStringImpliedChild - layout "overwrite", :only => :show + layout "overwrite", only: :show def index - render :template => ActionView::Template::Text.new("Hello index!") + render template: ActionView::Template::Text.new("Hello index!") end def show - render :template => ActionView::Template::Text.new("Hello show!") + render template: ActionView::Template::Text.new("Hello show!") end end + class WithOnlyConditionalFlipped < WithOnlyConditional + layout "hello_override", only: :index + end + + class WithOnlyConditionalFlippedAndInheriting < WithOnlyConditional + layout nil, only: :index + end + class WithExceptConditional < WithStringImpliedChild - layout "overwrite", :except => :show + layout "overwrite", except: :show def index - render :template => ActionView::Template::Text.new("Hello index!") + render template: ActionView::Template::Text.new("Hello index!") end def show - render :template => ActionView::Template::Text.new("Hello show!") + render template: ActionView::Template::Text.new("Hello show!") end end + class AbstractWithString < Base + layout "hello" + abstract! + end + + class AbstractWithStringChild < AbstractWithString + def index + render template: ActionView::Template::Text.new("Hello abstract child!") + end + end + + class AbstractWithStringChildDefaultsToInherited < AbstractWithString + layout nil + + def index + render template: ActionView::Template::Text.new("Hello abstract child!") + end + end + + class WithConditionalOverride < WithString + layout "overwrite", only: :overwritten + + def non_overwritten + render template: ActionView::Template::Text.new("Hello non overwritten!") + end + + def overwritten + render template: ActionView::Template::Text.new("Hello overwritten!") + end + end + + class WithConditionalOverrideFlipped < WithConditionalOverride + layout "hello_override", only: :non_overwritten + end + + class WithConditionalOverrideFlippedAndInheriting < WithConditionalOverride + layout nil, only: :non_overwritten + end + class TestBase < ActiveSupport::TestCase test "when no layout is specified, and no default is available, render without a layout" do controller = Blank.new @@ -208,6 +276,31 @@ module AbstractControllerTests assert_equal "Hello blank!", controller.response_body end + test "with locals" do + controller = WithStringLocals.new + controller.process(:index) + assert_equal "With String hello less than 3 bar", controller.response_body + end + + test "cache should not grow when locals change for a string template" do + cache = WithString.view_paths.paths.first.instance_variable_get(:@cache) + + controller = WithString.new + controller.process(:index) # heat the cache + + size = cache.size + + 10.times do |x| + controller = WithString.new + controller.define_singleton_method :index do + render template: ActionView::Template::Text.new("Hello string!"), locals: { :"x#{x}" => :omg } + end + controller.process(:index) + end + + assert_equal size, cache.size + end + test "when layout is specified as a string, render with that layout" do controller = WithString.new controller.process(:index) @@ -255,7 +348,7 @@ module AbstractControllerTests end test "when layout is specified as a proc, do not leak any methods into controller's action_methods" do - assert_equal Set.new(['index']), WithProc.action_methods + assert_equal Set.new(["index"]), WithProc.action_methods end test "when layout is specified as a proc, call it and use the layout returned" do @@ -264,10 +357,16 @@ module AbstractControllerTests assert_equal "Overwrite Hello proc!", controller.response_body end - test "when layout is specified as a proc and the proc returns nil, don't use a layout" do + test "when layout is specified as a proc and the proc returns nil, use inherited layout" do controller = WithProcReturningNil.new controller.process(:index) - assert_equal "Hello nil!", controller.response_body + assert_equal "With String Hello nil!", controller.response_body + end + + test "when layout is specified as a proc and the proc returns false, use no layout instead of inherited layout" do + controller = WithProcReturningFalse.new + controller.process(:index) + assert_equal "Hello false!", controller.response_body end test "when layout is specified as a proc without parameters it works just the same" do @@ -322,16 +421,28 @@ module AbstractControllerTests test "when a grandchild has no layout specified, the child has an implied layout, and the " \ "parent has specified a layout, use the child controller layout" do - controller = WithChildOfImplied.new - controller.process(:index) - assert_equal "With Implied Hello string!", controller.response_body + controller = WithChildOfImplied.new + controller.process(:index) + assert_equal "With Implied Hello string!", controller.response_body end test "when a grandchild has nil layout specified, the child has an implied layout, and the " \ - "parent has specified a layout, use the child controller layout" do - controller = WithGrandChildOfImplied.new - controller.process(:index) - assert_equal "With Grand Child Hello string!", controller.response_body + "parent has specified a layout, use the grand child controller layout" do + controller = WithGrandChildOfImplied.new + controller.process(:index) + assert_equal "With Grand Child Hello string!", controller.response_body + end + + test "a child inherits layout from abstract controller" do + controller = AbstractWithStringChild.new + controller.process(:index) + assert_equal "With String Hello abstract child!", controller.response_body + end + + test "a child inherits layout from abstract controller2" do + controller = AbstractWithStringChildDefaultsToInherited.new + controller.process(:index) + assert_equal "With String Hello abstract child!", controller.response_body end test "raises an exception when specifying layout true" do @@ -356,6 +467,30 @@ module AbstractControllerTests assert_equal "With Implied Hello index!", controller.response_body end + test "when specify an :only option which match current action name and is opposite from parent controller" do + controller = WithOnlyConditionalFlipped.new + controller.process(:show) + assert_equal "With Implied Hello show!", controller.response_body + end + + test "when specify an :only option which does not match current action name and is opposite from parent controller" do + controller = WithOnlyConditionalFlipped.new + controller.process(:index) + assert_equal "With Override Hello index!", controller.response_body + end + + test "when specify to inherit and an :only option which match current action name and is opposite from parent controller" do + controller = WithOnlyConditionalFlippedAndInheriting.new + controller.process(:show) + assert_equal "With Implied Hello show!", controller.response_body + end + + test "when specify to inherit and an :only option which does not match current action name and is opposite from parent controller" do + controller = WithOnlyConditionalFlippedAndInheriting.new + controller.process(:index) + assert_equal "Overwrite Hello index!", controller.response_body + end + test "when specify an :except option which match current action name" do controller = WithExceptConditional.new controller.process(:show) @@ -368,10 +503,46 @@ module AbstractControllerTests assert_equal "Overwrite Hello index!", controller.response_body end + test "when specify overwrite as an :only option which match current action name" do + controller = WithConditionalOverride.new + controller.process(:overwritten) + assert_equal "Overwrite Hello overwritten!", controller.response_body + end + + test "when specify overwrite as an :only option which does not match current action name" do + controller = WithConditionalOverride.new + controller.process(:non_overwritten) + assert_equal "Hello non overwritten!", controller.response_body + end + + test "when specify overwrite as an :only option which match current action name and is opposite from parent controller" do + controller = WithConditionalOverrideFlipped.new + controller.process(:overwritten) + assert_equal "Hello overwritten!", controller.response_body + end + + test "when specify overwrite as an :only option which does not match current action name and is opposite from parent controller" do + controller = WithConditionalOverrideFlipped.new + controller.process(:non_overwritten) + assert_equal "With Override Hello non overwritten!", controller.response_body + end + + test "when specify to inherit and overwrite as an :only option which match current action name and is opposite from parent controller" do + controller = WithConditionalOverrideFlippedAndInheriting.new + controller.process(:overwritten) + assert_equal "Hello overwritten!", controller.response_body + end + + test "when specify to inherit and overwrite as an :only option which does not match current action name and is opposite from parent controller" do + controller = WithConditionalOverrideFlippedAndInheriting.new + controller.process(:non_overwritten) + assert_equal "Overwrite Hello non overwritten!", controller.response_body + end + test "layout for anonymous controller" do klass = Class.new(WithString) do def index - render plain: 'index', layout: true + render plain: "index", layout: true end end @@ -379,6 +550,17 @@ module AbstractControllerTests controller.process(:index) assert_equal "With String index", controller.response_body end + + test "when layout is disabled with #action_has_layout? returning false, render no layout" do + controller = WithString.new + controller.instance_eval do + def action_has_layout? + false + end + end + controller.process(:action_has_layout_false) + assert_equal "Hello string!", controller.response_body + end end end end diff --git a/actionview/test/actionpack/abstract/render_test.rb b/actionview/test/actionpack/abstract/render_test.rb index e185b76adb..8b0c54fb77 100644 --- a/actionview/test/actionpack/abstract/render_test.rb +++ b/actionview/test/actionpack/abstract/render_test.rb @@ -1,8 +1,7 @@ -require 'abstract_unit' +require "abstract_unit" module AbstractController module Testing - class ControllerRenderer < AbstractController::Base include AbstractController::Rendering include ActionView::Rendering @@ -21,15 +20,15 @@ module AbstractController )] def template - render :template => "template" + render template: "template" end def file - render :file => "some/file" + render file: "some/file" end def inline - render :inline => "With <%= :Inline %>" + render inline: "With <%= :Inline %>" end def text @@ -54,7 +53,6 @@ module AbstractController end class TestRenderer < ActiveSupport::TestCase - def setup @controller = ControllerRenderer.new end diff --git a/actionview/test/actionpack/controller/capture_test.rb b/actionview/test/actionpack/controller/capture_test.rb index 933456ce9d..f0ae609845 100644 --- a/actionview/test/actionpack/controller/capture_test.rb +++ b/actionview/test/actionpack/controller/capture_test.rb @@ -1,30 +1,30 @@ -require 'abstract_unit' -require 'active_support/logger' +require "abstract_unit" +require "active_support/logger" class CaptureController < ActionController::Base - self.view_paths = [ File.dirname(__FILE__) + '/../../fixtures/actionpack' ] + self.view_paths = [ File.dirname(__FILE__) + "/../../fixtures/actionpack" ] def self.controller_name; "test"; end def self.controller_path; "test"; end def content_for @title = nil - render :layout => "talk_from_action" + render layout: "talk_from_action" end def content_for_with_parameter @title = nil - render :layout => "talk_from_action" + render layout: "talk_from_action" end def content_for_concatenated @title = nil - render :layout => "talk_from_action" + render layout: "talk_from_action" end def non_erb_block_content_for @title = nil - render :layout => "talk_from_action" + render layout: "talk_from_action" end def proper_block_detection diff --git a/actionview/test/actionpack/controller/layout_test.rb b/actionview/test/actionpack/controller/layout_test.rb index 64bc4c41d6..a342b22161 100644 --- a/actionview/test/actionpack/controller/layout_test.rb +++ b/actionview/test/actionpack/controller/layout_test.rb @@ -1,15 +1,15 @@ -require 'abstract_unit' -require 'active_support/core_ext/array/extract_options' +require "abstract_unit" +require "active_support/core_ext/array/extract_options" # The view_paths array must be set on Base and not LayoutTest so that LayoutTest's inherited # method has access to the view_paths array when looking for a layout to automatically assign. old_load_paths = ActionController::Base.view_paths -ActionController::Base.view_paths = [ File.dirname(__FILE__) + '/../../fixtures/actionpack/layout_tests/' ] +ActionController::Base.view_paths = [ File.dirname(__FILE__) + "/../../fixtures/actionpack/layout_tests/" ] class LayoutTest < ActionController::Base - def self.controller_path; 'views' end - def self._implied_layout_name; to_s.underscore.gsub(/_controller$/, '') ; end + def self.controller_path; "views" end + def self._implied_layout_name; to_s.underscore.gsub(/_controller$/, "") ; end self.view_paths = ActionController::Base.view_paths.dup end @@ -54,13 +54,13 @@ class LayoutAutoDiscoveryTest < ActionController::TestCase def test_application_layout_is_default_when_no_controller_match @controller = ProductController.new get :hello - assert_equal 'layout_test.erb hello.erb', @response.body + assert_equal "layout_test.erb hello.erb", @response.body end def test_controller_name_layout_name_match @controller = ItemController.new get :hello - assert_equal 'item.erb hello.erb', @response.body + assert_equal "item.erb hello.erb", @response.body end def test_third_party_template_library_auto_discovers_layout @@ -68,20 +68,20 @@ class LayoutAutoDiscoveryTest < ActionController::TestCase @controller = ThirdPartyTemplateLibraryController.new get :hello assert_response :success - assert_equal 'layouts/third_party_template_library.mab', @response.body + assert_equal "layouts/third_party_template_library.mab", @response.body end end def test_namespaced_controllers_auto_detect_layouts1 @controller = ControllerNameSpace::NestedController.new get :hello - assert_equal 'controller_name_space/nested.erb hello.erb', @response.body + assert_equal "controller_name_space/nested.erb hello.erb", @response.body end def test_namespaced_controllers_auto_detect_layouts2 @controller = MultipleExtensions.new get :hello - assert_equal 'multiple_extensions.html.erb hello.erb', @response.body.strip + assert_equal "multiple_extensions.html.erb hello.erb", @response.body.strip end end @@ -91,16 +91,16 @@ end class StreamingLayoutController < LayoutTest def render(*args) options = args.extract_options! - super(*args, options.merge(:stream => true)) + super(*args, options.merge(stream: true)) end end class AbsolutePathLayoutController < LayoutTest - layout File.expand_path(File.expand_path(__FILE__) + '/../../../fixtures/actionpack/layout_tests/layouts/layout_test') + layout File.expand_path(File.expand_path(__FILE__) + "/../../../fixtures/actionpack/layout_tests/layouts/layout_test") end class HasOwnLayoutController < LayoutTest - layout 'item' + layout "item" end class HasNilLayoutSymbol < LayoutTest @@ -117,28 +117,28 @@ end class PrependsViewPathController < LayoutTest def hello - prepend_view_path File.dirname(__FILE__) + '/../../fixtures/actionpack/layout_tests/alt/' - render :layout => 'alt' + prepend_view_path File.dirname(__FILE__) + "/../../fixtures/actionpack/layout_tests/alt/" + render layout: "alt" end end class OnlyLayoutController < LayoutTest - layout 'item', :only => "hello" + layout "item", only: "hello" end class ExceptLayoutController < LayoutTest - layout 'item', :except => "goodbye" + layout "item", except: "goodbye" end class SetsLayoutInRenderController < LayoutTest def hello - render :layout => 'third_party_template_library' + render layout: "third_party_template_library" end end class RendersNoLayoutController < LayoutTest def hello - render :layout => false + render layout: false end end @@ -149,75 +149,75 @@ class LayoutSetInResponseTest < ActionController::TestCase def test_layout_set_when_using_default_layout @controller = DefaultLayoutController.new get :hello - assert_includes @response.body, 'layout_test.erb' + assert_includes @response.body, "layout_test.erb" end def test_layout_set_when_using_streaming_layout @controller = StreamingLayoutController.new get :hello - assert_includes @response.body, 'layout_test.erb' + assert_includes @response.body, "layout_test.erb" end def test_layout_set_when_set_in_controller @controller = HasOwnLayoutController.new get :hello - assert_includes @response.body, 'item.erb' + assert_includes @response.body, "item.erb" end def test_layout_symbol_set_in_controller_returning_nil_falls_back_to_default @controller = HasNilLayoutSymbol.new get :hello - assert_includes @response.body, 'layout_test.erb' + assert_includes @response.body, "layout_test.erb" end def test_layout_proc_set_in_controller_returning_nil_falls_back_to_default @controller = HasNilLayoutProc.new get :hello - assert_includes @response.body, 'layout_test.erb' + assert_includes @response.body, "layout_test.erb" end def test_layout_only_exception_when_included @controller = OnlyLayoutController.new get :hello - assert_includes @response.body, 'item.erb' + assert_includes @response.body, "item.erb" end def test_layout_only_exception_when_excepted @controller = OnlyLayoutController.new get :goodbye - assert_not_includes @response.body, 'item.erb' + assert_not_includes @response.body, "item.erb" end def test_layout_except_exception_when_included @controller = ExceptLayoutController.new get :hello - assert_includes @response.body, 'item.erb' + assert_includes @response.body, "item.erb" end def test_layout_except_exception_when_excepted @controller = ExceptLayoutController.new get :goodbye - assert_not_includes @response.body, 'item.erb' + assert_not_includes @response.body, "item.erb" end def test_layout_set_when_using_render with_template_handler :mab, lambda { |template| template.source.inspect } do @controller = SetsLayoutInRenderController.new get :hello - assert_includes @response.body, 'layouts/third_party_template_library.mab' + assert_includes @response.body, "layouts/third_party_template_library.mab" end end def test_layout_is_not_set_when_none_rendered @controller = RendersNoLayoutController.new get :hello - assert_equal 'hello.erb', @response.body + assert_equal "hello.erb", @response.body end def test_layout_is_picked_from_the_controller_instances_view_path @controller = PrependsViewPathController.new get :hello - assert_includes @response.body, 'alt.erb' + assert_includes @response.body, "alt.erb" end def test_absolute_pathed_layout @@ -240,7 +240,7 @@ end class LayoutStatusIsRendered < LayoutTest def hello - render :status => 401 + render status: 401 end end @@ -252,7 +252,7 @@ class LayoutStatusIsRenderedTest < ActionController::TestCase end end -unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ +unless /mswin|mingw/.match?(RbConfig::CONFIG["host_os"]) class LayoutSymlinkedTest < LayoutTest layout "symlinked/symlinked_layout" end @@ -262,7 +262,7 @@ unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ @controller = LayoutSymlinkedTest.new get :hello assert_response 200 - assert_includes @response.body, 'This is my layout' + assert_includes @response.body, "This is my layout" end end end diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb index bdb9e0397b..cd89dceb45 100644 --- a/actionview/test/actionpack/controller/render_test.rb +++ b/actionview/test/actionpack/controller/render_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_model' +require "abstract_unit" +require "active_model" class ApplicationController < ActionController::Base self.view_paths = File.join(FIXTURE_LOAD_PATH, "actionpack") @@ -51,7 +51,7 @@ module Quiz # Controller class QuestionsController < ApplicationController def new - render :partial => Quiz::Question.new("Namespaced Partial") + render partial: Quiz::Question.new("Namespaced Partial") end end end @@ -64,7 +64,7 @@ module Fun def hello_world; end def nested_partial_with_form_builder - render :partial => ActionView::Helpers::FormBuilder.new(:post, nil, view_context, {}) + render partial: ActionView::Helpers::FormBuilder.new(:post, nil, view_context, {}) end end end @@ -90,7 +90,7 @@ class TestController < ApplicationController end def hello_world_file - render :file => File.expand_path("../../../fixtures/actionpack/hello", __FILE__), :formats => [:html] + render file: File.expand_path("../../../fixtures/actionpack/hello", __FILE__), formats: [:html] end # :ported: @@ -110,12 +110,12 @@ class TestController < ApplicationController # :ported: def render_template_in_top_directory - render :template => 'shared' + render template: "shared" end # :deprecated: def render_template_in_top_directory_with_slash - render '/shared' + render "/shared" end # :ported: @@ -126,11 +126,11 @@ class TestController < ApplicationController # :ported: def render_action_hello_world - render :action => "hello_world" + render action: "hello_world" end def render_action_upcased_hello_world - render :action => "Hello_world" + render action: "Hello_world" end def render_action_hello_world_as_string @@ -138,7 +138,7 @@ class TestController < ApplicationController end def render_action_hello_world_with_symbol - render :action => :hello_world + render action: :hello_world end # :ported: @@ -149,70 +149,70 @@ class TestController < ApplicationController # :ported: def render_text_hello_world_with_layout @variable_for_layout = ", I am here!" - render plain: "hello world", :layout => true + render plain: "hello world", layout: true end def hello_world_with_layout_false - render :layout => false + render layout: false end # :ported: def render_file_with_instance_variables - @secret = 'in the sauce' - path = File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_ivar') - render :file => path + @secret = "in the sauce" + path = File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_ivar") + render file: path end # :ported: def render_file_not_using_full_path - @secret = 'in the sauce' - render :file => 'test/render_file_with_ivar' + @secret = "in the sauce" + render file: "test/render_file_with_ivar" end def render_file_not_using_full_path_with_dot_in_path - @secret = 'in the sauce' - render :file => 'test/dot.directory/render_file_with_ivar' + @secret = "in the sauce" + render file: "test/dot.directory/render_file_with_ivar" end def render_file_using_pathname - @secret = 'in the sauce' - render :file => Pathname.new(File.dirname(__FILE__)).join('..', '..', 'fixtures', 'test', 'dot.directory', 'render_file_with_ivar') + @secret = "in the sauce" + render file: Pathname.new(File.dirname(__FILE__)).join("..", "..", "fixtures", "test", "dot.directory", "render_file_with_ivar") end def render_file_from_template - @secret = 'in the sauce' - @path = File.expand_path(File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_ivar')) + @secret = "in the sauce" + @path = File.expand_path(File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_ivar")) end def render_file_with_locals - path = File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_locals') - render :file => path, :locals => {:secret => 'in the sauce'} + path = File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_locals") + render file: path, locals: { secret: "in the sauce" } end def render_file_as_string_with_locals - path = File.expand_path(File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_locals')) - render file: path, :locals => {:secret => 'in the sauce'} + path = File.expand_path(File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_locals")) + render file: path, locals: { secret: "in the sauce" } end def accessing_request_in_template - render :inline => "Hello: <%= request.host %>" + render inline: "Hello: <%= request.host %>" end def accessing_logger_in_template - render :inline => "<%= logger.class %>" + render inline: "<%= logger.class %>" end def accessing_action_name_in_template - render :inline => "<%= action_name %>" + render inline: "<%= action_name %>" end def accessing_controller_name_in_template - render :inline => "<%= controller_name %>" + render inline: "<%= controller_name %>" end # :ported: def render_custom_code - render plain: "hello world", :status => 404 + render plain: "hello world", status: 404 end # :ported: @@ -240,7 +240,7 @@ class TestController < ApplicationController # setting content type def render_xml_hello @name = "David" - render :template => "test/hello" + render template: "test/hello" end def render_xml_hello_as_string_template @@ -249,7 +249,7 @@ class TestController < ApplicationController end def render_line_offset - render :inline => '<% raise %>', :locals => {:foo => 'bar'} + render inline: "<% raise %>", locals: { foo: "bar" } end def heading @@ -262,49 +262,49 @@ class TestController < ApplicationController # :ported: def blank_response - render plain: ' ' + render plain: " " end # :ported: def layout_test - render :action => "hello_world" + render action: "hello_world" end # :ported: def builder_layout_test @name = nil - render :action => "hello", :layout => "layouts/builder" + render action: "hello", layout: "layouts/builder" end # :move: test this in Action View def builder_partial_test - render :action => "hello_world_container" + render action: "hello_world_container" end # :ported: def partials_list - @test_unchanged = 'hello' + @test_unchanged = "hello" @customers = [ Customer.new("david"), Customer.new("mary") ] - render :action => "list" + render action: "list" end def partial_only - render :partial => true + render partial: true end def hello_in_a_string @customers = [ Customer.new("david"), Customer.new("mary") ] - render plain: "How's there? " + render_to_string(:template => "test/list") + render plain: "How's there? " + render_to_string(template: "test/list") end def accessing_params_in_template - render :inline => "Hello: <%= params[:name] %>" + render inline: "Hello: <%= params[:name] %>" end def accessing_local_assigns_in_inline_template name = params[:local_name] - render :inline => "<%= 'Goodbye, ' + local_name %>", - :locals => { :local_name => name } + render inline: "<%= 'Goodbye, ' + local_name %>", + locals: { local_name: name } end def render_implicit_html_template_from_xhr_request @@ -320,7 +320,7 @@ class TestController < ApplicationController end def render_to_string_test - @foo = render_to_string :inline => "this is a test" + @foo = render_to_string inline: "this is a test" end def default_render @@ -333,27 +333,27 @@ class TestController < ApplicationController end def render_action_hello_world_as_symbol - render :action => :hello_world + render action: :hello_world end def layout_test_with_different_layout - render :action => "hello_world", :layout => "standard" + render action: "hello_world", layout: "standard" end def layout_test_with_different_layout_and_string_action - render "hello_world", :layout => "standard" + render "hello_world", layout: "standard" end def layout_test_with_different_layout_and_symbol_action - render :hello_world, :layout => "standard" + render :hello_world, layout: "standard" end def rendering_without_layout - render :action => "hello_world", :layout => false + render action: "hello_world", layout: false end def layout_overriding_layout - render :action => "hello_world", :layout => "standard" + render action: "hello_world", layout: "standard" end def rendering_nothing_on_layout @@ -364,38 +364,38 @@ class TestController < ApplicationController @before = "i'm before the render" render_to_string plain: "foo" @after = "i'm after the render" - render :template => "test/hello_world" + render template: "test/hello_world" end def render_to_string_with_exception - render_to_string :file => "exception that will not be caught - this will certainly not work" + render_to_string file: "exception that will not be caught - this will certainly not work" end def render_to_string_with_caught_exception @before = "i'm before the render" begin - render_to_string :file => "exception that will be caught- hope my future instance vars still work!" + render_to_string file: "exception that will be caught- hope my future instance vars still work!" rescue end @after = "i'm after the render" - render :template => "test/hello_world" + render template: "test/hello_world" end def accessing_params_in_template_with_layout - render :layout => true, :inline => "Hello: <%= params[:name] %>" + render layout: true, inline: "Hello: <%= params[:name] %>" end # :ported: def render_with_explicit_template - render :template => "test/hello_world" + render template: "test/hello_world" end def render_with_explicit_unescaped_template - render :template => "test/h*llo_world" + render template: "test/h*llo_world" end def render_with_explicit_escaped_template - render :template => "test/hello,world" + render template: "test/hello,world" end def render_with_explicit_string_template @@ -404,7 +404,7 @@ class TestController < ApplicationController # :ported: def render_with_explicit_template_with_locals - render :template => "test/render_file_with_locals", :locals => { :secret => 'area51' } + render template: "test/render_file_with_locals", locals: { secret: "area51" } end # :ported: @@ -414,13 +414,13 @@ class TestController < ApplicationController end def double_redirect - redirect_to :action => "double_render" - redirect_to :action => "double_render" + redirect_to action: "double_render" + redirect_to action: "double_render" end def render_and_redirect render plain: "hello" - redirect_to :action => "double_render" + redirect_to action: "double_render" end def render_to_string_and_render @@ -429,22 +429,22 @@ class TestController < ApplicationController end def render_to_string_with_inline_and_render - render_to_string :inline => "<%= 'dlrow olleh'.reverse %>" - render :template => "test/hello_world" + render_to_string inline: "<%= 'dlrow olleh'.reverse %>" + render template: "test/hello_world" end def rendering_with_conflicting_local_vars @name = "David" - render :action => "potential_conflicts" + render action: "potential_conflicts" end def hello_world_from_rxml_using_action - render :action => "hello_world_from_rxml", :handlers => [:builder] + render action: "hello_world_from_rxml", handlers: [:builder] end # :deprecated: def hello_world_from_rxml_using_template - render :template => "test/hello_world_from_rxml", :handlers => [:builder] + render template: "test/hello_world_from_rxml", handlers: [:builder] end def action_talk_to_layout @@ -458,11 +458,11 @@ class TestController < ApplicationController end def render_with_assigns_option - render inline: '<%= @hello %>', assigns: { hello: "world" } + render inline: "<%= @hello %>", assigns: { hello: "world" } end def yield_content_for - render :action => "content_for", :layout => "yield" + render action: "content_for", layout: "yield" end def render_content_type_from_body @@ -471,117 +471,117 @@ class TestController < ApplicationController end def render_using_layout_around_block - render :action => "using_layout_around_block" + render action: "using_layout_around_block" end def render_using_layout_around_block_in_main_layout_and_within_content_for_layout - render :action => "using_layout_around_block", :layout => "layouts/block_with_layout" + render action: "using_layout_around_block", layout: "layouts/block_with_layout" end def partial_formats_html - render :partial => 'partial', :formats => [:html] + render partial: "partial", formats: [:html] end def partial - render :partial => 'partial' + render partial: "partial" end def partial_html_erb - render :partial => 'partial_html_erb' + render partial: "partial_html_erb" end def render_to_string_with_partial - @partial_only = render_to_string :partial => "partial_only" - @partial_with_locals = render_to_string :partial => "customer", :locals => { :customer => Customer.new("david") } - render :template => "test/hello_world" + @partial_only = render_to_string partial: "partial_only" + @partial_with_locals = render_to_string partial: "customer", locals: { customer: Customer.new("david") } + render template: "test/hello_world" end def render_to_string_with_template_and_html_partial - @text = render_to_string :template => "test/with_partial", :formats => [:text] - @html = render_to_string :template => "test/with_partial", :formats => [:html] - render :template => "test/with_html_partial" + @text = render_to_string template: "test/with_partial", formats: [:text] + @html = render_to_string template: "test/with_partial", formats: [:html] + render template: "test/with_html_partial" end def render_to_string_and_render_with_different_formats - @html = render_to_string :template => "test/with_partial", :formats => [:html] - render :template => "test/with_partial", :formats => [:text] + @html = render_to_string template: "test/with_partial", formats: [:html] + render template: "test/with_partial", formats: [:text] end def render_template_within_a_template_with_other_format - render :template => "test/with_xml_template", - :formats => [:html], - :layout => "with_html_partial" + render template: "test/with_xml_template", + formats: [:html], + layout: "with_html_partial" end def partial_with_counter - render :partial => "counter", :locals => { :counter_counter => 5 } + render partial: "counter", locals: { counter_counter: 5 } end def partial_with_locals - render :partial => "customer", :locals => { :customer => Customer.new("david") } + render partial: "customer", locals: { customer: Customer.new("david") } end def partial_with_form_builder - render :partial => ActionView::Helpers::FormBuilder.new(:post, nil, view_context, {}) + render partial: ActionView::Helpers::FormBuilder.new(:post, nil, view_context, {}) end def partial_with_form_builder_subclass - render :partial => LabellingFormBuilder.new(:post, nil, view_context, {}) + render partial: LabellingFormBuilder.new(:post, nil, view_context, {}) end def partial_collection - render :partial => "customer", :collection => [ Customer.new("david"), Customer.new("mary") ] + render partial: "customer", collection: [ Customer.new("david"), Customer.new("mary") ] end def partial_collection_with_as - render :partial => "customer_with_var", :collection => [ Customer.new("david"), Customer.new("mary") ], :as => :customer + render partial: "customer_with_var", collection: [ Customer.new("david"), Customer.new("mary") ], as: :customer end def partial_collection_with_iteration - render partial: "customer_iteration", collection: [ Customer.new("david"), Customer.new("mary"), Customer.new('christine') ] + render partial: "customer_iteration", collection: [ Customer.new("david"), Customer.new("mary"), Customer.new("christine") ] end def partial_collection_with_as_and_iteration - render partial: "customer_iteration_with_as", collection: [ Customer.new("david"), Customer.new("mary"), Customer.new('christine') ], as: :client + render partial: "customer_iteration_with_as", collection: [ Customer.new("david"), Customer.new("mary"), Customer.new("christine") ], as: :client end def partial_collection_with_counter - render :partial => "customer_counter", :collection => [ Customer.new("david"), Customer.new("mary") ] + render partial: "customer_counter", collection: [ Customer.new("david"), Customer.new("mary") ] end def partial_collection_with_as_and_counter - render :partial => "customer_counter_with_as", :collection => [ Customer.new("david"), Customer.new("mary") ], :as => :client + render partial: "customer_counter_with_as", collection: [ Customer.new("david"), Customer.new("mary") ], as: :client end def partial_collection_with_locals - render :partial => "customer_greeting", :collection => [ Customer.new("david"), Customer.new("mary") ], :locals => { :greeting => "Bonjour" } + render partial: "customer_greeting", collection: [ Customer.new("david"), Customer.new("mary") ], locals: { greeting: "Bonjour" } end def partial_collection_with_spacer - render :partial => "customer", :spacer_template => "partial_only", :collection => [ Customer.new("david"), Customer.new("mary") ] + render partial: "customer", spacer_template: "partial_only", collection: [ Customer.new("david"), Customer.new("mary") ] end def partial_collection_with_spacer_which_uses_render - render :partial => "customer", :spacer_template => "partial_with_partial", :collection => [ Customer.new("david"), Customer.new("mary") ] + render partial: "customer", spacer_template: "partial_with_partial", collection: [ Customer.new("david"), Customer.new("mary") ] end def partial_collection_shorthand_with_locals - render :partial => [ Customer.new("david"), Customer.new("mary") ], :locals => { :greeting => "Bonjour" } + render partial: [ Customer.new("david"), Customer.new("mary") ], locals: { greeting: "Bonjour" } end def partial_collection_shorthand_with_different_types_of_records - render :partial => [ + render partial: [ BadCustomer.new("mark"), GoodCustomer.new("craig"), BadCustomer.new("john"), GoodCustomer.new("zach"), GoodCustomer.new("brandon"), BadCustomer.new("dan") ], - :locals => { :greeting => "Bonjour" } + locals: { greeting: "Bonjour" } end def empty_partial_collection - render :partial => "customer", :collection => [] + render partial: "customer", collection: [] end def partial_collection_shorthand_with_different_types_of_records_with_counter @@ -589,15 +589,15 @@ class TestController < ApplicationController end def missing_partial - render :partial => 'thisFileIsntHere' + render partial: "thisFileIsntHere" end def partial_with_hash_object - render :partial => "hash_object", :object => {:first_name => "Sam"} + render partial: "hash_object", object: { first_name: "Sam" } end def partial_with_nested_object - render :partial => "quiz/questions/question", :object => Quiz::Question.new("first") + render partial: "quiz/questions/question", object: Quiz::Question.new("first") end def partial_with_nested_object_shorthand @@ -605,24 +605,24 @@ class TestController < ApplicationController end def partial_hash_collection - render :partial => "hash_object", :collection => [ {:first_name => "Pratik"}, {:first_name => "Amy"} ] + render partial: "hash_object", collection: [ { first_name: "Pratik" }, { first_name: "Amy" } ] end def partial_hash_collection_with_locals - render :partial => "hash_greeting", :collection => [ {:first_name => "Pratik"}, {:first_name => "Amy"} ], :locals => { :greeting => "Hola" } + render partial: "hash_greeting", collection: [ { first_name: "Pratik" }, { first_name: "Amy" } ], locals: { greeting: "Hola" } end def partial_with_implicit_local_assignment @customer = Customer.new("Marcel") - render :partial => "customer" + render partial: "customer" end def render_call_to_partial_with_layout - render :action => "calling_partial_with_layout" + render action: "calling_partial_with_layout" end def render_call_to_partial_with_layout_in_main_layout_and_within_content_for_layout - render :action => "calling_partial_with_layout", :layout => "layouts/partial_with_layout" + render action: "calling_partial_with_layout", layout: "layouts/partial_with_layout" end before_action only: :render_with_filters do @@ -631,7 +631,7 @@ class TestController < ApplicationController # Ensure that the before filter is executed *before* self.formats is set. def render_with_filters - render :action => :formatted_xml_erb + render action: :formatted_xml_erb end private @@ -642,7 +642,7 @@ class TestController < ApplicationController def determine_layout case action_name - when "hello_world", "layout_test", "rendering_without_layout", + when "hello_world", "layout_test", "rendering_without_layout", "rendering_nothing_on_layout", "render_text_hello_world", "render_text_hello_world_with_layout", "hello_world_with_layout_false", @@ -652,11 +652,11 @@ class TestController < ApplicationController "render_with_explicit_string_template", "update_page", "update_page_with_instance_variables" - "layouts/standard" - when "action_talk_to_layout", "layout_overriding_layout" - "layouts/talk_from_action" - when "render_implicit_html_template_from_xhr_request" - (request.xhr? ? 'layouts/xhr' : 'layouts/standard') + "layouts/standard" + when "action_talk_to_layout", "layout_overriding_layout" + "layouts/talk_from_action" + when "render_implicit_html_template_from_xhr_request" + (request.xhr? ? "layouts/xhr" : "layouts/standard") end end end @@ -726,11 +726,6 @@ class RenderTest < ActionController::TestCase assert_equal "Elastica", @response.body end - def test_render_process - get :render_action_hello_world_as_string - assert_equal "Hello world!", @controller.process(:render_action_hello_world_as_string) - end - # :ported: def test_render_from_variable get :render_hello_world_from_variable @@ -776,7 +771,7 @@ class RenderTest < ActionController::TestCase # :ported: def test_do_with_render_action_and_layout_false get :hello_world_with_layout_false - assert_equal 'Hello world!', @response.body + assert_equal "Hello world!", @response.body end # :ported: @@ -831,27 +826,27 @@ class RenderTest < ActionController::TestCase get :render_custom_code assert_response 404 assert_response :missing - assert_equal 'hello world', @response.body + assert_equal "hello world", @response.body end # :ported: def test_render_text_with_nil get :render_text_with_nil assert_response 200 - assert_equal '', @response.body + assert_equal "", @response.body end # :ported: def test_render_text_with_false get :render_text_with_false - assert_equal 'false', @response.body + assert_equal "false", @response.body end # :ported: def test_render_nothing_with_appendix get :render_nothing_with_appendix assert_response 200 - assert_equal 'appended', @response.body + assert_equal "appended", @response.body end def test_render_text_with_resource @@ -946,7 +941,7 @@ class RenderTest < ActionController::TestCase def test_render_to_string_inline get :render_to_string_with_inline_and_render - assert_equal 'Hello world!', @response.body + assert_equal "Hello world!", @response.body end # :ported: @@ -979,24 +974,24 @@ class RenderTest < ActionController::TestCase def test_should_render_formatted_template get :formatted_html_erb - assert_equal 'formatted html erb', @response.body + assert_equal "formatted html erb", @response.body end def test_should_render_formatted_html_erb_template get :formatted_xml_erb - assert_equal '<test>passed formatted html erb</test>', @response.body + assert_equal "<test>passed formatted html erb</test>", @response.body end def test_should_render_formatted_html_erb_template_with_bad_accepts_header @request.env["HTTP_ACCEPT"] = "; a=dsf" get :formatted_xml_erb - assert_equal '<test>passed formatted html erb</test>', @response.body + assert_equal "<test>passed formatted html erb</test>", @response.body end def test_should_render_formatted_html_erb_template_with_faulty_accepts_header @request.accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, */*" get :formatted_xml_erb - assert_equal '<test>passed formatted html erb</test>', @response.body + assert_equal "<test>passed formatted html erb</test>", @response.body end def test_layout_test_with_different_layout @@ -1026,7 +1021,7 @@ class RenderTest < ActionController::TestCase def test_rendering_nothing_on_layout get :rendering_nothing_on_layout - assert_equal '', @response.body + assert_equal "", @response.body end def test_render_to_string_doesnt_break_assigns @@ -1108,7 +1103,7 @@ class RenderTest < ActionController::TestCase def test_render_text_with_assigns_option get :render_with_assigns_option - assert_equal 'world', response.body + assert_equal "world", response.body end # :ported: diff --git a/actionview/test/actionpack/controller/view_paths_test.rb b/actionview/test/actionpack/controller/view_paths_test.rb index e99659c802..4c58b959a9 100644 --- a/actionview/test/actionpack/controller/view_paths_test.rb +++ b/actionview/test/actionpack/controller/view_paths_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class ViewLoadPathsTest < ActionController::TestCase class TestController < ActionController::Base @@ -7,7 +7,7 @@ class ViewLoadPathsTest < ActionController::TestCase before_action :add_view_path, only: :hello_world_at_request_time def hello_world() end - def hello_world_at_request_time() render(:action => 'hello_world') end + def hello_world_at_request_time() render(action: "hello_world") end private def add_view_path @@ -17,15 +17,15 @@ class ViewLoadPathsTest < ActionController::TestCase module Test class SubController < ActionController::Base - layout 'test/sub' - def hello_world; render(:template => 'test/hello_world'); end + layout "test/sub" + def hello_world; render(template: "test/hello_world"); end end end def setup - @request = ActionController::TestRequest.create - @response = ActionDispatch::TestResponse.new @controller = TestController.new + @request = ActionController::TestRequest.create(@controller.class) + @response = ActionDispatch::TestResponse.new @paths = TestController.view_paths end @@ -34,7 +34,7 @@ class ViewLoadPathsTest < ActionController::TestCase end def expand(array) - array.map {|x| File.expand_path(x.to_s)} + array.map { |x| File.expand_path(x.to_s) } end def assert_paths(*paths) @@ -47,7 +47,7 @@ class ViewLoadPathsTest < ActionController::TestCase end def test_controller_appends_view_path_correctly - @controller.append_view_path 'foo' + @controller.append_view_path "foo" assert_paths(FIXTURE_LOAD_PATH, "foo") @controller.append_view_path(%w(bar baz)) @@ -58,7 +58,7 @@ class ViewLoadPathsTest < ActionController::TestCase end def test_controller_prepends_view_path_correctly - @controller.prepend_view_path 'baz' + @controller.prepend_view_path "baz" assert_paths("baz", FIXTURE_LOAD_PATH) @controller.prepend_view_path(%w(foo bar)) @@ -72,7 +72,7 @@ class ViewLoadPathsTest < ActionController::TestCase @controller.instance_variable_set :@template, ActionView::Base.new(TestController.view_paths, {}, @controller) class_view_paths = TestController.view_paths - @controller.append_view_path 'foo' + @controller.append_view_path "foo" assert_paths FIXTURE_LOAD_PATH, "foo" @controller.append_view_path(%w(bar baz)) @@ -84,7 +84,7 @@ class ViewLoadPathsTest < ActionController::TestCase @controller.instance_variable_set :@template, ActionView::Base.new(TestController.view_paths, {}, @controller) class_view_paths = TestController.view_paths - @controller.prepend_view_path 'baz' + @controller.prepend_view_path "baz" assert_paths "baz", FIXTURE_LOAD_PATH @controller.prepend_view_path(%w(foo bar)) @@ -131,10 +131,8 @@ class ViewLoadPathsTest < ActionController::TestCase "Decorated body", template.identifier, template.handler, - { - :virtual_path => template.virtual_path, - :format => template.formats - } + virtual_path: template.virtual_path, + format: template.formats ) end end @@ -157,14 +155,14 @@ class ViewLoadPathsTest < ActionController::TestCase class C < ActionController::Base; end } - A.view_paths = ['a/path'] + A.view_paths = ["a/path"] assert_paths A, "a/path" assert_paths A, *B.view_paths assert_paths C, *original_load_paths C.view_paths = [] - assert_nothing_raised { C.append_view_path 'c/path' } + assert_nothing_raised { C.append_view_path "c/path" } assert_paths C, "c/path" end diff --git a/actionview/test/active_record_unit.rb b/actionview/test/active_record_unit.rb index f9e94413b5..7f94b7ebb4 100644 --- a/actionview/test/active_record_unit.rb +++ b/actionview/test/active_record_unit.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" # Define the essentials class ActiveRecordTestConnector @@ -16,7 +16,7 @@ unless defined?(ActiveRecord) && defined?(FixtureSet) PATH_TO_AR = "#{File.dirname(__FILE__)}/../../activerecord/lib" raise LoadError, "#{PATH_TO_AR} doesn't exist" unless File.directory?(PATH_TO_AR) $LOAD_PATH.unshift PATH_TO_AR - require 'active_record' + require "active_record" rescue LoadError => e $stderr.print "Failed to load Active Record. Skipping Active Record assertion tests: #{e}" ActiveRecordTestConnector.able_to_connect = false @@ -24,12 +24,11 @@ unless defined?(ActiveRecord) && defined?(FixtureSet) end $stderr.flush - # Define the rest of the connector class ActiveRecordTestConnector class << self def setup - unless self.connected || !self.able_to_connect + unless connected || !able_to_connect setup_connection load_schema require_fixture_models @@ -44,14 +43,14 @@ class ActiveRecordTestConnector private def setup_connection if Object.const_defined?(:ActiveRecord) - defaults = { :database => ':memory:' } - adapter = defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3' - options = defaults.merge :adapter => adapter, :timeout => 500 + defaults = { database: ":memory:" } + adapter = defined?(JRUBY_VERSION) ? "jdbcsqlite3" : "sqlite3" + options = defaults.merge adapter: adapter, timeout: 500 ActiveRecord::Base.establish_connection(options) - ActiveRecord::Base.configurations = { 'sqlite3_ar_integration' => options } + ActiveRecord::Base.configurations = { "sqlite3_ar_integration" => options } ActiveRecord::Base.connection - Object.send(:const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name('type')) unless Object.const_defined?(:QUOTED_TYPE) + Object.send(:const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name("type")) unless Object.const_defined?(:QUOTED_TYPE) else raise "Can't setup connection since ActiveRecord isn't loaded." end @@ -59,13 +58,13 @@ class ActiveRecordTestConnector # Load actionpack sqlite3 tables def load_schema - File.read(File.dirname(__FILE__) + "/fixtures/db_definitions/sqlite.sql").split(';').each do |sql| + File.read(File.dirname(__FILE__) + "/fixtures/db_definitions/sqlite.sql").split(";").each do |sql| ActiveRecord::Base.connection.execute(sql) unless sql.blank? end end def require_fixture_models - Dir.glob(File.dirname(__FILE__) + "/fixtures/*.rb").each {|f| require f} + Dir.glob(File.dirname(__FILE__) + "/fixtures/*.rb").each { |f| require f } end end end diff --git a/actionview/test/activerecord/controller_runtime_test.rb b/actionview/test/activerecord/controller_runtime_test.rb index af91348d76..590559f592 100644 --- a/actionview/test/activerecord/controller_runtime_test.rb +++ b/actionview/test/activerecord/controller_runtime_test.rb @@ -1,19 +1,19 @@ -require 'active_record_unit' -require 'active_record/railties/controller_runtime' -require 'fixtures/project' -require 'active_support/log_subscriber/test_helper' -require 'action_controller/log_subscriber' +require "active_record_unit" +require "active_record/railties/controller_runtime" +require "fixtures/project" +require "active_support/log_subscriber/test_helper" +require "action_controller/log_subscriber" ActionController::Base.include(ActiveRecord::Railties::ControllerRuntime) class ControllerRuntimeLogSubscriberTest < ActionController::TestCase class LogSubscriberController < ActionController::Base def show - render :inline => "<%= Project.all %>" + render inline: "<%= Project.all %>" end def zero - render :inline => "Zero DB runtime" + render inline: "Zero DB runtime" end def create @@ -24,11 +24,11 @@ class ControllerRuntimeLogSubscriberTest < ActionController::TestCase def redirect Project.all - redirect_to :action => 'show' + redirect_to action: "show" end def db_after_render - render :inline => "Hello world" + render inline: "Hello world" Project.all ActiveRecord::LogSubscriber.runtime += 100 end @@ -38,8 +38,8 @@ class ControllerRuntimeLogSubscriberTest < ActionController::TestCase tests LogSubscriberController def setup - super @old_logger = ActionController::Base.logger + super ActionController::LogSubscriber.attach_to :action_controller end diff --git a/actionview/test/activerecord/debug_helper_test.rb b/actionview/test/activerecord/debug_helper_test.rb index 03cb1d5a91..06ae555a03 100644 --- a/actionview/test/activerecord/debug_helper_test.rb +++ b/actionview/test/activerecord/debug_helper_test.rb @@ -1,14 +1,17 @@ -require 'active_record_unit' -require 'nokogiri' +require "active_record_unit" +require "nokogiri" class DebugHelperTest < ActionView::TestCase def test_debug company = Company.new(name: "firebase") - assert_match "name: firebase", debug(company) + output = debug(company) + assert_match "name: name", output + assert_match "value_before_type_cast: firebase", output + assert_match "active_record_yaml_version: 2", output end def test_debug_with_marshal_error - obj = -> { } + obj = -> {} assert_match obj.inspect, Nokogiri.XML(debug(obj)).content end end diff --git a/actionview/test/activerecord/form_helper_activerecord_test.rb b/actionview/test/activerecord/form_helper_activerecord_test.rb index 2769b97445..6152ec4720 100644 --- a/actionview/test/activerecord/form_helper_activerecord_test.rb +++ b/actionview/test/activerecord/form_helper_activerecord_test.rb @@ -1,6 +1,6 @@ -require 'active_record_unit' -require 'fixtures/project' -require 'fixtures/developer' +require "active_record_unit" +require "fixtures/project" +require "fixtures/developer" class FormHelperActiveRecordTest < ActionView::TestCase tests ActionView::Helpers::FormHelper @@ -39,12 +39,12 @@ class FormHelperActiveRecordTest < ActionView::TestCase def test_nested_fields_for_with_child_index_option_override_on_a_nested_attributes_collection_association form_for(@developer) do |f| - concat f.fields_for(:projects, @developer.projects.first, :child_index => 'abc') { |cf| + concat f.fields_for(:projects, @developer.projects.first, child_index: "abc") { |cf| concat cf.text_field(:name) } end - expected = whole_form('/developers/123', 'edit_developer_123', 'edit_developer', :method => 'patch') do + 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_id" name="developer[projects_attributes][abc][id]" type="hidden" value="321" />' end @@ -54,35 +54,35 @@ class FormHelperActiveRecordTest < ActionView::TestCase protected - def hidden_fields(method = nil) - txt = %{<input name="utf8" type="hidden" value="✓" />} + def hidden_fields(method = nil) + txt = %{<input name="utf8" type="hidden" value="✓" />} - if method && !%w(get post).include?(method.to_s) - txt << %{<input name="_method" type="hidden" value="#{method}" />} + if method && !%w(get post).include?(method.to_s) + txt << %{<input name="_method" type="hidden" value="#{method}" />} + end + + txt end - txt - end + def form_text(action = "/", id = nil, html_class = nil, remote = nil, multipart = nil, method = nil) + txt = %{<form accept-charset="UTF-8" action="#{action}"} + txt << %{ enctype="multipart/form-data"} if multipart + txt << %{ data-remote="true"} if remote + txt << %{ class="#{html_class}"} if html_class + txt << %{ id="#{id}"} if id + method = method.to_s == "get" ? "get" : "post" + txt << %{ method="#{method}">} + end - def form_text(action = "/", id = nil, html_class = nil, remote = nil, multipart = nil, method = nil) - txt = %{<form accept-charset="UTF-8" action="#{action}"} - txt << %{ enctype="multipart/form-data"} if multipart - txt << %{ data-remote="true"} if remote - txt << %{ class="#{html_class}"} if html_class - txt << %{ id="#{id}"} if id - method = method.to_s == "get" ? "get" : "post" - txt << %{ method="#{method}">} - end + def whole_form(action = "/", id = nil, html_class = nil, options = nil) + contents = block_given? ? yield : "" - def whole_form(action = "/", id = nil, html_class = nil, options = nil) - contents = block_given? ? yield : "" + if options.is_a?(Hash) + method, remote, multipart = options.values_at(:method, :remote, :multipart) + else + method = options + end - if options.is_a?(Hash) - method, remote, multipart = options.values_at(:method, :remote, :multipart) - else - method = options + form_text(action, id, html_class, remote, multipart, method) + hidden_fields(method) + contents + "</form>" end - - form_text(action, id, html_class, remote, multipart, method) + hidden_fields(method) + contents + "</form>" - end end diff --git a/actionview/test/activerecord/polymorphic_routes_test.rb b/actionview/test/activerecord/polymorphic_routes_test.rb index 34b2698c7f..8495949975 100644 --- a/actionview/test/activerecord/polymorphic_routes_test.rb +++ b/actionview/test/activerecord/polymorphic_routes_test.rb @@ -1,28 +1,28 @@ -require 'active_record_unit' -require 'fixtures/project' +require "active_record_unit" +require "fixtures/project" class Task < ActiveRecord::Base - self.table_name = 'projects' + self.table_name = "projects" end class Step < ActiveRecord::Base - self.table_name = 'projects' + self.table_name = "projects" end class Bid < ActiveRecord::Base - self.table_name = 'projects' + self.table_name = "projects" end class Tax < ActiveRecord::Base - self.table_name = 'projects' + self.table_name = "projects" end class Fax < ActiveRecord::Base - self.table_name = 'projects' + self.table_name = "projects" end class Series < ActiveRecord::Base - self.table_name = 'projects' + self.table_name = "projects" end class ModelDelegator @@ -41,17 +41,17 @@ class ModelDelegate end def to_param - 'overridden' + "overridden" end end module Blog class Post < ActiveRecord::Base - self.table_name = 'projects' + self.table_name = "projects" end class Blog < ActiveRecord::Base - self.table_name = 'projects' + self.table_name = "projects" end def self.use_relative_model_naming? @@ -61,7 +61,7 @@ end class PolymorphicRoutesTest < ActionController::TestCase include SharedTestRoutes.url_helpers - self.default_url_options[:host] = 'example.com' + self.default_url_options[:host] = "example.com" def setup @project = Project.new @@ -79,7 +79,7 @@ class PolymorphicRoutesTest < ActionController::TestCase def assert_url(url, args) host = self.class.default_url_options[:host] - assert_equal url.sub(/http:\/\/#{host}/, ''), polymorphic_path(args) + assert_equal url.sub(/http:\/\/#{host}/, ""), polymorphic_path(args) assert_equal url, polymorphic_url(args) assert_equal url, url_for(args) end @@ -95,7 +95,7 @@ class PolymorphicRoutesTest < ActionController::TestCase def test_string_with_options with_test_routes do - assert_equal "http://example.com/projects?id=10", polymorphic_url("projects", :id => 10) + assert_equal "http://example.com/projects?id=10", polymorphic_url("projects", id: 10) end end @@ -107,7 +107,7 @@ class PolymorphicRoutesTest < ActionController::TestCase def test_symbol_with_options with_test_routes do - assert_equal "http://example.com/projects?id=10", polymorphic_url(:projects, :id => 10) + assert_equal "http://example.com/projects?id=10", polymorphic_url(:projects, id: 10) end end @@ -179,7 +179,7 @@ class PolymorphicRoutesTest < ActionController::TestCase def test_with_nil_id with_test_routes do exception = assert_raise ArgumentError do - polymorphic_url({ :id => nil }) + polymorphic_url(id: nil) end assert_equal "Nil location provided. Can't build URI.", exception.message end @@ -233,8 +233,8 @@ class PolymorphicRoutesTest < ActionController::TestCase def test_class_with_options with_test_routes do - assert_equal "http://example.com/projects?foo=bar", polymorphic_url(@project.class, { :foo => :bar }) - assert_equal "/projects?foo=bar", polymorphic_path(@project.class, { :foo => :bar }) + assert_equal "http://example.com/projects?foo=bar", polymorphic_url(@project.class, foo: :bar) + assert_equal "/projects?foo=bar", polymorphic_path(@project.class, foo: :bar) end end @@ -274,7 +274,7 @@ class PolymorphicRoutesTest < ActionController::TestCase def test_with_record_and_action with_test_routes do - assert_equal "http://example.com/projects/new", polymorphic_url(@project, :action => 'new') + assert_equal "http://example.com/projects/new", polymorphic_url(@project, action: "new") end end @@ -303,35 +303,35 @@ class PolymorphicRoutesTest < ActionController::TestCase def test_url_helper_prefixed_with_edit_with_url_options with_test_routes do @project.save - assert_equal "http://example.com/projects/#{@project.id}/edit?param1=10", edit_polymorphic_url(@project, :param1 => '10') + assert_equal "http://example.com/projects/#{@project.id}/edit?param1=10", edit_polymorphic_url(@project, param1: "10") end end def test_url_helper_with_url_options with_test_routes do @project.save - assert_equal "http://example.com/projects/#{@project.id}?param1=10", polymorphic_url(@project, :param1 => '10') + assert_equal "http://example.com/projects/#{@project.id}?param1=10", polymorphic_url(@project, param1: "10") end end def test_format_option with_test_routes do @project.save - assert_equal "http://example.com/projects/#{@project.id}.pdf", polymorphic_url(@project, :format => :pdf) + assert_equal "http://example.com/projects/#{@project.id}.pdf", polymorphic_url(@project, format: :pdf) end end def test_format_option_with_url_options with_test_routes do @project.save - assert_equal "http://example.com/projects/#{@project.id}.pdf?param1=10", polymorphic_url(@project, :format => :pdf, :param1 => '10') + assert_equal "http://example.com/projects/#{@project.id}.pdf?param1=10", polymorphic_url(@project, format: :pdf, param1: "10") end end def test_id_and_format_option with_test_routes do @project.save - assert_equal "http://example.com/projects/#{@project.id}.pdf", polymorphic_url(:id => @project, :format => :pdf) + assert_equal "http://example.com/projects/#{@project.id}.pdf", polymorphic_url(id: @project, format: :pdf) end end @@ -373,7 +373,7 @@ class PolymorphicRoutesTest < ActionController::TestCase def test_new_with_array_and_namespace with_admin_test_routes do - assert_equal "http://example.com/admin/projects/new", polymorphic_url([:admin, @project], :action => 'new') + assert_equal "http://example.com/admin/projects/new", polymorphic_url([:admin, @project], action: "new") end end @@ -426,7 +426,7 @@ class PolymorphicRoutesTest < ActionController::TestCase with_test_routes do @project.save @task.save - assert_equal "http://example.com/projects/#{@project.id}/bid/tasks/#{@task.id}.pdf", polymorphic_url([@project, :bid, @task], :format => :pdf) + assert_equal "http://example.com/projects/#{@project.id}/bid/tasks/#{@task.id}.pdf", polymorphic_url([@project, :bid, @task], format: :pdf) end end @@ -474,13 +474,13 @@ class PolymorphicRoutesTest < ActionController::TestCase def test_with_hash with_test_routes do @project.save - assert_equal "http://example.com/projects/#{@project.id}", polymorphic_url(:id => @project) + assert_equal "http://example.com/projects/#{@project.id}", polymorphic_url(id: @project) end end def test_polymorphic_path_accepts_options with_test_routes do - assert_equal "/projects/new", polymorphic_path(@project, :action => 'new') + assert_equal "/projects/new", polymorphic_path(@project, action: "new") end end @@ -493,7 +493,7 @@ class PolymorphicRoutesTest < ActionController::TestCase object_array = [:admin, @project, @task] original_args = [object_array.dup, options.dup] - assert_no_difference('object_array.size') { polymorphic_path(object_array, options) } + assert_no_difference("object_array.size") { polymorphic_path(object_array, options) } assert_equal original_args, [object_array, options] end end @@ -527,7 +527,7 @@ class PolymorphicRoutesTest < ActionController::TestCase def test_with_irregular_plural_record_and_action with_test_routes do - assert_equal "http://example.com/taxes/new", polymorphic_url(@tax, :action => 'new') + assert_equal "http://example.com/taxes/new", polymorphic_url(@tax, action: "new") end end @@ -561,7 +561,7 @@ class PolymorphicRoutesTest < ActionController::TestCase def test_new_with_irregular_plural_array_and_namespace with_admin_test_routes do - assert_equal "http://example.com/admin/taxes/new", polymorphic_url([:admin, @tax], :action => 'new') + assert_equal "http://example.com/admin/taxes/new", polymorphic_url([:admin, @tax], action: "new") end end @@ -598,7 +598,7 @@ class PolymorphicRoutesTest < ActionController::TestCase end end - # Tests for uncountable names + # Tests for uncountable names def test_uncountable_resource with_test_routes do @series.save @@ -622,7 +622,7 @@ class PolymorphicRoutesTest < ActionController::TestCase def with_namespaced_routes(name) with_routing do |set| set.draw do - scope(:module => name) do + scope(module: name) do resources :blogs do resources :posts do resources :faxes @@ -728,6 +728,6 @@ class PolymorphicPathRoutesTest < PolymorphicRoutesTest def assert_url(url, args) host = self.class.default_url_options[:host] - assert_equal url.sub(/http:\/\/#{host}/, ''), url_for(args) + assert_equal url.sub(/http:\/\/#{host}/, ""), url_for(args) end end diff --git a/actionview/test/activerecord/relation_cache_test.rb b/actionview/test/activerecord/relation_cache_test.rb index 8e97417b94..880e80a8dc 100644 --- a/actionview/test/activerecord/relation_cache_test.rb +++ b/actionview/test/activerecord/relation_cache_test.rb @@ -1,4 +1,4 @@ -require 'active_record_unit' +require "active_record_unit" class RelationCacheTest < ActionView::TestCase tests ActionView::Helpers::CacheHelper @@ -9,10 +9,9 @@ class RelationCacheTest < ActionView::TestCase end def test_cache_relation_other - cache(Project.all){ concat("Hello World") } + cache(Project.all) { concat("Hello World") } assert_equal "Hello World", controller.cache_store.read("views/projects-#{Project.count}/") end def view_cache_dependencies; end - end 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 9772ebb39e..55886da30f 100644 --- a/actionview/test/activerecord/render_partial_with_record_identification_test.rb +++ b/actionview/test/activerecord/render_partial_with_record_identification_test.rb @@ -1,48 +1,48 @@ -require 'active_record_unit' +require "active_record_unit" class RenderPartialWithRecordIdentificationController < ActionController::Base def render_with_has_many_and_belongs_to_association @developer = Developer.find(1) - render :partial => @developer.projects + render partial: @developer.projects end def render_with_has_many_association @topic = Topic.find(1) - render :partial => @topic.replies + render partial: @topic.replies end def render_with_scope - render :partial => Reply.base + render partial: Reply.base end def render_with_has_many_through_association @developer = Developer.first - render :partial => @developer.topics + render partial: @developer.topics end def render_with_has_one_association @company = Company.find(1) - render :partial => @company.mascot + render partial: @company.mascot end def render_with_belongs_to_association @reply = Reply.find(1) - render :partial => @reply.topic + render partial: @reply.topic end def render_with_record @developer = Developer.first - render :partial => @developer + render partial: @developer end def render_with_record_collection @developers = Developer.all - render :partial => @developers + render partial: @developers end def render_with_record_collection_and_spacer_template @developer = Developer.find(1) - render :partial => @developer.projects, :spacer_template => 'test/partial_only' + render partial: @developer.projects, spacer_template: "test/partial_only" end end @@ -57,27 +57,27 @@ class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase def test_rendering_partial_with_has_many_association get :render_with_has_many_association - assert_equal 'Birdman is better!', @response.body + assert_equal "Birdman is better!", @response.body end def test_rendering_partial_with_scope get :render_with_scope - assert_equal 'Birdman is better!Nuh uh!', @response.body + assert_equal "Birdman is better!Nuh uh!", @response.body end def test_render_with_record get :render_with_record - assert_equal 'David', @response.body + assert_equal "David", @response.body end def test_render_with_record_collection get :render_with_record_collection - assert_equal 'DavidJamisfixture_3fixture_4fixture_5fixture_6fixture_7fixture_8fixture_9fixture_10Jamis', @response.body + assert_equal "DavidJamisfixture_3fixture_4fixture_5fixture_6fixture_7fixture_8fixture_9fixture_10Jamis", @response.body end def test_render_with_record_collection_and_spacer_template get :render_with_record_collection_and_spacer_template - assert_equal Developer.find(1).projects.map(&:name).join('only partial'), @response.body + assert_equal Developer.find(1).projects.map(&:name).join("only partial"), @response.body end def test_rendering_partial_with_has_one_association @@ -98,22 +98,22 @@ end module Fun class NestedController < ActionController::Base def render_with_record_in_nested_controller - render :partial => Game.new("Pong") + render partial: Game.new("Pong") end def render_with_record_collection_in_nested_controller - render :partial => [ Game.new("Pong"), Game.new("Tank") ] + render partial: [ Game.new("Pong"), Game.new("Tank") ] end end module Serious class NestedDeeperController < ActionController::Base def render_with_record_in_deeper_nested_controller - render :partial => Game.new("Chess") + render partial: Game.new("Chess") end def render_with_record_collection_in_deeper_nested_controller - render :partial => [ Game.new("Chess"), Game.new("Sudoku"), Game.new("Solitaire") ] + render partial: [ Game.new("Chess"), Game.new("Sudoku"), Game.new("Solitaire") ] end end end diff --git a/actionview/test/fixtures/company.rb b/actionview/test/fixtures/company.rb index f3ac3642fa..9f527acdd8 100644 --- a/actionview/test/fixtures/company.rb +++ b/actionview/test/fixtures/company.rb @@ -4,6 +4,6 @@ class Company < ActiveRecord::Base validates_presence_of :name def validate - errors.add('rating', 'rating should not be 2') if rating == 2 + errors.add("rating", "rating should not be 2") if rating == 2 end end diff --git a/actionview/test/fixtures/developer.rb b/actionview/test/fixtures/developer.rb index 8b3f0a8039..1a686a33ce 100644 --- a/actionview/test/fixtures/developer.rb +++ b/actionview/test/fixtures/developer.rb @@ -1,6 +1,6 @@ class Developer < ActiveRecord::Base has_and_belongs_to_many :projects has_many :replies - has_many :topics, :through => :replies + has_many :topics, through: :replies accepts_nested_attributes_for :projects end diff --git a/actionview/test/fixtures/digestor/api/comments/_comment.json.erb b/actionview/test/fixtures/digestor/api/comments/_comment.json.erb new file mode 100644 index 0000000000..696eb13917 --- /dev/null +++ b/actionview/test/fixtures/digestor/api/comments/_comment.json.erb @@ -0,0 +1 @@ +{"content": "Great story!"} diff --git a/actionview/test/fixtures/digestor/api/comments/_comments.json.erb b/actionview/test/fixtures/digestor/api/comments/_comments.json.erb new file mode 100644 index 0000000000..c28646a283 --- /dev/null +++ b/actionview/test/fixtures/digestor/api/comments/_comments.json.erb @@ -0,0 +1 @@ +<%= render partial: "comments/comment", collection: commentable.comments %> diff --git a/actionview/test/fixtures/digestor/messages/peek.html.erb b/actionview/test/fixtures/digestor/messages/peek.html.erb new file mode 100644 index 0000000000..84885ab0bc --- /dev/null +++ b/actionview/test/fixtures/digestor/messages/peek.html.erb @@ -0,0 +1,2 @@ +<%# Template Dependency: messages/message %> +<%= render "comments/comments" %> diff --git a/actionview/test/fixtures/digestor/messages/thread.json.erb b/actionview/test/fixtures/digestor/messages/thread.json.erb new file mode 100644 index 0000000000..e4c1ba97cd --- /dev/null +++ b/actionview/test/fixtures/digestor/messages/thread.json.erb @@ -0,0 +1 @@ +<%= render "comments/comments" %> diff --git a/actionview/test/fixtures/helpers_missing/invalid_require_helper.rb b/actionview/test/fixtures/helpers_missing/invalid_require_helper.rb index d8801e54d5..6ac6677daa 100644 --- a/actionview/test/fixtures/helpers_missing/invalid_require_helper.rb +++ b/actionview/test/fixtures/helpers_missing/invalid_require_helper.rb @@ -1,5 +1,4 @@ -require 'very_invalid_file_name' +require "very_invalid_file_name" module InvalidRequireHelper end - diff --git a/actionview/test/fixtures/mascot.rb b/actionview/test/fixtures/mascot.rb index f9f1448b8f..1c0bd7c8fd 100644 --- a/actionview/test/fixtures/mascot.rb +++ b/actionview/test/fixtures/mascot.rb @@ -1,3 +1,3 @@ class Mascot < ActiveRecord::Base belongs_to :company -end
\ No newline at end of file +end diff --git a/actionview/test/fixtures/test/_builder_tag_nested_in_content_tag.erb b/actionview/test/fixtures/test/_builder_tag_nested_in_content_tag.erb new file mode 100644 index 0000000000..ddad7ec3ac --- /dev/null +++ b/actionview/test/fixtures/test/_builder_tag_nested_in_content_tag.erb @@ -0,0 +1,3 @@ +<%= tag.p do %> + <%= tag.b 'Hello' %> +<% end %> diff --git a/actionview/test/fixtures/test/_customer.mobile.erb b/actionview/test/fixtures/test/_customer.mobile.erb new file mode 100644 index 0000000000..d8220afeda --- /dev/null +++ b/actionview/test/fixtures/test/_customer.mobile.erb @@ -0,0 +1 @@ +Hello: <%= customer.name rescue "Anonymous" %>
\ No newline at end of file diff --git a/actionview/test/fixtures/test/_ðŸ£.erb b/actionview/test/fixtures/test/_ðŸ£.erb new file mode 100644 index 0000000000..4bbe59410a --- /dev/null +++ b/actionview/test/fixtures/test/_ðŸ£.erb @@ -0,0 +1 @@ +🣠diff --git a/actionview/test/fixtures/test/render_file_inspect_local_assigns.erb b/actionview/test/fixtures/test/render_file_inspect_local_assigns.erb new file mode 100644 index 0000000000..aea5c351c5 --- /dev/null +++ b/actionview/test/fixtures/test/render_file_inspect_local_assigns.erb @@ -0,0 +1 @@ +<%= local_assigns.inspect.html_safe %>
\ No newline at end of file diff --git a/actionview/test/fixtures/test/render_file_unicode_local.erb b/actionview/test/fixtures/test/render_file_unicode_local.erb new file mode 100644 index 0000000000..cbfd040a76 --- /dev/null +++ b/actionview/test/fixtures/test/render_file_unicode_local.erb @@ -0,0 +1 @@ +<%= 🎃 %>
\ No newline at end of file diff --git a/actionview/test/fixtures/test/render_file_with_ruby_keyword_locals.erb b/actionview/test/fixtures/test/render_file_with_ruby_keyword_locals.erb new file mode 100644 index 0000000000..7e3fe6c6d9 --- /dev/null +++ b/actionview/test/fixtures/test/render_file_with_ruby_keyword_locals.erb @@ -0,0 +1 @@ +The class is <%= local_assigns[:class] %>
\ No newline at end of file diff --git a/actionview/test/fixtures/topic.rb b/actionview/test/fixtures/topic.rb index 9fa9746535..48a3dfba88 100644 --- a/actionview/test/fixtures/topic.rb +++ b/actionview/test/fixtures/topic.rb @@ -1,3 +1,3 @@ class Topic < ActiveRecord::Base - has_many :replies, :dependent => :destroy + has_many :replies, dependent: :destroy end diff --git a/actionview/test/lib/controller/fake_models.rb b/actionview/test/lib/controller/fake_models.rb index a122fe17c9..cc5f5c1d59 100644 --- a/actionview/test/lib/controller/fake_models.rb +++ b/actionview/test/lib/controller/fake_models.rb @@ -31,27 +31,6 @@ end class GoodCustomer < Customer end -class TicketType < Struct.new(:name) - extend ActiveModel::Naming - include ActiveModel::Conversion - extend ActiveModel::Translation - - def initialize(*args) - super - @persisted = false - end - - def persisted=(boolean) - @persisted = boolean - end - - def persisted? - @persisted - end - - attr_accessor :name -end - class Post < Struct.new(:title, :author_name, :body, :secret, :persisted, :written_on, :cost) extend ActiveModel::Naming include ActiveModel::Conversion @@ -129,7 +108,6 @@ class Tag attr_accessor :relevances def relevances_attributes=(attributes); end - end class CommentRelevance @@ -212,7 +190,7 @@ class Plane attr_reader :to_key def model_name - OpenStruct.new param_key: 'airplane' + OpenStruct.new param_key: "airplane" end def save diff --git a/actionview/test/template/active_model_helper_test.rb b/actionview/test/template/active_model_helper_test.rb index 55d62cf692..8b8f686f96 100644 --- a/actionview/test/template/active_model_helper_test.rb +++ b/actionview/test/template/active_model_helper_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class ActiveModelHelperTest < ActionView::TestCase tests ActionView::Helpers::ActiveModelHelper @@ -50,28 +50,28 @@ class ActiveModelHelperTest < ActionView::TestCase def test_select_with_errors_and_blank_option expected_dom = %(<div class="field_with_errors"><select name="post[author_name]" id="post_author_name"><option value="">Choose one...</option>\n<option value="a">a</option>\n<option value="b">b</option></select></div>) - assert_dom_equal(expected_dom, select("post", "author_name", [:a, :b], :include_blank => 'Choose one...')) - assert_dom_equal(expected_dom, select("post", "author_name", [:a, :b], :prompt => 'Choose one...')) + assert_dom_equal(expected_dom, select("post", "author_name", [:a, :b], include_blank: "Choose one...")) + assert_dom_equal(expected_dom, select("post", "author_name", [:a, :b], prompt: "Choose one...")) end def test_date_select_with_errors assert_dom_equal( %(<div class="field_with_errors"><select id="post_updated_at_1i" name="post[updated_at(1i)]">\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n</select>\n<input id="post_updated_at_2i" name="post[updated_at(2i)]" type="hidden" value="6" />\n<input id="post_updated_at_3i" name="post[updated_at(3i)]" type="hidden" value="1" />\n</div>), - date_select("post", "updated_at", :discard_month => true, :discard_day => true, :start_year => 2004, :end_year => 2005) + date_select("post", "updated_at", discard_month: true, discard_day: true, start_year: 2004, end_year: 2005) ) end def test_datetime_select_with_errors assert_dom_equal( %(<div class="field_with_errors"><input id="post_updated_at_1i" name="post[updated_at(1i)]" type="hidden" value="2004" />\n<input id="post_updated_at_2i" name="post[updated_at(2i)]" type="hidden" value="6" />\n<input id="post_updated_at_3i" name="post[updated_at(3i)]" type="hidden" value="1" />\n<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n<option selected="selected" value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n</select>\n : <select id="post_updated_at_5i" name="post[updated_at(5i)]">\n<option selected="selected" value="00">00</option>\n</select>\n</div>), - datetime_select("post", "updated_at", :discard_year => true, :discard_month => true, :discard_day => true, :minute_step => 60) + datetime_select("post", "updated_at", discard_year: true, discard_month: true, discard_day: true, minute_step: 60) ) end def test_time_select_with_errors assert_dom_equal( %(<div class="field_with_errors"><input id="post_updated_at_1i" name="post[updated_at(1i)]" type="hidden" value="2004" />\n<input id="post_updated_at_2i" name="post[updated_at(2i)]" type="hidden" value="6" />\n<input id="post_updated_at_3i" name="post[updated_at(3i)]" type="hidden" value="15" />\n<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n<option selected="selected" value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n</select>\n : <select id="post_updated_at_5i" name="post[updated_at(5i)]">\n<option selected="selected" value="00">00</option>\n</select>\n</div>), - time_select("post", "updated_at", :minute_step => 60) + time_select("post", "updated_at", minute_step: 60) ) end @@ -95,5 +95,4 @@ class ActiveModelHelperTest < ActionView::TestCase ensure ActionView::Base.field_error_proc = old_proc if old_proc end - end diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb index 8bfd19eb26..3bdab42f7a 100644 --- a/actionview/test/template/asset_tag_helper_test.rb +++ b/actionview/test/template/asset_tag_helper_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/ordered_options' +require "abstract_unit" +require "active_support/ordered_options" class AssetTagHelperTest < ActionView::TestCase tests ActionView::Helpers::AssetTagHelper @@ -13,10 +13,10 @@ class AssetTagHelperTest < ActionView::TestCase @request = Class.new do attr_accessor :script_name - def protocol() 'http://' end + def protocol() "http://" end def ssl?() false end - def host_with_port() 'localhost' end - def base_url() 'http://www.example.com' end + def host_with_port() "localhost" end + def base_url() "http://www.example.com" end end.new @controller.request = @request @@ -238,7 +238,7 @@ class AssetTagHelperTest < ActionView::TestCase VideoLinkToTag = { %(video_tag("xml.ogg")) => %(<video src="/videos/xml.ogg"></video>), %(video_tag("rss.m4v", :autoplay => true, :controls => true)) => %(<video autoplay="autoplay" controls="controls" src="/videos/rss.m4v"></video>), - %(video_tag("rss.m4v", :autobuffer => true)) => %(<video autobuffer="autobuffer" src="/videos/rss.m4v"></video>), + %(video_tag("rss.m4v", :preload => 'none')) => %(<video preload="none" src="/videos/rss.m4v"></video>), %(video_tag("gold.m4v", :size => "160x120")) => %(<video height="120" src="/videos/gold.m4v" width="160"></video>), %(video_tag("gold.m4v", "size" => "320x240")) => %(<video height="240" src="/videos/gold.m4v" width="320"></video>), %(video_tag("trailer.ogg", :poster => "screenshot.png")) => %(<video poster="/images/screenshot.png" src="/videos/trailer.ogg"></video>), @@ -288,7 +288,7 @@ class AssetTagHelperTest < ActionView::TestCase %(audio_tag("//media.rubyonrails.org/audio/rails_blog_2.mov")) => %(<audio src="//media.rubyonrails.org/audio/rails_blog_2.mov"></audio>), %(audio_tag("audio.mp3", "audio.ogg")) => %(<audio><source src="/audios/audio.mp3" /><source src="/audios/audio.ogg" /></audio>), %(audio_tag(["audio.mp3", "audio.ogg"])) => %(<audio><source src="/audios/audio.mp3" /><source src="/audios/audio.ogg" /></audio>), - %(audio_tag(["audio.mp3", "audio.ogg"], :autobuffer => true, :controls => true)) => %(<audio autobuffer="autobuffer" controls="controls"><source src="/audios/audio.mp3" /><source src="/audios/audio.ogg" /></audio>) + %(audio_tag(["audio.mp3", "audio.ogg"], :preload => 'none', :controls => true)) => %(<audio preload="none" controls="controls"><source src="/audios/audio.mp3" /><source src="/audios/audio.ogg" /></audio>) } FontPathToTag = { @@ -306,7 +306,7 @@ class AssetTagHelperTest < ActionView::TestCase end def test_autodiscovery_link_tag_with_unknown_type - result = auto_discovery_link_tag(:xml, '/feed.xml', :type => 'application/xml') + result = auto_discovery_link_tag(:xml, "/feed.xml", type: "application/xml") expected = %(<link href="/feed.xml" rel="alternate" title="XML" type="application/xml" />) assert_dom_equal expected, result end @@ -322,18 +322,18 @@ class AssetTagHelperTest < ActionView::TestCase def test_asset_path_tag_to_not_create_duplicate_slashes @controller.config.asset_host = "host/" - assert_dom_equal('http://host/foo', asset_path("foo")) + assert_dom_equal("http://host/foo", asset_path("foo")) - @controller.config.relative_url_root = '/some/root/' - assert_dom_equal('http://host/some/root/foo', asset_path("foo")) + @controller.config.relative_url_root = "/some/root/" + assert_dom_equal("http://host/some/root/foo", asset_path("foo")) end def test_compute_asset_public_path assert_equal "/robots.txt", compute_asset_path("robots.txt") assert_equal "/robots.txt", compute_asset_path("/robots.txt") - assert_equal "/javascripts/foo.js", compute_asset_path("foo.js", :type => :javascript) - assert_equal "/javascripts/foo.js", compute_asset_path("/foo.js", :type => :javascript) - assert_equal "/stylesheets/foo.css", compute_asset_path("foo.css", :type => :stylesheet) + assert_equal "/javascripts/foo.js", compute_asset_path("foo.js", type: :javascript) + assert_equal "/javascripts/foo.js", compute_asset_path("/foo.js", type: :javascript) + assert_equal "/stylesheets/foo.css", compute_asset_path("foo.css", type: :stylesheet) end def test_auto_discovery_link_tag @@ -362,11 +362,11 @@ class AssetTagHelperTest < ActionView::TestCase def test_javascript_include_tag_with_missing_source assert_nothing_raised { - javascript_include_tag('missing_security_guard') + javascript_include_tag("missing_security_guard") } assert_nothing_raised { - javascript_include_tag('http://example.com/css/missing_security_guard') + javascript_include_tag("http://example.com/css/missing_security_guard") } end @@ -376,13 +376,13 @@ class AssetTagHelperTest < ActionView::TestCase def test_javascript_include_tag_relative_protocol @controller.config.asset_host = "assets.example.com" - assert_dom_equal %(<script src="//assets.example.com/javascripts/prototype.js"></script>), javascript_include_tag('prototype', protocol: :relative) + assert_dom_equal %(<script src="//assets.example.com/javascripts/prototype.js"></script>), javascript_include_tag("prototype", protocol: :relative) end def test_javascript_include_tag_default_protocol @controller.config.asset_host = "assets.example.com" @controller.config.default_asset_host_protocol = :relative - assert_dom_equal %(<script src="//assets.example.com/javascripts/prototype.js"></script>), javascript_include_tag('prototype') + assert_dom_equal %(<script src="//assets.example.com/javascripts/prototype.js"></script>), javascript_include_tag("prototype") end def test_stylesheet_path @@ -407,36 +407,36 @@ class AssetTagHelperTest < ActionView::TestCase def test_stylesheet_link_tag_with_missing_source assert_nothing_raised { - stylesheet_link_tag('missing_security_guard') + stylesheet_link_tag("missing_security_guard") } assert_nothing_raised { - stylesheet_link_tag('http://example.com/css/missing_security_guard') + stylesheet_link_tag("http://example.com/css/missing_security_guard") } end def test_stylesheet_link_tag_is_html_safe - assert stylesheet_link_tag('dir/file').html_safe? - assert stylesheet_link_tag('dir/other/file', 'dir/file2').html_safe? + assert stylesheet_link_tag("dir/file").html_safe? + assert stylesheet_link_tag("dir/other/file", "dir/file2").html_safe? end def test_stylesheet_link_tag_escapes_options - assert_dom_equal %(<link href="/file.css" media="<script>" rel="stylesheet" />), stylesheet_link_tag('/file', :media => '<script>') + assert_dom_equal %(<link href="/file.css" media="<script>" rel="stylesheet" />), stylesheet_link_tag("/file", media: "<script>") end def test_stylesheet_link_tag_should_not_output_the_same_asset_twice - assert_dom_equal %(<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" />), stylesheet_link_tag('wellington', 'wellington', 'amsterdam') + assert_dom_equal %(<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" />), stylesheet_link_tag("wellington", "wellington", "amsterdam") end def test_stylesheet_link_tag_with_relative_protocol @controller.config.asset_host = "assets.example.com" - assert_dom_equal %(<link href="//assets.example.com/stylesheets/wellington.css" media="screen" rel="stylesheet" />), stylesheet_link_tag('wellington', protocol: :relative) + assert_dom_equal %(<link href="//assets.example.com/stylesheets/wellington.css" media="screen" rel="stylesheet" />), stylesheet_link_tag("wellington", protocol: :relative) end def test_stylesheet_link_tag_with_default_protocol @controller.config.asset_host = "assets.example.com" @controller.config.default_asset_host_protocol = :relative - assert_dom_equal %(<link href="//assets.example.com/stylesheets/wellington.css" media="screen" rel="stylesheet" />), stylesheet_link_tag('wellington') + assert_dom_equal %(<link href="//assets.example.com/stylesheets/wellington.css" media="screen" rel="stylesheet" />), stylesheet_link_tag("wellington") end def test_image_path @@ -456,12 +456,12 @@ class AssetTagHelperTest < ActionView::TestCase end def test_image_alt - [nil, '/', '/foo/bar/', 'foo/bar/'].each do |prefix| - assert_equal 'Rails', image_alt("#{prefix}rails.png") - assert_equal 'Rails', image_alt("#{prefix}rails-9c0a079bdd7701d7e729bd956823d153.png") - assert_equal 'Rails', image_alt("#{prefix}rails-f56ef62bc41b040664e801a38f068082a75d506d9048307e8096737463503d0b.png") - assert_equal 'Long file name with hyphens', image_alt("#{prefix}long-file-name-with-hyphens.png") - assert_equal 'Long file name with underscores', image_alt("#{prefix}long_file_name_with_underscores.png") + [nil, "/", "/foo/bar/", "foo/bar/"].each do |prefix| + assert_equal "Rails", image_alt("#{prefix}rails.png") + assert_equal "Rails", image_alt("#{prefix}rails-9c0a079bdd7701d7e729bd956823d153.png") + assert_equal "Rails", image_alt("#{prefix}rails-f56ef62bc41b040664e801a38f068082a75d506d9048307e8096737463503d0b.png") + assert_equal "Long file name with hyphens", image_alt("#{prefix}long-file-name-with-hyphens.png") + assert_equal "Long file name with underscores", image_alt("#{prefix}long_file_name_with_underscores.png") end end @@ -470,14 +470,14 @@ class AssetTagHelperTest < ActionView::TestCase end def test_image_tag_does_not_modify_options - options = {:size => '16x10'} - image_tag('icon', options) - assert_equal({:size => '16x10'}, options) + options = { size: "16x10" } + image_tag("icon", options) + assert_equal({ size: "16x10" }, options) end def test_image_tag_raises_an_error_for_competing_size_arguments exception = assert_raise(ArgumentError) do - image_tag("gold.png", :height => "100", :width => "200", :size => "45x70") + image_tag("gold.png", height: "100", width: "200", size: "45x70") end assert_equal("Cannot pass a :size option with a :height or :width option", exception.message) @@ -532,11 +532,11 @@ class AssetTagHelperTest < ActionView::TestCase end def test_video_audio_tag_does_not_modify_options - options = {:autoplay => true} - video_tag('video', options) - assert_equal({:autoplay => true}, options) - audio_tag('audio', options) - assert_equal({:autoplay => true}, options) + options = { autoplay: true } + video_tag("video", options) + assert_equal({ autoplay: true }, options) + audio_tag("audio", options) + assert_equal({ autoplay: true }, options) end def test_image_tag_interpreting_email_cid_correctly @@ -545,11 +545,11 @@ class AssetTagHelperTest < ActionView::TestCase end def test_image_tag_interpreting_email_adding_optional_alt_tag - assert_equal '<img alt="Image" src="cid:thi%25%25sis@acontentid" />', image_tag("cid:thi%25%25sis@acontentid", :alt => "Image") + assert_equal '<img alt="Image" src="cid:thi%25%25sis@acontentid" />', image_tag("cid:thi%25%25sis@acontentid", alt: "Image") end def test_should_not_modify_source_string - source = '/images/rails.png' + source = "/images/rails.png" copy = source.dup image_tag(source) assert_equal copy, source @@ -557,13 +557,13 @@ class AssetTagHelperTest < ActionView::TestCase class PlaceholderImage def blank?; true; end - def to_s; 'no-image-yet.png'; end + def to_s; "no-image-yet.png"; end end def test_image_tag_with_blank_placeholder assert_equal '<img alt="" src="/images/no-image-yet.png" />', image_tag(PlaceholderImage.new, alt: "") end def test_image_path_with_blank_placeholder - assert_equal '/images/no-image-yet.png', image_path(PlaceholderImage.new) + assert_equal "/images/no-image-yet.png", image_path(PlaceholderImage.new) end def test_image_path_with_asset_host_proc_returning_nil @@ -634,11 +634,11 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase end def test_should_current_request_host_is_always_returned_for_request - assert_equal "gopher://www.example.com", compute_asset_host("foo", :protocol => :request) + assert_equal "gopher://www.example.com", compute_asset_host("foo", protocol: :request) end def test_should_return_custom_host_if_passed_in_options - assert_equal "http://custom.example.com", compute_asset_host("foo", :host => "http://custom.example.com") + assert_equal "http://custom.example.com", compute_asset_host("foo", host: "http://custom.example.com") end def test_should_ignore_relative_root_path_on_complete_url @@ -652,12 +652,12 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase def test_should_return_relative_asset_host @controller.config.asset_host = "assets.example.com" - assert_equal "//assets.example.com", compute_asset_host("foo", :protocol => :relative) + assert_equal "//assets.example.com", compute_asset_host("foo", protocol: :relative) end def test_should_return_custom_protocol_asset_host @controller.config.asset_host = "assets.example.com" - assert_equal "ftp://assets.example.com", compute_asset_host("foo", :protocol => "ftp") + assert_equal "ftp://assets.example.com", compute_asset_host("foo", protocol: "ftp") end def test_should_compute_proper_path_with_asset_host @@ -708,26 +708,26 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase end def test_should_wildcard_asset_host - @controller.config.asset_host = 'http://a%d.example.com' + @controller.config.asset_host = "http://a%d.example.com" assert_match(%r(http://a[0123].example.com), compute_asset_host("foo")) end def test_should_wildcard_asset_host_between_zero_and_four - @controller.config.asset_host = 'http://a%d.example.com' - assert_match(%r(http://a[0123].example.com/collaboration/hieraki/images/xml.png), image_path('xml.png')) - assert_match(%r(http://a[0123].example.com/collaboration/hieraki/images/xml.png), image_url('xml.png')) + @controller.config.asset_host = "http://a%d.example.com" + assert_match(%r(http://a[0123].example.com/collaboration/hieraki/images/xml.png), image_path("xml.png")) + assert_match(%r(http://a[0123].example.com/collaboration/hieraki/images/xml.png), image_url("xml.png")) end def test_asset_host_without_protocol_should_be_protocol_relative - @controller.config.asset_host = 'a.example.com' - assert_equal 'gopher://a.example.com/collaboration/hieraki/images/xml.png', image_path('xml.png') - assert_equal 'gopher://a.example.com/collaboration/hieraki/images/xml.png', image_url('xml.png') + @controller.config.asset_host = "a.example.com" + assert_equal "gopher://a.example.com/collaboration/hieraki/images/xml.png", image_path("xml.png") + assert_equal "gopher://a.example.com/collaboration/hieraki/images/xml.png", image_url("xml.png") end def test_asset_host_without_protocol_should_be_protocol_relative_even_if_path_present - @controller.config.asset_host = 'a.example.com/files/go/here' - assert_equal 'gopher://a.example.com/files/go/here/collaboration/hieraki/images/xml.png', image_path('xml.png') - assert_equal 'gopher://a.example.com/files/go/here/collaboration/hieraki/images/xml.png', image_url('xml.png') + @controller.config.asset_host = "a.example.com/files/go/here" + assert_equal "gopher://a.example.com/files/go/here/collaboration/hieraki/images/xml.png", image_path("xml.png") + assert_equal "gopher://a.example.com/files/go/here/collaboration/hieraki/images/xml.png", image_url("xml.png") end def test_assert_css_and_js_of_the_same_name_return_correct_extension @@ -747,10 +747,10 @@ class AssetUrlHelperControllerTest < ActionView::TestCase @request = Class.new do attr_accessor :script_name - def protocol() 'http://' end + def protocol() "http://" end def ssl?() false end - def host_with_port() 'www.example.com' end - def base_url() 'http://www.example.com' end + def host_with_port() "www.example.com" end + def base_url() "http://www.example.com" end end.new @controller.request = @request @@ -813,6 +813,6 @@ class AssetUrlHelperEmptyModuleTest < ActionView::TestCase end assert @module.config.asset_host - assert_equal "http://custom.example.com/foo", @module.asset_url("foo", :host => "http://custom.example.com") + assert_equal "http://custom.example.com/foo", @module.asset_url("foo", host: "http://custom.example.com") end end diff --git a/actionview/test/template/atom_feed_helper_test.rb b/actionview/test/template/atom_feed_helper_test.rb index 591cd71404..e9a923dd72 100644 --- a/actionview/test/template/atom_feed_helper_test.rb +++ b/actionview/test/template/atom_feed_helper_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class Scroll < Struct.new(:id, :to_param, :title, :body, :updated_at, :created_at) extend ActiveModel::Naming @@ -28,7 +28,7 @@ class ScrollsController < ActionController::Base end end EOT - FEEDS["entry_options"] = <<-EOT + FEEDS["entry_options"] = <<-EOT atom_feed do |feed| feed.title("My great blog!") feed.updated(@scrolls.first.created_at) @@ -45,7 +45,7 @@ class ScrollsController < ActionController::Base end end EOT - FEEDS["entry_type_options"] = <<-EOT + FEEDS["entry_type_options"] = <<-EOT atom_feed(:schema_date => '2008') do |feed| feed.title("My great blog!") feed.updated(@scrolls.first.created_at) @@ -62,7 +62,7 @@ class ScrollsController < ActionController::Base end end EOT - FEEDS["entry_url_false_option"] = <<-EOT + FEEDS["entry_url_false_option"] = <<-EOT atom_feed do |feed| feed.title("My great blog!") feed.updated(@scrolls.first.created_at) @@ -79,7 +79,7 @@ class ScrollsController < ActionController::Base end end EOT - FEEDS["xml_block"] = <<-EOT + FEEDS["xml_block"] = <<-EOT atom_feed do |feed| feed.title("My great blog!") feed.updated(@scrolls.first.created_at) @@ -96,7 +96,7 @@ class ScrollsController < ActionController::Base end end EOT - FEEDS["feed_with_atomPub_namespace"] = <<-EOT + FEEDS["feed_with_atomPub_namespace"] = <<-EOT atom_feed({'xmlns:app' => 'http://www.w3.org/2007/app', 'xmlns:openSearch' => 'http://a9.com/-/spec/opensearch/1.1/'}) do |feed| feed.title("My great blog!") @@ -115,7 +115,7 @@ class ScrollsController < ActionController::Base end end EOT - FEEDS["feed_with_overridden_ids"] = <<-EOT + FEEDS["feed_with_overridden_ids"] = <<-EOT atom_feed({:id => 'tag:test.rubyonrails.org,2008:test/'}) do |feed| feed.title("My great blog!") feed.updated(@scrolls.first.created_at) @@ -169,7 +169,7 @@ class ScrollsController < ActionController::Base end end EOT - FEEDS["feed_with_xhtml_content"] = <<-'EOT' + FEEDS["feed_with_xhtml_content"] = <<-'EOT' atom_feed do |feed| feed.title("My great blog!") feed.updated(@scrolls.first.created_at) @@ -191,7 +191,7 @@ class ScrollsController < ActionController::Base end end EOT - FEEDS["provide_builder"] = <<-'EOT' + FEEDS["provide_builder"] = <<-'EOT' # we pass in the new_xml to the helper so it doesn't # call anything on the original builder new_xml = Builder::XmlMarkup.new(:target=>'') @@ -217,7 +217,7 @@ class ScrollsController < ActionController::Base Scroll.new(2, "2", "Hello Two", "Something Boring", Time.utc(2007, 12, 12, 15)), ] - render :inline => FEEDS[params[:id]], :type => :builder + render inline: FEEDS[params[:id]], type: :builder end end @@ -278,22 +278,22 @@ class AtomFeedTest < ActionController::TestCase def test_feed_id_should_be_a_valid_tag with_restful_routing(:scrolls) do get :index, params: { id: "defaults" } - assert_select "id", :text => "tag:www.nextangle.com,2008:/scrolls?id=defaults" + assert_select "id", text: "tag:www.nextangle.com,2008:/scrolls?id=defaults" end end def test_entry_id_should_be_a_valid_tag with_restful_routing(:scrolls) do get :index, params: { id: "defaults" } - assert_select "entry id", :text => "tag:www.nextangle.com,2008:Scroll/1" - assert_select "entry id", :text => "tag:www.nextangle.com,2008:Scroll/2" + assert_select "entry id", text: "tag:www.nextangle.com,2008:Scroll/1" + assert_select "entry id", text: "tag:www.nextangle.com,2008:Scroll/2" end end def test_feed_should_allow_nested_xml_blocks with_restful_routing(:scrolls) do get :index, params: { id: "xml_block" } - assert_select "author name", :text => "DHH" + assert_select "author name", text: "DHH" end end @@ -309,15 +309,15 @@ class AtomFeedTest < ActionController::TestCase def test_feed_should_allow_overriding_ids with_restful_routing(:scrolls) do get :index, params: { id: "feed_with_overridden_ids" } - assert_select "id", :text => "tag:test.rubyonrails.org,2008:test/" - assert_select "entry id", :text => "tag:test.rubyonrails.org,2008:1" - assert_select "entry id", :text => "tag:test.rubyonrails.org,2008:2" + assert_select "id", text: "tag:test.rubyonrails.org,2008:test/" + assert_select "entry id", text: "tag:test.rubyonrails.org,2008:1" + assert_select "entry id", text: "tag:test.rubyonrails.org,2008:2" end end def test_feed_xml_processing_instructions with_restful_routing(:scrolls) do - get :index, params: { id: 'feed_with_xml_processing_instructions' } + get :index, params: { id: "feed_with_xml_processing_instructions" } assert_match %r{<\?xml-stylesheet [^\?]*type="text/css"}, @response.body assert_match %r{<\?xml-stylesheet [^\?]*href="t.css"}, @response.body end @@ -325,7 +325,7 @@ class AtomFeedTest < ActionController::TestCase def test_feed_xml_processing_instructions_duplicate_targets with_restful_routing(:scrolls) do - get :index, params: { id: 'feed_with_xml_processing_instructions_duplicate_targets' } + get :index, params: { id: "feed_with_xml_processing_instructions_duplicate_targets" } assert_match %r{<\?target1 (a="1" b="2"|b="2" a="1")\?>}, @response.body assert_match %r{<\?target1 (c="3" d="4"|d="4" c="3")\?>}, @response.body end @@ -335,28 +335,28 @@ class AtomFeedTest < ActionController::TestCase with_restful_routing(:scrolls) do get :index, params: { id: "feed_with_xhtml_content" } assert_match %r{xmlns="http://www.w3.org/1999/xhtml"}, @response.body - assert_select "summary", :text => /Something Boring/ - assert_select "summary", :text => /after 2/ + assert_select "summary", text: /Something Boring/ + assert_select "summary", text: /after 2/ end end def test_feed_entry_type_option_default_to_text_html with_restful_routing(:scrolls) do - get :index, params: { id: 'defaults' } + get :index, params: { id: "defaults" } assert_select "entry link[rel=alternate][type=\"text/html\"]" end end def test_feed_entry_type_option_specified with_restful_routing(:scrolls) do - get :index, params: { id: 'entry_type_options' } + get :index, params: { id: "entry_type_options" } assert_select "entry link[rel=alternate][type=\"text/xml\"]" end end def test_feed_entry_url_false_option_adds_no_link with_restful_routing(:scrolls) do - get :index, params: { id: 'entry_url_false_option' } + get :index, params: { id: "entry_url_false_option" } assert_select "entry link", false end end diff --git a/actionview/test/template/capture_helper_test.rb b/actionview/test/template/capture_helper_test.rb index ffaf773c53..54bf9b4c33 100644 --- a/actionview/test/template/capture_helper_test.rb +++ b/actionview/test/template/capture_helper_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class CaptureHelperTest < ActionView::TestCase def setup @@ -10,18 +10,18 @@ class CaptureHelperTest < ActionView::TestCase def test_capture_captures_the_temporary_output_buffer_in_its_block assert_nil @av.output_buffer string = @av.capture do - @av.output_buffer << 'foo' - @av.output_buffer << 'bar' + @av.output_buffer << "foo" + @av.output_buffer << "bar" end assert_nil @av.output_buffer - assert_equal 'foobar', string + assert_equal "foobar", string end def test_capture_captures_the_value_returned_by_the_block_if_the_temporary_buffer_is_blank - string = @av.capture('foo', 'bar') do |a, b| + string = @av.capture("foo", "bar") do |a, b| a + b end - assert_equal 'foobar', string + assert_equal "foobar", string end def test_capture_returns_nil_if_the_returned_value_is_not_a_string @@ -29,112 +29,112 @@ class CaptureHelperTest < ActionView::TestCase end def test_capture_escapes_html - string = @av.capture { '<em>bar</em>' } - assert_equal '<em>bar</em>', string + string = @av.capture { "<em>bar</em>" } + assert_equal "<em>bar</em>", string end def test_capture_doesnt_escape_twice - string = @av.capture { raw('<em>bar</em>') } - assert_equal '<em>bar</em>', string + string = @av.capture { raw("<em>bar</em>") } + assert_equal "<em>bar</em>", string end def test_capture_used_for_read content_for :foo, "foo" assert_equal "foo", content_for(:foo) - content_for(:bar){ "bar" } + content_for(:bar) { "bar" } assert_equal "bar", content_for(:bar) end def test_content_for_with_multiple_calls assert ! content_for?(:title) - content_for :title, 'foo' - content_for :title, 'bar' - assert_equal 'foobar', content_for(:title) + content_for :title, "foo" + content_for :title, "bar" + assert_equal "foobar", content_for(:title) end def test_content_for_with_multiple_calls_and_flush assert ! content_for?(:title) - content_for :title, 'foo' - content_for :title, 'bar', flush: true - assert_equal 'bar', content_for(:title) + content_for :title, "foo" + content_for :title, "bar", flush: true + assert_equal "bar", content_for(:title) end def test_content_for_with_block assert ! content_for?(:title) content_for :title do - output_buffer << 'foo' - output_buffer << 'bar' + output_buffer << "foo" + output_buffer << "bar" nil end - assert_equal 'foobar', content_for(:title) + assert_equal "foobar", content_for(:title) end def test_content_for_with_block_and_multiple_calls_with_flush assert ! content_for?(:title) content_for :title do - 'foo' + "foo" end content_for :title, flush: true do - 'bar' + "bar" end - assert_equal 'bar', content_for(:title) + assert_equal "bar", content_for(:title) end def test_content_for_with_block_and_multiple_calls_with_flush_nil_content assert ! content_for?(:title) content_for :title do - 'foo' + "foo" end content_for :title, nil, flush: true do - 'bar' + "bar" end - assert_equal 'bar', content_for(:title) + assert_equal "bar", content_for(:title) end def test_content_for_with_block_and_multiple_calls_without_flush assert ! content_for?(:title) content_for :title do - 'foo' + "foo" end content_for :title, flush: false do - 'bar' + "bar" end - assert_equal 'foobar', content_for(:title) + assert_equal "foobar", content_for(:title) end def test_content_for_with_whitespace_block assert ! content_for?(:title) - content_for :title, 'foo' + content_for :title, "foo" content_for :title do output_buffer << " \n " nil end - content_for :title, 'bar' - assert_equal 'foobar', content_for(:title) + content_for :title, "bar" + assert_equal "foobar", content_for(:title) end def test_content_for_with_whitespace_block_and_flush assert ! content_for?(:title) - content_for :title, 'foo' + content_for :title, "foo" content_for :title, flush: true do output_buffer << " \n " nil end - content_for :title, 'bar', flush: true - assert_equal 'bar', content_for(:title) + content_for :title, "bar", flush: true + assert_equal "bar", content_for(:title) end 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, "foo") + assert_equal nil, content_for(:title) { output_buffer << "bar"; nil } assert_equal 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 "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_equal 'bar', content_for(:title) + assert_equal "bar", content_for(:title) end def test_content_for_returns_nil_when_content_missing @@ -143,7 +143,7 @@ class CaptureHelperTest < ActionView::TestCase def test_content_for_question_mark assert ! content_for?(:title) - content_for :title, 'title' + content_for :title, "title" assert content_for?(:title) assert ! content_for?(:something_else) end @@ -151,12 +151,12 @@ class CaptureHelperTest < ActionView::TestCase def test_content_for_should_be_html_safe_after_flush_empty assert ! content_for?(:title) content_for :title do - content_tag(:p, 'title') + content_tag(:p, "title") end assert content_for(:title).html_safe? content_for :title, "", flush: true content_for(:title) do - content_tag(:p, 'title') + content_tag(:p, "title") end assert content_for(:title).html_safe? end @@ -178,19 +178,19 @@ class CaptureHelperTest < ActionView::TestCase def test_with_output_buffer_swaps_the_output_buffer_given_no_argument assert_nil @av.output_buffer buffer = @av.with_output_buffer do - @av.output_buffer << '.' + @av.output_buffer << "." end - assert_equal '.', buffer + assert_equal ".", buffer assert_nil @av.output_buffer end def test_with_output_buffer_swaps_the_output_buffer_with_an_argument assert_nil @av.output_buffer - buffer = ActionView::OutputBuffer.new('.') + buffer = ActionView::OutputBuffer.new(".") @av.with_output_buffer(buffer) do - @av.output_buffer << '.' + @av.output_buffer << "." end - assert_equal '..', buffer + assert_equal "..", buffer assert_nil @av.output_buffer end @@ -198,7 +198,7 @@ class CaptureHelperTest < ActionView::TestCase buffer = ActionView::OutputBuffer.new @av.output_buffer = buffer @av.with_output_buffer do - @av.output_buffer << '.' + @av.output_buffer << "." end assert buffer.equal?(@av.output_buffer) end diff --git a/actionview/test/template/compiled_templates_test.rb b/actionview/test/template/compiled_templates_test.rb index f6c1283b92..3ecac46d34 100644 --- a/actionview/test/template/compiled_templates_test.rb +++ b/actionview/test/template/compiled_templates_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class CompiledTemplatesTest < ActiveSupport::TestCase teardown do @@ -6,28 +6,47 @@ class CompiledTemplatesTest < ActiveSupport::TestCase end def test_template_with_nil_erb_return - assert_equal "This is nil: \n", render(:template => "test/nil_return") + assert_equal "This is nil: \n", render(template: "test/nil_return") + end + + def test_template_with_ruby_keyword_locals + assert_equal "The class is foo", + render(file: "test/render_file_with_ruby_keyword_locals", locals: { class: "foo" }) + end + + def test_template_with_invalid_identifier_locals + locals = { + foo: "bar", + Foo: "bar", + "d-a-s-h-e-s": "", + "white space": "", + } + assert_equal locals.inspect, render(file: "test/render_file_inspect_local_assigns", locals: locals) + end + + def test_template_with_unicode_identifier + assert_equal "🎂", render(file: "test/render_file_unicode_local", locals: { 🎃: "🎂" }) 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" }) + 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" }) end def test_template_changes_are_not_reflected_with_cached_templates - assert_equal "Hello world!", render(:file => "test/hello_world") + assert_equal "Hello world!", render(file: "test/hello_world") modify_template "test/hello_world.erb", "Goodbye world!" do - assert_equal "Hello world!", render(:file => "test/hello_world") + assert_equal "Hello world!", render(file: "test/hello_world") end - assert_equal "Hello world!", render(:file => "test/hello_world") + assert_equal "Hello world!", render(file: "test/hello_world") end def test_template_changes_are_reflected_with_uncached_templates - assert_equal "Hello world!", render_without_cache(:file => "test/hello_world") + assert_equal "Hello world!", render_without_cache(file: "test/hello_world") modify_template "test/hello_world.erb", "Goodbye world!" do - assert_equal "Goodbye world!", render_without_cache(:file => "test/hello_world") + assert_equal "Goodbye world!", render_without_cache(file: "test/hello_world") end - assert_equal "Hello world!", render_without_cache(:file => "test/hello_world") + assert_equal "Hello world!", render_without_cache(file: "test/hello_world") end private diff --git a/actionview/test/template/controller_helper_test.rb b/actionview/test/template/controller_helper_test.rb index b5e94ea4f1..8dd0cedb75 100644 --- a/actionview/test/template/controller_helper_test.rb +++ b/actionview/test/template/controller_helper_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class ControllerHelperTest < ActionView::TestCase tests ActionView::Helpers::ControllerHelper @@ -9,13 +9,13 @@ class ControllerHelperTest < ActionView::TestCase @controller = OpenStruct.new(default_form_builder: SpecializedFormBuilder) assign_controller(@controller) - assert_equal SpecializedFormBuilder, self.default_form_builder + assert_equal SpecializedFormBuilder, default_form_builder end def test_assign_controller_skips_default_form_builder @controller = OpenStruct.new assign_controller(@controller) - assert_nil self.default_form_builder + assert_nil default_form_builder end end diff --git a/actionview/test/template/date_helper_i18n_test.rb b/actionview/test/template/date_helper_i18n_test.rb index 52aef56a61..207c8a683e 100644 --- a/actionview/test/template/date_helper_i18n_test.rb +++ b/actionview/test/template/date_helper_i18n_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class DateHelperDistanceOfTimeInWordsI18nTests < ActiveSupport::TestCase include ActionView::Helpers::DateHelper @@ -12,24 +12,24 @@ class DateHelperDistanceOfTimeInWordsI18nTests < ActiveSupport::TestCase def test_distance_of_time_in_words_calls_i18n { # with include_seconds - [2.seconds, { :include_seconds => true }] => [:'less_than_x_seconds', 5], - [9.seconds, { :include_seconds => true }] => [:'less_than_x_seconds', 10], - [19.seconds, { :include_seconds => true }] => [:'less_than_x_seconds', 20], - [30.seconds, { :include_seconds => true }] => [:'half_a_minute', nil], - [59.seconds, { :include_seconds => true }] => [:'less_than_x_minutes', 1], - [60.seconds, { :include_seconds => true }] => [:'x_minutes', 1], + [2.seconds, { include_seconds: true }] => [:'less_than_x_seconds', 5], + [9.seconds, { include_seconds: true }] => [:'less_than_x_seconds', 10], + [19.seconds, { include_seconds: true }] => [:'less_than_x_seconds', 20], + [30.seconds, { include_seconds: true }] => [:'half_a_minute', nil], + [59.seconds, { include_seconds: true }] => [:'less_than_x_minutes', 1], + [60.seconds, { include_seconds: true }] => [:'x_minutes', 1], # without include_seconds - [29.seconds, { :include_seconds => false }] => [:'less_than_x_minutes', 1], - [60.seconds, { :include_seconds => false }] => [:'x_minutes', 1], - [44.minutes, { :include_seconds => false }] => [:'x_minutes', 44], - [61.minutes, { :include_seconds => false }] => [:'about_x_hours', 1], - [24.hours, { :include_seconds => false }] => [:'x_days', 1], - [30.days, { :include_seconds => false }] => [:'about_x_months', 1], - [60.days, { :include_seconds => false }] => [:'x_months', 2], - [1.year, { :include_seconds => false }] => [:'about_x_years', 1], - [3.years + 6.months, { :include_seconds => false }] => [:'over_x_years', 3], - [3.years + 10.months, { :include_seconds => false }] => [:'almost_x_years', 4] + [29.seconds, { include_seconds: false }] => [:'less_than_x_minutes', 1], + [60.seconds, { include_seconds: false }] => [:'x_minutes', 1], + [44.minutes, { include_seconds: false }] => [:'x_minutes', 44], + [61.minutes, { include_seconds: false }] => [:'about_x_hours', 1], + [24.hours, { include_seconds: false }] => [:'x_days', 1], + [30.days, { include_seconds: false }] => [:'about_x_months', 1], + [60.days, { include_seconds: false }] => [:'x_months', 2], + [1.year, { include_seconds: false }] => [:'about_x_years', 1], + [3.years + 6.months, { include_seconds: false }] => [:'over_x_years', 3], + [3.years + 10.months, { include_seconds: false }] => [:'almost_x_years', 4] }.each do |passed, expected| assert_distance_of_time_in_words_translates_key passed, expected @@ -46,30 +46,30 @@ class DateHelperDistanceOfTimeInWordsI18nTests < ActiveSupport::TestCase end def test_time_ago_in_words_passes_locale - assert_called_with(I18n, :t, [:less_than_x_minutes, :scope => :'datetime.distance_in_words', :count => 1, :locale => 'ru']) do - time_ago_in_words(15.seconds.ago, :locale => 'ru') + assert_called_with(I18n, :t, [:less_than_x_minutes, scope: :'datetime.distance_in_words', count: 1, locale: "ru"]) do + time_ago_in_words(15.seconds.ago, locale: "ru") end end def test_distance_of_time_pluralizations - { [:'less_than_x_seconds', 1] => 'less than 1 second', - [:'less_than_x_seconds', 2] => 'less than 2 seconds', - [:'less_than_x_minutes', 1] => 'less than a minute', - [:'less_than_x_minutes', 2] => 'less than 2 minutes', - [:'x_minutes', 1] => '1 minute', - [:'x_minutes', 2] => '2 minutes', - [:'about_x_hours', 1] => 'about 1 hour', - [:'about_x_hours', 2] => 'about 2 hours', - [:'x_days', 1] => '1 day', - [:'x_days', 2] => '2 days', - [:'about_x_years', 1] => 'about 1 year', - [:'about_x_years', 2] => 'about 2 years', - [:'over_x_years', 1] => 'over 1 year', - [:'over_x_years', 2] => 'over 2 years' + { [:'less_than_x_seconds', 1] => "less than 1 second", + [:'less_than_x_seconds', 2] => "less than 2 seconds", + [:'less_than_x_minutes', 1] => "less than a minute", + [:'less_than_x_minutes', 2] => "less than 2 minutes", + [:'x_minutes', 1] => "1 minute", + [:'x_minutes', 2] => "2 minutes", + [:'about_x_hours', 1] => "about 1 hour", + [:'about_x_hours', 2] => "about 2 hours", + [:'x_days', 1] => "1 day", + [:'x_days', 2] => "2 days", + [:'about_x_years', 1] => "about 1 year", + [:'about_x_years', 2] => "about 2 years", + [:'over_x_years', 1] => "over 1 year", + [:'over_x_years', 2] => "over 2 years" }.each do |args, expected| key, count = *args - assert_equal expected, I18n.t(key, :count => count, :scope => 'datetime.distance_in_words') + assert_equal expected, I18n.t(key, count: count, scope: "datetime.distance_in_words") end end @@ -78,11 +78,11 @@ class DateHelperDistanceOfTimeInWordsI18nTests < ActiveSupport::TestCase key, count = *expected to = @from + diff - options = { locale: 'en', scope: :'datetime.distance_in_words' }.merge!(expected_options) + options = { locale: "en", scope: :'datetime.distance_in_words' }.merge!(expected_options) options[:count] = count if count assert_called_with(I18n, :t, [key, options]) do - distance_of_time_in_words(@from, to, passed_options.merge(locale: 'en')) + distance_of_time_in_words(@from, to, passed_options.merge(locale: "en")) end end end @@ -95,28 +95,28 @@ class DateHelperSelectTagsI18nTests < ActiveSupport::TestCase def test_select_month_given_use_month_names_option_does_not_translate_monthnames assert_not_called(I18n, :translate) do - select_month(8, :locale => 'en', :use_month_names => Date::MONTHNAMES) + select_month(8, locale: "en", use_month_names: Date::MONTHNAMES) end end def test_select_month_translates_monthnames - assert_called_with(I18n, :translate, [:'date.month_names', :locale => 'en'], returns: Date::MONTHNAMES) do - select_month(8, :locale => 'en') + assert_called_with(I18n, :translate, [:'date.month_names', locale: "en"], returns: Date::MONTHNAMES) do + select_month(8, locale: "en") end end def test_select_month_given_use_short_month_option_translates_abbr_monthnames - assert_called_with(I18n, :translate, [:'date.abbr_month_names', :locale => 'en'], returns: Date::ABBR_MONTHNAMES) do - select_month(8, :locale => 'en', :use_short_month => true) + assert_called_with(I18n, :translate, [:'date.abbr_month_names', locale: "en"], returns: Date::ABBR_MONTHNAMES) do + select_month(8, locale: "en", use_short_month: true) end end def test_date_or_time_select_translates_prompts - prompt_defaults = {:year => 'Year', :month => 'Month', :day => 'Day', :hour => 'Hour', :minute => 'Minute', :second => 'Seconds'} - defaults = {[:'date.order', :locale => 'en', :default => []] => %w(year month day)} + prompt_defaults = { year: "Year", month: "Month", day: "Day", hour: "Hour", minute: "Minute", second: "Seconds" } + defaults = { [:'date.order', locale: "en", default: []] => %w(year month day) } prompt_defaults.each do |key, prompt| - defaults[[('datetime.prompts.' + key.to_s).to_sym, :locale => 'en']] = prompt + defaults[[("datetime.prompts." + key.to_s).to_sym, locale: "en"]] = prompt end prompts_check = -> (prompt, x) do @@ -129,7 +129,7 @@ class DateHelperSelectTagsI18nTests < ActiveSupport::TestCase end I18n.stub(:translate, prompts_check) do - datetime_select('post', 'updated_at', :locale => 'en', :include_seconds => true, :prompt => true, :use_month_names => Date::MONTHNAMES) + datetime_select("post", "updated_at", locale: "en", include_seconds: true, prompt: true, use_month_names: Date::MONTHNAMES) end assert_equal defaults.count, @prompt_called end @@ -138,27 +138,27 @@ class DateHelperSelectTagsI18nTests < ActiveSupport::TestCase def test_date_or_time_select_given_an_order_options_does_not_translate_order assert_not_called(I18n, :translate) do - datetime_select('post', 'updated_at', :order => [:year, :month, :day], :locale => 'en', :use_month_names => Date::MONTHNAMES) + datetime_select("post", "updated_at", order: [:year, :month, :day], locale: "en", use_month_names: Date::MONTHNAMES) end end def test_date_or_time_select_given_no_order_options_translates_order - assert_called_with(I18n, :translate, [ [:'date.order', :locale => 'en', :default => []], [:"date.month_names", {:locale=>"en"}] ], returns: %w(year month day)) do - datetime_select('post', 'updated_at', :locale => 'en') + assert_called_with(I18n, :translate, [ [:'date.order', locale: "en", default: []], [:"date.month_names", { locale: "en" }] ], returns: %w(year month day)) do + datetime_select("post", "updated_at", locale: "en") end end def test_date_or_time_select_given_invalid_order - assert_called_with(I18n, :translate, [:'date.order', :locale => 'en', :default => []], returns: %w(invalid month day)) do + assert_called_with(I18n, :translate, [:'date.order', locale: "en", default: []], returns: %w(invalid month day)) do assert_raise StandardError do - datetime_select('post', 'updated_at', :locale => 'en') + datetime_select("post", "updated_at", locale: "en") end end end def test_date_or_time_select_given_symbol_keys - assert_called_with(I18n, :translate, [ [:'date.order', :locale => 'en', :default => []], [:"date.month_names", {:locale=>"en"}] ], returns: [:year, :month, :day]) do - datetime_select('post', 'updated_at', :locale => 'en') + assert_called_with(I18n, :translate, [ [:'date.order', locale: "en", default: []], [:"date.month_names", { locale: "en" }] ], returns: [:year, :month, :day]) do + datetime_select("post", "updated_at", locale: "en") end end end diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb index 4678998bdc..44e5a8c346 100644 --- a/actionview/test/template/date_helper_test.rb +++ b/actionview/test/template/date_helper_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class DateHelperTest < ActionView::TestCase tests ActionView::Helpers::DateHelper @@ -13,7 +13,7 @@ class DateHelperTest < ActionView::TestCase 123 end def to_param - '123' + "123" end end end @@ -22,32 +22,32 @@ class DateHelperTest < ActionView::TestCase to ||= from # 0..1 minute with :include_seconds => true - assert_equal "less than 5 seconds", distance_of_time_in_words(from, to + 0.seconds, :include_seconds => true) - assert_equal "less than 5 seconds", distance_of_time_in_words(from, to + 4.seconds, :include_seconds => true) - assert_equal "less than 10 seconds", distance_of_time_in_words(from, to + 5.seconds, :include_seconds => true) - assert_equal "less than 10 seconds", distance_of_time_in_words(from, to + 9.seconds, :include_seconds => true) - assert_equal "less than 20 seconds", distance_of_time_in_words(from, to + 10.seconds, :include_seconds => true) - assert_equal "less than 20 seconds", distance_of_time_in_words(from, to + 19.seconds, :include_seconds => true) - assert_equal "half a minute", distance_of_time_in_words(from, to + 20.seconds, :include_seconds => true) - assert_equal "half a minute", distance_of_time_in_words(from, to + 39.seconds, :include_seconds => true) - assert_equal "less than a minute", distance_of_time_in_words(from, to + 40.seconds, :include_seconds => true) - assert_equal "less than a minute", distance_of_time_in_words(from, to + 59.seconds, :include_seconds => true) - assert_equal "1 minute", distance_of_time_in_words(from, to + 60.seconds, :include_seconds => true) - assert_equal "1 minute", distance_of_time_in_words(from, to + 89.seconds, :include_seconds => true) + assert_equal "less than 5 seconds", distance_of_time_in_words(from, to + 0.seconds, include_seconds: true) + assert_equal "less than 5 seconds", distance_of_time_in_words(from, to + 4.seconds, include_seconds: true) + assert_equal "less than 10 seconds", distance_of_time_in_words(from, to + 5.seconds, include_seconds: true) + assert_equal "less than 10 seconds", distance_of_time_in_words(from, to + 9.seconds, include_seconds: true) + assert_equal "less than 20 seconds", distance_of_time_in_words(from, to + 10.seconds, include_seconds: true) + assert_equal "less than 20 seconds", distance_of_time_in_words(from, to + 19.seconds, include_seconds: true) + assert_equal "half a minute", distance_of_time_in_words(from, to + 20.seconds, include_seconds: true) + assert_equal "half a minute", distance_of_time_in_words(from, to + 39.seconds, include_seconds: true) + assert_equal "less than a minute", distance_of_time_in_words(from, to + 40.seconds, include_seconds: true) + assert_equal "less than a minute", distance_of_time_in_words(from, to + 59.seconds, include_seconds: true) + assert_equal "1 minute", distance_of_time_in_words(from, to + 60.seconds, include_seconds: true) + assert_equal "1 minute", distance_of_time_in_words(from, to + 89.seconds, include_seconds: true) # 0..1 minute with :include_seconds => false - assert_equal "less than a minute", distance_of_time_in_words(from, to + 0.seconds, :include_seconds => false) - assert_equal "less than a minute", distance_of_time_in_words(from, to + 4.seconds, :include_seconds => false) - assert_equal "less than a minute", distance_of_time_in_words(from, to + 5.seconds, :include_seconds => false) - assert_equal "less than a minute", distance_of_time_in_words(from, to + 9.seconds, :include_seconds => false) - assert_equal "less than a minute", distance_of_time_in_words(from, to + 10.seconds, :include_seconds => false) - assert_equal "less than a minute", distance_of_time_in_words(from, to + 19.seconds, :include_seconds => false) - assert_equal "less than a minute", distance_of_time_in_words(from, to + 20.seconds, :include_seconds => false) - assert_equal "1 minute", distance_of_time_in_words(from, to + 39.seconds, :include_seconds => false) - assert_equal "1 minute", distance_of_time_in_words(from, to + 40.seconds, :include_seconds => false) - assert_equal "1 minute", distance_of_time_in_words(from, to + 59.seconds, :include_seconds => false) - assert_equal "1 minute", distance_of_time_in_words(from, to + 60.seconds, :include_seconds => false) - assert_equal "1 minute", distance_of_time_in_words(from, to + 89.seconds, :include_seconds => false) + assert_equal "less than a minute", distance_of_time_in_words(from, to + 0.seconds, include_seconds: false) + assert_equal "less than a minute", distance_of_time_in_words(from, to + 4.seconds, include_seconds: false) + assert_equal "less than a minute", distance_of_time_in_words(from, to + 5.seconds, include_seconds: false) + assert_equal "less than a minute", distance_of_time_in_words(from, to + 9.seconds, include_seconds: false) + assert_equal "less than a minute", distance_of_time_in_words(from, to + 10.seconds, include_seconds: false) + assert_equal "less than a minute", distance_of_time_in_words(from, to + 19.seconds, include_seconds: false) + assert_equal "less than a minute", distance_of_time_in_words(from, to + 20.seconds, include_seconds: false) + assert_equal "1 minute", distance_of_time_in_words(from, to + 39.seconds, include_seconds: false) + assert_equal "1 minute", distance_of_time_in_words(from, to + 40.seconds, include_seconds: false) + assert_equal "1 minute", distance_of_time_in_words(from, to + 59.seconds, include_seconds: false) + assert_equal "1 minute", distance_of_time_in_words(from, to + 60.seconds, include_seconds: false) + assert_equal "1 minute", distance_of_time_in_words(from, to + 89.seconds, include_seconds: false) # Note that we are including a 30-second boundary around the interval we # want to test. For instance, "1 minute" is actually 30s to 1m29s. The @@ -119,8 +119,8 @@ class DateHelperTest < ActionView::TestCase # test to < from assert_equal "about 4 hours", distance_of_time_in_words(from + 4.hours, to) - assert_equal "less than 20 seconds", distance_of_time_in_words(from + 19.seconds, to, :include_seconds => true) - assert_equal "less than a minute", distance_of_time_in_words(from + 19.seconds, to, :include_seconds => false) + assert_equal "less than 20 seconds", distance_of_time_in_words(from + 19.seconds, to, include_seconds: true) + assert_equal "less than a minute", distance_of_time_in_words(from + 19.seconds, to, include_seconds: false) end def test_distance_in_words @@ -136,21 +136,21 @@ class DateHelperTest < ActionView::TestCase end def test_time_ago_in_words_passes_include_seconds - assert_equal "less than 20 seconds", time_ago_in_words(15.seconds.ago, :include_seconds => true) - assert_equal "less than a minute", time_ago_in_words(15.seconds.ago, :include_seconds => false) + assert_equal "less than 20 seconds", time_ago_in_words(15.seconds.ago, include_seconds: true) + assert_equal "less than a minute", time_ago_in_words(15.seconds.ago, include_seconds: false) end def test_distance_in_words_with_time_zones from = Time.mktime(2004, 6, 6, 21, 45, 0) - assert_distance_of_time_in_words(from.in_time_zone('Alaska')) - assert_distance_of_time_in_words(from.in_time_zone('Hawaii')) + assert_distance_of_time_in_words(from.in_time_zone("Alaska")) + assert_distance_of_time_in_words(from.in_time_zone("Hawaii")) end def test_distance_in_words_with_different_time_zones from = Time.mktime(2004, 6, 6, 21, 45, 0) assert_distance_of_time_in_words( - from.in_time_zone('Alaska'), - from.in_time_zone('Hawaii') + from.in_time_zone("Alaska"), + from.in_time_zone("Hawaii") ) end @@ -185,10 +185,10 @@ class DateHelperTest < ActionView::TestCase assert_equal "about 1 hour", distance_of_time_in_words(60.minutes) # include seconds - assert_equal "half a minute", distance_of_time_in_words(39.seconds, 0, :include_seconds => true) - assert_equal "less than a minute", distance_of_time_in_words(40.seconds, 0, :include_seconds => true) - assert_equal "less than a minute", distance_of_time_in_words(59.seconds, 0, :include_seconds => true) - assert_equal "1 minute", distance_of_time_in_words(60.seconds, 0, :include_seconds => true) + assert_equal "half a minute", distance_of_time_in_words(39.seconds, 0, include_seconds: true) + assert_equal "less than a minute", distance_of_time_in_words(40.seconds, 0, include_seconds: true) + assert_equal "less than a minute", distance_of_time_in_words(59.seconds, 0, include_seconds: true) + assert_equal "1 minute", distance_of_time_in_words(60.seconds, 0, include_seconds: true) end def test_time_ago_in_words @@ -209,8 +209,8 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value=""></option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_day(Time.mktime(2003, 8, 16), :include_blank => true) - assert_dom_equal expected, select_day(16, :include_blank => true) + assert_dom_equal expected, select_day(Time.mktime(2003, 8, 16), include_blank: true) + assert_dom_equal expected, select_day(16, include_blank: true) end def test_select_day_nil_with_blank @@ -218,7 +218,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value=""></option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_day(nil, :include_blank => true) + assert_dom_equal expected, select_day(nil, include_blank: true) end def test_select_day_with_two_digit_numbers @@ -226,8 +226,8 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">01</option>\n<option selected="selected" value="2">02</option>\n<option value="3">03</option>\n<option value="4">04</option>\n<option value="5">05</option>\n<option value="6">06</option>\n<option value="7">07</option>\n<option value="8">08</option>\n<option value="9">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_day(Time.mktime(2011, 8, 2), :use_two_digit_numbers => true) - assert_dom_equal expected, select_day(2, :use_two_digit_numbers => true) + assert_dom_equal expected, select_day(Time.mktime(2011, 8, 2), use_two_digit_numbers: true) + assert_dom_equal expected, select_day(2, use_two_digit_numbers: true) end def test_select_day_with_html_options @@ -235,8 +235,8 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_day(Time.mktime(2003, 8, 16), {}, :class => 'selector') - assert_dom_equal expected, select_day(16, {}, :class => 'selector') + assert_dom_equal expected, select_day(Time.mktime(2003, 8, 16), {}, class: "selector") + assert_dom_equal expected, select_day(16, {}, class: "selector") end def test_select_day_with_default_prompt @@ -244,7 +244,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="">Day</option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_day(16, :prompt => true) + assert_dom_equal expected, select_day(16, prompt: true) end def test_select_day_with_custom_prompt @@ -252,7 +252,23 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="">Choose day</option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_day(16, :prompt => 'Choose day') + assert_dom_equal expected, select_day(16, prompt: "Choose day") + end + + def test_select_day_with_generic_with_css_classes + expected = %(<select id="date_day" name="date[day]" class="day">\n) + expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_day(16, with_css_classes: true) + end + + def test_select_day_with_custom_with_css_classes + expected = %(<select id="date_day" name="date[day]" class="my-day">\n) + expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_day(16, with_css_classes: { day: "my-day" }) end def test_select_month @@ -269,8 +285,8 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">01</option>\n<option value="2">02</option>\n<option value="3">03</option>\n<option value="4">04</option>\n<option value="5">05</option>\n<option value="6">06</option>\n<option value="7">07</option>\n<option value="8" selected="selected">08</option>\n<option value="9">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_month(Time.mktime(2011, 8, 16), :use_two_digit_numbers => true) - assert_dom_equal expected, select_month(8, :use_two_digit_numbers => true) + assert_dom_equal expected, select_month(Time.mktime(2011, 8, 16), use_two_digit_numbers: true) + assert_dom_equal expected, select_month(8, use_two_digit_numbers: true) end def test_select_month_with_disabled @@ -278,8 +294,8 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), :disabled => true) - assert_dom_equal expected, select_month(8, :disabled => true) + assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), disabled: true) + assert_dom_equal expected, select_month(8, disabled: true) end def test_select_month_with_field_name_override @@ -287,8 +303,8 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), :field_name => 'mois') - assert_dom_equal expected, select_month(8, :field_name => 'mois') + assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), field_name: "mois") + assert_dom_equal expected, select_month(8, field_name: "mois") end def test_select_month_with_blank @@ -296,8 +312,8 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value=""></option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), :include_blank => true) - assert_dom_equal expected, select_month(8, :include_blank => true) + assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), include_blank: true) + assert_dom_equal expected, select_month(8, include_blank: true) end def test_select_month_nil_with_blank @@ -305,7 +321,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value=""></option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_month(nil, :include_blank => true) + assert_dom_equal expected, select_month(nil, include_blank: true) end def test_select_month_with_numbers @@ -313,8 +329,8 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8" selected="selected">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), :use_month_numbers => true) - assert_dom_equal expected, select_month(8, :use_month_numbers => true) + assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), use_month_numbers: true) + assert_dom_equal expected, select_month(8, use_month_numbers: true) end def test_select_month_with_numbers_and_names @@ -322,8 +338,8 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">1 - January</option>\n<option value="2">2 - February</option>\n<option value="3">3 - March</option>\n<option value="4">4 - April</option>\n<option value="5">5 - May</option>\n<option value="6">6 - June</option>\n<option value="7">7 - July</option>\n<option value="8" selected="selected">8 - August</option>\n<option value="9">9 - September</option>\n<option value="10">10 - October</option>\n<option value="11">11 - November</option>\n<option value="12">12 - December</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), :add_month_numbers => true) - assert_dom_equal expected, select_month(8, :add_month_numbers => true) + assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), add_month_numbers: true) + assert_dom_equal expected, select_month(8, add_month_numbers: true) end def test_select_month_with_format_string @@ -331,9 +347,9 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">January (01)</option>\n<option value="2">February (02)</option>\n<option value="3">March (03)</option>\n<option value="4">April (04)</option>\n<option value="5">May (05)</option>\n<option value="6">June (06)</option>\n<option value="7">July (07)</option>\n<option value="8" selected="selected">August (08)</option>\n<option value="9">September (09)</option>\n<option value="10">October (10)</option>\n<option value="11">November (11)</option>\n<option value="12">December (12)</option>\n) expected << "</select>\n" - format_string = '%{name} (%<number>02d)' - assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), :month_format_string => format_string) - assert_dom_equal expected, select_month(8, :month_format_string => format_string) + format_string = "%{name} (%<number>02d)" + assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), month_format_string: format_string) + assert_dom_equal expected, select_month(8, month_format_string: format_string) end def test_select_month_with_numbers_and_names_with_abbv @@ -341,8 +357,8 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">1 - Jan</option>\n<option value="2">2 - Feb</option>\n<option value="3">3 - Mar</option>\n<option value="4">4 - Apr</option>\n<option value="5">5 - May</option>\n<option value="6">6 - Jun</option>\n<option value="7">7 - Jul</option>\n<option value="8" selected="selected">8 - Aug</option>\n<option value="9">9 - Sep</option>\n<option value="10">10 - Oct</option>\n<option value="11">11 - Nov</option>\n<option value="12">12 - Dec</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), :add_month_numbers => true, :use_short_month => true) - assert_dom_equal expected, select_month(8, :add_month_numbers => true, :use_short_month => true) + assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), add_month_numbers: true, use_short_month: true) + assert_dom_equal expected, select_month(8, add_month_numbers: true, use_short_month: true) end def test_select_month_with_abbv @@ -350,8 +366,8 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">Jan</option>\n<option value="2">Feb</option>\n<option value="3">Mar</option>\n<option value="4">Apr</option>\n<option value="5">May</option>\n<option value="6">Jun</option>\n<option value="7">Jul</option>\n<option value="8" selected="selected">Aug</option>\n<option value="9">Sep</option>\n<option value="10">Oct</option>\n<option value="11">Nov</option>\n<option value="12">Dec</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), :use_short_month => true) - assert_dom_equal expected, select_month(8, :use_short_month => true) + assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), use_short_month: true) + assert_dom_equal expected, select_month(8, use_short_month: true) end def test_select_month_with_custom_names @@ -361,8 +377,8 @@ class DateHelperTest < ActionView::TestCase 1.upto(12) { |month| expected << %(<option value="#{month}"#{' selected="selected"' if month == 8}>#{month_names[month]}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), :use_month_names => month_names) - assert_dom_equal expected, select_month(8, :use_month_names => month_names) + assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), use_month_names: month_names) + assert_dom_equal expected, select_month(8, use_month_names: month_names) end def test_select_month_with_zero_indexed_custom_names @@ -372,16 +388,16 @@ class DateHelperTest < ActionView::TestCase 1.upto(12) { |month| expected << %(<option value="#{month}"#{' selected="selected"' if month == 8}>#{month_names[month-1]}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), :use_month_names => month_names) - assert_dom_equal expected, select_month(8, :use_month_names => month_names) + assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), use_month_names: month_names) + assert_dom_equal expected, select_month(8, use_month_names: month_names) end def test_select_month_with_hidden - assert_dom_equal "<input type=\"hidden\" id=\"date_month\" name=\"date[month]\" value=\"8\" />\n", select_month(8, :use_hidden => true) + assert_dom_equal "<input type=\"hidden\" id=\"date_month\" name=\"date[month]\" value=\"8\" />\n", select_month(8, use_hidden: true) end def test_select_month_with_hidden_and_field_name - assert_dom_equal "<input type=\"hidden\" id=\"date_mois\" name=\"date[mois]\" value=\"8\" />\n", select_month(8, :use_hidden => true, :field_name => 'mois') + assert_dom_equal "<input type=\"hidden\" id=\"date_mois\" name=\"date[mois]\" value=\"8\" />\n", select_month(8, use_hidden: true, field_name: "mois") end def test_select_month_with_html_options @@ -389,7 +405,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), {}, :class => 'selector', :accesskey => 'M') + assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), {}, class: "selector", accesskey: "M") end def test_select_month_with_default_prompt @@ -397,7 +413,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="">Month</option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_month(8, :prompt => true) + assert_dom_equal expected, select_month(8, prompt: true) end def test_select_month_with_custom_prompt @@ -405,7 +421,23 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="">Choose month</option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_month(8, :prompt => 'Choose month') + assert_dom_equal expected, select_month(8, prompt: "Choose month") + end + + def test_select_month_with_generic_with_css_classes + expected = %(<select id="date_month" name="date[month]" class="month">\n) + expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_month(8, with_css_classes: true) + end + + def test_select_month_with_custom_with_css_classes + expected = %(<select id="date_month" name="date[month]" class="my-month">\n) + expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_month(8, with_css_classes: { month: "my-month" }) end def test_select_year @@ -413,8 +445,8 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_year(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005) - assert_dom_equal expected, select_year(2003, :start_year => 2003, :end_year => 2005) + assert_dom_equal expected, select_year(Time.mktime(2003, 8, 16), start_year: 2003, end_year: 2005) + assert_dom_equal expected, select_year(2003, start_year: 2003, end_year: 2005) end def test_select_year_with_disabled @@ -422,8 +454,8 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_year(Time.mktime(2003, 8, 16), :disabled => true, :start_year => 2003, :end_year => 2005) - assert_dom_equal expected, select_year(2003, :disabled => true, :start_year => 2003, :end_year => 2005) + assert_dom_equal expected, select_year(Time.mktime(2003, 8, 16), disabled: true, start_year: 2003, end_year: 2005) + assert_dom_equal expected, select_year(2003, disabled: true, start_year: 2003, end_year: 2005) end def test_select_year_with_field_name_override @@ -431,8 +463,8 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_year(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :field_name => 'annee') - assert_dom_equal expected, select_year(2003, :start_year => 2003, :end_year => 2005, :field_name => 'annee') + assert_dom_equal expected, select_year(Time.mktime(2003, 8, 16), start_year: 2003, end_year: 2005, field_name: "annee") + assert_dom_equal expected, select_year(2003, start_year: 2003, end_year: 2005, field_name: "annee") end def test_select_year_with_type_discarding @@ -441,9 +473,9 @@ class DateHelperTest < ActionView::TestCase expected << "</select>\n" assert_dom_equal expected, select_year( - Time.mktime(2003, 8, 16), :prefix => "date_year", :discard_type => true, :start_year => 2003, :end_year => 2005) + Time.mktime(2003, 8, 16), prefix: "date_year", discard_type: true, start_year: 2003, end_year: 2005) assert_dom_equal expected, select_year( - 2003, :prefix => "date_year", :discard_type => true, :start_year => 2003, :end_year => 2005) + 2003, prefix: "date_year", discard_type: true, start_year: 2003, end_year: 2005) end def test_select_year_descending @@ -451,16 +483,16 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="2005" selected="selected">2005</option>\n<option value="2004">2004</option>\n<option value="2003">2003</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_year(Time.mktime(2005, 8, 16), :start_year => 2005, :end_year => 2003) - assert_dom_equal expected, select_year(2005, :start_year => 2005, :end_year => 2003) + assert_dom_equal expected, select_year(Time.mktime(2005, 8, 16), start_year: 2005, end_year: 2003) + assert_dom_equal expected, select_year(2005, start_year: 2005, end_year: 2003) end def test_select_year_with_hidden - assert_dom_equal "<input type=\"hidden\" id=\"date_year\" name=\"date[year]\" value=\"2007\" />\n", select_year(2007, :use_hidden => true) + assert_dom_equal "<input type=\"hidden\" id=\"date_year\" name=\"date[year]\" value=\"2007\" />\n", select_year(2007, use_hidden: true) end def test_select_year_with_hidden_and_field_name - assert_dom_equal "<input type=\"hidden\" id=\"date_anno\" name=\"date[anno]\" value=\"2007\" />\n", select_year(2007, :use_hidden => true, :field_name => 'anno') + assert_dom_equal "<input type=\"hidden\" id=\"date_anno\" name=\"date[anno]\" value=\"2007\" />\n", select_year(2007, use_hidden: true, field_name: "anno") end def test_select_year_with_html_options @@ -468,7 +500,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_year(Time.mktime(2003, 8, 16), {:start_year => 2003, :end_year => 2005}, :class => 'selector', :accesskey => 'M') + assert_dom_equal expected, select_year(Time.mktime(2003, 8, 16), { start_year: 2003, end_year: 2005 }, class: "selector", accesskey: "M") end def test_select_year_with_default_prompt @@ -476,7 +508,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="">Year</option>\n<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_year(nil, :start_year => 2003, :end_year => 2005, :prompt => true) + assert_dom_equal expected, select_year(nil, start_year: 2003, end_year: 2005, prompt: true) end def test_select_year_with_custom_prompt @@ -484,7 +516,30 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="">Choose year</option>\n<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_year(nil, :start_year => 2003, :end_year => 2005, :prompt => 'Choose year') + assert_dom_equal expected, select_year(nil, start_year: 2003, end_year: 2005, prompt: "Choose year") + end + + def test_select_year_with_generic_with_css_classes + expected = %(<select id="date_year" name="date[year]" class="year">\n) + expected << %(<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_year(nil, start_year: 2003, end_year: 2005, with_css_classes: true) + end + + def test_select_year_with_custom_with_css_classes + expected = %(<select id="date_year" name="date[year]" class="my-year">\n) + expected << %(<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_year(nil, start_year: 2003, end_year: 2005, with_css_classes: { year: "my-year" }) + end + + def test_select_year_with_position + expected = %(<select id="date_year_1i" name="date[year(1i)]">\n) + expected << %(<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) + expected << "</select>\n" + assert_dom_equal expected, select_year(Date.current, include_position: true, start_year: 2003, end_year: 2005) end def test_select_hour @@ -500,7 +555,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="00">12 AM</option>\n<option value="01">01 AM</option>\n<option value="02">02 AM</option>\n<option value="03">03 AM</option>\n<option value="04">04 AM</option>\n<option value="05">05 AM</option>\n<option value="06">06 AM</option>\n<option value="07">07 AM</option>\n<option value="08" selected="selected">08 AM</option>\n<option value="09">09 AM</option>\n<option value="10">10 AM</option>\n<option value="11">11 AM</option>\n<option value="12">12 PM</option>\n<option value="13">01 PM</option>\n<option value="14">02 PM</option>\n<option value="15">03 PM</option>\n<option value="16">04 PM</option>\n<option value="17">05 PM</option>\n<option value="18">06 PM</option>\n<option value="19">07 PM</option>\n<option value="20">08 PM</option>\n<option value="21">09 PM</option>\n<option value="22">10 PM</option>\n<option value="23">11 PM</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :ampm => true) + assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), ampm: true) end def test_select_hour_with_disabled @@ -508,7 +563,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :disabled => true) + assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), disabled: true) end def test_select_hour_with_field_name_override @@ -516,7 +571,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :field_name => 'heure') + assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), field_name: "heure") end def test_select_hour_with_blank @@ -524,7 +579,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :include_blank => true) + assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), include_blank: true) end def test_select_hour_nil_with_blank @@ -532,7 +587,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_hour(nil, :include_blank => true) + assert_dom_equal expected, select_hour(nil, include_blank: true) end def test_select_hour_with_html_options @@ -540,7 +595,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), {}, :class => 'selector', :accesskey => 'M') + assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), {}, class: "selector", accesskey: "M") end def test_select_hour_with_default_prompt @@ -548,7 +603,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="">Hour</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => true) + assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), prompt: true) end def test_select_hour_with_custom_prompt @@ -556,7 +611,23 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="">Choose hour</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => 'Choose hour') + assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), prompt: "Choose hour") + end + + def test_select_hour_with_generic_with_css_classes + expected = %(<select id="date_hour" name="date[hour]" class="hour">\n) + expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), with_css_classes: true) + end + + def test_select_hour_with_custom_with_css_classes + expected = %(<select id="date_hour" name="date[hour]" class="my-hour">\n) + expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), with_css_classes: { hour: "my-hour" }) end def test_select_minute @@ -572,7 +643,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), :disabled => true) + assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), disabled: true) end def test_select_minute_with_field_name_override @@ -580,7 +651,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), :field_name => 'minuto') + assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), field_name: "minuto") end def test_select_minute_with_blank @@ -588,7 +659,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), :include_blank => true) + assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), include_blank: true) end def test_select_minute_with_blank_and_step @@ -596,7 +667,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="15">15</option>\n<option value="30">30</option>\n<option value="45">45</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), { :include_blank => true , :minute_step => 15 }) + assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), include_blank: true , minute_step: 15) end def test_select_minute_nil_with_blank @@ -604,7 +675,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_minute(nil, :include_blank => true) + assert_dom_equal expected, select_minute(nil, include_blank: true) end def test_select_minute_nil_with_blank_and_step @@ -612,15 +683,15 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="15">15</option>\n<option value="30">30</option>\n<option value="45">45</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_minute(nil, { :include_blank => true , :minute_step => 15 }) + assert_dom_equal expected, select_minute(nil, include_blank: true , minute_step: 15) end def test_select_minute_with_hidden - assert_dom_equal "<input type=\"hidden\" id=\"date_minute\" name=\"date[minute]\" value=\"8\" />\n", select_minute(8, :use_hidden => true) + assert_dom_equal "<input type=\"hidden\" id=\"date_minute\" name=\"date[minute]\" value=\"8\" />\n", select_minute(8, use_hidden: true) end def test_select_minute_with_hidden_and_field_name - assert_dom_equal "<input type=\"hidden\" id=\"date_minuto\" name=\"date[minuto]\" value=\"8\" />\n", select_minute(8, :use_hidden => true, :field_name => 'minuto') + assert_dom_equal "<input type=\"hidden\" id=\"date_minuto\" name=\"date[minuto]\" value=\"8\" />\n", select_minute(8, use_hidden: true, field_name: "minuto") end def test_select_minute_with_html_options @@ -628,7 +699,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), {}, :class => 'selector', :accesskey => 'M') + assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), {}, class: "selector", accesskey: "M") end def test_select_minute_with_default_prompt @@ -636,7 +707,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="">Minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => true) + assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), prompt: true) end def test_select_minute_with_custom_prompt @@ -644,7 +715,23 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="">Choose minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => 'Choose minute') + assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), prompt: "Choose minute") + end + + def test_select_minute_with_generic_with_css_classes + expected = %(<select id="date_minute" name="date[minute]" class="minute">\n) + expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), with_css_classes: true) + end + + def test_select_minute_with_custom_with_css_classes + expected = %(<select id="date_minute" name="date[minute]" class="my-minute">\n) + expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), with_css_classes: { minute: "my-minute" }) end def test_select_second @@ -660,7 +747,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), :disabled => true) + assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), disabled: true) end def test_select_second_with_field_name_override @@ -668,7 +755,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), :field_name => 'segundo') + assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), field_name: "segundo") end def test_select_second_with_blank @@ -676,7 +763,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), :include_blank => true) + assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), include_blank: true) end def test_select_second_nil_with_blank @@ -684,7 +771,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_second(nil, :include_blank => true) + assert_dom_equal expected, select_second(nil, include_blank: true) end def test_select_second_with_html_options @@ -692,7 +779,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), {}, :class => 'selector', :accesskey => 'M') + assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), {}, class: "selector", accesskey: "M") end def test_select_second_with_default_prompt @@ -700,7 +787,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="">Seconds</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => true) + assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), prompt: true) end def test_select_second_with_custom_prompt @@ -708,7 +795,23 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="">Choose seconds</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => 'Choose seconds') + assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), prompt: "Choose seconds") + end + + def test_select_second_with_generic_with_css_classes + expected = %(<select id="date_second" name="date[second]" class="second">\n) + expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), with_css_classes: true) + end + + def test_select_second_with_custom_with_css_classes + expected = %(<select id="date_second" name="date[second]" class="my-second">\n) + expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), with_css_classes: { second: "my-second" }) end def test_select_date @@ -724,16 +827,16 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :prefix => "date[first]") + assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), start_year: 2003, end_year: 2005, prefix: "date[first]") end def test_select_date_with_too_big_range_between_start_year_and_end_year - assert_raise(ArgumentError) { select_date(Time.mktime(2003, 8, 16), :start_year => 2000, :end_year => 20000, :prefix => "date[first]", :order => [:month, :day, :year]) } - assert_raise(ArgumentError) { select_date(Time.mktime(2003, 8, 16), :start_year => Date.today.year - 100.years, :end_year => 2000, :prefix => "date[first]", :order => [:month, :day, :year]) } + assert_raise(ArgumentError) { select_date(Time.mktime(2003, 8, 16), start_year: 2000, end_year: 20000, prefix: "date[first]", order: [:month, :day, :year]) } + assert_raise(ArgumentError) { select_date(Time.mktime(2003, 8, 16), start_year: Date.today.year - 100.years, end_year: 2000, prefix: "date[first]", order: [:month, :day, :year]) } end def test_select_date_can_have_more_then_1000_years_interval_if_forced_via_parameter - assert_nothing_raised { select_date(Time.mktime(2003, 8, 16), :start_year => 2000, :end_year => 3100, :max_years_allowed => 2000) } + assert_nothing_raised { select_date(Time.mktime(2003, 8, 16), start_year: 2000, end_year: 3100, max_years_allowed: 2000) } end def test_select_date_with_order @@ -749,7 +852,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :prefix => "date[first]", :order => [:month, :day, :year]) + assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), start_year: 2003, end_year: 2005, prefix: "date[first]", order: [:month, :day, :year]) end def test_select_date_with_incomplete_order @@ -758,7 +861,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<input id="date_first_month" name="date[first][month]" type="hidden" value="8" />\n) expected << %(<input id="date_first_day" name="date[first][day]" type="hidden" value="1" />\n) - assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :prefix => "date[first]", :order => [:day]) + assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), start_year: 2003, end_year: 2005, prefix: "date[first]", order: [:day]) end def test_select_date_with_disabled @@ -774,7 +877,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :prefix => "date[first]", :disabled => true) + assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), start_year: 2003, end_year: 2005, prefix: "date[first]", disabled: true) end def test_select_date_with_no_start_year @@ -797,7 +900,7 @@ class DateHelperTest < ActionView::TestCase expected << "</select>\n" assert_dom_equal expected, select_date( - Time.mktime(Date.today.year, 8, 16), :end_year => Date.today.year+1, :prefix => "date[first]" + Time.mktime(Date.today.year, 8, 16), end_year: Date.today.year+1, prefix: "date[first]" ) end @@ -821,7 +924,7 @@ class DateHelperTest < ActionView::TestCase expected << "</select>\n" assert_dom_equal expected, select_date( - Time.mktime(2003, 8, 16), :start_year => 2003, :prefix => "date[first]" + Time.mktime(2003, 8, 16), start_year: 2003, prefix: "date[first]" ) end @@ -845,7 +948,7 @@ class DateHelperTest < ActionView::TestCase expected << "</select>\n" assert_dom_equal expected, select_date( - Time.mktime(Date.today.year, 8, 16), :prefix => "date[first]" + Time.mktime(Date.today.year, 8, 16), prefix: "date[first]" ) end @@ -862,7 +965,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_date(0, :start_year => 2003, :end_year => 2005, :prefix => "date[first]") + assert_dom_equal expected, select_date(0, start_year: 2003, end_year: 2005, prefix: "date[first]") end def test_select_date_with_zero_value_and_no_start_year @@ -878,7 +981,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_date(0, :end_year => Date.today.year+1, :prefix => "date[first]") + assert_dom_equal expected, select_date(0, end_year: Date.today.year+1, prefix: "date[first]") end def test_select_date_with_zero_value_and_no_end_year @@ -895,7 +998,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_date(0, :start_year => 2003, :prefix => "date[first]") + assert_dom_equal expected, select_date(0, start_year: 2003, prefix: "date[first]") end def test_select_date_with_zero_value_and_no_start_and_end_year @@ -911,7 +1014,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_date(0, :prefix => "date[first]") + assert_dom_equal expected, select_date(0, prefix: "date[first]") end def test_select_date_with_nil_value_and_no_start_and_end_year @@ -927,7 +1030,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_date(nil, :prefix => "date[first]") + assert_dom_equal expected, select_date(nil, prefix: "date[first]") end def test_select_date_with_html_options @@ -943,7 +1046,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), {:start_year => 2003, :end_year => 2005, :prefix => "date[first]"}, :class => "selector") + assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { start_year: 2003, end_year: 2005, prefix: "date[first]" }, class: "selector") end def test_select_date_with_separator @@ -963,7 +1066,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { :date_separator => " / ", :start_year => 2003, :end_year => 2005, :prefix => "date[first]"}) + assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), date_separator: " / ", start_year: 2003, end_year: 2005, prefix: "date[first]") end def test_select_date_with_separator_and_discard_day @@ -979,7 +1082,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<input type="hidden" id="date_first_day" name="date[first][day]" value="1" />\n) - assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { :date_separator => " / ", :discard_day => true, :start_year => 2003, :end_year => 2005, :prefix => "date[first]"}) + assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), date_separator: " / ", discard_day: true, start_year: 2003, end_year: 2005, prefix: "date[first]") end def test_select_date_with_separator_discard_month_and_day @@ -990,7 +1093,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<input type="hidden" id="date_first_month" name="date[first][month]" value="8" />\n) expected << %(<input type="hidden" id="date_first_day" name="date[first][day]" value="1" />\n) - assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { :date_separator => " / ", :discard_month => true, :discard_day => true, :start_year => 2003, :end_year => 2005, :prefix => "date[first]"}) + assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), date_separator: " / ", discard_month: true, discard_day: true, start_year: 2003, end_year: 2005, prefix: "date[first]") end def test_select_date_with_hidden @@ -998,8 +1101,8 @@ class DateHelperTest < ActionView::TestCase expected << %(<input id="date_first_month" name="date[first][month]" type="hidden" value="8" />\n) expected << %(<input id="date_first_day" name="date[first][day]" type="hidden" value="16" />\n) - assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { :prefix => "date[first]", :use_hidden => true }) - assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { :date_separator => " / ", :prefix => "date[first]", :use_hidden => true }) + assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), prefix: "date[first]", use_hidden: true) + assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), date_separator: " / ", prefix: "date[first]", use_hidden: true) end def test_select_date_with_css_classes_option @@ -1015,7 +1118,23 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), {:start_year => 2003, :end_year => 2005, :prefix => "date[first]", :with_css_classes => true}) + assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), start_year: 2003, end_year: 2005, prefix: "date[first]", with_css_classes: true) + end + + def test_select_date_with_custom_with_css_classes + expected = %(<select id="date_year" name="date[year]" class="my-year">\n) + expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) + expected << "</select>\n" + + expected << %(<select id="date_month" name="date[month]" class="my-month">\n) + expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) + expected << "</select>\n" + + expected << %(<select id="date_day" name="date[day]" class="my-day">\n) + expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), start_year: 2003, end_year: 2005, with_css_classes: { year: "my-year", month: "my-month", day: "my-day" }) end def test_select_date_with_css_classes_option_and_html_class_option @@ -1031,7 +1150,55 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), {:start_year => 2003, :end_year => 2005, :prefix => "date[first]", :with_css_classes => true}, { class: 'datetime optional' }) + assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { start_year: 2003, end_year: 2005, prefix: "date[first]", with_css_classes: true }, class: "datetime optional") + end + + def test_select_date_with_custom_with_css_classes_and_html_class_option + expected = %(<select id="date_year" name="date[year]" class="date optional my-year">\n) + expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) + expected << "</select>\n" + + expected << %(<select id="date_month" name="date[month]" class="date optional my-month">\n) + expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) + expected << "</select>\n" + + expected << %(<select id="date_day" name="date[day]" class="date optional my-day">\n) + expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { start_year: 2003, end_year: 2005, with_css_classes: { year: "my-year", month: "my-month", day: "my-day" } }, class: "date optional") + end + + def test_select_date_with_partial_with_css_classes_and_html_class_option + expected = %(<select id="date_year" name="date[year]" class="date optional">\n) + expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) + expected << "</select>\n" + + expected << %(<select id="date_month" name="date[month]" class="date optional my-month custom-grid">\n) + expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) + expected << "</select>\n" + + expected << %(<select id="date_day" name="date[day]" class="date optional">\n) + expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { start_year: 2003, end_year: 2005, with_css_classes: { month: "my-month custom-grid" } }, class: "date optional") + end + + def test_select_date_with_html_class_option + expected = %(<select id="date_year" name="date[year]" class="date optional custom-grid">\n) + expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) + expected << "</select>\n" + + expected << %(<select id="date_month" name="date[month]" class="date optional custom-grid">\n) + expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) + expected << "</select>\n" + + expected << %(<select id="date_day" name="date[day]" class="date optional custom-grid">\n) + expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { start_year: 2003, end_year: 2005 }, class: "date optional custom-grid") end def test_select_datetime @@ -1059,7 +1226,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :start_year => 2003, :end_year => 2005, :prefix => "date[first]") + assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), start_year: 2003, end_year: 2005, prefix: "date[first]") end def test_select_datetime_with_ampm @@ -1087,7 +1254,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :start_year => 2003, :end_year => 2005, :prefix => "date[first]", :ampm => true) + assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), start_year: 2003, end_year: 2005, prefix: "date[first]", ampm: true) end def test_select_datetime_with_separators @@ -1115,7 +1282,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :start_year => 2003, :end_year => 2005, :prefix => "date[first]", :datetime_separator => ' — ', :time_separator => ' : ') + assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), start_year: 2003, end_year: 2005, prefix: "date[first]", datetime_separator: " — ", time_separator: " : ") end def test_select_datetime_with_nil_value_and_no_start_and_end_year @@ -1143,7 +1310,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_datetime(nil, :prefix => "date[first]") + assert_dom_equal expected, select_datetime(nil, prefix: "date[first]") end def test_select_datetime_with_html_options @@ -1151,7 +1318,6 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) expected << "</select>\n" - expected << %(<select id="date_first_month" name="date[first][month]" class="selector">\n) expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) expected << "</select>\n" @@ -1172,7 +1338,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), {:start_year => 2003, :end_year => 2005, :prefix => "date[first]"}, :class => 'selector') + assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), { start_year: 2003, end_year: 2005, prefix: "date[first]" }, class: "selector") end def test_select_datetime_with_all_separators @@ -1204,7 +1370,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), { :datetime_separator => "—", :date_separator => "/", :time_separator => ":", :start_year => 2003, :end_year => 2005, :prefix => "date[first]"}, :class => 'selector') + assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), { datetime_separator: "—", date_separator: "/", time_separator: ":", start_year: 2003, end_year: 2005, prefix: "date[first]" }, class: "selector") end def test_select_datetime_should_work_with_date @@ -1236,8 +1402,8 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="">Minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :start_year => 2003, :end_year => 2005, - :prefix => "date[first]", :prompt => true) + assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), start_year: 2003, end_year: 2005, + prefix: "date[first]", prompt: true) end def test_select_datetime_with_custom_prompt @@ -1266,8 +1432,64 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="">Choose minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :start_year => 2003, :end_year => 2005, :prefix => "date[first]", - :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year', :hour => 'Choose hour', :minute => 'Choose minute'}) + assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), start_year: 2003, end_year: 2005, prefix: "date[first]", + prompt: { day: "Choose day", month: "Choose month", year: "Choose year", hour: "Choose hour", minute: "Choose minute" }) + end + + def test_select_datetime_with_generic_with_css_classes + expected = %(<select id="date_year" name="date[year]" class="year">\n) + expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) + expected << "</select>\n" + + expected << %(<select id="date_month" name="date[month]" class="month">\n) + expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) + expected << "</select>\n" + + expected << %(<select id="date_day" name="date[day]" class="day">\n) + expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) + expected << "</select>\n" + + expected << " — " + + expected << %(<select id="date_hour" name="date[hour]" class="hour">\n) + expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) + expected << "</select>\n" + + expected << " : " + + expected << %(<select id="date_minute" name="date[minute]" class="minute">\n) + expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), start_year: 2003, end_year: 2005, with_css_classes: true) + end + + def test_select_datetime_with_custom_with_css_classes + expected = %(<select id="date_year" name="date[year]" class="my-year">\n) + expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) + expected << "</select>\n" + + expected << %(<select id="date_month" name="date[month]" class="my-month">\n) + expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) + expected << "</select>\n" + + expected << %(<select id="date_day" name="date[day]" class="my-day">\n) + expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) + expected << "</select>\n" + + expected << " — " + + expected << %(<select id="date_hour" name="date[hour]" class="my-hour">\n) + expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) + expected << "</select>\n" + + expected << " : " + + expected << %(<select id="date_minute" name="date[minute]" class="my-minute">\n) + expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), start_year: 2003, end_year: 2005, with_css_classes: { day: "my-day", month: "my-month", year: "my-year", hour: "my-hour", minute: "my-minute" }) end def test_select_datetime_with_custom_hours @@ -1295,8 +1517,8 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="">Choose minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :start_year => 2003, :end_year => 2005, :start_hour => 1, :end_hour => 9, :prefix => "date[first]", - :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year', :hour => 'Choose hour', :minute => 'Choose minute'}) + assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), start_year: 2003, end_year: 2005, start_hour: 1, end_hour: 9, prefix: "date[first]", + prompt: { day: "Choose day", month: "Choose month", year: "Choose year", hour: "Choose hour", minute: "Choose minute" }) end def test_select_datetime_with_hidden @@ -1306,9 +1528,9 @@ class DateHelperTest < ActionView::TestCase expected << %(<input id="date_first_hour" name="date[first][hour]" type="hidden" value="8" />\n) expected << %(<input id="date_first_minute" name="date[first][minute]" type="hidden" value="4" />\n) - assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :prefix => "date[first]", :use_hidden => true) - assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :datetime_separator => "—", :date_separator => "/", - :time_separator => ":", :prefix => "date[first]", :use_hidden => true) + assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), prefix: "date[first]", use_hidden: true) + assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), datetime_separator: "—", date_separator: "/", + time_separator: ":", prefix: "date[first]", use_hidden: true) end def test_select_time @@ -1327,7 +1549,7 @@ class DateHelperTest < ActionView::TestCase expected << "</select>\n" assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18)) - assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => false) + assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), include_seconds: false) end def test_select_time_with_ampm @@ -1345,7 +1567,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => false, :ampm => true) + assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), include_seconds: false, ampm: true) end def test_select_time_with_separator @@ -1362,8 +1584,8 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :time_separator => ' : ') - assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :time_separator => ' : ', :include_seconds => false) + assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), time_separator: " : ") + assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), time_separator: " : ", include_seconds: false) end def test_select_time_with_seconds @@ -1375,19 +1597,19 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) expected << "</select>\n" - expected << ' : ' + expected << " : " expected << %(<select id="date_minute" name="date[minute]">\n) expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - expected << ' : ' + expected << " : " expected << %(<select id="date_second" name="date[second]">\n) expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => true) + assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), include_seconds: true) end def test_select_time_with_seconds_and_separator @@ -1411,7 +1633,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => true, :time_separator => ' : ') + assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), include_seconds: true, time_separator: " : ") end def test_select_time_with_html_options @@ -1429,8 +1651,8 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), {}, :class => 'selector') - assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), {:include_seconds => false}, :class => 'selector') + assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), {}, class: "selector") + assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), { include_seconds: false }, class: "selector") end def test_select_time_should_work_with_date @@ -1458,7 +1680,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="">Seconds</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => true, :prompt => true) + assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), include_seconds: true, prompt: true) end def test_select_time_with_custom_prompt @@ -1482,8 +1704,56 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="">Choose seconds</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => true, - :prompt => {:hour => 'Choose hour', :minute => 'Choose minute', :second => 'Choose seconds'}) + assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), include_seconds: true, + prompt: { hour: "Choose hour", minute: "Choose minute", second: "Choose seconds" }) + end + + def test_select_time_with_generic_with_css_classes + expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n) + expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" />\n) + expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" />\n) + + expected << %(<select id="date_hour" name="date[hour]" class="hour">\n) + expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) + expected << "</select>\n" + + expected << " : " + + expected << %(<select id="date_minute" name="date[minute]" class="minute">\n) + expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) + expected << "</select>\n" + + expected << " : " + + expected << %(<select id="date_second" name="date[second]" class="second">\n) + expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), include_seconds: true, with_css_classes: true) + end + + def test_select_time_with_custom_with_css_classes + expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n) + expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" />\n) + expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" />\n) + + expected << %(<select id="date_hour" name="date[hour]" class="my-hour">\n) + expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) + expected << "</select>\n" + + expected << " : " + + expected << %(<select id="date_minute" name="date[minute]" class="my-minute">\n) + expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) + expected << "</select>\n" + + expected << " : " + + expected << %(<select id="date_second" name="date[second]" class="my-second">\n) + expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), include_seconds: true, with_css_classes: { hour: "my-hour", minute: "my-minute", second: "my-second" }) end def test_select_time_with_hidden @@ -1493,8 +1763,8 @@ class DateHelperTest < ActionView::TestCase expected << %(<input id="date_first_hour" name="date[first][hour]" type="hidden" value="8" />\n) expected << %(<input id="date_first_minute" name="date[first][minute]" type="hidden" value="4" />\n) - assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :prefix => "date[first]", :use_hidden => true) - assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :time_separator => ":", :prefix => "date[first]", :use_hidden => true) + assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), prefix: "date[first]", use_hidden: true) + assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), time_separator: ":", prefix: "date[first]", use_hidden: true) end def test_date_select @@ -1534,7 +1804,7 @@ class DateHelperTest < ActionView::TestCase expected << "</select>\n" - assert_dom_equal expected, date_select("post", "written_on", :selected => Date.new(2004, 07, 10)) + assert_dom_equal expected, date_select("post", "written_on", selected: Date.new(2004, 07, 10)) end def test_date_select_with_selected_in_hash @@ -1554,7 +1824,7 @@ class DateHelperTest < ActionView::TestCase expected << "</select>\n" - assert_dom_equal expected, date_select("post", "written_on", :selected => {day: 10, month: 07, year: 2004}) + assert_dom_equal expected, date_select("post", "written_on", selected: { day: 10, month: 07, year: 2004 }) end def test_date_select_with_selected_nil @@ -1589,7 +1859,7 @@ class DateHelperTest < ActionView::TestCase expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} expected << "</select>\n" - assert_dom_equal expected, date_select("post", "written_on", :order => [ :month, :year ]) + assert_dom_equal expected, date_select("post", "written_on", order: [ :month, :year ]) end def test_date_select_without_day_and_month @@ -1603,7 +1873,7 @@ class DateHelperTest < ActionView::TestCase expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} expected << "</select>\n" - assert_dom_equal expected, date_select("post", "written_on", :order => [ :year ]) + assert_dom_equal expected, date_select("post", "written_on", order: [ :year ]) end def test_date_select_without_day_with_separator @@ -1622,7 +1892,7 @@ class DateHelperTest < ActionView::TestCase expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} expected << "</select>\n" - assert_dom_equal expected, date_select("post", "written_on", :date_separator => '/', :order => [ :month, :year ]) + assert_dom_equal expected, date_select("post", "written_on", date_separator: "/", order: [ :month, :year ]) end def test_date_select_without_day_and_with_disabled_html_option @@ -1639,7 +1909,7 @@ class DateHelperTest < ActionView::TestCase expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} expected << "</select>\n" - assert_dom_equal expected, date_select("post", "written_on", { :order => [ :month, :year ] }, :disabled => true) + assert_dom_equal expected, date_select("post", "written_on", { order: [ :month, :year ] }, disabled: true) end def test_date_select_within_fields_for @@ -1662,7 +1932,7 @@ class DateHelperTest < ActionView::TestCase @post.written_on = Date.new(2004, 6, 15) id = 27 - output_buffer = fields_for :post, @post, :index => id do |f| + output_buffer = fields_for :post, @post, index: id do |f| concat f.date_select(:written_on) end @@ -1678,11 +1948,10 @@ class DateHelperTest < ActionView::TestCase @post.written_on = Date.new(2004, 6, 15) id = nil - output_buffer = fields_for :post, @post, :index => id do |f| + output_buffer = fields_for :post, @post, index: id do |f| concat f.date_select(:written_on) end - expected = %{<select id="post_#{id}_written_on_1i" name="post[#{id}][written_on(1i)]">\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n</select>\n} expected << %{<select id="post_#{id}_written_on_2i" name="post[#{id}][written_on(2i)]">\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option selected="selected" value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n</select>\n} expected << %{<select id="post_#{id}_written_on_3i" name="post[#{id}][written_on(3i)]">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option selected="selected" value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n</select>\n} @@ -1707,7 +1976,7 @@ class DateHelperTest < ActionView::TestCase expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} expected << "</select>\n" - assert_dom_equal expected, date_select("post", "written_on", :index => id) + assert_dom_equal expected, date_select("post", "written_on", index: id) end def test_date_select_with_auto_index @@ -1746,7 +2015,7 @@ class DateHelperTest < ActionView::TestCase 1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, date_select("post", "written_on", :order => [:day, :month, :year]) + assert_dom_equal expected, date_select("post", "written_on", order: [:day, :month, :year]) end def test_date_select_with_nil @@ -1789,7 +2058,7 @@ class DateHelperTest < ActionView::TestCase 1.upto(31) { |i| expected << %(<option value="#{i}">#{i}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, date_select("post", "written_on", :include_blank => true) + assert_dom_equal expected, date_select("post", "written_on", include_blank: true) end def test_date_select_with_nil_and_blank_and_order @@ -1809,7 +2078,7 @@ class DateHelperTest < ActionView::TestCase 1.upto(12) { |i| expected << %(<option value="#{i}">#{Date::MONTHNAMES[i]}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, date_select("post", "written_on", :order=>[:year, :month], :include_blank=>true) + assert_dom_equal expected, date_select("post", "written_on", order: [:year, :month], include_blank: true) end def test_date_select_with_nil_and_blank_and_discard_month @@ -1825,7 +2094,7 @@ class DateHelperTest < ActionView::TestCase expected << '<input name="post[written_on(2i)]" type="hidden" id="post_written_on_2i" value="1"/>' + "\n" expected << '<input name="post[written_on(3i)]" type="hidden" id="post_written_on_3i" value="1"/>' + "\n" - assert_dom_equal expected, date_select("post", "written_on", :discard_month => true, :include_blank=>true) + assert_dom_equal expected, date_select("post", "written_on", discard_month: true, include_blank: true) end def test_date_select_with_nil_and_blank_and_discard_year @@ -1843,7 +2112,7 @@ class DateHelperTest < ActionView::TestCase 1.upto(31) { |i| expected << %(<option value="#{i}">#{i}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, date_select("post", "written_on", :discard_year => true, :include_blank=>true) + assert_dom_equal expected, date_select("post", "written_on", discard_year: true, include_blank: true) end def test_date_select_cant_override_discard_hour @@ -1862,7 +2131,7 @@ class DateHelperTest < ActionView::TestCase expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} expected << "</select>\n" - assert_dom_equal expected, date_select("post", "written_on", :discard_hour => false) + assert_dom_equal expected, date_select("post", "written_on", discard_hour: false) end def test_date_select_with_html_options @@ -1882,7 +2151,7 @@ class DateHelperTest < ActionView::TestCase expected << "</select>\n" - assert_dom_equal expected, date_select("post", "written_on", {}, :class => 'selector') + assert_dom_equal expected, date_select("post", "written_on", {}, class: "selector") end def test_date_select_with_html_options_within_fields_for @@ -1890,7 +2159,7 @@ class DateHelperTest < ActionView::TestCase @post.written_on = Date.new(2004, 6, 15) output_buffer = fields_for :post, @post do |f| - concat f.date_select(:written_on, {}, :class => 'selector') + concat f.date_select(:written_on, {}, class: "selector") end expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]" class="selector">\n} @@ -1930,7 +2199,7 @@ class DateHelperTest < ActionView::TestCase expected << "</select>\n" - assert_dom_equal expected, date_select("post", "written_on", { :date_separator => " / " }) + assert_dom_equal expected, date_select("post", "written_on", date_separator: " / ") end def test_date_select_with_separator_and_order @@ -1953,7 +2222,7 @@ class DateHelperTest < ActionView::TestCase expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} expected << "</select>\n" - assert_dom_equal expected, date_select("post", "written_on", { :order => [:day, :month, :year], :date_separator => " / " }) + assert_dom_equal expected, date_select("post", "written_on", order: [:day, :month, :year], date_separator: " / ") end def test_date_select_with_separator_and_order_and_year_discarded @@ -1971,7 +2240,7 @@ class DateHelperTest < ActionView::TestCase expected << "</select>\n" expected << %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n} - assert_dom_equal expected, date_select("post", "written_on", { :order => [:day, :month, :year], :discard_year => true, :date_separator => " / " }) + assert_dom_equal expected, date_select("post", "written_on", order: [:day, :month, :year], discard_year: true, date_separator: " / ") end def test_date_select_with_default_prompt @@ -1991,7 +2260,7 @@ class DateHelperTest < ActionView::TestCase expected << "</select>\n" - assert_dom_equal expected, date_select("post", "written_on", :prompt => true) + assert_dom_equal expected, date_select("post", "written_on", prompt: true) end def test_date_select_with_custom_prompt @@ -2011,7 +2280,47 @@ class DateHelperTest < ActionView::TestCase expected << "</select>\n" - assert_dom_equal expected, date_select("post", "written_on", :prompt => {:year => 'Choose year', :month => 'Choose month', :day => 'Choose day'}) + assert_dom_equal expected, date_select("post", "written_on", prompt: { year: "Choose year", month: "Choose month", day: "Choose day" }) + end + + def test_date_select_with_generic_with_css_classes + @post = Post.new + @post.written_on = Date.new(2004, 6, 15) + + expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]" class="year">\n} + expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} + expected << "</select>\n" + + expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]" class="month">\n} + expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} + expected << "</select>\n" + + expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]" class="day">\n} + expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} + + expected << "</select>\n" + + assert_dom_equal expected, date_select("post", "written_on", with_css_classes: true) + end + + def test_date_select_with_custom_with_css_classes + @post = Post.new + @post.written_on = Date.new(2004, 6, 15) + + expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]" class="my-year">\n} + expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} + expected << "</select>\n" + + expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]" class="my-month">\n} + expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} + expected << "</select>\n" + + expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]" class="my-day">\n} + expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} + + expected << "</select>\n" + + assert_dom_equal expected, date_select("post", "written_on", with_css_classes: { year: "my-year", month: "my-month", day: "my-day" }) end def test_time_select @@ -2083,7 +2392,7 @@ class DateHelperTest < ActionView::TestCase 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, time_select("post", "written_on", :ignore_date => true) + assert_dom_equal expected, time_select("post", "written_on", ignore_date: true) end def test_time_select_with_seconds @@ -2106,7 +2415,7 @@ class DateHelperTest < ActionView::TestCase 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 35}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, time_select("post", "written_on", :include_seconds => true) + assert_dom_equal expected, time_select("post", "written_on", include_seconds: true) end def test_time_select_with_html_options @@ -2125,7 +2434,7 @@ class DateHelperTest < ActionView::TestCase 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, time_select("post", "written_on", {}, :class => 'selector') + assert_dom_equal expected, time_select("post", "written_on", {}, class: "selector") end def test_time_select_with_html_options_within_fields_for @@ -2133,7 +2442,7 @@ class DateHelperTest < ActionView::TestCase @post.written_on = Time.local(2004, 6, 15, 15, 16, 35) output_buffer = fields_for :post, @post do |f| - concat f.time_select(:written_on, {}, :class => 'selector') + concat f.time_select(:written_on, {}, class: "selector") end expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n} @@ -2175,7 +2484,7 @@ class DateHelperTest < ActionView::TestCase 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 35}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, time_select("post", "written_on", { :time_separator => " - ", :include_seconds => true }) + assert_dom_equal expected, time_select("post", "written_on", time_separator: " - ", include_seconds: true) end def test_time_select_with_default_prompt @@ -2192,11 +2501,11 @@ class DateHelperTest < ActionView::TestCase expected << "</select>\n" expected << " : " expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n) - expected << %(<option value="">Minute</option>\n) + expected << %(<option value="">Minute</option>\n) 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, time_select("post", "written_on", :prompt => true) + assert_dom_equal expected, time_select("post", "written_on", prompt: true) end def test_time_select_with_custom_prompt @@ -2213,11 +2522,53 @@ class DateHelperTest < ActionView::TestCase expected << "</select>\n" expected << " : " expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n) - expected << %(<option value="">Choose minute</option>\n) + expected << %(<option value="">Choose minute</option>\n) + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + + assert_dom_equal expected, time_select("post", "written_on", prompt: { hour: "Choose hour", minute: "Choose minute" }) + end + + def test_time_select_with_generic_with_css_classes + @post = Post.new + @post.written_on = Time.local(2004, 6, 15, 15, 16, 35) + + expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n} + expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n} + expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n} + + expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]" class="hour">\n) + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + + expected << " : " + + expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]" class="minute">\n) 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, time_select("post", "written_on", :prompt => {:hour => 'Choose hour', :minute => 'Choose minute'}) + assert_dom_equal expected, time_select("post", "written_on", with_css_classes: true) + end + + def test_time_select_with_custom_with_css_classes + @post = Post.new + @post.written_on = Time.local(2004, 6, 15, 15, 16, 35) + + expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n} + expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n} + expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n} + + expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]" class="my-hour">\n) + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + + expected << " : " + + expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]" class="my-minute">\n) + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + + assert_dom_equal expected, time_select("post", "written_on", with_css_classes: { hour: "my-hour", minute: "my-minute" }) end def test_time_select_with_disabled_html_option @@ -2236,7 +2587,7 @@ class DateHelperTest < ActionView::TestCase 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, time_select("post", "written_on", {}, :disabled => true) + assert_dom_equal expected, time_select("post", "written_on", {}, disabled: true) end def test_datetime_select @@ -2294,7 +2645,7 @@ class DateHelperTest < ActionView::TestCase expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30" selected="selected">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n} expected << "</select>\n" - assert_dom_equal expected, datetime_select("post", "updated_at", :selected => Time.local(2004, 3, 10, 12, 30)) + assert_dom_equal expected, datetime_select("post", "updated_at", selected: Time.local(2004, 3, 10, 12, 30)) end def test_datetime_select_with_selected_nil @@ -2328,7 +2679,7 @@ class DateHelperTest < ActionView::TestCase # The love zone is UTC+0 mytz = Class.new(ActiveSupport::TimeZone) { attr_accessor :now - }.create('tenderlove', 0, ActiveSupport::TimeZone.find_tzinfo('UTC')) + }.create("tenderlove", 0, ActiveSupport::TimeZone.find_tzinfo("UTC")) now = Time.mktime(2004, 6, 15, 16, 35, 0) mytz.now = now @@ -2370,7 +2721,7 @@ class DateHelperTest < ActionView::TestCase @post.updated_at = Time.local(2004, 6, 15, 16, 35) output_buffer = fields_for :post, @post do |f| - concat f.datetime_select(:updated_at, {}, :class => 'selector') + concat f.datetime_select(:updated_at, {}, class: "selector") end expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]" class="selector">\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n</select>\n} @@ -2420,7 +2771,7 @@ class DateHelperTest < ActionView::TestCase 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 35}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, datetime_select("post", "updated_at", { :date_separator => " / ", :datetime_separator => " , ", :time_separator => " - ", :include_seconds => true }) + assert_dom_equal expected, datetime_select("post", "updated_at", date_separator: " / ", datetime_separator: " , ", time_separator: " - ", include_seconds: true) end def test_datetime_select_with_integer @@ -2461,7 +2812,7 @@ class DateHelperTest < ActionView::TestCase expected << %{<option value="">Minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n} expected << "</select>\n" - assert_dom_equal expected, datetime_select("post", "updated_at", :start_year=>1999, :end_year=>2009, :prompt => true) + assert_dom_equal expected, datetime_select("post", "updated_at", start_year: 1999, end_year: 2009, prompt: true) end def test_datetime_select_with_custom_prompt @@ -2490,7 +2841,65 @@ class DateHelperTest < ActionView::TestCase expected << %{<option value="">Choose minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n} expected << "</select>\n" - assert_dom_equal expected, datetime_select("post", "updated_at", :start_year=>1999, :end_year=>2009, :prompt => {:year => 'Choose year', :month => 'Choose month', :day => 'Choose day', :hour => 'Choose hour', :minute => 'Choose minute'}) + assert_dom_equal expected, datetime_select("post", "updated_at", start_year: 1999, end_year: 2009, prompt: { year: "Choose year", month: "Choose month", day: "Choose day", hour: "Choose hour", minute: "Choose minute" }) + end + + def test_datetime_select_with_generic_with_css_classes + @post = Post.new + @post.written_on = Time.local(2004, 6, 15, 15, 16, 35) + + expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]" class="year">\n} + expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} + expected << "</select>\n" + + expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]" class="month">\n} + expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} + expected << "</select>\n" + + expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]" class="day">\n} + expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} + expected << "</select>\n" + + expected << " — " + + expected << %{<select id="post_written_on_4i" name="post[written_on(4i)]" class="hour">\n} + expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n} + expected << "</select>\n" + expected << " : " + expected << %{<select id="post_written_on_5i" name="post[written_on(5i)]" class="minute">\n} + expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n} + expected << "</select>\n" + + assert_dom_equal expected, datetime_select("post", "written_on", start_year: 1999, end_year: 2009, with_css_classes: true) + end + + def test_datetime_select_with_custom_with_css_classes + @post = Post.new + @post.written_on = Time.local(2004, 6, 15, 15, 16, 35) + + expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]" class="my-year">\n} + expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} + expected << "</select>\n" + + expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]" class="my-month">\n} + expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} + expected << "</select>\n" + + expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]" class="my-day">\n} + expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} + expected << "</select>\n" + + expected << " — " + + expected << %{<select id="post_written_on_4i" name="post[written_on(4i)]" class="my-hour">\n} + expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n} + expected << "</select>\n" + expected << " : " + expected << %{<select id="post_written_on_5i" name="post[written_on(5i)]" class="my-minute">\n} + expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n} + expected << "</select>\n" + + assert_dom_equal expected, datetime_select("post", "written_on", start_year: 1999, end_year: 2009, with_css_classes: { year: "my-year", month: "my-month", day: "my-day", hour: "my-hour", minute: "my-minute" }) end def test_date_select_with_zero_value_and_no_start_year @@ -2506,7 +2915,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_date(0, :end_year => Date.today.year+1, :prefix => "date[first]") + assert_dom_equal expected, select_date(0, end_year: Date.today.year+1, prefix: "date[first]") end def test_date_select_with_zero_value_and_no_end_year @@ -2523,7 +2932,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_date(0, :start_year => 2003, :prefix => "date[first]") + assert_dom_equal expected, select_date(0, start_year: 2003, prefix: "date[first]") end def test_date_select_with_zero_value_and_no_start_and_end_year @@ -2539,7 +2948,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_date(0, :prefix => "date[first]") + assert_dom_equal expected, select_date(0, prefix: "date[first]") end def test_date_select_with_nil_value_and_no_start_and_end_year @@ -2555,7 +2964,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_date(nil, :prefix => "date[first]") + assert_dom_equal expected, select_date(nil, prefix: "date[first]") end def test_datetime_select_with_nil_value_and_no_start_and_end_year @@ -2583,7 +2992,7 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" - assert_dom_equal expected, select_datetime(nil, :prefix => "date[first]") + assert_dom_equal expected, select_datetime(nil, prefix: "date[first]") end def test_datetime_select_with_options_index @@ -2613,7 +3022,7 @@ class DateHelperTest < ActionView::TestCase expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35" selected="selected">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n} expected << "</select>\n" - assert_dom_equal expected, datetime_select("post", "updated_at", :index => id) + assert_dom_equal expected, datetime_select("post", "updated_at", index: id) end def test_datetime_select_within_fields_for_with_options_index @@ -2621,7 +3030,7 @@ class DateHelperTest < ActionView::TestCase @post.updated_at = Time.local(2004, 6, 15, 16, 35) id = 456 - output_buffer = fields_for :post, @post, :index => id do |f| + output_buffer = fields_for :post, @post, index: id do |f| concat f.datetime_select(:updated_at) end @@ -2708,7 +3117,7 @@ class DateHelperTest < ActionView::TestCase 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 35}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, datetime_select("post", "updated_at", :include_seconds => true) + assert_dom_equal expected, datetime_select("post", "updated_at", include_seconds: true) end def test_datetime_select_discard_year @@ -2733,7 +3142,7 @@ class DateHelperTest < ActionView::TestCase 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, datetime_select("post", "updated_at", :discard_year => true) + assert_dom_equal expected, datetime_select("post", "updated_at", discard_year: true) end def test_datetime_select_discard_month @@ -2756,7 +3165,7 @@ class DateHelperTest < ActionView::TestCase 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, datetime_select("post", "updated_at", :discard_month => true) + assert_dom_equal expected, datetime_select("post", "updated_at", discard_month: true) end def test_datetime_select_discard_year_and_month @@ -2775,7 +3184,7 @@ class DateHelperTest < ActionView::TestCase 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, datetime_select("post", "updated_at", :discard_year => true, :discard_month => true) + assert_dom_equal expected, datetime_select("post", "updated_at", discard_year: true, discard_month: true) end def test_datetime_select_discard_year_and_month_with_disabled_html_option @@ -2794,7 +3203,7 @@ class DateHelperTest < ActionView::TestCase 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, datetime_select("post", "updated_at", { :discard_year => true, :discard_month => true }, :disabled => true) + assert_dom_equal expected, datetime_select("post", "updated_at", { discard_year: true, discard_month: true }, disabled: true) end def test_datetime_select_discard_hour @@ -2811,7 +3220,7 @@ class DateHelperTest < ActionView::TestCase 1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 15}>#{i}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, datetime_select("post", "updated_at", :discard_hour => true) + assert_dom_equal expected, datetime_select("post", "updated_at", discard_hour: true) end def test_datetime_select_discard_minute @@ -2835,7 +3244,7 @@ class DateHelperTest < ActionView::TestCase expected << "</select>\n" expected << %{<input type="hidden" id="post_updated_at_5i" name="post[updated_at(5i)]" value="16" />\n} - assert_dom_equal expected, datetime_select("post", "updated_at", :discard_minute => true) + assert_dom_equal expected, datetime_select("post", "updated_at", discard_minute: true) end def test_datetime_select_disabled_and_discard_minute @@ -2859,7 +3268,7 @@ class DateHelperTest < ActionView::TestCase expected << "</select>\n" expected << %{<input type="hidden" id="post_updated_at_5i" disabled="disabled" name="post[updated_at(5i)]" value="16" />\n} - assert_dom_equal expected, datetime_select("post", "updated_at", :discard_minute => true, :disabled => true) + assert_dom_equal expected, datetime_select("post", "updated_at", discard_minute: true, disabled: true) end def test_datetime_select_invalid_order @@ -2886,7 +3295,7 @@ class DateHelperTest < ActionView::TestCase 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, datetime_select("post", "updated_at", :order => [:minute, :day, :hour, :month, :year, :second]) + assert_dom_equal expected, datetime_select("post", "updated_at", order: [:minute, :day, :hour, :month, :year, :second]) end def test_datetime_select_discard_with_order @@ -2911,7 +3320,7 @@ class DateHelperTest < ActionView::TestCase 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, datetime_select("post", "updated_at", :order => [:day, :month]) + assert_dom_equal expected, datetime_select("post", "updated_at", order: [:day, :month]) end def test_datetime_select_with_default_value_as_time @@ -2938,7 +3347,7 @@ class DateHelperTest < ActionView::TestCase 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, datetime_select("post", "updated_at", :default => Time.local(2006, 9, 19, 15, 16, 35)) + assert_dom_equal expected, datetime_select("post", "updated_at", default: Time.local(2006, 9, 19, 15, 16, 35)) end def test_include_blank_overrides_default_option @@ -2958,7 +3367,7 @@ class DateHelperTest < ActionView::TestCase 1.upto(31) { |i| expected << %(<option value="#{i}">#{i}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, date_select("post", "updated_at", :default => Time.local(2006, 9, 19, 15, 16, 35), :include_blank => true) + assert_dom_equal expected, date_select("post", "updated_at", default: Time.local(2006, 9, 19, 15, 16, 35), include_blank: true) end def test_datetime_select_with_default_value_as_hash @@ -2985,7 +3394,7 @@ class DateHelperTest < ActionView::TestCase 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 42}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" - assert_dom_equal expected, datetime_select("post", "updated_at", :default => { :month => 10, :minute => 42, :hour => 9 }) + assert_dom_equal expected, datetime_select("post", "updated_at", default: { month: 10, minute: 42, hour: 9 }) end def test_datetime_select_with_html_options @@ -3014,7 +3423,7 @@ class DateHelperTest < ActionView::TestCase expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35" selected="selected">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n} expected << "</select>\n" - assert_dom_equal expected, datetime_select("post", "updated_at", {}, :class => 'selector') + assert_dom_equal expected, datetime_select("post", "updated_at", {}, class: "selector") end def test_date_select_should_not_change_passed_options_hash @@ -3022,24 +3431,24 @@ class DateHelperTest < ActionView::TestCase @post.updated_at = Time.local(2008, 7, 16, 23, 30) options = { - :order => [ :year, :month, :day ], - :default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 }, - :discard_type => false, - :include_blank => false, - :ignore_date => false, - :include_seconds => true + order: [ :year, :month, :day ], + default: { year: 2008, month: 7, day: 16, hour: 23, minute: 30, second: 1 }, + discard_type: false, + include_blank: false, + ignore_date: false, + include_seconds: true } date_select(@post, :updated_at, options) # note: the literal hash is intentional to show that the actual options hash isn't modified # don't change this! assert_equal({ - :order => [ :year, :month, :day ], - :default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 }, - :discard_type => false, - :include_blank => false, - :ignore_date => false, - :include_seconds => true + order: [ :year, :month, :day ], + default: { year: 2008, month: 7, day: 16, hour: 23, minute: 30, second: 1 }, + discard_type: false, + include_blank: false, + ignore_date: false, + include_seconds: true }, options) end @@ -3048,24 +3457,24 @@ class DateHelperTest < ActionView::TestCase @post.updated_at = Time.local(2008, 7, 16, 23, 30) options = { - :order => [ :year, :month, :day ], - :default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 }, - :discard_type => false, - :include_blank => false, - :ignore_date => false, - :include_seconds => true + order: [ :year, :month, :day ], + default: { year: 2008, month: 7, day: 16, hour: 23, minute: 30, second: 1 }, + discard_type: false, + include_blank: false, + ignore_date: false, + include_seconds: true } datetime_select(@post, :updated_at, options) # note: the literal hash is intentional to show that the actual options hash isn't modified # don't change this! assert_equal({ - :order => [ :year, :month, :day ], - :default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 }, - :discard_type => false, - :include_blank => false, - :ignore_date => false, - :include_seconds => true + order: [ :year, :month, :day ], + default: { year: 2008, month: 7, day: 16, hour: 23, minute: 30, second: 1 }, + discard_type: false, + include_blank: false, + ignore_date: false, + include_seconds: true }, options) end @@ -3074,93 +3483,93 @@ class DateHelperTest < ActionView::TestCase @post.updated_at = Time.local(2008, 7, 16, 23, 30) options = { - :order => [ :year, :month, :day ], - :default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 }, - :discard_type => false, - :include_blank => false, - :ignore_date => false, - :include_seconds => true + order: [ :year, :month, :day ], + default: { year: 2008, month: 7, day: 16, hour: 23, minute: 30, second: 1 }, + discard_type: false, + include_blank: false, + ignore_date: false, + include_seconds: true } time_select(@post, :updated_at, options) # note: the literal hash is intentional to show that the actual options hash isn't modified # don't change this! assert_equal({ - :order => [ :year, :month, :day ], - :default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 }, - :discard_type => false, - :include_blank => false, - :ignore_date => false, - :include_seconds => true + order: [ :year, :month, :day ], + default: { year: 2008, month: 7, day: 16, hour: 23, minute: 30, second: 1 }, + discard_type: false, + include_blank: false, + ignore_date: false, + include_seconds: true }, options) end def test_select_date_should_not_change_passed_options_hash options = { - :order => [ :year, :month, :day ], - :default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 }, - :discard_type => false, - :include_blank => false, - :ignore_date => false, - :include_seconds => true + order: [ :year, :month, :day ], + default: { year: 2008, month: 7, day: 16, hour: 23, minute: 30, second: 1 }, + discard_type: false, + include_blank: false, + ignore_date: false, + include_seconds: true } select_date(Date.today, options) # note: the literal hash is intentional to show that the actual options hash isn't modified # don't change this! assert_equal({ - :order => [ :year, :month, :day ], - :default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 }, - :discard_type => false, - :include_blank => false, - :ignore_date => false, - :include_seconds => true + order: [ :year, :month, :day ], + default: { year: 2008, month: 7, day: 16, hour: 23, minute: 30, second: 1 }, + discard_type: false, + include_blank: false, + ignore_date: false, + include_seconds: true }, options) end def test_select_datetime_should_not_change_passed_options_hash options = { - :order => [ :year, :month, :day ], - :default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 }, - :discard_type => false, - :include_blank => false, - :ignore_date => false, - :include_seconds => true + order: [ :year, :month, :day ], + default: { year: 2008, month: 7, day: 16, hour: 23, minute: 30, second: 1 }, + discard_type: false, + include_blank: false, + ignore_date: false, + include_seconds: true } select_datetime(Time.now, options) # note: the literal hash is intentional to show that the actual options hash isn't modified # don't change this! assert_equal({ - :order => [ :year, :month, :day ], - :default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 }, - :discard_type => false, - :include_blank => false, - :ignore_date => false, - :include_seconds => true + order: [ :year, :month, :day ], + default: { year: 2008, month: 7, day: 16, hour: 23, minute: 30, second: 1 }, + discard_type: false, + include_blank: false, + ignore_date: false, + include_seconds: true }, options) end def test_select_time_should_not_change_passed_options_hash options = { - :order => [ :year, :month, :day ], - :default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 }, - :discard_type => false, - :include_blank => false, - :ignore_date => false, - :include_seconds => true + order: [ :year, :month, :day ], + default: { year: 2008, month: 7, day: 16, hour: 23, minute: 30, second: 1 }, + discard_type: false, + include_blank: false, + ignore_date: false, + include_seconds: true } select_time(Time.now, options) # note: the literal hash is intentional to show that the actual options hash isn't modified # don't change this! assert_equal({ - :order => [ :year, :month, :day ], - :default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 }, - :discard_type => false, - :include_blank => false, - :ignore_date => false, - :include_seconds => true + order: [ :year, :month, :day ], + default: { year: 2008, month: 7, day: 16, hour: 23, minute: 30, second: 1 }, + discard_type: false, + include_blank: false, + ignore_date: false, + include_seconds: true }, options) end @@ -3171,19 +3580,19 @@ class DateHelperTest < ActionView::TestCase assert select_minute(Time.mktime(2003, 8, 16, 8, 4, 18)).html_safe? assert select_second(Time.mktime(2003, 8, 16, 8, 4, 18)).html_safe? - assert select_minute(8, :use_hidden => true).html_safe? - assert select_month(8, :prompt => 'Choose month').html_safe? + assert select_minute(8, use_hidden: true).html_safe? + assert select_month(8, prompt: "Choose month").html_safe? - assert select_time(Time.mktime(2003, 8, 16, 8, 4, 18), {}, :class => 'selector').html_safe? - assert select_date(Time.mktime(2003, 8, 16), :date_separator => " / ", :start_year => 2003, :end_year => 2005, :prefix => "date[first]").html_safe? + assert select_time(Time.mktime(2003, 8, 16, 8, 4, 18), {}, class: "selector").html_safe? + assert select_date(Time.mktime(2003, 8, 16), date_separator: " / ", start_year: 2003, end_year: 2005, prefix: "date[first]").html_safe? end def test_object_select_html_safety @post = Post.new @post.written_on = Date.new(2004, 6, 15) - assert date_select("post", "written_on", :default => Time.local(2006, 9, 19, 15, 16, 35), :include_blank => true).html_safe? - assert time_select("post", "written_on", :ignore_date => true).html_safe? + assert date_select("post", "written_on", default: Time.local(2006, 9, 19, 15, 16, 35), include_blank: true).html_safe? + assert time_select("post", "written_on", ignore_date: true).html_safe? end def test_time_tag_with_date @@ -3193,26 +3602,22 @@ class DateHelperTest < ActionView::TestCase end def test_time_tag_with_time - time = Time.new(2013, 2, 20, 0, 0, 0, '+00:00') + time = Time.new(2013, 2, 20, 0, 0, 0, "+00:00") expected = '<time datetime="2013-02-20T00:00:00+00:00">February 20, 2013 00:00</time>' assert_equal expected, time_tag(time) end - def test_time_tag_pubdate_option - assert_match(/<time.*pubdate="pubdate">.*<\/time>/, time_tag(Time.now, :pubdate => true)) - end - def test_time_tag_with_given_text - assert_match(/<time.*>Right now<\/time>/, time_tag(Time.now, 'Right now')) + assert_match(/<time.*>Right now<\/time>/, time_tag(Time.now, "Right now")) end def test_time_tag_with_given_block - assert_match(/<time.*><span>Right now<\/span><\/time>/, time_tag(Time.now){ raw('<span>Right now</span>') }) + assert_match(/<time.*><span>Right now<\/span><\/time>/, time_tag(Time.now) { raw("<span>Right now</span>") }) end def test_time_tag_with_different_format - time = Time.new(2013, 2, 20, 0, 0, 0, '+00:00') + time = Time.new(2013, 2, 20, 0, 0, 0, "+00:00") expected = '<time datetime="2013-02-20T00:00:00+00:00">20 Feb 00:00</time>' - assert_equal expected, time_tag(time, :format => :short) + assert_equal expected, time_tag(time, format: :short) end end diff --git a/actionview/test/template/dependency_tracker_test.rb b/actionview/test/template/dependency_tracker_test.rb index 3ece9e50cd..89917035ff 100644 --- a/actionview/test/template/dependency_tracker_test.rb +++ b/actionview/test/template/dependency_tracker_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'action_view/dependency_tracker' +require "abstract_unit" +require "action_view/dependency_tracker" class NeckbeardTracker def self.call(name, template) @@ -15,8 +15,8 @@ class FakeTemplate end end -Neckbeard = lambda {|template| template.source } -Bowtie = lambda {|template| template.source } +Neckbeard = lambda { |template| template.source } +Bowtie = lambda { |template| template.source } class DependencyTrackerTest < ActionView::TestCase def tracker diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb index bfab97cf1e..093ff28c14 100644 --- a/actionview/test/template/digestor_test.rb +++ b/actionview/test/template/digestor_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'fileutils' -require 'action_view/dependency_tracker' +require "abstract_unit" +require "fileutils" +require "action_view/dependency_tracker" class FixtureTemplate attr_reader :source, :handler @@ -17,7 +17,14 @@ class FixtureFinder < ActionView::LookupContext FIXTURES_DIR = "#{File.dirname(__FILE__)}/../fixtures/digestor" def initialize(details = {}) - super(ActionView::PathSet.new(['digestor']), details, []) + super(ActionView::PathSet.new(["digestor", "digestor/api"]), details, []) + @rendered_format = :html + end +end + +class ActionView::Digestor::Node + def flatten + [self] + children.flat_map(&:flatten) end end @@ -26,6 +33,7 @@ class TemplateDigestorTest < ActionView::TestCase @cwd = Dir.pwd @tmp_dir = Dir.mktmpdir + ActionView::LookupContext::DetailsKey.clear FileUtils.cp_r FixtureFinder::FIXTURES_DIR, @tmp_dir Dir.chdir @tmp_dir end @@ -33,7 +41,6 @@ class TemplateDigestorTest < ActionView::TestCase def teardown Dir.chdir @cwd FileUtils.rm_r @tmp_dir - ActionView::Digestor.cache.clear end def test_top_level_change_reflected @@ -61,25 +68,21 @@ class TemplateDigestorTest < ActionView::TestCase end def test_explicit_dependency_wildcard_picks_up_added_file - old_caching, ActionView::Resolver.caching = ActionView::Resolver.caching, false - - assert_digest_difference("events/index") do - add_template("events/_uncompleted") + disable_resolver_caching do + assert_digest_difference("events/index") do + add_template("events/_uncompleted") + end end - ensure - remove_template("events/_uncompleted") - ActionView::Resolver.caching = old_caching end def test_explicit_dependency_wildcard_picks_up_removed_file - old_caching, ActionView::Resolver.caching = ActionView::Resolver.caching, false - add_template("events/_subscribers_changed") + disable_resolver_caching do + add_template("events/_subscribers_changed") - assert_digest_difference("events/index") do - remove_template("events/_subscribers_changed") + assert_digest_difference("events/index") do + remove_template("events/_subscribers_changed") + end end - ensure - ActionView::Resolver.caching = old_caching end def test_second_level_dependency @@ -130,12 +133,42 @@ class TemplateDigestorTest < ActionView::TestCase end end + def test_getting_of_singly_nested_dependencies + singly_nested_dependencies = ["messages/header", "messages/form", "messages/message", "events/event", "comments/comment"] + assert_equal singly_nested_dependencies, nested_dependencies("messages/edit") + end + + def test_getting_of_doubly_nested_dependencies + doubly_nested = [{ "comments/comments"=>["comments/comment"] }, "messages/message"] + assert_equal doubly_nested, nested_dependencies("messages/peek") + end + def test_nested_template_directory assert_digest_difference("messages/show") do change_template("messages/actions/_move") end end + def test_nested_template_deps + nested_deps = ["messages/header", { "comments/comments"=>["comments/comment"] }, "messages/actions/move", "events/event", "messages/something_missing", "messages/something_missing_1", "messages/message", "messages/form"] + assert_equal nested_deps, nested_dependencies("messages/show") + end + + def test_nested_template_deps_with_non_default_rendered_format + finder.rendered_format = nil + nested_deps = [{ "comments/comments"=>["comments/comment"] }] + assert_equal nested_deps, nested_dependencies("messages/thread") + end + + def test_template_formats_of_nested_deps_with_non_default_rendered_format + finder.rendered_format = nil + assert_equal [:json], tree_template_formats("messages/thread").uniq + end + + def test_template_formats_of_dependencies_with_same_logical_name_and_different_rendered_format + assert_equal [:html], tree_template_formats("messages/show").uniq + end + def test_recursion_in_renders assert digest("level/recursion") # assert recursion is possible assert_not_nil digest("level/recursion") # assert digest is stored @@ -162,7 +195,7 @@ class TemplateDigestorTest < ActionView::TestCase end def test_dont_generate_a_digest_for_missing_templates - assert_equal '', digest("nothing/there") + assert_equal "", digest("nothing/there") end def test_collection_dependency @@ -183,7 +216,7 @@ class TemplateDigestorTest < ActionView::TestCase def test_details_are_included_in_cache_key # Cache the template digest. - @finder = FixtureFinder.new({:formats => [:html]}) + @finder = FixtureFinder.new(formats: [:html]) old_digest = digest("events/_event") # Change the template; the cached digest remains unchanged. @@ -247,6 +280,13 @@ class TemplateDigestorTest < ActionView::TestCase assert_not_equal digest_phone, digest_fridge_phone end + def test_different_formats_with_same_logical_template_names_results_in_different_digests + html_digest = digest("comments/_comment", format: :html) + json_digest = digest("comments/_comment", format: :json) + + assert_not_equal html_digest, json_digest + end + def test_digest_cache_cleanup_with_recursion first_digest = digest("level/_recursion") second_digest = digest("level/_recursion") @@ -258,21 +298,17 @@ class TemplateDigestorTest < ActionView::TestCase end def test_digest_cache_cleanup_with_recursion_and_template_caching_off - resolver_before = ActionView::Resolver.caching - ActionView::Resolver.caching = false + disable_resolver_caching do + first_digest = digest("level/_recursion") + second_digest = digest("level/_recursion") - first_digest = digest("level/_recursion") - second_digest = digest("level/_recursion") - - assert first_digest + assert first_digest - # If the cache is cleaned up correctly, subsequent digests should return the same - assert_equal first_digest, second_digest - ensure - ActionView::Resolver.caching = resolver_before + # If the cache is cleaned up correctly, subsequent digests should return the same + assert_equal first_digest, second_digest + end end - private def assert_logged(message) old_logger = ActionView::Base.logger @@ -291,28 +327,44 @@ class TemplateDigestorTest < ActionView::TestCase def assert_digest_difference(template_name, options = {}) previous_digest = digest(template_name, options) - ActionView::Digestor.cache.clear + finder.digest_cache.clear yield assert_not_equal previous_digest, digest(template_name, options), "digest didn't change" - ActionView::Digestor.cache.clear + finder.digest_cache.clear end def digest(template_name, options = {}) options = options.dup + finder_options = options.extract!(:variants, :format) - finder.variants = options.delete(:variants) || [] + finder.variants = finder_options[:variants] || [] + finder.rendered_format = finder_options[:format] if finder_options[:format] - ActionView::Digestor.digest({ name: template_name, finder: finder }.merge(options)) + ActionView::Digestor.digest(name: template_name, finder: finder, dependencies: (options[:dependencies] || [])) end def dependencies(template_name) - ActionView::Digestor.new(template_name, finder).dependencies + tree = ActionView::Digestor.tree(template_name, finder) + tree.children.map(&:name) end def nested_dependencies(template_name) - ActionView::Digestor.new(template_name, finder).nested_dependencies + tree = ActionView::Digestor.tree(template_name, finder) + tree.children.map(&:to_dep_map) + end + + def tree_template_formats(template_name) + tree = ActionView::Digestor.tree(template_name, finder) + tree.flatten.map(&:template).compact.flat_map(&:formats) + end + + def disable_resolver_caching + old_caching, ActionView::Resolver.caching = ActionView::Resolver.caching, false + yield + ensure + ActionView::Resolver.caching = old_caching end def finder diff --git a/actionview/test/template/erb_util_test.rb b/actionview/test/template/erb_util_test.rb index 3e72be31de..0a16364ed3 100644 --- a/actionview/test/template/erb_util_test.rb +++ b/actionview/test/template/erb_util_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/json' +require "abstract_unit" +require "active_support/json" class ErbUtilTest < ActiveSupport::TestCase include ERB::Util @@ -17,19 +17,19 @@ class ErbUtilTest < ActiveSupport::TestCase end HTML_ESCAPE_TEST_CASES = [ - ['<br>', '<br>'], - ['a & b', 'a & b'], - ['"quoted" string', '"quoted" string'], - ["'quoted' string", ''quoted' string'], + ["<br>", "<br>"], + ["a & b", "a & b"], + ['"quoted" string', ""quoted" string"], + ["'quoted' string", "'quoted' string"], [ '<script type="application/javascript">alert("You are \'pwned\'!")</script>', - '<script type="application/javascript">alert("You are 'pwned'!")</script>' + "<script type="application/javascript">alert("You are 'pwned'!")</script>" ] ] JSON_ESCAPE_TEST_CASES = [ - ['1', '1'], - ['null', 'null'], + ["1", "1"], + ["null", "null"], ['"&"', '"\u0026"'], ['"</script>"', '"\u003c/script\u003e"'], ['["</script>"]', '["\u003c/script\u003e"]'], @@ -91,17 +91,17 @@ class ErbUtilTest < ActiveSupport::TestCase end def test_html_escape_once - assert_equal '1 <>&"' 2 & 3', html_escape_once('1 <>&"\' 2 & 3') + assert_equal "1 <>&"' 2 & 3", html_escape_once('1 <>&"\' 2 & 3') assert_equal " ' ' λ λ " ' < > ", html_escape_once(" ' ' λ λ \" ' < > ") end def test_html_escape_once_returns_unsafe_strings_when_passed_unsafe_strings - value = html_escape_once('1 < 2 & 3') + value = html_escape_once("1 < 2 & 3") assert !value.html_safe? end def test_html_escape_once_returns_safe_strings_when_passed_safe_strings - value = html_escape_once('1 < 2 & 3'.html_safe) + value = html_escape_once("1 < 2 & 3".html_safe) assert value.html_safe? end end diff --git a/actionview/test/template/form_collections_helper_test.rb b/actionview/test/template/form_collections_helper_test.rb index 4f7ea88169..3774dcf872 100644 --- a/actionview/test/template/form_collections_helper_test.rb +++ b/actionview/test/template/form_collections_helper_test.rb @@ -1,11 +1,11 @@ -require 'abstract_unit' +require "abstract_unit" class Category < Struct.new(:id, :name) end class FormCollectionsHelperTest < ActionView::TestCase def assert_no_select(selector, value = nil) - assert_select(selector, :text => value, :count => 0) + assert_select(selector, text: value, count: 0) end def with_collection_radio_buttons(*args, &block) @@ -17,170 +17,170 @@ class FormCollectionsHelperTest < ActionView::TestCase end # COLLECTION RADIO BUTTONS - test 'collection radio accepts a collection and generates inputs from value method' do + test "collection radio accepts a collection and generates inputs from value method" do with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s - assert_select 'input[type=radio][value=true]#user_active_true' - assert_select 'input[type=radio][value=false]#user_active_false' + assert_select "input[type=radio][value=true]#user_active_true" + assert_select "input[type=radio][value=false]#user_active_false" end - test 'collection radio accepts a collection and generates inputs from label method' do + test "collection radio accepts a collection and generates inputs from label method" do with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s - assert_select 'label[for=user_active_true]', 'true' - assert_select 'label[for=user_active_false]', 'false' + assert_select "label[for=user_active_true]", "true" + assert_select "label[for=user_active_false]", "false" end - test 'collection radio handles camelized collection values for labels correctly' do - with_collection_radio_buttons :user, :active, ['Yes', 'No'], :to_s, :to_s + test "collection radio handles camelized collection values for labels correctly" do + with_collection_radio_buttons :user, :active, ["Yes", "No"], :to_s, :to_s - assert_select 'label[for=user_active_yes]', 'Yes' - assert_select 'label[for=user_active_no]', 'No' + assert_select "label[for=user_active_yes]", "Yes" + assert_select "label[for=user_active_no]", "No" end - test 'collection radio should sanitize collection values for labels correctly' do - with_collection_radio_buttons :user, :name, ['$0.99', '$1.99'], :to_s, :to_s - assert_select 'label[for=user_name_099]', '$0.99' - assert_select 'label[for=user_name_199]', '$1.99' + test "collection radio should sanitize collection values for labels correctly" do + with_collection_radio_buttons :user, :name, ["$0.99", "$1.99"], :to_s, :to_s + assert_select "label[for=user_name_099]", "$0.99" + assert_select "label[for=user_name_199]", "$1.99" end - test 'collection radio accepts checked item' do - with_collection_radio_buttons :user, :active, [[1, true], [0, false]], :last, :first, :checked => true + test "collection radio accepts checked item" do + with_collection_radio_buttons :user, :active, [[1, true], [0, false]], :last, :first, checked: true - assert_select 'input[type=radio][value=true][checked=checked]' - assert_no_select 'input[type=radio][value=false][checked=checked]' + assert_select "input[type=radio][value=true][checked=checked]" + assert_no_select "input[type=radio][value=false][checked=checked]" end - test 'collection radio accepts multiple disabled items' do - collection = [[1, true], [0, false], [2, 'other']] - with_collection_radio_buttons :user, :active, collection, :last, :first, :disabled => [true, false] + test "collection radio accepts multiple disabled items" do + collection = [[1, true], [0, false], [2, "other"]] + with_collection_radio_buttons :user, :active, collection, :last, :first, disabled: [true, false] - assert_select 'input[type=radio][value=true][disabled=disabled]' - assert_select 'input[type=radio][value=false][disabled=disabled]' - assert_no_select 'input[type=radio][value=other][disabled=disabled]' + assert_select "input[type=radio][value=true][disabled=disabled]" + assert_select "input[type=radio][value=false][disabled=disabled]" + assert_no_select "input[type=radio][value=other][disabled=disabled]" end - test 'collection radio accepts single disabled item' do + test "collection radio accepts single disabled item" do collection = [[1, true], [0, false]] - with_collection_radio_buttons :user, :active, collection, :last, :first, :disabled => true + with_collection_radio_buttons :user, :active, collection, :last, :first, disabled: true - assert_select 'input[type=radio][value=true][disabled=disabled]' - assert_no_select 'input[type=radio][value=false][disabled=disabled]' + assert_select "input[type=radio][value=true][disabled=disabled]" + assert_no_select "input[type=radio][value=false][disabled=disabled]" end - test 'collection radio accepts multiple readonly items' do - collection = [[1, true], [0, false], [2, 'other']] - with_collection_radio_buttons :user, :active, collection, :last, :first, :readonly => [true, false] + test "collection radio accepts multiple readonly items" do + collection = [[1, true], [0, false], [2, "other"]] + with_collection_radio_buttons :user, :active, collection, :last, :first, readonly: [true, false] - assert_select 'input[type=radio][value=true][readonly=readonly]' - assert_select 'input[type=radio][value=false][readonly=readonly]' - assert_no_select 'input[type=radio][value=other][readonly=readonly]' + assert_select "input[type=radio][value=true][readonly=readonly]" + assert_select "input[type=radio][value=false][readonly=readonly]" + assert_no_select "input[type=radio][value=other][readonly=readonly]" end - test 'collection radio accepts single readonly item' do + test "collection radio accepts single readonly item" do collection = [[1, true], [0, false]] - with_collection_radio_buttons :user, :active, collection, :last, :first, :readonly => true + with_collection_radio_buttons :user, :active, collection, :last, :first, readonly: true - assert_select 'input[type=radio][value=true][readonly=readonly]' - assert_no_select 'input[type=radio][value=false][readonly=readonly]' + assert_select "input[type=radio][value=true][readonly=readonly]" + assert_no_select "input[type=radio][value=false][readonly=readonly]" end - test 'collection radio accepts html options as input' do + test "collection radio accepts html options as input" do collection = [[1, true], [0, false]] - with_collection_radio_buttons :user, :active, collection, :last, :first, {}, :class => 'special-radio' + with_collection_radio_buttons :user, :active, collection, :last, :first, {}, class: "special-radio" - assert_select 'input[type=radio][value=true].special-radio#user_active_true' - assert_select 'input[type=radio][value=false].special-radio#user_active_false' + assert_select "input[type=radio][value=true].special-radio#user_active_true" + assert_select "input[type=radio][value=false].special-radio#user_active_false" end - test 'collection radio accepts html options as the last element of array' do - collection = [[1, true, {class: 'foo'}], [0, false, {class: 'bar'}]] + test "collection radio accepts html options as the last element of array" do + collection = [[1, true, { class: "foo" }], [0, false, { class: "bar" }]] with_collection_radio_buttons :user, :active, collection, :second, :first - assert_select 'input[type=radio][value=true].foo#user_active_true' - assert_select 'input[type=radio][value=false].bar#user_active_false' + assert_select "input[type=radio][value=true].foo#user_active_true" + assert_select "input[type=radio][value=false].bar#user_active_false" end - test 'collection radio sets the label class defined inside the block' do - collection = [[1, true, {class: 'foo'}], [0, false, {class: 'bar'}]] + test "collection radio sets the label class defined inside the block" do + collection = [[1, true, { class: "foo" }], [0, false, { class: "bar" }]] with_collection_radio_buttons :user, :active, collection, :second, :first do |b| b.label(class: "collection_radio_buttons") end - assert_select 'label.collection_radio_buttons[for=user_active_true]' - assert_select 'label.collection_radio_buttons[for=user_active_false]' + assert_select "label.collection_radio_buttons[for=user_active_true]" + assert_select "label.collection_radio_buttons[for=user_active_false]" end - test 'collection radio does not include the input class in the respective label' do - collection = [[1, true, {class: 'foo'}], [0, false, {class: 'bar'}]] + test "collection radio does not include the input class in the respective label" do + collection = [[1, true, { class: "foo" }], [0, false, { class: "bar" }]] with_collection_radio_buttons :user, :active, collection, :second, :first - assert_no_select 'label.foo[for=user_active_true]' - assert_no_select 'label.bar[for=user_active_false]' + assert_no_select "label.foo[for=user_active_true]" + assert_no_select "label.bar[for=user_active_false]" end - test 'collection radio does not wrap input inside the label' do + test "collection radio does not wrap input inside the label" do with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s - assert_select 'input[type=radio] + label' - assert_no_select 'label input' + assert_select "input[type=radio] + label" + assert_no_select "label input" end - test 'collection radio accepts a block to render the label as radio button wrapper' do + test "collection radio accepts a block to render the label as radio button wrapper" do with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b| b.label { b.radio_button } end - assert_select 'label[for=user_active_true] > input#user_active_true[type=radio]' - assert_select 'label[for=user_active_false] > input#user_active_false[type=radio]' + assert_select "label[for=user_active_true] > input#user_active_true[type=radio]" + assert_select "label[for=user_active_false] > input#user_active_false[type=radio]" end - test 'collection radio accepts a block to change the order of label and radio button' do + test "collection radio accepts a block to change the order of label and radio button" do with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b| b.label + b.radio_button end - assert_select 'label[for=user_active_true] + input#user_active_true[type=radio]' - assert_select 'label[for=user_active_false] + input#user_active_false[type=radio]' + assert_select "label[for=user_active_true] + input#user_active_true[type=radio]" + assert_select "label[for=user_active_false] + input#user_active_false[type=radio]" end - test 'collection radio with block helpers accept extra html options' do + test "collection radio with block helpers accept extra html options" do with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b| - b.label(:class => "radio_button") + b.radio_button(:class => "radio_button") + b.label(class: "radio_button") + b.radio_button(class: "radio_button") end - assert_select 'label.radio_button[for=user_active_true] + input#user_active_true.radio_button[type=radio]' - assert_select 'label.radio_button[for=user_active_false] + input#user_active_false.radio_button[type=radio]' + assert_select "label.radio_button[for=user_active_true] + input#user_active_true.radio_button[type=radio]" + assert_select "label.radio_button[for=user_active_false] + input#user_active_false.radio_button[type=radio]" end - test 'collection radio with block helpers allows access to current text and value' do + test "collection radio with block helpers allows access to current text and value" do with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b| - b.label(:"data-value" => b.value) { b.radio_button + b.text } + b.label("data-value": b.value) { b.radio_button + b.text } end - assert_select 'label[for=user_active_true][data-value=true]', 'true' do - assert_select 'input#user_active_true[type=radio]' + assert_select "label[for=user_active_true][data-value=true]", "true" do + assert_select "input#user_active_true[type=radio]" end - assert_select 'label[for=user_active_false][data-value=false]', 'false' do - assert_select 'input#user_active_false[type=radio]' + assert_select "label[for=user_active_false][data-value=false]", "false" do + assert_select "input#user_active_false[type=radio]" end end - test 'collection radio with block helpers allows access to the current object item in the collection to access extra properties' do + test "collection radio with block helpers allows access to the current object item in the collection to access extra properties" do with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b| - b.label(:class => b.object) { b.radio_button + b.text } + b.label(class: b.object) { b.radio_button + b.text } end - assert_select 'label.true[for=user_active_true]', 'true' do - assert_select 'input#user_active_true[type=radio]' + assert_select "label.true[for=user_active_true]", "true" do + assert_select "input#user_active_true[type=radio]" end - assert_select 'label.false[for=user_active_false]', 'false' do - assert_select 'input#user_active_false[type=radio]' + assert_select "label.false[for=user_active_false]", "false" do + assert_select "input#user_active_false[type=radio]" end end - test 'collection radio buttons with fields for' do - collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] + test "collection radio buttons with fields for" do + collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] @output_buffer = fields_for(:post) do |p| p.collection_radio_buttons :category_id, collection, :id, :name end @@ -188,186 +188,186 @@ class FormCollectionsHelperTest < ActionView::TestCase assert_select 'input#post_category_id_1[type=radio][value="1"]' assert_select 'input#post_category_id_2[type=radio][value="2"]' - assert_select 'label[for=post_category_id_1]', 'Category 1' - assert_select 'label[for=post_category_id_2]', 'Category 2' + assert_select "label[for=post_category_id_1]", "Category 1" + assert_select "label[for=post_category_id_2]", "Category 2" end - test 'collection radio accepts checked item which has a value of false' do - with_collection_radio_buttons :user, :active, [[1, true], [0, false]], :last, :first, :checked => false - assert_no_select 'input[type=radio][value=true][checked=checked]' - assert_select 'input[type=radio][value=false][checked=checked]' + test "collection radio accepts checked item which has a value of false" do + with_collection_radio_buttons :user, :active, [[1, true], [0, false]], :last, :first, checked: false + assert_no_select "input[type=radio][value=true][checked=checked]" + assert_select "input[type=radio][value=false][checked=checked]" end - test 'collection radio buttons generates only one hidden field for the entire collection, to ensure something will be sent back to the server when posting an empty collection' do - collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] + test "collection radio buttons generates only one hidden field for the entire collection, to ensure something will be sent back to the server when posting an empty collection" do + collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] with_collection_radio_buttons :user, :category_ids, collection, :id, :name assert_select "input[type=hidden][name='user[category_ids]'][value='']", count: 1 end - test 'collection radio buttons generates a hidden field using the given :name in :html_options' do - collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] - with_collection_radio_buttons :user, :category_ids, collection, :id, :name, {}, { name: "user[other_category_ids]" } + test "collection radio buttons generates a hidden field using the given :name in :html_options" do + collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] + with_collection_radio_buttons :user, :category_ids, collection, :id, :name, {}, name: "user[other_category_ids]" assert_select "input[type=hidden][name='user[other_category_ids]'][value='']", count: 1 end - test 'collection radio buttons generates a hidden field with index if it was provided' do - collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] - with_collection_radio_buttons :user, :category_ids, collection, :id, :name, { index: 322 } + test "collection radio buttons generates a hidden field with index if it was provided" do + collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] + with_collection_radio_buttons :user, :category_ids, collection, :id, :name, index: 322 assert_select "input[type=hidden][name='user[322][category_ids]'][value='']", count: 1 end - test 'collection radio buttons does not generate a hidden field if include_hidden option is false' do - collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] + test "collection radio buttons does not generate a hidden field if include_hidden option is false" do + collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] with_collection_radio_buttons :user, :category_ids, collection, :id, :name, include_hidden: false assert_select "input[type=hidden][name='user[category_ids]'][value='']", count: 0 end - test 'collection radio buttons does not generate a hidden field if include_hidden option is false with key as string' do - collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] - with_collection_radio_buttons :user, :category_ids, collection, :id, :name, 'include_hidden' => false + test "collection radio buttons does not generate a hidden field if include_hidden option is false with key as string" do + collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] + with_collection_radio_buttons :user, :category_ids, collection, :id, :name, "include_hidden" => false assert_select "input[type=hidden][name='user[category_ids]'][value='']", count: 0 end # COLLECTION CHECK BOXES - test 'collection check boxes accepts a collection and generate a series of checkboxes for value method' do - collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] + test "collection check boxes accepts a collection and generate a series of checkboxes for value method" do + collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] with_collection_check_boxes :user, :category_ids, collection, :id, :name assert_select 'input#user_category_ids_1[type=checkbox][value="1"]' assert_select 'input#user_category_ids_2[type=checkbox][value="2"]' end - test 'collection check boxes generates only one hidden field for the entire collection, to ensure something will be sent back to the server when posting an empty collection' do - collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] + test "collection check boxes generates only one hidden field for the entire collection, to ensure something will be sent back to the server when posting an empty collection" do + collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] with_collection_check_boxes :user, :category_ids, collection, :id, :name - assert_select "input[type=hidden][name='user[category_ids][]'][value='']", :count => 1 + assert_select "input[type=hidden][name='user[category_ids][]'][value='']", count: 1 end - test 'collection check boxes generates a hidden field using the given :name in :html_options' do - collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] - with_collection_check_boxes :user, :category_ids, collection, :id, :name, {}, {name: "user[other_category_ids][]"} + test "collection check boxes generates a hidden field using the given :name in :html_options" do + collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] + with_collection_check_boxes :user, :category_ids, collection, :id, :name, {}, name: "user[other_category_ids][]" - assert_select "input[type=hidden][name='user[other_category_ids][]'][value='']", :count => 1 + assert_select "input[type=hidden][name='user[other_category_ids][]'][value='']", count: 1 end - test 'collection check boxes generates a hidden field with index if it was provided' do - collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] - with_collection_check_boxes :user, :category_ids, collection, :id, :name, { index: 322 } + test "collection check boxes generates a hidden field with index if it was provided" do + collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] + with_collection_check_boxes :user, :category_ids, collection, :id, :name, index: 322 assert_select "input[type=hidden][name='user[322][category_ids][]'][value='']", count: 1 end - test 'collection check boxes does not generate a hidden field if include_hidden option is false' do - collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] + test "collection check boxes does not generate a hidden field if include_hidden option is false" do + collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] with_collection_check_boxes :user, :category_ids, collection, :id, :name, include_hidden: false - assert_select "input[type=hidden][name='user[category_ids][]'][value='']", :count => 0 + assert_select "input[type=hidden][name='user[category_ids][]'][value='']", count: 0 end - test 'collection check boxes does not generate a hidden field if include_hidden option is false with key as string' do - collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] - with_collection_check_boxes :user, :category_ids, collection, :id, :name, 'include_hidden' => false + test "collection check boxes does not generate a hidden field if include_hidden option is false with key as string" do + collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] + with_collection_check_boxes :user, :category_ids, collection, :id, :name, "include_hidden" => false assert_select "input[type=hidden][name='user[category_ids][]'][value='']", count: 0 end - test 'collection check boxes accepts a collection and generate a series of checkboxes with labels for label method' do - collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] + test "collection check boxes accepts a collection and generate a series of checkboxes with labels for label method" do + collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] with_collection_check_boxes :user, :category_ids, collection, :id, :name - assert_select 'label[for=user_category_ids_1]', 'Category 1' - assert_select 'label[for=user_category_ids_2]', 'Category 2' + assert_select "label[for=user_category_ids_1]", "Category 1" + assert_select "label[for=user_category_ids_2]", "Category 2" end - test 'collection check boxes handles camelized collection values for labels correctly' do - with_collection_check_boxes :user, :active, ['Yes', 'No'], :to_s, :to_s + test "collection check boxes handles camelized collection values for labels correctly" do + with_collection_check_boxes :user, :active, ["Yes", "No"], :to_s, :to_s - assert_select 'label[for=user_active_yes]', 'Yes' - assert_select 'label[for=user_active_no]', 'No' + assert_select "label[for=user_active_yes]", "Yes" + assert_select "label[for=user_active_no]", "No" end - test 'collection check box should sanitize collection values for labels correctly' do - with_collection_check_boxes :user, :name, ['$0.99', '$1.99'], :to_s, :to_s - assert_select 'label[for=user_name_099]', '$0.99' - assert_select 'label[for=user_name_199]', '$1.99' + test "collection check box should sanitize collection values for labels correctly" do + with_collection_check_boxes :user, :name, ["$0.99", "$1.99"], :to_s, :to_s + assert_select "label[for=user_name_099]", "$0.99" + assert_select "label[for=user_name_199]", "$1.99" end - test 'collection check boxes accepts html options as the last element of array' do - collection = [[1, 'Category 1', {class: 'foo'}], [2, 'Category 2', {class: 'bar'}]] + test "collection check boxes accepts html options as the last element of array" do + collection = [[1, "Category 1", { class: "foo" }], [2, "Category 2", { class: "bar" }]] with_collection_check_boxes :user, :active, collection, :first, :second assert_select 'input[type=checkbox][value="1"].foo' assert_select 'input[type=checkbox][value="2"].bar' end - test 'collection check boxes propagates input id to the label for attribute' do - collection = [[1, 'Category 1', {id: 'foo'}], [2, 'Category 2', {id: 'bar'}]] + test "collection check boxes propagates input id to the label for attribute" do + collection = [[1, "Category 1", { id: "foo" }], [2, "Category 2", { id: "bar" }]] with_collection_check_boxes :user, :active, collection, :first, :second assert_select 'input[type=checkbox][value="1"]#foo' assert_select 'input[type=checkbox][value="2"]#bar' - assert_select 'label[for=foo]' - assert_select 'label[for=bar]' + assert_select "label[for=foo]" + assert_select "label[for=bar]" end - test 'collection check boxes sets the label class defined inside the block' do - collection = [[1, 'Category 1', {class: 'foo'}], [2, 'Category 2', {class: 'bar'}]] + test "collection check boxes sets the label class defined inside the block" do + collection = [[1, "Category 1", { class: "foo" }], [2, "Category 2", { class: "bar" }]] with_collection_check_boxes :user, :active, collection, :second, :first do |b| - b.label(class: 'collection_check_boxes') + b.label(class: "collection_check_boxes") end - assert_select 'label.collection_check_boxes[for=user_active_category_1]' - assert_select 'label.collection_check_boxes[for=user_active_category_2]' + assert_select "label.collection_check_boxes[for=user_active_category_1]" + assert_select "label.collection_check_boxes[for=user_active_category_2]" end - test 'collection check boxes does not include the input class in the respective label' do - collection = [[1, 'Category 1', {class: 'foo'}], [2, 'Category 2', {class: 'bar'}]] + test "collection check boxes does not include the input class in the respective label" do + collection = [[1, "Category 1", { class: "foo" }], [2, "Category 2", { class: "bar" }]] with_collection_check_boxes :user, :active, collection, :second, :first - assert_no_select 'label.foo[for=user_active_category_1]' - assert_no_select 'label.bar[for=user_active_category_2]' + assert_no_select "label.foo[for=user_active_category_1]" + assert_no_select "label.bar[for=user_active_category_2]" end - test 'collection check boxes accepts selected values as :checked option' do - collection = (1..3).map{|i| [i, "Category #{i}"] } - with_collection_check_boxes :user, :category_ids, collection, :first, :last, :checked => [1, 3] + test "collection check boxes accepts selected values as :checked option" do + collection = (1..3).map { |i| [i, "Category #{i}"] } + with_collection_check_boxes :user, :category_ids, collection, :first, :last, checked: [1, 3] assert_select 'input[type=checkbox][value="1"][checked=checked]' assert_select 'input[type=checkbox][value="3"][checked=checked]' assert_no_select 'input[type=checkbox][value="2"][checked=checked]' end - test 'collection check boxes accepts selected string values as :checked option' do - collection = (1..3).map{|i| [i, "Category #{i}"] } - with_collection_check_boxes :user, :category_ids, collection, :first, :last, :checked => ['1', '3'] + test "collection check boxes accepts selected string values as :checked option" do + collection = (1..3).map { |i| [i, "Category #{i}"] } + with_collection_check_boxes :user, :category_ids, collection, :first, :last, checked: ["1", "3"] assert_select 'input[type=checkbox][value="1"][checked=checked]' assert_select 'input[type=checkbox][value="3"][checked=checked]' assert_no_select 'input[type=checkbox][value="2"][checked=checked]' end - test 'collection check boxes accepts a single checked value' do - collection = (1..3).map{|i| [i, "Category #{i}"] } - with_collection_check_boxes :user, :category_ids, collection, :first, :last, :checked => 3 + test "collection check boxes accepts a single checked value" do + collection = (1..3).map { |i| [i, "Category #{i}"] } + with_collection_check_boxes :user, :category_ids, collection, :first, :last, checked: 3 assert_select 'input[type=checkbox][value="3"][checked=checked]' assert_no_select 'input[type=checkbox][value="1"][checked=checked]' assert_no_select 'input[type=checkbox][value="2"][checked=checked]' end - test 'collection check boxes accepts selected values as :checked option and override the model values' do + test "collection check boxes accepts selected values as :checked option and override the model values" do user = Struct.new(:category_ids).new(2) - collection = (1..3).map{|i| [i, "Category #{i}"] } + collection = (1..3).map { |i| [i, "Category #{i}"] } @output_buffer = fields_for(:user, user) do |p| - p.collection_check_boxes :category_ids, collection, :first, :last, :checked => [1, 3] + p.collection_check_boxes :category_ids, collection, :first, :last, checked: [1, 3] end assert_select 'input[type=checkbox][value="1"][checked=checked]' @@ -375,70 +375,70 @@ class FormCollectionsHelperTest < ActionView::TestCase assert_no_select 'input[type=checkbox][value="2"][checked=checked]' end - test 'collection check boxes accepts multiple disabled items' do - collection = (1..3).map{|i| [i, "Category #{i}"] } - with_collection_check_boxes :user, :category_ids, collection, :first, :last, :disabled => [1, 3] + test "collection check boxes accepts multiple disabled items" do + collection = (1..3).map { |i| [i, "Category #{i}"] } + with_collection_check_boxes :user, :category_ids, collection, :first, :last, disabled: [1, 3] assert_select 'input[type=checkbox][value="1"][disabled=disabled]' assert_select 'input[type=checkbox][value="3"][disabled=disabled]' assert_no_select 'input[type=checkbox][value="2"][disabled=disabled]' end - test 'collection check boxes accepts single disabled item' do - collection = (1..3).map{|i| [i, "Category #{i}"] } - with_collection_check_boxes :user, :category_ids, collection, :first, :last, :disabled => 1 + test "collection check boxes accepts single disabled item" do + collection = (1..3).map { |i| [i, "Category #{i}"] } + with_collection_check_boxes :user, :category_ids, collection, :first, :last, disabled: 1 assert_select 'input[type=checkbox][value="1"][disabled=disabled]' assert_no_select 'input[type=checkbox][value="3"][disabled=disabled]' assert_no_select 'input[type=checkbox][value="2"][disabled=disabled]' end - test 'collection check boxes accepts a proc to disabled items' do - collection = (1..3).map{|i| [i, "Category #{i}"] } - with_collection_check_boxes :user, :category_ids, collection, :first, :last, :disabled => proc { |i| i.first == 1 } + test "collection check boxes accepts a proc to disabled items" do + collection = (1..3).map { |i| [i, "Category #{i}"] } + with_collection_check_boxes :user, :category_ids, collection, :first, :last, disabled: proc { |i| i.first == 1 } assert_select 'input[type=checkbox][value="1"][disabled=disabled]' assert_no_select 'input[type=checkbox][value="3"][disabled=disabled]' assert_no_select 'input[type=checkbox][value="2"][disabled=disabled]' end - test 'collection check boxes accepts multiple readonly items' do - collection = (1..3).map{|i| [i, "Category #{i}"] } - with_collection_check_boxes :user, :category_ids, collection, :first, :last, :readonly => [1, 3] + test "collection check boxes accepts multiple readonly items" do + collection = (1..3).map { |i| [i, "Category #{i}"] } + with_collection_check_boxes :user, :category_ids, collection, :first, :last, readonly: [1, 3] assert_select 'input[type=checkbox][value="1"][readonly=readonly]' assert_select 'input[type=checkbox][value="3"][readonly=readonly]' assert_no_select 'input[type=checkbox][value="2"][readonly=readonly]' end - test 'collection check boxes accepts single readonly item' do - collection = (1..3).map{|i| [i, "Category #{i}"] } - with_collection_check_boxes :user, :category_ids, collection, :first, :last, :readonly => 1 + test "collection check boxes accepts single readonly item" do + collection = (1..3).map { |i| [i, "Category #{i}"] } + with_collection_check_boxes :user, :category_ids, collection, :first, :last, readonly: 1 assert_select 'input[type=checkbox][value="1"][readonly=readonly]' assert_no_select 'input[type=checkbox][value="3"][readonly=readonly]' assert_no_select 'input[type=checkbox][value="2"][readonly=readonly]' end - test 'collection check boxes accepts a proc to readonly items' do - collection = (1..3).map{|i| [i, "Category #{i}"] } - with_collection_check_boxes :user, :category_ids, collection, :first, :last, :readonly => proc { |i| i.first == 1 } + test "collection check boxes accepts a proc to readonly items" do + collection = (1..3).map { |i| [i, "Category #{i}"] } + with_collection_check_boxes :user, :category_ids, collection, :first, :last, readonly: proc { |i| i.first == 1 } assert_select 'input[type=checkbox][value="1"][readonly=readonly]' assert_no_select 'input[type=checkbox][value="3"][readonly=readonly]' assert_no_select 'input[type=checkbox][value="2"][readonly=readonly]' end - test 'collection check boxes accepts html options' do - collection = [[1, 'Category 1'], [2, 'Category 2']] - with_collection_check_boxes :user, :category_ids, collection, :first, :last, {}, :class => 'check' + test "collection check boxes accepts html options" do + collection = [[1, "Category 1"], [2, "Category 2"]] + with_collection_check_boxes :user, :category_ids, collection, :first, :last, {}, class: "check" assert_select 'input.check[type=checkbox][value="1"]' assert_select 'input.check[type=checkbox][value="2"]' end - test 'collection check boxes with fields for' do - collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] + test "collection check boxes with fields for" do + collection = [Category.new(1, "Category 1"), Category.new(2, "Category 2")] @output_buffer = fields_for(:post) do |p| p.collection_check_boxes :category_ids, collection, :id, :name end @@ -446,67 +446,67 @@ class FormCollectionsHelperTest < ActionView::TestCase assert_select 'input#post_category_ids_1[type=checkbox][value="1"]' assert_select 'input#post_category_ids_2[type=checkbox][value="2"]' - assert_select 'label[for=post_category_ids_1]', 'Category 1' - assert_select 'label[for=post_category_ids_2]', 'Category 2' + assert_select "label[for=post_category_ids_1]", "Category 1" + assert_select "label[for=post_category_ids_2]", "Category 2" end - test 'collection check boxes does not wrap input inside the label' do + test "collection check boxes does not wrap input inside the label" do with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s - assert_select 'input[type=checkbox] + label' - assert_no_select 'label input' + assert_select "input[type=checkbox] + label" + assert_no_select "label input" end - test 'collection check boxes accepts a block to render the label as check box wrapper' do + test "collection check boxes accepts a block to render the label as check box wrapper" do with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b| b.label { b.check_box } end - assert_select 'label[for=user_active_true] > input#user_active_true[type=checkbox]' - assert_select 'label[for=user_active_false] > input#user_active_false[type=checkbox]' + assert_select "label[for=user_active_true] > input#user_active_true[type=checkbox]" + assert_select "label[for=user_active_false] > input#user_active_false[type=checkbox]" end - test 'collection check boxes accepts a block to change the order of label and check box' do + test "collection check boxes accepts a block to change the order of label and check box" do with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b| b.label + b.check_box end - assert_select 'label[for=user_active_true] + input#user_active_true[type=checkbox]' - assert_select 'label[for=user_active_false] + input#user_active_false[type=checkbox]' + assert_select "label[for=user_active_true] + input#user_active_true[type=checkbox]" + assert_select "label[for=user_active_false] + input#user_active_false[type=checkbox]" end - test 'collection check boxes with block helpers accept extra html options' do + test "collection check boxes with block helpers accept extra html options" do with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b| - b.label(:class => "check_box") + b.check_box(:class => "check_box") + b.label(class: "check_box") + b.check_box(class: "check_box") end - assert_select 'label.check_box[for=user_active_true] + input#user_active_true.check_box[type=checkbox]' - assert_select 'label.check_box[for=user_active_false] + input#user_active_false.check_box[type=checkbox]' + assert_select "label.check_box[for=user_active_true] + input#user_active_true.check_box[type=checkbox]" + assert_select "label.check_box[for=user_active_false] + input#user_active_false.check_box[type=checkbox]" end - test 'collection check boxes with block helpers allows access to current text and value' do + test "collection check boxes with block helpers allows access to current text and value" do with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b| - b.label(:"data-value" => b.value) { b.check_box + b.text } + b.label("data-value": b.value) { b.check_box + b.text } end - assert_select 'label[for=user_active_true][data-value=true]', 'true' do - assert_select 'input#user_active_true[type=checkbox]' + assert_select "label[for=user_active_true][data-value=true]", "true" do + assert_select "input#user_active_true[type=checkbox]" end - assert_select 'label[for=user_active_false][data-value=false]', 'false' do - assert_select 'input#user_active_false[type=checkbox]' + assert_select "label[for=user_active_false][data-value=false]", "false" do + assert_select "input#user_active_false[type=checkbox]" end end - test 'collection check boxes with block helpers allows access to the current object item in the collection to access extra properties' do + test "collection check boxes with block helpers allows access to the current object item in the collection to access extra properties" do with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b| - b.label(:class => b.object) { b.check_box + b.text } + b.label(class: b.object) { b.check_box + b.text } end - assert_select 'label.true[for=user_active_true]', 'true' do - assert_select 'input#user_active_true[type=checkbox]' + assert_select "label.true[for=user_active_true]", "true" do + assert_select "input#user_active_true[type=checkbox]" end - assert_select 'label.false[for=user_active_false]', 'false' do - assert_select 'input#user_active_false[type=checkbox]' + assert_select "label.false[for=user_active_false]", "false" do + assert_select "input#user_active_false[type=checkbox]" end end end diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb index 034b8a4bf6..395c4e4f41 100644 --- a/actionview/test/template/form_helper_test.rb +++ b/actionview/test/template/form_helper_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'controller/fake_models' +require "abstract_unit" +require "controller/fake_models" class FormHelperTest < ActionView::TestCase include RenderERBUtils @@ -16,13 +16,13 @@ class FormHelperTest < ActionView::TestCase setup do # Create "label" locale for testing I18n label helpers - I18n.backend.store_translations 'label', { + I18n.backend.store_translations "label", activemodel: { attributes: { post: { cost: "Total cost" }, - :"post/language" => { + "post/language": { spanish: "Espanol" } } @@ -42,33 +42,31 @@ class FormHelperTest < ActionView::TestCase value: "Tag" }, post_delegate: { - title: 'Delegate model_name title' + title: "Delegate model_name title" } } } - } # Create "submit" locale for testing I18n submit helpers - I18n.backend.store_translations 'submit', { + I18n.backend.store_translations "submit", helpers: { submit: { - create: 'Create %{model}', - update: 'Confirm %{model} changes', - submit: 'Save changes', + create: "Create %{model}", + update: "Confirm %{model} changes", + submit: "Save changes", another_post: { - update: 'Update your %{model}' + update: "Update your %{model}" } } } - } - I18n.backend.store_translations 'placeholder', { + I18n.backend.store_translations "placeholder", activemodel: { attributes: { post: { cost: "Total cost" }, - :"post/cost" => { + "post/cost": { uk: "Pounds" } } @@ -85,14 +83,13 @@ class FormHelperTest < ActionView::TestCase } }, post_delegate: { - title: 'Delegate model_name title' + title: "Delegate model_name title" }, tag: { value: "Tag" } } } - } @post = Post.new @comment = Comment.new @@ -108,7 +105,7 @@ class FormHelperTest < ActionView::TestCase def @post.id; 0; end def @post.id_before_type_cast; "omg"; end def @post.id_came_from_user?; true; end - def @post.to_param; '123'; end + def @post.to_param; "123"; end @post.persisted = true @post.title = "Hello World" @@ -125,11 +122,9 @@ class FormHelperTest < ActionView::TestCase @post_delegator = PostDelegator.new - @post_delegator.title = 'Hello World' + @post_delegator.title = "Hello World" @car = Car.new("#000FFF") - - @ticket_type = TicketType.new end Routes = ActionDispatch::Routing::RouteSet.new @@ -138,8 +133,6 @@ class FormHelperTest < ActionView::TestCase resources :comments end - resources :ticket_types - namespace :admin do resources :posts do resources :comments @@ -182,7 +175,7 @@ class FormHelperTest < ActionView::TestCase ) assert_dom_equal( '<label class="title_label" for="post_title">Title</label>', - label("post", "title", nil, class: 'title_label') + label("post", "title", nil, class: "title_label") ) assert_dom_equal('<label for="post_secret">Secret?</label>', label("post", "secret?")) end @@ -233,7 +226,7 @@ class FormHelperTest < ActionView::TestCase def test_label_with_locales_and_nested_attributes with_locale :label do - form_for(@post, html: { id: 'create-post' }) do |f| + form_for(@post, html: { id: "create-post" }) do |f| f.fields_for(:comments) do |cf| concat cf.label(:body) end @@ -249,7 +242,7 @@ class FormHelperTest < ActionView::TestCase def test_label_with_locales_fallback_and_nested_attributes with_locale :label do - form_for(@post, html: { id: 'create-post' }) do |f| + form_for(@post, html: { id: "create-post" }) do |f| f.fields_for(:tags) do |cf| concat cf.label(:value) end @@ -264,7 +257,7 @@ class FormHelperTest < ActionView::TestCase end def test_label_with_non_active_record_object - form_for(OpenStruct.new(name:'ok'), as: 'person', url: 'an_url', html: { id: 'create-person' }) do |f| + form_for(OpenStruct.new(name:"ok"), as: "person", url: "an_url", html: { id: "create-person" }) do |f| f.label(:name) end @@ -284,7 +277,7 @@ class FormHelperTest < ActionView::TestCase end def test_label_does_not_generate_for_attribute_when_given_nil - assert_dom_equal('<label>Title</label>', label(:post, :title, for: nil)) + assert_dom_equal("<label>Title</label>", label(:post, :title, for: nil)) end def test_label_with_id_attribute_as_symbol @@ -433,7 +426,7 @@ class FormHelperTest < ActionView::TestCase def test_text_field_placeholder_with_locales_and_nested_attributes with_locale :placeholder do - form_for(@post, html: { id: 'create-post' }) do |f| + form_for(@post, html: { id: "create-post" }) do |f| f.fields_for(:comments) do |cf| concat cf.text_field(:body, placeholder: true) end @@ -449,7 +442,7 @@ class FormHelperTest < ActionView::TestCase def test_text_field_placeholder_with_locales_fallback_and_nested_attributes with_locale :placeholder do - form_for(@post, html: { id: 'create-post' }) do |f| + form_for(@post, html: { id: "create-post" }) do |f| f.fields_for(:tags) do |cf| concat cf.text_field(:value, placeholder: true) end @@ -527,39 +520,24 @@ class FormHelperTest < ActionView::TestCase end def test_text_field_doesnt_change_param_values - object_name = 'post[]' + object_name = "post[]" expected = '<input id="post_123_title" name="post[123][title]" type="text" value="Hello World" />' assert_dom_equal expected, text_field(object_name, "title") end - def test_file_field_does_generate_a_hidden_field - expected = '<input name="user[avatar]" type="hidden" value="" /><input id="user_avatar" name="user[avatar]" type="file" />' - assert_dom_equal expected, file_field("user", "avatar") - end - - def test_file_field_does_not_generate_a_hidden_field_if_included_hidden_option_is_false - expected = '<input id="user_avatar" name="user[avatar]" type="file" />' - assert_dom_equal expected, file_field("user", "avatar", include_hidden: false) - end - - def test_file_field_does_not_generate_a_hidden_field_if_included_hidden_option_is_false_with_key_as_string - expected = '<input id="user_avatar" name="user[avatar]" type="file" />' - assert_dom_equal expected, file_field("user", "avatar", "include_hidden" => false) - end - def test_file_field_has_no_size - expected = '<input name="user[avatar]" type="hidden" value="" /><input id="user_avatar" name="user[avatar]" type="file" />' + expected = '<input id="user_avatar" name="user[avatar]" type="file" />' assert_dom_equal expected, file_field("user", "avatar") end def test_file_field_with_multiple_behavior - expected = '<input name="import[file][]" type="hidden" value="" /><input id="import_file" multiple="multiple" name="import[file][]" type="file" />' - assert_dom_equal expected, file_field("import", "file", :multiple => true) + expected = '<input id="import_file" multiple="multiple" name="import[file][]" type="file" />' + assert_dom_equal expected, file_field("import", "file", multiple: true) end def test_file_field_with_multiple_behavior_and_explicit_name - expected = '<input name="custom" type="hidden" value="" /><input id="import_file" multiple="multiple" name="custom" type="file" />' - assert_dom_equal expected, file_field("import", "file", :multiple => true, :name => "custom") + expected = '<input id="import_file" multiple="multiple" name="custom" type="file" />' + assert_dom_equal expected, file_field("import", "file", multiple: true, name: "custom") end def test_hidden_field @@ -640,19 +618,19 @@ class FormHelperTest < ActionView::TestCase end def test_check_box_checked_if_object_value_includes_checked_value - @post.secret = ['0'] + @post.secret = ["0"] assert_dom_equal( '<input name="post[secret]" type="hidden" value="0" /><input id="post_secret" name="post[secret]" type="checkbox" value="1" />', check_box("post", "secret") ) - @post.secret = ['1'] + @post.secret = ["1"] assert_dom_equal( '<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />', check_box("post", "secret") ) - @post.secret = Set.new(['1']) + @post.secret = Set.new(["1"]) assert_dom_equal( '<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />', check_box("post", "secret") @@ -795,7 +773,7 @@ class FormHelperTest < ActionView::TestCase def test_checkbox_disabled_disables_hidden_field assert_dom_equal( '<input name="post[secret]" type="hidden" value="0" disabled="disabled"/><input checked="checked" disabled="disabled" id="post_secret" name="post[secret]" type="checkbox" value="1" />', - check_box("post", "secret", { disabled: true }) + check_box("post", "secret", disabled: true) ) end @@ -830,9 +808,9 @@ class FormHelperTest < ActionView::TestCase end def test_radio_button_respects_passed_in_id - assert_dom_equal('<input checked="checked" id="foo" name="post[secret]" type="radio" value="1" />', - radio_button("post", "secret", "1", id: "foo") - ) + assert_dom_equal('<input checked="checked" id="foo" name="post[secret]" type="radio" value="1" />', + radio_button("post", "secret", "1", id: "foo") + ) end def test_radio_button_with_booleans @@ -901,7 +879,7 @@ class FormHelperTest < ActionView::TestCase def test_text_area_placeholder_with_locales_and_nested_attributes with_locale :placeholder do - form_for(@post, html: { id: 'create-post' }) do |f| + form_for(@post, html: { id: "create-post" }) do |f| f.fields_for(:comments) do |cf| concat cf.text_area(:body, placeholder: true) end @@ -917,7 +895,7 @@ class FormHelperTest < ActionView::TestCase def test_text_area_placeholder_with_locales_fallback_and_nested_attributes with_locale :placeholder do - form_for(@post, html: { id: 'create-post' }) do |f| + form_for(@post, html: { id: "create-post" }) do |f| f.fields_for(:tags) do |cf| concat cf.text_area(:value, placeholder: true) end @@ -1060,9 +1038,9 @@ class FormHelperTest < ActionView::TestCase end def test_date_field_with_timewithzone_value - previous_time_zone, Time.zone = Time.zone, 'UTC' + previous_time_zone, Time.zone = Time.zone, "UTC" expected = %{<input id="post_written_on" name="post[written_on]" type="date" value="2004-06-15" />} - @post.written_on = Time.zone.parse('2004-06-15 15:30:45') + @post.written_on = Time.zone.parse("2004-06-15 15:30:45") assert_dom_equal(expected, date_field("post", "written_on")) ensure Time.zone = previous_time_zone @@ -1111,9 +1089,9 @@ class FormHelperTest < ActionView::TestCase end def test_time_field_with_timewithzone_value - previous_time_zone, Time.zone = Time.zone, 'UTC' + previous_time_zone, Time.zone = Time.zone, "UTC" expected = %{<input id="post_written_on" name="post[written_on]" type="time" value="01:02:03.000" />} - @post.written_on = Time.zone.parse('2004-06-15 01:02:03') + @post.written_on = Time.zone.parse("2004-06-15 01:02:03") assert_dom_equal(expected, time_field("post", "written_on")) ensure Time.zone = previous_time_zone @@ -1142,18 +1120,18 @@ class FormHelperTest < ActionView::TestCase end def test_datetime_field - expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" value="2004-06-15T00:00:00.000+0000" />} + expected = %{<input id="post_written_on" name="post[written_on]" type="datetime-local" value="2004-06-15T00:00:00" />} assert_dom_equal(expected, datetime_field("post", "written_on")) end def test_datetime_field_with_datetime_value - expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" value="2004-06-15T01:02:03.000+0000" />} + expected = %{<input id="post_written_on" name="post[written_on]" type="datetime-local" value="2004-06-15T01:02:03" />} @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3) assert_dom_equal(expected, datetime_field("post", "written_on")) end def test_datetime_field_with_extra_attrs - expected = %{<input id="post_written_on" step="60" max="2010-08-15T10:25:00.000+0000" min="2000-06-15T20:45:30.000+0000" name="post[written_on]" type="datetime" value="2004-06-15T01:02:03.000+0000" />} + expected = %{<input id="post_written_on" step="60" max="2010-08-15T10:25:00" min="2000-06-15T20:45:30" name="post[written_on]" type="datetime-local" value="2004-06-15T01:02:03" />} @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3) min_value = DateTime.new(2000, 6, 15, 20, 45, 30) max_value = DateTime.new(2010, 8, 15, 10, 25, 00) @@ -1162,36 +1140,36 @@ class FormHelperTest < ActionView::TestCase end def test_datetime_field_with_value_attr - expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" value="2013-06-29T13:37:00+00:00" />} + expected = %{<input id="post_written_on" name="post[written_on]" type="datetime-local" value="2013-06-29T13:37:00+00:00" />} value = DateTime.new(2013,6,29,13,37) assert_dom_equal(expected, datetime_field("post", "written_on", value: value)) end def test_datetime_field_with_timewithzone_value - previous_time_zone, Time.zone = Time.zone, 'UTC' - expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" value="2004-06-15T15:30:45.000+0000" />} - @post.written_on = Time.zone.parse('2004-06-15 15:30:45') + previous_time_zone, Time.zone = Time.zone, "UTC" + expected = %{<input id="post_written_on" name="post[written_on]" type="datetime-local" value="2004-06-15T15:30:45" />} + @post.written_on = Time.zone.parse("2004-06-15 15:30:45") assert_dom_equal(expected, datetime_field("post", "written_on")) ensure Time.zone = previous_time_zone end def test_datetime_field_with_nil_value - expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" />} + expected = %{<input id="post_written_on" name="post[written_on]" type="datetime-local" />} @post.written_on = nil assert_dom_equal(expected, datetime_field("post", "written_on")) end def test_datetime_field_with_string_values_for_min_and_max - expected = %{<input id="post_written_on" max="2010-08-15T10:25:00.000+0000" min="2000-06-15T20:45:30.000+0000" name="post[written_on]" type="datetime" value="2004-06-15T01:02:03.000+0000" />} + expected = %{<input id="post_written_on" max="2010-08-15T10:25:00" min="2000-06-15T20:45:30" name="post[written_on]" type="datetime-local" value="2004-06-15T01:02:03" />} @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3) - min_value = "2000-06-15T20:45:30.000+0000" - max_value = "2010-08-15T10:25:00.000+0000" + min_value = "2000-06-15T20:45:30" + max_value = "2010-08-15T10:25:00" assert_dom_equal(expected, datetime_field("post", "written_on", min: min_value, max: max_value)) end def test_datetime_field_with_invalid_string_values_for_min_and_max - expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" value="2004-06-15T01:02:03.000+0000" />} + expected = %{<input id="post_written_on" name="post[written_on]" type="datetime-local" value="2004-06-15T01:02:03" />} @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3) min_value = "foo" max_value = "bar" @@ -1203,52 +1181,6 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal(expected, datetime_local_field("post", "written_on")) end - def test_datetime_local_field_with_datetime_value - expected = %{<input id="post_written_on" name="post[written_on]" type="datetime-local" value="2004-06-15T01:02:03" />} - @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3) - assert_dom_equal(expected, datetime_local_field("post", "written_on")) - end - - def test_datetime_local_field_with_extra_attrs - expected = %{<input id="post_written_on" step="60" max="2010-08-15T10:25:00" min="2000-06-15T20:45:30" name="post[written_on]" type="datetime-local" value="2004-06-15T01:02:03" />} - @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3) - min_value = DateTime.new(2000, 6, 15, 20, 45, 30) - max_value = DateTime.new(2010, 8, 15, 10, 25, 00) - step = 60 - assert_dom_equal(expected, datetime_local_field("post", "written_on", min: min_value, max: max_value, step: step)) - end - - def test_datetime_local_field_with_timewithzone_value - previous_time_zone, Time.zone = Time.zone, 'UTC' - expected = %{<input id="post_written_on" name="post[written_on]" type="datetime-local" value="2004-06-15T15:30:45" />} - @post.written_on = Time.zone.parse('2004-06-15 15:30:45') - assert_dom_equal(expected, datetime_local_field("post", "written_on")) - ensure - Time.zone = previous_time_zone - end - - def test_datetime_local_field_with_nil_value - expected = %{<input id="post_written_on" name="post[written_on]" type="datetime-local" />} - @post.written_on = nil - assert_dom_equal(expected, datetime_local_field("post", "written_on")) - end - - def test_datetime_local_field_with_string_values_for_min_and_max - expected = %{<input id="post_written_on" max="2010-08-15T10:25:00" min="2000-06-15T20:45:30" name="post[written_on]" type="datetime-local" value="2004-06-15T01:02:03" />} - @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3) - min_value = "2000-06-15T20:45:30" - max_value = "2010-08-15T10:25:00" - assert_dom_equal(expected, datetime_local_field("post", "written_on", min: min_value, max: max_value)) - end - - def test_datetime_local_field_with_invalid_string_values_for_min_and_max - expected = %{<input id="post_written_on" name="post[written_on]" type="datetime-local" value="2004-06-15T01:02:03" />} - @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3) - min_value = "foo" - max_value = "bar" - assert_dom_equal(expected, datetime_local_field("post", "written_on", min: min_value, max: max_value)) - end - def test_month_field expected = %{<input id="post_written_on" name="post[written_on]" type="month" value="2004-06" />} assert_dom_equal(expected, month_field("post", "written_on")) @@ -1276,9 +1208,9 @@ class FormHelperTest < ActionView::TestCase end def test_month_field_with_timewithzone_value - previous_time_zone, Time.zone = Time.zone, 'UTC' + previous_time_zone, Time.zone = Time.zone, "UTC" expected = %{<input id="post_written_on" name="post[written_on]" type="month" value="2004-06" />} - @post.written_on = Time.zone.parse('2004-06-15 15:30:45') + @post.written_on = Time.zone.parse("2004-06-15 15:30:45") assert_dom_equal(expected, month_field("post", "written_on")) ensure Time.zone = previous_time_zone @@ -1311,9 +1243,9 @@ class FormHelperTest < ActionView::TestCase end def test_week_field_with_timewithzone_value - previous_time_zone, Time.zone = Time.zone, 'UTC' + previous_time_zone, Time.zone = Time.zone, "UTC" expected = %{<input id="post_written_on" name="post[written_on]" type="week" value="2004-W25" />} - @post.written_on = Time.zone.parse('2004-06-15 15:30:45') + @post.written_on = Time.zone.parse("2004-06-15 15:30:45") assert_dom_equal(expected, week_field("post", "written_on")) ensure Time.zone = previous_time_zone @@ -1472,26 +1404,26 @@ class FormHelperTest < ActionView::TestCase def test_index_with_nil_id assert_dom_equal( '<input name="post[5][title]" type="text" value="Hello World" />', - text_field("post", "title", "index" => 5, 'id' => nil) + text_field("post", "title", "index" => 5, "id" => nil) ) assert_dom_equal( %{<textarea name="post[5][body]">\nBack to the hill and over it again!</textarea>}, - text_area("post", "body", "index" => 5, 'id' => nil) + text_area("post", "body", "index" => 5, "id" => nil) ) assert_dom_equal( '<input name="post[5][secret]" type="hidden" value="0" /><input checked="checked" name="post[5][secret]" type="checkbox" value="1" />', - check_box("post", "secret", "index" => 5, 'id' => nil) + check_box("post", "secret", "index" => 5, "id" => nil) ) assert_dom_equal( - text_field("post", "title", "index" => 5, 'id' => nil), + text_field("post", "title", "index" => 5, "id" => nil), text_field("post", "title", index: 5, id: nil) ) assert_dom_equal( - text_area("post", "body", "index" => 5, 'id' => nil), + text_area("post", "body", "index" => 5, "id" => nil), text_area("post", "body", index: 5, id: nil) ) assert_dom_equal( - check_box("post", "secret", "index" => 5, 'id' => nil), + check_box("post", "secret", "index" => 5, "id" => nil), check_box("post", "secret", index: 5, id: nil) ) end @@ -1514,10 +1446,10 @@ class FormHelperTest < ActionView::TestCase %{<input name="post[#{pid}][secret]" type="hidden" value="0" /><input checked="checked" id="post_#{pid}_secret" name="post[#{pid}][secret]" type="checkbox" value="1" />}, check_box("post[]", "secret") ) - assert_dom_equal( - %{<input checked="checked" id="post_#{pid}_title_hello_world" name="post[#{pid}][title]" type="radio" value="Hello World" />}, - radio_button("post[]", "title", "Hello World") - ) + assert_dom_equal( + %{<input checked="checked" id="post_#{pid}_title_hello_world" name="post[#{pid}][title]" type="radio" value="Hello World" />}, + radio_button("post[]", "title", "Hello World") + ) assert_dom_equal( %{<input id="post_#{pid}_title_goodbye_world" name="post[#{pid}][title]" type="radio" value="Goodbye World" />}, radio_button("post[]", "title", "Goodbye World") @@ -1538,10 +1470,10 @@ class FormHelperTest < ActionView::TestCase %{<input name="post[#{pid}][secret]" type="hidden" value="0" /><input checked="checked" name="post[#{pid}][secret]" type="checkbox" value="1" />}, check_box("post[]", "secret", id: nil) ) - assert_dom_equal( - %{<input checked="checked" name="post[#{pid}][title]" type="radio" value="Hello World" />}, - radio_button("post[]", "title", "Hello World", id: nil) - ) + assert_dom_equal( + %{<input checked="checked" name="post[#{pid}][title]" type="radio" value="Hello World" />}, + radio_button("post[]", "title", "Hello World", id: nil) + ) assert_dom_equal( %{<input name="post[#{pid}][title]" type="radio" value="Goodbye World" />}, radio_button("post[]", "title", "Goodbye World", id: nil) @@ -1550,35 +1482,35 @@ class FormHelperTest < ActionView::TestCase def test_form_for_requires_block error = assert_raises(ArgumentError) do - form_for(@post, html: { id: 'create-post' }) + form_for(@post, html: { id: "create-post" }) end assert_equal "Missing block", error.message end def test_form_for_requires_arguments error = assert_raises(ArgumentError) do - form_for(nil, html: { id: 'create-post' }) do + form_for(nil, html: { id: "create-post" }) do end end assert_equal "First argument in form cannot contain nil or be empty", error.message error = assert_raises(ArgumentError) do - form_for([nil, nil], html: { id: 'create-post' }) do + form_for([nil, nil], html: { id: "create-post" }) do end end assert_equal "First argument in form cannot contain nil or be empty", error.message end def test_form_for - form_for(@post, html: { id: 'create-post' }) do |f| + form_for(@post, html: { id: "create-post" }) do |f| concat f.label(:title) { "The Title" } concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) - concat f.submit('Create post') - concat f.button('Create post') + concat f.submit("Create post") + concat f.button("Create post") concat f.button { - concat content_tag(:span, 'Create post') + concat content_tag(:span, "Create post") } end @@ -1669,7 +1601,7 @@ class FormHelperTest < ActionView::TestCase post = Post.new def post.active; false; end - form_for(post, namespace: 'foo') do |f| + form_for(post, namespace: "foo") do |f| concat f.collection_radio_buttons(:active, [true, false], :to_s, :to_s) end @@ -1688,7 +1620,7 @@ class FormHelperTest < ActionView::TestCase post = Post.new def post.active; false; end - form_for(post, index: '1') do |f| + form_for(post, index: "1") do |f| concat f.collection_radio_buttons(:active, [true, false], :to_s, :to_s) end @@ -1787,7 +1719,7 @@ class FormHelperTest < ActionView::TestCase def post.tag_ids; [1]; end collection = [[1, "Tag 1"]] - form_for(post, namespace: 'foo') do |f| + form_for(post, namespace: "foo") do |f| concat f.collection_check_boxes(:tag_ids, collection, :first, :last) end @@ -1805,7 +1737,7 @@ class FormHelperTest < ActionView::TestCase def post.tag_ids; [1]; end collection = [[1, "Tag 1"]] - form_for(post, index: '1') do |f| + form_for(post, index: "1") do |f| concat f.collection_check_boxes(:tag_ids, collection, :first, :last) end @@ -1821,12 +1753,12 @@ class FormHelperTest < ActionView::TestCase def test_form_for_with_file_field_generate_multipart Post.send :attr_accessor, :file - form_for(@post, html: { id: 'create-post' }) do |f| + form_for(@post, html: { id: "create-post" }) do |f| concat f.file_field(:file) end expected = whole_form("/posts/123", "create-post", "edit_post", method: "patch", multipart: true) do - "<input name='post[file]' type='hidden' value='' /><input name='post[file]' type='file' id='post_file' />" + "<input name='post[file]' type='file' id='post_file' />" end assert_dom_equal expected, output_buffer @@ -1842,7 +1774,7 @@ class FormHelperTest < ActionView::TestCase end expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch", multipart: true) do - "<input name='post[comment][file]' type='hidden' value='' /><input name='post[comment][file]' type='file' id='post_comment_file' />" + "<input name='post[comment][file]' type='file' id='post_comment_file' />" end assert_dom_equal expected, output_buffer @@ -1853,7 +1785,7 @@ class FormHelperTest < ActionView::TestCase concat f.label(:title) end - expected = whole_form("/posts/123.json", "edit_post_123", "edit_post", method: 'patch') do + expected = whole_form("/posts/123.json", "edit_post_123", "edit_post", method: "patch") do "<label for='post_title'>Title</label>" end @@ -1865,7 +1797,7 @@ class FormHelperTest < ActionView::TestCase form_for(blog_post) do |f| concat f.text_field :title - concat f.submit('Edit post') + concat f.submit("Edit post") end expected = whole_form("/posts/44", "edit_post_44", "edit_post", method: "patch") do @@ -1876,27 +1808,13 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end - def test_lowercase_model_name_default_submit_button_value - form_for(@ticket_type) do |f| - concat f.submit - end - - expected = - '<form class="new_ticket_type" id="new_ticket_type" action="/ticket_types" accept-charset="UTF-8" method="post">' + - hidden_fields + - '<input type="submit" name="commit" value="Create ticket type" data-disable-with="Create ticket type" />' + - '</form>' - - assert_dom_equal expected, output_buffer - end - - def test_form_for_with_symbol_object_name + def test_form_for_with_symbol_as form_for(@post, as: "other_name", html: { id: "create-post" }) do |f| - concat f.label(:title, class: 'post_title') + concat f.label(:title, class: "post_title") concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) - concat f.submit('Create post') + concat f.submit("Create post") end expected = whole_form("/posts/123", "create-post", "edit_other_name", method: "patch") do @@ -1920,13 +1838,13 @@ class FormHelperTest < ActionView::TestCase end end.new - form_for(obj, as: "other_name", url: '/', html: { id: "edit-other-name" }) do |f| + form_for(obj, as: "other_name", url: "/", html: { id: "edit-other-name" }) do |f| assert_raise(NoMethodError) { f.hidden_field(:private_property) } end end def test_form_for_with_method_as_part_of_html_options - form_for(@post, url: '/', html: { id: 'create-post', method: :delete }) do |f| + form_for(@post, url: "/", html: { id: "create-post", method: :delete }) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -1943,7 +1861,7 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_method - form_for(@post, url: '/', method: :delete, html: { id: 'create-post' }) do |f| + form_for(@post, url: "/", method: :delete, html: { id: "create-post" }) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -1974,7 +1892,7 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_remote - form_for(@post, url: '/', remote: true, html: { id: 'create-post', method: :patch }) do |f| + form_for(@post, url: "/", remote: true, html: { id: "create-post", method: :patch }) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -2015,7 +1933,7 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_remote_in_html - form_for(@post, url: '/', html: { remote: true, id: 'create-post', method: :patch }) do |f| + form_for(@post, url: "/", html: { remote: true, id: "create-post", method: :patch }) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -2052,7 +1970,7 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_without_object - form_for(:post, html: { id: 'create-post' }) do |f| + form_for(:post, html: { id: "create-post" }) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -2076,7 +1994,7 @@ class FormHelperTest < ActionView::TestCase concat f.check_box(:secret) end - expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', method: 'patch') do + 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>" + @@ -2094,7 +2012,7 @@ class FormHelperTest < ActionView::TestCase concat f.check_box(:secret) end - expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', method: 'patch') do + 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' />" + @@ -2106,12 +2024,12 @@ class FormHelperTest < ActionView::TestCase def test_form_for_label_error_wrapping form_for(@post) do |f| - concat f.label(:author_name, class: 'label') + concat f.label(:author_name, class: "label") concat f.text_field(:author_name) - concat f.submit('Create post') + concat f.submit("Create post") end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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>" + "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />" @@ -2124,12 +2042,12 @@ class FormHelperTest < ActionView::TestCase post = remove_instance_variable :@post form_for(post) do |f| - concat f.label(:author_name, class: 'label') + concat f.label(:author_name, class: "label") concat f.text_field(:author_name) - concat f.submit('Create post') + concat f.submit("Create post") end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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>" + "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />" @@ -2140,11 +2058,11 @@ class FormHelperTest < ActionView::TestCase def test_form_for_label_error_wrapping_block_and_non_block_versions form_for(@post) do |f| - concat f.label(:author_name, 'Name', class: 'label') - concat f.label(:author_name, class: 'label') { 'Name' } + concat f.label(:author_name, "Name", class: "label") + concat f.label(:author_name, class: "label") { "Name" } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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>" end @@ -2153,13 +2071,13 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_namespace - form_for(@post, namespace: 'namespace') do |f| + form_for(@post, namespace: "namespace") do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) end - expected = whole_form('/posts/123', 'namespace_edit_post_123', 'edit_post', method: 'patch') do + 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' />" + @@ -2170,20 +2088,20 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_namespace_with_date_select - form_for(@post, namespace: 'namespace') do |f| + form_for(@post, namespace: "namespace") do |f| concat f.date_select(:written_on) end - assert_select 'select#namespace_post_written_on_1i' + assert_select "select#namespace_post_written_on_1i" end def test_form_for_with_namespace_with_label - form_for(@post, namespace: 'namespace') do |f| + form_for(@post, namespace: "namespace") do |f| concat f.label(:title) concat f.text_field(:title) end - expected = whole_form('/posts/123', 'namespace_edit_post_123', 'edit_post', method: 'patch') do + expected = whole_form("/posts/123", "namespace_edit_post_123", "edit_post", method: "patch") do "<label for='namespace_post_title'>Title</label>" + "<input name='post[title]' type='text' id='namespace_post_title' value='Hello World' />" end @@ -2192,11 +2110,11 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_namespace_and_as_option - form_for(@post, namespace: 'namespace', as: 'custom_name') do |f| + form_for(@post, namespace: "namespace", as: "custom_name") do |f| concat f.text_field(:title) end - expected = whole_form('/posts/123', 'namespace_edit_custom_name', 'edit_custom_name', method: 'patch') do + expected = whole_form("/posts/123", "namespace_edit_custom_name", "edit_custom_name", method: "patch") do "<input id='namespace_custom_name_title' name='custom_name[title]' type='text' value='Hello World' />" end @@ -2204,24 +2122,24 @@ class FormHelperTest < ActionView::TestCase end def test_two_form_for_with_namespace - form_for(@post, namespace: 'namespace_1') do |f| + form_for(@post, namespace: "namespace_1") do |f| concat f.label(:title) concat f.text_field(:title) end - expected_1 = whole_form('/posts/123', 'namespace_1_edit_post_123', 'edit_post', method: 'patch') do + expected_1 = whole_form("/posts/123", "namespace_1_edit_post_123", "edit_post", method: "patch") do "<label for='namespace_1_post_title'>Title</label>" + "<input name='post[title]' type='text' id='namespace_1_post_title' value='Hello World' />" end assert_dom_equal expected_1, output_buffer - form_for(@post, namespace: 'namespace_2') do |f| + form_for(@post, namespace: "namespace_2") do |f| concat f.label(:title) concat f.text_field(:title) end - expected_2 = whole_form('/posts/123', 'namespace_2_edit_post_123', 'edit_post', method: 'patch') do + expected_2 = whole_form("/posts/123", "namespace_2_edit_post_123", "edit_post", method: "patch") do "<label for='namespace_2_post_title'>Title</label>" + "<input name='post[title]' type='text' id='namespace_2_post_title' value='Hello World' />" end @@ -2230,8 +2148,8 @@ class FormHelperTest < ActionView::TestCase end def test_fields_for_with_namespace - @comment.body = 'Hello World' - form_for(@post, namespace: 'namespace') do |f| + @comment.body = "Hello World" + form_for(@post, namespace: "namespace") do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.fields_for(@comment) { |c| @@ -2239,7 +2157,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'namespace_edit_post_123', 'edit_post', method: 'patch') do + 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[comment][body]' type='text' id='namespace_post_comment_body' value='Hello World' />" @@ -2256,8 +2174,8 @@ class FormHelperTest < ActionView::TestCase concat f.submit end - expected = whole_form('/posts', 'new_post', 'new_post') do - "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />" + expected = whole_form("/posts", "new_post", "new_post") do + "<input name='commit' data-disable-with='Create Post' type='submit' value='Create Post' />" end assert_dom_equal expected, output_buffer @@ -2271,8 +2189,8 @@ class FormHelperTest < ActionView::TestCase concat f.submit end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do - "<input name='commit' data-disable-with='Confirm post changes' type='submit' value='Confirm post changes' />" + expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do + "<input name='commit' data-disable-with='Confirm Post changes' type='submit' value='Confirm Post changes' />" end assert_dom_equal expected, output_buffer @@ -2286,7 +2204,7 @@ class FormHelperTest < ActionView::TestCase end expected = whole_form do - "<input name='commit' class='extra' data-disable-with='Save changes' type='submit' value='Save changes' />" + "<input name='commit' class='extra' data-disable-with='Save changes' type='submit' value='Save changes' />" end assert_dom_equal expected, output_buffer @@ -2299,8 +2217,8 @@ class FormHelperTest < ActionView::TestCase concat f.submit end - expected = whole_form('/posts/123', 'edit_another_post', 'edit_another_post', method: 'patch') do - "<input name='commit' data-disable-with='Update your post' type='submit' value='Update your post' />" + expected = whole_form("/posts/123", "edit_another_post", "edit_another_post", method: "patch") do + "<input name='commit' data-disable-with='Update your Post' type='submit' value='Update your Post' />" end assert_dom_equal expected, output_buffer @@ -2308,14 +2226,14 @@ class FormHelperTest < ActionView::TestCase end def test_nested_fields_for - @comment.body = 'Hello World' + @comment.body = "Hello World" form_for(@post) do |f| concat f.fields_for(@comment) { |c| concat c.text_field(:body) } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do "<input name='post[comment][body]' type='text' id='post_comment_body' value='Hello World' />" end @@ -2325,10 +2243,10 @@ class FormHelperTest < ActionView::TestCase def test_deep_nested_fields_for @comment.save form_for(:posts) do |f| - f.fields_for('post[]', @post) do |f2| + f.fields_for("post[]", @post) do |f2| f2.text_field(:id) @post.comments.each do |comment| - concat f2.fields_for('comment[]', comment) { |c| + concat f2.fields_for("comment[]", comment) { |c| concat c.text_field(:name) } end @@ -2342,16 +2260,15 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end - def test_nested_fields_for_with_nested_collections - form_for(@post, as: 'post[]') do |f| + form_for(@post, as: "post[]") do |f| concat f.text_field(:title) - concat f.fields_for('comment[]', @comment) { |c| + concat f.fields_for("comment[]", @comment) { |c| concat c.text_field(:name) } end - expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', method: 'patch') do + 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' />" end @@ -2362,12 +2279,12 @@ class FormHelperTest < ActionView::TestCase def test_nested_fields_for_with_index_and_parent_fields form_for(@post, index: 1) do |c| concat c.text_field(:title) - concat c.fields_for('comment', @comment, index: 1) { |r| + concat c.fields_for("comment", @comment, index: 1) { |r| concat r.text_field(:name) } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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][comment][1][name]' type='text' id='post_1_comment_1_name' value='new comment' />" end @@ -2382,7 +2299,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do "<input name='post[1][comment][title]' type='text' id='post_1_comment_title' value='Hello World' />" end @@ -2396,7 +2313,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do "<input name='post[1][comment][5][title]' type='text' id='post_1_comment_5_title' value='Hello World' />" end @@ -2410,7 +2327,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', method: 'patch') do + expected = whole_form("/posts/123", "edit_post[]", "edit_post[]", method: "patch") do "<input name='post[123][comment][title]' type='text' id='post_123_comment_title' value='Hello World' />" end @@ -2424,7 +2341,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do "<input name='post[comment][5][title]' type='radio' id='post_comment_5_title_hello' value='hello' />" end @@ -2438,7 +2355,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', method: 'patch') do + expected = whole_form("/posts/123", "edit_post[]", "edit_post[]", method: "patch") do "<input name='post[123][comment][123][title]' type='text' id='post_123_comment_123_title' value='Hello World' />" end @@ -2458,9 +2375,9 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', method: 'patch') do + expected = whole_form("/posts/123", "edit_post[]", "edit_post[]", method: "patch") do "<input name='post[123][comment][5][title]' type='text' id='post_123_comment_5_title' value='Hello World' />" - end + whole_form('/posts/123', 'edit_post', 'edit_post', method: 'patch') do + end + whole_form("/posts/123", "edit_post", "edit_post", method: "patch") do "<input name='post[1][comment][123][title]' type='text' id='post_1_comment_123_title' value='Hello World' />" end @@ -2477,7 +2394,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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="new author" />' end @@ -2504,11 +2421,11 @@ 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" />' - 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" />' + end assert_dom_equal expected, output_buffer end @@ -2523,7 +2440,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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" />' @@ -2542,7 +2459,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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" />' end @@ -2560,7 +2477,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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" />' end @@ -2578,7 +2495,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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" />' @@ -2598,7 +2515,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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 id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' @@ -2619,7 +2536,7 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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" />' + @@ -2646,7 +2563,7 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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" />' + @@ -2673,7 +2590,7 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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" />' + @@ -2699,7 +2616,7 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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" />' + @@ -2722,7 +2639,7 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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" />' + @@ -2746,7 +2663,7 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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" />' + @@ -2769,7 +2686,7 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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 id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="new comment" />' @@ -2790,7 +2707,7 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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" />' + @@ -2808,7 +2725,7 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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" />' end @@ -2825,7 +2742,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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" />' + @@ -2846,7 +2763,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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" />' + @@ -2860,7 +2777,7 @@ class FormHelperTest < ActionView::TestCase def test_nested_fields_label_translation_with_more_than_10_records @post.comments = Array.new(11) { |id| Comment.new(id + 1) } - params = 11.times.map { ['post.comments.body', default: [:"comment.body", ''], scope: "helpers.label"] } + params = 11.times.map { ["post.comments.body", default: [:"comment.body", ""], scope: "helpers.label"] } assert_called_with(I18n, :t, params, returns: "Write body here") do form_for(@post) do |f| f.fields_for(:comments) do |cf| @@ -2881,7 +2798,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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" />' + @@ -2904,7 +2821,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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" />' + @@ -2919,12 +2836,12 @@ class FormHelperTest < ActionView::TestCase @post.comments = [] form_for(@post) do |f| - concat f.fields_for(:comments, Comment.new(321), child_index: 'abc') { |cf| + concat f.fields_for(:comments, Comment.new(321), child_index: "abc") { |cf| concat cf.text_field(:name) } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />' end @@ -2936,12 +2853,12 @@ class FormHelperTest < ActionView::TestCase @post.comments = [] form_for(@post) do |f| - concat f.fields_for(:comments, Comment.new(321), child_index: -> { 'abc' } ) { |cf| + concat f.fields_for(:comments, Comment.new(321), child_index: -> { "abc" } ) { |cf| concat cf.text_field(:name) } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />' end @@ -2959,12 +2876,12 @@ class FormHelperTest < ActionView::TestCase @post.comments = FakeAssociationProxy.new form_for(@post) do |f| - concat f.fields_for(:comments, Comment.new(321), child_index: 'abc') { |cf| + concat f.fields_for(:comments, Comment.new(321), child_index: "abc") { |cf| concat cf.text_field(:name) } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />' end @@ -3016,8 +2933,8 @@ class FormHelperTest < ActionView::TestCase @post.comments = [] form_for(@post) do |f| - f.fields_for(:comments, Comment.new(321), child_index: 'abc') { |cf| - assert_equal cf.index, 'abc' + f.fields_for(:comments, Comment.new(321), child_index: "abc") { |cf| + assert_equal cf.index, "abc" } end end @@ -3042,7 +2959,7 @@ class FormHelperTest < ActionView::TestCase concat trf.text_field(:value) } } - concat f.fields_for('tags', @post.tags[1]) { |tf| + concat f.fields_for("tags", @post.tags[1]) { |tf| concat tf.text_field(:value) concat tf.fields_for(:relevances, TagRelevance.new(31415)) { |trf| concat trf.text_field(:value) @@ -3050,7 +2967,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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" />' + @@ -3077,7 +2994,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="hash backed author" />' end @@ -3203,11 +3120,11 @@ class FormHelperTest < ActionView::TestCase end def test_form_builder_does_not_have_form_for_method - assert !ActionView::Helpers::FormBuilder.instance_methods.include?(:form_for) + assert_not_includes ActionView::Helpers::FormBuilder.instance_methods, :form_for end def test_form_for_and_fields_for - form_for(@post, as: :post, html: { id: 'create-post' }) do |post_form| + form_for(@post, as: :post, html: { id: "create-post" }) do |post_form| concat post_form.text_field(:title) concat post_form.text_area(:body) @@ -3216,7 +3133,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'create-post', 'edit_post', method: 'patch') do + 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' />" + @@ -3227,7 +3144,7 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_and_fields_for_with_object - form_for(@post, as: :post, html: { id: 'create-post' }) do |post_form| + form_for(@post, as: :post, html: { id: "create-post" }) do |post_form| concat post_form.text_field(:title) concat post_form.text_area(:body) @@ -3236,7 +3153,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'create-post', 'edit_post', method: 'patch') do + 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[comment][name]' type='text' id='post_comment_name' value='new comment' />" @@ -3252,7 +3169,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do "<input name='post[category][name]' type='text' id='post_category_name' />" end @@ -3276,7 +3193,7 @@ class FormHelperTest < ActionView::TestCase concat f.check_box(:secret) end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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='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/>" @@ -3295,7 +3212,7 @@ class FormHelperTest < ActionView::TestCase concat f.check_box(:secret) end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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='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/>" @@ -3314,7 +3231,7 @@ class FormHelperTest < ActionView::TestCase concat f.text_field(:title) end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + 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/>" end @@ -3368,7 +3285,7 @@ class FormHelperTest < ActionView::TestCase form_for(@post, builder: LabelledFormBuilder) do |f| f.fields_for(:comments, Comment.new) do |nested_fields| klass = nested_fields.class - '' + "" end end @@ -3379,9 +3296,9 @@ class FormHelperTest < ActionView::TestCase klass = nil form_for(@post, builder: LabelledFormBuilder) do |f| - f.fields_for(:comments, Comment.new, index: 'foo') do |nested_fields| + f.fields_for(:comments, Comment.new, index: "foo") do |nested_fields| klass = nested_fields.class - '' + "" end end @@ -3393,10 +3310,10 @@ class FormHelperTest < ActionView::TestCase form_for(@post, builder: LabelledFormBuilder) do |f| path = f.to_partial_path - '' + "" end - assert_equal 'labelled_form', path + assert_equal "labelled_form", path end class LabelledFormBuilderSubclass < LabelledFormBuilder; end @@ -3407,7 +3324,7 @@ class FormHelperTest < ActionView::TestCase form_for(@post, builder: LabelledFormBuilder) do |f| f.fields_for(:comments, Comment.new, builder: LabelledFormBuilderSubclass) do |nested_fields| klass = nested_fields.class - '' + "" end end @@ -3415,23 +3332,23 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_html_options_adds_options_to_form_tag - form_for(@post, html: { id: 'some_form', class: 'some_class', multipart: true }) do |f| end + form_for(@post, html: { id: "some_form", class: "some_class", multipart: true }) do |f| end expected = whole_form("/posts/123", "some_form", "some_class", method: "patch", multipart: "multipart/form-data") assert_dom_equal expected, output_buffer end def test_form_for_with_string_url_option - form_for(@post, url: 'http://www.otherdomain.com') do |f| end + form_for(@post, url: "http://www.otherdomain.com") do |f| end assert_dom_equal whole_form("http://www.otherdomain.com", "edit_post_123", "edit_post", method: "patch"), output_buffer end def test_form_for_with_hash_url_option - form_for(@post, url: { controller: 'controller', action: 'action' }) do |f| end + form_for(@post, url: { controller: "controller", action: "action" }) do |f| end - assert_equal 'controller', @url_for_options[:controller] - assert_equal 'action', @url_for_options[:action] + assert_equal "controller", @url_for_options[:controller] + assert_equal "action", @url_for_options[:action] end def test_form_for_with_record_url_option @@ -3522,54 +3439,54 @@ class FormHelperTest < ActionView::TestCase end end - form_for(@post, builder: builder_class) { } - assert_equal 1, initialization_count, 'form builder instantiated more than once' + form_for(@post, builder: builder_class) {} + assert_equal 1, initialization_count, "form builder instantiated more than once" end protected - def hidden_fields(options = {}) - method = options[:method] + def hidden_fields(options = {}) + method = options[:method] - if options.fetch(:enforce_utf8, true) - txt = %{<input name="utf8" type="hidden" value="✓" />} - else - txt = '' - end + if options.fetch(:enforce_utf8, true) + txt = %{<input name="utf8" type="hidden" value="✓" />} + else + txt = "" + end - if method && !%w(get post).include?(method.to_s) - txt << %{<input name="_method" type="hidden" value="#{method}" />} - end + if method && !%w(get post).include?(method.to_s) + txt << %{<input name="_method" type="hidden" value="#{method}" />} + end - txt - end + txt + end - def form_text(action = "/", id = nil, html_class = nil, remote = nil, multipart = nil, method = nil) - txt = %{<form accept-charset="UTF-8" action="#{action}"} - txt << %{ enctype="multipart/form-data"} if multipart - txt << %{ data-remote="true"} if remote - txt << %{ class="#{html_class}"} if html_class - txt << %{ id="#{id}"} if id - method = method.to_s == "get" ? "get" : "post" - txt << %{ method="#{method}">} - end + def form_text(action = "/", id = nil, html_class = nil, remote = nil, multipart = nil, method = nil) + txt = %{<form accept-charset="UTF-8" action="#{action}"} + txt << %{ enctype="multipart/form-data"} if multipart + txt << %{ data-remote="true"} if remote + txt << %{ class="#{html_class}"} if html_class + txt << %{ id="#{id}"} if id + method = method.to_s == "get" ? "get" : "post" + txt << %{ method="#{method}">} + end - def whole_form(action = "/", id = nil, html_class = nil, options = {}) - contents = block_given? ? yield : "" + def whole_form(action = "/", id = nil, html_class = nil, options = {}) + contents = block_given? ? yield : "" - method, remote, multipart = options.values_at(:method, :remote, :multipart) + method, remote, multipart = options.values_at(:method, :remote, :multipart) - form_text(action, id, html_class, remote, multipart, method) + hidden_fields(options.slice :method, :enforce_utf8) + contents + "</form>" - end + form_text(action, id, html_class, remote, multipart, method) + hidden_fields(options.slice :method, :enforce_utf8) + contents + "</form>" + end - def protect_against_forgery? - false - end + def protect_against_forgery? + false + end - def with_locale(testing_locale = :label) - old_locale, I18n.locale = I18n.locale, testing_locale - yield - ensure - I18n.locale = old_locale - end + def with_locale(testing_locale = :label) + old_locale, I18n.locale = I18n.locale, testing_locale + yield + ensure + I18n.locale = old_locale + end end diff --git a/actionview/test/template/form_options_helper_i18n_test.rb b/actionview/test/template/form_options_helper_i18n_test.rb index 26ede09a5f..a1048fbb89 100644 --- a/actionview/test/template/form_options_helper_i18n_test.rb +++ b/actionview/test/template/form_options_helper_i18n_test.rb @@ -1,12 +1,12 @@ -require 'abstract_unit' +require "abstract_unit" class FormOptionsHelperI18nTests < ActionView::TestCase tests ActionView::Helpers::FormOptionsHelper def setup - @prompt_message = 'Select!' + @prompt_message = "Select!" I18n.backend.send(:init_translations) - I18n.backend.store_translations :en, :helpers => { :select => { :prompt => @prompt_message } } + I18n.backend.store_translations :en, helpers: { select: { prompt: @prompt_message } } end def teardown @@ -14,15 +14,15 @@ class FormOptionsHelperI18nTests < ActionView::TestCase end def test_select_with_prompt_true_translates_prompt_message - assert_called_with(I18n, :translate, ['helpers.select.prompt', { :default => 'Please select' }]) do - select('post', 'category', [], :prompt => true) + assert_called_with(I18n, :translate, ["helpers.select.prompt", { default: "Please select" }]) do + select("post", "category", [], prompt: true) end end def test_select_with_translated_prompt assert_dom_equal( %Q(<select id="post_category" name="post[category]"><option value="">#{@prompt_message}</option>\n</select>), - select('post', 'category', [], :prompt => true) + select("post", "category", [], prompt: true) ) end end diff --git a/actionview/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb index c5b63d33f1..477d4f9eca 100644 --- a/actionview/test/template/form_options_helper_test.rb +++ b/actionview/test/template/form_options_helper_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class Map < Hash def category @@ -10,11 +10,11 @@ class FormOptionsHelperTest < ActionView::TestCase tests ActionView::Helpers::FormOptionsHelper silence_warnings do - Post = Struct.new('Post', :title, :author_name, :body, :secret, :written_on, :category, :origin, :allow_comments) - Continent = Struct.new('Continent', :continent_name, :countries) - Country = Struct.new('Country', :country_id, :country_name) - Firm = Struct.new('Firm', :time_zone) - Album = Struct.new('Album', :id, :title, :genre) + Post = Struct.new("Post", :title, :author_name, :body, :secret, :written_on, :category, :origin, :allow_comments) + Continent = Struct.new("Continent", :continent_name, :countries) + Country = Struct.new("Country", :country_id, :country_name) + Firm = Struct.new("Firm", :time_zone) + Album = Struct.new("Album", :id, :title, :genre) end module FakeZones @@ -57,7 +57,6 @@ class FormOptionsHelperTest < ActionView::TestCase ) end - def test_collection_options_with_preselected_value assert_dom_equal( "<option value=\"<Abe>\"><Abe> went home</option>\n<option value=\"Babe\" selected=\"selected\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option>", @@ -66,44 +65,44 @@ class FormOptionsHelperTest < ActionView::TestCase end def test_collection_options_with_preselected_value_array - assert_dom_equal( - "<option value=\"<Abe>\"><Abe> went home</option>\n<option value=\"Babe\" selected=\"selected\">Babe went home</option>\n<option value=\"Cabe\" selected=\"selected\">Cabe went home</option>", - options_from_collection_for_select(dummy_posts, "author_name", "title", [ "Babe", "Cabe" ]) - ) + assert_dom_equal( + "<option value=\"<Abe>\"><Abe> went home</option>\n<option value=\"Babe\" selected=\"selected\">Babe went home</option>\n<option value=\"Cabe\" selected=\"selected\">Cabe went home</option>", + options_from_collection_for_select(dummy_posts, "author_name", "title", [ "Babe", "Cabe" ]) + ) end def test_collection_options_with_proc_for_selected assert_dom_equal( "<option value=\"<Abe>\"><Abe> went home</option>\n<option value=\"Babe\" selected=\"selected\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option>", - options_from_collection_for_select(dummy_posts, "author_name", "title", lambda{|p| p.author_name == 'Babe' }) + options_from_collection_for_select(dummy_posts, "author_name", "title", lambda { |p| p.author_name == "Babe" }) ) end def test_collection_options_with_disabled_value assert_dom_equal( "<option value=\"<Abe>\"><Abe> went home</option>\n<option value=\"Babe\" disabled=\"disabled\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option>", - options_from_collection_for_select(dummy_posts, "author_name", "title", :disabled => "Babe") + options_from_collection_for_select(dummy_posts, "author_name", "title", disabled: "Babe") ) end def test_collection_options_with_disabled_array assert_dom_equal( "<option value=\"<Abe>\"><Abe> went home</option>\n<option value=\"Babe\" disabled=\"disabled\">Babe went home</option>\n<option value=\"Cabe\" disabled=\"disabled\">Cabe went home</option>", - options_from_collection_for_select(dummy_posts, "author_name", "title", :disabled => [ "Babe", "Cabe" ]) + options_from_collection_for_select(dummy_posts, "author_name", "title", disabled: [ "Babe", "Cabe" ]) ) end def test_collection_options_with_preselected_and_disabled_value assert_dom_equal( "<option value=\"<Abe>\"><Abe> went home</option>\n<option value=\"Babe\" disabled=\"disabled\">Babe went home</option>\n<option value=\"Cabe\" selected=\"selected\">Cabe went home</option>", - options_from_collection_for_select(dummy_posts, "author_name", "title", :selected => "Cabe", :disabled => "Babe") + options_from_collection_for_select(dummy_posts, "author_name", "title", selected: "Cabe", disabled: "Babe") ) end def test_collection_options_with_proc_for_disabled assert_dom_equal( "<option value=\"<Abe>\"><Abe> went home</option>\n<option value=\"Babe\" disabled=\"disabled\">Babe went home</option>\n<option value=\"Cabe\" disabled=\"disabled\">Cabe went home</option>", - options_from_collection_for_select(dummy_posts, "author_name", "title", :disabled => lambda {|p| %w(Babe Cabe).include?(p.author_name)}) + options_from_collection_for_select(dummy_posts, "author_name", "title", disabled: lambda { |p| %w(Babe Cabe).include?(p.author_name) }) ) end @@ -124,7 +123,7 @@ class FormOptionsHelperTest < ActionView::TestCase def test_collection_options_with_element_attributes assert_dom_equal( "<option value=\"USA\" class=\"bold\">USA</option>", - options_from_collection_for_select([[ "USA", "USA", { :class => 'bold' } ]], :first, :second) + options_from_collection_for_select([[ "USA", "USA", { class: "bold" } ]], :first, :second) ) end @@ -147,8 +146,8 @@ class FormOptionsHelperTest < ActionView::TestCase assert_dom_equal( "<option selected=\"selected\" type=\"Coach\" value=\"1\">Richard Bandler</option>\n<option type=\"Coachee\" value=\"1\">Richard Bandler</option>", options_for_select([ - ['Richard Bandler', 1, { type: 'Coach', selected: 'selected' }], - ['Richard Bandler', 1, { type: 'Coachee' }] + ["Richard Bandler", 1, { type: "Coach", selected: "selected" }], + ["Richard Bandler", 1, { type: "Coachee" }] ]) ) end @@ -157,8 +156,8 @@ class FormOptionsHelperTest < ActionView::TestCase assert_dom_equal( "<option disabled=\"disabled\" type=\"Coach\" value=\"1\">Richard Bandler</option>\n<option type=\"Coachee\" value=\"1\">Richard Bandler</option>", options_for_select([ - ['Richard Bandler', 1, { type: 'Coach', disabled: 'disabled' }], - ['Richard Bandler', 1, { type: 'Coachee' }] + ["Richard Bandler", 1, { type: "Coach", disabled: "disabled" }], + ["Richard Bandler", 1, { type: "Coachee" }] ]) ) end @@ -171,37 +170,37 @@ class FormOptionsHelperTest < ActionView::TestCase end def test_array_options_for_select_with_selection_array - assert_dom_equal( - "<option value=\"Denmark\">Denmark</option>\n<option value=\"<USA>\" selected=\"selected\"><USA></option>\n<option value=\"Sweden\" selected=\"selected\">Sweden</option>", - options_for_select([ "Denmark", "<USA>", "Sweden" ], [ "<USA>", "Sweden" ]) - ) + assert_dom_equal( + "<option value=\"Denmark\">Denmark</option>\n<option value=\"<USA>\" selected=\"selected\"><USA></option>\n<option value=\"Sweden\" selected=\"selected\">Sweden</option>", + options_for_select([ "Denmark", "<USA>", "Sweden" ], [ "<USA>", "Sweden" ]) + ) end def test_array_options_for_select_with_disabled_value assert_dom_equal( "<option value=\"Denmark\">Denmark</option>\n<option value=\"<USA>\" disabled=\"disabled\"><USA></option>\n<option value=\"Sweden\">Sweden</option>", - options_for_select([ "Denmark", "<USA>", "Sweden" ], :disabled => "<USA>") + options_for_select([ "Denmark", "<USA>", "Sweden" ], disabled: "<USA>") ) end def test_array_options_for_select_with_disabled_array assert_dom_equal( "<option value=\"Denmark\">Denmark</option>\n<option value=\"<USA>\" disabled=\"disabled\"><USA></option>\n<option value=\"Sweden\" disabled=\"disabled\">Sweden</option>", - options_for_select([ "Denmark", "<USA>", "Sweden" ], :disabled => ["<USA>", "Sweden"]) + options_for_select([ "Denmark", "<USA>", "Sweden" ], disabled: ["<USA>", "Sweden"]) ) end def test_array_options_for_select_with_selection_and_disabled_value assert_dom_equal( "<option value=\"Denmark\" selected=\"selected\">Denmark</option>\n<option value=\"<USA>\" disabled=\"disabled\"><USA></option>\n<option value=\"Sweden\">Sweden</option>", - options_for_select([ "Denmark", "<USA>", "Sweden" ], :selected => "Denmark", :disabled => "<USA>") + options_for_select([ "Denmark", "<USA>", "Sweden" ], selected: "Denmark", disabled: "<USA>") ) end def test_boolean_array_options_for_select_with_selection_and_disabled_value assert_dom_equal( "<option value=\"true\">true</option>\n<option value=\"false\" selected=\"selected\">false</option>", - options_for_select([ true, false ], :selected => false, :disabled => nil) + options_for_select([ true, false ], selected: false, disabled: nil) ) end @@ -213,18 +212,18 @@ class FormOptionsHelperTest < ActionView::TestCase end def test_array_options_for_string_include_in_other_string_bug_fix - assert_dom_equal( - "<option value=\"ruby\">ruby</option>\n<option value=\"rubyonrails\" selected=\"selected\">rubyonrails</option>", - options_for_select([ "ruby", "rubyonrails" ], "rubyonrails") - ) - assert_dom_equal( - "<option value=\"ruby\" selected=\"selected\">ruby</option>\n<option value=\"rubyonrails\">rubyonrails</option>", - options_for_select([ "ruby", "rubyonrails" ], "ruby") - ) - assert_dom_equal( - %(<option value="ruby" selected="selected">ruby</option>\n<option value="rubyonrails">rubyonrails</option>\n<option value=""></option>), - options_for_select([ "ruby", "rubyonrails", nil ], "ruby") - ) + assert_dom_equal( + "<option value=\"ruby\">ruby</option>\n<option value=\"rubyonrails\" selected=\"selected\">rubyonrails</option>", + options_for_select([ "ruby", "rubyonrails" ], "rubyonrails") + ) + assert_dom_equal( + "<option value=\"ruby\" selected=\"selected\">ruby</option>\n<option value=\"rubyonrails\">rubyonrails</option>", + options_for_select([ "ruby", "rubyonrails" ], "ruby") + ) + assert_dom_equal( + %(<option value="ruby" selected="selected">ruby</option>\n<option value="rubyonrails">rubyonrails</option>\n<option value=""></option>), + options_for_select([ "ruby", "rubyonrails", nil ], "ruby") + ) end def test_hash_options_for_select @@ -262,7 +261,7 @@ class FormOptionsHelperTest < ActionView::TestCase albums = [ Album.new(1, "first","rap"), Album.new(2, "second","pop")] assert_dom_equal( %(<option selected="selected" value="1">rap</option>\n<option value="2">pop</option>), - options_from_collection_for_select(albums, "id", "genre", :selected => "1") + options_from_collection_for_select(albums, "id", "genre", selected: "1") ) end @@ -271,7 +270,7 @@ class FormOptionsHelperTest < ActionView::TestCase assert_dom_equal( %(<option selected="selected" value="1">rap</option>\n<option value="2">pop</option>), - options_from_collection_for_select(albums, "id", "genre", :selected => 1) + options_from_collection_for_select(albums, "id", "genre", selected: 1) ) end @@ -280,7 +279,7 @@ class FormOptionsHelperTest < ActionView::TestCase assert_dom_equal( %(<option value="1.0">rap</option>\n<option value="2.0" selected="selected">pop</option>), - options_from_collection_for_select(albums, "id", "genre", :selected => "2.0") + options_from_collection_for_select(albums, "id", "genre", selected: "2.0") ) end @@ -289,7 +288,7 @@ class FormOptionsHelperTest < ActionView::TestCase assert_dom_equal( %(<option value="1.0">rap</option>\n<option value="2.0">pop</option>), - options_from_collection_for_select(albums, "id", "genre", :selected => nil) + options_from_collection_for_select(albums, "id", "genre", selected: nil) ) end @@ -298,7 +297,7 @@ class FormOptionsHelperTest < ActionView::TestCase assert_dom_equal( %(<option value="1.0">rap</option>\n<option value="2.0">pop</option>), - options_from_collection_for_select(albums, "id", "genre", :disabled => nil) + options_from_collection_for_select(albums, "id", "genre", disabled: nil) ) end @@ -307,7 +306,7 @@ class FormOptionsHelperTest < ActionView::TestCase assert_dom_equal( %(<option disabled="disabled" value="1.0">rap</option>\n<option disabled="disabled" value="2.0">pop</option>), - options_from_collection_for_select(albums, "id", "genre", :disabled => ["1.0", 2.0]) + options_from_collection_for_select(albums, "id", "genre", disabled: ["1.0", 2.0]) ) end @@ -336,7 +335,7 @@ class FormOptionsHelperTest < ActionView::TestCase "<optgroup label=\"North America\"><option value=\"US\">United States</option>\n<option value=\"Canada\">Canada</option></optgroup><optgroup label=\"Europe\"><option value=\"GB\">Great Britain</option>\n<option value=\"Germany\">Germany</option></optgroup>", grouped_options_for_select([ ["North America", - [['United States','US'],"Canada"]], + [["United States","US"],"Canada"]], ["Europe", [["Great Britain","GB"], "Germany"]] ]) @@ -347,8 +346,8 @@ class FormOptionsHelperTest < ActionView::TestCase assert_dom_equal( "<optgroup label=\"North America\" data-foo=\"bar\"><option value=\"US\">United States</option>\n<option value=\"Canada\">Canada</option></optgroup><optgroup label=\"Europe\" disabled=\"disabled\"><option value=\"GB\">Great Britain</option>\n<option value=\"Germany\">Germany</option></optgroup>", grouped_options_for_select([ - ["North America", [['United States','US'],"Canada"], :data => { :foo => 'bar' }], - ["Europe", [["Great Britain","GB"], "Germany"], :disabled => 'disabled'] + ["North America", [["United States","US"],"Canada"], data: { foo: "bar" }], + ["Europe", [["Great Britain","GB"], "Germany"], disabled: "disabled"] ]) ) end @@ -357,7 +356,7 @@ class FormOptionsHelperTest < ActionView::TestCase assert_dom_equal( "<optgroup label=\"----------\"><option value=\"US\">US</option>\n<option value=\"Canada\">Canada</option></optgroup><optgroup label=\"----------\"><option value=\"GB\">GB</option>\n<option value=\"Germany\">Germany</option></optgroup>", - grouped_options_for_select([['US',"Canada"] , ["GB", "Germany"]], nil, divider: "----------") + grouped_options_for_select([["US","Canada"] , ["GB", "Germany"]], nil, divider: "----------") ) end @@ -382,13 +381,13 @@ class FormOptionsHelperTest < ActionView::TestCase def test_grouped_options_for_select_with_prompt_returns_html_escaped_string assert_dom_equal( "<option value=\"\"><Choose One></option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>", - grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], nil, prompt: '<Choose One>')) + grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], nil, prompt: "<Choose One>")) end def test_optgroups_with_with_options_with_hash assert_dom_equal( "<optgroup label=\"North America\"><option value=\"United States\">United States</option>\n<option value=\"Canada\">Canada</option></optgroup><optgroup label=\"Europe\"><option value=\"Denmark\">Denmark</option>\n<option value=\"Germany\">Germany</option></optgroup>", - grouped_options_for_select({'North America' => ['United States','Canada'], 'Europe' => ['Denmark','Germany']}) + grouped_options_for_select("North America" => ["United States","Canada"], "Europe" => ["Denmark","Germany"]) ) end @@ -481,7 +480,7 @@ class FormOptionsHelperTest < ActionView::TestCase def test_select_without_multiple assert_dom_equal( "<select id=\"post_category\" name=\"post[category]\"></select>", - select(:post, :category, "", {}, :multiple => false) + select(:post, :category, "", {}, multiple: false) ) end @@ -495,9 +494,9 @@ class FormOptionsHelperTest < ActionView::TestCase assert_dom_equal( [ - %Q{<select id="post_origin" name="post[origin]"><optgroup label="<Africa>"><option value="<sa>"><South Africa></option>}, - %Q{<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk">Denmark</option>}, - %Q{<option value="ie">Ireland</option></optgroup></select>}, + '<select id="post_origin" name="post[origin]"><optgroup label="<Africa>"><option value="<sa>"><South Africa></option>', + '<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk">Denmark</option>', + '<option value="ie">Ireland</option></optgroup></select>', ].join("\n"), select("post", "origin", countries_by_continent) ) @@ -513,9 +512,9 @@ class FormOptionsHelperTest < ActionView::TestCase assert_dom_equal( [ - %Q{<select id="post_origin" name="post[origin]"><optgroup label="<Africa>"><option value="<sa>"><South Africa></option>}, - %Q{<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk">Denmark</option>}, - %Q{<option value="ie">Ireland</option></optgroup></select>}, + '<select id="post_origin" name="post[origin]"><optgroup label="<Africa>"><option value="<sa>"><South Africa></option>', + '<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk">Denmark</option>', + '<option value="ie">Ireland</option></optgroup></select>', ].join("\n"), select("post", "origin", countries_by_continent) ) @@ -561,7 +560,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post = Post.new @post.category = "<mus>" - output_buffer = fields_for :post, @post, :index => 108 do |f| + output_buffer = fields_for :post, @post, index: 108 do |f| concat f.select(:category, %w( abe <mus> hest)) end @@ -591,7 +590,7 @@ class FormOptionsHelperTest < ActionView::TestCase options = raw("<option value=\"abe\">abe</option><option value=\"mus\">mus</option><option value=\"hest\">hest</option>") output_buffer = fields_for :post, @post do |f| - concat f.select(:category, options, :prompt => 'The prompt') + concat f.select(:category, options, prompt: "The prompt") end assert_dom_equal( @@ -629,7 +628,7 @@ class FormOptionsHelperTest < ActionView::TestCase end def test_select_with_multiple_to_add_hidden_input - output_buffer = select(:post, :category, "", {}, :multiple => true) + output_buffer = select(:post, :category, "", {}, multiple: true) assert_dom_equal( "<input type=\"hidden\" name=\"post[category][]\" value=\"\"/><select multiple=\"multiple\" id=\"post_category\" name=\"post[category][]\"></select>", output_buffer @@ -637,7 +636,7 @@ class FormOptionsHelperTest < ActionView::TestCase end def test_select_with_multiple_and_without_hidden_input - output_buffer = select(:post, :category, "", {:include_hidden => false}, :multiple => true) + output_buffer = select(:post, :category, "", { include_hidden: false }, multiple: true) assert_dom_equal( "<select multiple=\"multiple\" id=\"post_category\" name=\"post[category][]\"></select>", output_buffer @@ -645,7 +644,7 @@ class FormOptionsHelperTest < ActionView::TestCase end def test_select_with_multiple_and_with_explicit_name_ending_with_brackets - output_buffer = select(:post, :category, [], {include_hidden: false}, multiple: true, name: 'post[category][]') + output_buffer = select(:post, :category, [], { include_hidden: false }, multiple: true, name: "post[category][]") assert_dom_equal( "<select multiple=\"multiple\" id=\"post_category\" name=\"post[category][]\"></select>", output_buffer @@ -653,7 +652,7 @@ class FormOptionsHelperTest < ActionView::TestCase end def test_select_with_multiple_and_disabled_to_add_disabled_hidden_input - output_buffer = select(:post, :category, "", {}, :multiple => true, :disabled => true) + output_buffer = select(:post, :category, "", {}, multiple: true, disabled: true) assert_dom_equal( "<input disabled=\"disabled\"type=\"hidden\" name=\"post[category][]\" value=\"\"/><select multiple=\"multiple\" disabled=\"disabled\" id=\"post_category\" name=\"post[category][]\"></select>", output_buffer @@ -665,14 +664,14 @@ class FormOptionsHelperTest < ActionView::TestCase @post.category = "<mus>" assert_dom_equal( "<select id=\"post_category\" name=\"post[category]\"><option value=\"\"></option>\n<option value=\"abe\">abe</option>\n<option value=\"<mus>\" selected=\"selected\"><mus></option>\n<option value=\"hest\">hest</option></select>", - select("post", "category", %w( abe <mus> hest), :include_blank => true) + select("post", "category", %w( abe <mus> hest), include_blank: true) ) end def test_select_with_include_blank_false_and_required @post = Post.new @post.category = "<mus>" - e = assert_raises(ArgumentError) { select("post", "category", %w( abe <mus> hest), { include_blank: false }, required: 'required') } + e = assert_raises(ArgumentError) { select("post", "category", %w( abe <mus> hest), { include_blank: false }, required: "required") } assert_match(/include_blank cannot be false for a required field./, e.message) end @@ -681,7 +680,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post.category = "<mus>" assert_dom_equal( "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">None</option>\n<option value=\"abe\">abe</option>\n<option value=\"<mus>\" selected=\"selected\"><mus></option>\n<option value=\"hest\">hest</option></select>", - select("post", "category", %w( abe <mus> hest), :include_blank => 'None') + select("post", "category", %w( abe <mus> hest), include_blank: "None") ) end @@ -690,7 +689,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post.category = "<mus>" assert_dom_equal( "<select id=\"post_category\" name=\"post[category]\"><option value=\"\"><None></option>\n<option value=\"abe\">abe</option>\n<option value=\"<mus>\" selected=\"selected\"><mus></option>\n<option value=\"hest\">hest</option></select>", - select("post", "category", %w( abe <mus> hest), :include_blank => '<None>') + select("post", "category", %w( abe <mus> hest), include_blank: "<None>") ) end @@ -699,7 +698,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post.category = "" assert_dom_equal( "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">Please select</option>\n<option value=\"abe\">abe</option>\n<option value=\"<mus>\"><mus></option>\n<option value=\"hest\">hest</option></select>", - select("post", "category", %w( abe <mus> hest), :prompt => true) + select("post", "category", %w( abe <mus> hest), prompt: true) ) end @@ -708,7 +707,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post.category = "<mus>" assert_dom_equal( "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\">abe</option>\n<option value=\"<mus>\" selected=\"selected\"><mus></option>\n<option value=\"hest\">hest</option></select>", - select("post", "category", %w( abe <mus> hest), :prompt => true) + select("post", "category", %w( abe <mus> hest), prompt: true) ) end @@ -717,7 +716,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post.category = "" assert_dom_equal( "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">The prompt</option>\n<option value=\"abe\">abe</option>\n<option value=\"<mus>\"><mus></option>\n<option value=\"hest\">hest</option></select>", - select("post", "category", %w( abe <mus> hest), :prompt => 'The prompt') + select("post", "category", %w( abe <mus> hest), prompt: "The prompt") ) end @@ -725,7 +724,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post = Post.new assert_dom_equal( "<select id=\"post_category\" name=\"post[category]\"><option value=\"\"><The prompt></option>\n<option value=\"abe\">abe</option>\n<option value=\"<mus>\"><mus></option>\n<option value=\"hest\">hest</option></select>", - select("post", "category", %w( abe <mus> hest), :prompt => '<The prompt>') + select("post", "category", %w( abe <mus> hest), prompt: "<The prompt>") ) end @@ -734,16 +733,25 @@ class FormOptionsHelperTest < ActionView::TestCase @post.category = "" assert_dom_equal( "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">Please select</option>\n<option value=\"\"></option>\n<option value=\"abe\">abe</option>\n<option value=\"<mus>\"><mus></option>\n<option value=\"hest\">hest</option></select>", - select("post", "category", %w( abe <mus> hest), :prompt => true, :include_blank => true) + select("post", "category", %w( abe <mus> hest), prompt: true, include_blank: true) ) end - def test_empty + def test_select_with_empty @post = Post.new @post.category = "" assert_dom_equal( "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">Please select</option>\n<option value=\"\"></option>\n</select>", - select("post", "category", [], :prompt => true, :include_blank => true) + select("post", "category", [], prompt: true, include_blank: true) + ) + end + + def test_select_with_html_options + @post = Post.new + @post.category = "" + assert_dom_equal( + "<select class=\"disabled\" disabled=\"disabled\" name=\"post[category]\" id=\"post_category\"><option value=\"\">Please select</option>\n<option value=\"\"></option>\n</select>", + select("post", "category", [], { prompt: true, include_blank: true }, class: "disabled", disabled: true) ) end @@ -798,12 +806,12 @@ class FormOptionsHelperTest < ActionView::TestCase ) end - def test_select_with_fixnum + def test_select_with_integer @post = Post.new @post.category = "" assert_dom_equal( "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">Please select</option>\n<option value=\"\"></option>\n<option value=\"1\">1</option></select>", - select("post", "category", [1], :prompt => true, :include_blank => true) + select("post", "category", [1], prompt: true, include_blank: true) ) end @@ -812,7 +820,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post.category = "" assert_dom_equal( "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">Please select</option>\n<option value=\"\"></option>\n<option value=\"number\">Number</option>\n<option value=\"text\">Text</option>\n<option value=\"boolean\">Yes/No</option></select>", - select("post", "category", [["Number", "number"], ["Text", "text"], ["Yes/No", "boolean"]], :prompt => true, :include_blank => true) + select("post", "category", [["Number", "number"], ["Text", "text"], ["Yes/No", "boolean"]], prompt: true, include_blank: true) ) end @@ -821,7 +829,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post.category = "<mus>" assert_dom_equal( "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\" selected=\"selected\">abe</option>\n<option value=\"<mus>\"><mus></option>\n<option value=\"hest\">hest</option></select>", - select("post", "category", %w( abe <mus> hest ), :selected => 'abe') + select("post", "category", %w( abe <mus> hest ), selected: "abe") ) end @@ -833,14 +841,14 @@ class FormOptionsHelperTest < ActionView::TestCase assert_dom_equal( expected, - select("album[]", "genre", %w[rap rock country], {}, { :index => nil }) + select("album[]", "genre", %w[rap rock country], {}, index: nil) ) end def test_select_escapes_options assert_dom_equal( '<select id="post_title" name="post[title]"><script>alert(1)</script></select>', - select('post', 'title', '<script>alert(1)</script>') + select("post", "title", "<script>alert(1)</script>") ) end @@ -849,7 +857,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post.category = "<mus>" assert_dom_equal( "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\">abe</option>\n<option value=\"<mus>\"><mus></option>\n<option value=\"hest\">hest</option></select>", - select("post", "category", %w( abe <mus> hest ), :selected => nil) + select("post", "category", %w( abe <mus> hest ), selected: nil) ) end @@ -858,7 +866,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post.category = "<mus>" assert_dom_equal( "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\">abe</option>\n<option value=\"<mus>\" selected=\"selected\"><mus></option>\n<option value=\"hest\" disabled=\"disabled\">hest</option></select>", - select("post", "category", %w( abe <mus> hest ), :disabled => 'hest') + select("post", "category", %w( abe <mus> hest ), disabled: "hest") ) end @@ -866,7 +874,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post = Post.new assert_dom_equal( "<select id=\"post_locale\" name=\"post[locale]\"><option value=\"en\">en</option>\n<option value=\"ru\" selected=\"selected\">ru</option></select>", - select("post", "locale", %w( en ru ), :selected => 'ru') + select("post", "locale", %w( en ru ), selected: "ru") ) end @@ -874,7 +882,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post = Post.new assert_dom_equal( "<select id=\"post_category\" name=\"post[category]\"><option value=\"one\">one</option>\n<option selected=\"selected\" value=\"two\">two</option></select>", - select("post", "category", %w( one two ), :selected => 'two', :prompt => true) + select("post", "category", %w( one two ), selected: "two", prompt: true) ) end @@ -883,7 +891,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post.category = "<mus>" assert_dom_equal( "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\" disabled=\"disabled\">abe</option>\n<option value=\"<mus>\" selected=\"selected\"><mus></option>\n<option value=\"hest\" disabled=\"disabled\">hest</option></select>", - select("post", "category", %w( abe <mus> hest ), :disabled => ['hest', 'abe']) + select("post", "category", %w( abe <mus> hest ), disabled: ["hest", "abe"]) ) end @@ -924,7 +932,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post = Post.new @post.author_name = "Babe" - output_buffer = fields_for :post, @post, :index => 815 do |f| + output_buffer = fields_for :post, @post, index: 815 do |f| concat f.collection_select(:author_name, dummy_posts, :author_name, :author_name) end @@ -955,7 +963,7 @@ class FormOptionsHelperTest < ActionView::TestCase assert_dom_equal( "<select id=\"post_author_name\" name=\"post[author_name]\" style=\"width: 200px\"><option value=\"\"></option>\n<option value=\"<Abe>\"><Abe></option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>", - collection_select("post", "author_name", dummy_posts, "author_name", "author_name", { :include_blank => true }, "style" => "width: 200px") + collection_select("post", "author_name", dummy_posts, "author_name", "author_name", { include_blank: true }, "style" => "width: 200px") ) end @@ -965,7 +973,7 @@ class FormOptionsHelperTest < ActionView::TestCase assert_dom_equal( "<select id=\"post_author_name\" name=\"post[author_name]\" style=\"width: 200px\"><option value=\"\">No Selection</option>\n<option value=\"<Abe>\"><Abe></option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>", - collection_select("post", "author_name", dummy_posts, "author_name", "author_name", { :include_blank => 'No Selection' }, "style" => "width: 200px") + collection_select("post", "author_name", dummy_posts, "author_name", "author_name", { include_blank: "No Selection" }, "style" => "width: 200px") ) end @@ -976,10 +984,10 @@ class FormOptionsHelperTest < ActionView::TestCase expected = "<input type=\"hidden\" name=\"post[author_name][]\" value=\"\"/><select id=\"post_author_name\" name=\"post[author_name][]\" multiple=\"multiple\"><option value=\"\"></option>\n<option value=\"<Abe>\"><Abe></option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>" # Should suffix default name with []. - assert_dom_equal expected, collection_select("post", "author_name", dummy_posts, "author_name", "author_name", { :include_blank => true }, :multiple => true) + assert_dom_equal expected, collection_select("post", "author_name", dummy_posts, "author_name", "author_name", { include_blank: true }, multiple: true) # Shouldn't suffix custom name with []. - assert_dom_equal expected, collection_select("post", "author_name", dummy_posts, "author_name", "author_name", { :include_blank => true, :name => 'post[author_name][]' }, :multiple => true) + assert_dom_equal expected, collection_select("post", "author_name", dummy_posts, "author_name", "author_name", { include_blank: true, name: "post[author_name][]" }, multiple: true) end def test_collection_select_with_blank_and_selected @@ -988,7 +996,7 @@ class FormOptionsHelperTest < ActionView::TestCase assert_dom_equal( %{<select id="post_author_name" name="post[author_name]"><option value=""></option>\n<option value="<Abe>" selected="selected"><Abe></option>\n<option value="Babe">Babe</option>\n<option value="Cabe">Cabe</option></select>}, - collection_select("post", "author_name", dummy_posts, "author_name", "author_name", {:include_blank => true, :selected => "<Abe>"}) + collection_select("post", "author_name", dummy_posts, "author_name", "author_name", include_blank: true, selected: "<Abe>") ) end @@ -998,7 +1006,7 @@ class FormOptionsHelperTest < ActionView::TestCase assert_dom_equal( "<select id=\"post_author_name\" name=\"post[author_name]\"><option value=\"<Abe>\"><Abe></option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\" disabled=\"disabled\">Cabe</option></select>", - collection_select("post", "author_name", dummy_posts, "author_name", "author_name", :disabled => 'Cabe') + collection_select("post", "author_name", dummy_posts, "author_name", "author_name", disabled: "Cabe") ) end @@ -1055,7 +1063,7 @@ class FormOptionsHelperTest < ActionView::TestCase def test_time_zone_select_under_fields_for_with_index @firm = Firm.new("D") - output_buffer = fields_for :firm, @firm, :index => 305 do |f| + output_buffer = fields_for :firm, @firm, index: 305 do |f| concat f.time_zone_select(:time_zone) end @@ -1093,7 +1101,7 @@ 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) + 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" + @@ -1107,7 +1115,7 @@ 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') + 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" + @@ -1132,13 +1140,13 @@ class FormOptionsHelperTest < ActionView::TestCase "</select>", html assert_dom_equal html, time_zone_select("firm", "time_zone", nil, {}, - :style => "color: red") + style: "color: red") end def test_time_zone_select_with_blank_and_style @firm = Firm.new("D") html = time_zone_select("firm", "time_zone", nil, - { :include_blank => true }, "style" => "color: red") + { 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" + @@ -1149,13 +1157,13 @@ class FormOptionsHelperTest < ActionView::TestCase "</select>", html assert_dom_equal html, time_zone_select("firm", "time_zone", nil, - { :include_blank => true }, :style => "color: red") + { include_blank: true }, style: "color: red") end def test_time_zone_select_with_blank_as_string_and_style @firm = Firm.new("D") html = time_zone_select("firm", "time_zone", nil, - { :include_blank => 'No Zone' }, "style" => "color: red") + { 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" + @@ -1166,7 +1174,7 @@ class FormOptionsHelperTest < ActionView::TestCase "</select>", html assert_dom_equal html, time_zone_select("firm", "time_zone", nil, - { :include_blank => 'No Zone' }, :style => "color: red") + { include_blank: "No Zone" }, style: "color: red") end def test_time_zone_select_with_priority_zones @@ -1225,24 +1233,24 @@ class FormOptionsHelperTest < ActionView::TestCase end def test_time_zone_select_with_default_time_zone_and_nil_value - @firm = Firm.new() - @firm.time_zone = nil + @firm = Firm.new() + @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>" + - "</select>", - html + 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>" + + "</select>", + html end def test_time_zone_select_with_default_time_zone_and_value - @firm = Firm.new('D') + @firm = Firm.new("D") - html = time_zone_select( "firm", "time_zone", nil, :default => 'B' ) + 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" + @@ -1256,86 +1264,86 @@ class FormOptionsHelperTest < ActionView::TestCase def test_options_for_select_with_element_attributes assert_dom_equal( "<option value=\"<Denmark>\" class=\"bold\"><Denmark></option>\n<option value=\"USA\" onclick=\"alert('Hello World');\">USA</option>\n<option value=\"Sweden\">Sweden</option>\n<option value=\"Germany\">Germany</option>", - options_for_select([ [ "<Denmark>", { :class => 'bold' } ], [ "USA", { :onclick => "alert('Hello World');" } ], [ "Sweden" ], "Germany" ]) + options_for_select([ [ "<Denmark>", { class: "bold" } ], [ "USA", { onclick: "alert('Hello World');" } ], [ "Sweden" ], "Germany" ]) ) end def test_options_for_select_with_data_element assert_dom_equal( "<option value=\"<Denmark>\" data-test=\"bold\"><Denmark></option>", - options_for_select([ [ "<Denmark>", { :data => { :test => 'bold' } } ] ]) + options_for_select([ [ "<Denmark>", { data: { test: "bold" } } ] ]) ) end def test_options_for_select_with_data_element_with_special_characters assert_dom_equal( "<option value=\"<Denmark>\" data-test=\"<bold>\"><Denmark></option>", - options_for_select([ [ "<Denmark>", { :data => { :test => '<bold>' } } ] ]) + options_for_select([ [ "<Denmark>", { data: { test: "<bold>" } } ] ]) ) end def test_options_for_select_with_element_attributes_and_selection assert_dom_equal( "<option value=\"<Denmark>\"><Denmark></option>\n<option value=\"USA\" class=\"bold\" selected=\"selected\">USA</option>\n<option value=\"Sweden\">Sweden</option>", - options_for_select([ "<Denmark>", [ "USA", { :class => 'bold' } ], "Sweden" ], "USA") + options_for_select([ "<Denmark>", [ "USA", { class: "bold" } ], "Sweden" ], "USA") ) end def test_options_for_select_with_element_attributes_and_selection_array assert_dom_equal( "<option value=\"<Denmark>\"><Denmark></option>\n<option value=\"USA\" class=\"bold\" selected=\"selected\">USA</option>\n<option value=\"Sweden\" selected=\"selected\">Sweden</option>", - options_for_select([ "<Denmark>", [ "USA", { :class => 'bold' } ], "Sweden" ], [ "USA", "Sweden" ]) + options_for_select([ "<Denmark>", [ "USA", { class: "bold" } ], "Sweden" ], [ "USA", "Sweden" ]) ) end def test_options_for_select_with_special_characters assert_dom_equal( "<option value=\"<Denmark>\" onclick=\"alert("<code>")\"><Denmark></option>", - options_for_select([ [ "<Denmark>", { :onclick => %(alert("<code>")) } ] ]) + options_for_select([ [ "<Denmark>", { onclick: %(alert("<code>")) } ] ]) ) end def test_option_html_attributes_with_no_array_element - assert_equal({}, option_html_attributes('foo')) + assert_equal({}, option_html_attributes("foo")) end def test_option_html_attributes_without_hash - assert_equal({}, option_html_attributes([ 'foo', 'bar' ])) + assert_equal({}, option_html_attributes([ "foo", "bar" ])) end def test_option_html_attributes_with_single_element_hash assert_equal( - {:class => 'fancy'}, - option_html_attributes([ 'foo', 'bar', { :class => 'fancy' } ]) + { class: "fancy" }, + option_html_attributes([ "foo", "bar", { class: "fancy" } ]) ) end def test_option_html_attributes_with_multiple_element_hash assert_equal( - {:class => 'fancy', 'onclick' => "alert('Hello World');"}, - option_html_attributes([ 'foo', 'bar', { :class => 'fancy', 'onclick' => "alert('Hello World');" } ]) + { :class => "fancy", "onclick" => "alert('Hello World');" }, + option_html_attributes([ "foo", "bar", { :class => "fancy", "onclick" => "alert('Hello World');" } ]) ) end def test_option_html_attributes_with_multiple_hashes assert_equal( - {:class => 'fancy', 'onclick' => "alert('Hello World');"}, - option_html_attributes([ 'foo', 'bar', { :class => 'fancy' }, { 'onclick' => "alert('Hello World');" } ]) + { :class => "fancy", "onclick" => "alert('Hello World');" }, + option_html_attributes([ "foo", "bar", { class: "fancy" }, { "onclick" => "alert('Hello World');" } ]) ) end def test_option_html_attributes_with_multiple_hashes_does_not_modify_them - options1 = { class: 'fancy' } + options1 = { class: "fancy" } options2 = { onclick: "alert('Hello World');" } - option_html_attributes([ 'foo', 'bar', options1, options2 ]) + option_html_attributes([ "foo", "bar", options1, options2 ]) - assert_equal({ class: 'fancy' }, options1) + assert_equal({ class: "fancy" }, options1) assert_equal({ onclick: "alert('Hello World');" }, options2) end def test_grouped_collection_select @post = Post.new - @post.origin = 'dk' + @post.origin = "dk" assert_dom_equal( %Q{<select id="post_origin" name="post[origin]"><optgroup label="<Africa>"><option value="<sa>"><South Africa></option>\n<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk" selected="selected">Denmark</option>\n<option value="ie">Ireland</option></optgroup></select>}, @@ -1348,7 +1356,7 @@ class FormOptionsHelperTest < ActionView::TestCase assert_dom_equal( %Q{<select id="post_origin" name="post[origin]"><optgroup label="<Africa>"><option value="<sa>"><South Africa></option>\n<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk" selected="selected">Denmark</option>\n<option value="ie">Ireland</option></optgroup></select>}, - grouped_collection_select("post", "origin", dummy_continents, :countries, :continent_name, :country_id, :country_name, :selected => 'dk') + grouped_collection_select("post", "origin", dummy_continents, :countries, :continent_name, :country_id, :country_name, selected: "dk") ) end @@ -1357,13 +1365,13 @@ class FormOptionsHelperTest < ActionView::TestCase assert_dom_equal( %Q{<select id="post_origin" name="post[origin]"><optgroup label="<Africa>"><option value="<sa>"><South Africa></option>\n<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option disabled="disabled" value="dk">Denmark</option>\n<option value="ie">Ireland</option></optgroup></select>}, - grouped_collection_select("post", "origin", dummy_continents, :countries, :continent_name, :country_id, :country_name, :disabled => 'dk') + grouped_collection_select("post", "origin", dummy_continents, :countries, :continent_name, :country_id, :country_name, disabled: "dk") ) end def test_grouped_collection_select_under_fields_for @post = Post.new - @post.origin = 'dk' + @post.origin = "dk" output_buffer = fields_for :post, @post do |f| concat f.grouped_collection_select("origin", dummy_continents, :countries, :continent_name, :country_id, :country_name) @@ -1377,14 +1385,14 @@ class FormOptionsHelperTest < ActionView::TestCase private - def dummy_posts - [ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"), - Post.new("Babe went home", "Babe", "To a little house", "shh!"), - Post.new("Cabe went home", "Cabe", "To a little house", "shh!") ] - end + def dummy_posts + [ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"), + Post.new("Babe went home", "Babe", "To a little house", "shh!"), + Post.new("Cabe went home", "Cabe", "To a little house", "shh!") ] + end - def dummy_continents - [ Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")]), - Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")]) ] - end + def dummy_continents + [ Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")]), + Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")]) ] + end end diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb index 07b3fba754..da929cac8f 100644 --- a/actionview/test/template/form_tag_helper_test.rb +++ b/actionview/test/template/form_tag_helper_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class FormTagHelperTest < ActionView::TestCase include RenderERBUtils @@ -14,7 +14,7 @@ class FormTagHelperTest < ActionView::TestCase method = options[:method] enforce_utf8 = options.fetch(:enforce_utf8, true) - ''.tap do |txt| + "".tap do |txt| if enforce_utf8 txt << %{<input name="utf8" type="hidden" value="✓" />} end @@ -78,7 +78,7 @@ class FormTagHelperTest < ActionView::TestCase def test_check_box_tag_id_sanitized label_elem = root_elem(check_box_tag("project[2][admin]")) - assert_match VALID_HTML_ID, label_elem['id'] + assert_match VALID_HTML_ID, label_elem["id"] end def test_form_tag @@ -88,54 +88,54 @@ class FormTagHelperTest < ActionView::TestCase end def test_form_tag_multipart - actual = form_tag({}, { 'multipart' => true }) - expected = whole_form("http://www.example.com", :enctype => true) + actual = form_tag({}, "multipart" => true) + expected = whole_form("http://www.example.com", enctype: true) assert_dom_equal expected, actual end def test_form_tag_with_method_patch - actual = form_tag({}, { :method => :patch }) - expected = whole_form("http://www.example.com", :method => :patch) + actual = form_tag({}, method: :patch) + expected = whole_form("http://www.example.com", method: :patch) assert_dom_equal expected, actual end def test_form_tag_with_method_put - actual = form_tag({}, { :method => :put }) - expected = whole_form("http://www.example.com", :method => :put) + actual = form_tag({}, method: :put) + expected = whole_form("http://www.example.com", method: :put) assert_dom_equal expected, actual end def test_form_tag_with_method_delete - actual = form_tag({}, { :method => :delete }) + actual = form_tag({}, method: :delete) - expected = whole_form("http://www.example.com", :method => :delete) + expected = whole_form("http://www.example.com", method: :delete) assert_dom_equal expected, actual end def test_form_tag_with_remote - actual = form_tag({}, :remote => true) + actual = form_tag({}, remote: true) - expected = whole_form("http://www.example.com", :remote => true) + expected = whole_form("http://www.example.com", remote: true) assert_dom_equal expected, actual end def test_form_tag_with_remote_false - actual = form_tag({}, :remote => false) + actual = form_tag({}, remote: false) expected = whole_form assert_dom_equal expected, actual end def test_form_tag_enforce_utf8_true - actual = form_tag({}, { :enforce_utf8 => true }) - expected = whole_form("http://www.example.com", :enforce_utf8 => true) + actual = form_tag({}, enforce_utf8: true) + expected = whole_form("http://www.example.com", enforce_utf8: true) assert_dom_equal expected, actual assert actual.html_safe? end def test_form_tag_enforce_utf8_false - actual = form_tag({}, { :enforce_utf8 => false }) - expected = whole_form("http://www.example.com", :enforce_utf8 => false) + actual = form_tag({}, enforce_utf8: false) + expected = whole_form("http://www.example.com", enforce_utf8: false) assert_dom_equal expected, actual assert actual.html_safe? end @@ -150,7 +150,7 @@ class FormTagHelperTest < ActionView::TestCase def test_form_tag_with_block_and_method_in_erb output_buffer = render_erb("<%= form_tag('http://www.example.com', :method => :put) do %>Hello world!<% end %>") - expected = whole_form("http://www.example.com", :method => "put") do + expected = whole_form("http://www.example.com", method: "put") do "Hello world!" end @@ -165,7 +165,7 @@ class FormTagHelperTest < ActionView::TestCase def test_hidden_field_tag_id_sanitized input_elem = root_elem(hidden_field_tag("item[][title]")) - assert_match VALID_HTML_ID, input_elem['id'] + assert_match VALID_HTML_ID, input_elem["id"] end def test_file_field_tag @@ -173,7 +173,7 @@ class FormTagHelperTest < ActionView::TestCase end def test_file_field_tag_with_options - assert_dom_equal "<input name=\"picsplz\" type=\"file\" id=\"picsplz\" class=\"pix\"/>", file_field_tag("picsplz", :class => "pix") + assert_dom_equal "<input name=\"picsplz\" type=\"file\" id=\"picsplz\" class=\"pix\"/>", file_field_tag("picsplz", class: "pix") end def test_password_field_tag @@ -183,7 +183,7 @@ class FormTagHelperTest < ActionView::TestCase end def test_multiple_field_tags_with_same_options - options = {class: 'important'} + options = { class: "important" } assert_dom_equal %(<input name="title" type="file" id="title" class="important"/>), file_field_tag("title", options) assert_dom_equal %(<input type="password" name="title" id="title" value="Hello!" class="important" />), password_field_tag("title", "Hello!", options) assert_dom_equal %(<input type="text" name="title" id="title" value="Hello!" class="important" />), text_field_tag("title", "Hello!", options) @@ -210,7 +210,7 @@ class FormTagHelperTest < ActionView::TestCase expected = %(<input id="person_gender_m" name="person[gender]" type="radio" value="m" />) assert_dom_equal expected, actual - actual = radio_button_tag('ctrlname', 'apache2.2') + actual = radio_button_tag("ctrlname", "apache2.2") expected = %(<input id="ctrlname_apache2.2" name="ctrlname" type="radio" value="apache2.2" />) assert_dom_equal expected, actual end @@ -235,12 +235,12 @@ class FormTagHelperTest < ActionView::TestCase def test_select_tag_id_sanitized input_elem = root_elem(select_tag("project[1]people", "<option>david</option>")) - assert_match VALID_HTML_ID, input_elem['id'] + assert_match VALID_HTML_ID, input_elem["id"] end def test_select_tag_with_include_blank - actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), :include_blank => true - expected = %(<select id="places" name="places"><option value=""></option><option>Home</option><option>Work</option><option>Pub</option></select>) + actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), include_blank: true + expected = %(<select id="places" name="places"><option value="" label=" "></option><option>Home</option><option>Work</option><option>Pub</option></select>) assert_dom_equal expected, actual end @@ -251,37 +251,37 @@ class FormTagHelperTest < ActionView::TestCase end def test_select_tag_with_include_blank_string - actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), include_blank: 'Choose' + actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), include_blank: "Choose" expected = %(<select id="places" name="places"><option value="">Choose</option><option>Home</option><option>Work</option><option>Pub</option></select>) assert_dom_equal expected, actual end def test_select_tag_with_prompt - actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), :prompt => "string" + actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), prompt: "string" expected = %(<select id="places" name="places"><option value="">string</option><option>Home</option><option>Work</option><option>Pub</option></select>) assert_dom_equal expected, actual end def test_select_tag_escapes_prompt - actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), :prompt => "<script>alert(1337)</script>" + actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), prompt: "<script>alert(1337)</script>" expected = %(<select id="places" name="places"><option value=""><script>alert(1337)</script></option><option>Home</option><option>Work</option><option>Pub</option></select>) assert_dom_equal expected, actual end def test_select_tag_with_prompt_and_include_blank - actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), :prompt => "string", :include_blank => true - expected = %(<select name="places" id="places"><option value="">string</option><option value=""></option><option>Home</option><option>Work</option><option>Pub</option></select>) + actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), prompt: "string", include_blank: true + expected = %(<select name="places" id="places"><option value="">string</option><option value="" label=" "></option><option>Home</option><option>Work</option><option>Pub</option></select>) assert_dom_equal expected, actual end def test_select_tag_with_nil_option_tags_and_include_blank - actual = select_tag "places", nil, :include_blank => true - expected = %(<select id="places" name="places"><option value=""></option></select>) + actual = select_tag "places", nil, include_blank: true + expected = %(<select id="places" name="places"><option value="" label=" "></option></select>) assert_dom_equal expected, actual end def test_select_tag_with_nil_option_tags_and_prompt - actual = select_tag "places", nil, :prompt => "string" + actual = select_tag "places", nil, prompt: "string" expected = %(<select id="places" name="places"><option value="">string</option></select>) assert_dom_equal expected, actual end @@ -293,36 +293,36 @@ class FormTagHelperTest < ActionView::TestCase end def test_text_area_tag_size_symbol - actual = text_area_tag "body", "hello world", :size => "20x40" + actual = text_area_tag "body", "hello world", size: "20x40" expected = %(<textarea cols="20" id="body" name="body" rows="40">\nhello world</textarea>) assert_dom_equal expected, actual end def test_text_area_tag_should_disregard_size_if_its_given_as_an_integer - actual = text_area_tag "body", "hello world", :size => 20 + actual = text_area_tag "body", "hello world", size: 20 expected = %(<textarea id="body" name="body">\nhello world</textarea>) assert_dom_equal expected, actual end def test_text_area_tag_id_sanitized input_elem = root_elem(text_area_tag("item[][description]")) - assert_match VALID_HTML_ID, input_elem['id'] + assert_match VALID_HTML_ID, input_elem["id"] end def test_text_area_tag_escape_content - actual = text_area_tag "body", "<b>hello world</b>", :size => "20x40" + actual = text_area_tag "body", "<b>hello world</b>", size: "20x40" expected = %(<textarea cols="20" id="body" name="body" rows="40">\n<b>hello world</b></textarea>) assert_dom_equal expected, actual end def test_text_area_tag_unescaped_content - actual = text_area_tag "body", "<b>hello world</b>", :size => "20x40", :escape => false + actual = text_area_tag "body", "<b>hello world</b>", size: "20x40", escape: false expected = %(<textarea cols="20" id="body" name="body" rows="40">\n<b>hello world</b></textarea>) assert_dom_equal expected, actual end def test_text_area_tag_unescaped_nil_content - actual = text_area_tag "body", nil, :escape => false + actual = text_area_tag "body", nil, escape: false expected = %(<textarea id="body" name="body">\n</textarea>) assert_dom_equal expected, actual end @@ -340,7 +340,7 @@ class FormTagHelperTest < ActionView::TestCase end def test_text_field_tag_size_symbol - actual = text_field_tag "title", "Hello!", :size => 75 + actual = text_field_tag "title", "Hello!", size: 75 expected = %(<input id="title" name="title" size="75" type="text" value="Hello!" />) assert_dom_equal expected, actual end @@ -352,7 +352,7 @@ class FormTagHelperTest < ActionView::TestCase end def test_text_field_tag_maxlength_symbol - actual = text_field_tag "title", "Hello!", :maxlength => 75 + actual = text_field_tag "title", "Hello!", maxlength: 75 expected = %(<input id="title" name="title" maxlength="75" type="text" value="Hello!" />) assert_dom_equal expected, actual end @@ -370,20 +370,20 @@ class FormTagHelperTest < ActionView::TestCase end def test_text_field_tag_with_placeholder_option - actual = text_field_tag "title", "Hello!", placeholder: 'Enter search term...' + actual = text_field_tag "title", "Hello!", placeholder: "Enter search term..." expected = %(<input id="title" name="title" placeholder="Enter search term..." type="text" value="Hello!" />) assert_dom_equal expected, actual end def test_text_field_tag_with_multiple_options - actual = text_field_tag "title", "Hello!", :size => 70, :maxlength => 80 + actual = text_field_tag "title", "Hello!", size: 70, maxlength: 80 expected = %(<input id="title" name="title" size="70" maxlength="80" type="text" value="Hello!" />) assert_dom_equal expected, actual end def test_text_field_tag_id_sanitized input_elem = root_elem(text_field_tag("item[][title]")) - assert_match VALID_HTML_ID, input_elem['id'] + assert_match VALID_HTML_ID, input_elem["id"] end def test_label_tag_without_text @@ -412,11 +412,11 @@ class FormTagHelperTest < ActionView::TestCase def test_label_tag_id_sanitized label_elem = root_elem(label_tag("item[title]")) - assert_match VALID_HTML_ID, label_elem['for'] + assert_match VALID_HTML_ID, label_elem["for"] end def test_label_tag_with_block - assert_dom_equal('<label>Blocked</label>', label_tag { "Blocked" }) + assert_dom_equal("<label>Blocked</label>", label_tag { "Blocked" }) end def test_label_tag_with_block_and_argument @@ -425,21 +425,21 @@ class FormTagHelperTest < ActionView::TestCase end def test_label_tag_with_block_and_argument_and_options - output = label_tag("clock", :id => "label_clock") { "Grandfather" } + output = label_tag("clock", id: "label_clock") { "Grandfather" } assert_dom_equal('<label for="clock" id="label_clock">Grandfather</label>', output) end def test_boolean_options - assert_dom_equal %(<input checked="checked" disabled="disabled" id="admin" name="admin" readonly="readonly" type="checkbox" value="1" />), check_box_tag("admin", 1, true, 'disabled' => true, :readonly => "yes") - assert_dom_equal %(<input checked="checked" id="admin" name="admin" type="checkbox" value="1" />), check_box_tag("admin", 1, true, :disabled => false, :readonly => nil) - assert_dom_equal %(<input type="checkbox" />), tag(:input, :type => "checkbox", :checked => false) - assert_dom_equal %(<select id="people" multiple="multiple" name="people[]"><option>david</option></select>), select_tag("people", raw("<option>david</option>"), :multiple => true) - assert_dom_equal %(<select id="people_" multiple="multiple" name="people[]"><option>david</option></select>), select_tag("people[]", raw("<option>david</option>"), :multiple => true) - assert_dom_equal %(<select id="people" name="people"><option>david</option></select>), select_tag("people", raw("<option>david</option>"), :multiple => nil) + assert_dom_equal %(<input checked="checked" disabled="disabled" id="admin" name="admin" readonly="readonly" type="checkbox" value="1" />), check_box_tag("admin", 1, true, "disabled" => true, :readonly => "yes") + assert_dom_equal %(<input checked="checked" id="admin" name="admin" type="checkbox" value="1" />), check_box_tag("admin", 1, true, disabled: false, readonly: nil) + assert_dom_equal %(<input type="checkbox" />), tag(:input, type: "checkbox", checked: false) + assert_dom_equal %(<select id="people" multiple="multiple" name="people[]"><option>david</option></select>), select_tag("people", raw("<option>david</option>"), multiple: true) + assert_dom_equal %(<select id="people_" multiple="multiple" name="people[]"><option>david</option></select>), select_tag("people[]", raw("<option>david</option>"), multiple: true) + assert_dom_equal %(<select id="people" name="people"><option>david</option></select>), select_tag("people", raw("<option>david</option>"), multiple: nil) end def test_stringify_symbol_keys - actual = text_field_tag "title", "Hello!", :id => "admin" + actual = text_field_tag "title", "Hello!", id: "admin" expected = %(<input id="admin" name="title" type="text" value="Hello!" />) assert_dom_equal expected, actual end @@ -447,7 +447,7 @@ class FormTagHelperTest < ActionView::TestCase def test_submit_tag assert_dom_equal( %(<input name='commit' data-disable-with="Saving..." onclick="alert('hello!')" type="submit" value="Save" />), - submit_tag("Save", :onclick => "alert('hello!')", :data => { :disable_with => "Saving..." }) + submit_tag("Save", onclick: "alert('hello!')", data: { disable_with: "Saving..." }) ) end @@ -471,42 +471,42 @@ class FormTagHelperTest < ActionView::TestCase def test_submit_tag_having_data_disable_with_string assert_dom_equal( %(<input data-disable-with="Processing..." data-confirm="Are you sure?" name='commit' type="submit" value="Save" />), - submit_tag("Save", { "data-disable-with" => "Processing...", "data-confirm" => "Are you sure?" }) + submit_tag("Save", "data-disable-with" => "Processing...", "data-confirm" => "Are you sure?") ) end def test_submit_tag_having_data_disable_with_boolean assert_dom_equal( %(<input data-confirm="Are you sure?" name='commit' type="submit" value="Save" />), - submit_tag("Save", { "data-disable-with" => false, "data-confirm" => "Are you sure?" }) + submit_tag("Save", "data-disable-with" => false, "data-confirm" => "Are you sure?") ) end def test_submit_tag_having_data_hash_disable_with_boolean assert_dom_equal( %(<input data-confirm="Are you sure?" name='commit' type="submit" value="Save" />), - submit_tag("Save", { :data => { :confirm => "Are you sure?", :disable_with => false } }) + submit_tag("Save", data: { confirm: "Are you sure?", disable_with: false }) ) end def test_submit_tag_with_no_onclick_options assert_dom_equal( %(<input name='commit' data-disable-with="Saving..." type="submit" value="Save" />), - submit_tag("Save", :data => { :disable_with => "Saving..." }) + submit_tag("Save", data: { disable_with: "Saving..." }) ) end def test_submit_tag_with_confirmation assert_dom_equal( %(<input name='commit' type='submit' value='Save' data-confirm="Are you sure?" data-disable-with="Save" />), - submit_tag("Save", :data => { :confirm => "Are you sure?" }) + submit_tag("Save", data: { confirm: "Are you sure?" }) ) end def test_submit_tag_doesnt_have_data_disable_with_twice assert_equal( %(<input type="submit" name="commit" value="Save" data-confirm="Are you sure?" data-disable-with="Processing..." />), - submit_tag("Save", { "data-disable-with" => "Processing...", "data-confirm" => "Are you sure?" }) + submit_tag("Save", "data-disable-with" => "Processing...", "data-confirm" => "Are you sure?") ) end @@ -517,7 +517,6 @@ class FormTagHelperTest < ActionView::TestCase ) end - def test_button_tag assert_dom_equal( %(<button name="button" type="submit">Button</button>), @@ -528,56 +527,56 @@ class FormTagHelperTest < ActionView::TestCase def test_button_tag_with_submit_type assert_dom_equal( %(<button name="button" type="submit">Save</button>), - button_tag("Save", :type => "submit") + button_tag("Save", type: "submit") ) end def test_button_tag_with_button_type assert_dom_equal( %(<button name="button" type="button">Button</button>), - button_tag("Button", :type => "button") + button_tag("Button", type: "button") ) end def test_button_tag_with_reset_type assert_dom_equal( %(<button name="button" type="reset">Reset</button>), - button_tag("Reset", :type => "reset") + button_tag("Reset", type: "reset") ) end def test_button_tag_with_disabled_option assert_dom_equal( %(<button name="button" type="reset" disabled="disabled">Reset</button>), - button_tag("Reset", :type => "reset", :disabled => true) + button_tag("Reset", type: "reset", disabled: true) ) end def test_button_tag_escape_content assert_dom_equal( %(<button name="button" type="reset" disabled="disabled"><b>Reset</b></button>), - button_tag("<b>Reset</b>", :type => "reset", :disabled => true) + button_tag("<b>Reset</b>", type: "reset", disabled: true) ) end def test_button_tag_with_block - assert_dom_equal('<button name="button" type="submit">Content</button>', button_tag { 'Content' }) + assert_dom_equal('<button name="button" type="submit">Content</button>', button_tag { "Content" }) end def test_button_tag_with_block_and_options - output = button_tag(:name => 'temptation', :type => 'button') { content_tag(:strong, 'Do not press me') } + output = button_tag(name: "temptation", type: "button") { content_tag(:strong, "Do not press me") } assert_dom_equal('<button name="temptation" type="button"><strong>Do not press me</strong></button>', output) end def test_button_tag_defaults_with_block_and_options - output = button_tag(:name => 'temptation', :value => 'within') { content_tag(:strong, 'Do not press me') } + output = button_tag(name: "temptation", value: "within") { content_tag(:strong, "Do not press me") } assert_dom_equal('<button name="temptation" value="within" type="submit" ><strong>Do not press me</strong></button>', output) end def test_button_tag_with_confirmation assert_dom_equal( %(<button name="button" type="submit" data-confirm="Are you sure?">Save</button>), - button_tag("Save", :type => "submit", :data => { :confirm => "Are you sure?" }) + button_tag("Save", type: "submit", data: { confirm: "Are you sure?" }) ) end @@ -591,7 +590,7 @@ class FormTagHelperTest < ActionView::TestCase def test_image_submit_tag_with_confirmation assert_dom_equal( %(<input alt="Save" type="image" src="/images/save.gif" data-confirm="Are you sure?" />), - image_submit_tag("save.gif", :data => { :confirm => "Are you sure?" }) + image_submit_tag("save.gif", data: { confirm: "Are you sure?" }) ) end @@ -621,7 +620,7 @@ class FormTagHelperTest < ActionView::TestCase end def test_datetime_field_tag - expected = %{<input id="appointment" name="appointment" type="datetime" />} + expected = %{<input id="appointment" name="appointment" type="datetime-local" />} assert_dom_equal(expected, datetime_field_tag("appointment")) end @@ -652,12 +651,12 @@ class FormTagHelperTest < ActionView::TestCase def test_number_field_tag expected = %{<input name="quantity" max="9" id="quantity" type="number" min="1" />} - assert_dom_equal(expected, number_field_tag("quantity", nil, :in => 1...10)) + assert_dom_equal(expected, number_field_tag("quantity", nil, in: 1...10)) end def test_range_input_tag expected = %{<input name="volume" step="0.1" max="11" id="volume" type="range" min="0" />} - assert_dom_equal(expected, range_field_tag("volume", nil, :in => 0..11, :step => 0.1)) + assert_dom_equal(expected, range_field_tag("volume", nil, in: 0..11, step: 0.1)) end def test_field_set_tag_in_erb @@ -693,33 +692,33 @@ class FormTagHelperTest < ActionView::TestCase end def test_text_area_tag_options_symbolize_keys_side_effects - options = { :option => "random_option" } + options = { option: "random_option" } text_area_tag "body", "hello world", options - assert_equal options, { :option => "random_option" } + assert_equal options, option: "random_option" end def test_submit_tag_options_symbolize_keys_side_effects - options = { :option => "random_option" } + options = { option: "random_option" } submit_tag "submit value", options - assert_equal options, { :option => "random_option" } + assert_equal options, option: "random_option" end def test_button_tag_options_symbolize_keys_side_effects - options = { :option => "random_option" } + options = { option: "random_option" } button_tag "button value", options - assert_equal options, { :option => "random_option" } + assert_equal options, option: "random_option" end def test_image_submit_tag_options_symbolize_keys_side_effects - options = { :option => "random_option" } + options = { option: "random_option" } image_submit_tag "submit source", options - assert_equal options, { :option => "random_option" } + assert_equal options, option: "random_option" end def test_image_label_tag_options_symbolize_keys_side_effects - options = { :option => "random_option" } + options = { option: "random_option" } label_tag "submit source", "title", options - assert_equal options, { :option => "random_option" } + assert_equal options, option: "random_option" end def protect_against_forgery? @@ -728,7 +727,7 @@ class FormTagHelperTest < ActionView::TestCase private - def root_elem(rendered_content) - Nokogiri::HTML::DocumentFragment.parse(rendered_content).children.first # extract from nodeset - end + def root_elem(rendered_content) + Nokogiri::HTML::DocumentFragment.parse(rendered_content).children.first # extract from nodeset + end end diff --git a/actionview/test/template/html_test.rb b/actionview/test/template/html_test.rb index 549c12c88c..60f4b0aee6 100644 --- a/actionview/test/template/html_test.rb +++ b/actionview/test/template/html_test.rb @@ -1,17 +1,17 @@ -require 'abstract_unit' +require "abstract_unit" class HTMLTest < ActiveSupport::TestCase - test 'formats returns symbol for recognized MIME type' do - assert_equal [:html], ActionView::Template::HTML.new('', :html).formats + test "formats returns symbol for recognized MIME type" do + assert_equal [:html], ActionView::Template::HTML.new("", :html).formats end - test 'formats returns string for recognized MIME type when MIME does not have symbol' do + test "formats returns string for recognized MIME type when MIME does not have symbol" do foo = Mime::Type.lookup("foo") assert_nil foo.to_sym - assert_equal ['foo'], ActionView::Template::HTML.new('', foo).formats + assert_equal ["foo"], ActionView::Template::HTML.new("", foo).formats end - test 'formats returns string for unknown MIME type' do - assert_equal ['foo'], ActionView::Template::HTML.new('', 'foo').formats + test "formats returns string for unknown MIME type" do + assert_equal ["foo"], ActionView::Template::HTML.new("", "foo").formats end end diff --git a/actionview/test/template/javascript_helper_test.rb b/actionview/test/template/javascript_helper_test.rb index 9f1535ef53..0468c845d2 100644 --- a/actionview/test/template/javascript_helper_test.rb +++ b/actionview/test/template/javascript_helper_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class JavaScriptHelperTest < ActionView::TestCase tests ActionView::Helpers::JavaScriptHelper @@ -16,7 +16,7 @@ class JavaScriptHelperTest < ActionView::TestCase end def test_escape_javascript - assert_equal '', escape_javascript(nil) + assert_equal "", escape_javascript(nil) assert_equal %(This \\"thing\\" is really\\n netos\\'), escape_javascript(%(This "thing" is really\n netos')) assert_equal %(backslash\\\\test), escape_javascript( %(backslash\\test) ) assert_equal %(dont <\\/close> tags), escape_javascript(%(dont </close> tags)) @@ -36,24 +36,24 @@ class JavaScriptHelperTest < ActionView::TestCase end def test_javascript_tag - self.output_buffer = 'foo' + self.output_buffer = "foo" assert_dom_equal "<script>\n//<![CDATA[\nalert('hello')\n//]]>\n</script>", javascript_tag("alert('hello')") - assert_equal 'foo', output_buffer, 'javascript_tag without a block should not concat to output_buffer' + assert_equal "foo", output_buffer, "javascript_tag without a block should not concat to output_buffer" end # Setting the :extname option will control what extension (if any) is appended to the url for assets def test_javascript_include_tag - assert_dom_equal "<script src='/foo.js'></script>", javascript_include_tag('/foo') - assert_dom_equal "<script src='/foo'></script>", javascript_include_tag('/foo', extname: false ) - assert_dom_equal "<script src='/foo.bar'></script>", javascript_include_tag('/foo', extname: '.bar' ) + assert_dom_equal "<script src='/foo.js'></script>", javascript_include_tag("/foo") + assert_dom_equal "<script src='/foo'></script>", javascript_include_tag("/foo", extname: false ) + assert_dom_equal "<script src='/foo.bar'></script>", javascript_include_tag("/foo", extname: ".bar" ) end def test_javascript_tag_with_options assert_dom_equal "<script id=\"the_js_tag\">\n//<![CDATA[\nalert('hello')\n//]]>\n</script>", - javascript_tag("alert('hello')", :id => "the_js_tag") + javascript_tag("alert('hello')", id: "the_js_tag") end def test_javascript_cdata_section diff --git a/actionview/test/template/log_subscriber_test.rb b/actionview/test/template/log_subscriber_test.rb index 4776c18b0b..7f358add7e 100644 --- a/actionview/test/template/log_subscriber_test.rb +++ b/actionview/test/template/log_subscriber_test.rb @@ -30,83 +30,148 @@ class AVLogSubscriberTest < ActiveSupport::TestCase ActionView::Base.logger = logger end + def set_cache_controller + controller = ActionController::Base.new + controller.perform_caching = true + controller.cache_store = ActiveSupport::Cache::MemoryStore.new + @view.controller = controller + end + + def set_view_cache_dependencies + def @view.view_cache_dependencies; []; end + def @view.fragment_cache_key(*); "ahoy `controller` dependency"; end + end + def test_render_file_template Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do - @view.render(:file => "test/hello_world") + @view.render(file: "test/hello_world") wait - assert_equal 1, @logger.logged(:info).size + assert_equal 2, @logger.logged(:info).size + assert_match(/Rendering test\/hello_world\.erb/, @logger.logged(:info).first) assert_match(/Rendered test\/hello_world\.erb/, @logger.logged(:info).last) end end def test_render_text_template Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do - @view.render(:text => "TEXT") + @view.render(plain: "TEXT") wait - assert_equal 1, @logger.logged(:info).size + assert_equal 2, @logger.logged(:info).size + assert_match(/Rendering text template/, @logger.logged(:info).first) assert_match(/Rendered text template/, @logger.logged(:info).last) end end def test_render_inline_template Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do - @view.render(:inline => "<%= 'TEXT' %>") + @view.render(inline: "<%= 'TEXT' %>") wait - assert_equal 1, @logger.logged(:info).size + assert_equal 2, @logger.logged(:info).size + assert_match(/Rendering inline template/, @logger.logged(:info).first) assert_match(/Rendered inline template/, @logger.logged(:info).last) end end - def test_render_partial_template + def test_render_partial_with_implicit_path Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do - @view.render(:partial => "test/customer") + @view.render(Customer.new("david"), greeting: "hi") wait assert_equal 1, @logger.logged(:info).size - assert_match(/Rendered test\/_customer.erb/, @logger.logged(:info).last) + assert_match(/Rendered customers\/_customer\.html\.erb/, @logger.logged(:info).last) end end - def test_render_partial_with_implicit_path + def test_render_partial_with_cache_missed Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do - @view.render(Customer.new("david"), :greeting => "hi") + set_view_cache_dependencies + set_cache_controller + + @view.render(partial: "test/cached_customer", locals: { cached_customer: Customer.new("david") }) wait assert_equal 1, @logger.logged(:info).size - assert_match(/Rendered customers\/_customer\.html\.erb/, @logger.logged(:info).last) + assert_match(/Rendered test\/_cached_customer\.erb (.*) \[cache miss\]/, @logger.logged(:info).last) + end + end + + def test_render_partial_with_cache_hitted + Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do + set_view_cache_dependencies + set_cache_controller + + @view.render(partial: "test/cached_customer", locals: { cached_customer: Customer.new("david") }) + # Second render should hit cache. + @view.render(partial: "test/cached_customer", locals: { cached_customer: Customer.new("david") }) + wait + + assert_equal 2, @logger.logged(:info).size + assert_match(/Rendered test\/_cached_customer\.erb (.*) \[cache hit\]/, @logger.logged(:info).last) + end + end + + def test_render_partial_with_cache_hitted_and_missed + Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do + set_view_cache_dependencies + set_cache_controller + + @view.render(partial: "test/cached_customer", locals: { cached_customer: Customer.new("david") }) + wait + assert_match(/Rendered test\/_cached_customer\.erb (.*) \[cache miss\]/, @logger.logged(:info).last) + + @view.render(partial: "test/cached_customer", locals: { cached_customer: Customer.new("david") }) + wait + assert_match(/Rendered test\/_cached_customer\.erb (.*) \[cache hit\]/, @logger.logged(:info).last) + + @view.render(partial: "test/cached_customer", locals: { cached_customer: Customer.new("Stan") }) + wait + assert_match(/Rendered test\/_cached_customer\.erb (.*) \[cache miss\]/, @logger.logged(:info).last) end end def test_render_collection_template Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do - @view.render(:partial => "test/customer", :collection => [ Customer.new("david"), Customer.new("mary") ]) + @view.render(partial: "test/customer", collection: [ Customer.new("david"), Customer.new("mary") ]) wait assert_equal 1, @logger.logged(:info).size - assert_match(/Rendered test\/_customer.erb/, @logger.logged(:info).last) + assert_match(/Rendered collection of test\/_customer.erb \[2 times\]/, @logger.logged(:info).last) end end def test_render_collection_with_implicit_path Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do - @view.render([ Customer.new("david"), Customer.new("mary") ], :greeting => "hi") + @view.render([ Customer.new("david"), Customer.new("mary") ], greeting: "hi") wait assert_equal 1, @logger.logged(:info).size - assert_match(/Rendered customers\/_customer\.html\.erb/, @logger.logged(:info).last) + assert_match(/Rendered collection of customers\/_customer\.html\.erb \[2 times\]/, @logger.logged(:info).last) end end def test_render_collection_template_without_path Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do - @view.render([ GoodCustomer.new("david"), Customer.new("mary") ], :greeting => "hi") + @view.render([ GoodCustomer.new("david"), Customer.new("mary") ], greeting: "hi") + wait + + assert_equal 1, @logger.logged(:info).size + assert_match(/Rendered collection of templates/, @logger.logged(:info).last) + end + end + + def test_render_collection_with_cached_set + Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do + set_view_cache_dependencies + + @view.render(partial: "customers/customer", collection: [ Customer.new("david"), Customer.new("mary") ], cached: true, + locals: { greeting: "hi" }) wait assert_equal 1, @logger.logged(:info).size - assert_match(/Rendered collection/, @logger.logged(:info).last) + assert_match(/Rendered collection of customers\/_customer\.html\.erb \[0 \/ 2 cache hits\]/, @logger.logged(:info).last) end end end diff --git a/actionview/test/template/lookup_context_test.rb b/actionview/test/template/lookup_context_test.rb index 2e3a3f9bae..b47d92df34 100644 --- a/actionview/test/template/lookup_context_test.rb +++ b/actionview/test/template/lookup_context_test.rb @@ -120,8 +120,8 @@ class LookupContextTest < ActiveSupport::TestCase @lookup_context.with_fallbacks do assert_equal 3, @lookup_context.view_paths.size - assert @lookup_context.view_paths.include?(ActionView::FallbackFileSystemResolver.new("")) - assert @lookup_context.view_paths.include?(ActionView::FallbackFileSystemResolver.new("/")) + assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.new("") + assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.new("/") end end @@ -279,10 +279,9 @@ class TestMissingTemplate < ActiveSupport::TestCase test "if a single prefix is passed as a string and the lookup fails, MissingTemplate accepts it" do e = assert_raise ActionView::MissingTemplate do - details = {:handlers=>[], :formats=>[], :variants=>[], :locale=>[]} + details = { handlers: [], formats: [], variants: [], locale: [] } @lookup_context.view_paths.find("foo", "parent", true, details) end assert_match %r{Missing partial parent/_foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message end - end diff --git a/actionview/test/template/number_helper_test.rb b/actionview/test/template/number_helper_test.rb index ace3e950b8..2a2ada2b36 100644 --- a/actionview/test/template/number_helper_test.rb +++ b/actionview/test/template/number_helper_test.rb @@ -20,16 +20,16 @@ class NumberHelperTest < ActionView::TestCase assert_equal "&pound;1,234,567,890.50", number_to_currency("1234567890.50", unit: "£") assert_equal "<b>1,234,567,890.50</b> $", number_to_currency("1234567890.50", format: "<b>%n</b> %u") assert_equal "<b>1,234,567,890.50</b> $", number_to_currency("-1234567890.50", negative_format: "<b>%n</b> %u") - assert_equal "<b>1,234,567,890.50</b> $", number_to_currency("-1234567890.50", 'negative_format' => "<b>%n</b> %u") - assert_equal '₹ 12,30,000.00', number_to_currency(1230000, delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/, unit: '₹', format: "%u %n") + assert_equal "<b>1,234,567,890.50</b> $", number_to_currency("-1234567890.50", "negative_format" => "<b>%n</b> %u") + assert_equal "₹ 12,30,000.00", number_to_currency(1230000, delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/, unit: "₹", format: "%u %n") end def test_number_to_percentage assert_equal 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> %') - assert_equal "<b>100.000</b> %", number_to_percentage(100, format: raw('<b>%n</b> %')) + assert_equal "100.000 %", number_to_percentage(100, format: "%n %") + assert_equal "<b>100.000</b> %", number_to_percentage(100, format: "<b>%n</b> %") + assert_equal "<b>100.000</b> %", number_to_percentage(100, format: raw("<b>%n</b> %")) assert_equal "100%", number_to_percentage(100, precision: 0) assert_equal "123.4%", number_to_percentage(123.400, precision: 3, strip_insignificant_zeros: true) assert_equal "1.000,000%", number_to_percentage(1000, delimiter: ".", separator: ",") @@ -71,27 +71,27 @@ class NumberHelperTest < ActionView::TestCase def test_number_to_human_escape_units volume = { unit: "<b>ml</b>", thousand: "<b>lt</b>", million: "<b>m3</b>", trillion: "<b>km3</b>", quadrillion: "<b>Pl</b>" } - assert_equal '123 <b>lt</b>', number_to_human(123456, :units => volume) - assert_equal '12 <b>ml</b>', number_to_human(12, :units => volume) - assert_equal '1.23 <b>m3</b>', number_to_human(1234567, :units => volume) - assert_equal '1.23 <b>km3</b>', number_to_human(1_234_567_000_000, :units => volume) - assert_equal '1.23 <b>Pl</b>', number_to_human(1_234_567_000_000_000, :units => volume) + assert_equal "123 <b>lt</b>", number_to_human(123456, units: volume) + assert_equal "12 <b>ml</b>", number_to_human(12, units: volume) + assert_equal "1.23 <b>m3</b>", number_to_human(1234567, units: volume) + assert_equal "1.23 <b>km3</b>", number_to_human(1_234_567_000_000, units: volume) + assert_equal "1.23 <b>Pl</b>", number_to_human(1_234_567_000_000_000, units: volume) #Including fractionals distance = { mili: "<b>mm</b>", centi: "<b>cm</b>", deci: "<b>dm</b>", unit: "<b>m</b>", ten: "<b>dam</b>", hundred: "<b>hm</b>", thousand: "<b>km</b>", - micro: "<b>um</b>", nano: "<b>nm</b>", pico: "<b>pm</b>", femto: "<b>fm</b>"} - assert_equal '1.23 <b>mm</b>', number_to_human(0.00123, :units => distance) - assert_equal '1.23 <b>cm</b>', number_to_human(0.0123, :units => distance) - assert_equal '1.23 <b>dm</b>', number_to_human(0.123, :units => distance) - assert_equal '1.23 <b>m</b>', number_to_human(1.23, :units => distance) - assert_equal '1.23 <b>dam</b>', number_to_human(12.3, :units => distance) - assert_equal '1.23 <b>hm</b>', number_to_human(123, :units => distance) - assert_equal '1.23 <b>km</b>', number_to_human(1230, :units => distance) - assert_equal '1.23 <b>um</b>', number_to_human(0.00000123, :units => distance) - assert_equal '1.23 <b>nm</b>', number_to_human(0.00000000123, :units => distance) - assert_equal '1.23 <b>pm</b>', number_to_human(0.00000000000123, :units => distance) - assert_equal '1.23 <b>fm</b>', number_to_human(0.00000000000000123, :units => distance) + micro: "<b>um</b>", nano: "<b>nm</b>", pico: "<b>pm</b>", femto: "<b>fm</b>" } + assert_equal "1.23 <b>mm</b>", number_to_human(0.00123, units: distance) + assert_equal "1.23 <b>cm</b>", number_to_human(0.0123, units: distance) + assert_equal "1.23 <b>dm</b>", number_to_human(0.123, units: distance) + assert_equal "1.23 <b>m</b>", number_to_human(1.23, units: distance) + assert_equal "1.23 <b>dam</b>", number_to_human(12.3, units: distance) + assert_equal "1.23 <b>hm</b>", number_to_human(123, units: distance) + assert_equal "1.23 <b>km</b>", number_to_human(1230, units: distance) + assert_equal "1.23 <b>um</b>", number_to_human(0.00000123, units: distance) + assert_equal "1.23 <b>nm</b>", number_to_human(0.00000000123, units: distance) + assert_equal "1.23 <b>pm</b>", number_to_human(0.00000000000123, units: distance) + assert_equal "1.23 <b>fm</b>", number_to_human(0.00000000000000123, units: distance) end def test_number_helpers_escape_delimiter_and_separator @@ -116,9 +116,9 @@ class NumberHelperTest < ActionView::TestCase end def test_number_to_human_with_custom_translation_scope - I18n.backend.store_translations 'ts', - :custom_units_for_number_to_human => {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"} - assert_equal "1.01 cm", number_to_human(0.0101, :locale => 'ts', :units => :custom_units_for_number_to_human) + I18n.backend.store_translations "ts", + custom_units_for_number_to_human: { mili: "mm", centi: "cm", deci: "dm", unit: "m", ten: "dam", hundred: "hm", thousand: "km" } + assert_equal "1.01 cm", number_to_human(0.0101, locale: "ts", units: :custom_units_for_number_to_human) ensure I18n.reload! end diff --git a/actionview/test/template/output_safety_helper_test.rb b/actionview/test/template/output_safety_helper_test.rb index 8de0ae2f6f..98b1938276 100644 --- a/actionview/test/template/output_safety_helper_test.rb +++ b/actionview/test/template/output_safety_helper_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class OutputSafetyHelperTest < ActionView::TestCase tests ActionView::Helpers::OutputSafetyHelper @@ -26,10 +26,65 @@ class OutputSafetyHelperTest < ActionView::TestCase end test "safe_join should work recursively similarly to Array.join" do - joined = safe_join(['a',['b','c']], ':') - assert_equal 'a:b:c', joined + joined = safe_join(["a",["b","c"]], ":") + assert_equal "a:b:c", joined - joined = safe_join(['"a"',['<b>','<c>']], ' <br/> ') - assert_equal '"a" <br/> <b> <br/> <c>', joined + joined = safe_join(['"a"',["<b>","<c>"]], " <br/> ") + assert_equal ""a" <br/> <b> <br/> <c>", joined + end + + test "to_sentence should escape non-html_safe values" do + actual = to_sentence(%w(< > & ' ")) + assert actual.html_safe? + assert_equal("<, >, &, ', and "", actual) + + actual = to_sentence(%w(<script>)) + assert actual.html_safe? + assert_equal("<script>", actual) + end + + test "to_sentence does not double escape if single value is html_safe" do + assert_equal("<script>", to_sentence([ERB::Util.html_escape("<script>")])) + assert_equal("<script>", to_sentence(["<script>".html_safe])) + assert_equal("&lt;script&gt;", to_sentence(["<script>"])) + end + + test "to_sentence connector words are checked for html safety" do + assert_equal "one & two, and three", to_sentence(["one", "two", "three"], words_connector: " & ".html_safe) + assert_equal "one & two", to_sentence(["one", "two"], two_words_connector: " & ".html_safe) + assert_equal "one, two <script>alert(1)</script> three", to_sentence(["one", "two", "three"], last_word_connector: " <script>alert(1)</script> ") + end + + test "to_sentence should not escape html_safe values" do + ptag = content_tag("p") do + safe_join(["<marquee>shady stuff</marquee>", tag("br")]) + end + url = "https://example.com" + expected = %(<a href="#{url}">#{url}</a> and <p><marquee>shady stuff</marquee><br /></p>) + actual = to_sentence([link_to(url, url), ptag]) + assert actual.html_safe? + assert_equal(expected, actual) + end + + test "to_sentence handles blank strings" do + actual = to_sentence(["", "two", "three"]) + assert actual.html_safe? + assert_equal ", two, and three", actual + end + + test "to_sentence handles nil values" do + actual = to_sentence([nil, "two", "three"]) + assert actual.html_safe? + assert_equal ", two, and three", actual + end + + test "to_sentence still supports ActiveSupports Array#to_sentence arguments" do + assert_equal "one two, and three", to_sentence(["one", "two", "three"], words_connector: " ") + assert_equal "one & two, and three", to_sentence(["one", "two", "three"], words_connector: " & ".html_safe) + assert_equal "onetwo, and three", to_sentence(["one", "two", "three"], words_connector: nil) + assert_equal "one, two, and also three", to_sentence(["one", "two", "three"], last_word_connector: ", and also ") + assert_equal "one, twothree", to_sentence(["one", "two", "three"], last_word_connector: nil) + 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 end diff --git a/actionview/test/template/partial_iteration_test.rb b/actionview/test/template/partial_iteration_test.rb index 695f9f1bef..3ebf3b550a 100644 --- a/actionview/test/template/partial_iteration_test.rb +++ b/actionview/test/template/partial_iteration_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'action_view/renderer/partial_renderer' +require "abstract_unit" +require "action_view/renderer/partial_renderer" class PartialIterationTest < ActiveSupport::TestCase def test_has_size_and_index diff --git a/actionview/test/template/record_identifier_test.rb b/actionview/test/template/record_identifier_test.rb index 04898c0b0e..ce446715fd 100644 --- a/actionview/test/template/record_identifier_test.rb +++ b/actionview/test/template/record_identifier_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'controller/fake_models' +require "abstract_unit" +require "controller/fake_models" class RecordIdentifierTest < ActiveSupport::TestCase include ActionView::RecordIdentifier @@ -7,8 +7,8 @@ class RecordIdentifierTest < ActiveSupport::TestCase def setup @klass = Comment @record = @klass.new - @singular = 'comment' - @plural = 'comments' + @singular = "comment" + @plural = "comments" end def test_dom_id_with_new_record @@ -73,7 +73,7 @@ class RecordIdentifierWithoutActiveModelTest < ActiveSupport::TestCase end def test_dom_class - assert_equal 'airplane', dom_class(@record) + assert_equal "airplane", dom_class(@record) end def test_dom_class_with_prefix @@ -86,6 +86,6 @@ class RecordIdentifierWithoutActiveModelTest < ActiveSupport::TestCase end def test_dom_class_as_singleton_method - assert_equal 'airplane', ActionView::RecordIdentifier.dom_class(@record) + assert_equal "airplane", ActionView::RecordIdentifier.dom_class(@record) end end diff --git a/actionview/test/template/record_tag_helper_test.rb b/actionview/test/template/record_tag_helper_test.rb index bfc5d04bed..3685230558 100644 --- a/actionview/test/template/record_tag_helper_test.rb +++ b/actionview/test/template/record_tag_helper_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class RecordTagPost extend ActiveModel::Naming @@ -14,7 +14,6 @@ class RecordTagPost end class RecordTagHelperTest < ActionView::TestCase - tests ActionView::Helpers::RecordTagHelper def setup diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb index 333e0cca11..e7e0b147c7 100644 --- a/actionview/test/template/render_test.rb +++ b/actionview/test/template/render_test.rb @@ -1,12 +1,12 @@ -require 'abstract_unit' -require 'controller/fake_models' +require "abstract_unit" +require "controller/fake_models" class TestController < ActionController::Base end module RenderTestCases def setup_view(paths) - @assigns = { :secret => 'in the sauce' } + @assigns = { secret: "in the sauce" } @view = Class.new(ActionView::Base) do def view_cache_dependencies; end @@ -18,8 +18,8 @@ module RenderTestCases @controller_view = TestController.new.view_context # Reload and register danish language for testing - I18n.backend.store_translations 'da', {} - I18n.backend.store_translations 'pt-BR', {} + I18n.backend.store_translations "da", {} + I18n.backend.store_translations "pt-BR", {} # Ensure original are still the same since we are reindexing view paths assert_equal ORIGINAL_LOCALES, I18n.available_locales.map(&:to_s).sort @@ -31,117 +31,124 @@ module RenderTestCases end def test_render_file - assert_equal "Hello world!", @view.render(:file => "test/hello_world") + assert_equal "Hello world!", @view.render(file: "test/hello_world") end # Test if :formats, :locale etc. options are passed correctly to the resolvers. def test_render_file_with_format - assert_match "<h1>No Comment</h1>", @view.render(:file => "comments/empty", :formats => [:html]) - assert_match "<error>No Comment</error>", @view.render(:file => "comments/empty", :formats => [:xml]) - assert_match "<error>No Comment</error>", @view.render(:file => "comments/empty", :formats => :xml) + assert_match "<h1>No Comment</h1>", @view.render(file: "comments/empty", formats: [:html]) + assert_match "<error>No Comment</error>", @view.render(file: "comments/empty", formats: [:xml]) + assert_match "<error>No Comment</error>", @view.render(file: "comments/empty", formats: :xml) end def test_render_template_with_format - assert_match "<h1>No Comment</h1>", @view.render(:template => "comments/empty", :formats => [:html]) - assert_match "<error>No Comment</error>", @view.render(:template => "comments/empty", :formats => [:xml]) + assert_match "<h1>No Comment</h1>", @view.render(template: "comments/empty", formats: [:html]) + assert_match "<error>No Comment</error>", @view.render(template: "comments/empty", formats: [:xml]) end def test_rendered_format_without_format - @view.render(:inline => "test") + @view.render(inline: "test") assert_equal :html, @view.lookup_context.rendered_format end def test_render_partial_implicitly_use_format_of_the_rendered_template @view.lookup_context.formats = [:json] - assert_equal "Hello world", @view.render(:template => "test/one", :formats => [:html]) + assert_equal "Hello world", @view.render(template: "test/one", formats: [:html]) end def test_render_partial_implicitly_use_format_of_the_rendered_partial @view.lookup_context.formats = [:html] - assert_equal "Third level", @view.render(:template => "test/html_template") + assert_equal "Third level", @view.render(template: "test/html_template") end def test_render_partial_use_last_prepended_format_for_partials_with_the_same_names @view.lookup_context.formats = [:html] - assert_equal "\nHTML Template, but JSON partial", @view.render(:template => "test/change_priority") + assert_equal "\nHTML Template, but JSON partial", @view.render(template: "test/change_priority") end def test_render_template_with_a_missing_partial_of_another_format @view.lookup_context.formats = [:html] e = assert_raise ActionView::Template::Error do - @view.render(:template => "with_format", :formats => [:json]) + @view.render(template: "with_format", formats: [:json]) end assert_includes(e.message, "Missing partial /_missing with {:locale=>[:en], :formats=>[:json], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby]}.") end def test_render_file_with_locale - assert_equal "<h1>Kein Kommentar</h1>", @view.render(:file => "comments/empty", :locale => [:de]) - assert_equal "<h1>Kein Kommentar</h1>", @view.render(:file => "comments/empty", :locale => :de) + assert_equal "<h1>Kein Kommentar</h1>", @view.render(file: "comments/empty", locale: [:de]) + assert_equal "<h1>Kein Kommentar</h1>", @view.render(file: "comments/empty", locale: :de) end def test_render_template_with_locale - assert_equal "<h1>Kein Kommentar</h1>", @view.render(:template => "comments/empty", :locale => [:de]) + assert_equal "<h1>Kein Kommentar</h1>", @view.render(template: "comments/empty", locale: [:de]) end def test_render_file_with_handlers - assert_equal "<h1>No Comment</h1>\n", @view.render(:file => "comments/empty", :handlers => [:builder]) - assert_equal "<h1>No Comment</h1>\n", @view.render(:file => "comments/empty", :handlers => :builder) + assert_equal "<h1>No Comment</h1>\n", @view.render(file: "comments/empty", handlers: [:builder]) + assert_equal "<h1>No Comment</h1>\n", @view.render(file: "comments/empty", handlers: :builder) end def test_render_template_with_handlers - assert_equal "<h1>No Comment</h1>\n", @view.render(:template => "comments/empty", :handlers => [:builder]) + assert_equal "<h1>No Comment</h1>\n", @view.render(template: "comments/empty", handlers: [:builder]) end def test_render_raw_template_with_handlers - assert_equal "<%= hello_world %>\n", @view.render(:template => "plain_text") + assert_equal "<%= hello_world %>\n", @view.render(template: "plain_text") end def test_render_raw_template_with_quotes - assert_equal %q;Here are some characters: !@#$%^&*()-="'}{`; + "\n", @view.render(:template => "plain_text_with_characters") + assert_equal %q;Here are some characters: !@#$%^&*()-="'}{`; + "\n", @view.render(template: "plain_text_with_characters") + end + + def test_render_raw_is_html_safe_and_does_not_escape_output + buffer = ActiveSupport::SafeBuffer.new + buffer << @view.render(file: "plain_text") + assert_equal true, buffer.html_safe? + assert_equal buffer, "<%= hello_world %>\n" end def test_render_ruby_template_with_handlers - assert_equal "Hello from Ruby code", @view.render(:template => "ruby_template") + assert_equal "Hello from Ruby code", @view.render(template: "ruby_template") end def test_render_ruby_template_inline - assert_equal '4', @view.render(:inline => "(2**2).to_s", :type => :ruby) + assert_equal "4", @view.render(inline: "(2**2).to_s", type: :ruby) end def test_render_file_with_localization_on_context_level old_locale, @view.locale = @view.locale, :da - assert_equal "Hey verden", @view.render(:file => "test/hello_world") + assert_equal "Hey verden", @view.render(file: "test/hello_world") ensure @view.locale = old_locale end def test_render_file_with_dashed_locale old_locale, @view.locale = @view.locale, :"pt-BR" - assert_equal "Ola mundo", @view.render(:file => "test/hello_world") + assert_equal "Ola mundo", @view.render(file: "test/hello_world") ensure @view.locale = old_locale end def test_render_file_at_top_level - assert_equal 'Elastica', @view.render(:file => '/shared') + assert_equal "Elastica", @view.render(file: "/shared") end def test_render_file_with_full_path - template_path = File.join(File.dirname(__FILE__), '../fixtures/test/hello_world') - assert_equal "Hello world!", @view.render(:file => template_path) + template_path = File.join(File.dirname(__FILE__), "../fixtures/test/hello_world") + assert_equal "Hello world!", @view.render(file: template_path) end def test_render_file_with_instance_variables - assert_equal "The secret is in the sauce\n", @view.render(:file => "test/render_file_with_ivar") + assert_equal "The secret is in the sauce\n", @view.render(file: "test/render_file_with_ivar") end def test_render_file_with_locals - locals = { :secret => 'in the sauce' } - assert_equal "The secret is in the sauce\n", @view.render(:file => "test/render_file_with_locals", :locals => locals) + locals = { secret: "in the sauce" } + assert_equal "The secret is in the sauce\n", @view.render(file: "test/render_file_with_locals", locals: locals) end def test_render_file_not_using_full_path_with_dot_in_path - assert_equal "The secret is in the sauce\n", @view.render(:file => "test/dot.directory/render_file_with_ivar") + assert_equal "The secret is in the sauce\n", @view.render(file: "test/dot.directory/render_file_with_ivar") end def test_render_partial_from_default @@ -149,90 +156,94 @@ module RenderTestCases end def test_render_outside_path - assert File.exist?(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb')) + assert File.exist?(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb")) assert_raises ActionView::MissingTemplate do - @view.render(:template => "../\\../test/abstract_unit.rb") + @view.render(template: "../\\../test/abstract_unit.rb") end end def test_render_partial - assert_equal "only partial", @view.render(:partial => "test/partial_only") + assert_equal "only partial", @view.render(partial: "test/partial_only") end def test_render_partial_with_format - assert_equal 'partial html', @view.render(:partial => 'test/partial') + assert_equal "partial html", @view.render(partial: "test/partial") end def test_render_partial_with_selected_format - assert_equal 'partial html', @view.render(:partial => 'test/partial', :formats => :html) - assert_equal 'partial js', @view.render(:partial => 'test/partial', :formats => [:js]) + assert_equal "partial html", @view.render(partial: "test/partial", formats: :html) + assert_equal "partial js", @view.render(partial: "test/partial", formats: [:js]) end def test_render_partial_at_top_level # file fixtures/_top_level_partial_only (not fixtures/test) - assert_equal 'top level partial', @view.render(:partial => '/top_level_partial_only') + assert_equal "top level partial", @view.render(partial: "/top_level_partial_only") end def test_render_partial_with_format_at_top_level # file fixtures/_top_level_partial.html (not fixtures/test, with format extension) - assert_equal 'top level partial html', @view.render(:partial => '/top_level_partial') + assert_equal "top level partial html", @view.render(partial: "/top_level_partial") end def test_render_partial_with_locals - assert_equal "5", @view.render(:partial => "test/counter", :locals => { :counter_counter => 5 }) + assert_equal "5", @view.render(partial: "test/counter", locals: { counter_counter: 5 }) end def test_render_partial_with_locals_from_default - assert_equal "only partial", @view.render("test/partial_only", :counter_counter => 5) + assert_equal "only partial", @view.render("test/partial_only", counter_counter: 5) end def test_render_partial_with_number - assert_nothing_raised { @view.render(:partial => "test/200") } + assert_nothing_raised { @view.render(partial: "test/200") } end def test_render_partial_with_missing_filename - assert_raises(ActionView::MissingTemplate) { @view.render(:partial => "test/") } + assert_raises(ActionView::MissingTemplate) { @view.render(partial: "test/") } end def test_render_partial_with_incompatible_object - e = assert_raises(ArgumentError) { @view.render(:partial => nil) } + e = assert_raises(ArgumentError) { @view.render(partial: nil) } assert_equal "'#{nil.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.", e.message end def test_render_partial_starting_with_a_capital - assert_nothing_raised { @view.render(:partial => 'test/FooBar') } + assert_nothing_raised { @view.render(partial: "test/FooBar") } end def test_render_partial_with_hyphen - assert_nothing_raised { @view.render(:partial => "test/a-in") } + assert_nothing_raised { @view.render(partial: "test/a-in") } + end + + def test_render_partial_with_unicode_text + assert_nothing_raised { @view.render(partial: "test/ðŸ£") } end def test_render_partial_with_invalid_option_as - e = assert_raises(ArgumentError) { @view.render(:partial => "test/partial_only", :as => 'a-in') } + 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, " + "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') } + 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, " + "and is followed by any combination of letters, numbers and underscores.", e.message end def test_render_partial_with_errors - e = assert_raises(ActionView::Template::Error) { @view.render(:partial => "test/raise") } + e = assert_raises(ActionView::Template::Error) { @view.render(partial: "test/raise") } assert_match %r!method.*doesnt_exist!, e.message assert_equal "", e.sub_template_message assert_equal "1", e.line_number - assert_equal "1: <%= doesnt_exist %>", e.annoted_source_code.strip + assert_equal "1: <%= doesnt_exist %>", e.annoted_source_code[0].strip assert_equal File.expand_path("#{FIXTURE_LOAD_PATH}/test/_raise.html.erb"), e.file_name end def test_render_error_indentation - e = assert_raises(ActionView::Template::Error) { @view.render(:partial => "test/raise_indentation") } - error_lines = e.annoted_source_code.split("\n") + e = assert_raises(ActionView::Template::Error) { @view.render(partial: "test/raise_indentation") } + error_lines = e.annoted_source_code assert_match %r!error\shere!, e.message assert_equal "11", e.line_number assert_equal " 9: <p>Ninth paragraph</p>", error_lines.second @@ -240,7 +251,7 @@ module RenderTestCases end def test_render_sub_template_with_errors - e = assert_raises(ActionView::Template::Error) { @view.render(:template => "test/sub_template_raise") } + 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_equal "1", e.line_number @@ -248,113 +259,126 @@ module RenderTestCases end def test_render_file_with_errors - e = assert_raises(ActionView::Template::Error) { @view.render(:file => File.expand_path("test/_raise", FIXTURE_LOAD_PATH)) } + e = assert_raises(ActionView::Template::Error) { @view.render(file: File.expand_path("test/_raise", FIXTURE_LOAD_PATH)) } assert_match %r!method.*doesnt_exist!, e.message assert_equal "", e.sub_template_message assert_equal "1", e.line_number - assert_equal "1: <%= doesnt_exist %>", e.annoted_source_code.strip + assert_equal "1: <%= doesnt_exist %>", e.annoted_source_code[0].strip assert_equal File.expand_path("#{FIXTURE_LOAD_PATH}/test/_raise.html.erb"), e.file_name end def test_render_object - assert_equal "Hello: david", @view.render(:partial => "test/customer", :object => Customer.new("david")) - assert_equal "FalseClass", @view.render(:partial => "test/klass", :object => false) - assert_equal "NilClass", @view.render(:partial => "test/klass", :object => nil) + assert_equal "Hello: david", @view.render(partial: "test/customer", object: Customer.new("david")) + assert_equal "FalseClass", @view.render(partial: "test/klass", object: false) + assert_equal "NilClass", @view.render(partial: "test/klass", object: nil) end def test_render_object_with_array - assert_equal "[1, 2, 3]", @view.render(:partial => "test/object_inspector", :object => [1, 2, 3]) + assert_equal "[1, 2, 3]", @view.render(partial: "test/object_inspector", object: [1, 2, 3]) end def test_render_partial_collection - assert_equal "Hello: davidHello: mary", @view.render(:partial => "test/customer", :collection => [ Customer.new("david"), Customer.new("mary") ]) + assert_equal "Hello: davidHello: mary", @view.render(partial: "test/customer", collection: [ Customer.new("david"), Customer.new("mary") ]) + end + + def test_render_partial_collection_with_partial_name_containing_dot + assert_equal "Hello: davidHello: mary", + @view.render(partial: "test/customer.mobile", collection: [ Customer.new("david"), Customer.new("mary") ]) end def test_render_partial_collection_as_by_string assert_equal "david david davidmary mary mary", - @view.render(:partial => "test/customer_with_var", :collection => [ Customer.new("david"), Customer.new("mary") ], :as => 'customer') + @view.render(partial: "test/customer_with_var", collection: [ Customer.new("david"), Customer.new("mary") ], as: "customer") end def test_render_partial_collection_as_by_symbol assert_equal "david david davidmary mary mary", - @view.render(:partial => "test/customer_with_var", :collection => [ Customer.new("david"), Customer.new("mary") ], :as => :customer) + @view.render(partial: "test/customer_with_var", collection: [ Customer.new("david"), Customer.new("mary") ], as: :customer) end def test_render_partial_collection_without_as assert_equal "local_inspector,local_inspector_counter,local_inspector_iteration", - @view.render(:partial => "test/local_inspector", :collection => [ Customer.new("mary") ]) + @view.render(partial: "test/local_inspector", collection: [ Customer.new("mary") ]) end def test_render_partial_with_empty_collection_should_return_nil - assert_nil @view.render(:partial => "test/customer", :collection => []) + assert_nil @view.render(partial: "test/customer", collection: []) end def test_render_partial_with_nil_collection_should_return_nil - assert_nil @view.render(:partial => "test/customer", :collection => nil) + assert_nil @view.render(partial: "test/customer", collection: nil) + end + + def test_render_partial_collection_for_non_array + customers = Enumerator.new do |y| + y.yield(Customer.new("david")) + y.yield(Customer.new("mary")) + end + assert_equal "Hello: davidHello: mary", @view.render(partial: "test/customer", collection: customers) end def test_render_partial_without_object_does_not_put_partial_name_to_local_assigns - assert_equal 'false', @view.render(partial: 'test/partial_name_in_local_assigns') + assert_equal "false", @view.render(partial: "test/partial_name_in_local_assigns") end def test_render_partial_with_nil_object_puts_partial_name_to_local_assigns - assert_equal 'true', @view.render(partial: 'test/partial_name_in_local_assigns', object: nil) + assert_equal "true", @view.render(partial: "test/partial_name_in_local_assigns", object: nil) end def test_render_partial_with_nil_values_in_collection - assert_equal "Hello: davidHello: Anonymous", @view.render(:partial => "test/customer", :collection => [ Customer.new("david"), nil ]) + assert_equal "Hello: davidHello: Anonymous", @view.render(partial: "test/customer", collection: [ Customer.new("david"), nil ]) end def test_render_partial_with_layout_using_collection_and_template - assert_equal "<b>Hello: Amazon</b><b>Hello: Yahoo</b>", @view.render(:partial => "test/customer", :layout => 'test/b_layout_for_partial', :collection => [ Customer.new("Amazon"), Customer.new("Yahoo") ]) + assert_equal "<b>Hello: Amazon</b><b>Hello: Yahoo</b>", @view.render(partial: "test/customer", layout: "test/b_layout_for_partial", collection: [ Customer.new("Amazon"), Customer.new("Yahoo") ]) end def test_render_partial_with_layout_using_collection_and_template_makes_current_item_available_in_layout assert_equal '<b class="amazon">Hello: Amazon</b><b class="yahoo">Hello: Yahoo</b>', - @view.render(:partial => "test/customer", :layout => 'test/b_layout_for_partial_with_object', :collection => [ Customer.new("Amazon"), Customer.new("Yahoo") ]) + @view.render(partial: "test/customer", layout: "test/b_layout_for_partial_with_object", collection: [ Customer.new("Amazon"), Customer.new("Yahoo") ]) end def test_render_partial_with_layout_using_collection_and_template_makes_current_item_counter_available_in_layout assert_equal '<b data-counter="0">Hello: Amazon</b><b data-counter="1">Hello: Yahoo</b>', - @view.render(:partial => "test/customer", :layout => 'test/b_layout_for_partial_with_object_counter', :collection => [ Customer.new("Amazon"), Customer.new("Yahoo") ]) + @view.render(partial: "test/customer", layout: "test/b_layout_for_partial_with_object_counter", collection: [ Customer.new("Amazon"), Customer.new("Yahoo") ]) end def test_render_partial_with_layout_using_object_and_template_makes_object_available_in_layout assert_equal '<b class="amazon">Hello: Amazon</b>', - @view.render(:partial => "test/customer", :layout => 'test/b_layout_for_partial_with_object', :object => Customer.new("Amazon")) + @view.render(partial: "test/customer", layout: "test/b_layout_for_partial_with_object", object: Customer.new("Amazon")) end def test_render_partial_with_empty_array_should_return_nil - assert_nil @view.render(:partial => []) + assert_nil @view.render(partial: []) end def test_render_partial_using_string - assert_equal "Hello: Anonymous", @controller_view.render('customer') + assert_equal "Hello: Anonymous", @controller_view.render("customer") end def test_render_partial_with_locals_using_string - assert_equal "Hola: david", @controller_view.render('customer_greeting', :greeting => 'Hola', :customer_greeting => Customer.new("david")) + assert_equal "Hola: david", @controller_view.render("customer_greeting", greeting: "Hola", customer_greeting: Customer.new("david")) end def test_render_partial_with_object_uses_render_partial_path assert_equal "Hello: lifo", - @controller_view.render(:partial => Customer.new("lifo"), :locals => {:greeting => "Hello"}) + @controller_view.render(partial: Customer.new("lifo"), locals: { greeting: "Hello" }) end def test_render_partial_with_object_and_format_uses_render_partial_path assert_equal "<greeting>Hello</greeting><name>lifo</name>", - @controller_view.render(:partial => Customer.new("lifo"), :formats => :xml, :locals => {:greeting => "Hello"}) + @controller_view.render(partial: Customer.new("lifo"), formats: :xml, locals: { greeting: "Hello" }) end def test_render_partial_using_object assert_equal "Hello: lifo", - @controller_view.render(Customer.new("lifo"), :greeting => "Hello") + @controller_view.render(Customer.new("lifo"), greeting: "Hello") end def test_render_partial_using_collection customers = [ Customer.new("Amazon"), Customer.new("Yahoo") ] assert_equal "Hello: AmazonHello: Yahoo", - @controller_view.render(customers, :greeting => "Hello") + @controller_view.render(customers, greeting: "Hello") end def test_render_partial_using_collection_without_path @@ -371,32 +395,32 @@ module RenderTestCases # TODO: The reason for this test is unclear, improve documentation def test_render_partial_and_fallback_to_layout - assert_equal "Before (Josh)\n\nAfter", @view.render(:partial => "test/layout_for_partial", :locals => { :name => "Josh" }) + 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 @view.formats = [:xml] - assert_raises(ActionView::MissingTemplate) { @view.render(:partial => "test/layout_for_partial") } + assert_raises(ActionView::MissingTemplate) { @view.render(partial: "test/layout_for_partial") } ensure @view.formats = nil end def test_render_layout_with_block_and_other_partial_inside - render = @view.render(:layout => "test/layout_with_partial_and_yield") { "Yield!" } + render = @view.render(layout: "test/layout_with_partial_and_yield") { "Yield!" } assert_equal "Before\npartial html\nYield!\nAfter\n", render end def test_render_inline - assert_equal "Hello, World!", @view.render(:inline => "Hello, World!") + assert_equal "Hello, World!", @view.render(inline: "Hello, World!") end def test_render_inline_with_locals - assert_equal "Hello, Josh!", @view.render(:inline => "Hello, <%= name %>!", :locals => { :name => "Josh" }) + assert_equal "Hello, Josh!", @view.render(inline: "Hello, <%= name %>!", locals: { name: "Josh" }) end def test_render_fallbacks_to_erb_for_unknown_types - assert_equal "Hello, World!", @view.render(:inline => "Hello, World!", :type => :bar) + assert_equal "Hello, World!", @view.render(inline: "Hello, World!", type: :bar) end CustomHandler = lambda do |template| @@ -406,7 +430,7 @@ module RenderTestCases def test_render_inline_with_render_from_to_proc ActionView::Template.register_template_handler :ruby_handler, :source.to_proc - assert_equal '3', @view.render(inline: "(1 + 2).to_s", type: :ruby_handler) + assert_equal "3", @view.render(inline: "(1 + 2).to_s", type: :ruby_handler) ensure ActionView::Template.unregister_template_handler :ruby_handler end @@ -426,17 +450,17 @@ module RenderTestCases end def test_render_body - assert_equal 'some body', @view.render(body: 'some body') + assert_equal "some body", @view.render(body: "some body") end def test_render_plain - assert_equal 'some plaintext', @view.render(plain: 'some plaintext') + assert_equal "some plaintext", @view.render(plain: "some plaintext") end def test_render_knows_about_types_registered_when_extensions_are_checked_earlier_in_initialization ActionView::Template::Handlers.extensions ActionView::Template.register_template_handler :foo, CustomHandler - assert ActionView::Template::Handlers.extensions.include?(:foo) + assert_includes ActionView::Template::Handlers.extensions, :foo ensure ActionView::Template.unregister_template_handler :foo end @@ -454,54 +478,54 @@ module RenderTestCases 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}") } + assert_raises(ActionView::MissingTemplate) { @view.render(file: "test/malformed/#{name}") } end end end def test_render_with_layout assert_equal %(<title></title>\nHello world!\n), - @view.render(:file => "test/hello_world", :layout => "layouts/yield") + @view.render(file: "test/hello_world", layout: "layouts/yield") end def test_render_with_layout_which_has_render_inline assert_equal %(welcome\nHello world!\n), - @view.render(:file => "test/hello_world", :layout => "layouts/yield_with_render_inline_inside") + @view.render(file: "test/hello_world", layout: "layouts/yield_with_render_inline_inside") end def test_render_with_layout_which_renders_another_partial assert_equal %(partial html\nHello world!\n), - @view.render(:file => "test/hello_world", :layout => "layouts/yield_with_render_partial_inside") + @view.render(file: "test/hello_world", layout: "layouts/yield_with_render_partial_inside") end def test_render_partial_with_html_only_extension assert_equal %(<h1>partial html</h1>\nHello world!\n), - @view.render(:file => "test/hello_world", :layout => "layouts/render_partial_html") + @view.render(file: "test/hello_world", layout: "layouts/render_partial_html") end def test_render_layout_with_block_and_yield assert_equal %(Content from block!\n), - @view.render(:layout => "layouts/yield_only") { "Content from block!" } + @view.render(layout: "layouts/yield_only") { "Content from block!" } end def test_render_layout_with_block_and_yield_with_params assert_equal %(Yield! Content from block!\n), - @view.render(:layout => "layouts/yield_with_params") { |param| "#{param} Content from block!" } + @view.render(layout: "layouts/yield_with_params") { |param| "#{param} Content from block!" } end def test_render_layout_with_block_which_renders_another_partial_and_yields assert_equal %(partial html\nContent from block!\n), - @view.render(:layout => "layouts/partial_and_yield") { "Content from block!" } + @view.render(layout: "layouts/partial_and_yield") { "Content from block!" } end def test_render_partial_and_layout_without_block_with_locals assert_equal %(Before (Foo!)\npartial html\nAfter), - @view.render(:partial => 'test/partial', :layout => 'test/layout_for_partial', :locals => { :name => 'Foo!'}) + @view.render(partial: "test/partial", layout: "test/layout_for_partial", locals: { name: "Foo!" }) end def test_render_partial_and_layout_without_block_with_locals_and_rendering_another_partial assert_equal %(Before (Foo!)\npartial html\npartial with partial\n\nAfter), - @view.render(:partial => 'test/partial_with_partial', :layout => 'test/layout_for_partial', :locals => { :name => 'Foo!'}) + @view.render(partial: "test/partial_with_partial", layout: "test/layout_for_partial", locals: { name: "Foo!" }) end def test_render_partial_shortcut_with_block_content @@ -511,37 +535,37 @@ module RenderTestCases def test_render_layout_with_a_nested_render_layout_call assert_equal %(Before (Foo!)\nBefore (Bar!)\npartial html\nAfter\npartial with layout\n\nAfter), - @view.render(:partial => 'test/partial_with_layout', :layout => 'test/layout_for_partial', :locals => { :name => 'Foo!'}) + @view.render(partial: "test/partial_with_layout", layout: "test/layout_for_partial", locals: { name: "Foo!" }) end def test_render_layout_with_a_nested_render_layout_call_using_block_with_render_partial assert_equal %(Before (Foo!)\nBefore (Bar!)\n\n partial html\n\nAfterpartial with layout\n\nAfter), - @view.render(:partial => 'test/partial_with_layout_block_partial', :layout => 'test/layout_for_partial', :locals => { :name => 'Foo!'}) + @view.render(partial: "test/partial_with_layout_block_partial", layout: "test/layout_for_partial", locals: { name: "Foo!" }) end def test_render_layout_with_a_nested_render_layout_call_using_block_with_render_content assert_equal %(Before (Foo!)\nBefore (Bar!)\n\n Content from inside layout!\n\nAfterpartial with layout\n\nAfter), - @view.render(:partial => 'test/partial_with_layout_block_content', :layout => 'test/layout_for_partial', :locals => { :name => 'Foo!'}) + @view.render(partial: "test/partial_with_layout_block_content", layout: "test/layout_for_partial", locals: { name: "Foo!" }) end def test_render_partial_with_layout_raises_descriptive_error - e = assert_raises(ActionView::MissingTemplate) { @view.render(partial: 'test/partial', layout: true) } + e = assert_raises(ActionView::MissingTemplate) { @view.render(partial: "test/partial", layout: true) } assert_match "Missing partial /_true with", e.message end def test_render_with_nested_layout assert_equal %(<title>title</title>\n\n<div id="column">column</div>\n<div id="content">content</div>\n), - @view.render(:file => "test/nested_layout", :layout => "layouts/yield") + @view.render(file: "test/nested_layout", layout: "layouts/yield") end def test_render_with_file_in_layout assert_equal %(\n<title>title</title>\n\n), - @view.render(:file => "test/layout_render_file") + @view.render(file: "test/layout_render_file") end def test_render_layout_with_object assert_equal %(<title>David</title>), - @view.render(:file => "test/layout_render_object") + @view.render(file: "test/layout_render_object") end def test_render_with_passing_couple_extensions_to_one_register_template_handler_function_call @@ -591,7 +615,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase def test_render_utf8_template_with_magic_comment with_external_encoding Encoding::ASCII_8BIT do - result = @view.render(:file => "test/utf8_magic", :formats => [:html], :layouts => "layouts/yield") + result = @view.render(file: "test/utf8_magic", formats: [:html], layouts: "layouts/yield") assert_equal Encoding::UTF_8, result.encoding assert_equal "\nРуÑÑкий \nтекÑÑ‚\n\nUTF-8\nUTF-8\nUTF-8\n", result end @@ -599,7 +623,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase def test_render_utf8_template_with_default_external_encoding with_external_encoding Encoding::UTF_8 do - result = @view.render(:file => "test/utf8", :formats => [:html], :layouts => "layouts/yield") + result = @view.render(file: "test/utf8", formats: [:html], layouts: "layouts/yield") assert_equal Encoding::UTF_8, result.encoding assert_equal "РуÑÑкий текÑÑ‚\n\nUTF-8\nUTF-8\nUTF-8\n", result end @@ -607,15 +631,15 @@ class LazyViewRenderTest < ActiveSupport::TestCase def test_render_utf8_template_with_incompatible_external_encoding with_external_encoding Encoding::SHIFT_JIS do - e = assert_raises(ActionView::Template::Error) { @view.render(:file => "test/utf8", :formats => [:html], :layouts => "layouts/yield") } - assert_match 'Your template was not saved as valid Shift_JIS', e.cause.message + e = assert_raises(ActionView::Template::Error) { @view.render(file: "test/utf8", formats: [:html], layouts: "layouts/yield") } + assert_match "Your template was not saved as valid Shift_JIS", e.cause.message end end def test_render_utf8_template_with_partial_with_incompatible_encoding with_external_encoding Encoding::SHIFT_JIS do - e = assert_raises(ActionView::Template::Error) { @view.render(:file => "test/utf8_magic_with_bare_partial", :formats => [:html], :layouts => "layouts/yield") } - assert_match 'Your template was not saved as valid Shift_JIS', e.cause.message + e = assert_raises(ActionView::Template::Error) { @view.render(file: "test/utf8_magic_with_bare_partial", formats: [:html], layouts: "layouts/yield") } + assert_match "Your template was not saved as valid Shift_JIS", e.cause.message end end @@ -628,56 +652,59 @@ class LazyViewRenderTest < ActiveSupport::TestCase end end -class CachedCollectionViewRenderTest < CachedViewRenderTest +class CachedCollectionViewRenderTest < ActiveSupport::TestCase class CachedCustomer < Customer; end - teardown do - ActionView::PartialRenderer.collection_cache.clear - end + include RenderTestCases - test "with custom key" do - customer = Customer.new("david") - key = cache_key([customer, 'key'], "test/_customer") + # Ensure view path cache is primed + setup do + view_paths = ActionController::Base.view_paths + assert_equal ActionView::OptimizedFileSystemResolver, view_paths.first.class - ActionView::PartialRenderer.collection_cache.write(key, 'Hello') + ActionView::PartialRenderer.collection_cache = ActiveSupport::Cache::MemoryStore.new - assert_equal "Hello", - @view.render(partial: "test/customer", collection: [customer], cache: ->(item) { [item, 'key'] }) + setup_view(view_paths) end - test "with caching with custom key and rendering with different key" do - customer = Customer.new("david") - key = cache_key([customer, 'key'], "test/_customer") + teardown do + GC.start + I18n.reload! + end + + test "collection caching does not cache by default" do + customer = Customer.new("david", 1) + key = cache_key(customer, "test/_customer") - ActionView::PartialRenderer.collection_cache.write(key, 'Hello') + ActionView::PartialRenderer.collection_cache.write(key, "Cached") - assert_equal "Hello: david", - @view.render(partial: "test/customer", collection: [customer], cache: ->(item) { [item, 'another_key'] }) + assert_not_equal "Cached", + @view.render(partial: "test/customer", collection: [customer]) end - test "automatic caching with inferred cache name" do - customer = CachedCustomer.new("david") - key = cache_key(customer, "test/_cached_customer") + test "collection caching with partial that doesn't use fragment caching" do + customer = Customer.new("david", 1) + key = cache_key(customer, "test/_customer") - ActionView::PartialRenderer.collection_cache.write(key, 'Cached') + ActionView::PartialRenderer.collection_cache.write(key, "Cached") assert_equal "Cached", - @view.render(partial: "test/cached_customer", collection: [customer]) + @view.render(partial: "test/customer", collection: [customer], cached: true) end - test "automatic caching with as name" do - customer = CachedCustomer.new("david") - key = cache_key(customer, "test/_cached_customer_as") + test "collection caching with cached true" do + customer = CachedCustomer.new("david", 1) + key = cache_key(customer, "test/_cached_customer") - ActionView::PartialRenderer.collection_cache.write(key, 'Cached') + ActionView::PartialRenderer.collection_cache.write(key, "Cached") assert_equal "Cached", - @view.render(partial: "test/cached_customer_as", collection: [customer], as: :buyer) + @view.render(partial: "test/cached_customer", collection: [customer], cached: true) end private - def cache_key(names, virtual_path) + def cache_key(*names, virtual_path) digest = ActionView::Digestor.digest name: virtual_path, finder: @view.lookup_context, dependencies: [] - @view.fragment_cache_key([ *Array(names), digest ]) + @view.fragment_cache_key([ *names, digest ]) end end diff --git a/actionview/test/template/resolver_cache_test.rb b/actionview/test/template/resolver_cache_test.rb new file mode 100644 index 0000000000..0ecfccd375 --- /dev/null +++ b/actionview/test/template/resolver_cache_test.rb @@ -0,0 +1,7 @@ +require "abstract_unit" + +class ResolverCacheTest < ActiveSupport::TestCase + def test_inspect_shields_cache_internals + assert_match %r(#<ActionView::Resolver:0x[0-9a-f]+ @cache=#<ActionView::Resolver::Cache:0x[0-9a-f]+ keys=0 queries=0>>), ActionView::Resolver.new.inspect + end +end diff --git a/actionview/test/template/resolver_patterns_test.rb b/actionview/test/template/resolver_patterns_test.rb index 575eb9bd28..43e3f21076 100644 --- a/actionview/test/template/resolver_patterns_test.rb +++ b/actionview/test/template/resolver_patterns_test.rb @@ -1,19 +1,19 @@ -require 'abstract_unit' +require "abstract_unit" class ResolverPatternsTest < ActiveSupport::TestCase def setup path = File.expand_path("../../fixtures/", __FILE__) - pattern = ":prefix/{:formats/,}:action{.:formats,}{.:handlers,}" + pattern = ":prefix/{:formats/,}:action{.:formats,}{+:variants,}{.:handlers,}" @resolver = ActionView::FileSystemResolver.new(path, pattern) end def test_should_return_empty_list_for_unknown_path - templates = @resolver.find_all("unknown", "custom_pattern", false, {:locale => [], :formats => [:html], :handlers => [:erb]}) + templates = @resolver.find_all("unknown", "custom_pattern", false, locale: [], formats: [:html], variants: [], handlers: [:erb]) assert_equal [], templates, "expected an empty list of templates" end def test_should_return_template_for_declared_path - templates = @resolver.find_all("path", "custom_pattern", false, {:locale => [], :formats => [:html], :handlers => [:erb]}) + templates = @resolver.find_all("path", "custom_pattern", false, locale: [], formats: [:html], variants: [], handlers: [:erb]) assert_equal 1, templates.size, "expected one template" assert_equal "Hello custom patterns!", templates.first.source assert_equal "custom_pattern/path", templates.first.virtual_path @@ -21,11 +21,22 @@ class ResolverPatternsTest < ActiveSupport::TestCase end def test_should_return_all_templates_when_ambiguous_pattern - templates = @resolver.find_all("another", "custom_pattern", false, {:locale => [], :formats => [:html], :handlers => [:erb]}) + templates = @resolver.find_all("another", "custom_pattern", false, locale: [], formats: [:html], variants: [], handlers: [:erb]) assert_equal 2, templates.size, "expected two templates" assert_equal "Another template!", templates[0].source assert_equal "custom_pattern/another", templates[0].virtual_path assert_equal "Hello custom patterns!", templates[1].source assert_equal "custom_pattern/another", templates[1].virtual_path end + + def test_should_return_all_variants_for_any + templates = @resolver.find_all("hello_world", "test", false, locale: [], formats: [:html, :text], variants: :any, handlers: [:erb]) + assert_equal 3, templates.size, "expected three templates" + assert_equal "Hello phone!", templates[0].source + assert_equal "test/hello_world", templates[0].virtual_path + assert_equal "Hello texty phone!", templates[1].source + assert_equal "test/hello_world", templates[1].virtual_path + assert_equal "Hello world!", templates[2].source + assert_equal "test/hello_world", templates[2].virtual_path + end end diff --git a/actionview/test/template/sanitize_helper_test.rb b/actionview/test/template/sanitize_helper_test.rb index efe846a7eb..c8963fee9c 100644 --- a/actionview/test/template/sanitize_helper_test.rb +++ b/actionview/test/template/sanitize_helper_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" # The exhaustive tests are in test/controller/html/sanitizer_test.rb. # This tests that the helpers hook up correctly to the sanitizer classes. @@ -13,7 +13,7 @@ class SanitizeHelperTest < ActionView::TestCase end def test_sanitize_form - assert_equal '', sanitize("<form action=\"/foo/bar\" method=\"post\"><input></form>") + assert_equal "", sanitize("<form action=\"/foo/bar\" method=\"post\"><input></form>") end def test_should_sanitize_illegal_style_properties diff --git a/actionview/test/template/streaming_render_test.rb b/actionview/test/template/streaming_render_test.rb index d06ba4ceb0..6ce66a1275 100644 --- a/actionview/test/template/streaming_render_test.rb +++ b/actionview/test/template/streaming_render_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class TestController < ActionController::Base end @@ -6,7 +6,7 @@ end class FiberedTest < ActiveSupport::TestCase def setup view_paths = ActionController::Base.view_paths - @assigns = { :secret => 'in the sauce', :name => nil } + @assigns = { secret: "in the sauce", name: nil } @view = ActionView::Base.new(view_paths, @assigns) @controller_view = TestController.new.view_context end @@ -26,7 +26,7 @@ class FiberedTest < ActiveSupport::TestCase def test_streaming_works content = [] - body = render_body(:template => "test/hello_world", :layout => "layouts/yield") + body = render_body(template: "test/hello_world", layout: "layouts/yield") body.each do |piece| content << piece @@ -40,68 +40,68 @@ class FiberedTest < ActiveSupport::TestCase end def test_render_file - assert_equal "Hello world!", buffered_render(:file => "test/hello_world") + assert_equal "Hello world!", buffered_render(file: "test/hello_world") end def test_render_file_with_locals - locals = { :secret => 'in the sauce' } - assert_equal "The secret is in the sauce\n", buffered_render(:file => "test/render_file_with_locals", :locals => locals) + locals = { secret: "in the sauce" } + assert_equal "The secret is in the sauce\n", buffered_render(file: "test/render_file_with_locals", locals: locals) end def test_render_partial - assert_equal "only partial", buffered_render(:partial => "test/partial_only") + assert_equal "only partial", buffered_render(partial: "test/partial_only") end def test_render_inline - assert_equal "Hello, World!", buffered_render(:inline => "Hello, World!") + assert_equal "Hello, World!", buffered_render(inline: "Hello, World!") end def test_render_without_layout - assert_equal "Hello world!", buffered_render(:template => "test/hello_world") + assert_equal "Hello world!", buffered_render(template: "test/hello_world") end def test_render_with_layout assert_equal %(<title></title>\nHello world!\n), - buffered_render(:template => "test/hello_world", :layout => "layouts/yield") + buffered_render(template: "test/hello_world", layout: "layouts/yield") end def test_render_with_layout_which_has_render_inline assert_equal %(welcome\nHello world!\n), - buffered_render(:template => "test/hello_world", :layout => "layouts/yield_with_render_inline_inside") + buffered_render(template: "test/hello_world", layout: "layouts/yield_with_render_inline_inside") end def test_render_with_layout_which_renders_another_partial assert_equal %(partial html\nHello world!\n), - buffered_render(:template => "test/hello_world", :layout => "layouts/yield_with_render_partial_inside") + buffered_render(template: "test/hello_world", layout: "layouts/yield_with_render_partial_inside") end def test_render_with_nested_layout assert_equal %(<title>title</title>\n\n<div id="column">column</div>\n<div id="content">content</div>\n), - buffered_render(:template => "test/nested_layout", :layout => "layouts/yield") + buffered_render(template: "test/nested_layout", layout: "layouts/yield") end def test_render_with_file_in_layout assert_equal %(\n<title>title</title>\n\n), - buffered_render(:template => "test/layout_render_file") + buffered_render(template: "test/layout_render_file") end def test_render_with_handler_without_streaming_support - assert_match "<p>This is grand!</p>", buffered_render(:template => "test/hello") + assert_match "<p>This is grand!</p>", buffered_render(template: "test/hello") end def test_render_with_streaming_multiple_yields_provide_and_content_for assert_equal "Yes, \nthis works\n like a charm.", - buffered_render(:template => "test/streaming", :layout => "layouts/streaming") + buffered_render(template: "test/streaming", layout: "layouts/streaming") end def test_render_with_streaming_with_fake_yields_and_streaming_buster assert_equal "This won't look\n good.", - buffered_render(:template => "test/streaming_buster", :layout => "layouts/streaming") + buffered_render(template: "test/streaming_buster", layout: "layouts/streaming") end def test_render_with_nested_streaming_multiple_yields_provide_and_content_for assert_equal "?Yes, \n\nthis works\n\n? like a charm.", - buffered_render(:template => "test/nested_streaming", :layout => "layouts/streaming") + buffered_render(template: "test/nested_streaming", layout: "layouts/streaming") end def test_render_with_streaming_and_capture diff --git a/actionview/test/template/tag_helper_test.rb b/actionview/test/template/tag_helper_test.rb index 6f7a78ccef..f1e5946e14 100644 --- a/actionview/test/template/tag_helper_test.rb +++ b/actionview/test/template/tag_helper_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class TagHelperTest < ActionView::TestCase include RenderERBUtils @@ -7,10 +7,28 @@ class TagHelperTest < ActionView::TestCase def test_tag assert_equal "<br />", tag("br") - assert_equal "<br clear=\"left\" />", tag(:br, :clear => "left") + assert_equal "<br clear=\"left\" />", tag(:br, clear: "left") assert_equal "<br>", tag("br", nil, true) end + def test_tag_builder + assert_equal "<span></span>", tag.span + assert_equal "<span class=\"bookmark\"></span>", tag.span(class: "bookmark") + end + + def test_tag_builder_void_tag + assert_equal "<br>", tag.br + assert_equal "<br class=\"some_class\">", tag.br(class: "some_class") + end + + def test_tag_builder_void_tag_with_forced_content + assert_equal "<br>some content</br>", tag.br("some content") + end + + def test_tag_builder_is_singleton + assert_equal tag, tag + end + def test_tag_options str = tag("p", "class" => "show", :class => "elsewhere") assert_match(/class="show"/, str) @@ -18,31 +36,76 @@ class TagHelperTest < ActionView::TestCase end def test_tag_options_rejects_nil_option - assert_equal "<p />", tag("p", :ignored => nil) + assert_equal "<p />", tag("p", ignored: nil) + end + + def test_tag_builder_options_rejects_nil_option + assert_equal "<p></p>", tag.p(ignored: nil) end def test_tag_options_accepts_false_option - assert_equal "<p value=\"false\" />", tag("p", :value => false) + assert_equal "<p value=\"false\" />", tag("p", value: false) + end + + def test_tag_builder_options_accepts_false_option + assert_equal "<p value=\"false\"></p>", tag.p(value: false) end def test_tag_options_accepts_blank_option - assert_equal "<p included=\"\" />", tag("p", :included => '') + assert_equal "<p included=\"\" />", tag("p", included: "") + end + + def test_tag_builder_options_accepts_blank_option + assert_equal "<p included=\"\"></p>", tag.p(included: "") + end + + def test_tag_options_accepts_symbol_option_when_not_escaping + assert_equal "<p value=\"symbol\" />", tag("p", { value: :symbol }, false, false) + end + + def test_tag_options_accepts_integer_option_when_not_escaping + assert_equal "<p value=\"42\" />", tag("p", { value: 42 }, false, false) end def test_tag_options_converts_boolean_option assert_dom_equal '<p disabled="disabled" itemscope="itemscope" multiple="multiple" readonly="readonly" allowfullscreen="allowfullscreen" seamless="seamless" typemustmatch="typemustmatch" sortable="sortable" default="default" inert="inert" truespeed="truespeed" />', - tag("p", :disabled => true, :itemscope => true, :multiple => true, :readonly => true, :allowfullscreen => true, :seamless => true, :typemustmatch => true, :sortable => true, :default => true, :inert => true, :truespeed => true) + tag("p", disabled: true, itemscope: true, multiple: true, readonly: true, allowfullscreen: true, seamless: true, typemustmatch: true, sortable: true, default: true, inert: true, truespeed: true) + end + + def test_tag_builder_options_converts_boolean_option + assert_dom_equal '<p disabled="disabled" itemscope="itemscope" multiple="multiple" readonly="readonly" allowfullscreen="allowfullscreen" seamless="seamless" typemustmatch="typemustmatch" sortable="sortable" default="default" inert="inert" truespeed="truespeed" />', + tag.p(disabled: true, itemscope: true, multiple: true, readonly: true, allowfullscreen: true, seamless: true, typemustmatch: true, sortable: true, default: true, inert: true, truespeed: true) end def test_content_tag assert_equal "<a href=\"create\">Create</a>", content_tag("a", "Create", "href" => "create") assert content_tag("a", "Create", "href" => "create").html_safe? assert_equal content_tag("a", "Create", "href" => "create"), - content_tag("a", "Create", :href => "create") + content_tag("a", "Create", href: "create") assert_equal "<p><script>evil_js</script></p>", - content_tag(:p, '<script>evil_js</script>') + content_tag(:p, "<script>evil_js</script>") assert_equal "<p><script>evil_js</script></p>", - content_tag(:p, '<script>evil_js</script>', nil, false) + content_tag(:p, "<script>evil_js</script>", nil, false) + end + + def test_tag_builder_with_content + assert_equal "<div id=\"post_1\">Content</div>", tag.div("Content", id: "post_1") + assert tag.div("Content", id: "post_1").html_safe? + assert_equal tag.div("Content", id: "post_1"), + tag.div("Content", "id": "post_1") + assert_equal "<p><script>evil_js</script></p>", + tag.p("<script>evil_js</script>") + assert_equal "<p><script>evil_js</script></p>", + tag.p("<script>evil_js</script>", escape_attributes: false) + end + + def test_tag_builder_nested + assert_equal "<div>content</div>", + tag.div { "content" } + assert_equal "<div id=\"header\"><span>hello</span></div>", + tag.div(id: "header") { |tag| tag.span "hello" } + assert_equal "<div id=\"header\"><div class=\"world\"><span>hello</span></div></div>", + tag.div(id: "header") { |tag| tag.div(class: "world") { tag.span "hello" } } end def test_content_tag_with_block_in_erb @@ -50,72 +113,138 @@ class TagHelperTest < ActionView::TestCase assert_dom_equal "<div>Hello world!</div>", buffer end + def test_tag_builder_with_block_in_erb + buffer = render_erb("<%= tag.div do %>Hello world!<% end %>") + assert_dom_equal "<div>Hello world!</div>", buffer + end + def test_content_tag_with_block_in_erb_containing_non_displayed_erb buffer = render_erb("<%= content_tag(:p) do %><% 1 %><% end %>") assert_dom_equal "<p></p>", buffer end + def test_tag_builder_with_block_in_erb_containing_non_displayed_erb + buffer = render_erb("<%= tag.p do %><% 1 %><% end %>") + assert_dom_equal "<p></p>", buffer + end + def test_content_tag_with_block_and_options_in_erb buffer = render_erb("<%= content_tag(:div, :class => 'green') do %>Hello world!<% end %>") assert_dom_equal %(<div class="green">Hello world!</div>), buffer end + def test_tag_builder_with_block_and_options_in_erb + buffer = render_erb("<%= tag.div(class: 'green') do %>Hello world!<% end %>") + assert_dom_equal %(<div class="green">Hello world!</div>), buffer + end + def test_content_tag_with_block_and_options_out_of_erb - assert_dom_equal %(<div class="green">Hello world!</div>), content_tag(:div, :class => "green") { "Hello world!" } + assert_dom_equal %(<div class="green">Hello world!</div>), content_tag(:div, class: "green") { "Hello world!" } + end + + def test_tag_builder_with_block_and_options_out_of_erb + assert_dom_equal %(<div class="green">Hello world!</div>), tag.div(class: "green") { "Hello world!" } end def test_content_tag_with_block_and_options_outside_out_of_erb - assert_equal content_tag("a", "Create", :href => "create"), + assert_equal content_tag("a", "Create", href: "create"), content_tag("a", "href" => "create") { "Create" } end + def test_tag_builder_with_block_and_options_outside_out_of_erb + assert_equal tag.a("Create", href: "create"), + tag.a("href": "create") { "Create" } + end + def test_content_tag_with_block_and_non_string_outside_out_of_erb assert_equal content_tag("p"), content_tag("p") { 3.times { "do_something" } } end + def test_tag_builder_with_block_and_non_string_outside_out_of_erb + assert_equal tag.p, + tag.p { 3.times { "do_something" } } + end + def test_content_tag_nested_in_content_tag_out_of_erb assert_equal content_tag("p", content_tag("b", "Hello")), content_tag("p") { content_tag("b", "Hello") }, output_buffer + assert_equal tag.p(tag.b("Hello")), + tag.p { tag.b("Hello") }, + output_buffer end def test_content_tag_nested_in_content_tag_in_erb assert_equal "<p>\n <b>Hello</b>\n</p>", view.render("test/content_tag_nested_in_content_tag") + assert_equal "<p>\n <b>Hello</b>\n</p>", view.render("test/builder_tag_nested_in_content_tag") end def test_content_tag_with_escaped_array_class - str = content_tag('p', "limelight", :class => ["song", "play>"]) + str = content_tag("p", "limelight", class: ["song", "play>"]) assert_equal "<p class=\"song play>\">limelight</p>", str - str = content_tag('p', "limelight", :class => ["song", "play"]) + str = content_tag("p", "limelight", class: ["song", "play"]) assert_equal "<p class=\"song play\">limelight</p>", str - str = content_tag('p', "limelight", :class => ["song", ["play"]]) + str = content_tag("p", "limelight", class: ["song", ["play"]]) + assert_equal "<p class=\"song play\">limelight</p>", str + end + + def test_tag_builder_with_escaped_array_class + str = tag.p "limelight", class: ["song", "play>"] + assert_equal "<p class=\"song play>\">limelight</p>", str + + str = tag.p "limelight", class: ["song", "play"] + assert_equal "<p class=\"song play\">limelight</p>", str + + str = tag.p "limelight", class: ["song", ["play"]] assert_equal "<p class=\"song play\">limelight</p>", str end def test_content_tag_with_unescaped_array_class - str = content_tag('p', "limelight", {:class => ["song", "play>"]}, false) + str = content_tag("p", "limelight", { class: ["song", "play>"] }, false) assert_equal "<p class=\"song play>\">limelight</p>", str - str = content_tag('p', "limelight", {:class => ["song", ["play>"]]}, false) + str = content_tag("p", "limelight", { class: ["song", ["play>"]] }, false) + assert_equal "<p class=\"song play>\">limelight</p>", str + end + + def test_tag_builder_with_unescaped_array_class + str = tag.p "limelight", class: ["song", "play>"], escape_attributes: false + assert_equal "<p class=\"song play>\">limelight</p>", str + + str = tag.p "limelight", class: ["song", ["play>"]], escape_attributes: false assert_equal "<p class=\"song play>\">limelight</p>", str end def test_content_tag_with_empty_array_class - str = content_tag('p', 'limelight', {:class => []}) + str = content_tag("p", "limelight", class: []) assert_equal '<p class="">limelight</p>', str end + def test_tag_builder_with_empty_array_class + assert_equal '<p class="">limelight</p>', tag.p("limelight", class: []) + end + def test_content_tag_with_unescaped_empty_array_class - str = content_tag('p', 'limelight', {:class => []}, false) + str = content_tag("p", "limelight", { class: [] }, false) + assert_equal '<p class="">limelight</p>', str + end + + def test_tag_builder_with_unescaped_empty_array_class + str = tag.p "limelight", class: [], escape_attributes: false assert_equal '<p class="">limelight</p>', str end def test_content_tag_with_data_attributes assert_dom_equal '<p data-number="1" data-string="hello" data-string-with-quotes="double"quote"party"">limelight</p>', - content_tag('p', "limelight", data: { number: 1, string: 'hello', string_with_quotes: 'double"quote"party"' }) + content_tag("p", "limelight", data: { number: 1, string: "hello", string_with_quotes: 'double"quote"party"' }) + end + + def test_tag_builder_with_data_attributes + assert_dom_equal '<p data-number="1" data-string="hello" data-string-with-quotes="double"quote"party"">limelight</p>', + tag.p("limelight", data: { number: 1, string: "hello", string_with_quotes: 'double"quote"party"' }) end def test_cdata_section @@ -132,45 +261,95 @@ class TagHelperTest < ActionView::TestCase end def test_escape_once - assert_equal '1 < 2 & 3', escape_once('1 < 2 & 3') + assert_equal "1 < 2 & 3", escape_once("1 < 2 & 3") assert_equal " ' ' λ λ " ' < > ", escape_once(" ' ' λ λ \" ' < > ") end def test_tag_honors_html_safe_for_param_values - ['1&2', '1 < 2', '“test“'].each do |escaped| - assert_equal %(<a href="#{escaped}" />), tag('a', :href => escaped.html_safe) + ["1&2", "1 < 2", "“test“"].each do |escaped| + assert_equal %(<a href="#{escaped}" />), tag("a", href: escaped.html_safe) + assert_equal %(<a href="#{escaped}"></a>), tag.a(href: escaped.html_safe) end end def test_tag_honors_html_safe_with_escaped_array_class - str = tag('p', :class => ['song>', raw('play>')]) - assert_equal '<p class="song> play>" />', str + assert_equal '<p class="song> play>" />', tag("p", class: ["song>", raw("play>")]) + assert_equal '<p class="song> play>" />', tag("p", class: [raw("song>"), "play>"]) + end + + def test_tag_builder_honors_html_safe_with_escaped_array_class + assert_equal '<p class="song> play>"></p>', tag.p(class: ["song>", raw("play>")]) + assert_equal '<p class="song> play>"></p>', tag.p(class: [raw("song>"), "play>"]) + end + + def test_tag_does_not_honor_html_safe_double_quotes_as_attributes + assert_dom_equal '<p title=""">content</p>', + content_tag("p", "content", title: '"'.html_safe) + end - str = tag('p', :class => [raw('song>'), 'play>']) - assert_equal '<p class="song> play>" />', str + def test_data_tag_does_not_honor_html_safe_double_quotes_as_attributes + assert_dom_equal '<p data-title=""">content</p>', + content_tag("p", "content", data: { title: '"'.html_safe }) end def test_skip_invalid_escaped_attributes - ['&1;', 'dfa3;', '& #123;'].each do |escaped| - assert_equal %(<a href="#{escaped.gsub(/&/, '&')}" />), tag('a', :href => escaped) + ["&1;", "dfa3;", "& #123;"].each do |escaped| + assert_equal %(<a href="#{escaped.gsub(/&/, '&')}" />), tag("a", href: escaped) + assert_equal %(<a href="#{escaped.gsub(/&/, '&')}"></a>), tag.a(href: escaped) end end def test_disable_escaping - assert_equal '<a href="&" />', tag('a', { :href => '&' }, false, false) + assert_equal '<a href="&" />', tag("a", { href: "&" }, false, false) + end + + def test_tag_builder_disable_escaping + assert_equal '<a href="&"></a>', tag.a(href: "&", escape_attributes: false) + assert_equal '<a href="&">cnt</a>', tag.a(href: "&" , escape_attributes: false) { "cnt" } + assert_equal '<br data-hidden="&">', tag.br("data-hidden": "&" , escape_attributes: false) + assert_equal '<a href="&">content</a>', tag.a("content", href: "&", escape_attributes: false) + assert_equal '<a href="&">content</a>', tag.a(href: "&", escape_attributes: false) { "content" } end def test_data_attributes - ['data', :data].each { |data| + ["data", :data].each { |data| assert_dom_equal '<a data-a-float="3.14" data-a-big-decimal="-123.456" data-a-number="1" data-array="[1,2,3]" data-hash="{"key":"value"}" data-string-with-quotes="double"quote"party"" data-string="hello" data-symbol="foo" />', - tag('a', { data => { a_float: 3.14, a_big_decimal: BigDecimal.new("-123.456"), a_number: 1, string: 'hello', symbol: :foo, array: [1, 2, 3], hash: { key: 'value'}, string_with_quotes: 'double"quote"party"' } }) + tag("a", data => { a_float: 3.14, a_big_decimal: BigDecimal.new("-123.456"), a_number: 1, string: "hello", symbol: :foo, array: [1, 2, 3], hash: { key: "value" }, string_with_quotes: 'double"quote"party"' }) + assert_dom_equal '<a data-a-float="3.14" data-a-big-decimal="-123.456" data-a-number="1" data-array="[1,2,3]" data-hash="{"key":"value"}" data-string-with-quotes="double"quote"party"" data-string="hello" data-symbol="foo" />', + tag.a(data: { a_float: 3.14, a_big_decimal: BigDecimal.new("-123.456"), a_number: 1, string: "hello", symbol: :foo, array: [1, 2, 3], hash: { key: "value" }, string_with_quotes: 'double"quote"party"' }) } end def test_aria_attributes - ['aria', :aria].each { |aria| + ["aria", :aria].each { |aria| + assert_dom_equal '<a aria-a-float="3.14" aria-a-big-decimal="-123.456" aria-a-number="1" aria-array="[1,2,3]" aria-hash="{"key":"value"}" aria-string-with-quotes="double"quote"party"" aria-string="hello" aria-symbol="foo" />', + tag("a", aria => { a_float: 3.14, a_big_decimal: BigDecimal.new("-123.456"), a_number: 1, string: "hello", symbol: :foo, array: [1, 2, 3], hash: { key: "value" }, string_with_quotes: 'double"quote"party"' }) assert_dom_equal '<a aria-a-float="3.14" aria-a-big-decimal="-123.456" aria-a-number="1" aria-array="[1,2,3]" aria-hash="{"key":"value"}" aria-string-with-quotes="double"quote"party"" aria-string="hello" aria-symbol="foo" />', - tag('a', { aria => { a_float: 3.14, a_big_decimal: BigDecimal.new("-123.456"), a_number: 1, string: 'hello', symbol: :foo, array: [1, 2, 3], hash: { key: 'value'}, string_with_quotes: 'double"quote"party"' } }) + tag.a(aria: { a_float: 3.14, a_big_decimal: BigDecimal.new("-123.456"), a_number: 1, string: "hello", symbol: :foo, array: [1, 2, 3], hash: { key: "value" }, string_with_quotes: 'double"quote"party"' }) } end + + def test_link_to_data_nil_equal + div_type1 = content_tag(:div, "test", "data-tooltip" => nil) + div_type2 = content_tag(:div, "test", data: { tooltip: nil }) + assert_dom_equal div_type1, div_type2 + end + + def test_tag_builder_link_to_data_nil_equal + div_type1 = tag.div "test", 'data-tooltip': nil + div_type2 = tag.div "test", data: { tooltip: nil } + assert_dom_equal div_type1, div_type2 + end + + def test_tag_builder_allow_call_via_method_object + assert_equal "<foo></foo>", tag.method(:foo).call + end + + def test_tag_builder_dasherize_names + assert_equal "<img-slider></img-slider>", tag.img_slider + end + + def test_respond_to + assert_respond_to tag, :any_tag + end end diff --git a/actionview/test/template/template_test.rb b/actionview/test/template/template_test.rb index 921011b073..9d31a98703 100644 --- a/actionview/test/template/template_test.rb +++ b/actionview/test/template/template_test.rb @@ -35,7 +35,7 @@ class TestERBTemplate < ActiveSupport::TestCase "<%= @virtual_path %>", "partial", ERBHandler, - :virtual_path => "partial" + virtual_path: "partial" ) end @@ -53,7 +53,7 @@ class TestERBTemplate < ActiveSupport::TestCase end def new_template(body = "<%= hello %>", details = { format: :html }) - ActionView::Template.new(body, "hello template", details.fetch(:handler) { ERBHandler }, {:virtual_path => "hello"}.merge!(details)) + ActionView::Template.new(body, "hello template", details.fetch(:handler) { ERBHandler }, { virtual_path: "hello" }.merge!(details)) end def render(locals = {}) @@ -80,7 +80,7 @@ class TestERBTemplate < ActiveSupport::TestCase end def test_raw_template - @template = new_template("<%= hello %>", :handler => ActionView::Template::Handlers::Raw.new) + @template = new_template("<%= hello %>", handler: ActionView::Template::Handlers::Raw.new) assert_equal "<%= hello %>", render end @@ -91,7 +91,7 @@ class TestERBTemplate < ActiveSupport::TestCase end def test_template_does_not_lose_its_source_after_rendering_if_it_does_not_have_a_virtual_path - @template = new_template("Hello", :virtual_path => nil) + @template = new_template("Hello", virtual_path: nil) render assert_equal "Hello", @template.source end @@ -99,7 +99,7 @@ class TestERBTemplate < ActiveSupport::TestCase def test_locals @template = new_template("<%= my_local %>") @template.locals = [:my_local] - assert_equal "I am a local", render(:my_local => "I am a local") + assert_equal "I am a local", render(my_local: "I am a local") end def test_restores_buffer @@ -116,23 +116,23 @@ class TestERBTemplate < ActiveSupport::TestCase end def test_refresh_with_templates - @template = new_template("Hello", :virtual_path => "test/foo/bar") + @template = new_template("Hello", virtual_path: "test/foo/bar") @template.locals = [:key] - assert_called_with(@context.lookup_context, :find_template,["bar", %w(test/foo), false, [:key]], returns: "template") do + assert_called_with(@context.lookup_context, :find_template, ["bar", %w(test/foo), false, [:key]], returns: "template") do assert_equal "template", @template.refresh(@context) end end def test_refresh_with_partials - @template = new_template("Hello", :virtual_path => "test/_foo") + @template = new_template("Hello", virtual_path: "test/_foo") @template.locals = [:key] - assert_called_with(@context.lookup_context, :find_template,[ "foo", %w(test), true, [:key]], returns: "partial") do + assert_called_with(@context.lookup_context, :find_template, ["foo", %w(test), true, [:key]], returns: "partial") do assert_equal "partial", @template.refresh(@context) end end def test_refresh_raises_an_error_without_virtual_path - @template = new_template("Hello", :virtual_path => nil) + @template = new_template("Hello", virtual_path: nil) assert_raise RuntimeError do @template.refresh(@context) end @@ -171,14 +171,14 @@ class TestERBTemplate < ActiveSupport::TestCase # inside Rails. def test_lying_with_magic_comment assert_raises(ActionView::Template::Error) do - @template = new_template("# encoding: UTF-8\nhello \xFCmlat", :virtual_path => nil) + @template = new_template("# encoding: UTF-8\nhello \xFCmlat", virtual_path: nil) render end end def test_encoding_can_be_specified_with_magic_comment_in_erb with_external_encoding Encoding::UTF_8 do - @template = new_template("<%# encoding: ISO-8859-1 %>hello \xFCmlat", :virtual_path => nil) + @template = new_template("<%# encoding: ISO-8859-1 %>hello \xFCmlat", virtual_path: nil) assert_equal Encoding::UTF_8, render.encoding assert_equal "hello \u{fc}mlat", render end @@ -186,42 +186,12 @@ class TestERBTemplate < ActiveSupport::TestCase def test_error_when_template_isnt_valid_utf8 e = assert_raises ActionView::Template::Error do - @template = new_template("hello \xFCmlat", :virtual_path => nil) + @template = new_template("hello \xFCmlat", virtual_path: nil) render end - assert_match(/\xFC/, e.message) - end - - def test_not_eligible_for_collection_caching_without_cache_call - [ - "<%= 'Hello' %>", - "<% cache_customer = 42 %>", - "<% cache customer.name do %><% end %>", - "<% my_cache customer do %><% end %>" - ].each do |body| - template = new_template(body, virtual_path: "test/foo/_customer") - assert_not template.eligible_for_collection_caching?, "Template #{body.inspect} should not be eligible for collection caching" - end - end - - def test_eligible_for_collection_caching_with_cache_call_or_explicit - [ - "<% cache customer do %><% end %>", - "<% cache(customer) do %><% end %>", - "<% cache( customer) do %><% end %>", - "<% cache( customer ) do %><% end %>", - "<%cache customer do %><% end %>", - "<% cache customer do %><% end %>", - " <% cache customer do %>\n<% end %>\n", - "<%# comment %><% cache customer do %><% end %>", - "<%# comment %>\n<% cache customer do %><% end %>", - "<%# comment\n line 2\n line 3 %>\n<% cache customer do %><% end %>", - "<%# comment 1 %>\n<%# comment 2 %>\n<% cache customer do %><% end %>", - "<%# comment 1 %>\n<%# Template Collection: customer %>\n<% my_cache customer do %><% end %>" - ].each do |body| - template = new_template(body, virtual_path: "test/foo/_customer") - assert template.eligible_for_collection_caching?, "Template #{body.inspect} should be eligible for collection caching" - end + # Hack: We write the regexp this way because the parser of RuboCop + # errs with /\xFC/. + assert_match(Regexp.new("\xFC"), e.message) end def with_external_encoding(encoding) diff --git a/actionview/test/template/test_case_test.rb b/actionview/test/template/test_case_test.rb index d69d5819b6..41225000f0 100644 --- a/actionview/test/template/test_case_test.rb +++ b/actionview/test/template/test_case_test.rb @@ -1,20 +1,19 @@ -require 'abstract_unit' -require 'rails/engine' +require "abstract_unit" +require "rails/engine" module ActionView - module ATestHelper end module AnotherTestHelper def from_another_helper - 'Howdy!' + "Howdy!" end end module ASharedTestHelper def from_shared_helper - 'Holla!' + "Holla!" end end @@ -26,8 +25,8 @@ module ActionView def self.included(test_case) test_case.class_eval do test "helpers defined on ActionView::TestCase are available" do - assert test_case.ancestors.include?(ASharedTestHelper) - assert_equal 'Holla!', from_shared_helper + assert_includes test_case.ancestors, ASharedTestHelper + assert_equal "Holla!", from_shared_helper end end end @@ -55,18 +54,18 @@ module ActionView end test "works without testing a helper module" do - assert_equal 'Eloy', render('developers/developer', :developer => DeveloperStruct.new('Eloy')) + assert_equal "Eloy", render("developers/developer", developer: DeveloperStruct.new("Eloy")) end test "can render a layout with block" do assert_equal "Before (ChrisCruft)\n!\nAfter", - render(:layout => "test/layout_for_partial", :locals => {:name => "ChrisCruft"}) {"!"} + render(layout: "test/layout_for_partial", locals: { name: "ChrisCruft" }) { "!" } end helper AnotherTestHelper test "additional helper classes can be specified as in a controller" do - assert test_case.ancestors.include?(AnotherTestHelper) - assert_equal 'Howdy!', from_another_helper + assert_includes test_case.ancestors, AnotherTestHelper + assert_equal "Howdy!", from_another_helper end test "determine_default_helper_class returns nil if the test name constant resolves to a class" do @@ -86,7 +85,7 @@ module ActionView end test "uses controller lookup context" do - assert_equal self.lookup_context, @controller.lookup_context + assert_equal lookup_context, @controller.lookup_context end end @@ -97,44 +96,44 @@ module ActionView tests ATestHelper test "tests the specified helper module" do assert_equal ATestHelper, test_case.helper_class - assert test_case.ancestors.include?(ATestHelper) + assert_includes test_case.ancestors, ATestHelper end helper AnotherTestHelper test "additional helper classes can be specified as in a controller" do - assert test_case.ancestors.include?(AnotherTestHelper) - assert_equal 'Howdy!', from_another_helper + assert_includes test_case.ancestors, AnotherTestHelper + assert_equal "Howdy!", from_another_helper test_case.helper_class.module_eval do def render_from_helper from_another_helper end end - assert_equal 'Howdy!', render(:partial => 'test/from_helper') + assert_equal "Howdy!", render(partial: "test/from_helper") end end class HelperInclusionTest < ActionView::TestCase module RenderHelper def render_from_helper - render :partial => 'customer', :collection => @customers + render partial: "customer", collection: @customers end end helper RenderHelper test "helper class that is being tested is always included in view instance" do - @controller.controller_path = 'test' + @controller.controller_path = "test" - @customers = [DeveloperStruct.new('Eloy'), DeveloperStruct.new('Manfred')] - assert_match(/Hello: EloyHello: Manfred/, render(:partial => 'test/from_helper')) + @customers = [DeveloperStruct.new("Eloy"), DeveloperStruct.new("Manfred")] + assert_match(/Hello: EloyHello: Manfred/, render(partial: "test/from_helper")) end end class ControllerHelperMethod < ActionView::TestCase module SomeHelper def some_method - render :partial => 'test/from_helper' + render partial: "test/from_helper" end end @@ -148,21 +147,21 @@ module ActionView EOF @controller.class.helper_method :render_from_helper - assert_equal 'controller_helper_method', some_method + assert_equal "controller_helper_method", some_method end end class ViewAssignsTest < ActionView::TestCase test "view_assigns returns a Hash of user defined ivars" do - @a = 'b' - @c = 'd' - assert_equal({:a => 'b', :c => 'd'}, view_assigns) + @a = "b" + @c = "d" + assert_equal({ a: "b", c: "d" }, view_assigns) end test "view_assigns excludes internal ivars" do INTERNAL_IVARS.each do |ivar| assert defined?(ivar), "expected #{ivar} to be defined" - assert !view_assigns.keys.include?(ivar.to_s.tr('@', '').to_sym), "expected #{ivar} to be excluded from view_assigns" + assert_not_includes view_assigns.keys, ivar.to_s.tr("@", "").to_sym, "expected #{ivar} to be excluded from view_assigns" end end end @@ -174,10 +173,10 @@ module ActionView end end) test "is able to make methods available to the view" do - assert_equal 'Word!', render(:partial => 'test/from_helper') + assert_equal "Word!", render(partial: "test/from_helper") end - def from_test_case; 'Word!'; end + def from_test_case; "Word!"; end helper_method :from_test_case end @@ -193,7 +192,6 @@ module ActionView test "protect_from_forgery? in any helpers returns false" do assert !view.help_me end - end class ATestHelperTest < ActionView::TestCase @@ -202,30 +200,30 @@ module ActionView test "inflects the name of the helper module to test from the test case class" do assert_equal ATestHelper, test_case.helper_class - assert test_case.ancestors.include?(ATestHelper) + assert_includes test_case.ancestors, ATestHelper end test "a configured test controller is available" do assert_kind_of ActionController::Base, controller - assert_equal '', controller.controller_path + assert_equal "", controller.controller_path end test "no additional helpers should shared across test cases" do - assert !test_case.ancestors.include?(AnotherTestHelper) + assert_not_includes test_case.ancestors, AnotherTestHelper assert_raise(NoMethodError) { send :from_another_helper } end test "is able to use routes" do - controller.request.assign_parameters(@routes, 'foo', 'index', {}, '/foo', []) - assert_equal '/foo', url_for - assert_equal '/bar', url_for(:controller => 'bar') + controller.request.assign_parameters(@routes, "foo", "index", {}, "/foo", []) + assert_equal "/foo", url_for + assert_equal "/bar", url_for(controller: "bar") end test "is able to use named routes" do with_routing do |set| set.draw { resources :contents } - assert_equal 'http://test.host/contents/new', new_content_url - assert_equal 'http://test.host/contents/1', content_url(:id => 1) + assert_equal "http://test.host/contents/new", new_content_url + assert_equal "http://test.host/contents/1", content_url(id: 1) end end @@ -236,7 +234,7 @@ module ActionView @routes ||= ActionDispatch::Routing::RouteSet.new end - routes.draw { get "bar", :to => lambda {} } + routes.draw { get "bar", to: lambda {} } def self.call(*) end @@ -244,7 +242,7 @@ module ActionView set.draw { mount app => "/foo", :as => "foo_app" } - assert_equal '/foo/bar', foo_app.bar_path + assert_equal "/foo/bar", foo_app.bar_path end end @@ -257,21 +255,21 @@ module ActionView end end - assert_equal 'http://test.host/contents/new', render(:partial => 'test/from_helper') + assert_equal "http://test.host/contents/new", render(partial: "test/from_helper") end end test "is able to render partials with local variables" do - assert_equal 'Eloy', render('developers/developer', :developer => DeveloperStruct.new('Eloy')) - assert_equal 'Eloy', render(:partial => 'developers/developer', - :locals => { :developer => DeveloperStruct.new('Eloy') }) + assert_equal "Eloy", render("developers/developer", developer: DeveloperStruct.new("Eloy")) + assert_equal "Eloy", render(partial: "developers/developer", + locals: { developer: DeveloperStruct.new("Eloy") }) end test "is able to render partials from templates and also use instance variables" do @controller.controller_path = "test" - @customers = [DeveloperStruct.new('Eloy'), DeveloperStruct.new('Manfred')] - assert_match(/Hello: EloyHello: Manfred/, render(:file => 'test/list')) + @customers = [DeveloperStruct.new("Eloy"), DeveloperStruct.new("Manfred")] + assert_match(/Hello: EloyHello: Manfred/, render(file: "test/list")) end test "is able to render partials from templates and also use instance variables after view has been referenced" do @@ -279,37 +277,36 @@ module ActionView view - @customers = [DeveloperStruct.new('Eloy'), DeveloperStruct.new('Manfred')] - assert_match(/Hello: EloyHello: Manfred/, render(:file => 'test/list')) + @customers = [DeveloperStruct.new("Eloy"), DeveloperStruct.new("Manfred")] + assert_match(/Hello: EloyHello: Manfred/, render(file: "test/list")) end - end class AssertionsTest < ActionView::TestCase def render_from_helper - form_tag('/foo') do - safe_concat render(:text => '<ul><li>foo</li></ul>') + form_tag("/foo") do + safe_concat render(plain: "<ul><li>foo</li></ul>") end end helper_method :render_from_helper test "uses the output_buffer for assert_select" do - render(:partial => 'test/from_helper') + render(partial: "test/from_helper") - assert_select 'form' do - assert_select 'li', :text => 'foo' + assert_select "form" do + assert_select "li", text: "foo" end end test "do not memoize the document_root_element in view tests" do - concat form_tag('/foo') + concat form_tag("/foo") - assert_select 'form' + assert_select "form" - concat content_tag(:b, 'Strong', class: 'foo') + concat content_tag(:b, "Strong", class: "foo") - assert_select 'form' - assert_select 'b.foo' + assert_select "form" + assert_select "b.foo" end end diff --git a/actionview/test/template/test_test.rb b/actionview/test/template/test_test.rb index e1ff639979..52cac77bc5 100644 --- a/actionview/test/template/test_test.rb +++ b/actionview/test/template/test_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module PeopleHelper def title(text) @@ -41,7 +41,7 @@ class PeopleHelperTest < ActionView::TestCase extend ActiveModel::Naming def to_model; self; end def persisted?; true; end - def self.name; 'Minitest::Mock'; end + def self.name; "Minitest::Mock"; end }.new "David" the_model = nil @@ -60,7 +60,7 @@ class PeopleHelperTest < ActionView::TestCase def with_test_route_set with_routing do |set| set.draw do - get 'people', :to => 'people#index', :as => :people + get "people", to: "people#index", as: :people end yield end @@ -84,7 +84,7 @@ class CrazySymbolHelperTest < ActionView::TestCase end class CrazyStringHelperTest < ActionView::TestCase - tests 'people' + tests "people" def test_set_helper_class_using_string assert_equal PeopleHelper, self.class.helper_class diff --git a/actionview/test/template/testing/fixture_resolver_test.rb b/actionview/test/template/testing/fixture_resolver_test.rb index d6cfa997cd..effe453fc0 100644 --- a/actionview/test/template/testing/fixture_resolver_test.rb +++ b/actionview/test/template/testing/fixture_resolver_test.rb @@ -1,15 +1,15 @@ -require 'abstract_unit' +require "abstract_unit" class FixtureResolverTest < ActiveSupport::TestCase def test_should_return_empty_list_for_unknown_path resolver = ActionView::FixtureResolver.new() - templates = resolver.find_all("path", "arbitrary", false, {:locale => [], :formats => [:html], :variants => [], :handlers => []}) + templates = resolver.find_all("path", "arbitrary", false, locale: [], formats: [:html], variants: [], handlers: []) assert_equal [], templates, "expected an empty list of templates" end def test_should_return_template_for_declared_path resolver = ActionView::FixtureResolver.new("arbitrary/path.erb" => "this text") - templates = resolver.find_all("path", "arbitrary", false, {:locale => [], :formats => [:html], :variants => [], :handlers => [:erb]}) + templates = resolver.find_all("path", "arbitrary", false, locale: [], formats: [:html], variants: [], handlers: [:erb]) assert_equal 1, templates.size, "expected one template" assert_equal "this text", templates.first.source assert_equal "arbitrary/path", templates.first.virtual_path diff --git a/actionview/test/template/testing/null_resolver_test.rb b/actionview/test/template/testing/null_resolver_test.rb index 55ec36e753..5346fd3368 100644 --- a/actionview/test/template/testing/null_resolver_test.rb +++ b/actionview/test/template/testing/null_resolver_test.rb @@ -1,9 +1,9 @@ -require 'abstract_unit' +require "abstract_unit" class NullResolverTest < ActiveSupport::TestCase def test_should_return_template_for_any_path resolver = ActionView::NullResolver.new() - templates = resolver.find_all("path.erb", "arbitrary", false, {:locale => [], :formats => [:html], :handlers => []}) + templates = resolver.find_all("path.erb", "arbitrary", false, locale: [], formats: [:html], handlers: []) assert_equal 1, templates.size, "expected one template" assert_equal "Template generated by Null Resolver", templates.first.source assert_equal "arbitrary/path.erb", templates.first.virtual_path.to_s diff --git a/actionview/test/template/text_helper_test.rb b/actionview/test/template/text_helper_test.rb index 03c7597505..fb557c24ac 100644 --- a/actionview/test/template/text_helper_test.rb +++ b/actionview/test/template/text_helper_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class TextHelperTest < ActionView::TestCase tests ActionView::Helpers::TextHelper @@ -11,9 +11,9 @@ class TextHelperTest < ActionView::TestCase end def test_concat - self.output_buffer = 'foo' - assert_equal 'foobar', concat('bar') - assert_equal 'foobar', output_buffer + self.output_buffer = "foo" + assert_equal "foobar", concat("bar") + assert_equal "foobar", output_buffer end def test_simple_format_should_be_html_safe @@ -38,8 +38,8 @@ class TextHelperTest < ActionView::TestCase text = "A\r\n \nB\n\n\r\n\t\nC\nD".freeze assert_equal "<p>A\n<br /> \n<br />B</p>\n\n<p>\t\n<br />C\n<br />D</p>", simple_format(text) - assert_equal %q(<p class="test">This is a classy test</p>), simple_format("This is a classy test", :class => 'test') - assert_equal %Q(<p class="test">para 1</p>\n\n<p class="test">para 2</p>), simple_format("para 1\n\npara 2", :class => 'test') + assert_equal '<p class="test">This is a classy test</p>', simple_format("This is a classy test", class: "test") + assert_equal %Q(<p class="test">para 1</p>\n\n<p class="test">para 2</p>), simple_format("para 1\n\npara 2", class: "test") end def test_simple_format_should_sanitize_input_when_sanitize_option_is_not_false @@ -47,20 +47,20 @@ class TextHelperTest < ActionView::TestCase end def test_simple_format_should_sanitize_input_when_sanitize_option_is_true - assert_equal '<p><b> test with unsafe string </b>code!</p>', - simple_format('<b> test with unsafe string </b><script>code!</script>', {}, sanitize: true) + assert_equal "<p><b> test with unsafe string </b>code!</p>", + simple_format("<b> test with unsafe string </b><script>code!</script>", {}, sanitize: true) end def test_simple_format_should_not_sanitize_input_when_sanitize_option_is_false - assert_equal "<p><b> test with unsafe string </b><script>code!</script></p>", simple_format("<b> test with unsafe string </b><script>code!</script>", {}, :sanitize => false) + assert_equal "<p><b> test with unsafe string </b><script>code!</script></p>", simple_format("<b> test with unsafe string </b><script>code!</script>", {}, sanitize: false) end def test_simple_format_with_custom_wrapper - assert_equal "<div></div>", simple_format(nil, {}, :wrapper_tag => "div") + assert_equal "<div></div>", simple_format(nil, {}, wrapper_tag: "div") end def test_simple_format_with_custom_wrapper_and_multi_line_breaks - assert_equal "<div>We want to put a wrapper...</div>\n\n<div>...right there.</div>", simple_format("We want to put a wrapper...\n\n...right there.", {}, :wrapper_tag => "div") + assert_equal "<div>We want to put a wrapper...</div>\n\n<div>...right there.</div>", simple_format("We want to put a wrapper...\n\n...right there.", {}, wrapper_tag: "div") end def test_simple_format_should_not_change_the_text_passed @@ -71,22 +71,22 @@ class TextHelperTest < ActionView::TestCase end def test_simple_format_does_not_modify_the_html_options_hash - options = { :class => "foobar"} + options = { class: "foobar" } passed_options = options.dup simple_format("some text", passed_options) assert_equal options, passed_options end def test_simple_format_does_not_modify_the_options_hash - options = { :wrapper_tag => :div, :sanitize => false } + options = { wrapper_tag: :div, sanitize: false } passed_options = options.dup simple_format("some text", {}, passed_options) assert_equal options, passed_options end def test_truncate - assert_equal "Hello World!", truncate("Hello World!", :length => 12) - assert_equal "Hello Wor...", truncate("Hello World!!", :length => 12) + assert_equal "Hello World!", truncate("Hello World!", length: 12) + assert_equal "Hello Wor...", truncate("Hello World!!", length: 12) end def test_truncate_should_use_default_length_of_30 @@ -95,21 +95,21 @@ class TextHelperTest < ActionView::TestCase end def test_truncate_with_options_hash - assert_equal "This is a string that wil[...]", truncate("This is a string that will go longer then the default truncate length of 30", :omission => "[...]") - assert_equal "Hello W...", truncate("Hello World!", :length => 10) - assert_equal "Hello[...]", truncate("Hello World!", :omission => "[...]", :length => 10) - assert_equal "Hello[...]", truncate("Hello Big World!", :omission => "[...]", :length => 13, :separator => ' ') - assert_equal "Hello Big[...]", truncate("Hello Big World!", :omission => "[...]", :length => 14, :separator => ' ') - assert_equal "Hello Big[...]", truncate("Hello Big World!", :omission => "[...]", :length => 15, :separator => ' ') + assert_equal "This is a string that wil[...]", truncate("This is a string that will go longer then the default truncate length of 30", omission: "[...]") + assert_equal "Hello W...", truncate("Hello World!", length: 10) + assert_equal "Hello[...]", truncate("Hello World!", omission: "[...]", length: 10) + assert_equal "Hello[...]", truncate("Hello Big World!", omission: "[...]", length: 13, separator: " ") + assert_equal "Hello Big[...]", truncate("Hello Big World!", omission: "[...]", length: 14, separator: " ") + assert_equal "Hello Big[...]", truncate("Hello Big World!", omission: "[...]", length: 15, separator: " ") end def test_truncate_multibyte assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding(Encoding::UTF_8), - truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding(Encoding::UTF_8), :length => 10) + truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding(Encoding::UTF_8), length: 10) end def test_truncate_does_not_modify_the_options_hash - options = { :length => 10 } + options = { length: 10 } passed_options = options.dup truncate("some text", passed_options) assert_equal options, passed_options @@ -117,49 +117,49 @@ class TextHelperTest < ActionView::TestCase def test_truncate_with_link_options assert_equal "Here is a long test and ...<a href=\"#\">Continue</a>", - truncate("Here is a long test and I need a continue to read link", :length => 27) { link_to 'Continue', '#' } + truncate("Here is a long test and I need a continue to read link", length: 27) { link_to "Continue", "#" } end def test_truncate_should_be_html_safe - assert truncate("Hello World!", :length => 12).html_safe? + assert truncate("Hello World!", length: 12).html_safe? end def test_truncate_should_escape_the_input - assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!!", :length => 12) + assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!!", length: 12) end def test_truncate_should_not_escape_the_input_with_escape_false - assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!!", :length => 12, :escape => false) + assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!!", length: 12, escape: false) end def test_truncate_with_escape_false_should_be_html_safe - truncated = truncate("Hello <script>code!</script>World!!", :length => 12, :escape => false) + truncated = truncate("Hello <script>code!</script>World!!", length: 12, escape: false) assert truncated.html_safe? end def test_truncate_with_block_should_be_html_safe - truncated = truncate("Here's a long test and I need a continue to read link", :length => 27) { link_to 'Continue', '#' } + truncated = truncate("Here's a long test and I need a continue to read link", length: 27) { link_to "Continue", "#" } assert truncated.html_safe? end def test_truncate_with_block_should_escape_the_input assert_equal "<script>code!</script>He...<a href=\"#\">Continue</a>", - truncate("<script>code!</script>Here's a long test and I need a continue to read link", :length => 27) { link_to 'Continue', '#' } + truncate("<script>code!</script>Here's a long test and I need a continue to read link", length: 27) { link_to "Continue", "#" } end def test_truncate_with_block_should_not_escape_the_input_with_escape_false assert_equal "<script>code!</script>He...<a href=\"#\">Continue</a>", - truncate("<script>code!</script>Here's a long test and I need a continue to read link", :length => 27, :escape => false) { link_to 'Continue', '#' } + truncate("<script>code!</script>Here's a long test and I need a continue to read link", length: 27, escape: false) { link_to "Continue", "#" } end def test_truncate_with_block_with_escape_false_should_be_html_safe - truncated = truncate("<script>code!</script>Here's a long test and I need a continue to read link", :length => 27, :escape => false) { link_to 'Continue', '#' } + truncated = truncate("<script>code!</script>Here's a long test and I need a continue to read link", length: 27, escape: false) { link_to "Continue", "#" } assert truncated.html_safe? end def test_truncate_with_block_should_escape_the_block assert_equal "Here is a long test and ...<script>alert('foo');</script>", - truncate("Here is a long test and I need a continue to read link", :length => 27) { "<script>alert('foo');</script>" } + truncate("Here is a long test and I need a continue to read link", length: 27) { "<script>alert('foo');</script>" } end def test_highlight_should_be_html_safe @@ -179,7 +179,7 @@ class TextHelperTest < ActionView::TestCase assert_equal( "This is a <b>beautiful</b> morning, but also a <b>beautiful</b> day", - highlight("This is a beautiful morning, but also a beautiful day", "beautiful", :highlighter => '<b>\1</b>') + highlight("This is a beautiful morning, but also a beautiful day", "beautiful", highlighter: '<b>\1</b>') ) assert_equal( @@ -189,11 +189,11 @@ class TextHelperTest < ActionView::TestCase end def test_highlight_pending - assert_equal ' ', highlight(' ', 'blank text is returned verbatim') + assert_equal " ", highlight(" ", "blank text is returned verbatim") end def test_highlight_should_return_blank_string_for_nil - assert_equal '', highlight(nil, 'blank string is returned for nil') + assert_equal "", highlight(nil, "blank string is returned for nil") end def test_highlight_should_sanitize_input @@ -206,7 +206,7 @@ class TextHelperTest < ActionView::TestCase def test_highlight_should_not_sanitize_if_sanitize_option_if_false assert_equal( "This is a <mark>beautiful</mark> morning<script>code!</script>", - highlight("This is a beautiful morning<script>code!</script>", "beautiful", :sanitize => false) + highlight("This is a beautiful morning<script>code!</script>", "beautiful", sanitize: false) ) end @@ -233,7 +233,7 @@ class TextHelperTest < ActionView::TestCase end def test_highlight_with_multiple_phrases_in_one_pass - assert_equal %(<em>wow</em> <em>em</em>), highlight('wow em', %w(wow em), :highlighter => '<em>\1</em>') + assert_equal %(<em>wow</em> <em>em</em>), highlight("wow em", %w(wow em), highlighter: '<em>\1</em>') end def test_highlight_with_html @@ -259,12 +259,12 @@ class TextHelperTest < ActionView::TestCase ) assert_equal( "<div>abc <b>div</b></div>", - highlight("<div>abc div</div>", "div", :highlighter => '<b>\1</b>') + highlight("<div>abc div</div>", "div", highlighter: '<b>\1</b>') ) end def test_highlight_does_not_modify_the_options_hash - options = { :highlighter => '<b>\1</b>', :sanitize => false } + options = { highlighter: '<b>\1</b>', sanitize: false } passed_options = options.dup highlight("<div>abc div</div>", "div", passed_options) assert_equal options, passed_options @@ -278,89 +278,89 @@ class TextHelperTest < ActionView::TestCase end def test_excerpt - assert_equal("...is a beautiful morn...", excerpt("This is a beautiful morning", "beautiful", :radius => 5)) - assert_equal("This is a...", excerpt("This is a beautiful morning", "this", :radius => 5)) - assert_equal("...iful morning", excerpt("This is a beautiful morning", "morning", :radius => 5)) + assert_equal("...is a beautiful morn...", excerpt("This is a beautiful morning", "beautiful", radius: 5)) + assert_equal("This is a...", excerpt("This is a beautiful morning", "this", radius: 5)) + assert_equal("...iful morning", excerpt("This is a beautiful morning", "morning", radius: 5)) assert_nil excerpt("This is a beautiful morning", "day") end def test_excerpt_with_regex - assert_equal('...is a beautiful! mor...', excerpt('This is a beautiful! morning', 'beautiful', :radius => 5)) - assert_equal('...is a beautiful? mor...', excerpt('This is a beautiful? morning', 'beautiful', :radius => 5)) - assert_equal('...is a beautiful? mor...', excerpt('This is a beautiful? morning', /\bbeau\w*\b/i, :radius => 5)) - assert_equal('...is a beautiful? mor...', excerpt('This is a beautiful? morning', /\b(beau\w*)\b/i, :radius => 5)) - assert_equal("...udge Allen and...", excerpt("This day was challenging for judge Allen and his colleagues.", /\ballen\b/i, :radius => 5)) - assert_equal("...judge Allen and...", excerpt("This day was challenging for judge Allen and his colleagues.", /\ballen\b/i, :radius => 1, :separator => ' ')) - assert_equal("...was challenging for...", excerpt("This day was challenging for judge Allen and his colleagues.", /\b(\w*allen\w*)\b/i, :radius => 5)) + assert_equal("...is a beautiful! mor...", excerpt("This is a beautiful! morning", "beautiful", radius: 5)) + assert_equal("...is a beautiful? mor...", excerpt("This is a beautiful? morning", "beautiful", radius: 5)) + assert_equal("...is a beautiful? mor...", excerpt("This is a beautiful? morning", /\bbeau\w*\b/i, radius: 5)) + assert_equal("...is a beautiful? mor...", excerpt("This is a beautiful? morning", /\b(beau\w*)\b/i, radius: 5)) + assert_equal("...udge Allen and...", excerpt("This day was challenging for judge Allen and his colleagues.", /\ballen\b/i, radius: 5)) + assert_equal("...judge Allen and...", excerpt("This day was challenging for judge Allen and his colleagues.", /\ballen\b/i, radius: 1, separator: " ")) + assert_equal("...was challenging for...", excerpt("This day was challenging for judge Allen and his colleagues.", /\b(\w*allen\w*)\b/i, radius: 5)) end def test_excerpt_should_not_be_html_safe - assert !excerpt('This is a beautiful! morning', 'beautiful', :radius => 5).html_safe? + assert !excerpt("This is a beautiful! morning", "beautiful", radius: 5).html_safe? end def test_excerpt_in_borderline_cases - assert_equal("", excerpt("", "", :radius => 0)) - assert_equal("a", excerpt("a", "a", :radius => 0)) - assert_equal("...b...", excerpt("abc", "b", :radius => 0)) - assert_equal("abc", excerpt("abc", "b", :radius => 1)) - assert_equal("abc...", excerpt("abcd", "b", :radius => 1)) - assert_equal("...abc", excerpt("zabc", "b", :radius => 1)) - assert_equal("...abc...", excerpt("zabcd", "b", :radius => 1)) - assert_equal("zabcd", excerpt("zabcd", "b", :radius => 2)) + assert_equal("", excerpt("", "", radius: 0)) + assert_equal("a", excerpt("a", "a", radius: 0)) + assert_equal("...b...", excerpt("abc", "b", radius: 0)) + assert_equal("abc", excerpt("abc", "b", radius: 1)) + assert_equal("abc...", excerpt("abcd", "b", radius: 1)) + assert_equal("...abc", excerpt("zabc", "b", radius: 1)) + assert_equal("...abc...", excerpt("zabcd", "b", radius: 1)) + assert_equal("zabcd", excerpt("zabcd", "b", radius: 2)) # excerpt strips the resulting string before ap-/prepending excerpt_string. # whether this behavior is meaningful when excerpt_string is not to be # appended is questionable. - assert_equal("zabcd", excerpt(" zabcd ", "b", :radius => 4)) - assert_equal("...abc...", excerpt("z abc d", "b", :radius => 1)) + assert_equal("zabcd", excerpt(" zabcd ", "b", radius: 4)) + assert_equal("...abc...", excerpt("z abc d", "b", radius: 1)) end def test_excerpt_with_omission - assert_equal("[...]is a beautiful morn[...]", excerpt("This is a beautiful morning", "beautiful", :omission => "[...]",:radius => 5)) + assert_equal("[...]is a beautiful morn[...]", excerpt("This is a beautiful morning", "beautiful", omission: "[...]",radius: 5)) assert_equal( "This is the ultimate supercalifragilisticexpialidoceous very looooooooooooooooooong looooooooooooong beautiful morning with amazing sunshine and awesome tempera[...]", excerpt("This is the ultimate supercalifragilisticexpialidoceous very looooooooooooooooooong looooooooooooong beautiful morning with amazing sunshine and awesome temperatures. So what are you gonna do about it?", "very", - :omission => "[...]") + omission: "[...]") ) end def test_excerpt_with_utf8 - assert_equal("...\357\254\203ciency could not be...".force_encoding(Encoding::UTF_8), excerpt("That's why e\357\254\203ciency could not be helped".force_encoding(Encoding::UTF_8), 'could', :radius => 8)) + assert_equal("...\357\254\203ciency could not be...".force_encoding(Encoding::UTF_8), excerpt("That's why e\357\254\203ciency could not be helped".force_encoding(Encoding::UTF_8), "could", radius: 8)) end def test_excerpt_does_not_modify_the_options_hash - options = { :omission => "[...]",:radius => 5 } + options = { omission: "[...]",radius: 5 } passed_options = options.dup excerpt("This is a beautiful morning", "beautiful", passed_options) assert_equal options, passed_options end def test_excerpt_with_separator - options = { :separator => ' ', :radius => 1 } - assert_equal('...a very beautiful...', excerpt('This is a very beautiful morning', 'very', options)) - assert_equal('This is...', excerpt('This is a very beautiful morning', 'this', options)) - assert_equal('...beautiful morning', excerpt('This is a very beautiful morning', 'morning', options)) + options = { separator: " ", radius: 1 } + assert_equal("...a very beautiful...", excerpt("This is a very beautiful morning", "very", options)) + assert_equal("This is...", excerpt("This is a very beautiful morning", "this", options)) + assert_equal("...beautiful morning", excerpt("This is a very beautiful morning", "morning", options)) - options = { :separator => "\n", :radius => 0 } - assert_equal("...very long...", excerpt("my very\nvery\nvery long\nstring", 'long', options)) + options = { separator: "\n", radius: 0 } + assert_equal("...very long...", excerpt("my very\nvery\nvery long\nstring", "long", options)) - options = { :separator => "\n", :radius => 1 } - assert_equal("...very\nvery long\nstring", excerpt("my very\nvery\nvery long\nstring", 'long', options)) + options = { separator: "\n", radius: 1 } + assert_equal("...very\nvery long\nstring", excerpt("my very\nvery\nvery long\nstring", "long", options)) - assert_equal excerpt('This is a beautiful morning', 'a'), - excerpt('This is a beautiful morning', 'a', separator: nil) + assert_equal excerpt("This is a beautiful morning", "a"), + excerpt("This is a beautiful morning", "a", separator: nil) end def test_word_wrap - assert_equal("my very very\nvery long\nstring", word_wrap("my very very very long string", :line_width => 15)) + assert_equal("my very very\nvery long\nstring", word_wrap("my very very very long string", line_width: 15)) end def test_word_wrap_with_extra_newlines - assert_equal("my very very\nvery long\nstring\n\nwith another\nline", word_wrap("my very very very long string\n\nwith another line", :line_width => 15)) + assert_equal("my very very\nvery long\nstring\n\nwith another\nline", word_wrap("my very very very long string\n\nwith another line", line_width: 15)) end def test_word_wrap_does_not_modify_the_options_hash - options = { :line_width => 15 } + options = { line_width: 15 } passed_options = options.dup word_wrap("some text", passed_options) assert_equal options, passed_options @@ -373,12 +373,14 @@ class TextHelperTest < ActionView::TestCase def test_pluralization assert_equal("1 count", pluralize(1, "count")) assert_equal("2 counts", pluralize(2, "count")) - assert_equal("1 count", pluralize('1', "count")) - assert_equal("2 counts", pluralize('2', "count")) - assert_equal("1,066 counts", pluralize('1,066', "count")) - assert_equal("1.25 counts", pluralize('1.25', "count")) - assert_equal("1.0 count", pluralize('1.0', "count")) - assert_equal("1.00 count", pluralize('1.00', "count")) + assert_equal("1 count", pluralize("1", "count")) + assert_equal("2 counts", pluralize("2", "count")) + assert_equal("1,066 counts", pluralize("1,066", "count")) + assert_equal("1.25 counts", pluralize("1.25", "count")) + assert_equal("1.0 count", pluralize("1.0", "count")) + assert_equal("1.00 count", pluralize("1.00", "count")) + assert_equal("2 counters", pluralize(2, "count", "counters")) + assert_equal("0 counters", pluralize(nil, "count", "counters")) assert_equal("2 counters", pluralize(2, "count", plural: "counters")) assert_equal("0 counters", pluralize(nil, "count", plural: "counters")) assert_equal("2 people", pluralize(2, "person")) @@ -394,7 +396,7 @@ class TextHelperTest < ActionView::TestCase I18n.locale = :de ActiveSupport::Inflector.inflections(:de) do |inflect| - inflect.irregular 'region', 'regionen' + inflect.irregular "region", "regionen" end assert_equal("1 region", pluralize(1, "region")) @@ -405,12 +407,6 @@ class TextHelperTest < ActionView::TestCase end end - def test_deprecated_plural_as_positional_argument - assert_deprecated do - pluralize(2, 'count', 'counters') - end - end - def test_cycle_class value = Cycle.new("one", 2, "3") assert_equal("one", value.to_s) @@ -458,12 +454,12 @@ class TextHelperTest < ActionView::TestCase end def test_named_cycles - assert_equal("1", cycle(1, 2, 3, :name => "numbers")) - assert_equal("red", cycle("red", "blue", :name => "colors")) - assert_equal("2", cycle(1, 2, 3, :name => "numbers")) - assert_equal("blue", cycle("red", "blue", :name => "colors")) - assert_equal("3", cycle(1, 2, 3, :name => "numbers")) - assert_equal("red", cycle("red", "blue", :name => "colors")) + assert_equal("1", cycle(1, 2, 3, name: "numbers")) + assert_equal("red", cycle("red", "blue", name: "colors")) + assert_equal("2", cycle(1, 2, 3, name: "numbers")) + assert_equal("blue", cycle("red", "blue", name: "colors")) + assert_equal("3", cycle(1, 2, 3, name: "numbers")) + assert_equal("red", cycle("red", "blue", name: "colors")) end def test_current_cycle_with_default_name @@ -476,11 +472,11 @@ class TextHelperTest < ActionView::TestCase end def test_current_cycle_with_named_cycles - cycle("red", "blue", :name => "colors") + cycle("red", "blue", name: "colors") assert_equal "red", current_cycle("colors") - cycle("red", "blue", :name => "colors") + cycle("red", "blue", name: "colors") assert_equal "blue", current_cycle("colors") - cycle("red", "blue", :name => "colors") + cycle("red", "blue", name: "colors") assert_equal "red", current_cycle("colors") end @@ -502,7 +498,7 @@ class TextHelperTest < ActionView::TestCase def test_default_named_cycle assert_equal("1", cycle(1, 2, 3)) - assert_equal("2", cycle(1, 2, 3, :name => "default")) + assert_equal("2", cycle(1, 2, 3, name: "default")) assert_equal("3", cycle(1, 2, 3)) end @@ -518,13 +514,13 @@ class TextHelperTest < ActionView::TestCase end def test_reset_named_cycle - assert_equal("1", cycle(1, 2, 3, :name => "numbers")) - assert_equal("red", cycle("red", "blue", :name => "colors")) + assert_equal("1", cycle(1, 2, 3, name: "numbers")) + assert_equal("red", cycle("red", "blue", name: "colors")) reset_cycle("numbers") - assert_equal("1", cycle(1, 2, 3, :name => "numbers")) - assert_equal("blue", cycle("red", "blue", :name => "colors")) - assert_equal("2", cycle(1, 2, 3, :name => "numbers")) - assert_equal("red", cycle("red", "blue", :name => "colors")) + assert_equal("1", cycle(1, 2, 3, name: "numbers")) + assert_equal("blue", cycle("red", "blue", name: "colors")) + assert_equal("2", cycle(1, 2, 3, name: "numbers")) + assert_equal("red", cycle("red", "blue", name: "colors")) end def test_cycle_no_instance_variable_clashes diff --git a/actionview/test/template/text_test.rb b/actionview/test/template/text_test.rb index d899d54589..6510688f97 100644 --- a/actionview/test/template/text_test.rb +++ b/actionview/test/template/text_test.rb @@ -1,17 +1,17 @@ -require 'abstract_unit' +require "abstract_unit" class TextTest < ActiveSupport::TestCase - test 'formats returns symbol for recognized MIME type' do - assert_equal [:text], ActionView::Template::Text.new('', :text).formats + test "formats returns symbol for recognized MIME type" do + assert_equal [:text], ActionView::Template::Text.new("", :text).formats end - test 'formats returns string for recognized MIME type when MIME does not have symbol' do + test "formats returns string for recognized MIME type when MIME does not have symbol" do foo = Mime::Type.lookup("foo") assert_nil foo.to_sym - assert_equal ['foo'], ActionView::Template::Text.new('', foo).formats + assert_equal ["foo"], ActionView::Template::Text.new("", foo).formats end - test 'formats returns string for unknown MIME type' do - assert_equal ['foo'], ActionView::Template::Text.new('', 'foo').formats + test "formats returns string for unknown MIME type" do + assert_equal ["foo"], ActionView::Template::Text.new("", "foo").formats end end diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb index 38b9284767..1cfc13f337 100644 --- a/actionview/test/template/translation_helper_test.rb +++ b/actionview/test/template/translation_helper_test.rb @@ -1,9 +1,9 @@ -require 'abstract_unit' +require "abstract_unit" module I18n class CustomExceptionHandler def self.call(exception, locale, key, options) - 'from CustomExceptionHandler' + "from CustomExceptionHandler" end end end @@ -15,22 +15,22 @@ class TranslationHelperTest < ActiveSupport::TestCase setup do I18n.backend.store_translations(:en, - :translations => { - :templates => { - :found => { :foo => 'Foo' }, - :array => { :foo => { :bar => 'Foo Bar' } }, - :default => { :foo => 'Foo' } + translations: { + templates: { + found: { foo: "Foo" }, + array: { foo: { bar: "Foo Bar" } }, + default: { foo: "Foo" } }, - :foo => 'Foo', - :hello => '<a>Hello World</a>', - :html => '<a>Hello World</a>', - :hello_html => '<a>Hello World</a>', - :interpolated_html => '<a>Hello %{word}</a>', - :array_html => %w(foo bar), - :array => %w(foo bar), - :count_html => { - :one => '<a>One %{count}</a>', - :other => '<a>Other %{count}</a>' + foo: "Foo", + hello: "<a>Hello World</a>", + html: "<a>Hello World</a>", + hello_html: "<a>Hello World</a>", + interpolated_html: "<a>Hello %{word}</a>", + array_html: %w(foo bar), + array: %w(foo bar), + count_html: { + one: "<a>One %{count}</a>", + other: "<a>Other %{count}</a>" } } ) @@ -42,8 +42,8 @@ class TranslationHelperTest < ActiveSupport::TestCase end def test_delegates_setting_to_i18n - assert_called_with(I18n, :translate, [:foo, :locale => 'en', :raise => true], returns: "") do - translate :foo, :locale => 'en' + assert_called_with(I18n, :translate, [:foo, locale: "en", raise: true], returns: "") do + translate :foo, locale: "en" end end @@ -58,7 +58,7 @@ class TranslationHelperTest < ActiveSupport::TestCase old_value = ActionView::Base.debug_missing_translation ActionView::Base.debug_missing_translation = false - expected = 'translation missing: en.translations.missing' + expected = "translation missing: en.translations.missing" assert_equal expected, translate(:"translations.missing") ensure ActionView::Base.debug_missing_translation = old_value @@ -78,10 +78,10 @@ class TranslationHelperTest < ActiveSupport::TestCase def test_returns_missing_translation_message_does_filters_out_i18n_options expected = '<span class="translation_missing" title="translation missing: en.translations.missing, year: 2015">Missing</span>' - assert_equal expected, translate(:"translations.missing", year: '2015', default: []) + assert_equal expected, translate(:"translations.missing", year: "2015", default: []) expected = '<span class="translation_missing" title="translation missing: en.scoped.translations.missing, year: 2015">Missing</span>' - assert_equal expected, translate(:"translations.missing", year: '2015', scope: %i(scoped)) + assert_equal expected, translate(:"translations.missing", year: "2015", scope: %i(scoped)) end def test_raises_missing_translation_message_with_raise_config_option @@ -96,14 +96,14 @@ class TranslationHelperTest < ActiveSupport::TestCase def test_raises_missing_translation_message_with_raise_option assert_raise(I18n::MissingTranslationData) do - translate(:"translations.missing", :raise => true) + translate(:"translations.missing", raise: true) end end def test_uses_custom_exception_handler_when_specified old_exception_handler = I18n.exception_handler I18n.exception_handler = I18n::CustomExceptionHandler - assert_equal 'from CustomExceptionHandler', translate(:"translations.missing", raise: false) + assert_equal "from CustomExceptionHandler", translate(:"translations.missing", raise: false) ensure I18n.exception_handler = old_exception_handler end @@ -111,7 +111,7 @@ class TranslationHelperTest < ActiveSupport::TestCase def test_uses_custom_exception_handler_when_specified_for_html old_exception_handler = I18n.exception_handler I18n.exception_handler = I18n::CustomExceptionHandler - assert_equal 'from CustomExceptionHandler', translate(:"translations.missing_html", raise: false) + assert_equal "from CustomExceptionHandler", translate(:"translations.missing_html", raise: false) ensure I18n.exception_handler = old_exception_handler end @@ -122,20 +122,20 @@ class TranslationHelperTest < ActiveSupport::TestCase end def test_finds_translation_scoped_by_partial - assert_equal 'Foo', view.render(:file => 'translations/templates/found').strip + assert_equal "Foo", view.render(file: "translations/templates/found").strip end def test_finds_array_of_translations_scoped_by_partial - assert_equal 'Foo Bar', @view.render(:file => 'translations/templates/array').strip + assert_equal "Foo Bar", @view.render(file: "translations/templates/array").strip end def test_default_lookup_scoped_by_partial - assert_equal 'Foo', view.render(:file => 'translations/templates/default').strip + assert_equal "Foo", view.render(file: "translations/templates/default").strip end def test_missing_translation_scoped_by_partial expected = '<span class="translation_missing" title="translation missing: en.translations.templates.missing.missing">Missing</span>' - assert_equal expected, view.render(:file => 'translations/templates/missing').strip + assert_equal expected, view.render(file: "translations/templates/missing").strip end def test_translate_does_not_mark_plain_text_as_safe_html @@ -152,14 +152,14 @@ class TranslationHelperTest < ActiveSupport::TestCase def test_translate_escapes_interpolations_in_translations_with_a_html_suffix word_struct = Struct.new(:to_s) - assert_equal '<a>Hello <World></a>', translate(:'translations.interpolated_html', :word => '<World>') - assert_equal '<a>Hello <World></a>', translate(:'translations.interpolated_html', :word => word_struct.new("<World>")) + assert_equal "<a>Hello <World></a>", translate(:'translations.interpolated_html', word: "<World>") + assert_equal "<a>Hello <World></a>", translate(:'translations.interpolated_html', word: word_struct.new("<World>")) end def test_translate_with_html_count - assert_equal '<a>One 1</a>', translate(:'translations.count_html', :count => 1) - assert_equal '<a>Other 2</a>', translate(:'translations.count_html', :count => 2) - assert_equal '<a>Other <One></a>', translate(:'translations.count_html', :count => '<One>') + assert_equal "<a>One 1</a>", translate(:'translations.count_html', count: 1) + assert_equal "<a>Other 2</a>", translate(:'translations.count_html', count: 2) + assert_equal "<a>Other <One></a>", translate(:'translations.count_html', count: "<One>") end def test_translation_returning_an_array_ignores_html_suffix @@ -167,13 +167,13 @@ class TranslationHelperTest < ActiveSupport::TestCase end def test_translate_with_default_named_html - translation = translate(:'translations.missing', :default => :'translations.hello_html') - assert_equal '<a>Hello World</a>', translation + translation = translate(:'translations.missing', default: :'translations.hello_html') + assert_equal "<a>Hello World</a>", translation assert_equal true, translation.html_safe? end def test_translate_with_missing_default - translation = translate(:'translations.missing', :default => :'translations.missing_html') + translation = translate(:'translations.missing', default: :'translations.missing_html') expected = '<span class="translation_missing" title="translation missing: en.translations.missing_html">Missing Html</span>' assert_equal expected, translation assert_equal true, translation.html_safe? @@ -181,31 +181,31 @@ class TranslationHelperTest < ActiveSupport::TestCase def test_translate_with_missing_default_and_raise_option assert_raise(I18n::MissingTranslationData) do - translate(:'translations.missing', :default => :'translations.missing_html', :raise => true) + translate(:'translations.missing', default: :'translations.missing_html', raise: true) end end def test_translate_with_two_defaults_named_html - translation = translate(:'translations.missing', :default => [:'translations.missing_html', :'translations.hello_html']) - assert_equal '<a>Hello World</a>', translation + translation = translate(:'translations.missing', default: [:'translations.missing_html', :'translations.hello_html']) + assert_equal "<a>Hello World</a>", translation assert_equal true, translation.html_safe? end def test_translate_with_last_default_named_html - translation = translate(:'translations.missing', :default => [:'translations.missing', :'translations.hello_html']) - assert_equal '<a>Hello World</a>', translation + translation = translate(:'translations.missing', default: [:'translations.missing', :'translations.hello_html']) + assert_equal "<a>Hello World</a>", translation assert_equal true, translation.html_safe? end def test_translate_with_last_default_not_named_html - translation = translate(:'translations.missing', :default => [:'translations.missing_html', :'translations.foo']) - assert_equal 'Foo', translation + translation = translate(:'translations.missing', default: [:'translations.missing_html', :'translations.foo']) + assert_equal "Foo", translation assert_equal false, translation.html_safe? end def test_translate_with_string_default - translation = translate(:'translations.missing', default: 'A Generic String') - assert_equal 'A Generic String', translation + translation = translate(:'translations.missing', default: "A Generic String") + assert_equal "A Generic String", translation end def test_translate_with_object_default @@ -214,13 +214,13 @@ class TranslationHelperTest < ActiveSupport::TestCase end def test_translate_with_array_of_string_defaults - translation = translate(:'translations.missing', default: ['A Generic String', 'Second generic string']) - assert_equal 'A Generic String', translation + translation = translate(:'translations.missing', default: ["A Generic String", "Second generic string"]) + assert_equal "A Generic String", translation end def test_translate_with_array_of_defaults_with_nil - translation = translate(:'translations.missing', default: [:'also_missing', nil, 'A Generic String']) - assert_equal 'A Generic String', translation + translation = translate(:'translations.missing', default: [:'also_missing', nil, "A Generic String"]) + assert_equal "A Generic String", translation end def test_translate_with_array_of_array_default diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb index 3010656166..5a2319fe96 100644 --- a/actionview/test/template/url_helper_test.rb +++ b/actionview/test/template/url_helper_test.rb @@ -1,7 +1,6 @@ -require 'abstract_unit' +require "abstract_unit" class UrlHelperTest < ActiveSupport::TestCase - # In a few cases, the helper proxies to 'controller' # or request. # @@ -43,32 +42,60 @@ class UrlHelperTest < ActiveSupport::TestCase end def test_url_for_with_back - referer = 'http://www.example.com/referer' + referer = "http://www.example.com/referer" @controller = Struct.new(:request).new(Struct.new(:env).new("HTTP_REFERER" => referer)) - assert_equal 'http://www.example.com/referer', url_for(:back) + assert_equal "http://www.example.com/referer", url_for(:back) end def test_url_for_with_back_and_no_referer @controller = Struct.new(:request).new(Struct.new(:env).new({})) - assert_equal 'javascript:history.back()', url_for(:back) + assert_equal "javascript:history.back()", url_for(:back) end def test_url_for_with_back_and_no_controller @controller = nil - assert_equal 'javascript:history.back()', url_for(:back) + assert_equal "javascript:history.back()", url_for(:back) end def test_url_for_with_back_and_javascript_referer - referer = 'javascript:alert(document.cookie)' + referer = "javascript:alert(document.cookie)" @controller = Struct.new(:request).new(Struct.new(:env).new("HTTP_REFERER" => referer)) - assert_equal 'javascript:history.back()', url_for(:back) + assert_equal "javascript:history.back()", url_for(:back) end def test_url_for_with_invalid_referer - referer = 'THIS IS NOT A URL' + referer = "THIS IS NOT A URL" @controller = Struct.new(:request).new(Struct.new(:env).new("HTTP_REFERER" => referer)) - assert_equal 'javascript:history.back()', url_for(:back) + assert_equal "javascript:history.back()", url_for(:back) + end + + def test_to_form_params_with_hash + assert_equal( + [{ name: :name, value: "David" }, { name: :nationality, value: "Danish" }], + to_form_params(name: "David", nationality: "Danish") + ) + end + + def test_to_form_params_with_nested_hash + assert_equal( + [{ name: "country[name]", value: "Denmark" }], + to_form_params(country: { name: "Denmark" }) + ) + end + + def test_to_form_params_with_array_nested_in_hash + assert_equal( + [{ name: "countries[]", value: "Denmark" }, { name: "countries[]", value: "Sweden" }], + to_form_params(countries: ["Denmark", "Sweden"]) + ) + end + + def test_to_form_params_with_namespace + assert_equal( + [{ name: "country[name]", value: "Denmark" }], + to_form_params({ name: "Denmark" }, "country") + ) end def test_button_to_with_straight_url @@ -94,11 +121,11 @@ class UrlHelperTest < ActiveSupport::TestCase end def test_button_to_with_form_class - assert_dom_equal %{<form method="post" action="http://www.example.com" class="custom-class"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", form_class: 'custom-class') + assert_dom_equal %{<form method="post" action="http://www.example.com" class="custom-class"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", form_class: "custom-class") end def test_button_to_with_form_class_escapes - assert_dom_equal %{<form method="post" action="http://www.example.com" class="<script>evil_js</script>"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", form_class: '<script>evil_js</script>') + assert_dom_equal %{<form method="post" action="http://www.example.com" class="<script>evil_js</script>"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", form_class: "<script>evil_js</script>") end def test_button_to_with_query @@ -183,14 +210,55 @@ class UrlHelperTest < ActiveSupport::TestCase def test_button_to_with_block assert_dom_equal( %{<form method="post" action="http://www.example.com" class="button_to"><button type="submit"><span>Hello</span></button></form>}, - button_to("http://www.example.com") { content_tag(:span, 'Hello') } + button_to("http://www.example.com") { content_tag(:span, "Hello") } ) end def test_button_to_with_params assert_dom_equal( - %{<form action="http://www.example.com" class="button_to" method="post"><input type="submit" value="Hello" /><input type="hidden" name="foo" value="bar" /><input type="hidden" name="baz" value="quux" /></form>}, - button_to("Hello", "http://www.example.com", params: {foo: :bar, baz: "quux"}) + %{<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: { foo: :bar, baz: "quux" }) + ) + end + + class FakeParams + def initialize(permitted = true) + @permitted = permitted + end + + def permitted? + @permitted + end + + def to_h + { foo: :bar, baz: "quux" } + end + end + + def test_button_to_with_permited_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 + assert_raises(ArgumentError) do + button_to("Hello", "http://www.example.com", params: FakeParams.new(false)) + end + end + + def test_button_to_with_nested_hash_params + assert_dom_equal( + %{<form action="http://www.example.com" class="button_to" method="post"><input type="submit" value="Hello" /><input type="hidden" name="foo[bar]" value="baz" /></form>}, + button_to("Hello", "http://www.example.com", params: { foo: { bar: "baz" } }) + ) + end + + def test_button_to_with_nested_array_params + assert_dom_equal( + %{<form action="http://www.example.com" class="button_to" method="post"><input type="submit" value="Hello" /><input type="hidden" name="foo[]" value="bar" /></form>}, + button_to("Hello", "http://www.example.com", params: { foo: ["bar"] }) ) end @@ -199,13 +267,13 @@ class UrlHelperTest < ActiveSupport::TestCase end def test_link_tag_without_host_option - assert_dom_equal(%{<a href="/">Test Link</a>}, link_to('Test Link', url_hash)) + assert_dom_equal(%{<a href="/">Test Link</a>}, link_to("Test Link", url_hash)) end def test_link_tag_with_host_option hash = hash_for(host: "www.example.com") expected = %{<a href="http://www.example.com/">Test Link</a>} - assert_dom_equal(expected, link_to('Test Link', hash)) + assert_dom_equal(expected, link_to("Test Link", hash)) end def test_link_tag_with_query @@ -219,15 +287,15 @@ class UrlHelperTest < ActiveSupport::TestCase end def test_link_tag_with_back - env = {"HTTP_REFERER" => "http://www.example.com/referer"} + env = { "HTTP_REFERER" => "http://www.example.com/referer" } @controller = Struct.new(:request).new(Struct.new(:env).new(env)) expected = %{<a href="#{env["HTTP_REFERER"]}">go back</a>} - assert_dom_equal expected, link_to('go back', :back) + assert_dom_equal expected, link_to("go back", :back) end def test_link_tag_with_back_and_no_referer @controller = Struct.new(:request).new(Struct.new(:env).new({})) - link = link_to('go back', :back) + link = link_to("go back", :back) assert_dom_equal %{<a href="javascript:history.back()">go back</a>}, link end @@ -287,7 +355,7 @@ class UrlHelperTest < ActiveSupport::TestCase def test_link_to_with_string_remote_in_non_html_options assert_dom_equal( %{<a href="/" data-remote="true">Hello</a>}, - link_to("Hello", hash_for('remote' => true), {}) + link_to("Hello", hash_for("remote" => true), {}) ) end @@ -308,14 +376,14 @@ class UrlHelperTest < ActiveSupport::TestCase def test_link_tag_using_delete_javascript_and_href assert_dom_equal( %{<a href="\#" rel="nofollow" data-method="delete">Destroy</a>}, - link_to("Destroy", "http://www.example.com", method: :delete, href: '#') + link_to("Destroy", "http://www.example.com", method: :delete, href: "#") ) end def test_link_tag_using_post_javascript_and_rel assert_dom_equal( %{<a href="http://www.example.com" data-method="post" rel="example nofollow">Hello</a>}, - link_to("Hello", "http://www.example.com", method: :post, rel: 'example') + link_to("Hello", "http://www.example.com", method: :post, rel: "example") ) end @@ -329,24 +397,24 @@ class UrlHelperTest < ActiveSupport::TestCase def test_link_tag_using_delete_javascript_and_href_and_confirm assert_dom_equal( %{<a href="\#" rel="nofollow" data-confirm="Are you serious?" data-method="delete">Destroy</a>}, - link_to("Destroy", "http://www.example.com", method: :delete, href: '#', data: { confirm: "Are you serious?" }) + link_to("Destroy", "http://www.example.com", method: :delete, href: "#", data: { confirm: "Are you serious?" }) ) end def test_link_tag_with_block assert_dom_equal %{<a href="/"><span>Example site</span></a>}, - link_to('/') { content_tag(:span, 'Example site') } + link_to("/") { content_tag(:span, "Example site") } end def test_link_tag_with_block_and_html_options assert_dom_equal %{<a class="special" href="/"><span>Example site</span></a>}, - link_to('/', class: "special") { content_tag(:span, 'Example site') } + link_to("/", class: "special") { content_tag(:span, "Example site") } end def test_link_tag_using_block_and_hash assert_dom_equal( %{<a href="/"><span>Example site</span></a>}, - link_to(url_hash) { content_tag(:span, 'Example site') } + link_to(url_hash) { content_tag(:span, "Example site") } ) end @@ -410,7 +478,7 @@ class UrlHelperTest < ActiveSupport::TestCase end def test_current_page_with_http_head_method - @request = request_for_url("/", :method => :head) + @request = request_for_url("/", method: :head) assert current_page?(url_hash) assert current_page?("http://www.example.com/") end @@ -438,19 +506,19 @@ class UrlHelperTest < ActiveSupport::TestCase def test_current_page_with_not_get_verb @request = request_for_url("/events", method: :post) - assert !current_page?('/events') + assert !current_page?("/events") end def test_current_page_with_escaped_params @request = request_for_url("/category/administra%c3%a7%c3%a3o") - assert current_page?(controller: 'foo', action: 'category', category: 'administração') + assert current_page?(controller: "foo", action: "category", category: "administração") end def test_current_page_with_escaped_params_with_different_encoding @request = request_for_url("/") @request.stub(:path, "/category/administra%c3%a7%c3%a3o".force_encoding(Encoding::ASCII_8BIT)) do - assert current_page?(:controller => 'foo', :action => 'category', category: 'administração') + assert current_page?(controller: "foo", action: "category", category: "administração") assert current_page?("http://www.example.com/category/administra%c3%a7%c3%a3o") end end @@ -458,7 +526,13 @@ class UrlHelperTest < ActiveSupport::TestCase def test_current_page_with_double_escaped_params @request = request_for_url("/category/administra%c3%a7%c3%a3o?callback_url=http%3a%2f%2fexample.com%2ffoo") - assert current_page?(controller: 'foo', action: 'category', category: 'administração', callback_url: 'http://example.com/foo') + assert current_page?(controller: "foo", action: "category", category: "administração", callback_url: "http://example.com/foo") + end + + def test_current_page_with_trailing_slash + @request = request_for_url("/posts") + + assert current_page?("/posts/") end def test_link_unless_current @@ -479,7 +553,7 @@ class UrlHelperTest < ActiveSupport::TestCase @request = request_for_url("/?order=desc&page=1") assert_equal "Showing", - link_to_unless_current("Showing", hash_for(order: 'desc', page: '1')) + link_to_unless_current("Showing", hash_for(order: "desc", page: "1")) assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/?order=desc&page=1") @@ -535,13 +609,13 @@ class UrlHelperTest < ActiveSupport::TestCase assert_dom_equal( %{<a href="mailto:me@example.com?body=This%20is%20the%20body%20of%20the%20message.&subject=This%20is%20an%20example%20email">My email</a>}, - mail_to("me@example.com", "My email", cc: '', bcc: '', subject: "This is an example email", body: "This is the body of the message.") + mail_to("me@example.com", "My email", cc: "", bcc: "", subject: "This is an example email", body: "This is the body of the message.") ) end def test_mail_to_with_img assert_dom_equal %{<a href="mailto:feedback@example.com"><img src="/feedback.png" /></a>}, - mail_to('feedback@example.com', raw('<img src="/feedback.png" />')) + mail_to("feedback@example.com", raw('<img src="/feedback.png" />')) end def test_mail_to_with_html_safe_string @@ -564,22 +638,22 @@ class UrlHelperTest < ActiveSupport::TestCase def test_mail_to_with_block assert_dom_equal %{<a href="mailto:me@example.com"><span>Email me</span></a>}, - mail_to('me@example.com') { content_tag(:span, 'Email me') } + mail_to("me@example.com") { content_tag(:span, "Email me") } end def test_mail_to_with_block_and_options assert_dom_equal %{<a class="special" href="mailto:me@example.com?cc=ccaddress%40example.com"><span>Email me</span></a>}, - mail_to('me@example.com', cc: "ccaddress@example.com", class: "special") { content_tag(:span, 'Email me') } + mail_to("me@example.com", cc: "ccaddress@example.com", class: "special") { content_tag(:span, "Email me") } end def test_mail_to_does_not_modify_html_options_hash - options = { class: 'special' } - mail_to 'me@example.com', 'ME!', options - assert_equal({ class: 'special' }, options) + options = { class: "special" } + mail_to "me@example.com", "ME!", options + assert_equal({ class: "special" }, options) end def protect_against_forgery? - self.request_forgery + request_forgery end def form_authenticity_token(*args) @@ -592,8 +666,8 @@ class UrlHelperTest < ActiveSupport::TestCase private def sort_query_string_params(uri) - path, qs = uri.split('?') - qs = qs.split('&').sort.join('&') if qs + path, qs = uri.split("?") + qs = qs.split("&").sort.join("&") if qs qs ? "#{path}?#{qs}" : path end end @@ -601,32 +675,34 @@ end class UrlHelperControllerTest < ActionController::TestCase class UrlHelperController < ActionController::Base test_routes do - get 'url_helper_controller_test/url_helper/show/:id', - to: 'url_helper_controller_test/url_helper#show', + get "url_helper_controller_test/url_helper/show/:id", + to: "url_helper_controller_test/url_helper#show", as: :show - get 'url_helper_controller_test/url_helper/profile/:name', - to: 'url_helper_controller_test/url_helper#show', + get "url_helper_controller_test/url_helper/profile/:name", + to: "url_helper_controller_test/url_helper#show", as: :profile - get 'url_helper_controller_test/url_helper/show_named_route', - to: 'url_helper_controller_test/url_helper#show_named_route', + get "url_helper_controller_test/url_helper/show_named_route", + to: "url_helper_controller_test/url_helper#show_named_route", as: :show_named_route - get "/:controller(/:action(/:id))" + ActiveSupport::Deprecation.silence do + get "/:controller(/:action(/:id))" + end - get 'url_helper_controller_test/url_helper/normalize_recall_params', + get "url_helper_controller_test/url_helper/normalize_recall_params", to: UrlHelperController.action(:normalize_recall), as: :normalize_recall_params - get '/url_helper_controller_test/url_helper/override_url_helper/default', - to: 'url_helper_controller_test/url_helper#override_url_helper', + get "/url_helper_controller_test/url_helper/override_url_helper/default", + to: "url_helper_controller_test/url_helper#override_url_helper", as: :override_url_helper end def show if params[:name] - render inline: 'ok' + render inline: "ok" else redirect_to profile_path(params[:id]) end @@ -641,23 +717,23 @@ class UrlHelperControllerTest < ActionController::TestCase end def nil_url_for - render inline: '<%= url_for(nil) %>' + render inline: "<%= url_for(nil) %>" end def normalize_recall_params - render inline: '<%= normalize_recall_params_path %>' + render inline: "<%= normalize_recall_params_path %>" end def recall_params_not_changed - render inline: '<%= url_for(action: :show_url_for) %>' + render inline: "<%= url_for(action: :show_url_for) %>" end def override_url_helper - render inline: '<%= override_url_helper_path %>' + render inline: "<%= override_url_helper_path %>" end def override_url_helper_path - '/url_helper_controller_test/url_helper/override_url_helper/override' + "/url_helper_controller_test/url_helper/override_url_helper/override" end helper_method :override_url_helper_path end @@ -666,58 +742,58 @@ class UrlHelperControllerTest < ActionController::TestCase def test_url_for_shows_only_path get :show_url_for - assert_equal '/url_helper_controller_test/url_helper/show_url_for', @response.body + assert_equal "/url_helper_controller_test/url_helper/show_url_for", @response.body end def test_named_route_url_shows_host_and_path - get :show_named_route, params: { kind: 'url' } - assert_equal 'http://test.host/url_helper_controller_test/url_helper/show_named_route', + get :show_named_route, params: { kind: "url" } + assert_equal "http://test.host/url_helper_controller_test/url_helper/show_named_route", @response.body end def test_named_route_path_shows_only_path - get :show_named_route, params: { kind: 'path' } - assert_equal '/url_helper_controller_test/url_helper/show_named_route', @response.body + get :show_named_route, params: { kind: "path" } + assert_equal "/url_helper_controller_test/url_helper/show_named_route", @response.body end def test_url_for_nil_returns_current_path get :nil_url_for - assert_equal '/url_helper_controller_test/url_helper/nil_url_for', @response.body + assert_equal "/url_helper_controller_test/url_helper/nil_url_for", @response.body end def test_named_route_should_show_host_and_path_using_controller_default_url_options class << @controller def default_url_options - { host: 'testtwo.host' } + { host: "testtwo.host" } end end - get :show_named_route, params: { kind: 'url' } - assert_equal 'http://testtwo.host/url_helper_controller_test/url_helper/show_named_route', @response.body + get :show_named_route, params: { kind: "url" } + assert_equal "http://testtwo.host/url_helper_controller_test/url_helper/show_named_route", @response.body end def test_recall_params_should_be_normalized get :normalize_recall_params - assert_equal '/url_helper_controller_test/url_helper/normalize_recall_params', @response.body + assert_equal "/url_helper_controller_test/url_helper/normalize_recall_params", @response.body end def test_recall_params_should_not_be_changed get :recall_params_not_changed - assert_equal '/url_helper_controller_test/url_helper/show_url_for', @response.body + assert_equal "/url_helper_controller_test/url_helper/show_url_for", @response.body end def test_recall_params_should_normalize_id - get :show, params: { id: '123' } + get :show, params: { id: "123" } assert_equal 302, @response.status - assert_equal 'http://test.host/url_helper_controller_test/url_helper/profile/123', @response.location + assert_equal "http://test.host/url_helper_controller_test/url_helper/profile/123", @response.location - get :show, params: { name: '123' } - assert_equal 'ok', @response.body + get :show, params: { name: "123" } + assert_equal "ok", @response.body end def test_url_helper_can_be_overridden get :override_url_helper - assert_equal '/url_helper_controller_test/url_helper/override_url_helper/override', @response.body + assert_equal "/url_helper_controller_test/url_helper/override_url_helper/override", @response.body end end diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md index 229ef03879..5e8d8cb5c9 100644 --- a/activejob/CHANGELOG.md +++ b/activejob/CHANGELOG.md @@ -1,146 +1,41 @@ -* Change the default adapter from inline to async. It's a better default as tests will then not mistakenly - come to rely on behavior happening synchronously. This is especially important with things like jobs kicked off - in Active Record lifecycle callbacks. +* Removed deprecated support to passing the adapter class to `.queue_adapter`. - *DHH* - - -## Rails 5.0.0.beta2 (February 01, 2016) ## - -* No changes. - - -## Rails 5.0.0.beta1 (December 18, 2015) ## - -* Fixed serializing `:at` option for `assert_enqueued_with` - and `assert_performed_with`. - - *Wojciech WnÄ™trzak* - -* Support passing array to `assert_enqueued_jobs` in `:only` option. - - *Wojciech WnÄ™trzak* - -* Add job priorities to Active Job. - - *wvengen* - -* Implement a simple `AsyncJob` processor and associated `AsyncAdapter` that - queue jobs to a `concurrent-ruby` thread pool. - - *Jerry D'Antonio* - -* Implement `provider_job_id` for `queue_classic` adapter. This requires the - latest, currently unreleased, version of queue_classic. - - *Yves Senn* - -* `assert_enqueued_with` and `assert_performed_with` now returns the matched - job instance for further assertions. - - *Jean Boussier* - -* Include `I18n.locale` into job serialization/deserialization and use it around - `perform`. - - Fixes #20799. - - *Johannes Opper* - -* Allow `DelayedJob`, `Sidekiq`, `qu`, and `que` to report the job id back to - `ActiveJob::Base` as `provider_job_id`. - - Fixes #18821. - - *Kevin Deisz*, *Jeroen van Baarsen* + *Rafael Mendonça França* -* `assert_enqueued_jobs` and `assert_performed_jobs` in block form use the - given number as expected value. This makes the error message much easier to - understand. +* Removed deprecated `#original_exception` in `ActiveJob::DeserializationError`. - *y-yagi* + *Rafael Mendonça França* -* A generated job now inherits from `app/jobs/application_job.rb` by default. +* Added instance variable `@queue` to JobWrapper. - *Jeroen van Baarsen* + This will fix issues in [resque-scheduler](https://github.com/resque/resque-scheduler) `#job_to_hash` method, + so we can use `#enqueue_delayed_selection`, `#remove_delayed` method in resque-scheduler smoothly. -* Add an `:only` option to `perform_enqueued_jobs` to filter jobs based on - type. + *mu29* - This allows specific jobs to be tested, while preventing others from - being performed unnecessarily. +* Yield the job instance so you have access to things like `job.arguments` on the custom logic after retries fail. - Example: - - def test_hello_job - assert_performed_jobs 1, only: HelloJob do - HelloJob.perform_later('jeremy') - LoggingJob.perform_later - end - end - - An array may also be specified, to support testing multiple jobs. - - Example: - - def test_hello_and_logging_jobs - assert_nothing_raised do - assert_performed_jobs 2, only: [HelloJob, LoggingJob] do - HelloJob.perform_later('jeremy') - LoggingJob.perform_later('stewie') - RescueJob.perform_later('david') - end - end - end - - Fixes #18802. - - *Michael Ryan* - -* Allow keyword arguments to be used with Active Job. - - Fixes #18741. + *DHH* - *Sean Griffin* +* Added declarative exception handling via `ActiveJob::Base.retry_on` and `ActiveJob::Base.discard_on`. -* Add `:only` option to `assert_enqueued_jobs`, to check the number of times - a specific kind of job is enqueued. + Examples: - Example: + class RemoteServiceJob < ActiveJob::Base + retry_on CustomAppException # defaults to 3s wait, 5 attempts + retry_on AnotherCustomAppException, wait: ->(executions) { executions * 2 } + retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3 + retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 10 + discard_on ActiveJob::DeserializationError - def test_logging_job - assert_enqueued_jobs 1, only: LoggingJob do - LoggingJob.perform_later - HelloJob.perform_later('jeremy') + def perform(*args) + # Might raise CustomAppException or AnotherCustomAppException for something domain specific + # Might raise ActiveRecord::Deadlocked when a local db deadlock is detected + # Might raise Net::OpenTimeout when the remote service is down end end - *George Claghorn* - -* `ActiveJob::Base.deserialize` delegates to the job class. - - Since `ActiveJob::Base#deserialize` can be overridden by subclasses (like - `ActiveJob::Base#serialize`) this allows jobs to attach arbitrary metadata - when they get serialized and read it back when they get performed. - - Example: - - class DeliverWebhookJob < ActiveJob::Base - def serialize - super.merge('attempt_number' => (@attempt_number || 0) + 1) - end - - def deserialize(job_data) - super - @attempt_number = job_data['attempt_number'] - end - - rescue_from(TimeoutError) do |exception| - raise exception if @attempt_number > 5 - retry_job(wait: 10) - end - end + *DHH* - *Isaac Seymour* -Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activejob/CHANGELOG.md) for previous changes. +Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/activejob/CHANGELOG.md) for previous changes. diff --git a/activejob/Rakefile b/activejob/Rakefile index 2a853b4b6b..3953116061 100644 --- a/activejob/Rakefile +++ b/activejob/Rakefile @@ -1,41 +1,41 @@ -require 'rake/testtask' +require "rake/testtask" -ACTIVEJOB_ADAPTERS = %w(async inline delayed_job qu que queue_classic resque sidekiq sneakers sucker_punch backburner test) +#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) task default: :test -task test: 'test:default' +task test: "test:default" task :package -task "package:clean" namespace :test do - desc 'Run all adapter tests' + desc "Run all adapter tests" task :default do run_without_aborting ACTIVEJOB_ADAPTERS.map { |a| "test:#{a}" } end - desc 'Run all adapter tests in isolation' + desc "Run all adapter tests in isolation" task :isolated do run_without_aborting ACTIVEJOB_ADAPTERS.map { |a| "test:isolated:#{a}" } end - desc 'Run integration tests for all adapters' + desc "Run integration tests for all adapters" task :integration do - run_without_aborting (ACTIVEJOB_ADAPTERS - ['test']).map { |a| "test:integration:#{a}" } + run_without_aborting (ACTIVEJOB_ADAPTERS - ["test"]).map { |a| "test:integration:#{a}" } end - task 'env:integration' do - ENV['AJ_INTEGRATION_TESTS'] = "1" + task "env:integration" do + ENV["AJ_INTEGRATION_TESTS"] = "1" end ACTIVEJOB_ADAPTERS.each do |adapter| - task("env:#{adapter}") { ENV['AJ_ADAPTER'] = adapter } + task("env:#{adapter}") { ENV["AJ_ADAPTER"] = adapter } Rake::TestTask.new(adapter => "test:env:#{adapter}") do |t| t.description = "Run adapter tests for #{adapter}" - t.libs << 'test' - t.test_files = FileList['test/cases/**/*_test.rb'] + t.libs << "test" + t.test_files = FileList["test/cases/**/*_test.rb"] t.verbose = true t.warning = false t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) @@ -45,16 +45,16 @@ namespace :test do task adapter => "test:env:#{adapter}" do dir = File.dirname(__FILE__) Dir.glob("#{dir}/test/cases/**/*_test.rb").all? do |file| - sh(Gem.ruby, '-w', "-I#{dir}/lib", "-I#{dir}/test", file) - end or raise 'Failures' + sh(Gem.ruby, "-w", "-I#{dir}/lib", "-I#{dir}/test", file) + end || raise("Failures") end end namespace :integration do - Rake::TestTask.new(adapter => ["test:env:#{adapter}", 'test:env:integration']) do |t| + Rake::TestTask.new(adapter => ["test:env:#{adapter}", "test:env:integration"]) do |t| t.description = "Run integration tests for #{adapter}" - t.libs << 'test' - t.test_files = FileList['test/integration/**/*_test.rb'] + t.libs << "test" + t.test_files = FileList["test/integration/**/*_test.rb"] t.verbose = true t.warning = false t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) diff --git a/activejob/activejob.gemspec b/activejob/activejob.gemspec index bc1671b508..2547e91262 100644 --- a/activejob/activejob.gemspec +++ b/activejob/activejob.gemspec @@ -1,23 +1,23 @@ -version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip +version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY - s.name = 'activejob' + s.name = "activejob" s.version = version - s.summary = 'Job framework with pluggable queues.' - s.description = 'Declare job classes that can be run by a variety of queueing backends.' + s.summary = "Job framework with pluggable queues." + s.description = "Declare job classes that can be run by a variety of queueing backends." - s.required_ruby_version = '>= 2.2.2' + s.required_ruby_version = ">= 2.2.2" - s.license = 'MIT' + s.license = "MIT" - s.author = 'David Heinemeier Hansson' - s.email = 'david@loudthinking.com' - s.homepage = 'http://www.rubyonrails.org' + s.author = "David Heinemeier Hansson" + s.email = "david@loudthinking.com" + s.homepage = "http://rubyonrails.org" - s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.md', 'lib/**/*'] - s.require_path = 'lib' + s.files = Dir["CHANGELOG.md", "MIT-LICENSE", "README.md", "lib/**/*"] + s.require_path = "lib" - s.add_dependency 'activesupport', version - s.add_dependency 'globalid', '>= 0.3.6' + s.add_dependency "activesupport", version + s.add_dependency "globalid", ">= 0.3.6" end diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb index f7e05f7cd3..20ca7085c6 100644 --- a/activejob/lib/active_job.rb +++ b/activejob/lib/active_job.rb @@ -21,10 +21,10 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ -require 'active_support' -require 'active_support/rails' -require 'active_job/version' -require 'global_id' +require "active_support" +require "active_support/rails" +require "active_job/version" +require "global_id" module ActiveJob extend ActiveSupport::Autoload @@ -32,7 +32,6 @@ module ActiveJob autoload :Base autoload :QueueAdapters autoload :ConfiguredJob - autoload :AsyncJob autoload :TestCase autoload :TestHelper end diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb index 33bd5b4eb3..523a0e7f33 100644 --- a/activejob/lib/active_job/arguments.rb +++ b/activejob/lib/active_job/arguments.rb @@ -1,30 +1,18 @@ -require 'active_support/core_ext/hash' +require "active_support/core_ext/hash" module ActiveJob # Raised when an exception is raised during job arguments deserialization. # # Wraps the original exception raised as +cause+. class DeserializationError < StandardError - def initialize(e = nil) #:nodoc: - if e - ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \ - "Exceptions will automatically capture the original exception.", caller) - end - + def initialize #:nodoc: super("Error while trying to deserialize arguments: #{$!.message}") set_backtrace $!.backtrace end - - # The original exception that was raised during deserialization of job - # arguments. - def original_exception - ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller) - cause - end end # Raised when an unsupported argument type is set as a job argument. We - # currently support NilClass, Fixnum, Float, String, TrueClass, FalseClass, + # currently support NilClass, Integer, Fixnum, Float, String, TrueClass, FalseClass, # Bignum, BigDecimal, and objects that can be represented as GlobalIDs (ex: Active Record). # Raised if you set the key for a Hash something else than a string or # a symbol. Also raised when trying to serialize an object which can't be @@ -34,7 +22,8 @@ module ActiveJob module Arguments extend self # :nodoc: - TYPE_WHITELIST = [ NilClass, Fixnum, Float, String, TrueClass, FalseClass, Bignum, BigDecimal ] + TYPE_WHITELIST = [ NilClass, String, Integer, Float, BigDecimal, TrueClass, FalseClass ] + TYPE_WHITELIST.push(Fixnum, Bignum) unless 1.class == Integer # Serializes a set of arguments. Whitelisted types are returned # as-is. Arrays/Hashes are serialized element by element. @@ -54,11 +43,11 @@ module ActiveJob private # :nodoc: - GLOBALID_KEY = '_aj_globalid'.freeze + GLOBALID_KEY = "_aj_globalid".freeze # :nodoc: - SYMBOL_KEYS_KEY = '_aj_symbol_keys'.freeze + SYMBOL_KEYS_KEY = "_aj_symbol_keys".freeze # :nodoc: - WITH_INDIFFERENT_ACCESS_KEY = '_aj_hash_with_indifferent_access'.freeze + WITH_INDIFFERENT_ACCESS_KEY = "_aj_hash_with_indifferent_access".freeze private_constant :GLOBALID_KEY, :SYMBOL_KEYS_KEY, :WITH_INDIFFERENT_ACCESS_KEY def serialize_argument(argument) @@ -103,7 +92,7 @@ module ActiveJob end def serialized_global_id?(hash) - hash.size == 1 and hash.include?(GLOBALID_KEY) + hash.size == 1 && hash.include?(GLOBALID_KEY) end def deserialize_global_id(hash) diff --git a/activejob/lib/active_job/async_job.rb b/activejob/lib/active_job/async_job.rb deleted file mode 100644 index 417ace21d2..0000000000 --- a/activejob/lib/active_job/async_job.rb +++ /dev/null @@ -1,77 +0,0 @@ -require 'concurrent/map' -require 'concurrent/scheduled_task' -require 'concurrent/executor/thread_pool_executor' -require 'concurrent/utility/processor_counter' - -module ActiveJob - # == Active Job Async Job - # - # When enqueuing jobs with Async Job each job will be executed asynchronously - # on a +concurrent-ruby+ thread pool. All job data is retained in memory. - # Because job data is not saved to a persistent datastore there is no - # additional infrastructure needed and jobs process quickly. The lack of - # persistence, however, means that all unprocessed jobs will be lost on - # application restart. Therefore in-memory queue adapters are unsuitable for - # most production environments but are excellent for development and testing. - # - # Read more about Concurrent Ruby {here}[https://github.com/ruby-concurrency/concurrent-ruby]. - # - # To use Async Job set the queue_adapter config to +:async+. - # - # Rails.application.config.active_job.queue_adapter = :async - # - # Async Job supports job queues specified with +queue_as+. Queues are created - # automatically as needed and each has its own thread pool. - class AsyncJob - - DEFAULT_EXECUTOR_OPTIONS = { - min_threads: [2, Concurrent.processor_count].max, - max_threads: Concurrent.processor_count * 10, - auto_terminate: true, - idletime: 60, # 1 minute - max_queue: 0, # unlimited - fallback_policy: :caller_runs # shouldn't matter -- 0 max queue - }.freeze - - QUEUES = Concurrent::Map.new do |hash, queue_name| #:nodoc: - hash.compute_if_absent(queue_name) { ActiveJob::AsyncJob.create_thread_pool } - end - - class << self - # Forces jobs to process immediately when testing the Active Job gem. - # This should only be called from within unit tests. - def perform_immediately! #:nodoc: - @perform_immediately = true - end - - # Allows jobs to run asynchronously when testing the Active Job gem. - # This should only be called from within unit tests. - def perform_asynchronously! #:nodoc: - @perform_immediately = false - end - - def create_thread_pool #:nodoc: - if @perform_immediately - Concurrent::ImmediateExecutor.new - else - Concurrent::ThreadPoolExecutor.new(DEFAULT_EXECUTOR_OPTIONS) - end - end - - def enqueue(job_data, queue: 'default') #:nodoc: - QUEUES[queue].post(job_data) { |job| ActiveJob::Base.execute(job) } - end - - def enqueue_at(job_data, timestamp, queue: 'default') #:nodoc: - delay = timestamp - Time.current.to_f - if delay > 0 - Concurrent::ScheduledTask.execute(delay, args: [job_data], executor: QUEUES[queue]) do |job| - ActiveJob::Base.execute(job) - end - else - enqueue(job_data, queue: queue) - end - end - end - end -end diff --git a/activejob/lib/active_job/base.rb b/activejob/lib/active_job/base.rb index ff5c69ddc6..18e8641e50 100644 --- a/activejob/lib/active_job/base.rb +++ b/activejob/lib/active_job/base.rb @@ -1,12 +1,13 @@ -require 'active_job/core' -require 'active_job/queue_adapter' -require 'active_job/queue_name' -require 'active_job/queue_priority' -require 'active_job/enqueuing' -require 'active_job/execution' -require 'active_job/callbacks' -require 'active_job/logging' -require 'active_job/translation' +require "active_job/core" +require "active_job/queue_adapter" +require "active_job/queue_name" +require "active_job/queue_priority" +require "active_job/enqueuing" +require "active_job/execution" +require "active_job/callbacks" +require "active_job/exceptions" +require "active_job/logging" +require "active_job/translation" module ActiveJob #:nodoc: # = Active Job @@ -62,6 +63,7 @@ module ActiveJob #:nodoc: include Enqueuing include Execution include Callbacks + include Exceptions include Logging include Translation diff --git a/activejob/lib/active_job/callbacks.rb b/activejob/lib/active_job/callbacks.rb index 2b6149e84e..d5b17de8b5 100644 --- a/activejob/lib/active_job/callbacks.rb +++ b/activejob/lib/active_job/callbacks.rb @@ -1,4 +1,4 @@ -require 'active_support/callbacks' +require "active_support/callbacks" module ActiveJob # = Active Job Callbacks @@ -17,6 +17,11 @@ module ActiveJob extend ActiveSupport::Concern include ActiveSupport::Callbacks + class << self + include ActiveSupport::Callbacks + define_callbacks :execute + end + included do define_callbacks :perform define_callbacks :enqueue @@ -121,8 +126,8 @@ module ActiveJob set_callback(:enqueue, :after, *filters, &blk) end - # Defines a callback that will get called before and after the - # job is enqueued. + # Defines a callback that will get called around the enqueueing + # of the job. # # class VideoProcessJob < ActiveJob::Base # queue_as :default diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb index 19b900a285..a338061766 100644 --- a/activejob/lib/active_job/core.rb +++ b/activejob/lib/active_job/core.rb @@ -24,6 +24,9 @@ module ActiveJob # ID optionally provided by adapter attr_accessor :provider_job_id + # Number of times this job has been executed (which increments on every retry, like after an exception). + attr_accessor :executions + # I18n.locale to be used during the job. attr_accessor :locale end @@ -33,7 +36,7 @@ module ActiveJob module ClassMethods # Creates a new job instance from a hash created with +serialize+ def deserialize(job_data) - job = job_data['job_class'].constantize.new + job = job_data["job_class"].constantize.new job.deserialize(job_data) job end @@ -68,18 +71,20 @@ module ActiveJob @job_id = SecureRandom.uuid @queue_name = self.class.queue_name @priority = self.class.priority + @executions = 0 end # Returns a hash with the job data that can safely be passed to the # queueing adapter. def serialize { - 'job_class' => self.class.name, - 'job_id' => job_id, - 'queue_name' => queue_name, - 'priority' => priority, - 'arguments' => serialize_arguments(arguments), - 'locale' => I18n.locale + "job_class" => self.class.name, + "job_id" => job_id, + "queue_name" => queue_name, + "priority" => priority, + "arguments" => serialize_arguments(arguments), + "executions" => executions, + "locale" => I18n.locale.to_s } end @@ -104,11 +109,13 @@ module ActiveJob # end # end def deserialize(job_data) - self.job_id = job_data['job_id'] - self.queue_name = job_data['queue_name'] - self.priority = job_data['priority'] - self.serialized_arguments = job_data['arguments'] - self.locale = job_data['locale'] || I18n.locale + self.job_id = job_data["job_id"] + self.provider_job_id = job_data["provider_job_id"] + self.queue_name = job_data["queue_name"] + self.priority = job_data["priority"] + self.serialized_arguments = job_data["arguments"] + self.executions = job_data["executions"] + self.locale = job_data["locale"] || I18n.locale.to_s end private diff --git a/activejob/lib/active_job/enqueuing.rb b/activejob/lib/active_job/enqueuing.rb index 22154457fd..18051a7d65 100644 --- a/activejob/lib/active_job/enqueuing.rb +++ b/activejob/lib/active_job/enqueuing.rb @@ -1,7 +1,7 @@ -require 'active_job/arguments' +require "active_job/arguments" module ActiveJob - # Provides behavior for enqueuing and retrying jobs. + # Provides behavior for enqueuing jobs. module Enqueuing extend ActiveSupport::Concern @@ -24,31 +24,6 @@ module ActiveJob end end - # Reschedules the job to be re-executed. This is useful in combination - # with the +rescue_from+ option. When you rescue an exception from your job - # you can ask Active Job to retry performing your job. - # - # ==== Options - # * <tt>:wait</tt> - Enqueues the job with the specified delay - # * <tt>:wait_until</tt> - Enqueues the job at the time specified - # * <tt>:queue</tt> - Enqueues the job on the specified queue - # * <tt>:priority</tt> - Enqueues the job with the specified priority - # - # ==== Examples - # - # class SiteScrapperJob < ActiveJob::Base - # rescue_from(ErrorLoadingSite) do - # retry_job queue: :low_priority - # end - # - # def perform(*args) - # # raise ErrorLoadingSite if cannot scrape - # end - # end - def retry_job(options={}) - enqueue options - end - # Enqueues the job to be performed by the queue adapter. # # ==== Options @@ -70,8 +45,8 @@ module ActiveJob self.queue_name = self.class.queue_name_from_part(options[:queue]) if options[:queue] self.priority = options[:priority].to_i if options[:priority] run_callbacks :enqueue do - if self.scheduled_at - self.class.queue_adapter.enqueue_at self, self.scheduled_at + if scheduled_at + self.class.queue_adapter.enqueue_at self, scheduled_at else self.class.queue_adapter.enqueue self end diff --git a/activejob/lib/active_job/exceptions.rb b/activejob/lib/active_job/exceptions.rb new file mode 100644 index 0000000000..c759068f9e --- /dev/null +++ b/activejob/lib/active_job/exceptions.rb @@ -0,0 +1,122 @@ +require "active_support/core_ext/numeric/time" + +module ActiveJob + # Provides behavior for retrying and discarding jobs on exceptions. + module Exceptions + extend ActiveSupport::Concern + + module ClassMethods + # Catch the exception and reschedule job for re-execution after so many seconds, for a specific number of attempts. + # If the exception keeps getting raised beyond the specified number of attempts, the exception is allowed to + # bubble up to the underlying queuing system, which may have its own retry mechanism or place it in a + # holding queue for inspection. + # + # You can also pass a block that'll be invoked if the retry attempts fail for custom logic rather than letting + # the exception bubble up. This block is yielded with the job instance as the first and the error instance as the second parameter. + # + # ==== Options + # * <tt>:wait</tt> - Re-enqueues the job with a delay specified either in seconds (default: 3 seconds), + # as a computing proc that the number of executions so far as an argument, or as a symbol reference of + # <tt>:exponentially_longer</tt>, which applies the wait algorithm of <tt>(executions ** 4) + 2</tt> + # (first wait 3s, then 18s, then 83s, etc) + # * <tt>:attempts</tt> - Re-enqueues the job the specified number of times (default: 5 attempts) + # * <tt>:queue</tt> - Re-enqueues the job on a different queue + # * <tt>:priority</tt> - Re-enqueues the job with a different priority + # + # ==== Examples + # + # class RemoteServiceJob < ActiveJob::Base + # retry_on CustomAppException # defaults to 3s wait, 5 attempts + # retry_on AnotherCustomAppException, wait: ->(executions) { executions * 2 } + # retry_on(YetAnotherCustomAppException) do |job, exception| + # ExceptionNotifier.caught(exception) + # end + # retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3 + # retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 10 + # + # def perform(*args) + # # Might raise CustomAppException, AnotherCustomAppException, or YetAnotherCustomAppException for something domain specific + # # Might raise ActiveRecord::Deadlocked when a local db deadlock is detected + # # Might raise Net::OpenTimeout when the remote service is down + # end + # end + def retry_on(exception, wait: 3.seconds, attempts: 5, queue: nil, priority: nil) + rescue_from exception do |error| + if executions < attempts + logger.error "Retrying #{self.class} in #{wait} seconds, due to a #{exception}. The original exception was #{error.cause.inspect}." + retry_job wait: determine_delay(wait), queue: queue, priority: priority + else + if block_given? + yield self, exception + else + logger.error "Stopped retrying #{self.class} due to a #{exception}, which reoccurred on #{executions} attempts. The original exception was #{error.cause.inspect}." + raise error + end + end + end + end + + # Discard the job with no attempts to retry, if the exception is raised. This is useful when the subject of the job, + # like an Active Record, is no longer available, and the job is thus no longer relevant. + # + # ==== Example + # + # class SearchIndexingJob < ActiveJob::Base + # discard_on ActiveJob::DeserializationError + # + # def perform(record) + # # Will raise ActiveJob::DeserializationError if the record can't be deserialized + # end + # end + def discard_on(exception) + rescue_from exception do |error| + logger.error "Discarded #{self.class} due to a #{exception}. The original exception was #{error.cause.inspect}." + end + end + end + + # Reschedules the job to be re-executed. This is useful in combination + # with the +rescue_from+ option. When you rescue an exception from your job + # you can ask Active Job to retry performing your job. + # + # ==== Options + # * <tt>:wait</tt> - Enqueues the job with the specified delay in seconds + # * <tt>:wait_until</tt> - Enqueues the job at the time specified + # * <tt>:queue</tt> - Enqueues the job on the specified queue + # * <tt>:priority</tt> - Enqueues the job with the specified priority + # + # ==== Examples + # + # class SiteScraperJob < ActiveJob::Base + # rescue_from(ErrorLoadingSite) do + # retry_job queue: :low_priority + # end + # + # def perform(*args) + # # raise ErrorLoadingSite if cannot scrape + # end + # end + def retry_job(options = {}) + enqueue options + end + + private + def determine_delay(seconds_or_duration_or_algorithm) + case seconds_or_duration_or_algorithm + when :exponentially_longer + (executions ** 4) + 2 + when ActiveSupport::Duration + duration = seconds_or_duration_or_algorithm + duration.to_i + when Integer + seconds = seconds_or_duration_or_algorithm + seconds + when Proc + algorithm = seconds_or_duration_or_algorithm + algorithm.call(executions) + else + raise "Couldn't determine a delay based on #{seconds_or_duration_or_algorithm.inspect}" + end + end + end +end diff --git a/activejob/lib/active_job/execution.rb b/activejob/lib/active_job/execution.rb index 79d232da4a..94d30c8eaf 100644 --- a/activejob/lib/active_job/execution.rb +++ b/activejob/lib/active_job/execution.rb @@ -1,5 +1,5 @@ -require 'active_support/rescuable' -require 'active_job/arguments' +require "active_support/rescuable" +require "active_job/arguments" module ActiveJob module Execution @@ -17,8 +17,10 @@ module ActiveJob end def execute(job_data) #:nodoc: - job = deserialize(job_data) - job.perform_now + ActiveJob::Callbacks.run_callbacks(:execute) do + job = deserialize(job_data) + job.perform_now + end end end @@ -29,10 +31,13 @@ module ActiveJob def perform_now deserialize_arguments_if_needed run_callbacks :perform do + # Guard against jobs that were persisted before we started counting executions by zeroing out nil counters + self.executions = (executions || 0) + 1 + perform(*arguments) end rescue => exception - rescue_with_handler(exception) || raise(exception) + rescue_with_handler(exception) || raise end def perform(*) diff --git a/activejob/lib/active_job/gem_version.rb b/activejob/lib/active_job/gem_version.rb index bc88221027..0d50c27938 100644 --- a/activejob/lib/active_job/gem_version.rb +++ b/activejob/lib/active_job/gem_version.rb @@ -6,9 +6,9 @@ module ActiveJob module VERSION MAJOR = 5 - MINOR = 0 + MINOR = 1 TINY = 0 - PRE = "beta2" + PRE = "alpha" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activejob/lib/active_job/logging.rb b/activejob/lib/active_job/logging.rb index 605057d1e8..aa97ab2e22 100644 --- a/activejob/lib/active_job/logging.rb +++ b/activejob/lib/active_job/logging.rb @@ -1,7 +1,7 @@ -require 'active_support/core_ext/hash/transform_values' -require 'active_support/core_ext/string/filters' -require 'active_support/tagged_logging' -require 'active_support/logger' +require "active_support/core_ext/hash/transform_values" +require "active_support/core_ext/string/filters" +require "active_support/tagged_logging" +require "active_support/logger" module ActiveJob module Logging #:nodoc: @@ -18,7 +18,7 @@ module ActiveJob around_perform do |job, block, _| tag_logger(job.class.name, job.job_id) do - payload = {adapter: job.class.queue_adapter, job: job} + payload = { adapter: job.class.queue_adapter, job: job } ActiveSupport::Notifications.instrument("perform_start.active_job", payload.dup) ActiveSupport::Notifications.instrument("perform.active_job", payload) do block.call @@ -41,7 +41,7 @@ module ActiveJob def tag_logger(*tags) if logger.respond_to?(:tagged) tags.unshift "ActiveJob" unless logger_tagged_by_active_job? - ActiveJob::Base.logger.tagged(*tags){ yield } + logger.tagged(*tags) { yield } else yield end @@ -51,70 +51,70 @@ module ActiveJob logger.formatter.current_tags.include?("ActiveJob") end - class LogSubscriber < ActiveSupport::LogSubscriber #:nodoc: - def enqueue(event) - info do - job = event.payload[:job] - "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)}" + args_info(job) + class LogSubscriber < ActiveSupport::LogSubscriber #:nodoc: + def enqueue(event) + info do + job = event.payload[:job] + "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)}" + args_info(job) + end end - end - def enqueue_at(event) - info do - job = event.payload[:job] - "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(event)}" + args_info(job) + def enqueue_at(event) + info do + job = event.payload[:job] + "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(event)}" + args_info(job) + end end - end - def perform_start(event) - info do - job = event.payload[:job] - "Performing #{job.class.name} from #{queue_name(event)}" + args_info(job) + def perform_start(event) + info do + job = event.payload[:job] + "Performing #{job.class.name} from #{queue_name(event)}" + args_info(job) + end end - end - def perform(event) - info do - job = event.payload[:job] - "Performed #{job.class.name} from #{queue_name(event)} in #{event.duration.round(2)}ms" + def perform(event) + info do + job = event.payload[:job] + "Performed #{job.class.name} from #{queue_name(event)} in #{event.duration.round(2)}ms" + end end - end - private - def queue_name(event) - event.payload[:adapter].class.name.demodulize.remove('Adapter') + "(#{event.payload[:job].queue_name})" - end + private + def queue_name(event) + event.payload[:adapter].class.name.demodulize.remove("Adapter") + "(#{event.payload[:job].queue_name})" + end - def args_info(job) - if job.arguments.any? - ' with arguments: ' + - job.arguments.map { |arg| format(arg).inspect }.join(', ') - else - '' + def args_info(job) + if job.arguments.any? + " with arguments: " + + job.arguments.map { |arg| format(arg).inspect }.join(", ") + else + "" + end end - end - def format(arg) - case arg - when Hash - arg.transform_values { |value| format(value) } - when Array - arg.map { |value| format(value) } - when GlobalID::Identification - arg.to_global_id rescue arg - else - arg + def format(arg) + case arg + when Hash + arg.transform_values { |value| format(value) } + when Array + arg.map { |value| format(value) } + when GlobalID::Identification + arg.to_global_id rescue arg + else + arg + end end - end - def scheduled_at(event) - Time.at(event.payload[:job].scheduled_at).utc - end + def scheduled_at(event) + Time.at(event.payload[:job].scheduled_at).utc + end - def logger - ActiveJob::Base.logger - end - end + def logger + ActiveJob::Base.logger + end + end end end diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb index 72e4ebf935..bcc555d33e 100644 --- a/activejob/lib/active_job/queue_adapter.rb +++ b/activejob/lib/active_job/queue_adapter.rb @@ -1,6 +1,5 @@ -require 'active_job/queue_adapters/inline_adapter' -require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/string/inflections' +require "active_job/queue_adapters/inline_adapter" +require "active_support/core_ext/string/inflections" module ActiveJob # The <tt>ActiveJob::QueueAdapter</tt> module is used to load the @@ -30,34 +29,24 @@ module ActiveJob private - def interpret_adapter(name_or_adapter_or_class) - case name_or_adapter_or_class - when Symbol, String - ActiveJob::QueueAdapters.lookup(name_or_adapter_or_class).new - else - if queue_adapter?(name_or_adapter_or_class) - name_or_adapter_or_class - elsif queue_adapter_class?(name_or_adapter_or_class) - ActiveSupport::Deprecation.warn "Passing an adapter class is deprecated " \ - "and will be removed in Rails 5.1. Please pass an adapter name " \ - "(.queue_adapter = :#{name_or_adapter_or_class.name.demodulize.remove('Adapter').underscore}) " \ - "or an instance (.queue_adapter = #{name_or_adapter_or_class.name}.new) instead." - name_or_adapter_or_class.new + def interpret_adapter(name_or_adapter_or_class) + case name_or_adapter_or_class + when Symbol, String + ActiveJob::QueueAdapters.lookup(name_or_adapter_or_class).new else - raise ArgumentError + if queue_adapter?(name_or_adapter_or_class) + name_or_adapter_or_class + else + raise ArgumentError + end end end - end - - QUEUE_ADAPTER_METHODS = [:enqueue, :enqueue_at].freeze - def queue_adapter?(object) - QUEUE_ADAPTER_METHODS.all? { |meth| object.respond_to?(meth) } - end + QUEUE_ADAPTER_METHODS = [:enqueue, :enqueue_at].freeze - def queue_adapter_class?(object) - object.is_a?(Class) && QUEUE_ADAPTER_METHODS.all? { |meth| object.public_method_defined?(meth) } - end + def queue_adapter?(object) + QUEUE_ADAPTER_METHODS.all? { |meth| object.respond_to?(meth) } + end end end end diff --git a/activejob/lib/active_job/queue_adapters.rb b/activejob/lib/active_job/queue_adapters.rb index 2c5039ef4d..c8eedb6156 100644 --- a/activejob/lib/active_job/queue_adapters.rb +++ b/activejob/lib/active_job/queue_adapters.rb @@ -8,7 +8,7 @@ module ActiveJob # * {Qu}[https://github.com/bkeepers/qu] # * {Que}[https://github.com/chanks/que] # * {queue_classic}[https://github.com/QueueClassic/queue_classic] - # * {Resque 1.x}[https://github.com/resque/resque/tree/1-x-stable] + # * {Resque}[https://github.com/resque/resque] # * {Sidekiq}[http://sidekiq.org] # * {Sneakers}[https://github.com/jondot/sneakers] # * {Sucker Punch}[https://github.com/brandonhilkert/sucker_punch] @@ -33,7 +33,8 @@ module ActiveJob # # ==== Async # - # Yes: The Queue Adapter runs the jobs in a separate or forked process. + # Yes: The Queue Adapter has the ability to run the job in a non-blocking manner. + # It either runs on a separate or forked process, or on a different thread. # # No: The job is run in the same process. # @@ -120,7 +121,7 @@ module ActiveJob autoload :SuckerPunchAdapter autoload :TestAdapter - ADAPTER = 'Adapter'.freeze + ADAPTER = "Adapter".freeze private_constant :ADAPTER class << self diff --git a/activejob/lib/active_job/queue_adapters/async_adapter.rb b/activejob/lib/active_job/queue_adapters/async_adapter.rb index 3d3c749883..e2bff9e646 100644 --- a/activejob/lib/active_job/queue_adapters/async_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/async_adapter.rb @@ -1,22 +1,113 @@ -require 'active_job/async_job' +require "securerandom" +require "concurrent/scheduled_task" +require "concurrent/executor/thread_pool_executor" +require "concurrent/utility/processor_counter" module ActiveJob module QueueAdapters # == Active Job Async adapter # - # When enqueuing jobs with the Async adapter the job will be executed - # asynchronously using {AsyncJob}[http://api.rubyonrails.org/classes/ActiveJob/AsyncJob.html]. + # The Async adapter runs jobs with an in-process thread pool. # - # To use +AsyncJob+ set the queue_adapter config to +:async+. + # This is the default queue adapter. It's well-suited for dev/test since + # it doesn't need an external infrastructure, but it's a poor fit for + # production since it drops pending jobs on restart. # - # Rails.application.config.active_job.queue_adapter = :async + # To use this adapter, set queue adapter to +:async+: + # + # config.active_job.queue_adapter = :async + # + # To configure the adapter's thread pool, instantiate the adapter and + # pass your own config: + # + # config.active_job.queue_adapter = ActiveJob::QueueAdapters::AsyncAdapter.new \ + # min_threads: 1, + # max_threads: 2 * Concurrent.processor_count, + # idletime: 600.seconds + # + # The adapter uses a {Concurrent Ruby}[https://github.com/ruby-concurrency/concurrent-ruby] thread pool to schedule and execute + # jobs. Since jobs share a single thread pool, long-running jobs will block + # short-lived jobs. Fine for dev/test; bad for production. class AsyncAdapter + # See {Concurrent::ThreadPoolExecutor}[http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadPoolExecutor.html] for executor options. + def initialize(**executor_options) + @scheduler = Scheduler.new(**executor_options) + end + def enqueue(job) #:nodoc: - ActiveJob::AsyncJob.enqueue(job.serialize, queue: job.queue_name) + @scheduler.enqueue JobWrapper.new(job), queue_name: job.queue_name end def enqueue_at(job, timestamp) #:nodoc: - ActiveJob::AsyncJob.enqueue_at(job.serialize, timestamp, queue: job.queue_name) + @scheduler.enqueue_at JobWrapper.new(job), timestamp, queue_name: job.queue_name + end + + # Gracefully stop processing jobs. Finishes in-progress work and handles + # any new jobs following the executor's fallback policy (`caller_runs`). + # Waits for termination by default. Pass `wait: false` to continue. + def shutdown(wait: true) #:nodoc: + @scheduler.shutdown wait: wait + end + + # Used for our test suite. + def immediate=(immediate) #:nodoc: + @scheduler.immediate = immediate + end + + # Note that we don't actually need to serialize the jobs since we're + # performing them in-process, but we do so anyway for parity with other + # adapters and deployment environments. Otherwise, serialization bugs + # may creep in undetected. + class JobWrapper #:nodoc: + def initialize(job) + job.provider_job_id = SecureRandom.uuid + @job_data = job.serialize + end + + def perform + Base.execute @job_data + end + end + + class Scheduler #:nodoc: + DEFAULT_EXECUTOR_OPTIONS = { + min_threads: 0, + max_threads: Concurrent.processor_count, + auto_terminate: true, + idletime: 60, # 1 minute + max_queue: 0, # unlimited + fallback_policy: :caller_runs # shouldn't matter -- 0 max queue + }.freeze + + attr_accessor :immediate + + def initialize(**options) + self.immediate = false + @immediate_executor = Concurrent::ImmediateExecutor.new + @async_executor = Concurrent::ThreadPoolExecutor.new(DEFAULT_EXECUTOR_OPTIONS.merge(options)) + end + + def enqueue(job, queue_name:) + executor.post(job, &:perform) + end + + def enqueue_at(job, timestamp, queue_name:) + delay = timestamp - Time.current.to_f + if delay > 0 + Concurrent::ScheduledTask.execute(delay, args: [job], executor: executor, &:perform) + else + enqueue(job, queue_name: queue_name) + end + end + + def shutdown(wait: true) + @async_executor.shutdown + @async_executor.wait_for_termination if wait + end + + def executor + immediate ? @immediate_executor : @async_executor + end end end end diff --git a/activejob/lib/active_job/queue_adapters/backburner_adapter.rb b/activejob/lib/active_job/queue_adapters/backburner_adapter.rb index 17703e3e41..e3eccce62b 100644 --- a/activejob/lib/active_job/queue_adapters/backburner_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/backburner_adapter.rb @@ -1,4 +1,4 @@ -require 'backburner' +require "backburner" module ActiveJob module QueueAdapters diff --git a/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb b/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb index 0a785fad3b..83ad2e767d 100644 --- a/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb @@ -1,4 +1,4 @@ -require 'delayed_job' +require "delayed_job" module ActiveJob module QueueAdapters diff --git a/activejob/lib/active_job/queue_adapters/qu_adapter.rb b/activejob/lib/active_job/queue_adapters/qu_adapter.rb index 0e198922fc..20cc97ebc7 100644 --- a/activejob/lib/active_job/queue_adapters/qu_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/qu_adapter.rb @@ -1,4 +1,4 @@ -require 'qu' +require "qu" module ActiveJob module QueueAdapters @@ -20,7 +20,7 @@ module ActiveJob qu_job = Qu::Payload.new(klass: JobWrapper, args: [job.serialize]).tap do |payload| payload.instance_variable_set(:@queue, job.queue_name) end.push - + # qu_job can be nil depending on the configured backend job.provider_job_id = qu_job.id unless qu_job.nil? qu_job diff --git a/activejob/lib/active_job/queue_adapters/que_adapter.rb b/activejob/lib/active_job/queue_adapters/que_adapter.rb index ab13689747..0e698f0d79 100644 --- a/activejob/lib/active_job/queue_adapters/que_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/que_adapter.rb @@ -1,4 +1,4 @@ -require 'que' +require "que" module ActiveJob module QueueAdapters diff --git a/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb b/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb index 0ee41407d8..1115eb88ae 100644 --- a/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb @@ -1,4 +1,4 @@ -require 'queue_classic' +require "queue_classic" module ActiveJob module QueueAdapters @@ -26,9 +26,9 @@ module ActiveJob def enqueue_at(job, timestamp) #:nodoc: queue = build_queue(job.queue_name) unless queue.respond_to?(:enqueue_at) - raise NotImplementedError, 'To be able to schedule jobs with queue_classic ' \ - 'the QC::Queue needs to respond to `enqueue_at(timestamp, method, *args)`. ' \ - 'You can implement this yourself or you can use the queue_classic-later gem.' + raise NotImplementedError, "To be able to schedule jobs with queue_classic " \ + "the QC::Queue needs to respond to `enqueue_at(timestamp, method, *args)`. " \ + "You can implement this yourself or you can use the queue_classic-later gem." end qc_job = queue.enqueue_at(timestamp, "#{JobWrapper.name}.perform", job.serialize) job.provider_job_id = qc_job["id"] if qc_job.is_a?(Hash) diff --git a/activejob/lib/active_job/queue_adapters/resque_adapter.rb b/activejob/lib/active_job/queue_adapters/resque_adapter.rb index 417854afd8..2df157ef89 100644 --- a/activejob/lib/active_job/queue_adapters/resque_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/resque_adapter.rb @@ -1,12 +1,12 @@ -require 'resque' -require 'active_support/core_ext/enumerable' -require 'active_support/core_ext/array/access' +require "resque" +require "active_support/core_ext/enumerable" +require "active_support/core_ext/array/access" begin - require 'resque-scheduler' + require "resque-scheduler" rescue LoadError begin - require 'resque_scheduler' + require "resque_scheduler" rescue LoadError false end @@ -27,6 +27,7 @@ module ActiveJob # Rails.application.config.active_job.queue_adapter = :resque class ResqueAdapter def enqueue(job) #:nodoc: + JobWrapper.instance_variable_set(:@queue, job.queue_name) Resque.enqueue_to job.queue_name, JobWrapper, job.serialize end diff --git a/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb b/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb index c321776bf5..895cc1f981 100644 --- a/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb @@ -1,4 +1,4 @@ -require 'sidekiq' +require "sidekiq" module ActiveJob module QueueAdapters @@ -18,26 +18,26 @@ module ActiveJob def enqueue(job) #:nodoc: #Sidekiq::Client does not support symbols as keys job.provider_job_id = Sidekiq::Client.push \ - 'class' => JobWrapper, - 'wrapped' => job.class.to_s, - 'queue' => job.queue_name, - 'args' => [ job.serialize ] + "class" => JobWrapper, + "wrapped" => job.class.to_s, + "queue" => job.queue_name, + "args" => [ job.serialize ] end def enqueue_at(job, timestamp) #:nodoc: job.provider_job_id = Sidekiq::Client.push \ - 'class' => JobWrapper, - 'wrapped' => job.class.to_s, - 'queue' => job.queue_name, - 'args' => [ job.serialize ], - 'at' => timestamp + "class" => JobWrapper, + "wrapped" => job.class.to_s, + "queue" => job.queue_name, + "args" => [ job.serialize ], + "at" => timestamp end class JobWrapper #:nodoc: include Sidekiq::Worker def perform(job_data) - Base.execute job_data + Base.execute job_data.merge("provider_job_id" => jid) end end end diff --git a/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb b/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb index d78bdecdcb..f00acfc04a 100644 --- a/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb @@ -1,5 +1,5 @@ -require 'sneakers' -require 'monitor' +require "sneakers" +require "monitor" module ActiveJob module QueueAdapters @@ -33,7 +33,7 @@ module ActiveJob class JobWrapper #:nodoc: include Sneakers::Worker - from_queue 'default' + from_queue "default" def work(msg) job_data = ActiveSupport::JSON.decode(msg) diff --git a/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb b/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb index 311109e958..dd59a79813 100644 --- a/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb @@ -1,11 +1,11 @@ -require 'sucker_punch' +require "sucker_punch" module ActiveJob module QueueAdapters # == Sucker Punch adapter for Active Job # # Sucker Punch is a single-process Ruby asynchronous processing library. - # This reduces the cost of of hosting on a service like Heroku along + # This reduces the cost of hosting on a service like Heroku along # with the memory footprint of having to maintain additional jobs if # hosting on a dedicated server. All queues can run within a # single application (eg. Rails, Sinatra, etc.) process. @@ -31,7 +31,7 @@ module ActiveJob delay = timestamp - Time.current.to_f JobWrapper.perform_in delay, job.serialize else - raise NotImplementedError, 'sucker_punch 1.0 does not support `enqueued_at`. Please upgrade to version ~> 2.0.0 to enable this behavior.' + raise NotImplementedError, "sucker_punch 1.0 does not support `enqueued_at`. Please upgrade to version ~> 2.0.0 to enable this behavior." end end diff --git a/activejob/lib/active_job/queue_adapters/test_adapter.rb b/activejob/lib/active_job/queue_adapters/test_adapter.rb index 9b7b7139f4..da042cfebf 100644 --- a/activejob/lib/active_job/queue_adapters/test_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/test_adapter.rb @@ -39,22 +39,22 @@ module ActiveJob private - def job_to_hash(job, extras = {}) - { job: job.class, args: job.serialize.fetch('arguments'), queue: job.queue_name }.merge!(extras) - end + def job_to_hash(job, extras = {}) + { job: job.class, args: job.serialize.fetch("arguments"), queue: job.queue_name }.merge!(extras) + end - def enqueue_or_perform(perform, job, job_data) - if perform - performed_jobs << job_data - Base.execute job.serialize - else - enqueued_jobs << job_data + def enqueue_or_perform(perform, job, job_data) + if perform + performed_jobs << job_data + Base.execute job.serialize + else + enqueued_jobs << job_data + end end - end - def filtered?(job) - filter && !Array(filter).include?(job.class) - end + def filtered?(job) + filter && !Array(filter).include?(job.class) + end end end end diff --git a/activejob/lib/active_job/queue_name.rb b/activejob/lib/active_job/queue_name.rb index 65786a49ff..143fac9888 100644 --- a/activejob/lib/active_job/queue_name.rb +++ b/activejob/lib/active_job/queue_name.rb @@ -36,7 +36,7 @@ module ActiveJob class_attribute :queue_name_delimiter, instance_accessor: false self.queue_name = default_queue_name - self.queue_name_delimiter = '_' # set default delimiter to '_' + self.queue_name_delimiter = "_" # set default delimiter to '_' end # Returns the name of the queue the job will be run on. @@ -46,6 +46,5 @@ module ActiveJob end @queue_name end - end end diff --git a/activejob/lib/active_job/queue_priority.rb b/activejob/lib/active_job/queue_priority.rb index 01d84910ff..a48e53b0ef 100644 --- a/activejob/lib/active_job/queue_priority.rb +++ b/activejob/lib/active_job/queue_priority.rb @@ -39,6 +39,5 @@ module ActiveJob end @priority end - end end diff --git a/activejob/lib/active_job/railtie.rb b/activejob/lib/active_job/railtie.rb index b249ec09dd..e4198a40a5 100644 --- a/activejob/lib/active_job/railtie.rb +++ b/activejob/lib/active_job/railtie.rb @@ -1,12 +1,12 @@ -require 'global_id/railtie' -require 'active_job' +require "global_id/railtie" +require "active_job" module ActiveJob # = Active Job Railtie class Railtie < Rails::Railtie # :nodoc: config.active_job = ActiveSupport::OrderedOptions.new - initializer 'active_job.logger' do + initializer "active_job.logger" do ActiveSupport.on_load(:active_job) { self.logger = ::Rails.logger } end @@ -19,5 +19,14 @@ module ActiveJob end end + initializer "active_job.set_reloader_hook" do |app| + ActiveSupport.on_load(:active_job) do + ActiveJob::Callbacks.singleton_class.set_callback(:execute, :around, prepend: true) do |_, inner| + app.reloader.wrap do + inner.call + end + end + end + end end end diff --git a/activejob/lib/active_job/test_case.rb b/activejob/lib/active_job/test_case.rb index d894a7b5cd..a5ec45e4a7 100644 --- a/activejob/lib/active_job/test_case.rb +++ b/activejob/lib/active_job/test_case.rb @@ -1,7 +1,9 @@ -require 'active_support/test_case' +require "active_support/test_case" module ActiveJob class TestCase < ActiveSupport::TestCase include ActiveJob::TestHelper + + ActiveSupport.run_load_hooks(:active_job_test_case, self) end end diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb index ed0c05c1e5..1a8b3375ae 100644 --- a/activejob/lib/active_job/test_helper.rb +++ b/activejob/lib/active_job/test_helper.rb @@ -1,328 +1,348 @@ -require 'active_support/core_ext/class/subclasses' -require 'active_support/core_ext/hash/keys' +require "active_support/core_ext/class/subclasses" +require "active_support/core_ext/hash/keys" module ActiveJob # Provides helper methods for testing Active Job module TestHelper - extend ActiveSupport::Concern + delegate :enqueued_jobs, :enqueued_jobs=, + :performed_jobs, :performed_jobs=, + to: :queue_adapter - included do - def before_setup # :nodoc: - test_adapter = ActiveJob::QueueAdapters::TestAdapter.new + def before_setup # :nodoc: + test_adapter = queue_adapter_for_test - @old_queue_adapters = (ActiveJob::Base.subclasses << 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 + @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 - - clear_enqueued_jobs - clear_performed_jobs - super end - def after_teardown # :nodoc: - super - @old_queue_adapters.each do |(klass, adapter)| - klass.queue_adapter = adapter - end - end + clear_enqueued_jobs + clear_performed_jobs + super + end - # Asserts that the number of enqueued jobs matches the given number. - # - # def test_jobs - # assert_enqueued_jobs 0 - # HelloJob.perform_later('david') - # assert_enqueued_jobs 1 - # HelloJob.perform_later('abdelkader') - # assert_enqueued_jobs 2 - # end - # - # If a block is passed, that block should cause the specified number of - # jobs to be enqueued. - # - # def test_jobs_again - # assert_enqueued_jobs 1 do - # HelloJob.perform_later('cristian') - # end - # - # assert_enqueued_jobs 2 do - # HelloJob.perform_later('aaron') - # HelloJob.perform_later('rafael') - # end - # end - # - # The number of times a specific job is enqueued can be asserted. - # - # def test_logging_job - # assert_enqueued_jobs 1, only: LoggingJob do - # LoggingJob.perform_later - # HelloJob.perform_later('jeremy') - # end - # end - def assert_enqueued_jobs(number, only: nil) - if block_given? - original_count = enqueued_jobs_size(only: only) - yield - new_count = enqueued_jobs_size(only: only) - 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) - assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued" - end + def after_teardown # :nodoc: + super + @old_queue_adapters.each do |(klass, adapter)| + klass.queue_adapter = adapter end + end - # Asserts that no jobs have been enqueued. - # - # def test_jobs - # assert_no_enqueued_jobs - # HelloJob.perform_later('jeremy') - # assert_enqueued_jobs 1 - # end - # - # If a block is passed, that block should not cause any job to be enqueued. - # - # def test_jobs_again - # assert_no_enqueued_jobs do - # # No job should be enqueued from this block - # end - # end - # - # It can be asserted that no jobs of a specific kind are enqueued: - # - # def test_no_logging - # assert_no_enqueued_jobs only: LoggingJob do - # HelloJob.perform_later('jeremy') - # end - # end - # - # Note: This assertion is simply a shortcut for: - # - # assert_enqueued_jobs 0, &block - def assert_no_enqueued_jobs(only: nil, &block) - assert_enqueued_jobs 0, only: only, &block - end + # Specifies the queue adapter to use with all active job test helpers. + # + # Returns an instance of the queue adapter and defaults to + # <tt>ActiveJob::QueueAdapters::TestAdapter</tt>. + # + # Note: The adapter provided by this method must provide some additional + # methods from those expected of a standard <tt>ActiveJob::QueueAdapter</tt> + # in order to be used with the active job test helpers. Refer to + # <tt>ActiveJob::QueueAdapters::TestAdapter</tt>. + def queue_adapter_for_test + ActiveJob::QueueAdapters::TestAdapter.new + end - # Asserts that the number of performed jobs matches the given number. - # If no block is passed, <tt>perform_enqueued_jobs</tt> - # must be called around the job call. - # - # def test_jobs - # assert_performed_jobs 0 - # - # perform_enqueued_jobs do - # HelloJob.perform_later('xavier') - # end - # assert_performed_jobs 1 - # - # perform_enqueued_jobs do - # HelloJob.perform_later('yves') - # assert_performed_jobs 2 - # end - # end - # - # If a block is passed, that block should cause the specified number of - # jobs to be performed. - # - # def test_jobs_again - # assert_performed_jobs 1 do - # HelloJob.perform_later('robin') - # end - # - # assert_performed_jobs 2 do - # HelloJob.perform_later('carlos') - # HelloJob.perform_later('sean') - # end - # end - # - # The block form supports filtering. If the :only option is specified, - # then only the listed job(s) will be performed. - # - # def test_hello_job - # assert_performed_jobs 1, only: HelloJob do - # HelloJob.perform_later('jeremy') - # LoggingJob.perform_later - # end - # end - # - # An array may also be specified, to support testing multiple jobs. - # - # def test_hello_and_logging_jobs - # assert_nothing_raised do - # assert_performed_jobs 2, only: [HelloJob, LoggingJob] do - # HelloJob.perform_later('jeremy') - # LoggingJob.perform_later('stewie') - # RescueJob.perform_later('david') - # end - # end - # end - def assert_performed_jobs(number, only: nil) - if block_given? - original_count = performed_jobs.size - perform_enqueued_jobs(only: only) { yield } - new_count = performed_jobs.size - assert_equal number, new_count - original_count, - "#{number} jobs expected, but #{new_count - original_count} were performed" - else - performed_jobs_size = performed_jobs.size - assert_equal number, performed_jobs_size, "#{number} jobs expected, but #{performed_jobs_size} were performed" - end + # Asserts that the number of enqueued jobs matches the given number. + # + # def test_jobs + # assert_enqueued_jobs 0 + # HelloJob.perform_later('david') + # assert_enqueued_jobs 1 + # HelloJob.perform_later('abdelkader') + # assert_enqueued_jobs 2 + # end + # + # If a block is passed, that block should cause the specified number of + # jobs to be enqueued. + # + # def test_jobs_again + # assert_enqueued_jobs 1 do + # HelloJob.perform_later('cristian') + # end + # + # assert_enqueued_jobs 2 do + # HelloJob.perform_later('aaron') + # HelloJob.perform_later('rafael') + # end + # end + # + # The number of times a specific job is enqueued can be asserted. + # + # def test_logging_job + # assert_enqueued_jobs 1, only: LoggingJob do + # LoggingJob.perform_later + # HelloJob.perform_later('jeremy') + # end + # end + def assert_enqueued_jobs(number, only: nil) + if block_given? + original_count = enqueued_jobs_size(only: only) + yield + new_count = enqueued_jobs_size(only: only) + 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) + assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued" end + end - # Asserts that no jobs have been performed. - # - # def test_jobs - # assert_no_performed_jobs - # - # perform_enqueued_jobs do - # HelloJob.perform_later('matthew') - # assert_performed_jobs 1 - # end - # end - # - # If a block is passed, that block should not cause any job to be performed. - # - # def test_jobs_again - # assert_no_performed_jobs do - # # No job should be performed from this block - # end - # end - # - # The block form supports filtering. If the :only option is specified, - # then only the listed job(s) will be performed. - # - # def test_hello_job - # assert_performed_jobs 1, only: HelloJob do - # HelloJob.perform_later('jeremy') - # LoggingJob.perform_later - # end - # end - # - # An array may also be specified, to support testing multiple jobs. - # - # def test_hello_and_logging_jobs - # assert_nothing_raised do - # assert_performed_jobs 2, only: [HelloJob, LoggingJob] do - # HelloJob.perform_later('jeremy') - # LoggingJob.perform_later('stewie') - # RescueJob.perform_later('david') - # end - # end - # end - # - # Note: This assertion is simply a shortcut for: - # - # assert_performed_jobs 0, &block - def assert_no_performed_jobs(only: nil, &block) - assert_performed_jobs 0, only: only, &block - end + # Asserts that no jobs have been enqueued. + # + # def test_jobs + # assert_no_enqueued_jobs + # HelloJob.perform_later('jeremy') + # assert_enqueued_jobs 1 + # end + # + # If a block is passed, that block should not cause any job to be enqueued. + # + # def test_jobs_again + # assert_no_enqueued_jobs do + # # No job should be enqueued from this block + # end + # end + # + # It can be asserted that no jobs of a specific kind are enqueued: + # + # def test_no_logging + # assert_no_enqueued_jobs only: LoggingJob do + # HelloJob.perform_later('jeremy') + # end + # end + # + # Note: This assertion is simply a shortcut for: + # + # assert_enqueued_jobs 0, &block + def assert_no_enqueued_jobs(only: nil, &block) + assert_enqueued_jobs 0, only: only, &block + end - # Asserts that the job passed in the block has been enqueued with the given arguments. - # - # def test_assert_enqueued_with - # assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low') do - # MyJob.perform_later(1,2,3) - # end - # - # assert_enqueued_with(job: MyJob, at: Date.tomorrow.noon) do - # MyJob.set(wait_until: Date.tomorrow.noon).perform_later - # end - # end - def assert_enqueued_with(args = {}) - original_enqueued_jobs_count = enqueued_jobs.count - args.assert_valid_keys(:job, :args, :at, :queue) - serialized_args = serialize_args_for_assertion(args) - yield - in_block_jobs = enqueued_jobs.drop(original_enqueued_jobs_count) - matching_job = in_block_jobs.find do |job| - serialized_args.all? { |key, value| value == job[key] } - end - assert matching_job, "No enqueued job found with #{args}" - instantiate_job(matching_job) + # Asserts that the number of performed jobs matches the given number. + # If no block is passed, <tt>perform_enqueued_jobs</tt> + # must be called around the job call. + # + # def test_jobs + # assert_performed_jobs 0 + # + # perform_enqueued_jobs do + # HelloJob.perform_later('xavier') + # end + # assert_performed_jobs 1 + # + # perform_enqueued_jobs do + # HelloJob.perform_later('yves') + # assert_performed_jobs 2 + # end + # end + # + # If a block is passed, that block should cause the specified number of + # jobs to be performed. + # + # def test_jobs_again + # assert_performed_jobs 1 do + # HelloJob.perform_later('robin') + # end + # + # assert_performed_jobs 2 do + # HelloJob.perform_later('carlos') + # HelloJob.perform_later('sean') + # end + # end + # + # The block form supports filtering. If the :only option is specified, + # then only the listed job(s) will be performed. + # + # def test_hello_job + # assert_performed_jobs 1, only: HelloJob do + # HelloJob.perform_later('jeremy') + # LoggingJob.perform_later + # end + # end + # + # An array may also be specified, to support testing multiple jobs. + # + # def test_hello_and_logging_jobs + # assert_nothing_raised do + # assert_performed_jobs 2, only: [HelloJob, LoggingJob] do + # HelloJob.perform_later('jeremy') + # LoggingJob.perform_later('stewie') + # RescueJob.perform_later('david') + # end + # end + # end + def assert_performed_jobs(number, only: nil) + if block_given? + original_count = performed_jobs.size + perform_enqueued_jobs(only: only) { yield } + new_count = performed_jobs.size + assert_equal number, new_count - original_count, + "#{number} jobs expected, but #{new_count - original_count} were performed" + else + performed_jobs_size = performed_jobs.size + assert_equal number, performed_jobs_size, "#{number} jobs expected, but #{performed_jobs_size} were performed" end + end - # Asserts that the job passed in the block has been performed with the given arguments. - # - # def test_assert_performed_with - # assert_performed_with(job: MyJob, args: [1,2,3], queue: 'high') do - # MyJob.perform_later(1,2,3) - # end - # - # assert_performed_with(job: MyJob, at: Date.tomorrow.noon) do - # MyJob.set(wait_until: Date.tomorrow.noon).perform_later - # end - # end - def assert_performed_with(args = {}) - original_performed_jobs_count = performed_jobs.count - args.assert_valid_keys(:job, :args, :at, :queue) - serialized_args = serialize_args_for_assertion(args) - perform_enqueued_jobs { yield } - in_block_jobs = performed_jobs.drop(original_performed_jobs_count) - matching_job = in_block_jobs.find do |job| - serialized_args.all? { |key, value| value == job[key] } - end - assert matching_job, "No performed job found with #{args}" - instantiate_job(matching_job) - end + # Asserts that no jobs have been performed. + # + # def test_jobs + # assert_no_performed_jobs + # + # perform_enqueued_jobs do + # HelloJob.perform_later('matthew') + # assert_performed_jobs 1 + # end + # end + # + # If a block is passed, that block should not cause any job to be performed. + # + # def test_jobs_again + # assert_no_performed_jobs do + # # No job should be performed from this block + # end + # end + # + # The block form supports filtering. If the :only option is specified, + # then only the listed job(s) will not be performed. + # + # def test_no_logging + # assert_no_performed_jobs only: LoggingJob do + # HelloJob.perform_later('jeremy') + # end + # end + # + # Note: This assertion is simply a shortcut for: + # + # assert_performed_jobs 0, &block + def assert_no_performed_jobs(only: nil, &block) + assert_performed_jobs 0, only: only, &block + end - def perform_enqueued_jobs(only: nil) - old_perform_enqueued_jobs = queue_adapter.perform_enqueued_jobs - old_perform_enqueued_at_jobs = queue_adapter.perform_enqueued_at_jobs - old_filter = queue_adapter.filter + # Asserts that the job passed in the block has been enqueued with the given arguments. + # + # def test_assert_enqueued_with + # assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low') do + # MyJob.perform_later(1,2,3) + # end + # + # assert_enqueued_with(job: MyJob, at: Date.tomorrow.noon) do + # MyJob.set(wait_until: Date.tomorrow.noon).perform_later + # end + # end + def assert_enqueued_with(args = {}) + original_enqueued_jobs_count = enqueued_jobs.count + args.assert_valid_keys(:job, :args, :at, :queue) + serialized_args = serialize_args_for_assertion(args) + yield + in_block_jobs = enqueued_jobs.drop(original_enqueued_jobs_count) + matching_job = in_block_jobs.find do |job| + serialized_args.all? { |key, value| value == job[key] } + end + assert matching_job, "No enqueued job found with #{args}" + instantiate_job(matching_job) + end - begin - queue_adapter.perform_enqueued_jobs = true - queue_adapter.perform_enqueued_at_jobs = true - queue_adapter.filter = only - yield - ensure - queue_adapter.perform_enqueued_jobs = old_perform_enqueued_jobs - queue_adapter.perform_enqueued_at_jobs = old_perform_enqueued_at_jobs - queue_adapter.filter = old_filter - end + # Asserts that the job passed in the block has been performed with the given arguments. + # + # def test_assert_performed_with + # assert_performed_with(job: MyJob, args: [1,2,3], queue: 'high') do + # MyJob.perform_later(1,2,3) + # end + # + # assert_performed_with(job: MyJob, at: Date.tomorrow.noon) do + # MyJob.set(wait_until: Date.tomorrow.noon).perform_later + # end + # end + def assert_performed_with(args = {}) + original_performed_jobs_count = performed_jobs.count + args.assert_valid_keys(:job, :args, :at, :queue) + serialized_args = serialize_args_for_assertion(args) + perform_enqueued_jobs { yield } + in_block_jobs = performed_jobs.drop(original_performed_jobs_count) + matching_job = in_block_jobs.find do |job| + serialized_args.all? { |key, value| value == job[key] } end + assert matching_job, "No performed job found with #{args}" + instantiate_job(matching_job) + end - def queue_adapter - ActiveJob::Base.queue_adapter + # Performs all enqueued jobs in the duration of the block. + # + # def test_perform_enqueued_jobs + # perform_enqueued_jobs do + # MyJob.perform_later(1, 2, 3) + # end + # assert_performed_jobs 1 + # end + # + # This method also supports filtering. If the +:only+ option is specified, + # then only the listed job(s) will be performed. + # + # 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 + # end + # assert_performed_jobs 1 + # end + def perform_enqueued_jobs(only: nil) + old_perform_enqueued_jobs = queue_adapter.perform_enqueued_jobs + old_perform_enqueued_at_jobs = queue_adapter.perform_enqueued_at_jobs + old_filter = queue_adapter.filter + + begin + queue_adapter.perform_enqueued_jobs = true + queue_adapter.perform_enqueued_at_jobs = true + queue_adapter.filter = only + yield + ensure + queue_adapter.perform_enqueued_jobs = old_perform_enqueued_jobs + queue_adapter.perform_enqueued_at_jobs = old_perform_enqueued_at_jobs + queue_adapter.filter = old_filter end + end - delegate :enqueued_jobs, :enqueued_jobs=, - :performed_jobs, :performed_jobs=, - to: :queue_adapter + # Accesses the queue_adapter set by ActiveJob::Base. + # + # def test_assert_job_has_custom_queue_adapter_set + # assert_instance_of CustomQueueAdapter, HelloJob.queue_adapter + # end + def queue_adapter + ActiveJob::Base.queue_adapter + end - private - def clear_enqueued_jobs # :nodoc: - enqueued_jobs.clear - end + private + def clear_enqueued_jobs # :nodoc: + enqueued_jobs.clear + end - def clear_performed_jobs # :nodoc: - performed_jobs.clear - end + def clear_performed_jobs # :nodoc: + 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 - end + def enqueued_jobs_size(only: nil) # :nodoc: + if only + enqueued_jobs.count { |job| Array(only).include?(job.fetch(:job)) } + else + enqueued_jobs.count end + end - def serialize_args_for_assertion(args) # :nodoc: - 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 + def serialize_args_for_assertion(args) # :nodoc: + 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: - job = payload[:job].new(*payload[:args]) - job.scheduled_at = Time.at(payload[:at]) if payload.key?(:at) - job.queue_name = payload[:queue] - job - end - end + def instantiate_job(payload) # :nodoc: + job = payload[:job].new(*payload[:args]) + job.scheduled_at = Time.at(payload[:at]) if payload.key?(:at) + job.queue_name = payload[:queue] + job + end end end diff --git a/activejob/lib/active_job/version.rb b/activejob/lib/active_job/version.rb index 971ba9fe0c..60b463817f 100644 --- a/activejob/lib/active_job/version.rb +++ b/activejob/lib/active_job/version.rb @@ -1,4 +1,4 @@ -require_relative 'gem_version' +require_relative "gem_version" module ActiveJob # Returns the version of the currently loaded Active Job as a <tt>Gem::Version</tt> diff --git a/activejob/lib/rails/generators/job/job_generator.rb b/activejob/lib/rails/generators/job/job_generator.rb index 2115fb9f71..97c11a9ea6 100644 --- a/activejob/lib/rails/generators/job/job_generator.rb +++ b/activejob/lib/rails/generators/job/job_generator.rb @@ -1,13 +1,13 @@ -require 'rails/generators/named_base' +require "rails/generators/named_base" module Rails # :nodoc: module Generators # :nodoc: class JobGenerator < Rails::Generators::NamedBase # :nodoc: - desc 'This generator creates an active job file at app/jobs' + desc "This generator creates an active job file at app/jobs" - class_option :queue, type: :string, default: 'default', desc: 'The queue name for the generated job' + class_option :queue, type: :string, default: "default", desc: "The queue name for the generated job" - check_class_collision suffix: 'Job' + check_class_collision suffix: "Job" hook_for :test_framework @@ -16,8 +16,23 @@ module Rails # :nodoc: end def create_job_file - template 'job.rb', File.join('app/jobs', class_path, "#{file_name}_job.rb") + 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) + template "application_job.rb", application_job_file_name + end + end end + + private + def application_job_file_name + @application_job_file_name ||= if mountable_engine? + "app/jobs/#{namespaced_path}/application_job.rb" + else + "app/jobs/application_job.rb" + end + end end end end diff --git a/activejob/lib/rails/generators/job/templates/application_job.rb b/activejob/lib/rails/generators/job/templates/application_job.rb new file mode 100644 index 0000000000..f93745a31a --- /dev/null +++ b/activejob/lib/rails/generators/job/templates/application_job.rb @@ -0,0 +1,9 @@ +<% module_namespacing do -%> +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end +<% end -%> diff --git a/activejob/test/adapters/async.rb b/activejob/test/adapters/async.rb index 5fcfb89566..08eb9658cd 100644 --- a/activejob/test/adapters/async.rb +++ b/activejob/test/adapters/async.rb @@ -1,4 +1,2 @@ -require 'active_job/async_job' - ActiveJob::Base.queue_adapter = :async -ActiveJob::AsyncJob.perform_immediately! +ActiveJob::Base.queue_adapter.immediate = true diff --git a/activejob/test/adapters/backburner.rb b/activejob/test/adapters/backburner.rb index 65d05f850b..17ba07b802 100644 --- a/activejob/test/adapters/backburner.rb +++ b/activejob/test/adapters/backburner.rb @@ -1,3 +1,3 @@ -require 'support/backburner/inline' +require "support/backburner/inline" -ActiveJob::Base.queue_adapter = :backburner
\ No newline at end of file +ActiveJob::Base.queue_adapter = :backburner diff --git a/activejob/test/adapters/delayed_job.rb b/activejob/test/adapters/delayed_job.rb index afd9c9deb7..5f0ee2418c 100644 --- a/activejob/test/adapters/delayed_job.rb +++ b/activejob/test/adapters/delayed_job.rb @@ -4,4 +4,3 @@ $LOAD_PATH << File.dirname(__FILE__) + "/../support/delayed_job" Delayed::Worker.delay_jobs = false Delayed::Worker.backend = :test - diff --git a/activejob/test/adapters/inline.rb b/activejob/test/adapters/inline.rb index e0092552c4..24113dfa5a 100644 --- a/activejob/test/adapters/inline.rb +++ b/activejob/test/adapters/inline.rb @@ -1 +1 @@ -ActiveJob::Base.queue_adapter = :inline
\ No newline at end of file +ActiveJob::Base.queue_adapter = :inline diff --git a/activejob/test/adapters/qu.rb b/activejob/test/adapters/qu.rb index 7728c843b4..b89c504091 100644 --- a/activejob/test/adapters/qu.rb +++ b/activejob/test/adapters/qu.rb @@ -1,3 +1,3 @@ -require 'qu-immediate' +require "qu-immediate" ActiveJob::Base.queue_adapter = :qu diff --git a/activejob/test/adapters/que.rb b/activejob/test/adapters/que.rb index e6abc57457..0c49274a4e 100644 --- a/activejob/test/adapters/que.rb +++ b/activejob/test/adapters/que.rb @@ -1,4 +1,4 @@ -require 'support/que/inline' +require "support/que/inline" ActiveJob::Base.queue_adapter = :que Que.mode = :sync diff --git a/activejob/test/adapters/queue_classic.rb b/activejob/test/adapters/queue_classic.rb index ad5ced3cc2..e301a7ca96 100644 --- a/activejob/test/adapters/queue_classic.rb +++ b/activejob/test/adapters/queue_classic.rb @@ -1,2 +1,2 @@ -require 'support/queue_classic/inline' +require "support/queue_classic/inline" ActiveJob::Base.queue_adapter = :queue_classic diff --git a/activejob/test/adapters/sidekiq.rb b/activejob/test/adapters/sidekiq.rb index cd9d2034de..8036331ef1 100644 --- a/activejob/test/adapters/sidekiq.rb +++ b/activejob/test/adapters/sidekiq.rb @@ -1,2 +1,2 @@ -require 'sidekiq/testing/inline' +require "sidekiq/testing/inline" ActiveJob::Base.queue_adapter = :sidekiq diff --git a/activejob/test/adapters/sneakers.rb b/activejob/test/adapters/sneakers.rb index 204166a700..159c0d1906 100644 --- a/activejob/test/adapters/sneakers.rb +++ b/activejob/test/adapters/sneakers.rb @@ -1,2 +1,2 @@ -require 'support/sneakers/inline' +require "support/sneakers/inline" ActiveJob::Base.queue_adapter = :sneakers diff --git a/activejob/test/adapters/sucker_punch.rb b/activejob/test/adapters/sucker_punch.rb index d2d1712946..5661b6ee92 100644 --- a/activejob/test/adapters/sucker_punch.rb +++ b/activejob/test/adapters/sucker_punch.rb @@ -1,2 +1,2 @@ -require 'sucker_punch/testing/inline' +require "sucker_punch/testing/inline" ActiveJob::Base.queue_adapter = :sucker_punch diff --git a/activejob/test/cases/adapter_test.rb b/activejob/test/cases/adapter_test.rb index 6d75ae9a7c..caf5d44a5e 100644 --- a/activejob/test/cases/adapter_test.rb +++ b/activejob/test/cases/adapter_test.rb @@ -1,4 +1,4 @@ -require 'helper' +require "helper" class AdapterTest < ActiveSupport::TestCase test "should load #{ENV['AJ_ADAPTER']} adapter" do diff --git a/activejob/test/cases/argument_serialization_test.rb b/activejob/test/cases/argument_serialization_test.rb index eb8ad185aa..7934d8e556 100644 --- a/activejob/test/cases/argument_serialization_test.rb +++ b/activejob/test/cases/argument_serialization_test.rb @@ -1,25 +1,25 @@ -require 'helper' -require 'active_job/arguments' -require 'models/person' -require 'active_support/core_ext/hash/indifferent_access' -require 'jobs/kwargs_job' +require "helper" +require "active_job/arguments" +require "models/person" +require "active_support/core_ext/hash/indifferent_access" +require "jobs/kwargs_job" class ArgumentSerializationTest < ActiveSupport::TestCase setup do - @person = Person.find('5') + @person = Person.find("5") end [ nil, 1, 1.0, 1_000_000_000_000_000_000_000, - 'a', true, false, BigDecimal.new(5), - [ 1, 'a' ], - { 'a' => 1 } + "a", true, false, BigDecimal.new(5), + [ 1, "a" ], + { "a" => 1 } ].each do |arg| - test "serializes #{arg.class} verbatim" do + test "serializes #{arg.class} - #{arg} verbatim" do assert_arguments_unchanged arg end end - [ :a, Object.new, self, Person.find('5').to_gid ].each do |arg| + [ :a, Object.new, self, Person.find("5").to_gid ].each do |arg| test "does not serialize #{arg.class}" do assert_raises ActiveJob::SerializationError do ActiveJob::Arguments.serialize [ arg ] @@ -31,22 +31,22 @@ class ArgumentSerializationTest < ActiveSupport::TestCase end end - test 'should convert records to Global IDs' do + test "should convert records to Global IDs" do assert_arguments_roundtrip [@person] end - test 'should dive deep into arrays and hashes' do + test "should dive deep into arrays and hashes" do assert_arguments_roundtrip [3, [@person]] - assert_arguments_roundtrip [{ 'a' => @person }] + assert_arguments_roundtrip [{ "a" => @person }] end - test 'should maintain string and symbol keys' do + test "should maintain string and symbol keys" do assert_arguments_roundtrip([a: 1, "b" => 2]) end - test 'should maintain hash with indifferent access' do + test "should maintain hash with indifferent access" do symbol_key = { a: 1 } - string_key = { 'a' => 1 } + string_key = { "a" => 1 } indifferent_access = { a: 1 }.with_indifferent_access assert_not_instance_of ActiveSupport::HashWithIndifferentAccess, perform_round_trip([symbol_key]).first @@ -54,25 +54,26 @@ class ArgumentSerializationTest < ActiveSupport::TestCase assert_instance_of ActiveSupport::HashWithIndifferentAccess, perform_round_trip([indifferent_access]).first end - test 'should disallow non-string/symbol hash keys' do + test "should disallow non-string/symbol hash keys" do assert_raises ActiveJob::SerializationError do ActiveJob::Arguments.serialize [ { 1 => 2 } ] end assert_raises ActiveJob::SerializationError do - ActiveJob::Arguments.serialize [ { :a => [{ 2 => 3 }] } ] - end - - assert_raises ActiveJob::SerializationError do - ActiveJob::Arguments.serialize [ '_aj_globalid' => 1 ] + ActiveJob::Arguments.serialize [ { a: [{ 2 => 3 }] } ] end + end - assert_raises ActiveJob::SerializationError do - ActiveJob::Arguments.serialize [ :_aj_globalid => 1 ] + test "should not allow reserved hash keys" do + ["_aj_globalid", :_aj_globalid, "_aj_symbol_keys", :_aj_symbol_keys, + "_aj_hash_with_indifferent_access", :_aj_hash_with_indifferent_access].each do |key| + assert_raises ActiveJob::SerializationError do + ActiveJob::Arguments.serialize [key => 1] + end end end - test 'should not allow non-primitive objects' do + test "should not allow non-primitive objects" do assert_raises ActiveJob::SerializationError do ActiveJob::Arguments.serialize [Object.new] end @@ -82,17 +83,17 @@ class ArgumentSerializationTest < ActiveSupport::TestCase end end - test 'allows for keyword arguments' do + test "allows for keyword arguments" do KwargsJob.perform_later(argument: 2) assert_equal "Job with argument: 2", JobBuffer.last_value end - test 'raises a friendly SerializationError for records without ids' do + test "raises a friendly SerializationError for records without ids" do err = assert_raises ActiveJob::SerializationError do ActiveJob::Arguments.serialize [Person.new(nil)] end - assert_match 'Unable to serialize Person without an id.', err.message + assert_match "Unable to serialize Person without an id.", err.message end private diff --git a/activejob/test/cases/async_job_test.rb b/activejob/test/cases/async_job_test.rb deleted file mode 100644 index 2642cfc608..0000000000 --- a/activejob/test/cases/async_job_test.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'helper' -require 'jobs/hello_job' -require 'jobs/queue_as_job' - -class AsyncJobTest < ActiveSupport::TestCase - def using_async_adapter? - ActiveJob::Base.queue_adapter.is_a? ActiveJob::QueueAdapters::AsyncAdapter - end - - setup do - ActiveJob::AsyncJob.perform_asynchronously! - end - - teardown do - ActiveJob::AsyncJob::QUEUES.clear - ActiveJob::AsyncJob.perform_immediately! - end - - test "#create_thread_pool returns a thread_pool" do - thread_pool = ActiveJob::AsyncJob.create_thread_pool - assert thread_pool.is_a? Concurrent::ExecutorService - assert_not thread_pool.is_a? Concurrent::ImmediateExecutor - end - - test "#create_thread_pool returns an ImmediateExecutor after #perform_immediately! is called" do - ActiveJob::AsyncJob.perform_immediately! - thread_pool = ActiveJob::AsyncJob.create_thread_pool - assert thread_pool.is_a? Concurrent::ImmediateExecutor - end - - test "enqueuing without specifying a queue uses the default queue" do - skip unless using_async_adapter? - HelloJob.perform_later - assert ActiveJob::AsyncJob::QUEUES.key? 'default' - end - - test "enqueuing to a queue that does not exist creates the queue" do - skip unless using_async_adapter? - QueueAsJob.perform_later - assert ActiveJob::AsyncJob::QUEUES.key? QueueAsJob::MY_QUEUE.to_s - end -end diff --git a/activejob/test/cases/callbacks_test.rb b/activejob/test/cases/callbacks_test.rb index 9af2380767..8d1c9c3058 100644 --- a/activejob/test/cases/callbacks_test.rb +++ b/activejob/test/cases/callbacks_test.rb @@ -1,10 +1,10 @@ -require 'helper' -require 'jobs/callback_job' +require "helper" +require "jobs/callback_job" -require 'active_support/core_ext/object/inclusion' +require "active_support/core_ext/object/inclusion" class CallbacksTest < ActiveSupport::TestCase - test 'perform callbacks' do + test "perform callbacks" do performed_callback_job = CallbackJob.new("A-JOB-ID") performed_callback_job.perform_now assert "CallbackJob ran before_perform".in? performed_callback_job.history @@ -13,7 +13,7 @@ class CallbacksTest < ActiveSupport::TestCase assert "CallbackJob ran around_perform_stop".in? performed_callback_job.history end - test 'enqueue callbacks' do + test "enqueue callbacks" do enqueued_callback_job = CallbackJob.perform_later assert "CallbackJob ran before_enqueue".in? enqueued_callback_job.history assert "CallbackJob ran after_enqueue".in? enqueued_callback_job.history diff --git a/activejob/test/cases/exceptions_test.rb b/activejob/test/cases/exceptions_test.rb new file mode 100644 index 0000000000..dbe55f7430 --- /dev/null +++ b/activejob/test/cases/exceptions_test.rb @@ -0,0 +1,107 @@ +require "helper" +require "jobs/retry_job" + +class ExceptionsTest < ActiveJob::TestCase + setup do + JobBuffer.clear + skip if ActiveJob::Base.queue_adapter.is_a?(ActiveJob::QueueAdapters::InlineAdapter) + end + + test "successfully retry job throwing exception against defaults" do + perform_enqueued_jobs do + RetryJob.perform_later "DefaultsError", 5 + + assert_equal [ + "Raised DefaultsError for the 1st time", + "Raised DefaultsError for the 2nd time", + "Raised DefaultsError for the 3rd time", + "Raised DefaultsError for the 4th time", + "Successfully completed job" ], JobBuffer.values + end + end + + test "successfully retry job throwing exception against higher limit" do + perform_enqueued_jobs do + RetryJob.perform_later "ShortWaitTenAttemptsError", 9 + assert_equal 9, JobBuffer.values.count + end + end + + test "failed retry job when exception kept occurring against defaults" do + perform_enqueued_jobs do + begin + RetryJob.perform_later "DefaultsError", 6 + assert_equal "Raised DefaultsError for the 5th time", JobBuffer.last_value + rescue DefaultsError + pass + end + end + end + + test "failed retry job when exception kept occurring against higher limit" do + perform_enqueued_jobs do + begin + RetryJob.perform_later "ShortWaitTenAttemptsError", 11 + assert_equal "Raised ShortWaitTenAttemptsError for the 10th time", JobBuffer.last_value + rescue ShortWaitTenAttemptsError + pass + end + end + end + + test "discard job" do + perform_enqueued_jobs do + RetryJob.perform_later "DiscardableError", 2 + assert_equal "Raised DiscardableError for the 1st time", JobBuffer.last_value + end + end + + test "custom handling of job that exceeds retry attempts" do + perform_enqueued_jobs do + RetryJob.perform_later "CustomCatchError", 6 + assert_equal "Dealt with a job that failed to retry in a custom way after 6 attempts", JobBuffer.last_value + end + end + + test "long wait job" do + travel_to Time.now + + perform_enqueued_jobs do + assert_performed_with at: (Time.now + 3600.seconds).to_i do + RetryJob.perform_later "LongWaitError", 5 + end + end + end + + test "exponentially retrying job" do + travel_to Time.now + + perform_enqueued_jobs do + assert_performed_with at: (Time.now + 3.seconds).to_i do + assert_performed_with at: (Time.now + 18.seconds).to_i do + assert_performed_with at: (Time.now + 83.seconds).to_i do + assert_performed_with at: (Time.now + 258.seconds).to_i do + RetryJob.perform_later "ExponentialWaitTenAttemptsError", 5 + end + end + end + end + end + end + + test "custom wait retrying job" do + travel_to Time.now + + perform_enqueued_jobs do + assert_performed_with at: (Time.now + 2.seconds).to_i do + assert_performed_with at: (Time.now + 4.seconds).to_i do + assert_performed_with at: (Time.now + 6.seconds).to_i do + assert_performed_with at: (Time.now + 8.seconds).to_i do + RetryJob.perform_later "CustomWaitTenAttemptsError", 5 + end + end + end + end + end + end +end diff --git a/activejob/test/cases/job_serialization_test.rb b/activejob/test/cases/job_serialization_test.rb index 229517774e..3f2e300dfa 100644 --- a/activejob/test/cases/job_serialization_test.rb +++ b/activejob/test/cases/job_serialization_test.rb @@ -1,7 +1,8 @@ -require 'helper' -require 'jobs/gid_job' -require 'jobs/hello_job' -require 'models/person' +require "helper" +require "jobs/gid_job" +require "jobs/hello_job" +require "models/person" +require "json" class JobSerializationTest < ActiveSupport::TestCase setup do @@ -9,24 +10,38 @@ class JobSerializationTest < ActiveSupport::TestCase @person = Person.find(5) end - test 'serialize job with gid' do + test "serialize job with gid" do GidJob.perform_later @person assert_equal "Person with ID: 5", JobBuffer.last_value end - test 'serialize includes current locale' do - assert_equal :en, HelloJob.new.serialize['locale'] + test "serialize includes current locale" do + assert_equal "en", HelloJob.new.serialize["locale"] end - test 'deserialize sets locale' do + test "serialize and deserialize are symmetric" do + # Round trip a job in memory only + h1 = HelloJob.new + h1.deserialize(h1.serialize) + + # Now verify it's identical to a JSON round trip. + # We don't want any non-native JSON elements in the job hash, + # like symbols. + payload = JSON.dump(h1.serialize) + h2 = HelloJob.new + h2.deserialize(JSON.load(payload)) + assert_equal h1.serialize, h2.serialize + end + + test "deserialize sets locale" do job = HelloJob.new - job.deserialize 'locale' => :es - assert_equal :es, job.locale + job.deserialize "locale" => "es" + assert_equal "es", job.locale end - test 'deserialize sets default locale' do + test "deserialize sets default locale" do job = HelloJob.new job.deserialize({}) - assert_equal :en, job.locale + assert_equal "en", job.locale end end diff --git a/activejob/test/cases/logging_test.rb b/activejob/test/cases/logging_test.rb index 820e9112de..954974b2a5 100644 --- a/activejob/test/cases/logging_test.rb +++ b/activejob/test/cases/logging_test.rb @@ -1,10 +1,11 @@ -require 'helper' +require "helper" require "active_support/log_subscriber/test_helper" -require 'active_support/core_ext/numeric/time' -require 'jobs/hello_job' -require 'jobs/logging_job' -require 'jobs/nested_job' -require 'models/person' +require "active_support/core_ext/numeric/time" +require "jobs/hello_job" +require "jobs/logging_job" +require "jobs/overridden_logging_job" +require "jobs/nested_job" +require "models/person" class LoggingTest < ActiveSupport::TestCase include ActiveSupport::LogSubscriber::TestHelper @@ -41,7 +42,6 @@ class LoggingTest < ActiveSupport::TestCase ActiveJob::Base.logger = logger end - def test_uses_active_job_as_tag HelloJob.perform_later "Cristian" assert_match(/\[ActiveJob\]/, @logger.messages) @@ -119,4 +119,9 @@ class LoggingTest < ActiveSupport::TestCase rescue NotImplementedError skip end + + def test_for_tagged_logger_support_is_consistent + set_logger ::Logger.new(nil) + OverriddenLoggingJob.perform_later "Dummy" + end end diff --git a/activejob/test/cases/queue_adapter_test.rb b/activejob/test/cases/queue_adapter_test.rb index fb3fdc392f..f1e0cf78ad 100644 --- a/activejob/test/cases/queue_adapter_test.rb +++ b/activejob/test/cases/queue_adapter_test.rb @@ -1,4 +1,4 @@ -require 'helper' +require "helper" module ActiveJob module QueueAdapters @@ -15,25 +15,12 @@ module ActiveJob end class QueueAdapterTest < ActiveJob::TestCase - test 'should forbid nonsense arguments' do + test "should forbid nonsense arguments" do assert_raises(ArgumentError) { ActiveJob::Base.queue_adapter = Mutex } assert_raises(ArgumentError) { ActiveJob::Base.queue_adapter = Mutex.new } end - test 'should warn on passing an adapter class' do - klass = Class.new do - def self.name - 'fake' - end - - def enqueue(*); end - def enqueue_at(*); end - end - - assert_deprecated { ActiveJob::Base.queue_adapter = klass } - end - - test 'should allow overriding the queue_adapter at the child class level without affecting the parent or its sibling' do + test "should allow overriding the queue_adapter at the child class level without affecting the parent or its sibling" do 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 898016a704..7777e557c9 100644 --- a/activejob/test/cases/queue_naming_test.rb +++ b/activejob/test/cases/queue_naming_test.rb @@ -1,14 +1,14 @@ -require 'helper' -require 'jobs/hello_job' -require 'jobs/logging_job' -require 'jobs/nested_job' +require "helper" +require "jobs/hello_job" +require "jobs/logging_job" +require "jobs/nested_job" class QueueNamingTest < ActiveSupport::TestCase - test 'name derived from base' do + test "name derived from base" do assert_equal "default", HelloJob.queue_name end - test 'uses given queue name job' do + test "uses given queue name job" do original_queue_name = HelloJob.queue_name begin @@ -19,7 +19,7 @@ class QueueNamingTest < ActiveSupport::TestCase end end - test 'allows a blank queue name' do + test "allows a blank queue name" do original_queue_name = HelloJob.queue_name begin @@ -30,7 +30,7 @@ class QueueNamingTest < ActiveSupport::TestCase end end - test 'does not use a nil queue name' do + test "does not use a nil queue name" do original_queue_name = HelloJob.queue_name begin @@ -41,7 +41,7 @@ class QueueNamingTest < ActiveSupport::TestCase end end - test 'evals block given to queue_as to determine queue' do + test "evals block given to queue_as to determine queue" do original_queue_name = HelloJob.queue_name begin @@ -52,42 +52,42 @@ class QueueNamingTest < ActiveSupport::TestCase end end - test 'can use arguments to determine queue_name in queue_as block' do + test "can use arguments to determine queue_name in queue_as block" do original_queue_name = HelloJob.queue_name begin - HelloJob.queue_as { self.arguments.first=='1' ? :one : :two } - assert_equal "one", HelloJob.new('1').queue_name - assert_equal "two", HelloJob.new('3').queue_name + HelloJob.queue_as { self.arguments.first=="1" ? :one : :two } + assert_equal "one", HelloJob.new("1").queue_name + assert_equal "two", HelloJob.new("3").queue_name ensure HelloJob.queue_name = original_queue_name end end - test 'queue_name_prefix prepended to the queue name with default delimiter' do + test "queue_name_prefix prepended to the queue name with default delimiter" do original_queue_name_prefix = ActiveJob::Base.queue_name_prefix original_queue_name = HelloJob.queue_name begin - ActiveJob::Base.queue_name_prefix = 'aj' + ActiveJob::Base.queue_name_prefix = "aj" HelloJob.queue_as :low - assert_equal 'aj_low', HelloJob.queue_name + assert_equal "aj_low", HelloJob.queue_name ensure ActiveJob::Base.queue_name_prefix = original_queue_name_prefix HelloJob.queue_name = original_queue_name end end - test 'queue_name_prefix prepended to the queue name with custom delimiter' do + test "queue_name_prefix prepended to the queue name with custom delimiter" do original_queue_name_prefix = ActiveJob::Base.queue_name_prefix original_queue_name_delimiter = ActiveJob::Base.queue_name_delimiter original_queue_name = HelloJob.queue_name begin - ActiveJob::Base.queue_name_delimiter = '.' - ActiveJob::Base.queue_name_prefix = 'aj' + ActiveJob::Base.queue_name_delimiter = "." + ActiveJob::Base.queue_name_prefix = "aj" HelloJob.queue_as :low - assert_equal 'aj.low', HelloJob.queue_name + assert_equal "aj.low", HelloJob.queue_name ensure ActiveJob::Base.queue_name_prefix = original_queue_name_prefix ActiveJob::Base.queue_name_delimiter = original_queue_name_delimiter @@ -95,7 +95,7 @@ class QueueNamingTest < ActiveSupport::TestCase end end - test 'uses queue passed to #set' do + test "uses queue passed to #set" do job = HelloJob.set(queue: :some_queue).perform_later assert_equal "some_queue", job.queue_name end diff --git a/activejob/test/cases/queue_priority_test.rb b/activejob/test/cases/queue_priority_test.rb index ca17b51dad..ab4a1bdf7b 100644 --- a/activejob/test/cases/queue_priority_test.rb +++ b/activejob/test/cases/queue_priority_test.rb @@ -1,12 +1,12 @@ -require 'helper' -require 'jobs/hello_job' +require "helper" +require "jobs/hello_job" class QueuePriorityTest < ActiveSupport::TestCase - test 'priority unset by default' do + test "priority unset by default" do assert_equal nil, HelloJob.priority end - test 'uses given priority' do + test "uses given priority" do original_priority = HelloJob.priority begin @@ -17,7 +17,7 @@ class QueuePriorityTest < ActiveSupport::TestCase end end - test 'evals block given to priority to determine priority' do + test "evals block given to priority to determine priority" do original_priority = HelloJob.priority begin @@ -28,19 +28,19 @@ class QueuePriorityTest < ActiveSupport::TestCase end end - test 'can use arguments to determine priority in priority block' do + test "can use arguments to determine priority in priority block" do original_priority = HelloJob.priority begin - HelloJob.queue_with_priority { self.arguments.first=='1' ? 99 : 11 } - assert_equal 99, HelloJob.new('1').priority - assert_equal 11, HelloJob.new('3').priority + HelloJob.queue_with_priority { self.arguments.first=="1" ? 99 : 11 } + assert_equal 99, HelloJob.new("1").priority + assert_equal 11, HelloJob.new("3").priority ensure HelloJob.priority = original_priority end end - test 'uses priority passed to #set' do + test "uses priority passed to #set" do job = HelloJob.set(priority: 123).perform_later assert_equal 123, job.priority end diff --git a/activejob/test/cases/queuing_test.rb b/activejob/test/cases/queuing_test.rb index 0eeabbf693..a3ecb15c31 100644 --- a/activejob/test/cases/queuing_test.rb +++ b/activejob/test/cases/queuing_test.rb @@ -1,24 +1,23 @@ -require 'helper' -require 'jobs/hello_job' -require 'active_support/core_ext/numeric/time' - +require "helper" +require "jobs/hello_job" +require "active_support/core_ext/numeric/time" class QueuingTest < ActiveSupport::TestCase setup do JobBuffer.clear end - test 'run queued job' do + test "run queued job" do HelloJob.perform_later assert_equal "David says hello", JobBuffer.last_value end - test 'run queued job with arguments' do + test "run queued job with arguments" do HelloJob.perform_later "Jamie" assert_equal "Jamie says hello", JobBuffer.last_value end - test 'run queued job later' do + test "run queued job later" do begin result = HelloJob.set(wait_until: 1.second.ago).perform_later "Jamie" assert result @@ -27,13 +26,12 @@ class QueuingTest < ActiveSupport::TestCase end end - test 'job returned by enqueue has the arguments available' do + test "job returned by enqueue has the arguments available" do job = HelloJob.perform_later "Jamie" assert_equal [ "Jamie" ], job.arguments end - - test 'job returned by perform_at has the timestamp available' do + test "job returned by perform_at has the timestamp available" do begin job = HelloJob.set(wait_until: Time.utc(2014, 1, 1)).perform_later assert_equal Time.utc(2014, 1, 1).to_f, job.scheduled_at diff --git a/activejob/test/cases/rescue_test.rb b/activejob/test/cases/rescue_test.rb index 58c9ca8992..afb1b48831 100644 --- a/activejob/test/cases/rescue_test.rb +++ b/activejob/test/cases/rescue_test.rb @@ -1,34 +1,34 @@ -require 'helper' -require 'jobs/rescue_job' -require 'models/person' +require "helper" +require "jobs/rescue_job" +require "models/person" class RescueTest < ActiveSupport::TestCase setup do JobBuffer.clear end - test 'rescue perform exception with retry' do + test "rescue perform exception with retry" do job = RescueJob.new("david") job.perform_now assert_equal [ "rescued from ArgumentError", "performed beautifully" ], JobBuffer.values end - test 'let through unhandled perform exception' do + test "let through unhandled perform exception" do job = RescueJob.new("other") assert_raises(RescueJob::OtherError) do job.perform_now end end - test 'rescue from deserialization errors' do + test "rescue from deserialization errors" do RescueJob.perform_later Person.new(404) - assert_includes JobBuffer.values, 'rescued from DeserializationError' - assert_includes JobBuffer.values, 'DeserializationError original exception was Person::RecordNotFound' - assert_not_includes JobBuffer.values, 'performed beautifully' + assert_includes JobBuffer.values, "rescued from DeserializationError" + assert_includes JobBuffer.values, "DeserializationError original exception was Person::RecordNotFound" + assert_not_includes JobBuffer.values, "performed beautifully" end test "should not wrap DeserializationError in DeserializationError" do RescueJob.perform_later [Person.new(404)] - assert_includes JobBuffer.values, 'DeserializationError original exception was Person::RecordNotFound' + assert_includes JobBuffer.values, "DeserializationError original exception was Person::RecordNotFound" end end diff --git a/activejob/test/cases/test_case_test.rb b/activejob/test/cases/test_case_test.rb index 616454a4b6..3db2d9dec7 100644 --- a/activejob/test/cases/test_case_test.rb +++ b/activejob/test/cases/test_case_test.rb @@ -1,7 +1,7 @@ -require 'helper' -require 'jobs/hello_job' -require 'jobs/logging_job' -require 'jobs/nested_job' +require "helper" +require "jobs/hello_job" +require "jobs/logging_job" +require "jobs/nested_job" class ActiveJobTestCaseTest < ActiveJob::TestCase # this tests that this job class doesn't get its adapter set. @@ -9,7 +9,7 @@ class ActiveJobTestCaseTest < ActiveJob::TestCase # the `class_attribute` inheritance class TestClassAttributeInheritanceJob < ActiveJob::Base def self.queue_adapter=(*) - raise 'Attempting to break `class_attribute` inheritance, bad!' + raise "Attempting to break `class_attribute` inheritance, bad!" end end @@ -18,6 +18,6 @@ class ActiveJobTestCaseTest < ActiveJob::TestCase end def test_set_test_adapter - assert_kind_of ActiveJob::QueueAdapters::TestAdapter, self.queue_adapter + assert_kind_of ActiveJob::QueueAdapters::TestAdapter, queue_adapter end end diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb index f7ee763e8a..685c93da2d 100644 --- a/activejob/test/cases/test_helper_test.rb +++ b/activejob/test/cases/test_helper_test.rb @@ -1,17 +1,18 @@ -require 'helper' -require 'active_support/core_ext/time' -require 'active_support/core_ext/date' -require 'jobs/hello_job' -require 'jobs/logging_job' -require 'jobs/nested_job' -require 'jobs/rescue_job' -require 'models/person' +require "helper" +require "active_support/core_ext/time" +require "active_support/core_ext/date" +require "jobs/hello_job" +require "jobs/logging_job" +require "jobs/nested_job" +require "jobs/rescue_job" +require "jobs/inherited_job" +require "models/person" class EnqueuedJobsTest < ActiveJob::TestCase def test_assert_enqueued_jobs assert_nothing_raised do assert_enqueued_jobs 1 do - HelloJob.perform_later('david') + HelloJob.perform_later("david") end end end @@ -19,23 +20,23 @@ class EnqueuedJobsTest < ActiveJob::TestCase def test_repeated_enqueued_jobs_calls assert_nothing_raised do assert_enqueued_jobs 1 do - HelloJob.perform_later('abdelkader') + HelloJob.perform_later("abdelkader") end end assert_nothing_raised do assert_enqueued_jobs 2 do - HelloJob.perform_later('sean') - HelloJob.perform_later('yves') + HelloJob.perform_later("sean") + HelloJob.perform_later("yves") end end end def test_assert_enqueued_jobs_message - HelloJob.perform_later('sean') + HelloJob.perform_later("sean") e = assert_raises Minitest::Assertion do assert_enqueued_jobs 2 do - HelloJob.perform_later('sean') + HelloJob.perform_later("sean") end end assert_match "Expected: 2", e.message @@ -44,13 +45,13 @@ class EnqueuedJobsTest < ActiveJob::TestCase def test_assert_enqueued_jobs_with_no_block assert_nothing_raised do - HelloJob.perform_later('rafael') + HelloJob.perform_later("rafael") assert_enqueued_jobs 1 end assert_nothing_raised do - HelloJob.perform_later('aaron') - HelloJob.perform_later('matthew') + HelloJob.perform_later("aaron") + HelloJob.perform_later("matthew") assert_enqueued_jobs 3 end end @@ -72,7 +73,7 @@ class EnqueuedJobsTest < ActiveJob::TestCase def test_assert_enqueued_jobs_too_few_sent error = assert_raise ActiveSupport::TestCase::Assertion do assert_enqueued_jobs 2 do - HelloJob.perform_later('xavier') + HelloJob.perform_later("xavier") end end @@ -82,8 +83,8 @@ class EnqueuedJobsTest < ActiveJob::TestCase def test_assert_enqueued_jobs_too_many_sent error = assert_raise ActiveSupport::TestCase::Assertion do assert_enqueued_jobs 1 do - HelloJob.perform_later('cristian') - HelloJob.perform_later('guillermo') + HelloJob.perform_later("cristian") + HelloJob.perform_later("guillermo") end end @@ -93,7 +94,7 @@ class EnqueuedJobsTest < ActiveJob::TestCase def test_assert_no_enqueued_jobs_failure error = assert_raise ActiveSupport::TestCase::Assertion do assert_no_enqueued_jobs do - HelloJob.perform_later('jeremy') + HelloJob.perform_later("jeremy") end end @@ -103,7 +104,7 @@ class EnqueuedJobsTest < ActiveJob::TestCase def test_assert_enqueued_jobs_with_only_option assert_nothing_raised do assert_enqueued_jobs 1, only: HelloJob do - HelloJob.perform_later('jeremy') + HelloJob.perform_later("jeremy") LoggingJob.perform_later end end @@ -122,7 +123,7 @@ class EnqueuedJobsTest < ActiveJob::TestCase def test_assert_enqueued_jobs_with_only_option_and_too_few_sent error = assert_raise ActiveSupport::TestCase::Assertion do assert_enqueued_jobs 5, only: HelloJob do - HelloJob.perform_later('jeremy') + HelloJob.perform_later("jeremy") 4.times { LoggingJob.perform_later } end end @@ -133,7 +134,7 @@ class EnqueuedJobsTest < ActiveJob::TestCase def test_assert_enqueued_jobs_with_only_option_and_too_many_sent error = assert_raise ActiveSupport::TestCase::Assertion do assert_enqueued_jobs 1, only: HelloJob do - 2.times { HelloJob.perform_later('jeremy') } + 2.times { HelloJob.perform_later("jeremy") } end end @@ -143,9 +144,9 @@ class EnqueuedJobsTest < ActiveJob::TestCase def test_assert_enqueued_jobs_with_only_option_as_array assert_nothing_raised do assert_enqueued_jobs 2, only: [HelloJob, LoggingJob] do - HelloJob.perform_later('jeremy') - LoggingJob.perform_later('stewie') - RescueJob.perform_later('david') + HelloJob.perform_later("jeremy") + LoggingJob.perform_later("stewie") + RescueJob.perform_later("david") end end end @@ -161,7 +162,7 @@ class EnqueuedJobsTest < ActiveJob::TestCase def test_assert_no_enqueued_jobs_with_only_option_failure error = assert_raise ActiveSupport::TestCase::Assertion do assert_no_enqueued_jobs only: HelloJob do - HelloJob.perform_later('jeremy') + HelloJob.perform_later("jeremy") LoggingJob.perform_later end end @@ -178,7 +179,7 @@ class EnqueuedJobsTest < ActiveJob::TestCase end def test_assert_enqueued_job - assert_enqueued_with(job: LoggingJob, queue: 'default') do + assert_enqueued_with(job: LoggingJob, queue: "default") do LoggingJob.set(wait_until: Date.tomorrow.noon).perform_later end end @@ -190,19 +191,19 @@ class EnqueuedJobsTest < ActiveJob::TestCase assert_instance_of LoggingJob, job assert_in_delta 5.minutes.from_now, job.scheduled_at, 1 - assert_equal 'default', job.queue_name + assert_equal "default", job.queue_name assert_equal [1, 2, 3], job.arguments end def test_assert_enqueued_job_failure assert_raise ActiveSupport::TestCase::Assertion do - assert_enqueued_with(job: LoggingJob, queue: 'default') do + assert_enqueued_with(job: LoggingJob, queue: "default") do NestedJob.perform_later end end error = assert_raise ActiveSupport::TestCase::Assertion do - assert_enqueued_with(job: NestedJob, queue: 'low') do + assert_enqueued_with(job: NestedJob, queue: "low") do NestedJob.perform_later end end @@ -249,7 +250,7 @@ class EnqueuedJobsTest < ActiveJob::TestCase HelloJob.perform_later end - assert_equal 2, ActiveJob::Base.queue_adapter.enqueued_jobs.count + assert_equal 2, queue_adapter.enqueued_jobs.count end end @@ -265,7 +266,7 @@ class PerformedJobsTest < ActiveJob::TestCase def test_assert_performed_jobs assert_nothing_raised do assert_performed_jobs 1 do - HelloJob.perform_later('david') + HelloJob.perform_later("david") end end end @@ -273,23 +274,23 @@ class PerformedJobsTest < ActiveJob::TestCase def test_repeated_performed_jobs_calls assert_nothing_raised do assert_performed_jobs 1 do - HelloJob.perform_later('abdelkader') + HelloJob.perform_later("abdelkader") end end assert_nothing_raised do assert_performed_jobs 2 do - HelloJob.perform_later('sean') - HelloJob.perform_later('yves') + HelloJob.perform_later("sean") + HelloJob.perform_later("yves") end end end def test_assert_performed_jobs_message - HelloJob.perform_later('sean') + HelloJob.perform_later("sean") e = assert_raises Minitest::Assertion do assert_performed_jobs 2 do - HelloJob.perform_later('sean') + HelloJob.perform_later("sean") end end assert_match "Expected: 2", e.message @@ -299,15 +300,15 @@ class PerformedJobsTest < ActiveJob::TestCase def test_assert_performed_jobs_with_no_block assert_nothing_raised do perform_enqueued_jobs do - HelloJob.perform_later('rafael') + HelloJob.perform_later("rafael") end assert_performed_jobs 1 end assert_nothing_raised do perform_enqueued_jobs do - HelloJob.perform_later('aaron') - HelloJob.perform_later('matthew') + HelloJob.perform_later("aaron") + HelloJob.perform_later("matthew") assert_performed_jobs 3 end end @@ -330,7 +331,7 @@ class PerformedJobsTest < ActiveJob::TestCase def test_assert_performed_jobs_too_few_sent error = assert_raise ActiveSupport::TestCase::Assertion do assert_performed_jobs 2 do - HelloJob.perform_later('xavier') + HelloJob.perform_later("xavier") end end @@ -340,8 +341,8 @@ class PerformedJobsTest < ActiveJob::TestCase def test_assert_performed_jobs_too_many_sent error = assert_raise ActiveSupport::TestCase::Assertion do assert_performed_jobs 1 do - HelloJob.perform_later('cristian') - HelloJob.perform_later('guillermo') + HelloJob.perform_later("cristian") + HelloJob.perform_later("guillermo") end end @@ -351,7 +352,7 @@ class PerformedJobsTest < ActiveJob::TestCase def test_assert_no_performed_jobs_failure error = assert_raise ActiveSupport::TestCase::Assertion do assert_no_performed_jobs do - HelloJob.perform_later('jeremy') + HelloJob.perform_later("jeremy") end end @@ -361,7 +362,7 @@ class PerformedJobsTest < ActiveJob::TestCase def test_assert_performed_jobs_with_only_option assert_nothing_raised do assert_performed_jobs 1, only: HelloJob do - HelloJob.perform_later('jeremy') + HelloJob.perform_later("jeremy") LoggingJob.perform_later end end @@ -370,9 +371,9 @@ class PerformedJobsTest < ActiveJob::TestCase def test_assert_performed_jobs_with_only_option_as_array assert_nothing_raised do assert_performed_jobs 2, only: [HelloJob, LoggingJob] do - HelloJob.perform_later('jeremy') - LoggingJob.perform_later('stewie') - RescueJob.perform_later('david') + HelloJob.perform_later("jeremy") + LoggingJob.perform_later("stewie") + RescueJob.perform_later("david") end end end @@ -390,7 +391,7 @@ class PerformedJobsTest < ActiveJob::TestCase def test_assert_performed_jobs_with_only_option_and_too_few_sent error = assert_raise ActiveSupport::TestCase::Assertion do assert_performed_jobs 5, only: HelloJob do - HelloJob.perform_later('jeremy') + HelloJob.perform_later("jeremy") 4.times { LoggingJob.perform_later } end end @@ -401,7 +402,7 @@ class PerformedJobsTest < ActiveJob::TestCase def test_assert_performed_jobs_with_only_option_and_too_many_sent error = assert_raise ActiveSupport::TestCase::Assertion do assert_performed_jobs 1, only: HelloJob do - 2.times { HelloJob.perform_later('jeremy') } + 2.times { HelloJob.perform_later("jeremy") } end end @@ -427,7 +428,7 @@ class PerformedJobsTest < ActiveJob::TestCase def test_assert_no_performed_jobs_with_only_option_failure error = assert_raise ActiveSupport::TestCase::Assertion do assert_no_performed_jobs only: HelloJob do - HelloJob.perform_later('jeremy') + HelloJob.perform_later("jeremy") LoggingJob.perform_later end end @@ -436,20 +437,20 @@ class PerformedJobsTest < ActiveJob::TestCase end def test_assert_performed_job - assert_performed_with(job: NestedJob, queue: 'default') do + assert_performed_with(job: NestedJob, queue: "default") do NestedJob.perform_later end end def test_assert_performed_job_returns - job = assert_performed_with(job: NestedJob, queue: 'default') do + job = assert_performed_with(job: NestedJob, queue: "default") do NestedJob.perform_later end assert_instance_of NestedJob, job assert_nil job.scheduled_at assert_equal [], job.arguments - assert_equal 'default', job.queue_name + assert_equal "default", job.queue_name end def test_assert_performed_job_failure @@ -460,8 +461,8 @@ class PerformedJobsTest < ActiveJob::TestCase end assert_raise ActiveSupport::TestCase::Assertion do - assert_performed_with(job: HelloJob, queue: 'low') do - HelloJob.set(queue: 'important').perform_later + assert_performed_with(job: HelloJob, queue: "low") do + HelloJob.set(queue: "important").perform_later end end end @@ -506,6 +507,24 @@ class PerformedJobsTest < ActiveJob::TestCase HelloJob.perform_later end - assert_equal 2, ActiveJob::Base.queue_adapter.performed_jobs.count + assert_equal 2, queue_adapter.performed_jobs.count + end +end + +class OverrideQueueAdapterTest < ActiveJob::TestCase + class CustomQueueAdapter < ActiveJob::QueueAdapters::TestAdapter; end + + def queue_adapter_for_test + CustomQueueAdapter.new + end + + def test_assert_job_has_custom_queue_adapter_set + assert_instance_of CustomQueueAdapter, HelloJob.queue_adapter + end +end + +class InheritedJobTest < ActiveJob::TestCase + def test_queue_adapter_is_test_adapter + assert_instance_of ActiveJob::QueueAdapters::TestAdapter, InheritedJob.queue_adapter end end diff --git a/activejob/test/cases/translation_test.rb b/activejob/test/cases/translation_test.rb index d5e3aaf9e3..27bfd50985 100644 --- a/activejob/test/cases/translation_test.rb +++ b/activejob/test/cases/translation_test.rb @@ -1,18 +1,18 @@ -require 'helper' -require 'jobs/translated_hello_job' +require "helper" +require "jobs/translated_hello_job" class TranslationTest < ActiveSupport::TestCase setup do JobBuffer.clear I18n.available_locales = [:en, :de] - @job = TranslatedHelloJob.new('Johannes') + @job = TranslatedHelloJob.new("Johannes") end teardown do I18n.available_locales = [:en] end - test 'it performs the job in the given locale' do + test "it performs the job in the given locale" do @job.locale = :de @job.perform_now assert_equal "Johannes says Guten Tag", JobBuffer.last_value diff --git a/activejob/test/helper.rb b/activejob/test/helper.rb index 7e86415f48..dbc7dad109 100644 --- a/activejob/test/helper.rb +++ b/activejob/test/helper.rb @@ -1,18 +1,16 @@ -require File.expand_path('../../../load_paths', __FILE__) - -require 'active_job' -require 'support/job_buffer' +require "active_job" +require "support/job_buffer" ActiveSupport.halt_callback_chains_on_return_false = false -GlobalID.app = 'aj' +GlobalID.app = "aj" -@adapter = ENV['AJ_ADAPTER'] || 'inline' +@adapter = ENV["AJ_ADAPTER"] || "inline" -if ENV['AJ_INTEGRATION_TESTS'] - require 'support/integration/helper' +if ENV["AJ_INTEGRATION_TESTS"] + require "support/integration/helper" else ActiveJob::Base.logger = Logger.new(nil) require "adapters/#{@adapter}" end -require 'active_support/testing/autorun' +require "active_support/testing/autorun" diff --git a/activejob/test/integration/queuing_test.rb b/activejob/test/integration/queuing_test.rb index d8425c9706..2669c52a1c 100644 --- a/activejob/test/integration/queuing_test.rb +++ b/activejob/test/integration/queuing_test.rb @@ -1,16 +1,17 @@ -require 'helper' -require 'jobs/logging_job' -require 'jobs/hello_job' -require 'active_support/core_ext/numeric/time' +require "helper" +require "jobs/logging_job" +require "jobs/hello_job" +require "jobs/provider_jid_job" +require "active_support/core_ext/numeric/time" class QueuingTest < ActiveSupport::TestCase - test 'should run jobs enqueued on a listening queue' do + test "should run jobs enqueued on a listening queue" do TestJob.perform_later @id wait_for_jobs_to_finish_for(5.seconds) assert job_executed end - test 'should not run jobs queued on a non-listening queue' do + test "should not run jobs queued on a non-listening queue" do skip if adapter_is?(:inline, :async, :sucker_punch, :que) old_queue = TestJob.queue_name @@ -24,17 +25,32 @@ class QueuingTest < ActiveSupport::TestCase end end - test 'should supply a wrapped class name to Sidekiq' do + test "should supply a wrapped class name to Sidekiq" do skip unless adapter_is?(:sidekiq) Sidekiq::Testing.fake! do ::HelloJob.perform_later hash = ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper.jobs.first - assert_equal "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper", hash['class'] - assert_equal "HelloJob", hash['wrapped'] + assert_equal "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper", hash["class"] + assert_equal "HelloJob", hash["wrapped"] end end - test 'should not run job enqueued in the future' do + test "should access provider_job_id inside Sidekiq job" do + skip unless adapter_is?(:sidekiq) + Sidekiq::Testing.inline! do + job = ::ProviderJidJob.perform_later + assert_equal "Provider Job ID: #{job.provider_job_id}", JobBuffer.last_value + end + end + + test "resque JobWrapper should have instance variable queue" do + skip unless adapter_is?(:resque) + job = ::HelloJob.set(wait: 5.seconds).perform_later + hash = Resque.decode(Resque.find_delayed_selection { true }[0]) + assert_equal hash["queue"], job.queue_name + end + + test "should not run job enqueued in the future" do begin TestJob.set(wait: 10.minutes).perform_later @id wait_for_jobs_to_finish_for(5.seconds) @@ -44,7 +60,7 @@ class QueuingTest < ActiveSupport::TestCase end end - test 'should run job enqueued in the future at the specified time' do + test "should run job enqueued in the future at the specified time" do begin TestJob.set(wait: 5.seconds).perform_later @id wait_for_jobs_to_finish_for(2.seconds) @@ -56,19 +72,19 @@ class QueuingTest < ActiveSupport::TestCase end end - test 'should supply a provider_job_id when available for immediate jobs' do - skip unless adapter_is?(:delayed_job, :sidekiq, :qu, :que, :queue_classic) + test "should supply a provider_job_id when available for immediate jobs" do + skip unless adapter_is?(:async, :delayed_job, :sidekiq, :qu, :que, :queue_classic) test_job = TestJob.perform_later @id - assert test_job.provider_job_id, 'Provider job id should be set by provider' + assert test_job.provider_job_id, "Provider job id should be set by provider" end - test 'should supply a provider_job_id when available for delayed jobs' do - skip unless adapter_is?(:delayed_job, :sidekiq, :que, :queue_classic) + test "should supply a provider_job_id when available for delayed jobs" do + skip unless adapter_is?(:async, :delayed_job, :sidekiq, :que, :queue_classic) delayed_test_job = TestJob.set(wait: 1.minute).perform_later @id - assert delayed_test_job.provider_job_id, 'Provider job id should by set for delayed jobs by provider' + assert delayed_test_job.provider_job_id, "Provider job id should by set for delayed jobs by provider" end - test 'current locale is kept while running perform_later' do + test "current locale is kept while running perform_later" do skip if adapter_is?(:inline) begin @@ -78,14 +94,14 @@ class QueuingTest < ActiveSupport::TestCase TestJob.perform_later @id wait_for_jobs_to_finish_for(5.seconds) assert job_executed - assert_equal 'de', job_executed_in_locale + assert_equal "de", job_executed_in_locale ensure I18n.available_locales = [:en] I18n.locale = :en end end - test 'should run job with higher priority first' do + test "should run job with higher priority first" do skip unless adapter_is?(:delayed_job, :que) wait_until = Time.now + 3.seconds diff --git a/activejob/test/jobs/application_job.rb b/activejob/test/jobs/application_job.rb new file mode 100644 index 0000000000..4a78b890ec --- /dev/null +++ b/activejob/test/jobs/application_job.rb @@ -0,0 +1,4 @@ +require_relative "../support/job_buffer" + +class ApplicationJob < ActiveJob::Base +end diff --git a/activejob/test/jobs/callback_job.rb b/activejob/test/jobs/callback_job.rb index 891ed9464e..ca2a8e031a 100644 --- a/activejob/test/jobs/callback_job.rb +++ b/activejob/test/jobs/callback_job.rb @@ -17,7 +17,6 @@ class CallbackJob < ActiveJob::Base job.history << "CallbackJob ran around_enqueue_stop" end - def perform(person = "david") # NOTHING! end @@ -25,5 +24,4 @@ class CallbackJob < ActiveJob::Base def history @history ||= [] end - end diff --git a/activejob/test/jobs/gid_job.rb b/activejob/test/jobs/gid_job.rb index e485bfa2dd..c7cfa6e7a2 100644 --- a/activejob/test/jobs/gid_job.rb +++ b/activejob/test/jobs/gid_job.rb @@ -1,8 +1,7 @@ -require_relative '../support/job_buffer' +require_relative "../support/job_buffer" class GidJob < ActiveJob::Base def perform(person) JobBuffer.add("Person with ID: #{person.id}") end end - diff --git a/activejob/test/jobs/hello_job.rb b/activejob/test/jobs/hello_job.rb index 022fa58e4a..126fefae00 100644 --- a/activejob/test/jobs/hello_job.rb +++ b/activejob/test/jobs/hello_job.rb @@ -1,4 +1,4 @@ -require_relative '../support/job_buffer' +require_relative "../support/job_buffer" class HelloJob < ActiveJob::Base def perform(greeter = "David") diff --git a/activejob/test/jobs/inherited_job.rb b/activejob/test/jobs/inherited_job.rb new file mode 100644 index 0000000000..60fca66f88 --- /dev/null +++ b/activejob/test/jobs/inherited_job.rb @@ -0,0 +1,5 @@ +require_relative "application_job" + +class InheritedJob < ApplicationJob + self.queue_adapter = :inline +end diff --git a/activejob/test/jobs/kwargs_job.rb b/activejob/test/jobs/kwargs_job.rb index 2df17d15ae..9f9d809673 100644 --- a/activejob/test/jobs/kwargs_job.rb +++ b/activejob/test/jobs/kwargs_job.rb @@ -1,4 +1,4 @@ -require_relative '../support/job_buffer' +require_relative "../support/job_buffer" class KwargsJob < ActiveJob::Base def perform(argument: 1) diff --git a/activejob/test/jobs/logging_job.rb b/activejob/test/jobs/logging_job.rb index d84ed8589b..119b32fa67 100644 --- a/activejob/test/jobs/logging_job.rb +++ b/activejob/test/jobs/logging_job.rb @@ -7,4 +7,3 @@ class LoggingJob < ActiveJob::Base "LOGGING-JOB-ID" end end - diff --git a/activejob/test/jobs/nested_job.rb b/activejob/test/jobs/nested_job.rb index 8c4ec549a6..21b7692cd1 100644 --- a/activejob/test/jobs/nested_job.rb +++ b/activejob/test/jobs/nested_job.rb @@ -7,4 +7,3 @@ class NestedJob < ActiveJob::Base "NESTED-JOB-ID" end end - diff --git a/activejob/test/jobs/overridden_logging_job.rb b/activejob/test/jobs/overridden_logging_job.rb new file mode 100644 index 0000000000..2b17a65142 --- /dev/null +++ b/activejob/test/jobs/overridden_logging_job.rb @@ -0,0 +1,9 @@ +class OverriddenLoggingJob < ActiveJob::Base + def perform(dummy) + logger.info "Dummy, here is it: #{dummy}" + end + + def logger + @logger ||= ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(nil)) + end +end diff --git a/activejob/test/jobs/provider_jid_job.rb b/activejob/test/jobs/provider_jid_job.rb new file mode 100644 index 0000000000..0c8ba040f5 --- /dev/null +++ b/activejob/test/jobs/provider_jid_job.rb @@ -0,0 +1,7 @@ +require_relative "../support/job_buffer" + +class ProviderJidJob < ActiveJob::Base + def perform + JobBuffer.add("Provider Job ID: #{provider_job_id}") + end +end diff --git a/activejob/test/jobs/queue_as_job.rb b/activejob/test/jobs/queue_as_job.rb index 897aef52e5..7dc318e736 100644 --- a/activejob/test/jobs/queue_as_job.rb +++ b/activejob/test/jobs/queue_as_job.rb @@ -1,4 +1,4 @@ -require_relative '../support/job_buffer' +require_relative "../support/job_buffer" class QueueAsJob < ActiveJob::Base MY_QUEUE = :low_priority diff --git a/activejob/test/jobs/rescue_job.rb b/activejob/test/jobs/rescue_job.rb index 4f6376c850..ef8f777437 100644 --- a/activejob/test/jobs/rescue_job.rb +++ b/activejob/test/jobs/rescue_job.rb @@ -1,16 +1,16 @@ -require_relative '../support/job_buffer' +require_relative "../support/job_buffer" class RescueJob < ActiveJob::Base class OtherError < StandardError; end rescue_from(ArgumentError) do - JobBuffer.add('rescued from ArgumentError') + JobBuffer.add("rescued from ArgumentError") arguments[0] = "DIFFERENT!" retry_job end rescue_from(ActiveJob::DeserializationError) do |e| - JobBuffer.add('rescued from DeserializationError') + JobBuffer.add("rescued from DeserializationError") JobBuffer.add("DeserializationError original exception was #{e.cause.class.name}") end @@ -21,7 +21,7 @@ class RescueJob < ActiveJob::Base when "other" raise OtherError else - JobBuffer.add('performed beautifully') + JobBuffer.add("performed beautifully") end end end diff --git a/activejob/test/jobs/retry_job.rb b/activejob/test/jobs/retry_job.rb new file mode 100644 index 0000000000..c02febc50c --- /dev/null +++ b/activejob/test/jobs/retry_job.rb @@ -0,0 +1,29 @@ +require_relative "../support/job_buffer" +require "active_support/core_ext/integer/inflections" + +class DefaultsError < StandardError; end +class LongWaitError < StandardError; end +class ShortWaitTenAttemptsError < StandardError; end +class ExponentialWaitTenAttemptsError < StandardError; end +class CustomWaitTenAttemptsError < StandardError; end +class CustomCatchError < StandardError; end +class DiscardableError < StandardError; end + +class RetryJob < ActiveJob::Base + retry_on DefaultsError + retry_on LongWaitError, wait: 1.hour, attempts: 10 + retry_on ShortWaitTenAttemptsError, wait: 1.second, attempts: 10 + retry_on ExponentialWaitTenAttemptsError, wait: :exponentially_longer, attempts: 10 + retry_on CustomWaitTenAttemptsError, wait: ->(executions) { executions * 2 }, attempts: 10 + retry_on(CustomCatchError) { |job, exception| JobBuffer.add("Dealt with a job that failed to retry in a custom way after #{job.arguments.second} attempts") } + discard_on DiscardableError + + def perform(raising, attempts) + if executions < attempts + JobBuffer.add("Raised #{raising} for the #{executions.ordinalize} time") + raise raising.constantize + else + JobBuffer.add("Successfully completed job") + end + end +end diff --git a/activejob/test/jobs/translated_hello_job.rb b/activejob/test/jobs/translated_hello_job.rb index 9657cd3f54..56e9f147b9 100644 --- a/activejob/test/jobs/translated_hello_job.rb +++ b/activejob/test/jobs/translated_hello_job.rb @@ -1,8 +1,8 @@ -require_relative '../support/job_buffer' +require_relative "../support/job_buffer" class TranslatedHelloJob < ActiveJob::Base def perform(greeter = "David") - translations = { en: 'Hello', de: 'Guten Tag' } + translations = { en: "Hello", de: "Guten Tag" } hello = translations[I18n.locale] JobBuffer.add("#{greeter} says #{hello}") diff --git a/activejob/test/support/backburner/inline.rb b/activejob/test/support/backburner/inline.rb index f761b53e27..647dbf317f 100644 --- a/activejob/test/support/backburner/inline.rb +++ b/activejob/test/support/backburner/inline.rb @@ -1,8 +1,8 @@ -require 'backburner' +require "backburner" Backburner::Worker.class_eval do class << self; alias_method :original_enqueue, :enqueue; end def self.enqueue(job_class, args=[], opts={}) job_class.perform(*args) end -end
\ No newline at end of file +end diff --git a/activejob/test/support/delayed_job/delayed/backend/test.rb b/activejob/test/support/delayed_job/delayed/backend/test.rb index f80ec3a5a6..a900b18e2a 100644 --- a/activejob/test/support/delayed_job/delayed/backend/test.rb +++ b/activejob/test/support/delayed_job/delayed/backend/test.rb @@ -1,5 +1,5 @@ #copied from https://github.com/collectiveidea/delayed_job/blob/master/spec/delayed/backend/test.rb -require 'ostruct' +require "ostruct" # An in-memory backend suitable only for testing. Tries to behave as if it were an ORM. module Delayed @@ -26,7 +26,7 @@ module Delayed self.attempts = 0 self.priority = 0 self.id = (self.class.id += 1) - hash.each{|k,v| send(:"#{k}=", v)} + hash.each { |k,v| send(:"#{k}=", v) } end @jobs = [] @@ -49,7 +49,7 @@ module Delayed def self.create!(*args); create(*args); end def self.clear_locks!(worker_name) - all.select{|j| j.locked_by == worker_name}.each {|j| j.locked_by = nil; j.locked_at = nil} + all.select { |j| j.locked_by == worker_name }.each { |j| j.locked_by = nil; j.locked_at = nil } end # Find a few candidate jobs to run (in case some immediately get locked by others). @@ -60,10 +60,10 @@ module Delayed !j.failed? end - jobs = jobs.select{|j| Worker.queues.include?(j.queue)} if Worker.queues.any? - jobs = jobs.select{|j| j.priority >= Worker.min_priority} if Worker.min_priority - jobs = jobs.select{|j| j.priority <= Worker.max_priority} if Worker.max_priority - jobs.sort_by{|j| [j.priority, j.run_at]}[0..limit-1] + jobs = jobs.select { |j| Worker.queues.include?(j.queue) } if Worker.queues.any? + jobs = jobs.select { |j| j.priority >= Worker.min_priority } if Worker.min_priority + jobs = jobs.select { |j| j.priority <= Worker.max_priority } if Worker.max_priority + jobs.sort_by { |j| [j.priority, j.run_at] }[0..limit-1] end # Lock this job for this worker. @@ -84,7 +84,7 @@ module Delayed end def update_attributes(attrs = {}) - attrs.each{|k,v| send(:"#{k}=", v)} + attrs.each { |k,v| send(:"#{k}=", v) } save end diff --git a/activejob/test/support/integration/adapters/async.rb b/activejob/test/support/integration/adapters/async.rb index 42beb12b1f..44ab98437a 100644 --- a/activejob/test/support/integration/adapters/async.rb +++ b/activejob/test/support/integration/adapters/async.rb @@ -1,9 +1,10 @@ module AsyncJobsManager def setup ActiveJob::Base.queue_adapter = :async + ActiveJob::Base.queue_adapter.immediate = false end def clear_jobs - ActiveJob::AsyncJob::QUEUES.clear + ActiveJob::Base.queue_adapter.shutdown end end diff --git a/activejob/test/support/integration/adapters/backburner.rb b/activejob/test/support/integration/adapters/backburner.rb index 2e82562948..263097c792 100644 --- a/activejob/test/support/integration/adapters/backburner.rb +++ b/activejob/test/support/integration/adapters/backburner.rb @@ -35,4 +35,3 @@ module BackburnerJobsManager true end end - diff --git a/activejob/test/support/integration/adapters/delayed_job.rb b/activejob/test/support/integration/adapters/delayed_job.rb index 0b591964bc..2adbbcd88f 100644 --- a/activejob/test/support/integration/adapters/delayed_job.rb +++ b/activejob/test/support/integration/adapters/delayed_job.rb @@ -1,5 +1,5 @@ -require 'delayed_job' -require 'delayed_job_active_record' +require "delayed_job" +require "delayed_job_active_record" module DelayedJobJobsManager def setup diff --git a/activejob/test/support/integration/adapters/inline.rb b/activejob/test/support/integration/adapters/inline.rb index 83c38f706f..e06b34c976 100644 --- a/activejob/test/support/integration/adapters/inline.rb +++ b/activejob/test/support/integration/adapters/inline.rb @@ -12,4 +12,3 @@ module InlineJobsManager def stop_workers end end - diff --git a/activejob/test/support/integration/adapters/qu.rb b/activejob/test/support/integration/adapters/qu.rb index 256ddb3cf3..71819f5a55 100644 --- a/activejob/test/support/integration/adapters/qu.rb +++ b/activejob/test/support/integration/adapters/qu.rb @@ -1,9 +1,9 @@ module QuJobsManager def setup - require 'qu-rails' - require 'qu-redis' + require "qu-rails" + require "qu-redis" ActiveJob::Base.queue_adapter = :qu - ENV['REDISTOGO_URL'] = "redis://127.0.0.1:6379/12" + ENV["REDISTOGO_URL"] = "redis://127.0.0.1:6379/12" backend = Qu::Backend::Redis.new backend.namespace = "active_jobs_int_test" Qu.backend = backend diff --git a/activejob/test/support/integration/adapters/que.rb b/activejob/test/support/integration/adapters/que.rb index 0cd8952a28..3d35a0439f 100644 --- a/activejob/test/support/integration/adapters/que.rb +++ b/activejob/test/support/integration/adapters/que.rb @@ -1,6 +1,6 @@ module QueJobsManager def setup - require 'sequel' + require "sequel" ActiveJob::Base.queue_adapter = :que Que.mode = :off Que.worker_count = 1 @@ -11,9 +11,9 @@ module QueJobsManager end def start_workers - que_url = ENV['QUE_DATABASE_URL'] || 'postgres:///active_jobs_que_int_test' + que_url = ENV["QUE_DATABASE_URL"] || "postgres:///active_jobs_que_int_test" uri = URI.parse(que_url) - user = uri.user||ENV['USER'] + user = uri.user||ENV["USER"] pass = uri.password db = uri.path[1..-1] %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'drop database if exists "#{db}"' -U #{user} -t template1} diff --git a/activejob/test/support/integration/adapters/queue_classic.rb b/activejob/test/support/integration/adapters/queue_classic.rb index 29c04bf625..b5d831428e 100644 --- a/activejob/test/support/integration/adapters/queue_classic.rb +++ b/activejob/test/support/integration/adapters/queue_classic.rb @@ -1,8 +1,8 @@ module QueueClassicJobsManager def setup - ENV['QC_DATABASE_URL'] ||= 'postgres:///active_jobs_qc_int_test' - ENV['QC_RAILS_DATABASE'] = 'false' - ENV['QC_LISTEN_TIME'] = "0.5" + ENV["QC_DATABASE_URL"] ||= "postgres:///active_jobs_qc_int_test" + ENV["QC_RAILS_DATABASE"] = "false" + ENV["QC_LISTEN_TIME"] = "0.5" ActiveJob::Base.queue_adapter = :queue_classic end @@ -11,8 +11,8 @@ module QueueClassicJobsManager end def start_workers - uri = URI.parse(ENV['QC_DATABASE_URL']) - user = uri.user||ENV['USER'] + uri = URI.parse(ENV["QC_DATABASE_URL"]) + user = uri.user||ENV["USER"] pass = uri.password db = uri.path[1..-1] %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'drop database if exists "#{db}"' -U #{user} -t template1} @@ -22,7 +22,7 @@ module QueueClassicJobsManager QC.default_conn_adapter.disconnect QC.default_conn_adapter = nil @pid = fork do - worker = QC::Worker.new(q_name: 'integration_tests') + worker = QC::Worker.new(q_name: "integration_tests") worker.start end @@ -32,6 +32,6 @@ module QueueClassicJobsManager end def stop_workers - Process.kill 'HUP', @pid + Process.kill "HUP", @pid end end diff --git a/activejob/test/support/integration/adapters/resque.rb b/activejob/test/support/integration/adapters/resque.rb index 912f4bc387..a21a1786c9 100644 --- a/activejob/test/support/integration/adapters/resque.rb +++ b/activejob/test/support/integration/adapters/resque.rb @@ -1,7 +1,7 @@ module ResqueJobsManager def setup ActiveJob::Base.queue_adapter = :resque - Resque.redis = Redis::Namespace.new 'active_jobs_int_test', redis: Redis.connect(url: "redis://127.0.0.1:6379/12", :thread_safe => true) + Resque.redis = Redis::Namespace.new "active_jobs_int_test", redis: Redis.connect(url: "redis://127.0.0.1:6379/12", thread_safe: true) Resque.logger = Rails.logger unless can_run? puts "Cannot run integration tests for resque. To be able to run integration tests for resque you need to install and start redis.\n" diff --git a/activejob/test/support/integration/adapters/sidekiq.rb b/activejob/test/support/integration/adapters/sidekiq.rb index 2f19d7dacc..bbb575f6da 100644 --- a/activejob/test/support/integration/adapters/sidekiq.rb +++ b/activejob/test/support/integration/adapters/sidekiq.rb @@ -1,10 +1,9 @@ -require 'sidekiq/api' +require "sidekiq/api" -require 'sidekiq/testing' +require "sidekiq/testing" Sidekiq::Testing.disable! module SidekiqJobsManager - def setup ActiveJob::Base.queue_adapter = :sidekiq unless can_run? @@ -29,7 +28,7 @@ module SidekiqJobsManager # Sidekiq is not warning-clean :( $VERBOSE = false - $stdin.reopen('/dev/null') + $stdin.reopen("/dev/null") $stdout.sync = true $stderr.sync = true @@ -49,12 +48,11 @@ module SidekiqJobsManager self_write.puts("TERM") end - require 'sidekiq/launcher' - sidekiq = Sidekiq::Launcher.new({queues: ["integration_tests"], + require "sidekiq/launcher" + sidekiq = Sidekiq::Launcher.new(queues: ["integration_tests"], environment: "test", concurrency: 1, - timeout: 1, - }) + timeout: 1) Sidekiq.average_scheduled_poll_interval = 0.5 Sidekiq.options[:poll_interval_average] = 1 begin @@ -79,7 +77,7 @@ module SidekiqJobsManager def stop_workers if @pid - Process.kill 'TERM', @pid + Process.kill "TERM", @pid Process.wait @pid end end diff --git a/activejob/test/support/integration/adapters/sneakers.rb b/activejob/test/support/integration/adapters/sneakers.rb index 875803a2d8..08743c1f05 100644 --- a/activejob/test/support/integration/adapters/sneakers.rb +++ b/activejob/test/support/integration/adapters/sneakers.rb @@ -1,6 +1,6 @@ -require 'sneakers/runner' -require 'sneakers/publisher' -require 'timeout' +require "sneakers/runner" +require "sneakers/publisher" +require "timeout" module Sneakers class Publisher @@ -12,20 +12,19 @@ module Sneakers end end - module SneakersJobsManager def setup ActiveJob::Base.queue_adapter = :sneakers - Sneakers.configure :heartbeat => 2, - :amqp => 'amqp://guest:guest@localhost:5672', - :vhost => '/', - :exchange => 'active_jobs_sneakers_int_test', - :exchange_type => :direct, - :daemonize => true, - :threads => 1, - :workers => 1, - :pid_path => Rails.root.join("tmp/sneakers.pid").to_s, - :log => Rails.root.join("log/sneakers.log").to_s + Sneakers.configure heartbeat: 2, + amqp: "amqp://guest:guest@localhost:5672", + vhost: "/", + exchange: "active_jobs_sneakers_int_test", + exchange_type: :direct, + daemonize: true, + threads: 1, + workers: 1, + pid_path: Rails.root.join("tmp/sneakers.pid").to_s, + log: Rails.root.join("log/sneakers.log").to_s unless can_run? puts "Cannot run integration tests for sneakers. To be able to run integration tests for sneakers you need to install and start rabbitmq.\n" exit @@ -60,8 +59,8 @@ module SneakersJobsManager end def stop_workers - Process.kill 'TERM', @pid - Process.kill 'TERM', File.open(Rails.root.join("tmp/sneakers.pid").to_s).read.to_i + Process.kill "TERM", @pid + Process.kill "TERM", File.open(Rails.root.join("tmp/sneakers.pid").to_s).read.to_i rescue end @@ -86,5 +85,4 @@ module SneakersJobsManager def bunny_queue @queue ||= bunny_publisher.exchange.channel.queue "integration_tests", durable: true end - end diff --git a/activejob/test/support/integration/dummy_app_template.rb b/activejob/test/support/integration/dummy_app_template.rb index 262ca72327..62f6fa13f6 100644 --- a/activejob/test/support/integration/dummy_app_template.rb +++ b/activejob/test/support/integration/dummy_app_template.rb @@ -1,19 +1,19 @@ -if ENV['AJ_ADAPTER'] == 'delayed_job' +if ENV["AJ_ADAPTER"] == "delayed_job" generate "delayed_job:active_record", "--quiet" end -rake("db:migrate") +rails_command("db:migrate") -initializer 'activejob.rb', <<-CODE +initializer "activejob.rb", <<-CODE require "#{File.expand_path("../jobs_manager.rb", __FILE__)}" JobsManager.current_manager.setup CODE -initializer 'i18n.rb', <<-CODE +initializer "i18n.rb", <<-CODE I18n.available_locales = [:en, :de] CODE -file 'app/jobs/test_job.rb', <<-CODE +file "app/jobs/test_job.rb", <<-CODE class TestJob < ActiveJob::Base queue_as :integration_tests diff --git a/activejob/test/support/integration/helper.rb b/activejob/test/support/integration/helper.rb index 4a1b0bfbcb..1aaee2c809 100644 --- a/activejob/test/support/integration/helper.rb +++ b/activejob/test/support/integration/helper.rb @@ -3,7 +3,7 @@ puts "\n\n*** rake aj:integration:#{ENV['AJ_ADAPTER']} ***\n" ENV["RAILS_ENV"] = "test" ActiveJob::Base.queue_name_prefix = nil -require 'rails/generators/rails/app/app_generator' +require "rails/generators/rails/app/app_generator" dummy_app_path = Dir.mktmpdir + "/dummy" dummy_app_template = File.expand_path("../dummy_app_template.rb", __FILE__) @@ -14,12 +14,12 @@ Rails::Generators::AppGenerator.start args require "#{dummy_app_path}/config/environment.rb" -ActiveRecord::Migrator.migrations_paths = [ Rails.root.join('db/migrate').to_s ] -require 'rails/test_help' +ActiveRecord::Migrator.migrations_paths = [ Rails.root.join("db/migrate").to_s ] +require "rails/test_help" Rails.backtrace_cleaner.remove_silencers! -require_relative 'test_case_helpers' +require_relative "test_case_helpers" ActiveSupport::TestCase.include(TestCaseHelpers) JobsManager.current_manager.start_workers diff --git a/activejob/test/support/integration/jobs_manager.rb b/activejob/test/support/integration/jobs_manager.rb index 78d48e8d9a..488756437b 100644 --- a/activejob/test/support/integration/jobs_manager.rb +++ b/activejob/test/support/integration/jobs_manager.rb @@ -3,7 +3,7 @@ class JobsManager attr :adapter_name def self.current_manager - @@managers[ENV['AJ_ADAPTER']] ||= new(ENV['AJ_ADAPTER']) + @@managers[ENV["AJ_ADAPTER"]] ||= new(ENV["AJ_ADAPTER"]) end def initialize(adapter_name) diff --git a/activejob/test/support/integration/test_case_helpers.rb b/activejob/test/support/integration/test_case_helpers.rb index 9897f76fd0..da8638895b 100644 --- a/activejob/test/support/integration/test_case_helpers.rb +++ b/activejob/test/support/integration/test_case_helpers.rb @@ -1,5 +1,5 @@ -require 'active_support/concern' -require 'support/integration/jobs_manager' +require "active_support/core_ext/string/inflections" +require "support/integration/jobs_manager" module TestCaseHelpers extend ActiveSupport::Concern @@ -28,7 +28,8 @@ module TestCaseHelpers end def adapter_is?(*adapter_class_symbols) - adapter_class_symbols.map(&:to_s).include?(ActiveJob::Base.queue_adapter.class.name.split("::").last.gsub(/Adapter$/, '').underscore) + adapter = ActiveJob::Base.queue_adapter.class.name.demodulize.chomp("Adapter").underscore + adapter_class_symbols.map(&:to_s).include? adapter end def wait_for_jobs_to_finish_for(seconds=60) diff --git a/activejob/test/support/que/inline.rb b/activejob/test/support/que/inline.rb index 0950e52d28..55eb0d01ef 100644 --- a/activejob/test/support/que/inline.rb +++ b/activejob/test/support/que/inline.rb @@ -1,4 +1,4 @@ -require 'que' +require "que" Que::Job.class_eval do class << self; alias_method :original_enqueue, :enqueue; end @@ -9,6 +9,6 @@ Que::Job.class_eval do options.delete(:priority) args << options unless options.empty? end - self.run(*args) + run(*args) end end diff --git a/activejob/test/support/queue_classic/inline.rb b/activejob/test/support/queue_classic/inline.rb index 5743d5bbb5..dcd834ecce 100644 --- a/activejob/test/support/queue_classic/inline.rb +++ b/activejob/test/support/queue_classic/inline.rb @@ -1,21 +1,21 @@ -require 'queue_classic' +require "queue_classic" module QC class Queue def enqueue(method, *args) - receiver_str, _, message = method.rpartition('.') + receiver_str, _, message = method.rpartition(".") receiver = eval(receiver_str) receiver.send(message, *args) end def enqueue_in(seconds, method, *args) - receiver_str, _, message = method.rpartition('.') + receiver_str, _, message = method.rpartition(".") receiver = eval(receiver_str) receiver.send(message, *args) end def enqueue_at(not_before, method, *args) - receiver_str, _, message = method.rpartition('.') + receiver_str, _, message = method.rpartition(".") receiver = eval(receiver_str) receiver.send(message, *args) end diff --git a/activejob/test/support/sneakers/inline.rb b/activejob/test/support/sneakers/inline.rb index 16d9b830fa..3cdc54e6d5 100644 --- a/activejob/test/support/sneakers/inline.rb +++ b/activejob/test/support/sneakers/inline.rb @@ -1,4 +1,4 @@ -require 'sneakers' +require "sneakers" module Sneakers module Worker diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 318e507ff1..10f1de6706 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,137 +1,12 @@ -## Rails 5.0.0.beta2 (February 01, 2016) ## +* Removed deprecated `:tokenizer` in the length validator. -* No changes. - - -## Rails 5.0.0.beta1 (December 18, 2015) ## - -* Validate multiple contexts on `valid?` and `invalid?` at once. - - Example: - - class Person - include ActiveModel::Validations - - attr_reader :name, :title - validates_presence_of :name, on: :create - validates_presence_of :title, on: :update - end - - person = Person.new - person.valid?([:create, :update]) # => false - person.errors.messages # => {:name=>["can't be blank"], :title=>["can't be blank"]} - - *Dmitry Polushkin* - -* Add case_sensitive option for confirmation validator in models. - - *Akshat Sharma* - -* Ensure `method_missing` is called for methods passed to - `ActiveModel::Serialization#serializable_hash` that don't exist. - - *Jay Elaraj* - -* Remove `ActiveModel::Serializers::Xml` from core. - - *Zachary Scott* - -* Add `ActiveModel::Dirty#[attr_name]_previously_changed?` and - `ActiveModel::Dirty#[attr_name]_previous_change` to improve access - to recorded changes after the model has been saved. - - It makes the dirty-attributes query methods consistent before and after - saving. - - *Fernando Tapia Rico* - -* Deprecate the `:tokenizer` option for `validates_length_of`, in favor of - plain Ruby. - - *Sean Griffin* - -* Deprecate `ActiveModel::Errors#add_on_empty` and `ActiveModel::Errors#add_on_blank` - with no replacement. - - *Wojciech Wnętrzak* - -* Deprecate `ActiveModel::Errors#get`, `ActiveModel::Errors#set` and - `ActiveModel::Errors#[]=` methods that have inconsistent behavior. - - *Wojciech Wnętrzak* - -* Allow symbol as values for `tokenize` of `LengthValidator`. - - *Kensuke Naito* - -* Assigning an unknown attribute key to an `ActiveModel` instance during initialization - will now raise `ActiveModel::AttributeAssignment::UnknownAttributeError` instead of - `NoMethodError`. - - Example: - - User.new(foo: 'some value') - # => ActiveModel::AttributeAssignment::UnknownAttributeError: unknown attribute 'foo' for User. - - *Eugene Gilburg* - -* Extracted `ActiveRecord::AttributeAssignment` to `ActiveModel::AttributeAssignment` - allowing to use it for any object as an includable module. - - Example: - - class Cat - include ActiveModel::AttributeAssignment - attr_accessor :name, :status - end - - cat = Cat.new - cat.assign_attributes(name: "Gorby", status: "yawning") - cat.name # => 'Gorby' - cat.status # => 'yawning' - cat.assign_attributes(status: "sleeping") - cat.name # => 'Gorby' - cat.status # => 'sleeping' - - *Bogdan Gusiev* - -* Add `ActiveModel::Errors#details` - - To be able to return type of used validator, one can now call `details` - on errors instance. - - Example: - - class User < ActiveRecord::Base - validates :name, presence: true - end - - user = User.new; user.valid?; user.errors.details - => {name: [{error: :blank}]} - - *Wojciech Wnętrzak* - -* Change validates_acceptance_of to accept true by default. + *Rafael Mendonça França* - The default for validates_acceptance_of is now "1" and true. - In the past, only "1" was the default and you were required to add - accept: true. +* Removed deprecated methods in `ActiveModel::Errors`. -* Remove deprecated `ActiveModel::Dirty#reset_#{attribute}` and - `ActiveModel::Dirty#reset_changes`. + `#get`, `#set`, `[]=`, `add_on_empty` and `add_on_blank`. *Rafael Mendonça França* -* Change the way in which callback chains can be halted. - - The preferred method to halt a callback chain from now on is to explicitly - `throw(:abort)`. - In the past, returning `false` in an Active Model `before_` callback had - the side effect of halting the callback chain. - This is not recommended anymore and, depending on the value of the - `ActiveSupport.halt_callback_chains_on_return_false` option, will - either not work at all or display a deprecation warning. - - *claudiob* -Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activemodel/CHANGELOG.md) for previous changes. +Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/activemodel/CHANGELOG.md) for previous changes. diff --git a/activemodel/Rakefile b/activemodel/Rakefile index 9982d49bcb..c7f97a4258 100644 --- a/activemodel/Rakefile +++ b/activemodel/Rakefile @@ -1,11 +1,10 @@ -require 'rake/testtask' +require "rake/testtask" dir = File.dirname(__FILE__) -task :default => :test +task default: :test task :package -task "package:clean" Rake::TestTask.new do |t| t.libs << "test" @@ -18,7 +17,7 @@ end namespace :test do task :isolated do Dir.glob("#{dir}/test/**/*_test.rb").all? do |file| - sh(Gem.ruby, '-w', "-I#{dir}/lib", "-I#{dir}/test", file) - end or raise "Failures" + sh(Gem.ruby, "-w", "-I#{dir}/lib", "-I#{dir}/test", file) + end || raise("Failures") end end diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec index 53206580f0..fd715f6ba9 100644 --- a/activemodel/activemodel.gemspec +++ b/activemodel/activemodel.gemspec @@ -1,22 +1,22 @@ -version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip +version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY - s.name = 'activemodel' + s.name = "activemodel" s.version = version - s.summary = 'A toolkit for building modeling frameworks (part of Rails).' - s.description = 'A toolkit for building modeling frameworks like Active Record. Rich support for attributes, callbacks, validations, serialization, internationalization, and testing.' + s.summary = "A toolkit for building modeling frameworks (part of Rails)." + s.description = "A toolkit for building modeling frameworks like Active Record. Rich support for attributes, callbacks, validations, serialization, internationalization, and testing." - s.required_ruby_version = '>= 2.2.2' + s.required_ruby_version = ">= 2.2.2" - s.license = 'MIT' + s.license = "MIT" - s.author = 'David Heinemeier Hansson' - s.email = 'david@loudthinking.com' - s.homepage = 'http://www.rubyonrails.org' + s.author = "David Heinemeier Hansson" + s.email = "david@loudthinking.com" + s.homepage = "http://rubyonrails.org" - s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*'] - s.require_path = 'lib' + s.files = Dir["CHANGELOG.md", "MIT-LICENSE", "README.rdoc", "lib/**/*"] + s.require_path = "lib" - s.add_dependency 'activesupport', version + s.add_dependency "activesupport", version end diff --git a/activemodel/bin/test b/activemodel/bin/test index 404cabba51..84a05bba08 100755 --- a/activemodel/bin/test +++ b/activemodel/bin/test @@ -1,4 +1,6 @@ #!/usr/bin/env ruby -COMPONENT_ROOT = File.expand_path("../../", __FILE__) + +COMPONENT_ROOT = File.expand_path("..", __dir__) require File.expand_path("../tools/test", COMPONENT_ROOT) + exit Minitest.run(ARGV) diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index b95c174d6e..a9b0663940 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -21,24 +21,24 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ -require 'active_support' -require 'active_support/rails' -require 'active_model/version' +require "active_support" +require "active_support/rails" +require "active_model/version" module ActiveModel extend ActiveSupport::Autoload autoload :AttributeAssignment autoload :AttributeMethods - autoload :BlockValidator, 'active_model/validator' + autoload :BlockValidator, "active_model/validator" autoload :Callbacks autoload :Conversion autoload :Dirty - autoload :EachValidator, 'active_model/validator' + autoload :EachValidator, "active_model/validator" autoload :ForbiddenAttributesProtection autoload :Lint autoload :Model - autoload :Name, 'active_model/naming' + autoload :Name, "active_model/naming" autoload :Naming autoload :SecurePassword autoload :Serialization @@ -49,8 +49,9 @@ module ActiveModel eager_autoload do autoload :Errors - autoload :StrictValidationFailed, 'active_model/errors' - autoload :UnknownAttributeError, 'active_model/errors' + autoload :RangeError, "active_model/errors" + autoload :StrictValidationFailed, "active_model/errors" + autoload :UnknownAttributeError, "active_model/errors" end module Serializers @@ -68,5 +69,5 @@ module ActiveModel end ActiveSupport.on_load(:i18n) do - I18n.load_path << File.dirname(__FILE__) + '/active_model/locale/en.yml' + I18n.load_path << File.dirname(__FILE__) + "/active_model/locale/en.yml" end diff --git a/activemodel/lib/active_model/attribute_assignment.rb b/activemodel/lib/active_model/attribute_assignment.rb index 62014cd1cd..7dad3b6dff 100644 --- a/activemodel/lib/active_model/attribute_assignment.rb +++ b/activemodel/lib/active_model/attribute_assignment.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/hash/keys' +require "active_support/core_ext/hash/keys" module ActiveModel module AttributeAssignment @@ -35,18 +35,18 @@ module ActiveModel private - def _assign_attributes(attributes) - attributes.each do |k, v| - _assign_attribute(k, v) + def _assign_attributes(attributes) + attributes.each do |k, v| + _assign_attribute(k, v) + end end - end - def _assign_attribute(k, v) - if respond_to?("#{k}=") - public_send("#{k}=", v) - else - raise UnknownAttributeError.new(self, k) + def _assign_attribute(k, v) + if respond_to?("#{k}=") + public_send("#{k}=", v) + else + raise UnknownAttributeError.new(self, k) + end end - end end end diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index cc6285f932..1441b146f8 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -1,5 +1,5 @@ -require 'concurrent/map' -require 'mutex_m' +require "concurrent/map" +require "mutex_m" module ActiveModel # Raised when an attribute is not defined. @@ -366,7 +366,7 @@ module ActiveModel # 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: - defn = if name =~ NAME_COMPILABLE_REGEXP + defn = if NAME_COMPILABLE_REGEXP.match?(name) "def #{name}(*args)" else "define_method(:'#{name}') do |*args|" @@ -374,7 +374,7 @@ module ActiveModel extra = (extra.map!(&:inspect) << "*args").join(", ".freeze) - target = if send =~ CALL_COMPILABLE_REGEXP + target = if CALL_COMPILABLE_REGEXP.match?(send) "#{"self." unless include_private}#{send}(#{extra})" else "send(:'#{send}', #{extra})" @@ -393,7 +393,7 @@ module ActiveModel AttributeMethodMatch = Struct.new(:target, :attr_name, :method_name) def initialize(options = {}) - @prefix, @suffix = options.fetch(:prefix, ''), options.fetch(:suffix, '') + @prefix, @suffix = options.fetch(:prefix, ""), options.fetch(:suffix, "") @regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/ @method_missing_target = "#{@prefix}attribute#{@suffix}" @method_name = "#{prefix}%s#{suffix}" diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb index 0d6a3dc52d..283090e380 100644 --- a/activemodel/lib/active_model/callbacks.rb +++ b/activemodel/lib/active_model/callbacks.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/array/extract_options' +require "active_support/core_ext/array/extract_options" module ActiveModel # == Active \Model \Callbacks @@ -122,28 +122,28 @@ module ActiveModel private - def _define_before_model_callback(klass, callback) #:nodoc: - klass.define_singleton_method("before_#{callback}") do |*args, &block| - set_callback(:"#{callback}", :before, *args, &block) + def _define_before_model_callback(klass, callback) #:nodoc: + klass.define_singleton_method("before_#{callback}") do |*args, &block| + set_callback(:"#{callback}", :before, *args, &block) + end end - end - def _define_around_model_callback(klass, callback) #:nodoc: - klass.define_singleton_method("around_#{callback}") do |*args, &block| - set_callback(:"#{callback}", :around, *args, &block) + def _define_around_model_callback(klass, callback) #:nodoc: + klass.define_singleton_method("around_#{callback}") do |*args, &block| + set_callback(:"#{callback}", :around, *args, &block) + end end - end - def _define_after_model_callback(klass, callback) #:nodoc: - klass.define_singleton_method("after_#{callback}") do |*args, &block| - options = args.extract_options! - options[:prepend] = true - conditional = ActiveSupport::Callbacks::Conditionals::Value.new { |v| - v != false - } - options[:if] = Array(options[:if]) << conditional - set_callback(:"#{callback}", :after, *(args << options), &block) + def _define_after_model_callback(klass, callback) #:nodoc: + klass.define_singleton_method("after_#{callback}") do |*args, &block| + options = args.extract_options! + options[:prepend] = true + conditional = ActiveSupport::Callbacks::Conditionals::Value.new { |v| + v != false + } + options[:if] = Array(options[:if]) << conditional + set_callback(:"#{callback}", :after, *(args << options), &block) + end end - end end end diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb index 9de6ea65be..12687c70d3 100644 --- a/activemodel/lib/active_model/conversion.rb +++ b/activemodel/lib/active_model/conversion.rb @@ -40,15 +40,19 @@ module ActiveModel self end - # Returns an Array of all key attributes if any is set, regardless if - # the object is persisted or not. Returns +nil+ if there are no key attributes. + # Returns an Array of all key attributes if any of the attributes is set, whether or not + # the object is persisted. Returns +nil+ if there are no key attributes. # # class Person # include ActiveModel::Conversion # attr_accessor :id + # + # def initialize(id) + # @id = id + # end # end # - # person = Person.create(id: 1) + # person = Person.new(1) # person.to_key # => [1] def to_key key = respond_to?(:id) && id @@ -61,15 +65,20 @@ module ActiveModel # class Person # include ActiveModel::Conversion # attr_accessor :id + # + # def initialize(id) + # @id = id + # end + # # def persisted? # true # end # end # - # person = Person.create(id: 1) + # person = Person.new(1) # person.to_param # => "1" def to_param - (persisted? && key = to_key) ? key.join('-') : nil + (persisted? && key = to_key) ? key.join("-") : nil end # Returns a +string+ identifying the path associated with the object. diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index 6e2e5afd1b..6e0af99ad7 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -1,5 +1,5 @@ -require 'active_support/hash_with_indifferent_access' -require 'active_support/core_ext/object/duplicable' +require "active_support/hash_with_indifferent_access" +require "active_support/core_ext/object/duplicable" module ActiveModel # == Active \Model \Dirty @@ -26,8 +26,8 @@ module ActiveModel # # define_attribute_methods :name # - # def initialize(name) - # @name = name + # def initialize + # @name = nil # end # # def name @@ -58,7 +58,7 @@ module ActiveModel # # A newly instantiated +Person+ object is unchanged: # - # person = Person.new("Uncle Bob") + # person = Person.new # person.changed? # => false # # Change the name: @@ -66,11 +66,11 @@ module ActiveModel # person.name = 'Bob' # person.changed? # => true # person.name_changed? # => true - # person.name_changed?(from: "Uncle Bob", to: "Bob") # => true - # person.name_was # => "Uncle Bob" - # person.name_change # => ["Uncle Bob", "Bob"] + # person.name_changed?(from: nil, to: "Bob") # => true + # person.name_was # => nil + # person.name_change # => [nil, "Bob"] # person.name = 'Bill' - # person.name_change # => ["Uncle Bob", "Bill"] + # person.name_change # => [nil, "Bill"] # # Save the changes: # @@ -80,9 +80,9 @@ module ActiveModel # # Reset the changes: # - # person.previous_changes # => {"name" => ["Uncle Bob", "Bill"]} + # person.previous_changes # => {"name" => [nil, "Bill"]} # person.name_previously_changed? # => true - # person.name_previous_change # => ["Uncle Bob", "Bill"] + # person.name_previous_change # => [nil, "Bill"] # person.reload! # person.previous_changes # => {} # @@ -119,10 +119,13 @@ module ActiveModel extend ActiveSupport::Concern include ActiveModel::AttributeMethods + OPTION_NOT_GIVEN = Object.new # :nodoc: + private_constant :OPTION_NOT_GIVEN + included do - attribute_method_suffix '_changed?', '_change', '_will_change!', '_was' - attribute_method_suffix '_previously_changed?', '_previous_change' - attribute_method_affix prefix: 'restore_', suffix: '!' + attribute_method_suffix "_changed?", "_change", "_will_change!", "_was" + attribute_method_suffix "_previously_changed?", "_previous_change" + attribute_method_affix prefix: "restore_", suffix: "!" end # Returns +true+ if any of the attributes have unsaved changes, +false+ otherwise. @@ -174,11 +177,10 @@ module ActiveModel end # Handles <tt>*_changed?</tt> for +method_missing+. - def attribute_changed?(attr, options = {}) #:nodoc: - result = changes_include?(attr) - result &&= options[:to] == __send__(attr) if options.key?(:to) - result &&= options[:from] == changed_attributes[attr] if options.key?(:from) - result + def attribute_changed?(attr, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) # :nodoc: + !!changes_include?(attr) && + (to == OPTION_NOT_GIVEN || to == __send__(attr)) && + (from == OPTION_NOT_GIVEN || from == changed_attributes[attr]) end # Handles <tt>*_was</tt> for +method_missing+. @@ -187,7 +189,7 @@ module ActiveModel end # Handles <tt>*_previously_changed?</tt> for +method_missing+. - def attribute_previously_changed?(attr, options = {}) #:nodoc: + def attribute_previously_changed?(attr) #:nodoc: previous_changes_include?(attr) end diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index ea69e7549e..17ed5de420 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -1,7 +1,7 @@ -require 'active_support/core_ext/array/conversions' -require 'active_support/core_ext/string/inflections' -require 'active_support/core_ext/object/deep_dup' -require 'active_support/core_ext/string/filters' +require "active_support/core_ext/array/conversions" +require "active_support/core_ext/string/inflections" +require "active_support/core_ext/object/deep_dup" +require "active_support/core_ext/string/filters" module ActiveModel # == Active \Model \Errors @@ -71,8 +71,8 @@ module ActiveModel # end def initialize(base) @base = base - @messages = Hash.new { |messages, attribute| messages[attribute] = [] } - @details = Hash.new { |details, attribute| details[attribute] = [] } + @messages = apply_default_array({}) + @details = apply_default_array({}) end def initialize_dup(other) # :nodoc: @@ -110,46 +110,16 @@ module ActiveModel # person.errors.include?(:name) # => true # person.errors.include?(:age) # => false def include?(attribute) - messages[attribute].present? + messages.key?(attribute) && messages[attribute].present? end alias :has_key? :include? alias :key? :include? - # Get messages for +key+. - # - # person.errors.messages # => {:name=>["cannot be nil"]} - # person.errors.get(:name) # => ["cannot be nil"] - # person.errors.get(:age) # => [] - def get(key) - ActiveSupport::Deprecation.warn(<<-MESSAGE.squish) - ActiveModel::Errors#get is deprecated and will be removed in Rails 5.1. - - To achieve the same use model.errors[:#{key}]. - MESSAGE - - messages[key] - end - - # Set messages for +key+ to +value+. - # - # person.errors[:name] # => ["cannot be nil"] - # person.errors.set(:name, ["can't be nil"]) - # person.errors[:name] # => ["can't be nil"] - def set(key, value) - ActiveSupport::Deprecation.warn(<<-MESSAGE.squish) - ActiveModel::Errors#set is deprecated and will be removed in Rails 5.1. - - Use model.errors.add(:#{key}, #{value.inspect}) instead. - MESSAGE - - messages[key] = value - end - # Delete messages for +key+. Returns the deleted messages. # - # person.errors[:name] # => ["cannot be nil"] + # person.errors[:name] # => ["cannot be nil"] # person.errors.delete(:name) # => ["cannot be nil"] - # person.errors[:name] # => [] + # person.errors[:name] # => [] def delete(key) details.delete(key) messages.delete(key) @@ -173,20 +143,6 @@ module ActiveModel messages[attribute.to_sym] end - # Adds to the supplied attribute the supplied error message. - # - # person.errors[:name] = "must be set" - # person.errors[:name] # => ['must be set'] - def []=(attribute, error) - ActiveSupport::Deprecation.warn(<<-MESSAGE.squish) - ActiveModel::Errors#[]= is deprecated and will be removed in Rails 5.1. - - Use model.errors.add(:#{attribute}, #{error.inspect}) instead. - MESSAGE - - messages[attribute.to_sym] << error - end - # Iterates through each error key, value pair in the error messages hash. # Yields the attribute and the error for that attribute. If the attribute # has more than one error message, yields once for each error message. @@ -276,11 +232,11 @@ module ActiveModel # person.errors.to_hash(true) # => {:name=>["name cannot be nil"]} def to_hash(full_messages = false) if full_messages - self.messages.each_with_object({}) do |(attribute, array), messages| + messages.each_with_object({}) do |(attribute, array), messages| messages[attribute] = array.map { |message| full_message(attribute, message) } end else - self.messages.dup + without_default_proc(messages) end end @@ -310,9 +266,9 @@ module ActiveModel # <tt>:strict</tt> option can also be set to any other exception. # # person.errors.add(:name, :invalid, strict: true) - # # => ActiveModel::StrictValidationFailed: name is invalid + # # => ActiveModel::StrictValidationFailed: Name is invalid # person.errors.add(:name, :invalid, strict: NameIsInvalid) - # # => NameIsInvalid: name is invalid + # # => NameIsInvalid: Name is invalid # # person.errors.messages # => {} # @@ -327,7 +283,7 @@ module ActiveModel # # => {:base=>[{error: :name_or_email_blank}]} def add(attribute, message = :invalid, options = {}) message = message.call if message.respond_to?(:call) - detail = normalize_detail(attribute, message, options) + detail = normalize_detail(message, options) message = normalize_message(attribute, message, options) if exception = options[:strict] exception = ActiveModel::StrictValidationFailed if exception == true @@ -338,54 +294,22 @@ module ActiveModel messages[attribute.to_sym] << message end - # Will add an error message to each of the attributes in +attributes+ - # that is empty. - # - # person.errors.add_on_empty(:name) - # person.errors.messages - # # => {:name=>["can't be empty"]} - def add_on_empty(attributes, options = {}) - ActiveSupport::Deprecation.warn(<<-MESSAGE.squish) - ActiveModel::Errors#add_on_empty is deprecated and will be removed in Rails 5.1 - - To achieve the same use: - - errors.add(attribute, :empty, options) if value.nil? || value.empty? - MESSAGE - - Array(attributes).each do |attribute| - value = @base.send(:read_attribute_for_validation, attribute) - is_empty = value.respond_to?(:empty?) ? value.empty? : false - add(attribute, :empty, options) if value.nil? || is_empty - end - end - - # Will add an error message to each of the attributes in +attributes+ that - # is blank (using Object#blank?). - # - # person.errors.add_on_blank(:name) - # person.errors.messages - # # => {:name=>["can't be blank"]} - def add_on_blank(attributes, options = {}) - ActiveSupport::Deprecation.warn(<<-MESSAGE.squish) - ActiveModel::Errors#add_on_blank is deprecated and will be removed in Rails 5.1 - - To achieve the same use: - - errors.add(attribute, :empty, options) if value.blank? - MESSAGE - - Array(attributes).each do |attribute| - value = @base.send(:read_attribute_for_validation, attribute) - add(attribute, :blank, options) if value.blank? - end - end - # Returns +true+ if an error on the attribute with the given message is - # present, +false+ otherwise. +message+ is treated the same as for +add+. + # present, or +false+ otherwise. +message+ is treated the same as for +add+. # # person.errors.add :name, :blank - # person.errors.added? :name, :blank # => true + # person.errors.added? :name, :blank # => true + # person.errors.added? :name, "can't be blank" # => true + # + # If the error message requires an option, then it returns +true+ with + # the correct option, or +false+ with an incorrect or missing option. + # + # person.errors.add :name, :too_long, { count: 25 } + # person.errors.added? :name, :too_long, count: 25 # => true + # person.errors.added? :name, "is too long (maximum is 25 characters)" # => true + # person.errors.added? :name, :too_long, count: 24 # => false + # person.errors.added? :name, :too_long # => false + # person.errors.added? :name, "is too long" # => false def added?(attribute, message = :invalid, options = {}) message = message.call if message.respond_to?(:call) message = normalize_message(attribute, message, options) @@ -426,13 +350,12 @@ module ActiveModel # person.errors.full_message(:name, 'is invalid') # => "Name is invalid" def full_message(attribute, message) return message if attribute == :base - attr_name = attribute.to_s.tr('.', '_').humanize + attr_name = attribute.to_s.tr(".", "_").humanize attr_name = @base.class.human_attribute_name(attribute, default: attr_name) - I18n.t(:"errors.format", { + I18n.t(:"errors.format", default: "%{attribute} %{message}", attribute: attr_name, - message: message - }) + message: message) end # Translates an error message in its default scope @@ -486,12 +409,23 @@ module ActiveModel default: defaults, model: @base.model_name.human, attribute: @base.class.human_attribute_name(attribute), - value: value + value: value, + object: @base }.merge!(options) I18n.translate(key, options) end + def marshal_dump + [@base, without_default_proc(@messages), without_default_proc(@details)] + end + + def marshal_load(array) + @base, @messages, @details = array + apply_default_array(@messages) + apply_default_array(@details) + end + private def normalize_message(attribute, message, options) case message @@ -502,9 +436,20 @@ module ActiveModel end end - def normalize_detail(attribute, message, options) + def normalize_detail(message, options) { error: message }.merge(options.except(*CALLBACKS_OPTIONS + MESSAGE_OPTIONS)) end + + def without_default_proc(hash) + hash.dup.tap do |new_h| + new_h.default_proc = nil + end + end + + def apply_default_array(hash) + hash.default_proc = proc { |h, key| h[key] = [] } + hash + end end # Raised when a validation cannot be corrected by end users and are considered @@ -525,7 +470,20 @@ module ActiveModel class StrictValidationFailed < StandardError end + # Raised when attribute values are out of range. + class RangeError < ::RangeError + end + # Raised when unknown attributes are supplied via mass assignment. + # + # class Person + # include ActiveModel::AttributeAssignment + # include ActiveModel::Validations + # end + # + # person = Person.new + # person.assign_attributes(name: 'Gorby') + # # => ActiveModel::UnknownAttributeError: unknown attribute 'name' for Person. class UnknownAttributeError < NoMethodError attr_reader :record, :attribute diff --git a/activemodel/lib/active_model/gem_version.rb b/activemodel/lib/active_model/gem_version.rb index 94514a0657..4a8ee915cf 100644 --- a/activemodel/lib/active_model/gem_version.rb +++ b/activemodel/lib/active_model/gem_version.rb @@ -6,9 +6,9 @@ module ActiveModel module VERSION MAJOR = 5 - MINOR = 0 + MINOR = 1 TINY = 0 - PRE = "beta2" + PRE = "alpha" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb index 010eaeb170..291a545528 100644 --- a/activemodel/lib/active_model/lint.rb +++ b/activemodel/lib/active_model/lint.rb @@ -20,7 +20,6 @@ module ActiveModel # to <tt>to_model</tt>. It is perfectly fine for <tt>to_model</tt> to return # +self+. module Tests - # Passes if the object's model responds to <tt>to_key</tt> and if calling # this method returns +nil+ when the object is not persisted. # Fails otherwise. diff --git a/activemodel/lib/active_model/model.rb b/activemodel/lib/active_model/model.rb index dac8d549a7..f5765c0c54 100644 --- a/activemodel/lib/active_model/model.rb +++ b/activemodel/lib/active_model/model.rb @@ -1,5 +1,4 @@ module ActiveModel - # == Active \Model \Basic \Model # # Includes the required interface for an object to interact with diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index d86ef6224e..840fc663c0 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -1,7 +1,6 @@ -require 'active_support/core_ext/hash/except' -require 'active_support/core_ext/module/introspection' -require 'active_support/core_ext/module/remove_method' -require 'active_support/core_ext/module/delegation' +require "active_support/core_ext/hash/except" +require "active_support/core_ext/module/introspection" +require "active_support/core_ext/module/remove_method" module ActiveModel class Name @@ -149,7 +148,7 @@ module ActiveModel raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if @name.blank? - @unnamespaced = @name.sub(/^#{namespace.name}::/, '') if namespace + @unnamespaced = @name.sub(/^#{namespace.name}::/, "") if namespace @klass = klass @singular = _singularize(@name) @plural = ActiveSupport::Inflector.pluralize(@singular) @@ -191,9 +190,9 @@ module ActiveModel private - def _singularize(string) - ActiveSupport::Inflector.underscore(string).tr('/'.freeze, '_'.freeze) - end + def _singularize(string) + ActiveSupport::Inflector.underscore(string).tr("/".freeze, "_".freeze) + end end # == Active \Model \Naming diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb index 89da74efa8..1c0fe92bc0 100644 --- a/activemodel/lib/active_model/secure_password.rb +++ b/activemodel/lib/active_model/secure_password.rb @@ -55,7 +55,7 @@ module ActiveModel # This is to avoid ActiveModel (and by extension the entire framework) # being dependent on a binary library. begin - require 'bcrypt' + require "bcrypt" rescue LoadError $stderr.puts "You don't have bcrypt installed in your application. Please add it to your Gemfile and run bundle install" raise diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb index 70e10fa06d..77834f26fc 100644 --- a/activemodel/lib/active_model/serialization.rb +++ b/activemodel/lib/active_model/serialization.rb @@ -1,5 +1,5 @@ -require 'active_support/core_ext/hash/except' -require 'active_support/core_ext/hash/slice' +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/slice" module ActiveModel # == Active \Model \Serialization diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb index b64a8299e6..b5e030a59b 100644 --- a/activemodel/lib/active_model/serializers/json.rb +++ b/activemodel/lib/active_model/serializers/json.rb @@ -1,4 +1,4 @@ -require 'active_support/json' +require "active_support/json" module ActiveModel module Serializers diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb index 8470915abb..b8cf43cc10 100644 --- a/activemodel/lib/active_model/translation.rb +++ b/activemodel/lib/active_model/translation.rb @@ -1,5 +1,4 @@ module ActiveModel - # == Active \Model \Translation # # Provides integration between your object and the Rails internationalization @@ -31,7 +30,7 @@ module ActiveModel # ActiveModel::Errors#full_messages and # ActiveModel::Translation#human_attribute_name. def lookup_ancestors - self.ancestors.select { |x| x.respond_to?(:model_name) } + ancestors.select { |x| x.respond_to?(:model_name) } end # Transforms attribute names into a more human format, such as "First name" diff --git a/activemodel/lib/active_model/type.rb b/activemodel/lib/active_model/type.rb index bec851594f..0d3349e236 100644 --- a/activemodel/lib/active_model/type.rb +++ b/activemodel/lib/active_model/type.rb @@ -1,22 +1,22 @@ -require 'active_model/type/helpers' -require 'active_model/type/value' +require "active_model/type/helpers" +require "active_model/type/value" -require 'active_model/type/big_integer' -require 'active_model/type/binary' -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/big_integer" +require "active_model/type/binary" +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' +require "active_model/type/registry" module ActiveModel module Type @@ -27,7 +27,7 @@ module ActiveModel delegate :add_modifier, to: :registry # Add a new type to the registry, allowing it to be referenced as a - # symbol by ActiveModel::Attributes::ClassMethods#attribute. If your + # symbol by ActiveRecord::Attributes::ClassMethods#attribute. If your # type is only meant to be used with a specific database adapter, you can # do so by passing +adapter: :postgresql+. If your type has the same # name as a native type for the current adapter, an exception will be @@ -47,7 +47,7 @@ module ActiveModel register(:binary, Type::Binary) register(:boolean, Type::Boolean) register(:date, Type::Date) - register(:date_time, Type::DateTime) + register(:datetime, Type::DateTime) register(:decimal, Type::Decimal) register(:float, Type::Float) register(:immutable_string, Type::ImmutableString) diff --git a/activemodel/lib/active_model/type/big_integer.rb b/activemodel/lib/active_model/type/big_integer.rb index 4168cbfce7..3b629682fe 100644 --- a/activemodel/lib/active_model/type/big_integer.rb +++ b/activemodel/lib/active_model/type/big_integer.rb @@ -1,13 +1,13 @@ -require 'active_model/type/integer' +require "active_model/type/integer" module ActiveModel module Type class BigInteger < Integer # :nodoc: private - def max_value - ::Float::INFINITY - end + def max_value + ::Float::INFINITY + end end end end diff --git a/activemodel/lib/active_model/type/binary.rb b/activemodel/lib/active_model/type/binary.rb index a0cc45b4c3..819e4e4a96 100644 --- a/activemodel/lib/active_model/type/binary.rb +++ b/activemodel/lib/active_model/type/binary.rb @@ -38,7 +38,7 @@ module ActiveModel alias_method :to_str, :to_s def hex - @value.unpack('H*')[0] + @value.unpack("H*")[0] end def ==(other) diff --git a/activemodel/lib/active_model/type/boolean.rb b/activemodel/lib/active_model/type/boolean.rb index c1bce98c87..f2a47370a3 100644 --- a/activemodel/lib/active_model/type/boolean.rb +++ b/activemodel/lib/active_model/type/boolean.rb @@ -1,21 +1,32 @@ module ActiveModel module Type - class Boolean < Value # :nodoc: - FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set + # == Active \Model \Type \Boolean + # + # A class that behaves like a boolean type, including rules for coercion of user input. + # + # === Coercion + # Values set from user input will first be coerced into the appropriate ruby type. + # Coercion behavior is roughly mapped to Ruby's boolean semantics. + # + # - "false", "f" , "0", +0+ or any other value in +FALSE_VALUES+ will be coerced to +false+ + # - Empty strings are coerced to +nil+ + # - All other values will be coerced to +true+ + class Boolean < Value + FALSE_VALUES = [false, 0, "0", "f", "F", "false", "FALSE", "off", "OFF"].to_set - def type + def type # :nodoc: :boolean end private - def cast_value(value) - if value == '' - nil - else - !FALSE_VALUES.include?(value) + def cast_value(value) + if value == "" + nil + else + !FALSE_VALUES.include?(value) + end end - end end end end diff --git a/activemodel/lib/active_model/type/date.rb b/activemodel/lib/active_model/type/date.rb index f74243a22c..6e313fbca8 100644 --- a/activemodel/lib/active_model/type/date.rb +++ b/activemodel/lib/active_model/type/date.rb @@ -7,44 +7,48 @@ module ActiveModel :date end + def serialize(value) + cast(value) + end + def type_cast_for_schema(value) "'#{value.to_s(:db)}'" end private - def cast_value(value) - if value.is_a?(::String) - return if value.empty? - fast_string_to_date(value) || fallback_string_to_date(value) - elsif value.respond_to?(:to_date) - value.to_date - else - value + def cast_value(value) + if value.is_a?(::String) + return if value.empty? + fast_string_to_date(value) || fallback_string_to_date(value) + elsif value.respond_to?(:to_date) + value.to_date + else + value + end end - end - ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/ - def fast_string_to_date(string) - if string =~ ISO_DATE - new_date $1.to_i, $2.to_i, $3.to_i + ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/ + def fast_string_to_date(string) + if string =~ ISO_DATE + new_date $1.to_i, $2.to_i, $3.to_i + end end - end - def fallback_string_to_date(string) - new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday)) - end + def fallback_string_to_date(string) + new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday)) + end - def new_date(year, mon, mday) - if year && year != 0 - ::Date.new(year, mon, mday) rescue nil + def new_date(year, mon, mday) + if year && year != 0 + ::Date.new(year, mon, mday) rescue nil + end end - end - def value_from_multiparameter_assignment(*) - time = super - time && time.to_date - end + def value_from_multiparameter_assignment(*) + time = super + time && time.to_date + end end end end diff --git a/activemodel/lib/active_model/type/date_time.rb b/activemodel/lib/active_model/type/date_time.rb index 2f2df4320f..5cb0077e45 100644 --- a/activemodel/lib/active_model/type/date_time.rb +++ b/activemodel/lib/active_model/type/date_time.rb @@ -12,33 +12,33 @@ module ActiveModel private - def cast_value(value) - return apply_seconds_precision(value) unless value.is_a?(::String) - return if value.empty? + def cast_value(value) + return apply_seconds_precision(value) unless value.is_a?(::String) + return if value.empty? - fast_string_to_time(value) || fallback_string_to_time(value) - end + fast_string_to_time(value) || fallback_string_to_time(value) + end - # '0.123456' -> 123456 - # '1.123456' -> 123456 - def microseconds(time) - time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0 - end + # '0.123456' -> 123456 + # '1.123456' -> 123456 + def microseconds(time) + time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0 + end - def fallback_string_to_time(string) - time_hash = ::Date._parse(string) - time_hash[:sec_fraction] = microseconds(time_hash) + def fallback_string_to_time(string) + time_hash = ::Date._parse(string) + time_hash[:sec_fraction] = microseconds(time_hash) - new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset)) - end + new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset)) + end - def value_from_multiparameter_assignment(values_hash) - missing_parameter = (1..3).detect { |key| !values_hash.key?(key) } - if missing_parameter - raise ArgumentError, missing_parameter + def value_from_multiparameter_assignment(values_hash) + missing_parameter = (1..3).detect { |key| !values_hash.key?(key) } + if missing_parameter + raise ArgumentError, missing_parameter + end + super end - super - end end end end diff --git a/activemodel/lib/active_model/type/decimal.rb b/activemodel/lib/active_model/type/decimal.rb index d19d8baada..6c5c0451c6 100644 --- a/activemodel/lib/active_model/type/decimal.rb +++ b/activemodel/lib/active_model/type/decimal.rb @@ -15,38 +15,47 @@ module ActiveModel private - def cast_value(value) - casted_value = case value - when ::Float - convert_float_to_big_decimal(value) - when ::Numeric, ::String - BigDecimal(value, precision.to_i) - else - if value.respond_to?(:to_d) - value.to_d + def cast_value(value) + casted_value = \ + case value + when ::Float + convert_float_to_big_decimal(value) + when ::Numeric, ::String + BigDecimal(value, precision.to_i) + else + if value.respond_to?(:to_d) + value.to_d + else + cast_value(value.to_s) + end + end + + apply_scale(casted_value) + end + + def convert_float_to_big_decimal(value) + if precision + BigDecimal(apply_scale(value), float_precision) else - cast_value(value.to_s) + value.to_d end end - scale ? casted_value.round(scale) : casted_value - end - - def convert_float_to_big_decimal(value) - if precision - BigDecimal(value, float_precision) - else - value.to_d + def float_precision + if precision.to_i > ::Float::DIG + 1 + ::Float::DIG + 1 + else + precision.to_i + end end - end - def float_precision - if precision.to_i > ::Float::DIG + 1 - ::Float::DIG + 1 - else - precision.to_i + def apply_scale(value) + if scale + value.round(scale) + else + value + end end - end end end end diff --git a/activemodel/lib/active_model/type/decimal_without_scale.rb b/activemodel/lib/active_model/type/decimal_without_scale.rb index 129baa0c10..985e1038ed 100644 --- a/activemodel/lib/active_model/type/decimal_without_scale.rb +++ b/activemodel/lib/active_model/type/decimal_without_scale.rb @@ -1,4 +1,4 @@ -require 'active_model/type/big_integer' +require "active_model/type/big_integer" module ActiveModel module Type diff --git a/activemodel/lib/active_model/type/float.rb b/activemodel/lib/active_model/type/float.rb index 0f925bc7e1..4d0d2771a0 100644 --- a/activemodel/lib/active_model/type/float.rb +++ b/activemodel/lib/active_model/type/float.rb @@ -7,19 +7,28 @@ module ActiveModel :float end + def type_cast_for_schema(value) + return "::Float::NAN" if value.try(:nan?) + case value + when ::Float::INFINITY then "::Float::INFINITY" + when -::Float::INFINITY then "-::Float::INFINITY" + else super + end + end + alias serialize cast private - def cast_value(value) - case value - when ::Float then value - when "Infinity" then ::Float::INFINITY - when "-Infinity" then -::Float::INFINITY - when "NaN" then ::Float::NAN - else value.to_f + def cast_value(value) + case value + when ::Float then value + when "Infinity" then ::Float::INFINITY + when "-Infinity" then -::Float::INFINITY + when "NaN" then ::Float::NAN + else value.to_f + end end - end end end end diff --git a/activemodel/lib/active_model/type/helpers.rb b/activemodel/lib/active_model/type/helpers.rb index a805a359ab..82cd9ebe98 100644 --- a/activemodel/lib/active_model/type/helpers.rb +++ b/activemodel/lib/active_model/type/helpers.rb @@ -1,4 +1,4 @@ -require 'active_model/type/helpers/accepts_multiparameter_time' -require 'active_model/type/helpers/numeric' -require 'active_model/type/helpers/mutable' -require 'active_model/type/helpers/time_value' +require "active_model/type/helpers/accepts_multiparameter_time" +require "active_model/type/helpers/numeric" +require "active_model/type/helpers/mutable" +require "active_model/type/helpers/time_value" diff --git a/activemodel/lib/active_model/type/helpers/numeric.rb b/activemodel/lib/active_model/type/helpers/numeric.rb index c883010506..98533f8771 100644 --- a/activemodel/lib/active_model/type/helpers/numeric.rb +++ b/activemodel/lib/active_model/type/helpers/numeric.rb @@ -3,12 +3,13 @@ module ActiveModel module Helpers module Numeric # :nodoc: def cast(value) - value = case value - when true then 1 - when false then 0 - when ::String then value.presence - else value - end + value = \ + case value + when true then 1 + when false then 0 + when ::String then value.presence + else value + end super(value) end @@ -18,16 +19,16 @@ module ActiveModel private - def number_to_non_number?(old_value, new_value_before_type_cast) - old_value != nil && non_numeric_string?(new_value_before_type_cast) - end + def number_to_non_number?(old_value, new_value_before_type_cast) + old_value != nil && non_numeric_string?(new_value_before_type_cast) + end - def non_numeric_string?(value) - # 'wibble'.to_i will give zero, we want to make sure - # that we aren't marking int zero to string zero as - # changed. - value.to_s !~ /\A-?\d+\.?\d*\z/ - end + def non_numeric_string?(value) + # 'wibble'.to_i will give zero, we want to make sure + # that we aren't marking int zero to string zero as + # changed. + value.to_s !~ /\A-?\d+\.?\d*\z/ + end end end end diff --git a/activemodel/lib/active_model/type/helpers/time_value.rb b/activemodel/lib/active_model/type/helpers/time_value.rb index 63993c0d93..d8c5d929a3 100644 --- a/activemodel/lib/active_model/type/helpers/time_value.rb +++ b/activemodel/lib/active_model/type/helpers/time_value.rb @@ -19,7 +19,7 @@ module ActiveModel end def is_utc? - ::Time.zone_default.nil? || ::Time.zone_default =~ 'UTC' + ::Time.zone_default.nil? || ::Time.zone_default =~ "UTC" end def default_timezone @@ -47,30 +47,30 @@ module ActiveModel private - def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil) - # Treat 0000-00-00 00:00:00 as nil. - return if year.nil? || (year == 0 && mon == 0 && mday == 0) + def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil) + # Treat 0000-00-00 00:00:00 as nil. + return if year.nil? || (year == 0 && mon == 0 && mday == 0) - if offset - time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil - return unless time + if offset + time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil + return unless time - time -= offset - is_utc? ? time : time.getlocal - else - ::Time.public_send(default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil + time -= offset + is_utc? ? time : time.getlocal + else + ::Time.public_send(default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil + end end - end - ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/ + ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/ - # Doesn't handle time zones. - def fast_string_to_time(string) - if string =~ ISO_DATETIME - microsec = ($7.to_r * 1_000_000).to_i - new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec + # Doesn't handle time zones. + def fast_string_to_time(string) + if string =~ ISO_DATETIME + microsec = ($7.to_r * 1_000_000).to_i + new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec + end end - end end end end diff --git a/activemodel/lib/active_model/type/immutable_string.rb b/activemodel/lib/active_model/type/immutable_string.rb index 20b8ca0cc4..58268540e5 100644 --- a/activemodel/lib/active_model/type/immutable_string.rb +++ b/activemodel/lib/active_model/type/immutable_string.rb @@ -16,14 +16,15 @@ module ActiveModel private - def cast_value(value) - result = case value - when true then "t" - when false then "f" - else value.to_s - end - result.freeze - end + def cast_value(value) + result = \ + case value + when true then "t" + when false then "f" + else value.to_s + end + result.freeze + end end end end diff --git a/activemodel/lib/active_model/type/integer.rb b/activemodel/lib/active_model/type/integer.rb index 2f73ede009..41dd655a5c 100644 --- a/activemodel/lib/active_model/type/integer.rb +++ b/activemodel/lib/active_model/type/integer.rb @@ -31,36 +31,36 @@ module ActiveModel protected - attr_reader :range + attr_reader :range private - def cast_value(value) - case value - when true then 1 - when false then 0 - else - value.to_i rescue nil + def cast_value(value) + case value + when true then 1 + when false then 0 + else + value.to_i rescue nil + end end - end - def ensure_in_range(value) - unless range.cover?(value) - raise RangeError, "#{value} is out of range for #{self.class} with limit #{_limit}" + def ensure_in_range(value) + unless range.cover?(value) + raise ActiveModel::RangeError, "#{value} is out of range for #{self.class} with limit #{_limit}" + end end - end - def max_value - 1 << (_limit * 8 - 1) # 8 bits per byte with one bit for sign - end + def max_value + 1 << (_limit * 8 - 1) # 8 bits per byte with one bit for sign + end - def min_value - -max_value - end + def min_value + -max_value + end - def _limit - self.limit || DEFAULT_LIMIT - end + def _limit + limit || DEFAULT_LIMIT + end end end end diff --git a/activemodel/lib/active_model/type/registry.rb b/activemodel/lib/active_model/type/registry.rb index adc88eb624..d25f1a129e 100644 --- a/activemodel/lib/active_model/type/registry.rb +++ b/activemodel/lib/active_model/type/registry.rb @@ -23,17 +23,17 @@ module ActiveModel protected - attr_reader :registrations + attr_reader :registrations private - def registration_klass - Registration - end + def registration_klass + Registration + end - def find_registration(symbol, *args) - registrations.find { |r| r.matches?(symbol, *args) } - end + def find_registration(symbol, *args) + registrations.find { |r| r.matches?(symbol, *args) } + end end class Registration @@ -57,7 +57,7 @@ module ActiveModel protected - attr_reader :name, :block + attr_reader :name, :block end end # :startdoc: diff --git a/activemodel/lib/active_model/type/string.rb b/activemodel/lib/active_model/type/string.rb index 8a91410998..c7e0208a5a 100644 --- a/activemodel/lib/active_model/type/string.rb +++ b/activemodel/lib/active_model/type/string.rb @@ -11,9 +11,9 @@ module ActiveModel private - def cast_value(value) - ::String.new(super) - end + def cast_value(value) + ::String.new(super) + end end end end diff --git a/activemodel/lib/active_model/type/text.rb b/activemodel/lib/active_model/type/text.rb index 1ad04daba4..7c0d647706 100644 --- a/activemodel/lib/active_model/type/text.rb +++ b/activemodel/lib/active_model/type/text.rb @@ -1,4 +1,4 @@ -require 'active_model/type/string' +require "active_model/type/string" module ActiveModel module Type diff --git a/activemodel/lib/active_model/type/time.rb b/activemodel/lib/active_model/type/time.rb index 34e09f0aba..54d6214e81 100644 --- a/activemodel/lib/active_model/type/time.rb +++ b/activemodel/lib/active_model/type/time.rb @@ -25,22 +25,22 @@ module ActiveModel private - def cast_value(value) - return value unless value.is_a?(::String) - return if value.empty? - - if value =~ /^2000-01-01/ - dummy_time_value = value - else - dummy_time_value = "2000-01-01 #{value}" + def cast_value(value) + return value unless value.is_a?(::String) + return if value.empty? + + if value.start_with?("2000-01-01") + dummy_time_value = value + else + dummy_time_value = "2000-01-01 #{value}" + end + + fast_string_to_time(dummy_time_value) || begin + time_hash = ::Date._parse(dummy_time_value) + return if time_hash[:hour].nil? + new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset)) + end end - - fast_string_to_time(dummy_time_value) || begin - time_hash = ::Date._parse(dummy_time_value) - return if time_hash[:hour].nil? - new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset)) - end - end end end end diff --git a/activemodel/lib/active_model/type/unsigned_integer.rb b/activemodel/lib/active_model/type/unsigned_integer.rb index 3f49f9f5f7..288fa23efe 100644 --- a/activemodel/lib/active_model/type/unsigned_integer.rb +++ b/activemodel/lib/active_model/type/unsigned_integer.rb @@ -3,13 +3,13 @@ module ActiveModel class UnsignedInteger < Integer # :nodoc: private - def max_value - super * 2 - end + def max_value + super * 2 + end - def min_value - 0 - end + def min_value + 0 + end end end end diff --git a/activemodel/lib/active_model/type/value.rb b/activemodel/lib/active_model/type/value.rb index 0d2d6873a8..7e9ae92245 100644 --- a/activemodel/lib/active_model/type/value.rb +++ b/activemodel/lib/active_model/type/value.rb @@ -105,12 +105,12 @@ module ActiveModel private - # Convenience method for types which do not need separate type casting - # behavior for user and database inputs. Called by Value#cast for - # values except +nil+. - def cast_value(value) # :doc: - value - end + # Convenience method for types which do not need separate type casting + # behavior for user and database inputs. Called by Value#cast for + # values except +nil+. + def cast_value(value) # :doc: + value + end end end end diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 8159b9b1d3..df6d3f2bcb 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -1,9 +1,8 @@ -require 'active_support/core_ext/array/extract_options' -require 'active_support/core_ext/hash/keys' -require 'active_support/core_ext/hash/except' +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/hash/except" module ActiveModel - # == Active \Model \Validations # # Provides a full validation framework to your objects. @@ -304,8 +303,6 @@ module ActiveModel # Runs all the specified validations and returns +true+ if no errors were # added otherwise +false+. # - # Aliased as validate. - # # class Person # include ActiveModel::Validations # diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb index c5c0cd4636..4fadc795cd 100644 --- a/activemodel/lib/active_model/validations/acceptance.rb +++ b/activemodel/lib/active_model/validations/acceptance.rb @@ -1,5 +1,4 @@ module ActiveModel - module Validations class AcceptanceValidator < EachValidator # :nodoc: def initialize(options) @@ -15,62 +14,58 @@ module ActiveModel private - def setup!(klass) - klass.include(LazilyDefineAttributes.new(AttributeDefinition.new(attributes))) - end + def setup!(klass) + klass.include(LazilyDefineAttributes.new(AttributeDefinition.new(attributes))) + end - def acceptable_option?(value) - Array(options[:accept]).include?(value) - end + def acceptable_option?(value) + Array(options[:accept]).include?(value) + end - class LazilyDefineAttributes < Module - def initialize(attribute_definition) - define_method(:respond_to_missing?) do |method_name, include_private=false| - super(method_name, include_private) || attribute_definition.matches?(method_name) - end + class LazilyDefineAttributes < Module + def initialize(attribute_definition) + define_method(:respond_to_missing?) do |method_name, include_private=false| + super(method_name, include_private) || attribute_definition.matches?(method_name) + end - define_method(:method_missing) do |method_name, *args, &block| - if attribute_definition.matches?(method_name) - attribute_definition.define_on(self.class) - send(method_name, *args, &block) - else - super(method_name, *args, &block) + define_method(:method_missing) do |method_name, *args, &block| + if attribute_definition.matches?(method_name) + attribute_definition.define_on(self.class) + send(method_name, *args, &block) + else + super(method_name, *args, &block) + end end end end - end - class AttributeDefinition - def initialize(attributes) - @attributes = attributes.map(&:to_s) - end + class AttributeDefinition + def initialize(attributes) + @attributes = attributes.map(&:to_s) + end - def matches?(method_name) - attr_name = convert_to_reader_name(method_name) - attributes.include?(attr_name) - end + def matches?(method_name) + attr_name = convert_to_reader_name(method_name) + attributes.include?(attr_name) + end - def define_on(klass) - attr_readers = attributes.reject { |name| klass.attribute_method?(name) } - attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") } - klass.send(:attr_reader, *attr_readers) - klass.send(:attr_writer, *attr_writers) - end + def define_on(klass) + attr_readers = attributes.reject { |name| klass.attribute_method?(name) } + attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") } + klass.send(:attr_reader, *attr_readers) + klass.send(:attr_writer, *attr_writers) + end - protected + protected - attr_reader :attributes + attr_reader :attributes - private + private - def convert_to_reader_name(method_name) - attr_name = method_name.to_s - if attr_name.end_with?("=") - attr_name = attr_name[0..-2] - end - attr_name + def convert_to_reader_name(method_name) + method_name.to_s.chomp("=") + end end - end end module HelperMethods diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb index 52111e5442..a201f72ed0 100644 --- a/activemodel/lib/active_model/validations/callbacks.rb +++ b/activemodel/lib/active_model/validations/callbacks.rb @@ -29,8 +29,7 @@ module ActiveModel end module ClassMethods - # Defines a callback that will get called right before validation - # happens. + # Defines a callback that will get called right before validation. # # class Person # include ActiveModel::Validations @@ -65,8 +64,7 @@ module ActiveModel set_callback(:validation, :before, *args, &block) end - # Defines a callback that will get called right after validation - # happens. + # Defines a callback that will get called right after validation. # # class Person # include ActiveModel::Validations diff --git a/activemodel/lib/active_model/validations/clusivity.rb b/activemodel/lib/active_model/validations/clusivity.rb index bad9e4f9a9..18f1056e2b 100644 --- a/activemodel/lib/active_model/validations/clusivity.rb +++ b/activemodel/lib/active_model/validations/clusivity.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/range' +require "active_support/core_ext/range" module ActiveModel module Validations @@ -16,12 +16,12 @@ module ActiveModel def include?(record, value) members = if delimiter.respond_to?(:call) - delimiter.call(record) - elsif delimiter.respond_to?(:to_sym) - record.send(delimiter) - else - delimiter - end + delimiter.call(record) + elsif delimiter.respond_to?(:to_sym) + record.send(delimiter) + else + delimiter + end members.send(inclusion_method(members), value) end @@ -30,14 +30,15 @@ module ActiveModel @delimiter ||= options[:in] || options[:within] end - # In Ruby 1.9 <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all + # In Ruby 2.2 <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all # possible values in the range for equality, which is slower but more accurate. # <tt>Range#cover?</tt> uses the previous logic of comparing a value with the range - # endpoints, which is fast but is only accurate on Numeric, Time, or DateTime ranges. + # endpoints, which is fast but is only accurate on Numeric, Time, Date, + # or DateTime ranges. def inclusion_method(enumerable) if enumerable.is_a? Range case enumerable.first - when Numeric, Time, DateTime + when Numeric, Time, DateTime, Date :cover? else :include? diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb index 8f8ade90bb..33ca6f6946 100644 --- a/activemodel/lib/active_model/validations/confirmation.rb +++ b/activemodel/lib/active_model/validations/confirmation.rb @@ -1,5 +1,4 @@ module ActiveModel - module Validations class ConfirmationValidator < EachValidator # :nodoc: def initialize(options) @@ -17,23 +16,23 @@ module ActiveModel end private - def setup!(klass) - klass.send(:attr_reader, *attributes.map do |attribute| - :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation") - end.compact) + def setup!(klass) + klass.send(:attr_reader, *attributes.map do |attribute| + :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation") + end.compact) - klass.send(:attr_writer, *attributes.map do |attribute| - :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation=") - end.compact) - end + klass.send(:attr_writer, *attributes.map do |attribute| + :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation=") + end.compact) + end - def confirmation_value_equal?(record, attribute, value, confirmed) - if !options[:case_sensitive] && value.is_a?(String) - value.casecmp(confirmed) == 0 - else - value == confirmed + def confirmation_value_equal?(record, attribute, value, confirmed) + if !options[:case_sensitive] && value.is_a?(String) + value.casecmp(confirmed) == 0 + else + value == confirmed + end end - end end module HelperMethods diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb index 6f4276cc2a..82a1893823 100644 --- a/activemodel/lib/active_model/validations/exclusion.rb +++ b/activemodel/lib/active_model/validations/exclusion.rb @@ -1,7 +1,6 @@ require "active_model/validations/clusivity" module ActiveModel - module Validations class ExclusionValidator < EachValidator # :nodoc: include Clusivity diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb index 46a2e54fba..fa183885ab 100644 --- a/activemodel/lib/active_model/validations/format.rb +++ b/activemodel/lib/active_model/validations/format.rb @@ -1,5 +1,5 @@ -module ActiveModel +module ActiveModel module Validations class FormatValidator < EachValidator # :nodoc: def validate_each(record, attribute, value) @@ -8,7 +8,7 @@ module ActiveModel record_error(record, attribute, :with, value) if value.to_s !~ regexp elsif options[:without] regexp = option_call(record, :without) - record_error(record, attribute, :without, value) if value.to_s =~ regexp + record_error(record, attribute, :without, value) if regexp.match?(value.to_s) end end @@ -23,33 +23,33 @@ module ActiveModel private - def option_call(record, name) - option = options[name] - option.respond_to?(:call) ? option.call(record) : option - end + def option_call(record, name) + option = options[name] + option.respond_to?(:call) ? option.call(record) : option + end - def record_error(record, attribute, name, value) - record.errors.add(attribute, :invalid, options.except(name).merge!(value: value)) - end + def record_error(record, attribute, name, value) + record.errors.add(attribute, :invalid, options.except(name).merge!(value: value)) + end - def check_options_validity(name) - if option = options[name] - if option.is_a?(Regexp) - if options[:multiline] != true && regexp_using_multiline_anchors?(option) - raise ArgumentError, "The provided regular expression is using multiline anchors (^ or $), " \ - "which may present a security risk. Did you mean to use \\A and \\z, or forgot to add the " \ - ":multiline => true option?" + def check_options_validity(name) + if option = options[name] + if option.is_a?(Regexp) + if options[:multiline] != true && regexp_using_multiline_anchors?(option) + raise ArgumentError, "The provided regular expression is using multiline anchors (^ or $), " \ + "which may present a security risk. Did you mean to use \\A and \\z, or forgot to add the " \ + ":multiline => true option?" + end + elsif !option.respond_to?(:call) + raise ArgumentError, "A regular expression or a proc or lambda must be supplied as :#{name}" end - elsif !option.respond_to?(:call) - raise ArgumentError, "A regular expression or a proc or lambda must be supplied as :#{name}" end end - end - def regexp_using_multiline_anchors?(regexp) - source = regexp.source - source.start_with?("^") || (source.end_with?("$") && !source.end_with?("\\$")) - end + def regexp_using_multiline_anchors?(regexp) + source = regexp.source + source.start_with?("^") || (source.end_with?("$") && !source.end_with?("\\$")) + end end module HelperMethods diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb index 03e0ef56d8..0ea6012a5d 100644 --- a/activemodel/lib/active_model/validations/inclusion.rb +++ b/activemodel/lib/active_model/validations/inclusion.rb @@ -1,7 +1,6 @@ require "active_model/validations/clusivity" module ActiveModel - module Validations class InclusionValidator < EachValidator # :nodoc: include Clusivity diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb index 910cca2f49..f73f16830f 100644 --- a/activemodel/lib/active_model/validations/length.rb +++ b/activemodel/lib/active_model/validations/length.rb @@ -6,7 +6,7 @@ module ActiveModel MESSAGES = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze - RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :tokenizer, :too_short, :too_long] + RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :too_short, :too_long] def initialize(options) if range = (options.delete(:in) || options.delete(:within)) @@ -18,27 +18,6 @@ module ActiveModel options[:minimum] = 1 end - if options[:tokenizer] - ActiveSupport::Deprecation.warn(<<-EOS.strip_heredoc) - The `:tokenizer` option is deprecated, and will be removed in Rails 5.1. - You can achieve the same functionality by defining an instance method - with the value that you want to validate the length of. For example, - - validates_length_of :essay, minimum: 100, - tokenizer: ->(str) { str.scan(/\w+/) } - - should be written as - - validates_length_of :words_in_essay, minimum: 100 - - private - - def words_in_essay - essay.scan(/\w+/) - end - EOS - end - super end @@ -46,7 +25,7 @@ module ActiveModel keys = CHECKS.keys & options.keys if keys.empty? - raise ArgumentError, 'Range unspecified. Specify the :in, :within, :maximum, :minimum, or :is option.' + raise ArgumentError, "Range unspecified. Specify the :in, :within, :maximum, :minimum, or :is option." end keys.each do |key| @@ -59,7 +38,6 @@ module ActiveModel end def validate_each(record, attribute, value) - value = tokenize(record, value) value_length = value.respond_to?(:length) ? value.length : value.to_s.length errors_options = options.except(*RESERVED_OPTIONS) @@ -80,24 +58,12 @@ module ActiveModel end private - def tokenize(record, value) - tokenizer = options[:tokenizer] - if tokenizer && value.kind_of?(String) - if tokenizer.kind_of?(Proc) - tokenizer.call(value) - elsif record.respond_to?(tokenizer) - record.send(tokenizer, value) - end - end || value - end - - def skip_nil_check?(key) - key == :maximum && options[:allow_nil].nil? && options[:allow_blank].nil? - end + def skip_nil_check?(key) + key == :maximum && options[:allow_nil].nil? && options[:allow_blank].nil? + end end module HelperMethods - # Validates that the specified attributes match the length restrictions # supplied. Only one constraint option can be used at a time apart from # +:minimum+ and +:maximum+ that can be combined together: @@ -136,7 +102,7 @@ module ActiveModel # * <tt>:too_long</tt> - The error message if the attribute goes over the # maximum (default is: "is too long (maximum is %{count} characters)"). # * <tt>:too_short</tt> - The error message if the attribute goes under the - # minimum (default is: "is too short (min is %{count} characters)"). + # minimum (default is: "is too short (minimum is %{count} characters)"). # * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> # method and the attribute is the wrong size (default is: "is the wrong # length (should be %{count} characters)"). diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index ad7012df48..48e0e5c9f6 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -1,5 +1,4 @@ module ActiveModel - module Validations class NumericalityValidator < EachValidator # :nodoc: CHECKS = { greater_than: :>, greater_than_or_equal_to: :>=, @@ -27,8 +26,6 @@ module ActiveModel raw_value = value end - return if options[:allow_nil] && raw_value.nil? - unless is_number?(raw_value) record.errors.add(attr_name, :not_a_number, filtered_options(raw_value)) return @@ -99,10 +96,10 @@ module ActiveModel 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 @@ -120,7 +117,7 @@ module ActiveModel # * <tt>:only_integer</tt> - Specifies whether the value has to be an # integer, e.g. an integral value (default is +false+). # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is - # +false+). Notice that for fixnum and float columns empty strings are + # +false+). Notice that for Integer and Float columns empty strings are # converted to +nil+. # * <tt>:greater_than</tt> - Specifies the value must be greater than the # supplied value. diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb index 5d593274eb..0c11cf4265 100644 --- a/activemodel/lib/active_model/validations/presence.rb +++ b/activemodel/lib/active_model/validations/presence.rb @@ -1,6 +1,5 @@ module ActiveModel - module Validations class PresenceValidator < EachValidator # :nodoc: def validate_each(record, attr_name, value) diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb index 1da4df21e7..c3cbb6e291 100644 --- a/activemodel/lib/active_model/validations/validates.rb +++ b/activemodel/lib/active_model/validations/validates.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/hash/slice' +require "active_support/core_ext/hash/slice" module ActiveModel module Validations @@ -115,7 +115,7 @@ module ActiveModel key = "#{key.to_s.camelize}Validator" begin - validator = key.include?('::'.freeze) ? key.constantize : const_get(key) + validator = key.include?("::".freeze) ? key.constantize : const_get(key) rescue NameError raise ArgumentError, "Unknown validator: '#{key}'" end diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb index 6de01b3392..6ae9630d82 100644 --- a/activemodel/lib/active_model/validations/with.rb +++ b/activemodel/lib/active_model/validations/with.rb @@ -1,3 +1,5 @@ +require "active_support/core_ext/array/extract_options" + module ActiveModel module Validations class WithValidator < EachValidator # :nodoc: diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index 1d2888a818..9c6065f61f 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -1,7 +1,6 @@ require "active_support/core_ext/module/anonymous" module ActiveModel - # == Active \Model \Validator # # A simple base class that can be used along with @@ -100,7 +99,7 @@ module ActiveModel # PresenceValidator.kind # => :presence # UniquenessValidator.kind # => :uniqueness def self.kind - @kind ||= name.split('::').last.underscore.sub(/_validator$/, '').to_sym unless anonymous? + @kind ||= name.split("::").last.underscore.chomp("_validator").to_sym unless anonymous? end # Accepts options that will be made available through the +options+ reader. @@ -163,10 +162,6 @@ module ActiveModel # +ArgumentError+ when invalid options are supplied. def check_validity! end - - def should_validate?(record) # :nodoc: - !record.persisted? || record.changed? || record.marked_for_destruction? - end end # +BlockValidator+ is a special +EachValidator+ which receives a block on initialization @@ -179,8 +174,8 @@ module ActiveModel private - def validate_each(record, attribute, value) - @block.call(record, attribute, value) - end + def validate_each(record, attribute, value) + @block.call(record, attribute, value) + end end end diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb index 6da3b4117b..6e7fd227fd 100644 --- a/activemodel/lib/active_model/version.rb +++ b/activemodel/lib/active_model/version.rb @@ -1,4 +1,4 @@ -require_relative 'gem_version' +require_relative "gem_version" module ActiveModel # Returns the version of the currently loaded \Active \Model as a <tt>Gem::Version</tt> diff --git a/activemodel/test/cases/attribute_assignment_test.rb b/activemodel/test/cases/attribute_assignment_test.rb index 287bea719c..8eb389d331 100644 --- a/activemodel/test/cases/attribute_assignment_test.rb +++ b/activemodel/test/cases/attribute_assignment_test.rb @@ -18,7 +18,7 @@ class AttributeAssignmentTest < ActiveModel::TestCase protected - attr_writer :metadata + attr_writer :metadata end class ErrorFromAttributeWriter < StandardError diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb index e81b7ac424..4767accb7c 100644 --- a/activemodel/test/cases/attribute_methods_test.rb +++ b/activemodel/test/cases/attribute_methods_test.rb @@ -1,16 +1,16 @@ -require 'cases/helper' +require "cases/helper" class ModelWithAttributes include ActiveModel::AttributeMethods class << self define_method(:bar) do - 'original bar' + "original bar" end end def attributes - { foo: 'value of foo', baz: 'value of baz' } + { foo: "value of foo", baz: "value of baz" } end private @@ -24,7 +24,7 @@ class ModelWithAttributes2 attr_accessor :attributes - attribute_method_suffix '_test' + attribute_method_suffix "_test" private def attribute(name) @@ -48,7 +48,7 @@ class ModelWithAttributesWithSpaces include ActiveModel::AttributeMethods def attributes - { :'foo bar' => 'value of foo bar'} + { 'foo bar': "value of foo bar" } end private @@ -62,12 +62,12 @@ class ModelWithWeirdNamesAttributes class << self define_method(:'c?d') do - 'original c?d' + "original c?d" end end def attributes - { :'a?b' => 'value of a?b' } + { 'a?b': "value of a?b" } end private @@ -80,7 +80,7 @@ class ModelWithRubyKeywordNamedAttributes include ActiveModel::AttributeMethods def attributes - { begin: 'value of begin', end: 'value of end' } + { begin: "value of begin", end: "value of end" } end private @@ -94,16 +94,16 @@ class ModelWithoutAttributesMethod end class AttributeMethodsTest < ActiveModel::TestCase - test 'method missing works correctly even if attributes method is not defined' do + test "method missing works correctly even if attributes method is not defined" do assert_raises(NoMethodError) { ModelWithoutAttributesMethod.new.foo } end - test 'unrelated classes should not share attribute method matchers' do + test "unrelated classes should not share attribute method matchers" do assert_not_equal ModelWithAttributes.send(:attribute_method_matchers), ModelWithAttributes2.send(:attribute_method_matchers) end - test '#define_attribute_method generates attribute method' do + test "#define_attribute_method generates attribute method" do begin ModelWithAttributes.define_attribute_method(:foo) @@ -114,19 +114,19 @@ class AttributeMethodsTest < ActiveModel::TestCase end end - test '#define_attribute_method does not generate attribute method if already defined in attribute module' do + test "#define_attribute_method does not generate attribute method if already defined in attribute module" do klass = Class.new(ModelWithAttributes) klass.generated_attribute_methods.module_eval do def foo - '<3' + "<3" end end klass.define_attribute_method(:foo) - assert_equal '<3', klass.new.foo + assert_equal "<3", klass.new.foo end - test '#define_attribute_method generates a method that is already defined on the host' do + test "#define_attribute_method generates a method that is already defined on the host" do klass = Class.new(ModelWithAttributes) do def foo super @@ -134,21 +134,21 @@ class AttributeMethodsTest < ActiveModel::TestCase end klass.define_attribute_method(:foo) - assert_equal 'value of foo', klass.new.foo + assert_equal "value of foo", klass.new.foo end - test '#define_attribute_method generates attribute method with invalid identifier characters' do + test "#define_attribute_method generates attribute method with invalid identifier characters" do begin ModelWithWeirdNamesAttributes.define_attribute_method(:'a?b') assert_respond_to ModelWithWeirdNamesAttributes.new, :'a?b' - assert_equal "value of a?b", ModelWithWeirdNamesAttributes.new.send('a?b') + assert_equal "value of a?b", ModelWithWeirdNamesAttributes.new.send("a?b") ensure ModelWithWeirdNamesAttributes.undefine_attribute_methods end end - test '#define_attribute_methods works passing multiple arguments' do + test "#define_attribute_methods works passing multiple arguments" do begin ModelWithAttributes.define_attribute_methods(:foo, :baz) @@ -159,7 +159,7 @@ class AttributeMethodsTest < ActiveModel::TestCase end end - test '#define_attribute_methods generates attribute methods' do + test "#define_attribute_methods generates attribute methods" do begin ModelWithAttributes.define_attribute_methods(:foo) @@ -170,7 +170,7 @@ class AttributeMethodsTest < ActiveModel::TestCase end end - test '#alias_attribute generates attribute_aliases lookup hash' do + test "#alias_attribute generates attribute_aliases lookup hash" do klass = Class.new(ModelWithAttributes) do define_attribute_methods :foo alias_attribute :bar, :foo @@ -179,7 +179,7 @@ class AttributeMethodsTest < ActiveModel::TestCase assert_equal({ "bar" => "foo" }, klass.attribute_aliases) end - test '#define_attribute_methods generates attribute methods with spaces in their names' do + test "#define_attribute_methods generates attribute methods with spaces in their names" do begin ModelWithAttributesWithSpaces.define_attribute_methods(:'foo bar') @@ -190,7 +190,7 @@ class AttributeMethodsTest < ActiveModel::TestCase end end - test '#alias_attribute works with attributes with spaces in their names' do + test "#alias_attribute works with attributes with spaces in their names" do begin ModelWithAttributesWithSpaces.define_attribute_methods(:'foo bar') ModelWithAttributesWithSpaces.alias_attribute(:'foo_bar', :'foo bar') @@ -201,7 +201,7 @@ class AttributeMethodsTest < ActiveModel::TestCase end end - test '#alias_attribute works with attributes named as a ruby keyword' do + test "#alias_attribute works with attributes named as a ruby keyword" do begin ModelWithRubyKeywordNamedAttributes.define_attribute_methods([:begin, :end]) ModelWithRubyKeywordNamedAttributes.alias_attribute(:from, :begin) @@ -214,7 +214,7 @@ class AttributeMethodsTest < ActiveModel::TestCase end end - test '#undefine_attribute_methods removes attribute methods' do + test "#undefine_attribute_methods removes attribute methods" do ModelWithAttributes.define_attribute_methods(:foo) ModelWithAttributes.undefine_attribute_methods @@ -222,21 +222,21 @@ class AttributeMethodsTest < ActiveModel::TestCase assert_raises(NoMethodError) { ModelWithAttributes.new.foo } end - test 'accessing a suffixed attribute' do + test "accessing a suffixed attribute" do m = ModelWithAttributes2.new - m.attributes = { 'foo' => 'bar' } + m.attributes = { "foo" => "bar" } - assert_equal 'bar', m.foo - assert_equal 'bar', m.foo_test + assert_equal "bar", m.foo + assert_equal "bar", m.foo_test end - test 'should not interfere with method_missing if the attr has a private/protected method' do + test "should not interfere with method_missing if the attr has a private/protected method" do m = ModelWithAttributes2.new - m.attributes = { 'private_method' => '<3', 'protected_method' => 'O_o' } + m.attributes = { "private_method" => "<3", "protected_method" => "O_o" } # dispatches to the *method*, not the attribute - assert_equal '<3 <3', m.send(:private_method) - assert_equal 'O_o O_o', m.send(:protected_method) + assert_equal "<3 <3", m.send(:private_method) + assert_equal "O_o O_o", m.send(:protected_method) # sees that a method is already defined, so doesn't intervene assert_raises(NoMethodError) { m.private_method } @@ -245,13 +245,13 @@ class AttributeMethodsTest < ActiveModel::TestCase class ClassWithProtected protected - def protected_method - end + def protected_method + end end - test 'should not interfere with respond_to? if the attribute has a private/protected method' do + test "should not interfere with respond_to? if the attribute has a private/protected method" do m = ModelWithAttributes2.new - m.attributes = { 'private_method' => '<3', 'protected_method' => 'O_o' } + m.attributes = { "private_method" => "<3", "protected_method" => "O_o" } assert !m.respond_to?(:private_method) assert m.respond_to?(:private_method, true) @@ -264,9 +264,9 @@ class AttributeMethodsTest < ActiveModel::TestCase assert m.respond_to?(:protected_method, true) end - test 'should use attribute_missing to dispatch a missing attribute' do + test "should use attribute_missing to dispatch a missing attribute" do m = ModelWithAttributes2.new - m.attributes = { 'foo' => 'bar' } + m.attributes = { "foo" => "bar" } def m.attribute_missing(match, *args, &block) match @@ -274,8 +274,8 @@ class AttributeMethodsTest < ActiveModel::TestCase match = m.foo_test - assert_equal 'foo', match.attr_name - assert_equal 'attribute_test', match.target - assert_equal 'foo_test', match.method_name + assert_equal "foo", match.attr_name + assert_equal "attribute_test", match.target + assert_equal "foo_test", match.method_name end end diff --git a/activemodel/test/cases/callbacks_test.rb b/activemodel/test/cases/callbacks_test.rb index e4ecc0adb4..63b6c56f8c 100644 --- a/activemodel/test/cases/callbacks_test.rb +++ b/activemodel/test/cases/callbacks_test.rb @@ -1,7 +1,6 @@ require "cases/helper" class CallbacksTest < ActiveModel::TestCase - class CallbackValidator def around_create(model) model.callbacks << :before_around_create @@ -110,8 +109,8 @@ class CallbacksTest < ActiveModel::TestCase end extend ActiveModel::Callbacks define_model_callbacks :create - def callback1; self.history << 'callback1'; end - def callback2; self.history << 'callback2'; end + def callback1; history << "callback1"; end + def callback2; history << "callback2"; end def create run_callbacks(:create) {} self @@ -131,5 +130,4 @@ class CallbacksTest < ActiveModel::TestCase test "after_create callbacks with both callbacks declared in different lines" do assert_equal ["callback1", "callback2"], Violin2.new.create.history end - end diff --git a/activemodel/test/cases/conversion_test.rb b/activemodel/test/cases/conversion_test.rb index 800cad6d9a..4a93347abc 100644 --- a/activemodel/test/cases/conversion_test.rb +++ b/activemodel/test/cases/conversion_test.rb @@ -1,6 +1,6 @@ -require 'cases/helper' -require 'models/contact' -require 'models/helicopter' +require "cases/helper" +require "models/contact" +require "models/helicopter" class ConversionTest < ActiveModel::TestCase test "to_model default implementation returns self" do diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb index d17a12ad12..fdd18d7601 100644 --- a/activemodel/test/cases/dirty_test.rb +++ b/activemodel/test/cases/dirty_test.rb @@ -62,13 +62,13 @@ class DirtyTest < ActiveModel::TestCase test "list of changed attribute keys" do assert_equal [], @model.changed @model.name = "Paul" - assert_equal ['name'], @model.changed + assert_equal ["name"], @model.changed end test "changes to attribute values" do - assert !@model.changes['name'] + assert !@model.changes["name"] @model.name = "John" - assert_equal [nil, "John"], @model.changes['name'] + assert_equal [nil, "John"], @model.changes["name"] end test "checking if an attribute has changed to a particular value" do @@ -84,14 +84,14 @@ class DirtyTest < ActiveModel::TestCase test "changes accessible through both strings and symbols" do @model.name = "David" assert_not_nil @model.changes[:name] - assert_not_nil @model.changes['name'] + assert_not_nil @model.changes["name"] end test "be consistent with symbols arguments after the changes are applied" do @model.name = "David" assert @model.attribute_changed?(:name) @model.save - @model.name = 'Rafael' + @model.name = "Rafael" assert @model.attribute_changed?(:name) end @@ -134,7 +134,7 @@ class DirtyTest < ActiveModel::TestCase test "saving should preserve previous changes" do @model.name = "Jericho Cane" @model.save - assert_equal [nil, "Jericho Cane"], @model.previous_changes['name'] + assert_equal [nil, "Jericho Cane"], @model.previous_changes["name"] end test "setting new attributes should not affect previous changes" do @@ -176,13 +176,13 @@ class DirtyTest < ActiveModel::TestCase end test "reload should reset all changes" do - @model.name = 'Dmitry' + @model.name = "Dmitry" @model.name_changed? @model.save - @model.name = 'Bob' + @model.name = "Bob" - assert_equal [nil, 'Dmitry'], @model.previous_changes['name'] - assert_equal 'Dmitry', @model.changed_attributes['name'] + assert_equal [nil, "Dmitry"], @model.previous_changes["name"] + assert_equal "Dmitry", @model.changed_attributes["name"] @model.reload @@ -191,30 +191,30 @@ class DirtyTest < ActiveModel::TestCase end test "restore_attributes should restore all previous data" do - @model.name = 'Dmitry' - @model.color = 'Red' + @model.name = "Dmitry" + @model.color = "Red" @model.save - @model.name = 'Bob' - @model.color = 'White' + @model.name = "Bob" + @model.color = "White" @model.restore_attributes assert_not @model.changed? - assert_equal 'Dmitry', @model.name - assert_equal 'Red', @model.color + assert_equal "Dmitry", @model.name + assert_equal "Red", @model.color end test "restore_attributes can restore only some attributes" do - @model.name = 'Dmitry' - @model.color = 'Red' + @model.name = "Dmitry" + @model.color = "Red" @model.save - @model.name = 'Bob' - @model.color = 'White' + @model.name = "Bob" + @model.color = "White" - @model.restore_attributes(['name']) + @model.restore_attributes(["name"]) assert @model.changed? - assert_equal 'Dmitry', @model.name - assert_equal 'White', @model.color + assert_equal "Dmitry", @model.name + assert_equal "White", @model.color end end diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index a5ac055033..605ad64e4d 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -11,7 +11,7 @@ class ErrorsTest < ActiveModel::TestCase attr_reader :errors def validate! - errors.add(:name, "cannot be nil") if name == nil + errors.add(:name, :blank, message: "cannot be nil") if name == nil end def read_attribute_for_validation(attr) @@ -29,45 +29,45 @@ class ErrorsTest < ActiveModel::TestCase def test_delete errors = ActiveModel::Errors.new(self) - errors[:foo] << 'omg' + errors[:foo] << "omg" errors.delete(:foo) assert_empty errors[:foo] end def test_include? errors = ActiveModel::Errors.new(self) - errors[:foo] << 'omg' - assert errors.include?(:foo), 'errors should include :foo' + errors[:foo] << "omg" + assert_includes errors, :foo, "errors should include :foo" end def test_dup errors = ActiveModel::Errors.new(self) - errors[:foo] << 'bar' + errors[:foo] << "bar" errors_dup = errors.dup - errors_dup[:bar] << 'omg' + errors_dup[:bar] << "omg" assert_not_same errors_dup.messages, errors.messages end def test_has_key? errors = ActiveModel::Errors.new(self) - errors[:foo] << 'omg' - assert_equal true, errors.has_key?(:foo), 'errors should have key :foo' + errors[:foo] << "omg" + assert_equal true, errors.has_key?(:foo), "errors should have key :foo" end def test_has_no_key errors = ActiveModel::Errors.new(self) - assert_equal false, errors.has_key?(:name), 'errors should not have key :name' + assert_equal false, errors.has_key?(:name), "errors should not have key :name" end def test_key? errors = ActiveModel::Errors.new(self) - errors[:foo] << 'omg' - assert_equal true, errors.key?(:foo), 'errors should have key :foo' + errors[:foo] << "omg" + assert_equal true, errors.key?(:foo), "errors should have key :foo" end def test_no_key errors = ActiveModel::Errors.new(self) - assert_equal false, errors.key?(:name), 'errors should not have key :name' + assert_equal false, errors.key?(:name), "errors should not have key :name" end test "clear errors" do @@ -79,24 +79,6 @@ class ErrorsTest < ActiveModel::TestCase assert person.errors.empty? end - test "get returns the errors for the provided key" do - errors = ActiveModel::Errors.new(self) - errors[:foo] << "omg" - - assert_deprecated do - assert_equal ["omg"], errors.get(:foo) - end - end - - test "sets the error with the provided key" do - errors = ActiveModel::Errors.new(self) - assert_deprecated do - errors.set(:foo, "omg") - end - - assert_equal({ foo: "omg" }, errors.messages) - end - test "error access is indifferent" do errors = ActiveModel::Errors.new(self) errors[:foo] << "omg" @@ -125,7 +107,14 @@ class ErrorsTest < ActiveModel::TestCase person.errors[:foo] assert person.errors.empty? assert person.errors.blank? - assert !person.errors.include?(:foo) + assert_not_includes person.errors, :foo + end + + test "include? does not add a key to messages hash" do + person = Person.new + person.errors.include?(:foo) + + assert_not person.errors.messages.key?(:foo) end test "adding errors using conditionals with Person#validate!" do @@ -135,14 +124,6 @@ class ErrorsTest < ActiveModel::TestCase assert_equal ["cannot be nil"], person.errors[:name] end - test "assign error" do - person = Person.new - assert_deprecated do - person.errors[:name] = 'should not be nil' - end - assert_equal ["should not be nil"], person.errors[:name] - end - test "add an error message on a specific attribute" do person = Person.new person.errors.add(:name, "cannot be blank") @@ -212,6 +193,12 @@ class ErrorsTest < ActiveModel::TestCase assert !person.errors.added?(:name, "cannot be blank") end + test "added? returns false when checking for an error, but not providing message arguments" do + person = Person.new + person.errors.add(:name, "cannot be blank") + assert !person.errors.added?(:name) + end + test "size calculates the number of error messages" do person = Person.new person.errors.add(:name, "cannot be blank") @@ -237,6 +224,16 @@ class ErrorsTest < ActiveModel::TestCase assert_equal({ name: ["cannot be blank"] }, person.errors.to_hash) end + test "to_hash returns a hash without default proc" do + person = Person.new + assert_nil person.errors.to_hash.default_proc + end + + test "as_json returns a hash without default proc" do + person = Person.new + assert_nil person.errors.as_json.default_proc + end + test "full_messages creates a list of error messages with the attribute name included" do person = Person.new person.errors.add(:name, "cannot be blank") @@ -297,72 +294,6 @@ class ErrorsTest < ActiveModel::TestCase } end - test "add_on_empty generates message" do - person = Person.new - assert_called_with(person.errors, :generate_message, [:name, :empty, {}]) do - assert_deprecated do - person.errors.add_on_empty :name - end - end - end - - test "add_on_empty generates message for multiple attributes" do - person = Person.new - expected_calls = [ [:name, :empty, {}], [:age, :empty, {}] ] - assert_called_with(person.errors, :generate_message, expected_calls) do - assert_deprecated do - person.errors.add_on_empty [:name, :age] - end - end - end - - test "add_on_empty generates message with custom default message" do - person = Person.new - assert_called_with(person.errors, :generate_message, [:name, :empty, { message: 'custom' }]) do - assert_deprecated do - person.errors.add_on_empty :name, message: 'custom' - end - end - end - - test "add_on_empty generates message with empty string value" do - person = Person.new - person.name = '' - assert_called_with(person.errors, :generate_message, [:name, :empty, {}]) do - assert_deprecated do - person.errors.add_on_empty :name - end - end - end - - test "add_on_blank generates message" do - person = Person.new - assert_called_with(person.errors, :generate_message, [:name, :blank, {}]) do - assert_deprecated do - person.errors.add_on_blank :name - end - end - end - - test "add_on_blank generates message for multiple attributes" do - person = Person.new - expected_calls = [ [:name, :blank, {}], [:age, :blank, {}] ] - assert_called_with(person.errors, :generate_message, expected_calls) do - assert_deprecated do - person.errors.add_on_blank [:name, :age] - end - end - end - - test "add_on_blank generates message with custom default message" do - person = Person.new - assert_called_with(person.errors, :generate_message, [:name, :blank, { message: 'custom' }]) do - assert_deprecated do - person.errors.add_on_blank :name, message: 'custom' - end - end - end - test "details returns added error detail" do person = Person.new person.errors.add(:name, :invalid) @@ -420,4 +351,13 @@ class ErrorsTest < ActiveModel::TestCase assert_equal [:name], person.errors.messages.keys assert_equal [:name], person.errors.details.keys end + + test "errors are marshalable" do + errors = ActiveModel::Errors.new(Person.new) + errors.add(:name, :invalid) + serialized = Marshal.load(Marshal.dump(errors)) + + assert_equal errors.messages, serialized.messages + assert_equal errors.details, serialized.details + end end diff --git a/activemodel/test/cases/forbidden_attributes_protection_test.rb b/activemodel/test/cases/forbidden_attributes_protection_test.rb index d8d757f52a..d8cc72e662 100644 --- a/activemodel/test/cases/forbidden_attributes_protection_test.rb +++ b/activemodel/test/cases/forbidden_attributes_protection_test.rb @@ -1,6 +1,6 @@ -require 'cases/helper' -require 'active_support/core_ext/hash/indifferent_access' -require 'models/account' +require "cases/helper" +require "active_support/core_ext/hash/indifferent_access" +require "models/account" class ProtectedParams attr_accessor :permitted @@ -25,18 +25,18 @@ end class ActiveModelMassUpdateProtectionTest < ActiveSupport::TestCase test "forbidden attributes cannot be used for mass updating" do - params = ProtectedParams.new({ "a" => "b" }) + params = ProtectedParams.new("a" => "b") assert_raises(ActiveModel::ForbiddenAttributesError) do Account.new.sanitize_for_mass_assignment(params) end end test "permitted attributes can be used for mass updating" do - params = ProtectedParams.new({ "a" => "b" }).permit! + params = ProtectedParams.new("a" => "b").permit! assert_equal({ "a" => "b" }, Account.new.sanitize_for_mass_assignment(params)) end test "regular attributes should still be allowed" do - assert_equal({ a: "b" }, Account.new.sanitize_for_mass_assignment(a: "b")) + assert_equal({ a: "b" }, Account.new.sanitize_for_mass_assignment(a: "b")) end end diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb index 27fdbc739c..4e9f43ad86 100644 --- a/activemodel/test/cases/helper.rb +++ b/activemodel/test/cases/helper.rb @@ -1,7 +1,4 @@ -require File.expand_path('../../../../load_paths', __FILE__) - -require 'active_model' -require 'active_support/core_ext/string/access' +require "active_model" # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true @@ -9,15 +6,15 @@ ActiveSupport::Deprecation.debug = true # Disable available locale checks to avoid warnings running the test suite. I18n.enforce_available_locales = false -require 'active_support/testing/autorun' -require 'active_support/testing/method_call_assertions' +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' +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 = '') +def jruby_skip(message = "") skip message if defined?(JRUBY_VERSION) end diff --git a/activemodel/test/cases/lint_test.rb b/activemodel/test/cases/lint_test.rb index 8faf93c056..7a817d7c01 100644 --- a/activemodel/test/cases/lint_test.rb +++ b/activemodel/test/cases/lint_test.rb @@ -1,4 +1,4 @@ -require 'cases/helper' +require "cases/helper" class LintTest < ActiveModel::TestCase include ActiveModel::Lint::Tests diff --git a/activemodel/test/cases/model_test.rb b/activemodel/test/cases/model_test.rb index 3017f3541b..ba87cd1506 100644 --- a/activemodel/test/cases/model_test.rb +++ b/activemodel/test/cases/model_test.rb @@ -1,4 +1,4 @@ -require 'cases/helper' +require "cases/helper" class ModelTest < ActiveModel::TestCase include ActiveModel::Lint::Tests @@ -9,7 +9,7 @@ class ModelTest < ActiveModel::TestCase end def initialize(*args) - @attr ||= 'default value' + @attr ||= "default value" super end end @@ -50,7 +50,7 @@ class ModelTest < ActiveModel::TestCase BasicModel.new() BasicModel.new(nil) BasicModel.new({}) - SimpleModel.new(attr: 'value') + SimpleModel.new(attr: "value") end end @@ -61,17 +61,17 @@ class ModelTest < ActiveModel::TestCase def test_mixin_inclusion_chain object = BasicModel.new - assert_equal 'default value', object.attr + assert_equal "default value", object.attr end def test_mixin_initializer_when_args_exist - object = BasicModel.new(hello: 'world') - assert_equal 'world', object.hello + object = BasicModel.new(hello: "world") + assert_equal "world", object.hello end def test_mixin_initializer_when_args_dont_exist assert_raises(ActiveModel::UnknownAttributeError) do - SimpleModel.new(hello: 'world') + SimpleModel.new(hello: "world") end end end diff --git a/activemodel/test/cases/naming_test.rb b/activemodel/test/cases/naming_test.rb index 7b8287edbf..d5cb1a62bc 100644 --- a/activemodel/test/cases/naming_test.rb +++ b/activemodel/test/cases/naming_test.rb @@ -1,8 +1,8 @@ -require 'cases/helper' -require 'models/contact' -require 'models/sheep' -require 'models/track_back' -require 'models/blog_post' +require "cases/helper" +require "models/contact" +require "models/sheep" +require "models/track_back" +require "models/blog_post" class NamingTest < ActiveModel::TestCase def setup @@ -10,31 +10,31 @@ class NamingTest < ActiveModel::TestCase end def test_singular - assert_equal 'post_track_back', @model_name.singular + assert_equal "post_track_back", @model_name.singular end def test_plural - assert_equal 'post_track_backs', @model_name.plural + assert_equal "post_track_backs", @model_name.plural end def test_element - assert_equal 'track_back', @model_name.element + assert_equal "track_back", @model_name.element end def test_collection - assert_equal 'post/track_backs', @model_name.collection + assert_equal "post/track_backs", @model_name.collection end def test_human - assert_equal 'Track back', @model_name.human + assert_equal "Track back", @model_name.human end def test_route_key - assert_equal 'post_track_backs', @model_name.route_key + assert_equal "post_track_backs", @model_name.route_key end def test_param_key - assert_equal 'post_track_back', @model_name.param_key + assert_equal "post_track_back", @model_name.param_key end def test_i18n_key @@ -48,31 +48,31 @@ class NamingWithNamespacedModelInIsolatedNamespaceTest < ActiveModel::TestCase end def test_singular - assert_equal 'blog_post', @model_name.singular + assert_equal "blog_post", @model_name.singular end def test_plural - assert_equal 'blog_posts', @model_name.plural + assert_equal "blog_posts", @model_name.plural end def test_element - assert_equal 'post', @model_name.element + assert_equal "post", @model_name.element end def test_collection - assert_equal 'blog/posts', @model_name.collection + assert_equal "blog/posts", @model_name.collection end def test_human - assert_equal 'Post', @model_name.human + assert_equal "Post", @model_name.human end def test_route_key - assert_equal 'posts', @model_name.route_key + assert_equal "posts", @model_name.route_key end def test_param_key - assert_equal 'post', @model_name.param_key + assert_equal "post", @model_name.param_key end def test_i18n_key @@ -86,31 +86,31 @@ class NamingWithNamespacedModelInSharedNamespaceTest < ActiveModel::TestCase end def test_singular - assert_equal 'blog_post', @model_name.singular + assert_equal "blog_post", @model_name.singular end def test_plural - assert_equal 'blog_posts', @model_name.plural + assert_equal "blog_posts", @model_name.plural end def test_element - assert_equal 'post', @model_name.element + assert_equal "post", @model_name.element end def test_collection - assert_equal 'blog/posts', @model_name.collection + assert_equal "blog/posts", @model_name.collection end def test_human - assert_equal 'Post', @model_name.human + assert_equal "Post", @model_name.human end def test_route_key - assert_equal 'blog_posts', @model_name.route_key + assert_equal "blog_posts", @model_name.route_key end def test_param_key - assert_equal 'blog_post', @model_name.param_key + assert_equal "blog_post", @model_name.param_key end def test_i18n_key @@ -120,35 +120,35 @@ end class NamingWithSuppliedModelNameTest < ActiveModel::TestCase def setup - @model_name = ActiveModel::Name.new(Blog::Post, nil, 'Article') + @model_name = ActiveModel::Name.new(Blog::Post, nil, "Article") end def test_singular - assert_equal 'article', @model_name.singular + assert_equal "article", @model_name.singular end def test_plural - assert_equal 'articles', @model_name.plural + assert_equal "articles", @model_name.plural end def test_element - assert_equal 'article', @model_name.element + assert_equal "article", @model_name.element end def test_collection - assert_equal 'articles', @model_name.collection + assert_equal "articles", @model_name.collection end def test_human - assert_equal 'Article', @model_name.human + assert_equal "Article", @model_name.human end def test_route_key - assert_equal 'articles', @model_name.route_key + assert_equal "articles", @model_name.route_key end def test_param_key - assert_equal 'article', @model_name.param_key + assert_equal "article", @model_name.param_key end def test_i18n_key @@ -162,31 +162,31 @@ class NamingUsingRelativeModelNameTest < ActiveModel::TestCase end def test_singular - assert_equal 'blog_post', @model_name.singular + assert_equal "blog_post", @model_name.singular end def test_plural - assert_equal 'blog_posts', @model_name.plural + assert_equal "blog_posts", @model_name.plural end def test_element - assert_equal 'post', @model_name.element + assert_equal "post", @model_name.element end def test_collection - assert_equal 'blog/posts', @model_name.collection + assert_equal "blog/posts", @model_name.collection end def test_human - assert_equal 'Post', @model_name.human + assert_equal "Post", @model_name.human end def test_route_key - assert_equal 'posts', @model_name.route_key + assert_equal "posts", @model_name.route_key end def test_param_key - assert_equal 'post', @model_name.param_key + assert_equal "post", @model_name.param_key end def test_i18n_key @@ -198,16 +198,16 @@ class NamingHelpersTest < ActiveModel::TestCase def setup @klass = Contact @record = @klass.new - @singular = 'contact' - @plural = 'contacts' + @singular = "contact" + @plural = "contacts" @uncountable = Sheep - @singular_route_key = 'contact' - @route_key = 'contacts' - @param_key = 'contact' + @singular_route_key = "contact" + @route_key = "contacts" + @param_key = "contact" end def test_to_model_called_on_record - assert_equal 'post_named_track_backs', plural(Post::TrackBack.new) + assert_equal "post_named_track_backs", plural(Post::TrackBack.new) end def test_singular diff --git a/activemodel/test/cases/railtie_test.rb b/activemodel/test/cases/railtie_test.rb index 96b3b07e50..a56b26b5ee 100644 --- a/activemodel/test/cases/railtie_test.rb +++ b/activemodel/test/cases/railtie_test.rb @@ -1,11 +1,11 @@ -require 'cases/helper' -require 'active_support/testing/isolation' +require "cases/helper" +require "active_support/testing/isolation" class RailtieTest < ActiveModel::TestCase include ActiveSupport::Testing::Isolation def setup - require 'active_model/railtie' + require "active_model/railtie" # Set a fake logger to avoid creating the log directory automatically fake_logger = Logger.new(nil) @@ -16,15 +16,15 @@ class RailtieTest < ActiveModel::TestCase end end - test 'secure password min_cost is false in the development environment' do - Rails.env = 'development' + test "secure password min_cost is false in the development environment" do + Rails.env = "development" @app.initialize! assert_equal false, ActiveModel::SecurePassword.min_cost end - test 'secure password min_cost is true in the test environment' do - Rails.env = 'test' + test "secure password min_cost is true in the test environment" do + Rails.env = "test" @app.initialize! assert_equal true, ActiveModel::SecurePassword.min_cost diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb index 6d56c8344a..9423df2c09 100644 --- a/activemodel/test/cases/secure_password_test.rb +++ b/activemodel/test/cases/secure_password_test.rb @@ -1,6 +1,6 @@ -require 'cases/helper' -require 'models/user' -require 'models/visitor' +require "cases/helper" +require "models/user" +require "models/visitor" class SecurePasswordTest < ActiveModel::TestCase setup do @@ -13,7 +13,7 @@ class SecurePasswordTest < ActiveModel::TestCase # Simulate loading an existing user from the DB @existing_user = User.new - @existing_user.password_digest = BCrypt::Password.create('password', cost: BCrypt::Engine::MIN_COST) + @existing_user.password_digest = BCrypt::Password.create("password", cost: BCrypt::Engine::MIN_COST) end teardown do @@ -29,152 +29,152 @@ class SecurePasswordTest < ActiveModel::TestCase end test "create a new user with validations and valid password/confirmation" do - @user.password = 'password' - @user.password_confirmation = 'password' + @user.password = "password" + @user.password_confirmation = "password" - assert @user.valid?(:create), 'user should be valid' + assert @user.valid?(:create), "user should be valid" - @user.password = 'a' * 72 - @user.password_confirmation = 'a' * 72 + @user.password = "a" * 72 + @user.password_confirmation = "a" * 72 - assert @user.valid?(:create), 'user should be valid' + assert @user.valid?(:create), "user should be valid" end test "create a new user with validation and a spaces only password" do - @user.password = ' ' * 72 - assert @user.valid?(:create), 'user should be valid' + @user.password = " " * 72 + assert @user.valid?(:create), "user should be valid" end test "create a new user with validation and a blank password" do - @user.password = '' - assert !@user.valid?(:create), 'user should be invalid' + @user.password = "" + assert !@user.valid?(:create), "user should be invalid" assert_equal 1, @user.errors.count assert_equal ["can't be blank"], @user.errors[:password] end test "create a new user with validation and a nil password" do @user.password = nil - assert !@user.valid?(:create), 'user should be invalid' + assert !@user.valid?(:create), "user should be invalid" assert_equal 1, @user.errors.count assert_equal ["can't be blank"], @user.errors[:password] end - test 'create a new user with validation and password length greater than 72' do - @user.password = 'a' * 73 - @user.password_confirmation = 'a' * 73 - assert !@user.valid?(:create), 'user should be invalid' + test "create a new user with validation and password length greater than 72" do + @user.password = "a" * 73 + @user.password_confirmation = "a" * 73 + assert !@user.valid?(:create), "user should be invalid" assert_equal 1, @user.errors.count assert_equal ["is too long (maximum is 72 characters)"], @user.errors[:password] end test "create a new user with validation and a blank password confirmation" do - @user.password = 'password' - @user.password_confirmation = '' - assert !@user.valid?(:create), 'user should be invalid' + @user.password = "password" + @user.password_confirmation = "" + assert !@user.valid?(:create), "user should be invalid" assert_equal 1, @user.errors.count assert_equal ["doesn't match Password"], @user.errors[:password_confirmation] end test "create a new user with validation and a nil password confirmation" do - @user.password = 'password' + @user.password = "password" @user.password_confirmation = nil - assert @user.valid?(:create), 'user should be valid' + assert @user.valid?(:create), "user should be valid" end test "create a new user with validation and an incorrect password confirmation" do - @user.password = 'password' - @user.password_confirmation = 'something else' - assert !@user.valid?(:create), 'user should be invalid' + @user.password = "password" + @user.password_confirmation = "something else" + assert !@user.valid?(:create), "user should be invalid" assert_equal 1, @user.errors.count assert_equal ["doesn't match Password"], @user.errors[:password_confirmation] end test "update an existing user with validation and no change in password" do - assert @existing_user.valid?(:update), 'user should be valid' + assert @existing_user.valid?(:update), "user should be valid" end test "update an existing user with validations and valid password/confirmation" do - @existing_user.password = 'password' - @existing_user.password_confirmation = 'password' + @existing_user.password = "password" + @existing_user.password_confirmation = "password" - assert @existing_user.valid?(:update), 'user should be valid' + assert @existing_user.valid?(:update), "user should be valid" - @existing_user.password = 'a' * 72 - @existing_user.password_confirmation = 'a' * 72 + @existing_user.password = "a" * 72 + @existing_user.password_confirmation = "a" * 72 - assert @existing_user.valid?(:update), 'user should be valid' + assert @existing_user.valid?(:update), "user should be valid" end test "updating an existing user with validation and a blank password" do - @existing_user.password = '' - assert @existing_user.valid?(:update), 'user should be valid' + @existing_user.password = "" + assert @existing_user.valid?(:update), "user should be valid" end test "updating an existing user with validation and a spaces only password" do - @user.password = ' ' * 72 - assert @user.valid?(:update), 'user should be valid' + @user.password = " " * 72 + assert @user.valid?(:update), "user should be valid" end test "updating an existing user with validation and a blank password and password_confirmation" do - @existing_user.password = '' - @existing_user.password_confirmation = '' - assert @existing_user.valid?(:update), 'user should be valid' + @existing_user.password = "" + @existing_user.password_confirmation = "" + assert @existing_user.valid?(:update), "user should be valid" end test "updating an existing user with validation and a nil password" do @existing_user.password = nil - assert !@existing_user.valid?(:update), 'user should be invalid' + assert !@existing_user.valid?(:update), "user should be invalid" assert_equal 1, @existing_user.errors.count assert_equal ["can't be blank"], @existing_user.errors[:password] end - test 'updating an existing user with validation and password length greater than 72' do - @existing_user.password = 'a' * 73 - @existing_user.password_confirmation = 'a' * 73 - assert !@existing_user.valid?(:update), 'user should be invalid' + test "updating an existing user with validation and password length greater than 72" do + @existing_user.password = "a" * 73 + @existing_user.password_confirmation = "a" * 73 + assert !@existing_user.valid?(:update), "user should be invalid" assert_equal 1, @existing_user.errors.count assert_equal ["is too long (maximum is 72 characters)"], @existing_user.errors[:password] end test "updating an existing user with validation and a blank password confirmation" do - @existing_user.password = 'password' - @existing_user.password_confirmation = '' - assert !@existing_user.valid?(:update), 'user should be invalid' + @existing_user.password = "password" + @existing_user.password_confirmation = "" + assert !@existing_user.valid?(:update), "user should be invalid" assert_equal 1, @existing_user.errors.count assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation] end test "updating an existing user with validation and a nil password confirmation" do - @existing_user.password = 'password' + @existing_user.password = "password" @existing_user.password_confirmation = nil - assert @existing_user.valid?(:update), 'user should be valid' + assert @existing_user.valid?(:update), "user should be valid" end test "updating an existing user with validation and an incorrect password confirmation" do - @existing_user.password = 'password' - @existing_user.password_confirmation = 'something else' - assert !@existing_user.valid?(:update), 'user should be invalid' + @existing_user.password = "password" + @existing_user.password_confirmation = "something else" + assert !@existing_user.valid?(:update), "user should be invalid" assert_equal 1, @existing_user.errors.count assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation] end test "updating an existing user with validation and a blank password digest" do - @existing_user.password_digest = '' - assert !@existing_user.valid?(:update), 'user should be invalid' + @existing_user.password_digest = "" + assert !@existing_user.valid?(:update), "user should be invalid" assert_equal 1, @existing_user.errors.count assert_equal ["can't be blank"], @existing_user.errors[:password] end test "updating an existing user with validation and a nil password digest" do @existing_user.password_digest = nil - assert !@existing_user.valid?(:update), 'user should be invalid' + assert !@existing_user.valid?(:update), "user should be invalid" assert_equal 1, @existing_user.errors.count assert_equal ["can't be blank"], @existing_user.errors[:password] end test "setting a blank password should not change an existing password" do - @existing_user.password = '' - assert @existing_user.password_digest == 'password' + @existing_user.password = "" + assert @existing_user.password_digest == "password" end test "setting a nil password should clear an existing password" do diff --git a/activemodel/test/cases/serialization_test.rb b/activemodel/test/cases/serialization_test.rb index 8d3165cd78..5ee53285a3 100644 --- a/activemodel/test/cases/serialization_test.rb +++ b/activemodel/test/cases/serialization_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'active_support/core_ext/object/instance_variables' +require "active_support/core_ext/object/instance_variables" class SerializationTest < ActiveModel::TestCase class User @@ -18,14 +18,14 @@ class SerializationTest < ActiveModel::TestCase def method_missing(method_name, *args) if method_name == :bar - 'i_am_bar' + "i_am_bar" else super end end def foo - 'i_am_foo' + "i_am_foo" end end @@ -40,43 +40,43 @@ class SerializationTest < ActiveModel::TestCase end setup do - @user = User.new('David', 'david@example.com', 'male') + @user = User.new("David", "david@example.com", "male") @user.address = Address.new @user.address.street = "123 Lane" @user.address.city = "Springfield" @user.address.state = "CA" @user.address.zip = 11111 - @user.friends = [User.new('Joe', 'joe@example.com', 'male'), - User.new('Sue', 'sue@example.com', 'female')] + @user.friends = [User.new("Joe", "joe@example.com", "male"), + User.new("Sue", "sue@example.com", "female")] end def test_method_serializable_hash_should_work - expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"} + expected = { "name"=>"David", "gender"=>"male", "email"=>"david@example.com" } assert_equal expected, @user.serializable_hash end def test_method_serializable_hash_should_work_with_only_option - expected = {"name"=>"David"} + expected = { "name"=>"David" } assert_equal expected, @user.serializable_hash(only: [:name]) end def test_method_serializable_hash_should_work_with_except_option - expected = {"gender"=>"male", "email"=>"david@example.com"} + expected = { "gender"=>"male", "email"=>"david@example.com" } assert_equal expected, @user.serializable_hash(except: [:name]) end def test_method_serializable_hash_should_work_with_methods_option - expected = {"name"=>"David", "gender"=>"male", "foo"=>"i_am_foo", "bar"=>"i_am_bar", "email"=>"david@example.com"} + expected = { "name"=>"David", "gender"=>"male", "foo"=>"i_am_foo", "bar"=>"i_am_bar", "email"=>"david@example.com" } assert_equal expected, @user.serializable_hash(methods: [:foo, :bar]) end def test_method_serializable_hash_should_work_with_only_and_methods - expected = {"foo"=>"i_am_foo", "bar"=>"i_am_bar"} + expected = { "foo"=>"i_am_foo", "bar"=>"i_am_bar" } assert_equal expected, @user.serializable_hash(only: [], methods: [:foo, :bar]) end def test_method_serializable_hash_should_work_with_except_and_methods - expected = {"gender"=>"male", "foo"=>"i_am_foo", "bar"=>"i_am_bar"} + expected = { "gender"=>"male", "foo"=>"i_am_foo", "bar"=>"i_am_bar" } assert_equal expected, @user.serializable_hash(except: [:name, :email], methods: [:foo, :bar]) end @@ -94,21 +94,21 @@ class SerializationTest < ActiveModel::TestCase end def test_include_option_with_singular_association - expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com", - "address"=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}} + expected = { "name"=>"David", "gender"=>"male", "email"=>"david@example.com", + "address"=>{ "street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111 } } assert_equal expected, @user.serializable_hash(include: :address) end def test_include_option_with_plural_association - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - "friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, - {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} + expected = { "email"=>"david@example.com", "gender"=>"male", "name"=>"David", + "friends"=>[{ "name"=>"Joe", "email"=>"joe@example.com", "gender"=>"male" }, + { "name"=>"Sue", "email"=>"sue@example.com", "gender"=>"female" }] } assert_equal expected, @user.serializable_hash(include: :friends) end def test_include_option_with_empty_association @user.friends = [] - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", "friends"=>[]} + expected = { "email"=>"david@example.com", "gender"=>"male", "name"=>"David", "friends"=>[] } assert_equal expected, @user.serializable_hash(include: :friends) end @@ -124,52 +124,52 @@ class SerializationTest < ActiveModel::TestCase def test_include_option_with_ary @user.friends = FriendList.new(@user.friends) - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - "friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, - {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} + expected = { "email"=>"david@example.com", "gender"=>"male", "name"=>"David", + "friends"=>[{ "name"=>"Joe", "email"=>"joe@example.com", "gender"=>"male" }, + { "name"=>"Sue", "email"=>"sue@example.com", "gender"=>"female" }] } assert_equal expected, @user.serializable_hash(include: :friends) end def test_multiple_includes - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - "address"=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}, - "friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, - {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} + expected = { "email"=>"david@example.com", "gender"=>"male", "name"=>"David", + "address"=>{ "street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111 }, + "friends"=>[{ "name"=>"Joe", "email"=>"joe@example.com", "gender"=>"male" }, + { "name"=>"Sue", "email"=>"sue@example.com", "gender"=>"female" }] } assert_equal expected, @user.serializable_hash(include: [:address, :friends]) end def test_include_with_options - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - "address"=>{"street"=>"123 Lane"}} + expected = { "email"=>"david@example.com", "gender"=>"male", "name"=>"David", + "address"=>{ "street"=>"123 Lane" } } assert_equal expected, @user.serializable_hash(include: { address: { only: "street" } }) end def test_nested_include @user.friends.first.friends = [@user] - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - "friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male', - "friends"=> [{"email"=>"david@example.com", "gender"=>"male", "name"=>"David"}]}, - {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female', "friends"=> []}]} + expected = { "email"=>"david@example.com", "gender"=>"male", "name"=>"David", + "friends"=>[{ "name"=>"Joe", "email"=>"joe@example.com", "gender"=>"male", + "friends"=> [{ "email"=>"david@example.com", "gender"=>"male", "name"=>"David" }] }, + { "name"=>"Sue", "email"=>"sue@example.com", "gender"=>"female", "friends"=> [] }] } assert_equal expected, @user.serializable_hash(include: { friends: { include: :friends } }) end def test_only_include - expected = {"name"=>"David", "friends" => [{"name" => "Joe"}, {"name" => "Sue"}]} + expected = { "name"=>"David", "friends" => [{ "name" => "Joe" }, { "name" => "Sue" }] } assert_equal expected, @user.serializable_hash(only: :name, include: { friends: { only: :name } }) end def test_except_include - expected = {"name"=>"David", "email"=>"david@example.com", - "friends"=> [{"name" => 'Joe', "email" => 'joe@example.com'}, - {"name" => "Sue", "email" => 'sue@example.com'}]} + expected = { "name"=>"David", "email"=>"david@example.com", + "friends"=> [{ "name" => "Joe", "email" => "joe@example.com" }, + { "name" => "Sue", "email" => "sue@example.com" }] } assert_equal expected, @user.serializable_hash(except: :gender, include: { friends: { except: :gender } }) end def test_multiple_includes_with_options - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - "address"=>{"street"=>"123 Lane"}, - "friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, - {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} - assert_equal expected, @user.serializable_hash(include: [{ address: {only: "street" } }, :friends]) + expected = { "email"=>"david@example.com", "gender"=>"male", "name"=>"David", + "address"=>{ "street"=>"123 Lane" }, + "friends"=>[{ "name"=>"Joe", "email"=>"joe@example.com", "gender"=>"male" }, + { "name"=>"Sue", "email"=>"sue@example.com", "gender"=>"female" }] } + assert_equal expected, @user.serializable_hash(include: [{ address: { only: "street" } }, :friends]) end end diff --git a/activemodel/test/cases/serializers/json_serialization_test.rb b/activemodel/test/cases/serializers/json_serialization_test.rb index d765a47636..d15ba64eb0 100644 --- a/activemodel/test/cases/serializers/json_serialization_test.rb +++ b/activemodel/test/cases/serializers/json_serialization_test.rb @@ -1,15 +1,15 @@ -require 'cases/helper' -require 'models/contact' -require 'active_support/core_ext/object/instance_variables' +require "cases/helper" +require "models/contact" +require "active_support/core_ext/object/instance_variables" class JsonSerializationTest < ActiveModel::TestCase def setup @contact = Contact.new - @contact.name = 'Konata Izumi' + @contact.name = "Konata Izumi" @contact.age = 16 @contact.created_at = Time.utc(2006, 8, 1) @contact.awesome = true - @contact.preferences = { 'shows' => 'anime' } + @contact.preferences = { "shows" => "anime" } end test "should not include root in json (class method)" do @@ -18,7 +18,7 @@ class JsonSerializationTest < ActiveModel::TestCase assert_no_match %r{^\{"contact":\{}, json assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json - assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}) assert_match %r{"awesome":true}, json assert_match %r{"preferences":\{"shows":"anime"\}}, json end @@ -32,7 +32,7 @@ class JsonSerializationTest < ActiveModel::TestCase assert_match %r{^\{"contact":\{}, json assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json - assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}) assert_match %r{"awesome":true}, json assert_match %r{"preferences":\{"shows":"anime"\}}, json ensure @@ -53,12 +53,12 @@ class JsonSerializationTest < ActiveModel::TestCase end test "should include custom root in json" do - json = @contact.to_json(root: 'json_contact') + json = @contact.to_json(root: "json_contact") assert_match %r{^\{"json_contact":\{}, json assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json - assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}) assert_match %r{"awesome":true}, json assert_match %r{"preferences":\{"shows":"anime"\}}, json end @@ -68,7 +68,7 @@ class JsonSerializationTest < ActiveModel::TestCase assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json - assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}) assert_match %r{"awesome":true}, json assert_match %r{"preferences":\{"shows":"anime"\}}, json end @@ -79,7 +79,7 @@ class JsonSerializationTest < ActiveModel::TestCase assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json assert_no_match %r{"awesome":true}, json - assert !json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_not_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}) assert_no_match %r{"preferences":\{"shows":"anime"\}}, json end @@ -89,7 +89,7 @@ class JsonSerializationTest < ActiveModel::TestCase assert_no_match %r{"name":"Konata Izumi"}, json assert_no_match %r{"age":16}, json assert_match %r{"awesome":true}, json - assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}) assert_match %r{"preferences":\{"shows":"anime"\}}, json end @@ -134,9 +134,9 @@ class JsonSerializationTest < ActiveModel::TestCase json = @contact.as_json assert_kind_of Hash, json - assert_kind_of Hash, json['contact'] + assert_kind_of Hash, json["contact"] %w(name age created_at awesome preferences).each do |field| - assert_equal @contact.send(field), json['contact'][field] + assert_equal @contact.send(field), json["contact"][field] end ensure Contact.include_root_in_json = original_include_root_in_json diff --git a/activemodel/test/cases/translation_test.rb b/activemodel/test/cases/translation_test.rb index 2c89388f14..9972f9daea 100644 --- a/activemodel/test/cases/translation_test.rb +++ b/activemodel/test/cases/translation_test.rb @@ -1,8 +1,7 @@ -require 'cases/helper' -require 'models/person' +require "cases/helper" +require "models/person" class ActiveModelI18nTests < ActiveModel::TestCase - def setup I18n.backend = I18n::Backend::Simple.new end @@ -12,102 +11,101 @@ class ActiveModelI18nTests < ActiveModel::TestCase end def test_translated_model_attributes - I18n.backend.store_translations 'en', activemodel: { attributes: { person: { name: 'person name attribute' } } } - assert_equal 'person name attribute', Person.human_attribute_name('name') + I18n.backend.store_translations "en", activemodel: { attributes: { person: { name: "person name attribute" } } } + assert_equal "person name attribute", Person.human_attribute_name("name") end def test_translated_model_attributes_with_default - I18n.backend.store_translations 'en', attributes: { name: 'name default attribute' } - assert_equal 'name default attribute', Person.human_attribute_name('name') + I18n.backend.store_translations "en", attributes: { name: "name default attribute" } + assert_equal "name default attribute", Person.human_attribute_name("name") end def test_translated_model_attributes_using_default_option - assert_equal 'name default attribute', Person.human_attribute_name('name', default: "name default attribute") + assert_equal "name default attribute", Person.human_attribute_name("name", default: "name default attribute") end def test_translated_model_attributes_using_default_option_as_symbol - I18n.backend.store_translations 'en', default_name: 'name default attribute' - assert_equal 'name default attribute', Person.human_attribute_name('name', default: :default_name) + I18n.backend.store_translations "en", default_name: "name default attribute" + assert_equal "name default attribute", Person.human_attribute_name("name", default: :default_name) end def test_translated_model_attributes_falling_back_to_default - assert_equal 'Name', Person.human_attribute_name('name') + assert_equal "Name", Person.human_attribute_name("name") end def test_translated_model_attributes_using_default_option_as_symbol_and_falling_back_to_default - assert_equal 'Name', Person.human_attribute_name('name', default: :default_name) + assert_equal "Name", Person.human_attribute_name("name", default: :default_name) end def test_translated_model_attributes_with_symbols - I18n.backend.store_translations 'en', activemodel: { attributes: { person: { name: 'person name attribute'} } } - assert_equal 'person name attribute', Person.human_attribute_name(:name) + I18n.backend.store_translations "en", activemodel: { attributes: { person: { name: "person name attribute" } } } + assert_equal "person name attribute", Person.human_attribute_name(:name) end def test_translated_model_attributes_with_ancestor - I18n.backend.store_translations 'en', activemodel: { attributes: { child: { name: 'child name attribute'} } } - assert_equal 'child name attribute', Child.human_attribute_name('name') + I18n.backend.store_translations "en", activemodel: { attributes: { child: { name: "child name attribute" } } } + assert_equal "child name attribute", Child.human_attribute_name("name") end def test_translated_model_attributes_with_ancestors_fallback - I18n.backend.store_translations 'en', activemodel: { attributes: { person: { name: 'person name attribute'} } } - assert_equal 'person name attribute', Child.human_attribute_name('name') + I18n.backend.store_translations "en", activemodel: { attributes: { person: { name: "person name attribute" } } } + assert_equal "person name attribute", Child.human_attribute_name("name") end def test_translated_model_attributes_with_attribute_matching_namespaced_model_name - I18n.backend.store_translations 'en', activemodel: { attributes: { - person: { gender: 'person gender'}, - :"person/gender" => { attribute: 'person gender attribute' } + I18n.backend.store_translations "en", activemodel: { attributes: { + person: { gender: "person gender" }, + "person/gender": { attribute: "person gender attribute" } } } - assert_equal 'person gender', Person.human_attribute_name('gender') - assert_equal 'person gender attribute', Person::Gender.human_attribute_name('attribute') + assert_equal "person gender", Person.human_attribute_name("gender") + assert_equal "person gender attribute", Person::Gender.human_attribute_name("attribute") end def test_translated_deeply_nested_model_attributes - I18n.backend.store_translations 'en', activemodel: { attributes: { :"person/contacts/addresses" => { street: 'Deeply Nested Address Street' } } } - assert_equal 'Deeply Nested Address Street', Person.human_attribute_name('contacts.addresses.street') + I18n.backend.store_translations "en", activemodel: { attributes: { "person/contacts/addresses": { street: "Deeply Nested Address Street" } } } + assert_equal "Deeply Nested Address Street", Person.human_attribute_name("contacts.addresses.street") end def test_translated_nested_model_attributes - I18n.backend.store_translations 'en', activemodel: { attributes: { :"person/addresses" => { street: 'Person Address Street' } } } - assert_equal 'Person Address Street', Person.human_attribute_name('addresses.street') + I18n.backend.store_translations "en", activemodel: { attributes: { "person/addresses": { street: "Person Address Street" } } } + assert_equal "Person Address Street", Person.human_attribute_name("addresses.street") end def test_translated_nested_model_attributes_with_namespace_fallback - I18n.backend.store_translations 'en', activemodel: { attributes: { addresses: { street: 'Cool Address Street' } } } - assert_equal 'Cool Address Street', Person.human_attribute_name('addresses.street') + I18n.backend.store_translations "en", activemodel: { attributes: { addresses: { street: "Cool Address Street" } } } + assert_equal "Cool Address Street", Person.human_attribute_name("addresses.street") end def test_translated_model_names - I18n.backend.store_translations 'en', activemodel: { models: { person: 'person model' } } - assert_equal 'person model', Person.model_name.human + I18n.backend.store_translations "en", activemodel: { models: { person: "person model" } } + assert_equal "person model", Person.model_name.human end def test_translated_model_names_with_sti - I18n.backend.store_translations 'en', activemodel: { models: { child: 'child model' } } - assert_equal 'child model', Child.model_name.human + I18n.backend.store_translations "en", activemodel: { models: { child: "child model" } } + assert_equal "child model", Child.model_name.human end def test_translated_model_with_namespace - I18n.backend.store_translations 'en', activemodel: { models: { 'person/gender': 'gender model' } } - assert_equal 'gender model', Person::Gender.model_name.human + I18n.backend.store_translations "en", activemodel: { models: { 'person/gender': "gender model" } } + assert_equal "gender model", Person::Gender.model_name.human end def test_translated_model_names_with_ancestors_fallback - I18n.backend.store_translations 'en', activemodel: { models: { person: 'person model' } } - assert_equal 'person model', Child.model_name.human + I18n.backend.store_translations "en", activemodel: { models: { person: "person model" } } + assert_equal "person model", Child.model_name.human end def test_human_does_not_modify_options - options = { default: 'person model' } + options = { default: "person model" } Person.model_name.human(options) - assert_equal({ default: 'person model' }, options) + assert_equal({ default: "person model" }, options) end def test_human_attribute_name_does_not_modify_options - options = { default: 'Cool gender' } - Person.human_attribute_name('gender', options) - assert_equal({ default: 'Cool gender' }, options) + options = { default: "Cool gender" } + Person.human_attribute_name("gender", options) + assert_equal({ default: "Cool gender" }, options) end end - diff --git a/activemodel/test/cases/type/big_integer_test.rb b/activemodel/test/cases/type/big_integer_test.rb new file mode 100644 index 0000000000..db1d215083 --- /dev/null +++ b/activemodel/test/cases/type/big_integer_test.rb @@ -0,0 +1,24 @@ +require "cases/helper" +require "active_model/type" + +module ActiveModel + module Type + class BigIntegerTest < ActiveModel::TestCase + def test_type_cast_big_integer + type = Type::BigInteger.new + assert_equal 1, type.cast(1) + assert_equal 1, type.cast("1") + end + + def test_small_values + type = Type::BigInteger.new + assert_equal -9999999999999999999999999999999, type.serialize(-9999999999999999999999999999999) + end + + def test_large_values + type = Type::BigInteger.new + assert_equal 9999999999999999999999999999999, type.serialize(9999999999999999999999999999999) + end + end + end +end diff --git a/activemodel/test/cases/type/binary_test.rb b/activemodel/test/cases/type/binary_test.rb new file mode 100644 index 0000000000..e6a32dbeec --- /dev/null +++ b/activemodel/test/cases/type/binary_test.rb @@ -0,0 +1,15 @@ +require "cases/helper" +require "active_model/type" + +module ActiveModel + module Type + class BinaryTest < ActiveModel::TestCase + def test_type_cast_binary + type = Type::Binary.new + assert_equal nil, type.cast(nil) + assert_equal "1", type.cast("1") + assert_equal 1, type.cast(1) + end + end + end +end diff --git a/activemodel/test/cases/type/boolean_test.rb b/activemodel/test/cases/type/boolean_test.rb new file mode 100644 index 0000000000..92e5aebfb7 --- /dev/null +++ b/activemodel/test/cases/type/boolean_test.rb @@ -0,0 +1,39 @@ +require "cases/helper" +require "active_model/type" + +module ActiveModel + module Type + class BooleanTest < ActiveModel::TestCase + def test_type_cast_boolean + type = Type::Boolean.new + assert type.cast("").nil? + assert type.cast(nil).nil? + + assert type.cast(true) + assert type.cast(1) + assert type.cast("1") + assert type.cast("t") + assert type.cast("T") + assert type.cast("true") + assert type.cast("TRUE") + assert type.cast("on") + assert type.cast("ON") + assert type.cast(" ") + assert type.cast("\u3000\r\n") + assert type.cast("\u0000") + assert type.cast("SOMETHING RANDOM") + + # explicitly check for false vs nil + assert_equal false, type.cast(false) + assert_equal false, type.cast(0) + assert_equal false, type.cast("0") + assert_equal false, type.cast("f") + assert_equal false, type.cast("F") + assert_equal false, type.cast("false") + assert_equal false, type.cast("FALSE") + assert_equal false, type.cast("off") + assert_equal false, type.cast("OFF") + end + end + end +end diff --git a/activemodel/test/cases/type/date_test.rb b/activemodel/test/cases/type/date_test.rb new file mode 100644 index 0000000000..44e20a327b --- /dev/null +++ b/activemodel/test/cases/type/date_test.rb @@ -0,0 +1,19 @@ +require "cases/helper" +require "active_model/type" + +module ActiveModel + module Type + 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") + + date_string = ::Time.now.utc.strftime("%F") + assert_equal date_string, type.cast(date_string).strftime("%F") + end + end + end +end diff --git a/activemodel/test/cases/type/date_time_test.rb b/activemodel/test/cases/type/date_time_test.rb new file mode 100644 index 0000000000..fb82260d2b --- /dev/null +++ b/activemodel/test/cases/type/date_time_test.rb @@ -0,0 +1,38 @@ +require "cases/helper" +require "active_model/type" + +module ActiveModel + module Type + 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") + + datetime_string = ::Time.now.utc.strftime("%FT%T") + assert_equal datetime_string, type.cast(datetime_string).strftime("%FT%T") + end + + def test_string_to_time_with_timezone + ["UTC", "US/Eastern"].each do |zone| + with_timezone_config default: zone do + type = Type::DateTime.new + assert_equal ::Time.utc(2013, 9, 4, 0, 0, 0), type.cast("Wed, 04 Sep 2013 03:00:00 EAT") + end + end + end + + private + + def with_timezone_config(default:) + old_zone_default = ::Time.zone_default + ::Time.zone_default = ::Time.find_zone(default) + yield + ensure + ::Time.zone_default = old_zone_default + end + end + end +end diff --git a/activemodel/test/cases/type/decimal_test.rb b/activemodel/test/cases/type/decimal_test.rb index 353dbf84ad..46a913258e 100644 --- a/activemodel/test/cases/type/decimal_test.rb +++ b/activemodel/test/cases/type/decimal_test.rb @@ -48,9 +48,16 @@ module ActiveModel def test_changed? type = Decimal.new - assert type.changed?(5.0, 5.0, '5.0wibble') - assert_not type.changed?(5.0, 5.0, '5.0') - assert_not type.changed?(-5.0, -5.0, '-5.0') + assert type.changed?(5.0, 5.0, "5.0wibble") + assert_not type.changed?(5.0, 5.0, "5.0") + assert_not type.changed?(-5.0, -5.0, "-5.0") + end + + def test_scale_is_applied_before_precision_to_prevent_rounding_errors + type = Decimal.new(precision: 5, scale: 3) + + assert_equal BigDecimal("1.250"), type.cast(1.250473853637869) + assert_equal BigDecimal("1.250"), type.cast("1.250473853637869") end end end diff --git a/activemodel/test/cases/type/float_test.rb b/activemodel/test/cases/type/float_test.rb new file mode 100644 index 0000000000..2e34f57f7e --- /dev/null +++ b/activemodel/test/cases/type/float_test.rb @@ -0,0 +1,22 @@ +require "cases/helper" +require "active_model/type" + +module ActiveModel + module Type + class FloatTest < ActiveModel::TestCase + def test_type_cast_float + type = Type::Float.new + assert_equal 1.0, type.cast("1") + end + + def test_changing_float + type = Type::Float.new + + assert type.changed?(5.0, 5.0, "5wibble") + assert_not type.changed?(5.0, 5.0, "5") + assert_not type.changed?(5.0, 5.0, "5.0") + assert_not type.changed?(nil, nil, nil) + end + end + end +end diff --git a/activemodel/test/cases/type/immutable_string_test.rb b/activemodel/test/cases/type/immutable_string_test.rb new file mode 100644 index 0000000000..23e58974fb --- /dev/null +++ b/activemodel/test/cases/type/immutable_string_test.rb @@ -0,0 +1,21 @@ +require "cases/helper" +require "active_model/type" + +module ActiveModel + module Type + class ImmutableStringTest < ActiveModel::TestCase + test "cast strings are frozen" do + s = "foo" + type = Type::ImmutableString.new + assert_equal true, type.cast(s).frozen? + end + + test "immutable strings are not duped coming out" do + s = "foo" + type = Type::ImmutableString.new + assert_same s, type.cast(s) + assert_same s, type.deserialize(s) + end + end + end +end diff --git a/activemodel/test/cases/type/integer_test.rb b/activemodel/test/cases/type/integer_test.rb index dac922db42..d81ce189e6 100644 --- a/activemodel/test/cases/type/integer_test.rb +++ b/activemodel/test/cases/type/integer_test.rb @@ -1,5 +1,6 @@ require "cases/helper" require "active_model/type" +require "active_support/core_ext/numeric/time" module ActiveModel module Type @@ -7,10 +8,10 @@ module ActiveModel test "simple values" do type = Type::Integer.new assert_equal 1, type.cast(1) - assert_equal 1, type.cast('1') - assert_equal 1, type.cast('1ignore') - assert_equal 0, type.cast('bad1') - assert_equal 0, type.cast('bad') + assert_equal 1, type.cast("1") + assert_equal 1, type.cast("1ignore") + assert_equal 0, type.cast("bad1") + assert_equal 0, type.cast("bad") assert_equal 1, type.cast(1.7) assert_equal 0, type.cast(false) assert_equal 1, type.cast(true) @@ -20,7 +21,7 @@ module ActiveModel test "random objects cast to nil" do type = Type::Integer.new assert_nil type.cast([1,2]) - assert_nil type.cast({1 => 2}) + assert_nil type.cast(1 => 2) assert_nil type.cast(1..2) end @@ -41,37 +42,43 @@ module ActiveModel assert_equal 0, type.serialize(false) end + test "casting duration" do + type = Type::Integer.new + assert_equal 1800, type.cast(30.minutes) + assert_equal 7200, type.cast(2.hours) + end + test "changed?" do type = Type::Integer.new - assert type.changed?(5, 5, '5wibble') - assert_not type.changed?(5, 5, '5') - assert_not type.changed?(5, 5, '5.0') - assert_not type.changed?(-5, -5, '-5') - assert_not type.changed?(-5, -5, '-5.0') + assert type.changed?(5, 5, "5wibble") + assert_not type.changed?(5, 5, "5") + assert_not type.changed?(5, 5, "5.0") + assert_not type.changed?(-5, -5, "-5") + assert_not type.changed?(-5, -5, "-5.0") assert_not type.changed?(nil, nil, nil) end test "values below int min value are out of range" do - assert_raises(::RangeError) do + assert_raises(ActiveModel::RangeError) do Integer.new.serialize(-2147483649) end end test "values above int max value are out of range" do - assert_raises(::RangeError) do + assert_raises(ActiveModel::RangeError) do Integer.new.serialize(2147483648) end end test "very small numbers are out of range" do - assert_raises(::RangeError) do + assert_raises(ActiveModel::RangeError) do Integer.new.serialize(-9999999999999999999999999999999) end end test "very large numbers are out of range" do - assert_raises(::RangeError) do + assert_raises(ActiveModel::RangeError) do Integer.new.serialize(9999999999999999999999999999999) end end @@ -96,10 +103,10 @@ module ActiveModel assert_equal(9223372036854775807, type.serialize(9223372036854775807)) assert_equal(-9223372036854775808, type.serialize(-9223372036854775808)) - assert_raises(::RangeError) do + assert_raises(ActiveModel::RangeError) do type.serialize(-9999999999999999999999999999999) end - assert_raises(::RangeError) do + assert_raises(ActiveModel::RangeError) do type.serialize(9999999999999999999999999999999) end end diff --git a/activemodel/test/cases/type/registry_test.rb b/activemodel/test/cases/type/registry_test.rb index 2a48998a62..927b6d0307 100644 --- a/activemodel/test/cases/type/registry_test.rb +++ b/activemodel/test/cases/type/registry_test.rb @@ -2,38 +2,40 @@ require "cases/helper" require "active_model/type" module ActiveModel - class RegistryTest < ActiveModel::TestCase - test "a class can be registered for a symbol" do - registry = Type::Registry.new - registry.register(:foo, ::String) - registry.register(:bar, ::Array) + module Type + class RegistryTest < ActiveModel::TestCase + test "a class can be registered for a symbol" do + registry = Type::Registry.new + registry.register(:foo, ::String) + registry.register(:bar, ::Array) - assert_equal "", registry.lookup(:foo) - assert_equal [], registry.lookup(:bar) - end - - test "a block can be registered" do - registry = Type::Registry.new - registry.register(:foo) do |*args| - [*args, "block for foo"] + assert_equal "", registry.lookup(:foo) + assert_equal [], registry.lookup(:bar) end - registry.register(:bar) do |*args| - [*args, "block for bar"] + + test "a block can be registered" do + registry = Type::Registry.new + registry.register(:foo) do |*args| + [*args, "block for foo"] + end + registry.register(:bar) do |*args| + [*args, "block for bar"] + end + + assert_equal [:foo, 1, "block for foo"], registry.lookup(:foo, 1) + assert_equal [:foo, 2, "block for foo"], registry.lookup(:foo, 2) + assert_equal [:bar, 1, 2, 3, "block for bar"], registry.lookup(:bar, 1, 2, 3) end - assert_equal [:foo, 1, "block for foo"], registry.lookup(:foo, 1) - assert_equal [:foo, 2, "block for foo"], registry.lookup(:foo, 2) - assert_equal [:bar, 1, 2, 3, "block for bar"], registry.lookup(:bar, 1, 2, 3) - end + test "a reasonable error is given when no type is found" do + registry = Type::Registry.new - test "a reasonable error is given when no type is found" do - registry = Type::Registry.new + e = assert_raises(ArgumentError) do + registry.lookup(:foo) + end - e = assert_raises(ArgumentError) do - registry.lookup(:foo) + assert_equal "Unknown type :foo", e.message end - - assert_equal "Unknown type :foo", e.message end end end diff --git a/activemodel/test/cases/type/string_test.rb b/activemodel/test/cases/type/string_test.rb index 7b25a1ef74..222083817e 100644 --- a/activemodel/test/cases/type/string_test.rb +++ b/activemodel/test/cases/type/string_test.rb @@ -2,26 +2,27 @@ require "cases/helper" require "active_model/type" module ActiveModel - class StringTypeTest < ActiveModel::TestCase - test "type casting" do - type = Type::String.new - assert_equal "t", type.cast(true) - assert_equal "f", type.cast(false) - assert_equal "123", type.cast(123) - end + module Type + class StringTest < ActiveModel::TestCase + test "type casting" do + type = Type::String.new + assert_equal "t", type.cast(true) + assert_equal "f", type.cast(false) + assert_equal "123", type.cast(123) + end - test "immutable strings are not duped coming out" do - s = "foo" - type = Type::ImmutableString.new - assert_same s, type.cast(s) - assert_same s, type.deserialize(s) - end + test "cast strings are mutable" do + s = "foo" + type = Type::String.new + assert_equal false, type.cast(s).frozen? + end - test "values are duped coming out" do - s = "foo" - type = Type::String.new - assert_not_same s, type.cast(s) - assert_not_same s, type.deserialize(s) + test "values are duped coming out" do + s = "foo" + type = Type::String.new + assert_not_same s, type.cast(s) + assert_not_same s, type.deserialize(s) + end end end end diff --git a/activemodel/test/cases/type/time_test.rb b/activemodel/test/cases/type/time_test.rb new file mode 100644 index 0000000000..a6a79833e6 --- /dev/null +++ b/activemodel/test/cases/type/time_test.rb @@ -0,0 +1,21 @@ +require "cases/helper" +require "active_model/type" + +module ActiveModel + module Type + 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") + + time_string = ::Time.now.utc.strftime("%T") + assert_equal time_string, type.cast(time_string).strftime("%T") + + assert_equal ::Time.utc(2000, 1, 1, 16, 45, 54), type.cast("2015-06-13T19:45:54+03:00") + assert_equal ::Time.utc(1999, 12, 31, 21, 7, 8), type.cast("06:07:08+09:00") + end + end + end +end diff --git a/activemodel/test/cases/type/unsigned_integer_test.rb b/activemodel/test/cases/type/unsigned_integer_test.rb index 16301b3ac0..026cb08a06 100644 --- a/activemodel/test/cases/type/unsigned_integer_test.rb +++ b/activemodel/test/cases/type/unsigned_integer_test.rb @@ -9,7 +9,7 @@ module ActiveModel end test "minus value is out of range" do - assert_raises(::RangeError) do + assert_raises(ActiveModel::RangeError) do UnsignedInteger.new.serialize(-1) end end diff --git a/activemodel/test/cases/type/value_test.rb b/activemodel/test/cases/type/value_test.rb new file mode 100644 index 0000000000..d8b3e7f164 --- /dev/null +++ b/activemodel/test/cases/type/value_test.rb @@ -0,0 +1,14 @@ +require "cases/helper" +require "active_model/type" + +module ActiveModel + module Type + class ValueTest < ActiveModel::TestCase + def test_type_equality + assert_equal Type::Value.new, Type::Value.new + assert_not_equal Type::Value.new, Type::Integer.new + assert_not_equal Type::Value.new(precision: 1), Type::Value.new(precision: 2) + end + end + end +end diff --git a/activemodel/test/cases/types_test.rb b/activemodel/test/cases/types_test.rb deleted file mode 100644 index 558c56f157..0000000000 --- a/activemodel/test/cases/types_test.rb +++ /dev/null @@ -1,125 +0,0 @@ -require "cases/helper" -require "active_model/type" -require "active_support/core_ext/numeric/time" - -module ActiveModel - class TypesTest < ActiveModel::TestCase - def test_type_cast_boolean - type = Type::Boolean.new - assert type.cast('').nil? - assert type.cast(nil).nil? - - assert type.cast(true) - assert type.cast(1) - assert type.cast('1') - assert type.cast('t') - assert type.cast('T') - assert type.cast('true') - assert type.cast('TRUE') - assert type.cast('on') - assert type.cast('ON') - assert type.cast(' ') - assert type.cast("\u3000\r\n") - assert type.cast("\u0000") - assert type.cast('SOMETHING RANDOM') - - # explicitly check for false vs nil - assert_equal false, type.cast(false) - assert_equal false, type.cast(0) - assert_equal false, type.cast('0') - assert_equal false, type.cast('f') - assert_equal false, type.cast('F') - assert_equal false, type.cast('false') - assert_equal false, type.cast('FALSE') - assert_equal false, type.cast('off') - assert_equal false, type.cast('OFF') - end - - def test_type_cast_float - type = Type::Float.new - assert_equal 1.0, type.cast("1") - end - - def test_changing_float - type = Type::Float.new - - assert type.changed?(5.0, 5.0, '5wibble') - assert_not type.changed?(5.0, 5.0, '5') - assert_not type.changed?(5.0, 5.0, '5.0') - assert_not type.changed?(nil, nil, nil) - end - - def test_type_cast_binary - type = Type::Binary.new - assert_equal nil, type.cast(nil) - assert_equal "1", type.cast("1") - assert_equal 1, type.cast(1) - end - - 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') - - time_string = Time.now.utc.strftime("%T") - assert_equal time_string, type.cast(time_string).strftime("%T") - - assert_equal ::Time.utc(2000, 1, 1, 16, 45, 54), type.cast('2015-06-13T19:45:54+03:00') - assert_equal ::Time.utc(1999, 12, 31, 21, 7, 8), type.cast('06:07:08+09:00') - end - - 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') - - datetime_string = Time.now.utc.strftime("%FT%T") - assert_equal datetime_string, type.cast(datetime_string).strftime("%FT%T") - end - - 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') - - date_string = Time.now.utc.strftime("%F") - assert_equal date_string, type.cast(date_string).strftime("%F") - end - - def test_type_cast_duration_to_integer - type = Type::Integer.new - assert_equal 1800, type.cast(30.minutes) - assert_equal 7200, type.cast(2.hours) - end - - def test_string_to_time_with_timezone - ["UTC", "US/Eastern"].each do |zone| - with_timezone_config default: zone do - type = Type::DateTime.new - assert_equal Time.utc(2013, 9, 4, 0, 0, 0), type.cast("Wed, 04 Sep 2013 03:00:00 EAT") - end - end - end - - def test_type_equality - assert_equal Type::Value.new, Type::Value.new - assert_not_equal Type::Value.new, Type::Integer.new - assert_not_equal Type::Value.new(precision: 1), Type::Value.new(precision: 2) - end - - private - - def with_timezone_config(default:) - old_zone_default = ::Time.zone_default - ::Time.zone_default = ::Time.find_zone(default) - yield - ensure - ::Time.zone_default = old_zone_default - end - end -end diff --git a/activemodel/test/cases/validations/absence_validation_test.rb b/activemodel/test/cases/validations/absence_validation_test.rb index 9cbc77dfb5..3e48e591e9 100644 --- a/activemodel/test/cases/validations/absence_validation_test.rb +++ b/activemodel/test/cases/validations/absence_validation_test.rb @@ -1,7 +1,7 @@ -require 'cases/helper' -require 'models/topic' -require 'models/person' -require 'models/custom_reader' +require "cases/helper" +require "models/topic" +require "models/person" +require "models/custom_reader" class AbsenceValidationTest < ActiveModel::TestCase teardown do diff --git a/activemodel/test/cases/validations/acceptance_validation_test.rb b/activemodel/test/cases/validations/acceptance_validation_test.rb index d3995ad5af..55ab213498 100644 --- a/activemodel/test/cases/validations/acceptance_validation_test.rb +++ b/activemodel/test/cases/validations/acceptance_validation_test.rb @@ -1,11 +1,10 @@ -require 'cases/helper' +require "cases/helper" -require 'models/topic' -require 'models/reply' -require 'models/person' +require "models/topic" +require "models/reply" +require "models/person" class AcceptanceValidationTest < ActiveModel::TestCase - def teardown Topic.clear_validators! end diff --git a/activemodel/test/cases/validations/callbacks_test.rb b/activemodel/test/cases/validations/callbacks_test.rb index 75eb18e795..83e8ac9522 100644 --- a/activemodel/test/cases/validations/callbacks_test.rb +++ b/activemodel/test/cases/validations/callbacks_test.rb @@ -1,4 +1,4 @@ -require 'cases/helper' +require "cases/helper" class Dog include ActiveModel::Validations @@ -15,37 +15,37 @@ class DogWithMethodCallbacks < Dog before_validation :set_before_validation_marker after_validation :set_after_validation_marker - def set_before_validation_marker; self.history << 'before_validation_marker'; end - def set_after_validation_marker; self.history << 'after_validation_marker' ; end + def set_before_validation_marker; history << "before_validation_marker"; end + def set_after_validation_marker; history << "after_validation_marker" ; end end class DogValidatorsAreProc < Dog - before_validation { self.history << 'before_validation_marker' } - after_validation { self.history << 'after_validation_marker' } + before_validation { history << "before_validation_marker" } + after_validation { history << "after_validation_marker" } end class DogWithTwoValidators < Dog - before_validation { self.history << 'before_validation_marker1' } - before_validation { self.history << 'before_validation_marker2' } + before_validation { history << "before_validation_marker1" } + before_validation { history << "before_validation_marker2" } end class DogDeprecatedBeforeValidatorReturningFalse < Dog before_validation { false } - before_validation { self.history << 'before_validation_marker2' } + before_validation { history << "before_validation_marker2" } end class DogBeforeValidatorThrowingAbort < Dog before_validation { throw :abort } - before_validation { self.history << 'before_validation_marker2' } + before_validation { history << "before_validation_marker2" } end class DogAfterValidatorReturningFalse < Dog after_validation { false } - after_validation { self.history << 'after_validation_marker' } + after_validation { history << "after_validation_marker" } end class DogWithMissingName < Dog - before_validation { self.history << 'before_validation_marker' } + before_validation { history << "before_validation_marker" } validates_presence_of :name end @@ -53,8 +53,8 @@ class DogValidatorWithOnCondition < Dog before_validation :set_before_validation_marker, on: :create after_validation :set_after_validation_marker, on: :create - def set_before_validation_marker; self.history << 'before_validation_marker'; end - def set_after_validation_marker; self.history << 'after_validation_marker' ; end + def set_before_validation_marker; history << "before_validation_marker"; end + def set_after_validation_marker; history << "after_validation_marker" ; end end class DogValidatorWithIfCondition < Dog @@ -64,16 +64,14 @@ class DogValidatorWithIfCondition < Dog after_validation :set_after_validation_marker1, if: -> { true } after_validation :set_after_validation_marker2, if: -> { false } - def set_before_validation_marker1; self.history << 'before_validation_marker1'; end - def set_before_validation_marker2; self.history << 'before_validation_marker2' ; end + def set_before_validation_marker1; history << "before_validation_marker1"; end + def set_before_validation_marker2; history << "before_validation_marker2" ; end - def set_after_validation_marker1; self.history << 'after_validation_marker1'; end - def set_after_validation_marker2; self.history << 'after_validation_marker2' ; end + def set_after_validation_marker1; history << "after_validation_marker1"; end + def set_after_validation_marker2; history << "after_validation_marker2" ; end end - class CallbacksWithMethodNamesShouldBeCalled < ActiveModel::TestCase - def test_if_condition_is_respected_for_before_validation d = DogValidatorWithIfCondition.new d.valid? @@ -101,19 +99,19 @@ class CallbacksWithMethodNamesShouldBeCalled < ActiveModel::TestCase def test_before_validation_and_after_validation_callbacks_should_be_called d = DogWithMethodCallbacks.new d.valid? - assert_equal ['before_validation_marker', 'after_validation_marker'], d.history + assert_equal ["before_validation_marker", "after_validation_marker"], d.history end def test_before_validation_and_after_validation_callbacks_should_be_called_with_proc d = DogValidatorsAreProc.new d.valid? - assert_equal ['before_validation_marker', 'after_validation_marker'], d.history + assert_equal ["before_validation_marker", "after_validation_marker"], d.history end def test_before_validation_and_after_validation_callbacks_should_be_called_in_declared_order d = DogWithTwoValidators.new d.valid? - assert_equal ['before_validation_marker1', 'before_validation_marker2'], d.history + assert_equal ["before_validation_marker1", "before_validation_marker2"], d.history end def test_further_callbacks_should_not_be_called_if_before_validation_throws_abort @@ -135,14 +133,13 @@ class CallbacksWithMethodNamesShouldBeCalled < ActiveModel::TestCase def test_further_callbacks_should_be_called_if_after_validation_returns_false d = DogAfterValidatorReturningFalse.new d.valid? - assert_equal ['after_validation_marker'], d.history + assert_equal ["after_validation_marker"], d.history end def test_validation_test_should_be_done d = DogWithMissingName.new output = d.valid? - assert_equal ['before_validation_marker'], d.history + assert_equal ["before_validation_marker"], d.history assert_equal false, output end - end diff --git a/activemodel/test/cases/validations/conditional_validation_test.rb b/activemodel/test/cases/validations/conditional_validation_test.rb index 296d3b4407..5e81083b63 100644 --- a/activemodel/test/cases/validations/conditional_validation_test.rb +++ b/activemodel/test/cases/validations/conditional_validation_test.rb @@ -1,9 +1,8 @@ -require 'cases/helper' +require "cases/helper" -require 'models/topic' +require "models/topic" class ConditionalValidationTest < ActiveModel::TestCase - def teardown Topic.clear_validators! end @@ -98,7 +97,7 @@ class ConditionalValidationTest < ActiveModel::TestCase def test_if_validation_using_block_false # When the block returns false Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", - if: Proc.new { |r| r.title != "uhohuhoh"}) + if: Proc.new { |r| r.title != "uhohuhoh" }) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? assert_empty t.errors[:title] @@ -107,7 +106,7 @@ class ConditionalValidationTest < ActiveModel::TestCase def test_unless_validation_using_block_false # When the block returns false Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", - unless: Proc.new { |r| r.title != "uhohuhoh"} ) + unless: Proc.new { |r| r.title != "uhohuhoh" } ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? diff --git a/activemodel/test/cases/validations/confirmation_validation_test.rb b/activemodel/test/cases/validations/confirmation_validation_test.rb index c56bf1c0ad..b88e1c4ca4 100644 --- a/activemodel/test/cases/validations/confirmation_validation_test.rb +++ b/activemodel/test/cases/validations/confirmation_validation_test.rb @@ -1,10 +1,9 @@ -require 'cases/helper' +require "cases/helper" -require 'models/topic' -require 'models/person' +require "models/topic" +require "models/person" class ConfirmationValidationTest < ActiveModel::TestCase - def teardown Topic.clear_validators! end @@ -56,10 +55,9 @@ class ConfirmationValidationTest < ActiveModel::TestCase @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend I18n.load_path.clear I18n.backend = I18n::Backend::Simple.new - I18n.backend.store_translations('en', { + I18n.backend.store_translations("en", errors: { messages: { confirmation: "doesn't match %{attribute}" } }, - activemodel: { attributes: { topic: { title: 'Test Title'} } } - }) + activemodel: { attributes: { topic: { title: "Test Title" } } }) Topic.validates_confirmation_of(:title) diff --git a/activemodel/test/cases/validations/exclusion_validation_test.rb b/activemodel/test/cases/validations/exclusion_validation_test.rb index 005bc15df5..06ae4fbecd 100644 --- a/activemodel/test/cases/validations/exclusion_validation_test.rb +++ b/activemodel/test/cases/validations/exclusion_validation_test.rb @@ -1,11 +1,10 @@ -require 'cases/helper' -require 'active_support/core_ext/numeric/time' +require "cases/helper" +require "active_support/core_ext/numeric/time" -require 'models/topic' -require 'models/person' +require "models/topic" +require "models/person" class ExclusionValidationTest < ActiveModel::TestCase - def teardown Topic.clear_validators! end @@ -68,8 +67,8 @@ class ExclusionValidationTest < ActiveModel::TestCase def test_validates_exclusion_of_with_range Topic.validates_exclusion_of :content, in: ("a".."g") - assert Topic.new(content: 'g').invalid? - assert Topic.new(content: 'h').valid? + assert Topic.new(content: "g").invalid? + assert Topic.new(content: "h").valid? end def test_validates_exclusion_of_with_time_range diff --git a/activemodel/test/cases/validations/format_validation_test.rb b/activemodel/test/cases/validations/format_validation_test.rb index 86bbbe6ebe..d7e6bf3707 100644 --- a/activemodel/test/cases/validations/format_validation_test.rb +++ b/activemodel/test/cases/validations/format_validation_test.rb @@ -1,10 +1,9 @@ -require 'cases/helper' +require "cases/helper" -require 'models/topic' -require 'models/person' +require "models/topic" +require "models/person" class PresenceValidationTest < ActiveModel::TestCase - def teardown Topic.clear_validators! end @@ -63,7 +62,7 @@ class PresenceValidationTest < ActiveModel::TestCase def test_validate_format_with_formatted_message Topic.validates_format_of(:title, with: /\AValid Title\z/, message: "can't be %{value}") - t = Topic.new(title: 'Invalid title') + t = Topic.new(title: "Invalid title") assert t.invalid? assert_equal ["can't be Invalid title"], t.errors[:title] end @@ -73,7 +72,7 @@ class PresenceValidationTest < ActiveModel::TestCase end def test_validate_format_of_with_multiline_regexp_and_option - assert_nothing_raised(ArgumentError) do + assert_nothing_raised do Topic.validates_format_of(:title, with: /^Valid Title$/, multiline: true) end end diff --git a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb index da63df9152..f049ee26e8 100644 --- a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb @@ -1,6 +1,6 @@ require "cases/helper" -require 'models/person' +require "models/person" class I18nGenerateMessageValidationTest < ActiveModel::TestCase def setup @@ -10,29 +10,29 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase # validates_inclusion_of: generate_message(attr_name, :inclusion, message: custom_message, value: value) def test_generate_message_inclusion_with_default_message - assert_equal 'is not included in the list', @person.errors.generate_message(:title, :inclusion, value: 'title') + assert_equal "is not included in the list", @person.errors.generate_message(:title, :inclusion, value: "title") end def test_generate_message_inclusion_with_custom_message - assert_equal 'custom message title', @person.errors.generate_message(:title, :inclusion, message: 'custom message %{value}', value: 'title') + assert_equal "custom message title", @person.errors.generate_message(:title, :inclusion, message: "custom message %{value}", value: "title") end # validates_exclusion_of: generate_message(attr_name, :exclusion, message: custom_message, value: value) def test_generate_message_exclusion_with_default_message - assert_equal 'is reserved', @person.errors.generate_message(:title, :exclusion, value: 'title') + assert_equal "is reserved", @person.errors.generate_message(:title, :exclusion, value: "title") end def test_generate_message_exclusion_with_custom_message - assert_equal 'custom message title', @person.errors.generate_message(:title, :exclusion, message: 'custom message %{value}', value: 'title') + assert_equal "custom message title", @person.errors.generate_message(:title, :exclusion, message: "custom message %{value}", value: "title") end # validates_format_of: generate_message(attr_name, :invalid, message: custom_message, value: value) def test_generate_message_invalid_with_default_message - assert_equal 'is invalid', @person.errors.generate_message(:title, :invalid, value: 'title') + assert_equal "is invalid", @person.errors.generate_message(:title, :invalid, value: "title") end def test_generate_message_invalid_with_custom_message - assert_equal 'custom message title', @person.errors.generate_message(:title, :invalid, message: 'custom message %{value}', value: 'title') + assert_equal "custom message title", @person.errors.generate_message(:title, :invalid, message: "custom message %{value}", value: "title") end # validates_confirmation_of: generate_message(attr_name, :confirmation, message: custom_message) @@ -41,7 +41,7 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase end def test_generate_message_confirmation_with_custom_message - assert_equal 'custom message', @person.errors.generate_message(:title, :confirmation, message: 'custom message') + assert_equal "custom message", @person.errors.generate_message(:title, :confirmation, message: "custom message") end # validates_acceptance_of: generate_message(attr_name, :accepted, message: custom_message) @@ -50,7 +50,7 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase end def test_generate_message_accepted_with_custom_message - assert_equal 'custom message', @person.errors.generate_message(:title, :accepted, message: 'custom message') + assert_equal "custom message", @person.errors.generate_message(:title, :accepted, message: "custom message") end # add_on_empty: generate_message(attr, :empty, message: custom_message) @@ -59,7 +59,7 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase end def test_generate_message_empty_with_custom_message - assert_equal 'custom message', @person.errors.generate_message(:title, :empty, message: 'custom message') + assert_equal "custom message", @person.errors.generate_message(:title, :empty, message: "custom message") end # validates_presence_of: generate_message(attr, :blank, message: custom_message) @@ -68,7 +68,7 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase end def test_generate_message_blank_with_custom_message - assert_equal 'custom message', @person.errors.generate_message(:title, :blank, message: 'custom message') + assert_equal "custom message", @person.errors.generate_message(:title, :blank, message: "custom message") end # validates_length_of: generate_message(attr, :too_long, message: custom_message, count: option_value.end) @@ -81,7 +81,7 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase end def test_generate_message_too_long_with_custom_message - assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_long, message: 'custom message %{count}', count: 10) + assert_equal "custom message 10", @person.errors.generate_message(:title, :too_long, message: "custom message %{count}", count: 10) end # validates_length_of: generate_message(attr, :too_short, default: custom_message, count: option_value.begin) @@ -94,7 +94,7 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase end def test_generate_message_too_short_with_custom_message - assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_short, message: 'custom message %{count}', count: 10) + assert_equal "custom message 10", @person.errors.generate_message(:title, :too_short, message: "custom message %{count}", count: 10) end # validates_length_of: generate_message(attr, :wrong_length, message: custom_message, count: option_value) @@ -107,44 +107,44 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase end def test_generate_message_wrong_length_with_custom_message - assert_equal 'custom message 10', @person.errors.generate_message(:title, :wrong_length, message: 'custom message %{count}', count: 10) + assert_equal "custom message 10", @person.errors.generate_message(:title, :wrong_length, message: "custom message %{count}", count: 10) end # validates_numericality_of: generate_message(attr_name, :not_a_number, value: raw_value, message: custom_message) def test_generate_message_not_a_number_with_default_message - assert_equal "is not a number", @person.errors.generate_message(:title, :not_a_number, value: 'title') + assert_equal "is not a number", @person.errors.generate_message(:title, :not_a_number, value: "title") end def test_generate_message_not_a_number_with_custom_message - assert_equal 'custom message title', @person.errors.generate_message(:title, :not_a_number, message: 'custom message %{value}', value: 'title') + assert_equal "custom message title", @person.errors.generate_message(:title, :not_a_number, message: "custom message %{value}", value: "title") end # validates_numericality_of: generate_message(attr_name, option, value: raw_value, default: custom_message) def test_generate_message_greater_than_with_default_message - assert_equal "must be greater than 10", @person.errors.generate_message(:title, :greater_than, value: 'title', count: 10) + assert_equal "must be greater than 10", @person.errors.generate_message(:title, :greater_than, value: "title", count: 10) end def test_generate_message_greater_than_or_equal_to_with_default_message - assert_equal "must be greater than or equal to 10", @person.errors.generate_message(:title, :greater_than_or_equal_to, value: 'title', count: 10) + assert_equal "must be greater than or equal to 10", @person.errors.generate_message(:title, :greater_than_or_equal_to, value: "title", count: 10) end def test_generate_message_equal_to_with_default_message - assert_equal "must be equal to 10", @person.errors.generate_message(:title, :equal_to, value: 'title', count: 10) + assert_equal "must be equal to 10", @person.errors.generate_message(:title, :equal_to, value: "title", count: 10) end def test_generate_message_less_than_with_default_message - assert_equal "must be less than 10", @person.errors.generate_message(:title, :less_than, value: 'title', count: 10) + assert_equal "must be less than 10", @person.errors.generate_message(:title, :less_than, value: "title", count: 10) end def test_generate_message_less_than_or_equal_to_with_default_message - assert_equal "must be less than or equal to 10", @person.errors.generate_message(:title, :less_than_or_equal_to, value: 'title', count: 10) + assert_equal "must be less than or equal to 10", @person.errors.generate_message(:title, :less_than_or_equal_to, value: "title", count: 10) end def test_generate_message_odd_with_default_message - assert_equal "must be odd", @person.errors.generate_message(:title, :odd, value: 'title', count: 10) + assert_equal "must be odd", @person.errors.generate_message(:title, :odd, value: "title", count: 10) end def test_generate_message_even_with_default_message - assert_equal "must be even", @person.errors.generate_message(:title, :even, value: 'title', count: 10) + assert_equal "must be even", @person.errors.generate_message(:title, :even, value: "title", count: 10) end end diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb index 09d7226b5a..f28cfc0ef5 100644 --- a/activemodel/test/cases/validations/i18n_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_validation_test.rb @@ -1,8 +1,7 @@ require "cases/helper" -require 'models/person' +require "models/person" class I18nValidationTest < ActiveModel::TestCase - def setup Person.clear_validators! @person = Person.new @@ -10,7 +9,7 @@ class I18nValidationTest < ActiveModel::TestCase @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend I18n.load_path.clear I18n.backend = I18n::Backend::Simple.new - I18n.backend.store_translations('en', errors: { messages: { custom: nil } }) + I18n.backend.store_translations("en", errors: { messages: { custom: nil } }) end def teardown @@ -21,23 +20,23 @@ class I18nValidationTest < ActiveModel::TestCase end def test_full_message_encoding - I18n.backend.store_translations('en', errors: { - messages: { too_short: '猫舌' } }) + I18n.backend.store_translations("en", errors: { + messages: { too_short: "猫舌" } }) Person.validates_length_of :title, within: 3..5 @person.valid? - assert_equal ['Title 猫舌'], @person.errors.full_messages + assert_equal ["Title 猫舌"], @person.errors.full_messages end def test_errors_full_messages_translates_human_attribute_name_for_model_attributes - @person.errors.add(:name, 'not found') - assert_called_with(Person, :human_attribute_name, [:name, default: 'Name'], returns: "Person's name") do + @person.errors.add(:name, "not found") + assert_called_with(Person, :human_attribute_name, [:name, default: "Name"], returns: "Person's name") do assert_equal ["Person's name not found"], @person.errors.full_messages end end def test_errors_full_messages_uses_format - I18n.backend.store_translations('en', errors: { format: "Field %{attribute} %{message}" }) - @person.errors.add('name', 'empty') + I18n.backend.store_translations("en", errors: { format: "Field %{attribute} %{message}" }) + @person.errors.add("name", "empty") assert_equal ["Field Name empty"], @person.errors.full_messages end @@ -47,19 +46,19 @@ class I18nValidationTest < ActiveModel::TestCase # are used to generate tests to keep things DRY # COMMON_CASES = [ - # [ case, validation_options, generate_message_options] + # [ case, validation_options, generate_message_options] [ "given no options", {}, {}], [ "given custom message", { message: "custom" }, { message: "custom" }], - [ "given if condition", { if: lambda { true }}, {}], - [ "given unless condition", { unless: lambda { false }}, {}], + [ "given if condition", { if: lambda { true } }, {}], + [ "given unless condition", { unless: lambda { false } }, {}], [ "given option that is not reserved", { format: "jpg" }, { format: "jpg" }] ] COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_confirmation_of on generated message #{name}" do Person.validates_confirmation_of :title, validation_options - @person.title_confirmation = 'foo' - call = [:title_confirmation, :confirmation, generate_message_options.merge(attribute: 'Title')] + @person.title_confirmation = "foo" + call = [:title_confirmation, :confirmation, generate_message_options.merge(attribute: "Title")] assert_called_with(@person.errors, :generate_message, call) do @person.valid? end @@ -99,7 +98,7 @@ class I18nValidationTest < ActiveModel::TestCase COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_length_of for :too_long generated message #{name}" do Person.validates_length_of :title, validation_options.merge(within: 3..5) - @person.title = 'this title is too long' + @person.title = "this title is too long" call = [:title, :too_long, generate_message_options.merge(count: 5)] assert_called_with(@person.errors, :generate_message, call) do @person.valid? @@ -120,8 +119,8 @@ class I18nValidationTest < ActiveModel::TestCase COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_format_of on generated message #{name}" do Person.validates_format_of :title, validation_options.merge(with: /\A[1-9][0-9]*\z/) - @person.title = '72x' - call = [:title, :invalid, generate_message_options.merge(value: '72x')] + @person.title = "72x" + call = [:title, :invalid, generate_message_options.merge(value: "72x")] assert_called_with(@person.errors, :generate_message, call) do @person.valid? end @@ -131,8 +130,8 @@ class I18nValidationTest < ActiveModel::TestCase COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_inclusion_of on generated message #{name}" do Person.validates_inclusion_of :title, validation_options.merge(in: %w(a b c)) - @person.title = 'z' - call = [:title, :inclusion, generate_message_options.merge(value: 'z')] + @person.title = "z" + call = [:title, :inclusion, generate_message_options.merge(value: "z")] assert_called_with(@person.errors, :generate_message, call) do @person.valid? end @@ -142,8 +141,8 @@ class I18nValidationTest < ActiveModel::TestCase COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_inclusion_of using :within on generated message #{name}" do Person.validates_inclusion_of :title, validation_options.merge(within: %w(a b c)) - @person.title = 'z' - call = [:title, :inclusion, generate_message_options.merge(value: 'z')] + @person.title = "z" + call = [:title, :inclusion, generate_message_options.merge(value: "z")] assert_called_with(@person.errors, :generate_message, call) do @person.valid? end @@ -153,8 +152,8 @@ class I18nValidationTest < ActiveModel::TestCase COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_exclusion_of generated message #{name}" do Person.validates_exclusion_of :title, validation_options.merge(in: %w(a b c)) - @person.title = 'a' - call = [:title, :exclusion, generate_message_options.merge(value: 'a')] + @person.title = "a" + call = [:title, :exclusion, generate_message_options.merge(value: "a")] assert_called_with(@person.errors, :generate_message, call) do @person.valid? end @@ -164,8 +163,8 @@ class I18nValidationTest < ActiveModel::TestCase COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_exclusion_of using :within generated message #{name}" do Person.validates_exclusion_of :title, validation_options.merge(within: %w(a b c)) - @person.title = 'a' - call = [:title, :exclusion, generate_message_options.merge(value: 'a')] + @person.title = "a" + call = [:title, :exclusion, generate_message_options.merge(value: "a")] assert_called_with(@person.errors, :generate_message, call) do @person.valid? end @@ -175,8 +174,8 @@ class I18nValidationTest < ActiveModel::TestCase COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_numericality_of generated message #{name}" do Person.validates_numericality_of :title, validation_options - @person.title = 'a' - call = [:title, :not_a_number, generate_message_options.merge(value: 'a')] + @person.title = "a" + call = [:title, :not_a_number, generate_message_options.merge(value: "a")] assert_called_with(@person.errors, :generate_message, call) do @person.valid? end @@ -186,8 +185,8 @@ class I18nValidationTest < ActiveModel::TestCase COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_numericality_of for :only_integer on generated message #{name}" do Person.validates_numericality_of :title, validation_options.merge(only_integer: true) - @person.title = '0.0' - call = [:title, :not_an_integer, generate_message_options.merge(value: '0.0')] + @person.title = "0.0" + call = [:title, :not_an_integer, generate_message_options.merge(value: "0.0")] assert_called_with(@person.errors, :generate_message, call) do @person.valid? end @@ -225,35 +224,35 @@ class I18nValidationTest < ActiveModel::TestCase end test "#{validation} finds custom model key translation when #{error_type}" do - I18n.backend.store_translations 'en', activemodel: { errors: { models: { person: { attributes: { attribute => { error_type => 'custom message' } } } } } } - I18n.backend.store_translations 'en', errors: { messages: { error_type => 'global message'}} + I18n.backend.store_translations "en", activemodel: { errors: { models: { person: { attributes: { attribute => { error_type => "custom message" } } } } } } + I18n.backend.store_translations "en", errors: { messages: { error_type => "global message" } } yield(@person, {}) @person.valid? - assert_equal ['custom message'], @person.errors[attribute] + assert_equal ["custom message"], @person.errors[attribute] end test "#{validation} finds custom model key translation with interpolation when #{error_type}" do - I18n.backend.store_translations 'en', activemodel: { errors: { models: { person: { attributes: { attribute => { error_type => 'custom message with %{extra}' } } } } } } - I18n.backend.store_translations 'en', errors: { messages: {error_type => 'global message'} } + I18n.backend.store_translations "en", activemodel: { errors: { models: { person: { attributes: { attribute => { error_type => "custom message with %{extra}" } } } } } } + I18n.backend.store_translations "en", errors: { messages: { error_type => "global message" } } yield(@person, { extra: "extra information" }) @person.valid? - assert_equal ['custom message with extra information'], @person.errors[attribute] + assert_equal ["custom message with extra information"], @person.errors[attribute] end test "#{validation} finds global default key translation when #{error_type}" do - I18n.backend.store_translations 'en', errors: { messages: {error_type => 'global message'} } + I18n.backend.store_translations "en", errors: { messages: { error_type => "global message" } } yield(@person, {}) @person.valid? - assert_equal ['global message'], @person.errors[attribute] + assert_equal ["global message"], @person.errors[attribute] end end set_expectations_for_validation "validates_confirmation_of", :confirmation do |person, options_to_merge| Person.validates_confirmation_of :title, options_to_merge - person.title_confirmation = 'foo' + person.title_confirmation = "foo" end set_expectations_for_validation "validates_acceptance_of", :accepted do |person, options_to_merge| @@ -287,17 +286,17 @@ class I18nValidationTest < ActiveModel::TestCase set_expectations_for_validation "validates_exclusion_of", :exclusion do |person, options_to_merge| Person.validates_exclusion_of :title, options_to_merge.merge(in: %w(a b c)) - person.title = 'a' + person.title = "a" end set_expectations_for_validation "validates_numericality_of", :not_a_number do |person, options_to_merge| Person.validates_numericality_of :title, options_to_merge - person.title = 'a' + person.title = "a" end set_expectations_for_validation "validates_numericality_of", :not_an_integer do |person, options_to_merge| Person.validates_numericality_of :title, options_to_merge.merge(only_integer: true) - person.title = '1.0' + person.title = "1.0" end set_expectations_for_validation "validates_numericality_of", :odd do |person, options_to_merge| @@ -311,7 +310,7 @@ class I18nValidationTest < ActiveModel::TestCase end def test_validations_with_message_symbol_must_translate - I18n.backend.store_translations 'en', errors: { messages: { custom_error: "I am a custom error" } } + I18n.backend.store_translations "en", errors: { messages: { custom_error: "I am a custom error" } } Person.validates_presence_of :title, message: :custom_error @person.title = nil @person.valid? @@ -319,7 +318,7 @@ class I18nValidationTest < ActiveModel::TestCase end def test_validates_with_message_symbol_must_translate_per_attribute - I18n.backend.store_translations 'en', activemodel: { errors: { models: { person: { attributes: { title: { custom_error: "I am a custom error" } } } } } } + I18n.backend.store_translations "en", activemodel: { errors: { models: { person: { attributes: { title: { custom_error: "I am a custom error" } } } } } } Person.validates_presence_of :title, message: :custom_error @person.title = nil @person.valid? @@ -327,7 +326,7 @@ class I18nValidationTest < ActiveModel::TestCase end def test_validates_with_message_symbol_must_translate_per_model - I18n.backend.store_translations 'en', activemodel: { errors: { models: { person: { custom_error: "I am a custom error" } } } } + I18n.backend.store_translations "en", activemodel: { errors: { models: { person: { custom_error: "I am a custom error" } } } } Person.validates_presence_of :title, message: :custom_error @person.title = nil @person.valid? diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb index 55d1fb4dcb..5aa43ea4a9 100644 --- a/activemodel/test/cases/validations/inclusion_validation_test.rb +++ b/activemodel/test/cases/validations/inclusion_validation_test.rb @@ -1,17 +1,16 @@ -require 'cases/helper' -require 'active_support/all' +require "cases/helper" +require "active_support/all" -require 'models/topic' -require 'models/person' +require "models/topic" +require "models/person" class InclusionValidationTest < ActiveModel::TestCase - def teardown Topic.clear_validators! end def test_validates_inclusion_of_range - Topic.validates_inclusion_of(:title, in: 'aaa'..'bbb') + Topic.validates_inclusion_of(:title, in: "aaa".."bbb") assert Topic.new("title" => "bbc", "content" => "abc").invalid? assert Topic.new("title" => "aa", "content" => "abc").invalid? assert Topic.new("title" => "aaab", "content" => "abc").invalid? @@ -21,24 +20,38 @@ class InclusionValidationTest < ActiveModel::TestCase end def test_validates_inclusion_of_time_range - Topic.validates_inclusion_of(:created_at, in: 1.year.ago..Time.now) - assert Topic.new(title: 'aaa', created_at: 2.years.ago).invalid? - assert Topic.new(title: 'aaa', created_at: 3.months.ago).valid? - assert Topic.new(title: 'aaa', created_at: 37.weeks.from_now).invalid? + range_begin = 1.year.ago + range_end = Time.now + Topic.validates_inclusion_of(:created_at, in: range_begin..range_end) + assert Topic.new(title: "aaa", created_at: 2.years.ago).invalid? + assert Topic.new(title: "aaa", created_at: 3.months.ago).valid? + assert Topic.new(title: "aaa", created_at: 37.weeks.from_now).invalid? + assert Topic.new(title: "aaa", created_at: range_begin).valid? + assert Topic.new(title: "aaa", created_at: range_end).valid? end def test_validates_inclusion_of_date_range - Topic.validates_inclusion_of(:created_at, in: 1.year.until(Date.today)..Date.today) - assert Topic.new(title: 'aaa', created_at: 2.years.until(Date.today)).invalid? - assert Topic.new(title: 'aaa', created_at: 3.months.until(Date.today)).valid? - assert Topic.new(title: 'aaa', created_at: 37.weeks.since(Date.today)).invalid? + range_begin = 1.year.until(Date.today) + range_end = Date.today + Topic.validates_inclusion_of(:created_at, in: range_begin..range_end) + assert Topic.new(title: "aaa", created_at: 2.years.until(Date.today)).invalid? + assert Topic.new(title: "aaa", created_at: 3.months.until(Date.today)).valid? + assert Topic.new(title: "aaa", created_at: 37.weeks.since(Date.today)).invalid? + assert Topic.new(title: "aaa", created_at: 1.year.until(Date.today)).valid? + assert Topic.new(title: "aaa", created_at: Date.today).valid? + assert Topic.new(title: "aaa", created_at: range_begin).valid? + assert Topic.new(title: "aaa", created_at: range_end).valid? end def test_validates_inclusion_of_date_time_range - Topic.validates_inclusion_of(:created_at, in: 1.year.until(DateTime.current)..DateTime.current) - assert Topic.new(title: 'aaa', created_at: 2.years.until(DateTime.current)).invalid? - assert Topic.new(title: 'aaa', created_at: 3.months.until(DateTime.current)).valid? - assert Topic.new(title: 'aaa', created_at: 37.weeks.since(DateTime.current)).invalid? + range_begin = 1.year.until(DateTime.current) + range_end = DateTime.current + Topic.validates_inclusion_of(:created_at, in: range_begin..range_end) + assert Topic.new(title: "aaa", created_at: 2.years.until(DateTime.current)).invalid? + assert Topic.new(title: "aaa", created_at: 3.months.until(DateTime.current)).valid? + assert Topic.new(title: "aaa", created_at: 37.weeks.since(DateTime.current)).invalid? + assert Topic.new(title: "aaa", created_at: range_begin).valid? + assert Topic.new(title: "aaa", created_at: range_end).valid? end def test_validates_inclusion_of @@ -58,9 +71,9 @@ class InclusionValidationTest < ActiveModel::TestCase assert_raise(ArgumentError) { Topic.validates_inclusion_of(:title, in: nil) } assert_raise(ArgumentError) { Topic.validates_inclusion_of(:title, in: 0) } - assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of(:title, in: "hi!") } - assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of(:title, in: {}) } - assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of(:title, in: []) } + assert_nothing_raised { Topic.validates_inclusion_of(:title, in: "hi!") } + assert_nothing_raised { Topic.validates_inclusion_of(:title, in: {}) } + assert_nothing_raised { Topic.validates_inclusion_of(:title, in: []) } end def test_validates_inclusion_of_with_allow_nil @@ -108,7 +121,7 @@ class InclusionValidationTest < ActiveModel::TestCase end def test_validates_inclusion_of_with_lambda - Topic.validates_inclusion_of :title, in: lambda{ |topic| topic.author_name == "sikachu" ? %w( monkey elephant ) : %w( abe wasabi ) } + Topic.validates_inclusion_of :title, in: lambda { |topic| topic.author_name == "sikachu" ? %w( monkey elephant ) : %w( abe wasabi ) } t = Topic.new t.title = "wasabi" diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb index ee901b75fb..62d9eaa346 100644 --- a/activemodel/test/cases/validations/length_validation_test.rb +++ b/activemodel/test/cases/validations/length_validation_test.rb @@ -1,7 +1,7 @@ -require 'cases/helper' +require "cases/helper" -require 'models/topic' -require 'models/person' +require "models/topic" +require "models/person" class LengthValidationTest < ActiveModel::TestCase def teardown @@ -125,7 +125,7 @@ class LengthValidationTest < ActiveModel::TestCase def test_optionally_validates_length_of_using_within Topic.validates_length_of :title, :content, within: 3..5, allow_nil: true - t = Topic.new('title' => 'abc', 'content' => 'abcd') + t = Topic.new("title" => "abc", "content" => "abcd") assert t.valid? t.title = nil @@ -228,17 +228,17 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_custom_errors_for_both_too_short_and_too_long - Topic.validates_length_of :title, minimum: 3, maximum: 5, too_short: 'too short', too_long: 'too long' + Topic.validates_length_of :title, minimum: 3, maximum: 5, too_short: "too short", too_long: "too long" - t = Topic.new(title: 'a') + t = Topic.new(title: "a") assert t.invalid? assert t.errors[:title].any? - assert_equal ['too short'], t.errors['title'] + assert_equal ["too short"], t.errors["title"] - t = Topic.new(title: 'aaaaaa') + t = Topic.new(title: "aaaaaa") assert t.invalid? assert t.errors[:title].any? - assert_equal ['too long'], t.errors['title'] + assert_equal ["too long"], t.errors["title"] end def test_validates_length_of_custom_errors_for_is_with_message @@ -318,44 +318,7 @@ class LengthValidationTest < ActiveModel::TestCase assert_equal ["is the wrong length (should be 5 characters)"], t.errors["title"] end - def test_validates_length_of_with_block - assert_deprecated do - Topic.validates_length_of( - :content, - minimum: 5, - too_short: "Your essay must be at least %{count} words.", - tokenizer: lambda {|str| str.scan(/\w+/) }, - ) - end - t = Topic.new(content: "this content should be long enough") - assert t.valid? - - t.content = "not long enough" - assert t.invalid? - assert t.errors[:content].any? - assert_equal ["Your essay must be at least 5 words."], t.errors[:content] - end - - - def test_validates_length_of_with_symbol - assert_deprecated do - Topic.validates_length_of( - :content, - minimum: 5, - too_short: "Your essay must be at least %{count} words.", - tokenizer: :my_word_tokenizer, - ) - end - t = Topic.new(content: "this content should be long enough") - assert t.valid? - - t.content = "not long enough" - assert t.invalid? - assert t.errors[:content].any? - assert_equal ["Your essay must be at least 5 words."], t.errors[:content] - end - - def test_validates_length_of_for_fixnum + def test_validates_length_of_for_integer Topic.validates_length_of(:approved, is: 4) t = Topic.new("title" => "uhohuhoh", "content" => "whatever", approved: 1) diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb index 74a048537d..fefab29f15 100644 --- a/activemodel/test/cases/validations/numericality_validation_test.rb +++ b/activemodel/test/cases/validations/numericality_validation_test.rb @@ -1,13 +1,12 @@ -require 'cases/helper' +require "cases/helper" -require 'models/topic' -require 'models/person' +require "models/topic" +require "models/person" -require 'bigdecimal' -require 'active_support/core_ext/big_decimal' +require "bigdecimal" +require "active_support/core_ext/big_decimal" class NumericalityValidationTest < ActiveModel::TestCase - def teardown Topic.clear_validators! end @@ -36,6 +35,13 @@ class NumericalityValidationTest < ActiveModel::TestCase valid!(NIL + FLOATS + INTEGERS + BIGDECIMAL + INFINITY) end + def test_validates_numericality_of_with_blank_allowed + Topic.validates_numericality_of :approved, allow_blank: true + + invalid!(JUNK) + valid!(NIL + BLANK + FLOATS + INTEGERS + BIGDECIMAL + INFINITY) + end + def test_validates_numericality_of_with_integer_only Topic.validates_numericality_of :approved, only_integer: true @@ -68,119 +74,119 @@ class NumericalityValidationTest < ActiveModel::TestCase def test_validates_numericality_with_greater_than Topic.validates_numericality_of :approved, greater_than: 10 - invalid!([-10, 10], 'must be greater than 10') + invalid!([-10, 10], "must be greater than 10") valid!([11]) end def test_validates_numericality_with_greater_than_using_differing_numeric_types - Topic.validates_numericality_of :approved, greater_than: BigDecimal.new('97.18') + Topic.validates_numericality_of :approved, greater_than: BigDecimal.new("97.18") - invalid!([-97.18, BigDecimal.new('97.18'), BigDecimal('-97.18')], 'must be greater than 97.18') - valid!([97.18, 98, BigDecimal.new('98')]) # Notice the 97.18 as a float is greater than 97.18 as a BigDecimal due to floating point precision + invalid!([-97.18, BigDecimal.new("97.18"), BigDecimal("-97.18")], "must be greater than 97.18") + valid!([97.18, 98, BigDecimal.new("98")]) # Notice the 97.18 as a float is greater than 97.18 as a BigDecimal due to floating point precision end def test_validates_numericality_with_greater_than_using_string_value Topic.validates_numericality_of :approved, greater_than: 10 - invalid!(['-10', '9', '9.9', '10'], 'must be greater than 10') - valid!(['10.1', '11']) + invalid!(["-10", "9", "9.9", "10"], "must be greater than 10") + valid!(["10.1", "11"]) end def test_validates_numericality_with_greater_than_or_equal Topic.validates_numericality_of :approved, greater_than_or_equal_to: 10 - invalid!([-9, 9], 'must be greater than or equal to 10') + invalid!([-9, 9], "must be greater than or equal to 10") valid!([10]) end def test_validates_numericality_with_greater_than_or_equal_using_differing_numeric_types - Topic.validates_numericality_of :approved, greater_than_or_equal_to: BigDecimal.new('97.18') + Topic.validates_numericality_of :approved, greater_than_or_equal_to: BigDecimal.new("97.18") - invalid!([-97.18, 97.17, 97, BigDecimal.new('97.17'), BigDecimal.new('-97.18')], 'must be greater than or equal to 97.18') - valid!([97.18, 98, BigDecimal.new('97.19')]) + invalid!([-97.18, 97.17, 97, BigDecimal.new("97.17"), BigDecimal.new("-97.18")], "must be greater than or equal to 97.18") + valid!([97.18, 98, BigDecimal.new("97.19")]) end def test_validates_numericality_with_greater_than_or_equal_using_string_value Topic.validates_numericality_of :approved, greater_than_or_equal_to: 10 - invalid!(['-10', '9', '9.9'], 'must be greater than or equal to 10') - valid!(['10', '10.1', '11']) + invalid!(["-10", "9", "9.9"], "must be greater than or equal to 10") + valid!(["10", "10.1", "11"]) end def test_validates_numericality_with_equal_to Topic.validates_numericality_of :approved, equal_to: 10 - invalid!([-10, 11] + INFINITY, 'must be equal to 10') + invalid!([-10, 11] + INFINITY, "must be equal to 10") valid!([10]) end def test_validates_numericality_with_equal_to_using_differing_numeric_types - Topic.validates_numericality_of :approved, equal_to: BigDecimal.new('97.18') + Topic.validates_numericality_of :approved, equal_to: BigDecimal.new("97.18") - invalid!([-97.18, 97.18], 'must be equal to 97.18') - valid!([BigDecimal.new('97.18')]) + invalid!([-97.18, 97.18], "must be equal to 97.18") + valid!([BigDecimal.new("97.18")]) end def test_validates_numericality_with_equal_to_using_string_value Topic.validates_numericality_of :approved, equal_to: 10 - invalid!(['-10', '9', '9.9', '10.1', '11'], 'must be equal to 10') - valid!(['10']) + invalid!(["-10", "9", "9.9", "10.1", "11"], "must be equal to 10") + valid!(["10"]) end def test_validates_numericality_with_less_than Topic.validates_numericality_of :approved, less_than: 10 - invalid!([10], 'must be less than 10') + invalid!([10], "must be less than 10") valid!([-9, 9]) end def test_validates_numericality_with_less_than_using_differing_numeric_types - Topic.validates_numericality_of :approved, less_than: BigDecimal.new('97.18') + Topic.validates_numericality_of :approved, less_than: BigDecimal.new("97.18") - invalid!([97.18, BigDecimal.new('97.18')], 'must be less than 97.18') - valid!([-97.0, 97.0, -97, 97, BigDecimal.new('-97'), BigDecimal.new('97')]) + invalid!([97.18, BigDecimal.new("97.18")], "must be less than 97.18") + valid!([-97.0, 97.0, -97, 97, BigDecimal.new("-97"), BigDecimal.new("97")]) end def test_validates_numericality_with_less_than_using_string_value Topic.validates_numericality_of :approved, less_than: 10 - invalid!(['10', '10.1', '11'], 'must be less than 10') - valid!(['-10', '9', '9.9']) + invalid!(["10", "10.1", "11"], "must be less than 10") + valid!(["-10", "9", "9.9"]) end def test_validates_numericality_with_less_than_or_equal_to Topic.validates_numericality_of :approved, less_than_or_equal_to: 10 - invalid!([11], 'must be less than or equal to 10') + invalid!([11], "must be less than or equal to 10") valid!([-10, 10]) end def test_validates_numericality_with_less_than_or_equal_to_using_differing_numeric_types - Topic.validates_numericality_of :approved, less_than_or_equal_to: BigDecimal.new('97.18') + Topic.validates_numericality_of :approved, less_than_or_equal_to: BigDecimal.new("97.18") - invalid!([97.18, 98], 'must be less than or equal to 97.18') - valid!([-97.18, BigDecimal.new('-97.18'), BigDecimal.new('97.18')]) + invalid!([97.18, 98], "must be less than or equal to 97.18") + valid!([-97.18, BigDecimal.new("-97.18"), BigDecimal.new("97.18")]) end def test_validates_numericality_with_less_than_or_equal_using_string_value Topic.validates_numericality_of :approved, less_than_or_equal_to: 10 - invalid!(['10.1', '11'], 'must be less than or equal to 10') - valid!(['-10', '9', '9.9', '10']) + invalid!(["10.1", "11"], "must be less than or equal to 10") + valid!(["-10", "9", "9.9", "10"]) end def test_validates_numericality_with_odd Topic.validates_numericality_of :approved, odd: true - invalid!([-2, 2], 'must be odd') + invalid!([-2, 2], "must be odd") valid!([-1, 1]) end def test_validates_numericality_with_even Topic.validates_numericality_of :approved, even: true - invalid!([-1, 1], 'must be even') + invalid!([-1, 1], "must be even") valid!([-2, 2]) end @@ -201,8 +207,8 @@ class NumericalityValidationTest < ActiveModel::TestCase def test_validates_numericality_with_other_than_using_string_value Topic.validates_numericality_of :approved, other_than: 0 - invalid!(['0', '0.0']) - valid!(['-1', '1.1', '42']) + invalid!(["0", "0.0"]) + valid!(["-1", "1.1", "42"]) end def test_validates_numericality_with_proc @@ -255,34 +261,34 @@ class NumericalityValidationTest < ActiveModel::TestCase end def test_validates_numericality_with_invalid_args - assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, greater_than_or_equal_to: "foo" } - assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, less_than_or_equal_to: "foo" } - assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, greater_than: "foo" } - assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, less_than: "foo" } - assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, equal_to: "foo" } + assert_raise(ArgumentError) { Topic.validates_numericality_of :approved, greater_than_or_equal_to: "foo" } + assert_raise(ArgumentError) { Topic.validates_numericality_of :approved, less_than_or_equal_to: "foo" } + assert_raise(ArgumentError) { Topic.validates_numericality_of :approved, greater_than: "foo" } + assert_raise(ArgumentError) { Topic.validates_numericality_of :approved, less_than: "foo" } + assert_raise(ArgumentError) { Topic.validates_numericality_of :approved, equal_to: "foo" } end private - def invalid!(values, error = nil) - with_each_topic_approved_value(values) do |topic, value| - assert topic.invalid?, "#{value.inspect} not rejected as a number" - assert topic.errors[:approved].any?, "FAILED for #{value.inspect}" - assert_equal error, topic.errors[:approved].first if error + def invalid!(values, error = nil) + with_each_topic_approved_value(values) do |topic, value| + assert topic.invalid?, "#{value.inspect} not rejected as a number" + assert topic.errors[:approved].any?, "FAILED for #{value.inspect}" + assert_equal error, topic.errors[:approved].first if error + end end - end - def valid!(values) - with_each_topic_approved_value(values) do |topic, value| - assert topic.valid?, "#{value.inspect} not accepted as a number with validation error: #{topic.errors[:approved].first}" + def valid!(values) + with_each_topic_approved_value(values) do |topic, value| + assert topic.valid?, "#{value.inspect} not accepted as a number with validation error: #{topic.errors[:approved].first}" + end end - end - def with_each_topic_approved_value(values) - topic = Topic.new(title: "numeric test", content: "whatever") - values.each do |value| - topic.approved = value - yield topic, value + def with_each_topic_approved_value(values) + topic = Topic.new(title: "numeric test", content: "whatever") + values.each do |value| + topic.approved = value + yield topic, value + end end - end end diff --git a/activemodel/test/cases/validations/presence_validation_test.rb b/activemodel/test/cases/validations/presence_validation_test.rb index 59b9db0795..feda817698 100644 --- a/activemodel/test/cases/validations/presence_validation_test.rb +++ b/activemodel/test/cases/validations/presence_validation_test.rb @@ -1,11 +1,10 @@ -require 'cases/helper' +require "cases/helper" -require 'models/topic' -require 'models/person' -require 'models/custom_reader' +require "models/topic" +require "models/person" +require "models/custom_reader" class PresenceValidationTest < ActiveModel::TestCase - teardown do Topic.clear_validators! Person.clear_validators! diff --git a/activemodel/test/cases/validations/validates_test.rb b/activemodel/test/cases/validations/validates_test.rb index 04101f3545..011033606e 100644 --- a/activemodel/test/cases/validations/validates_test.rb +++ b/activemodel/test/cases/validations/validates_test.rb @@ -1,8 +1,8 @@ -require 'cases/helper' -require 'models/person' -require 'models/topic' -require 'models/person_with_validator' -require 'validators/namespace/email_validator' +require "cases/helper" +require "models/person" +require "models/topic" +require "models/person_with_validator" +require "validators/namespace/email_validator" class ValidatesTest < ActiveModel::TestCase setup :reset_callbacks @@ -17,21 +17,21 @@ class ValidatesTest < ActiveModel::TestCase def test_validates_with_messages_empty Person.validates :title, presence: { message: "" } person = Person.new - assert !person.valid?, 'person should not be valid.' + assert !person.valid?, "person should not be valid." end def test_validates_with_built_in_validation Person.validates :title, numericality: true person = Person.new person.valid? - assert_equal ['is not a number'], person.errors[:title] + assert_equal ["is not a number"], person.errors[:title] end def test_validates_with_attribute_specified_as_string Person.validates "title", numericality: true person = Person.new person.valid? - assert_equal ['is not a number'], person.errors[:title] + assert_equal ["is not a number"], person.errors[:title] person = Person.new person.title = 123 @@ -39,24 +39,24 @@ class ValidatesTest < ActiveModel::TestCase end def test_validates_with_built_in_validation_and_options - Person.validates :salary, numericality: { message: 'my custom message' } + Person.validates :salary, numericality: { message: "my custom message" } person = Person.new person.valid? - assert_equal ['my custom message'], person.errors[:salary] + assert_equal ["my custom message"], person.errors[:salary] end def test_validates_with_validator_class Person.validates :karma, email: true person = Person.new person.valid? - assert_equal ['is not an email'], person.errors[:karma] + assert_equal ["is not an email"], person.errors[:karma] end def test_validates_with_namespaced_validator_class - Person.validates :karma, :'namespace/email' => true + Person.validates :karma, 'namespace/email': true person = Person.new person.valid? - assert_equal ['is not an email'], person.errors[:karma] + assert_equal ["is not an email"], person.errors[:karma] end def test_validates_with_if_as_local_conditions @@ -89,7 +89,7 @@ class ValidatesTest < ActiveModel::TestCase Person.validates :karma, format: /positive|negative/ person = Person.new assert person.invalid? - assert_equal ['is invalid'], person.errors[:karma] + assert_equal ["is invalid"], person.errors[:karma] person.karma = "positive" assert person.valid? end @@ -98,7 +98,7 @@ class ValidatesTest < ActiveModel::TestCase Person.validates :gender, inclusion: %w(m f) person = Person.new assert person.invalid? - assert_equal ['is not included in the list'], person.errors[:gender] + assert_equal ["is not included in the list"], person.errors[:gender] person.gender = "m" assert person.valid? end @@ -107,16 +107,16 @@ class ValidatesTest < ActiveModel::TestCase Person.validates :karma, length: 6..20 person = Person.new assert person.invalid? - assert_equal ['is too short (minimum is 6 characters)'], person.errors[:karma] - person.karma = 'something' + assert_equal ["is too short (minimum is 6 characters)"], person.errors[:karma] + person.karma = "something" assert person.valid? end def test_validates_with_validator_class_and_options - Person.validates :karma, email: { message: 'my custom message' } + Person.validates :karma, email: { message: "my custom message" } person = Person.new person.valid? - assert_equal ['my custom message'], person.errors[:karma] + assert_equal ["my custom message"], person.errors[:karma] end def test_validates_with_unknown_validator @@ -127,14 +127,14 @@ class ValidatesTest < ActiveModel::TestCase PersonWithValidator.validates :title, presence: true person = PersonWithValidator.new person.valid? - assert_equal ['Local validator'], person.errors[:title] + assert_equal ["Local validator"], person.errors[:title] end def test_validates_with_included_validator_and_options - PersonWithValidator.validates :title, presence: { custom: ' please' } + PersonWithValidator.validates :title, presence: { custom: " please" } person = PersonWithValidator.new person.valid? - assert_equal ['Local validator please'], person.errors[:title] + assert_equal ["Local validator please"], person.errors[:title] end def test_validates_with_included_validator_and_wildcard_shortcut @@ -143,15 +143,15 @@ class ValidatesTest < ActiveModel::TestCase person = PersonWithValidator.new person.title = "Ms. Pacman" person.valid? - assert_equal ['does not appear to be like Mr.'], person.errors[:title] + assert_equal ["does not appear to be like Mr."], person.errors[:title] end def test_defining_extra_default_keys_for_validates - Topic.validates :title, confirmation: true, message: 'Y U NO CONFIRM' + Topic.validates :title, confirmation: true, message: "Y U NO CONFIRM" topic = Topic.new topic.title = "What's happening" topic.title_confirmation = "Not this" assert !topic.valid? - assert_equal ['Y U NO CONFIRM'], topic.errors[:title_confirmation] + assert_equal ["Y U NO CONFIRM"], topic.errors[:title_confirmation] end end diff --git a/activemodel/test/cases/validations/validations_context_test.rb b/activemodel/test/cases/validations/validations_context_test.rb index b901a1523e..25c37a572f 100644 --- a/activemodel/test/cases/validations/validations_context_test.rb +++ b/activemodel/test/cases/validations/validations_context_test.rb @@ -1,6 +1,6 @@ -require 'cases/helper' +require "cases/helper" -require 'models/topic' +require "models/topic" class ValidationsContextTest < ActiveModel::TestCase def teardown @@ -38,7 +38,7 @@ class ValidationsContextTest < ActiveModel::TestCase Topic.validates_with(ValidatorThatAddsErrors, on: :create) topic = Topic.new assert topic.invalid?(:create), "Validation does run on create if 'on' is set to create" - assert topic.errors[:base].include?(ERROR_MESSAGE) + assert_includes topic.errors[:base], ERROR_MESSAGE end test "with a class that adds errors on multiple contexts and validating a new model" do @@ -48,10 +48,10 @@ class ValidationsContextTest < ActiveModel::TestCase assert topic.valid?, "Validation ran with no context given when 'on' is set to context1 and context2" assert topic.invalid?(:context1), "Validation did not run on context1 when 'on' is set to context1 and context2" - assert topic.errors[:base].include?(ERROR_MESSAGE) + assert_includes topic.errors[:base], ERROR_MESSAGE assert topic.invalid?(:context2), "Validation did not run on context2 when 'on' is set to context1 and context2" - assert topic.errors[:base].include?(ERROR_MESSAGE) + assert_includes topic.errors[:base], ERROR_MESSAGE end test "with a class that validating a model for a multiple contexts" do @@ -62,7 +62,7 @@ class ValidationsContextTest < ActiveModel::TestCase assert topic.valid?, "Validation ran with no context given when 'on' is set to context1 and context2" assert topic.invalid?([:context1, :context2]), "Validation did not run on context1 when 'on' is set to context1 and context2" - assert topic.errors[:base].include?(ERROR_MESSAGE) - assert topic.errors[:base].include?(ANOTHER_ERROR_MESSAGE) + assert_includes topic.errors[:base], ERROR_MESSAGE + assert_includes topic.errors[:base], ANOTHER_ERROR_MESSAGE end end diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb index c73580138d..20c11dd852 100644 --- a/activemodel/test/cases/validations/with_validation_test.rb +++ b/activemodel/test/cases/validations/with_validation_test.rb @@ -1,9 +1,8 @@ -require 'cases/helper' +require "cases/helper" -require 'models/topic' +require "models/topic" class ValidatesWithTest < ActiveModel::TestCase - def teardown Topic.clear_validators! end @@ -52,7 +51,7 @@ class ValidatesWithTest < ActiveModel::TestCase Topic.validates_with(ValidatorThatAddsErrors) topic = Topic.new assert topic.invalid?, "A class that adds errors causes the record to be invalid" - assert topic.errors[:base].include?(ERROR_MESSAGE) + assert_includes topic.errors[:base], ERROR_MESSAGE end test "with a class that returns valid" do @@ -65,8 +64,8 @@ class ValidatesWithTest < ActiveModel::TestCase Topic.validates_with(ValidatorThatAddsErrors, OtherValidatorThatAddsErrors) topic = Topic.new assert topic.invalid? - assert topic.errors[:base].include?(ERROR_MESSAGE) - assert topic.errors[:base].include?(OTHER_ERROR_MESSAGE) + assert_includes topic.errors[:base], ERROR_MESSAGE + assert_includes topic.errors[:base], OTHER_ERROR_MESSAGE end test "with if statements that return false" do @@ -79,7 +78,7 @@ class ValidatesWithTest < ActiveModel::TestCase Topic.validates_with(ValidatorThatAddsErrors, if: "1 == 1") topic = Topic.new assert topic.invalid? - assert topic.errors[:base].include?(ERROR_MESSAGE) + assert_includes topic.errors[:base], ERROR_MESSAGE end test "with unless statements that return true" do @@ -92,13 +91,13 @@ class ValidatesWithTest < ActiveModel::TestCase Topic.validates_with(ValidatorThatAddsErrors, unless: "1 == 2") topic = Topic.new assert topic.invalid? - assert topic.errors[:base].include?(ERROR_MESSAGE) + assert_includes topic.errors[:base], ERROR_MESSAGE end test "passes all configuration options to the validator class" do topic = Topic.new validator = Minitest::Mock.new - validator.expect(:new, validator, [{foo: :bar, if: "1 == 1", class: Topic}]) + validator.expect(:new, validator, [{ foo: :bar, if: "1 == 1", class: Topic }]) validator.expect(:validate, nil, [topic]) validator.expect(:is_a?, false, [Symbol]) validator.expect(:is_a?, false, [String]) @@ -112,7 +111,7 @@ class ValidatesWithTest < ActiveModel::TestCase Topic.validates_with(ValidatorThatValidatesOptions, field: :first_name) topic = Topic.new assert topic.invalid? - assert topic.errors[:base].include?(ERROR_MESSAGE) + assert_includes topic.errors[:base], ERROR_MESSAGE end test "validates_with each validator" do @@ -160,7 +159,7 @@ class ValidatesWithTest < ActiveModel::TestCase topic = Topic.new assert !topic.valid? - assert_equal ['is missing'], topic.errors[:title] + assert_equal ["is missing"], topic.errors[:title] end test "optionally pass in the attribute being validated when validating with an instance method" do @@ -169,6 +168,6 @@ class ValidatesWithTest < ActiveModel::TestCase topic = Topic.new title: "foo" assert !topic.valid? assert topic.errors[:title].empty? - assert_equal ['is missing'], topic.errors[:content] + assert_equal ["is missing"], topic.errors[:content] end end diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb index f0317ad219..6647191205 100644 --- a/activemodel/test/cases/validations_test.rb +++ b/activemodel/test/cases/validations_test.rb @@ -1,11 +1,11 @@ -require 'cases/helper' +require "cases/helper" -require 'models/topic' -require 'models/reply' -require 'models/custom_reader' +require "models/topic" +require "models/reply" +require "models/custom_reader" -require 'active_support/json' -require 'active_support/xml_mini' +require "active_support/json" +require "active_support/xml_mini" class ValidationsTest < ActiveModel::TestCase class CustomStrictValidationException < StandardError; end @@ -51,10 +51,10 @@ class ValidationsTest < ActiveModel::TestCase r = Reply.new r.valid? - errors = r.errors.collect {|attr, messages| [attr.to_s, messages]} + errors = r.errors.collect { |attr, messages| [attr.to_s, messages] } - assert errors.include?(["title", "is Empty"]) - assert errors.include?(["content", "is Empty"]) + assert_includes errors, ["title", "is Empty"] + assert_includes errors, ["content", "is Empty"] end def test_multiple_errors_per_attr_iteration_with_full_error_composition @@ -86,8 +86,8 @@ class ValidationsTest < ActiveModel::TestCase assert_equal ["Reply is not dignifying"], r.errors[:base] - assert errors.include?("Title is Empty") - assert errors.include?("Reply is not dignifying") + assert_includes errors, "Title is Empty" + assert_includes errors, "Reply is not dignifying" assert_equal 2, r.errors.count end @@ -101,8 +101,8 @@ class ValidationsTest < ActiveModel::TestCase assert_equal ["is invalid"], r.errors[:base] - assert errors.include?("Title is Empty") - assert errors.include?("is invalid") + assert_includes errors, "Title is Empty" + assert_includes errors, "is invalid" assert_equal 2, r.errors.count end @@ -116,7 +116,7 @@ class ValidationsTest < ActiveModel::TestCase def test_validates_each hits = 0 Topic.validates_each(:title, :content, [:title, :content]) do |record, attr| - record.errors.add attr, 'gotcha' + record.errors.add attr, "gotcha" hits += 1 end t = Topic.new("title" => "valid", "content" => "whatever") @@ -129,7 +129,7 @@ class ValidationsTest < ActiveModel::TestCase def test_validates_each_custom_reader hits = 0 CustomReader.validates_each(:title, :content, [:title, :content]) do |record, attr| - record.errors.add attr, 'gotcha' + record.errors.add attr, "gotcha" hits += 1 end t = CustomReader.new("title" => "valid", "content" => "whatever") @@ -170,7 +170,7 @@ class ValidationsTest < ActiveModel::TestCase # A common mistake -- we meant to call 'validates' Topic.validate :title, presence: true end - message = 'Unknown key: :presence. Valid keys are: :on, :if, :unless, :prepend. Perhaps you meant to call `validates` instead of `validate`?' + message = "Unknown key: :presence. Valid keys are: :on, :if, :unless, :prepend. Perhaps you meant to call `validates` instead of `validate`?" assert_equal message, error.message end @@ -198,9 +198,9 @@ class ValidationsTest < ActiveModel::TestCase end assert_nothing_raised do - klass.validate :validator_a, if: ->{ true } + klass.validate :validator_a, if: -> { true } klass.validate :validator_b, prepend: true - klass.validate :validator_c, unless: ->{ true } + klass.validate :validator_c, unless: -> { true } end t = klass.new @@ -233,25 +233,25 @@ class ValidationsTest < ActiveModel::TestCase assert t.invalid? assert_equal "can't be blank", t.errors["title"].first Topic.validates_presence_of :title, :author_name - Topic.validate {errors.add('author_email_address', 'will never be valid')} + Topic.validate { errors.add("author_email_address", "will never be valid") } Topic.validates_length_of :title, :content, minimum: 2 - t = Topic.new title: '' + t = Topic.new title: "" assert t.invalid? assert_equal :title, key = t.errors.keys[0] assert_equal "can't be blank", t.errors[key][0] - assert_equal 'is too short (minimum is 2 characters)', t.errors[key][1] + assert_equal "is too short (minimum is 2 characters)", t.errors[key][1] assert_equal :author_name, key = t.errors.keys[1] assert_equal "can't be blank", t.errors[key][0] assert_equal :author_email_address, key = t.errors.keys[2] - assert_equal 'will never be valid', t.errors[key][0] + assert_equal "will never be valid", t.errors[key][0] assert_equal :content, key = t.errors.keys[3] - assert_equal 'is too short (minimum is 2 characters)', t.errors[key][0] + assert_equal "is too short (minimum is 2 characters)", t.errors[key][0] end def test_validation_with_if_and_on - Topic.validates_presence_of :title, if: Proc.new{|x| x.author_name = "bad"; true }, on: :update + Topic.validates_presence_of :title, if: Proc.new { |x| x.author_name = "bad"; true }, on: :update t = Topic.new(title: "") @@ -271,7 +271,7 @@ class ValidationsTest < ActiveModel::TestCase assert t.invalid? assert t.errors[:title].any? - t.title = 'Things are going to change' + t.title = "Things are going to change" assert !t.invalid? end @@ -332,9 +332,9 @@ class ValidationsTest < ActiveModel::TestCase assert topic.invalid? assert_equal 3, topic.errors.size - topic.title = 'Some Title' - topic.author_name = 'Some Author' - topic.content = 'Some Content Whose Length is more than 10.' + topic.title = "Some Title" + topic.author_name = "Some Author" + topic.content = "Some Content Whose Length is more than 10." assert topic.valid? end @@ -440,8 +440,24 @@ class ValidationsTest < ActiveModel::TestCase assert duped.invalid? topic.title = nil - duped.title = 'Mathematics' + duped.title = "Mathematics" assert topic.invalid? assert duped.valid? end + + def test_validation_with_message_as_proc_that_takes_a_record_as_a_parameter + Topic.validates_presence_of(:title, message: proc { |record| "You have failed me for the last time, #{record.author_name}." }) + + t = Topic.new(author_name: "Admiral") + assert t.invalid? + assert_equal ["You have failed me for the last time, Admiral."], t.errors[:title] + end + + def test_validation_with_message_as_proc_that_takes_record_and_data_as_a_parameters + Topic.validates_presence_of(:title, message: proc { |record, data| "#{data[:attribute]} is missing. You have failed me for the last time, #{record.author_name}." }) + + t = Topic.new(author_name: "Admiral") + assert t.invalid? + assert_equal ["Title is missing. You have failed me for the last time, Admiral."], t.errors[:title] + end end diff --git a/activemodel/test/models/custom_reader.rb b/activemodel/test/models/custom_reader.rb index 2fbcf79c3d..dc26bb10ff 100644 --- a/activemodel/test/models/custom_reader.rb +++ b/activemodel/test/models/custom_reader.rb @@ -12,4 +12,4 @@ class CustomReader def read_attribute_for_validation(key) @data[key] end -end
\ No newline at end of file +end diff --git a/activemodel/test/models/project.rb b/activemodel/test/models/project.rb deleted file mode 100644 index 581b6dc0b3..0000000000 --- a/activemodel/test/models/project.rb +++ /dev/null @@ -1,3 +0,0 @@ -class Project - include ActiveModel::DeprecatedMassAssignmentSecurity -end diff --git a/activemodel/test/models/reply.rb b/activemodel/test/models/reply.rb index b77910e671..3fe11043d2 100644 --- a/activemodel/test/models/reply.rb +++ b/activemodel/test/models/reply.rb @@ -1,4 +1,4 @@ -require 'models/topic' +require "models/topic" class Reply < Topic validate :errors_on_empty_content diff --git a/activemodel/test/models/topic.rb b/activemodel/test/models/topic.rb index fed50bc361..192786c096 100644 --- a/activemodel/test/models/topic.rb +++ b/activemodel/test/models/topic.rb @@ -36,9 +36,4 @@ class Topic def my_validation_with_arg(attr) errors.add attr, "is missing" unless send(attr) end - - def my_word_tokenizer(str) - str.scan(/\w+/) - end - end diff --git a/activemodel/test/models/track_back.rb b/activemodel/test/models/track_back.rb index 768c96ecf0..357ee37d6d 100644 --- a/activemodel/test/models/track_back.rb +++ b/activemodel/test/models/track_back.rb @@ -8,4 +8,4 @@ class Post class NamedTrackBack extend ActiveModel::Naming end -end
\ No newline at end of file +end diff --git a/activemodel/test/models/user.rb b/activemodel/test/models/user.rb index 1ec6001c48..6556b1a7d9 100644 --- a/activemodel/test/models/user.rb +++ b/activemodel/test/models/user.rb @@ -1,7 +1,7 @@ class User extend ActiveModel::Callbacks include ActiveModel::SecurePassword - + define_model_callbacks :create has_secure_password diff --git a/activemodel/test/validators/email_validator.rb b/activemodel/test/validators/email_validator.rb index cff47ac230..9b74d83b37 100644 --- a/activemodel/test/validators/email_validator.rb +++ b/activemodel/test/validators/email_validator.rb @@ -1,6 +1,7 @@ + class EmailValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) record.errors[attribute] << (options[:message] || "is not an email") unless - value =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i + /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i.match?(value) end -end
\ No newline at end of file +end diff --git a/activemodel/test/validators/namespace/email_validator.rb b/activemodel/test/validators/namespace/email_validator.rb index 57e2793ce2..8639045b0d 100644 --- a/activemodel/test/validators/namespace/email_validator.rb +++ b/activemodel/test/validators/namespace/email_validator.rb @@ -1,4 +1,4 @@ -require 'validators/email_validator' +require "validators/email_validator" module Namespace class EmailValidator < ::EmailValidator diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index f7fdb6755d..c2f26bce70 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -5,1873 +5,266 @@ *Mehmet Emin İNAÇ* -* Rework `ActiveRecord::Relation#last` - - 1. Never perform additional SQL on loaded relation - 2. Use SQL reverse order instead of loading relation if relation doesn't have limit - 3. Deprecated relation loading when SQL order can not be automatically reversed +* Deprecate passing arguments and block at the same time to + `ActiveRecord::QueryMethods#select`. - Topic.order("title").load.last(3) - # before: SELECT ... - # after: No SQL - - Topic.order("title").last - # before: SELECT * FROM `topics` - # after: SELECT * FROM `topics` ORDER BY `topics`.`title` DESC LIMIT 1 - - Topic.order("coalesce(author, title)").last - # before: SELECT * FROM `topics` - # after: Deprecation Warning for irreversible order - - *Bogdan Gusiev* - -* Allow `joins` to be unscoped. - - Closes #13775. - - *Takashi Kokubun* - -* Add ActiveRecord `#second_to_last` and `#third_to_last` methods. - - *Brian Christian* - -* Added `numeric` helper into migrations. - - Example: - - create_table(:numeric_types) do |t| - t.numeric :numeric_type, precision: 10, scale: 2 - end - - *Mehmet Emin İNAÇ* - -* Bumped the minimum supported version of PostgreSQL to >= 9.1. - Both PG 9.0 and 8.4 are past their end of life date: - http://www.postgresql.org/support/versioning/ - - *Remo Mueller* - -## Rails 5.0.0.beta2 (February 01, 2016) ## - -* `ActiveRecord::Relation#reverse_order` throws `ActiveRecord::IrreversibleOrderError` - when the order can not be reversed using current trivial algorithm. - Also raises the same error when `#reverse_order` is called on - relation without any order and table has no primary key: - - Topic.order("concat(author_name, title)").reverse_order - # Before: SELECT `topics`.* FROM `topics` ORDER BY concat(author_name DESC, title) DESC - # After: raises ActiveRecord::IrreversibleOrderError - Edge.all.reverse_order - # Before: SELECT `edges`.* FROM `edges` ORDER BY `edges`.`` DESC - # After: raises ActiveRecord::IrreversibleOrderError - - *Bogdan Gusiev* - -* Improve schema_migrations insertion performance by inserting all versions - in one INSERT SQL. - - *Akira Matsuda*, *Naoto Koshikawa* - -* Using `references` or `belongs_to` in migrations will always add index - for the referenced column by default, without adding `index: true` option - to generated migration file. Users can opt out of this by passing - `index: false`. - - Fixes #18146. - - *Matthew Draper*, *Prathamesh Sonpatki* - -* Run `type` attributes through attributes API type-casting before - instantiating the corresponding subclass. This makes it possible to define - custom STI mappings. - - Fixes #21986. - - *Yves Senn* - -* Don't try to quote functions or expressions passed to `:default` option if - they are passed as procs. - - This will generate proper query with the passed function or expression for - the default option, instead of trying to quote it in incorrect fashion. - - Example: - - create_table :posts do |t| - t.datetime :published_at, default: -> { 'NOW()' } - end - - *Ryuta Kamizono* - -* Fix regression when loading fixture files with symbol keys. - - Fixes #22584. - - *Yves Senn* - -* Use `version` column as primary key for schema_migrations table because - `schema_migrations` versions are guaranteed to be unique. - - This makes it possible to use `update_attributes` on models that do - not have a primary key. - - *Richard Schneeman* - -* Add short-hand methods for text and blob types in MySQL. - - In Pg and Sqlite3, `:text` and `:binary` have variable unlimited length. - But in MySQL, these have limited length for each types (ref #21591, #21619). - This change adds short-hand methods for each text and blob types. - - Example: - - create_table :foos do |t| - t.tinyblob :tiny_blob - t.mediumblob :medium_blob - t.longblob :long_blob - t.tinytext :tiny_text - t.mediumtext :medium_text - t.longtext :long_text - end - - *Ryuta Kamizono* - -* Take into account UTC offset when assigning string representation of - timestamp with offset specified to attribute of time type. - - *Andrey Novikov* - -* When calling `first` with a `limit` argument, return directly from the - `loaded?` records if available. - - *Ben Woosley* - -* Deprecate sending the `offset` argument to `find_nth`. Please use the - `offset` method on relation instead. - - *Ben Woosley* - -## Rails 5.0.0.beta1 (December 18, 2015) ## - -* Order the result of `find(ids)` to match the passed array, if the relation - has no explicit order defined. - - Fixes #20338. - - *Miguel Grazziotin*, *Matthew Draper* - -* Omit default limit values in dumped schema. It's tidier, and if the defaults - change in the future, we can address that via Migration API Versioning. - - *Jean Boussier* - -* Support passing the schema name as a prefix to table name in - `ConnectionAdapters::SchemaStatements#indexes`. Previously the prefix would - be considered a full part of the index name, and only the schema in the - current search path would be considered. - - *Grey Baker* - -* Ignore index name in `index_exists?` and `remove_index` when not passed a - name to check for. - - *Grey Baker* - -* Extract support for the legacy `mysql` database adapter from core. It will - live on in a separate gem for now, but most users should just use `mysql2`. - - *Abdelkader Boudih* - -* ApplicationRecord is a new superclass for all app models, analogous to app - controllers subclassing ApplicationController instead of - ActionController::Base. This gives apps a single spot to configure app-wide - model behavior. - - Newly generated applications have `app/models/application_record.rb` - present by default. - - *Genadi Samokovarov* - -* Version the API presented to migration classes, so we can change parameter - defaults without breaking existing migrations, or forcing them to be - rewritten through a deprecation cycle. - - New migrations specify the Rails version they were written for: - - class AddStatusToOrders < ActiveRecord::Migration[5.0] - def change - # ... - end - end - - *Matthew Draper*, *Ravil Bayramgalin* - -* Use bind params for `limit` and `offset`. This will generate significantly - fewer prepared statements for common tasks like pagination. To support this - change, passing a string containing a comma to `limit` has been deprecated, - and passing an Arel node to `limit` is no longer supported. - - Fixes #22250. - - *Sean Griffin* - -* Introduce after_{create,update,delete}_commit callbacks. - - Before: - - after_commit :add_to_index_later, on: :create - after_commit :update_in_index_later, on: :update - after_commit :remove_from_index_later, on: :destroy - - After: - - after_create_commit :add_to_index_later - after_update_commit :update_in_index_later - after_destroy_commit :remove_from_index_later - - Fixes #22515. - - *Genadi Samokovarov* - -* Respect the column default values for `inheritance_column` when - instantiating records through the base class. - - Fixes #17121. - - Example: - - # The schema of BaseModel has `t.string :type, default: 'SubType'` - subtype = BaseModel.new - assert_equals SubType, subtype.class - - *Kuldeep Aggarwal* - -* Fix `rake db:structure:dump` on Postgres when multiple schemas are used. - - Fixes #22346. - - *Nick Muerdter*, *ckoenig* - -* Add schema dumping support for PostgreSQL geometric data types. - - *Ryuta Kamizono* - -* Except keys of `build_record`'s argument from `create_scope` in `initialize_attributes`. - - Fixes #21893. - - *Yuichiro Kaneko* - -* Deprecate `connection.tables` on the SQLite3 and MySQL adapters. - Also deprecate passing arguments to `#tables`. - And deprecate `table_exists?`. - - The `#tables` method of some adapters (mysql, mysql2, sqlite3) would return - both tables and views while others (postgresql) just return tables. To make - their behavior consistent, `#tables` will return only tables in the future. - - The `#table_exists?` method would check both tables and views. To make - their behavior consistent with `#tables`, `#table_exists?` will check only - tables in the future. - - *Yuichiro Kaneko* - -* Improve support for non Active Record objects on `validates_associated` - - Skipping `marked_for_destruction?` when the associated object does not responds - to it make easier to validate virtual associations built on top of Active Model - objects and/or serialized objects that implement a `valid?` instance method. - - *Kassio Borges*, *Lucas Mazza* - -* Change connection management middleware to return a new response with - a body proxy, rather than mutating the original. - - *Kevin Buchanan* - -* Make `db:migrate:status` to render `1_some.rb` format migrate files. - - These files are in `db/migrate`: - - * 1_valid_people_have_last_names.rb - * 20150819202140_irreversible_migration.rb - * 20150823202140_add_admin_flag_to_users.rb - * 20150823202141_migration_tests.rb - * 2_we_need_reminders.rb - * 3_innocent_jointable.rb - - Before: - - $ bundle exec rake db:migrate:status - ... - - Status Migration ID Migration Name - -------------------------------------------------- - up 001 ********** NO FILE ********** - up 002 ********** NO FILE ********** - up 003 ********** NO FILE ********** - up 20150819202140 Irreversible migration - up 20150823202140 Add admin flag to users - up 20150823202141 Migration tests - - After: - - $ bundle exec rake db:migrate:status - ... - - Status Migration ID Migration Name - -------------------------------------------------- - up 001 Valid people have last names - up 002 We need reminders - up 003 Innocent jointable - up 20150819202140 Irreversible migration - up 20150823202140 Add admin flag to users - up 20150823202141 Migration tests - - *Yuichiro Kaneko* - -* Define `ActiveRecord::Sanitization.sanitize_sql_for_order` and use it inside - `preprocess_order_args`. - - *Yuichiro Kaneko* - -* Allow bigint with default nil for avoiding auto increment primary key. - - *Ryuta Kamizono* - -* Remove `DEFAULT_CHARSET` and `DEFAULT_COLLATION` in `MySQLDatabaseTasks`. - - We should omit the collation entirely rather than providing a default. - Then the choice is the responsibility of the server and MySQL distribution. - - *Ryuta Kamizono* - -* Alias `ActiveRecord::Relation#left_joins` to - `ActiveRecord::Relation#left_outer_joins`. - - *Takashi Kokubun* - -* Use advisory locking to raise a `ConcurrentMigrationError` instead of - attempting to migrate when another migration is currently running. - - *Sam Davies* - -* Added `ActiveRecord::Relation#left_outer_joins`. - - Example: - - User.left_outer_joins(:posts) - # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON - "posts"."user_id" = "users"."id" - - *Florian Thomas* - -* Support passing an array to `order` for SQL parameter sanitization. - - *Aaron Suggs* - -* Avoid disabling errors on the PostgreSQL connection when enabling the - `standard_conforming_strings` setting. Errors were previously disabled because - the setting wasn't writable in Postgres 8.1 and didn't exist in earlier - versions. Now Rails only supports Postgres 8.2+ we're fine to assume the - setting exists. Disabling errors caused problems when using a connection - pooling tool like PgBouncer because it's not guaranteed to have the same - connection between calls to `execute` and it could leave the connection - with errors disabled. - - Fixes #22101. - - *Harry Marr* - -* Set `scope.reordering_value` to `true` if `:reordering`-values are specified. - - Fixes #21886. - - *Hiroaki Izu* - -* Add support for bidirectional destroy dependencies. - - Fixes #13609. - - Example: - - class Content < ActiveRecord::Base - has_one :position, dependent: :destroy - end - - class Position < ActiveRecord::Base - belongs_to :content, dependent: :destroy - end - - *Seb Jacobs* - -* Includes HABTM returns correct size now. It's caused by the join dependency - only instantiates one HABTM object because the join table hasn't a primary key. - - Fixes #16032. - - Examples: - - before: - - Project.first.salaried_developers.size # => 3 - Project.includes(:salaried_developers).first.salaried_developers.size # => 1 - - after: - - Project.first.salaried_developers.size # => 3 - Project.includes(:salaried_developers).first.salaried_developers.size # => 3 + *Prathamesh Sonpatki* - *Bigxiang* +* Optimistic locking: Added ability update locking_column value. + Ignore optimistic locking if update with new locking_column value. -* Add option to index errors in nested attributes + *bogdanvlviv* - For models which have nested attributes, errors within those models will - now be indexed if :index_errors is specified when defining a - has_many relationship, or if its set in the global config. +* Fixed: Optimistic locking does not work well with null in the database. - Example: + Fixes #26024 - class Guitar < ActiveRecord::Base - has_many :tuning_pegs - accepts_nested_attributes_for :tuning_pegs - end + *bogdanvlviv* - class TuningPeg < ActiveRecord::Base - belongs_to :guitar - validates_numericality_of :pitch - end +* Fixed support for case insensitive comparisons of `text` columns in + PostgreSQL. - # Old style - guitar.errors["tuning_pegs.pitch"] = ["is not a number"] + *Edho Arief* - # New style (if defined globally, or set in has_many_relationship) - guitar.errors["tuning_pegs[1].pitch"] = ["is not a number"] +* Serialize JSON attribute value `nil` as SQL `NULL`, not JSON `null` - *Michael Probber*, *Terence Sun* + *Trung Duc Tran* -* Exit with non-zero status for failed database rake tasks. +* Return `true` from `update_attribute` when the value of the attribute + to be updated is unchanged. - *Jay Hayes* + Fixes #26593. -* Queries such as `Computer.joins(:monitor).group(:status).count` will now be - interpreted as `Computer.joins(:monitor).group('computers.status').count` - so that when `Computer` and `Monitor` have both `status` columns we don't - have conflicts in projection. + *Prathamesh Sonpatki* - *Rafael Sales* +* Always store errors details information with symbols. -* Add ability to default to `uuid` as primary key when generating database migrations. + When the association is autosaved we were storing the details with + string keys. This was creating inconsistency with other details that are + added using the `Errors#add` method. It was also inconsistent with the + `Errors#messages` storage. - Example: + To fix this inconsistency we are always storing with symbols. This will + cause a small breaking change because in those cases the details could + be accessed as strings keys but now it can not. - config.generators do |g| - g.orm :active_record, primary_key_type: :uuid - end + Fix #26499. - *Jon McCartie* + *Rafael Mendonça França*, *Marcus Vieira* -* Don't cache arguments in `#find_by` if they are an `ActiveRecord::Relation`. +* Calling `touch` on a model using optimistic locking will now leave the model + in a non-dirty state with no attribute changes. - Fixes #20817. + Fixes #26496. - *Hiroaki Izu* + *Jakob Skjerning* -* Qualify column name inserted by `group` in calculation. +* Using a mysql2 connection after it fails to reconnect will now have an error message + saying the connection is closed rather than an undefined method error message. - Giving `group` an unqualified column name now works, even if the relation - has `JOIN` with another table which also has a column of the name. + *Dylan Thacker-Smith* - *Soutaro Matsumoto* +* PostgreSQL array columns will now respect the encoding of strings contained + in the array. -* Don't cache prepared statements containing an IN clause or a SQL literal, as - these queries will change often and are unlikely to have a cache hit. + Fixes #26326. *Sean Griffin* -* Fix `rewhere` in a `has_many` association. - - Fixes #21955. - - *Josh Branchaud*, *Kal* - -* `where` raises ArgumentError on unsupported types. - - Fixes #20473. +* Inverse association instances will now be set before `after_find` or + `after_initialize` callbacks are run. - *Jake Worth* - -* Add an immutable string type to help reduce memory usage for apps which do - not need mutation detection on strings. + Fixes #26320. *Sean Griffin* -* Give `ActiveRecord::Relation#update` its own deprecation warning when - passed an `ActiveRecord::Base` instance. - - Fixes #21945. - - *Ted Johansson* - -* Make it possible to pass `:to_table` when adding a foreign key through - `add_reference`. - - Fixes #21563. - - *Yves Senn* +* Remove unnecessarily association load when a `belongs_to` association has already been + loaded then the foreign key is changed directly and the record saved. -* No longer pass deprecated option `-i` to `pg_dump`. + *James Coleman* - *Paul Sadauskas* +* Remove standardized column types/arguments spaces in schema dump. -* Concurrent `AR::Base#increment!` and `#decrement!` on the same record - are all reflected in the database rather than overwriting each other. + *Tim Petricola* - *Bogdan Gusiev* +* Avoid loading records from database when they are already loaded using + the `pluck` method on a collection. -* Avoid leaking the first relation we call `first` on, per model. - - Fixes #21921. - - *Matthew Draper*, *Jean Boussier* - -* Remove unused `pk_and_sequence_for` in `AbstractMysqlAdapter`. + Fixes #25921. *Ryuta Kamizono* -* Allow fixtures files to set the model class in the YAML file itself. - - To load the fixtures file `accounts.yml` as the `User` model, use: - - _fixture: - model_class: User - david: - name: David - - Fixes #9516. - - *Roque Pinel* - -* Don't require a database connection to load a class which uses acceptance - validations. - - *Sean Griffin* - -* Correctly apply `unscope` when preloading through associations. - - *Jimmy Bourassa* - -* Fixed taking precision into count when assigning a value to timestamp attribute. - - Timestamp column can have less precision than ruby timestamp - In result in how big a fraction of a second can be stored in the - database. - - - m = Model.create! - m.created_at.usec == m.reload.created_at.usec # => false - # due to different precision in Time.now and database column - - If the precision is low enough, (mysql default is 0, so it is always low - enough by default) the value changes when model is reloaded from the - database. This patch fixes that issue ensuring that any timestamp - assigned as an attribute is converted to column precision under the - attribute. - - *Bogdan Gusiev* - -* Introduce `connection.data_sources` and `connection.data_source_exists?`. - These methods determine what relations can be used to back Active Record - models (usually tables and views). - - Also deprecate `SchemaCache#tables`, `SchemaCache#table_exists?` and - `SchemaCache#clear_table_cache!` in favor of their new data source - counterparts. - - *Yves Senn*, *Matthew Draper* - -* Add `ActiveRecord::Base.ignored_columns` to make some columns - invisible from Active Record. - - *Jean Boussier* - -* `ActiveRecord::Tasks::MySQLDatabaseTasks` fails if shellout to - mysql commands (like `mysqldump`) is not successful. - - *Steve Mitchell* - -* Ensure `select` quotes aliased attributes, even when using `from`. - - Fixes #21488. - - *Sean Griffin*, *@johanlunds* - -* MySQL: support `unsigned` numeric data types. - - Example: - - create_table :foos do |t| - t.unsigned_integer :quantity - t.unsigned_bigint :total - t.unsigned_float :percentage - t.unsigned_decimal :price, precision: 10, scale: 2 - end - - The `unsigned: true` option may be used for the primary key: - - create_table :foos, id: :bigint, unsigned: true do |t| - … - end - - *Ryuta Kamizono* - -* Add `#views` and `#view_exists?` methods on connection adapters. - - *Ryuta Kamizono* - -* Correctly dump composite primary key. - - Example: - - create_table :barcodes, primary_key: ["region", "code"] do |t| - t.string :region - t.integer :code - end - - *Ryuta Kamizono* - -* Lookup the attribute name for `restrict_with_error` messages on the - model class that defines the association. - - *kuboon*, *Ronak Jangir* - -* Correct query for PostgreSQL 8.2 compatibility. - - *Ben Murphy*, *Matthew Draper* - -* `bin/rails db:migrate` uses - `ActiveRecord::Tasks::DatabaseTasks.migrations_paths` instead of - `Migrator.migrations_paths`. - - *Tobias Bielohlawek* - -* Support dropping indexes concurrently in PostgreSQL. - - See http://www.postgresql.org/docs/9.4/static/sql-dropindex.html for more - details. - - *Grey Baker* - -* Deprecate passing conditions to `ActiveRecord::Relation#delete_all` - and `ActiveRecord::Relation#destroy_all`. - - *Wojciech Wnętrzak* - -* Instantiating an AR model with `ActionController::Parameters` now raises - an `ActiveModel::ForbiddenAttributesError` if the parameters include a - `type` field that has not been explicitly permitted. Previously, the - `type` field was simply ignored in the same situation. - - *Prem Sichanugrist* - -* PostgreSQL, `create_schema`, `drop_schema` and `rename_table` now quote - schema names. - - Fixes #21418. - - Example: - - create_schema("my.schema") - # CREATE SCHEMA "my.schema"; - - *Yves Senn* - -* PostgreSQL, add `:if_exists` option to `#drop_schema`. This makes it - possible to drop a schema that might exist without raising an exception if - it doesn't. - - *Yves Senn* - -* Only try to nullify has_one target association if the record is persisted. - - Fixes #21223. - - *Agis Anastasopoulos* - -* Uniqueness validator raises descriptive error when running on a persisted - record without primary key. - - Fixes #21304. - - *Yves Senn* - -* Add a native JSON data type support in MySQL. - - Example: - - create_table :json_data_type do |t| - t.json :settings - end - - *Ryuta Kamizono* - -* Descriptive error message when fixtures contain a missing column. - - Fixes #21201. - - *Yves Senn* - -* `ActiveRecord::Tasks::PostgreSQLDatabaseTasks` fail if shellout to - postgresql commands (like `pg_dump`) is not successful. - - *Bryan Paxton*, *Nate Berkopec* - -* Add `ActiveRecord::Relation#in_batches` to work with records and relations - in batches. - - Available options are `of` (batch size), `load`, `start`, and `finish`. - - Examples: - - Person.in_batches.each_record(&:party_all_night!) - Person.in_batches.update_all(awesome: true) - Person.in_batches.delete_all - Person.in_batches.each do |relation| - relation.delete_all - sleep 10 # Throttles the delete queries - end - - Fixes #20933. - - *Sina Siadat* - -* Added methods for PostgreSQL geometric data types to use in migrations. - - Example: - - create_table :foo do |t| - t.line :foo_line - t.lseg :foo_lseg - t.box :foo_box - t.path :foo_path - t.polygon :foo_polygon - t.circle :foo_circle - end - - *Mehmet Emin İNAÇ* - -* Add `cache_key` to ActiveRecord::Relation. - - Example: - - @users = User.where("name like ?", "%Alberto%") - @users.cache_key - # => "/users/query-5942b155a43b139f2471b872ac54251f-3-20150714212107656125000" - - *Alberto Fernández-Capel* - -* Properly allow uniqueness validations on primary keys. - - Fixes #20966. - - *Sean Griffin*, *presskey* - -* Don't raise an error if an association failed to destroy when `destroy` was - called on the parent (as opposed to `destroy!`). - - Fixes #20991. - - *Sean Griffin* - -* `ActiveRecord::RecordNotFound` modified to store model name, primary_key and - id of the caller model. It allows the catcher of this exception to make - a better decision to what to do with it. - - Example: - - class SomeAbstractController < ActionController::Base - rescue_from ActiveRecord::RecordNotFound, with: :redirect_to_404 - - private def redirect_to_404(e) - return redirect_to(posts_url) if e.model == 'Post' - raise +* Remove text default treated as an empty string in non-strict mode for + consistency with other types. + + Strict mode controls how MySQL handles invalid or missing values in + data-change statements such as INSERT or UPDATE. If strict mode is not + in effect, MySQL inserts adjusted values for invalid or missing values + and produces warnings. + + def test_mysql_not_null_defaults_non_strict + using_strict(false) do + with_mysql_not_null_table do |klass| + record = klass.new + assert_nil record.non_null_integer + assert_nil record.non_null_string + assert_nil record.non_null_text + assert_nil record.non_null_blob + + record.save! + record.reload + + assert_equal 0, record.non_null_integer + assert_equal "", record.non_null_string + assert_equal "", record.non_null_text + assert_equal "", record.non_null_blob + end end end - *Sameer Rahmani* - -* Deprecate the keys for association `restrict_dependent_destroy` errors in favor - of new key names. - - Previously `has_one` and `has_many` associations were using the - `one` and `many` keys respectively. Both of these keys have special - meaning in I18n (they are considered to be pluralizations) so by - renaming them to `has_one` and `has_many` we make the messages more explicit - and most importantly they don't clash with linguistical systems that need to - validate translation keys (and their pluralizations). - - The `:'restrict_dependent_destroy.one'` key should be replaced with - `:'restrict_dependent_destroy.has_one'`, and `:'restrict_dependent_destroy.many'` - with `:'restrict_dependent_destroy.has_many'`. - - *Roque Pinel*, *Christopher Dell* - -* Fix state being carried over from previous transaction. - - Considering the following example where `name` is a required attribute. - Before we had `new_record?` returning `true` for a persisted record: - - author = Author.create! name: 'foo' - author.name = nil - author.save # => false - author.new_record? # => true - - Fixes #20824. - - *Roque Pinel* - -* Correctly ignore `mark_for_destruction` when `autosave` isn't set to `true` - when validating associations. - - Fixes #20882. - - *Sean Griffin* - -* Fix a bug where counter_cache doesn't always work with polymorphic - relations. - - Fixes #16407. - - *Stefan Kanev*, *Sean Griffin* - -* Ensure that cyclic associations with autosave don't cause duplicate errors - to be added to the parent record. - - Fixes #20874. - - *Sean Griffin* - -* Ensure that `ActionController::Parameters` can still be passed to nested - attributes. - - Fixes #20922. - - *Sean Griffin* - -* Deprecate force association reload by passing a truthy argument to - association method. - - For collection association, you can call `#reload` on association proxy to - force a reload: - - @user.posts.reload # Instead of @user.posts(true) - - For singular association, you can call `#reload` on the parent object to - clear its association cache then call the association method: - - @user.reload.profile # Instead of @user.profile(true) - - Passing a truthy argument to force association to reload will be removed in - Rails 5.1. - - *Prem Sichanugrist* - -* Replaced `ActiveSupport::Concurrency::Latch` with `Concurrent::CountDownLatch` - from the concurrent-ruby gem. - - *Jerry D'Antonio* - -* Fix through associations using scopes having the scope merged multiple - times. - - Fixes #20721. - Fixes #20727. - - *Sean Griffin* - -* `ActiveRecord::Base.dump_schema_after_migration` applies migration tasks - other than `db:migrate`. (eg. `db:rollback`, `db:migrate:dup`, ...) - - Fixes #20743. - - *Yves Senn* - -* Add alternate syntax to make `change_column_default` reversible. - - User can pass in `:from` and `:to` to make `change_column_default` command - become reversible. - - Example: - - change_column_default :posts, :status, from: nil, to: "draft" - change_column_default :users, :authorized, from: true, to: false - - *Prem Sichanugrist* - -* Prevent error when using `force_reload: true` on an unassigned polymorphic - belongs_to association. - - Fixes #20426. - - *James Dabbs* - -* Correctly raise `ActiveRecord::AssociationTypeMismatch` when assigning - a wrong type to a namespaced association. - - Fixes #20545. - - *Diego Carrion* - -* `validates_absence_of` respects `marked_for_destruction?`. - - Fixes #20449. - - *Yves Senn* - -* Include the `Enumerable` module in `ActiveRecord::Relation` - - *Sean Griffin*, *bogdan* - -* Use `Enumerable#sum` in `ActiveRecord::Relation` if a block is given. - - *Sean Griffin* - -* Let `WITH` queries (Common Table Expressions) be explainable. - - *Vladimir Kochnev* - -* Make `remove_index :table, :column` reversible. - - *Yves Senn* - -* Fixed an error which would occur in dirty checking when calling - `update_attributes` from a getter. - - Fixes #20531. - - *Sean Griffin* - -* Make `remove_foreign_key` reversible. Any foreign key options must be - specified, similar to `remove_column`. - - *Aster Ryan* - -* Add `:_prefix` and `:_suffix` options to `enum` definition. - - Fixes #17511, #17415. - - *Igor Kapkov* - -* Correctly handle decimal arrays with defaults in the schema dumper. - - Fixes #20515. - - *Sean Griffin*, *jmondo* - -* Deprecate the PostgreSQL `:point` type in favor of a new one which will return - `Point` objects instead of an `Array` - - *Sean Griffin* - -* Ensure symbols passed to `ActiveRecord::Relation#select` are always treated - as columns. - - Fixes #20360. - - *Sean Griffin* - -* Do not set `sql_mode` if `strict: :default` is specified. - - # config/database.yml - production: - adapter: mysql2 - database: foo_prod - user: foo - strict: :default + https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sql-mode-strict *Ryuta Kamizono* -* Allow proc defaults to be passed to the attributes API. See documentation - for examples. - - *Sean Griffin*, *Kir Shatrov* - -* SQLite: `:collation` support for string and text columns. - - Example: - - create_table :foo do |t| - t.string :string_nocase, collation: 'NOCASE' - t.text :text_rtrim, collation: 'RTRIM' - end - - add_column :foo, :title, :string, collation: 'RTRIM' - - change_column :foo, :title, :string, collation: 'NOCASE' - - *Akshay Vishnoi* - -* Allow the use of symbols or strings to specify enum values in test - fixtures: - - awdr: - title: "Agile Web Development with Rails" - status: :proposed +* 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. - *George Claghorn* + Fixes #26087 -* Clear query cache when `ActiveRecord::Base#reload` is called. + *Travis O'Neill* - *Shane Hender, Pierre Nespo* - -* Include stored procedures and function on the MySQL structure dump. - - *Jonathan Worek* - -* Pass `:extend` option for `has_and_belongs_to_many` associations to the - underlying `has_many :through`. - - *Jaehyun Shin* - -* Deprecate `Relation#uniq` use `Relation#distinct` instead. - - See #9683. - - *Yves Senn* - -* Allow single table inheritance instantiation to work when storing - demodulized class names. - - *Alex Robbin* - -* Correctly pass MySQL options when using `structure_dump` or - `structure_load`. - - Specifically, it fixes an issue when using SSL authentication. - - *Alex Coomans* - -* Correctly dump `:options` on `create_table` for MySQL. +* Deprecate `sanitize_conditions`. Use `sanitize_sql` instead. *Ryuta Kamizono* -* PostgreSQL: `:collation` support for string and text columns. - - Example: - - create_table :foos do |t| - t.string :string_en, collation: 'en_US.UTF-8' - t.text :text_ja, collation: 'ja_JP.UTF-8' - end - - *Ryuta Kamizono* - -* Remove `ActiveRecord::Serialization::XmlSerializer` from core. - - *Zachary Scott* - -* Make `unscope` aware of "less than" and "greater than" conditions. - - *TAKAHASHI Kazuaki* +* Doing count on relations that contain LEFT OUTER JOIN Arel node no longer + force a DISTINCT. This solves issues when using count after a left_joins. -* `find_by` and `find_by!` raise `ArgumentError` when called without - arguments. + *Maxime Handfield Lapointe* - *Kohei Suzuki* +* RecordNotFound raised by association.find exposes `id`, `primary_key` and + `model` methods to be consistent with RecordNotFound raised by Record.find. -* Revert behavior of `db:schema:load` back to loading the full - environment. This ensures that initializers are run. + *Michel Pigassou* - Fixes #19545. +* Hashes can once again be passed to setters of `composed_of`, if all of the + mapping methods are methods implemented on `Hash`. - *Yves Senn* - -* Fix missing index when using `timestamps` with the `index` option. - - The `index` option used with `timestamps` should be passed to both - `column` definitions for `created_at` and `updated_at` rather than just - the first. - - *Paul Mucur* - -* Rename `:class` to `:anonymous_class` in association options. - - Fixes #19659. - - *Andrew White* - -* Autosave existing records on a has many through association when the parent - is new. - - Fixes #19782. - - *Sean Griffin* - -* Fixed a bug where uniqueness validations would error on out of range values, - even if an validation should have prevented it from hitting the database. - - *Andrey Voronkov* - -* MySQL: `:charset` and `:collation` support for string and text columns. - - Example: - - create_table :foos do |t| - t.string :string_utf8_bin, charset: 'utf8', collation: 'utf8_bin' - t.text :text_ascii, charset: 'ascii' - end - - *Ryuta Kamizono* - -* Foreign key related methods in the migration DSL respect - `ActiveRecord::Base.pluralize_table_names = false`. - - Fixes #19643. - - *Mehmet Emin İNAÇ* - -* Reduce memory usage from loading types on PostgreSQL. - - Fixes #19578. + Fixes #25978. *Sean Griffin* -* Add `config.active_record.warn_on_records_fetched_greater_than` option. - - When set to an integer, a warning will be logged whenever a result set - larger than the specified size is returned by a query. - - Fixes #16463. - - *Jason Nochlin* - -* Ignore `.psqlrc` when loading database structure. - - *Jason Weathered* - -* Fix referencing wrong table aliases while joining tables of has many through - association (only when calling calculation methods). - - Fixes #19276. - - *pinglamb* - -* Correctly persist a serialized attribute that has been returned to - its default value by an in-place modification. - - Fixes #19467. - - *Matthew Draper* - -* Fix generating the schema file when using PostgreSQL `BigInt[]` data type. - Previously the `limit: 8` was not coming through, and this caused it to - become `Int[]` data type after rebuilding from the schema. - - Fixes #19420. - - *Jake Waller* - -* Reuse the `CollectionAssociation#reader` cache when the foreign key is - available prior to save. - - *Ben Woosley* - -* Add `config.active_record.dump_schemas` to fix `db:structure:dump` - when using schema_search_path and PostgreSQL extensions. - - Fixes #17157. - - *Ryan Wallace* - -* Renaming `use_transactional_fixtures` to `use_transactional_tests` for clarity. +* Fix the SELECT statement in `#table_comment` for MySQL. - Fixes #18864. + *Takeshi Akima* - *Brandon Weiss* - -* Increase pg gem version requirement to `~> 0.18`. Earlier versions of the - pg gem are known to have problems with Ruby 2.2. - - *Matt Brictson* - -* Correctly dump `serial` and `bigserial`. - - *Ryuta Kamizono* - -* Fix default `format` value in `ActiveRecord::Tasks::DatabaseTasks#schema_file`. - - *James Cox* - -* Don't enroll records in the transaction if they don't have commit callbacks. - This was causing a memory leak when creating many records inside a transaction. - - Fixes #15549. - - *Will Bryant*, *Aaron Patterson* - -* Correctly create through records when created on a has many through - association when using `where`. - - Fixes #19073. +* Virtual attributes will no longer raise when read on models loaded from the + database *Sean Griffin* -* Add `SchemaMigration.create_table` support for any unicode charsets with MySQL. +* Support calling the method `merge` in `scope`'s lambda. - *Ryuta Kamizono* - -* PostgreSQL no longer disables user triggers if system triggers can't be - disabled. Disabling user triggers does not fulfill what the method promises. - Rails currently requires superuser privileges for this method. - - If you absolutely rely on this behavior, consider patching - `disable_referential_integrity`. + *Yasuhiro Sugino* - *Yves Senn* +* Fixes multi-parameter attributes conversion with invalid params. -* Restore aborted transaction state when `disable_referential_integrity` fails - due to missing permissions. + *Hiroyuki Ishii* - *Toby Ovod-Everett*, *Yves Senn* +* Add newline between each migration in `structure.sql`. -* In PostgreSQL, print a warning message if `disable_referential_integrity` - fails due to missing permissions. + Keeps schema migration inserts as a single commit, but allows for easier + git diffing. - *Andrey Nering*, *Yves Senn* + Fixes #25504. -* Allow a `:limit` option for MySQL bigint primary key support. + *Grey Baker*, *Norberto Lopes* - Example: +* The flag `error_on_ignored_order_or_limit` has been deprecated in favor of + the current `error_on_ignored_order`. - create_table :foos, id: :primary_key, limit: 8 do |t| - end + *Xavier Noria* - # or +* Batch processing methods support `limit`: - create_table :foos, id: false do |t| - t.primary_key :id, limit: 8 + Post.limit(10_000).find_each do |post| + # ... end - *Ryuta Kamizono* - -* `belongs_to` will now trigger a validation error by default if the association is not present. - You can turn this off on a per-association basis with `optional: true`. - (Note this new default only applies to new Rails apps that will be generated with - `config.active_record.belongs_to_required_by_default = true` in initializer.) - - *Josef Šimánek* - -* Fixed `ActiveRecord::Relation#becomes!` and `changed_attributes` issues for type - columns. - - Fixes #17139. - - *Miklos Fazekas* - -* Format the time string according to the precision of the time column. - - *Ryuta Kamizono* - -* Allow a `:precision` option for time type columns. - - *Ryuta Kamizono* - -* Add `ActiveRecord::Base.suppress` to prevent the receiver from being saved - during the given block. - - For example, here's a pattern of creating notifications when new comments - are posted. (The notification may in turn trigger an email, a push - notification, or just appear in the UI somewhere): - - class Comment < ActiveRecord::Base - belongs_to :commentable, polymorphic: true - after_create -> { Notification.create! comment: self, - recipients: commentable.recipients } - end - - That's what you want the bulk of the time. A new comment creates a new - Notification. There may be edge cases where you don't want that, like - when copying a commentable and its comments, in which case write a - concern with something like this: - - module Copyable - def copy_to(destination) - Notification.suppress do - # Copy logic that creates new comments that we do not want triggering - # notifications. - end - end - end - - *Michael Ryan* - -* `:time` option added for `#touch`. - - Fixes #18905. - - *Hyonjee Joo* - -* Deprecate passing of `start` value to `find_in_batches` and `find_each` - in favour of `begin_at` value. - - *Vipul A M* - -* Add `foreign_key_exists?` method. - - *Tõnis Simo* - -* Use SQL COUNT and LIMIT 1 queries for `none?` and `one?` methods - if no block or limit is given, instead of loading the entire - collection into memory. This applies to relations (e.g. `User.all`) - as well as associations (e.g. `account.users`) - - # Before: - - users.none? - # SELECT "users".* FROM "users" - - users.one? - # SELECT "users".* FROM "users" - - # After: - - users.none? - # SELECT 1 AS one FROM "users" LIMIT 1 - - users.one? - # SELECT COUNT(*) FROM "users" - - *Eugene Gilburg* - -* Have `enum` perform type casting consistently with the rest of Active - Record, such as `where`. - - *Sean Griffin* - -* `scoping` no longer pollutes the current scope of sibling classes when using - STI. - - Fixes #18806. - - Example: - - StiOne.none.scoping do - StiTwo.all - end - - - *Sean Griffin* - -* `remove_reference` with `foreign_key: true` removes the foreign key before - removing the column. This fixes a bug where it was not possible to remove - the column on MySQL. - - Fixes #18664. - - *Yves Senn* - -* `find_in_batches` now accepts an `:finish` parameter that complements the `:start` - parameter to specify where to stop batch processing. - - *Vipul A M* - -* Fix a rounding problem for PostgreSQL timestamp columns. - - If a timestamp column has a precision specified, it needs to - format according to that. - - *Ryuta Kamizono* - -* Respect the database default charset for `schema_migrations` table. - - The charset of `version` column in `schema_migrations` table depends - on the database default charset and collation rather than the encoding - of the connection. - - *Ryuta Kamizono* - -* Raise `ArgumentError` when passing `nil` or `false` to `Relation#merge`. - - These are not valid values to merge in a relation, so it should warn users - early. - - *Rafael Mendonça França* - -* Use `SCHEMA` instead of `DB_STRUCTURE` for specifying a structure file. - - This makes the `db:structure` tasks consistent with `test:load_structure`. - - *Dieter Komendera* - -* Respect custom primary keys for associations when calling `Relation#where` - - Fixes #18813. - - *Sean Griffin* - -* Fix several edge cases which could result in a counter cache updating - twice or not updating at all for `has_many` and `has_many :through`. - - Fixes #10865. - - *Sean Griffin* - -* Foreign keys added by migrations were given random, generated names. This - meant a different `structure.sql` would be generated every time a developer - ran migrations on their machine. - - The generated part of foreign key names is now a hash of the table name and - column name, which is consistent every time you run the migration. - - *Chris Sinjakli* - -* Validation errors would be raised for parent records when an association - was saved when the parent had `validate: false`. It should not be the - responsibility of the model to validate an associated object unless the - object was created or modified by the parent. - - This fixes the issue by skipping validations if the parent record is - persisted, not changed, and not marked for destruction. - - Fixes #17621. - - *Eileen M. Uchitelle*, *Aaron Patterson* - -* Fix n+1 query problem when eager loading nil associations (fixes #18312) - - *Sammy Larbi* - -* Change the default error message from `can't be blank` to `must exist` for - the presence validator of the `:required` option on `belongs_to`/`has_one` - associations. - - *Henrik Nygren* - -* Fixed `ActiveRecord::Relation#group` method when an argument is an SQL - reserved keyword: + It also works in `find_in_batches` and `in_batches`. - Example: - - SplitTest.group(:key).count - Property.group(:value).count - - *Bogdan Gusiev* - -* Added the `#or` method on `ActiveRecord::Relation`, allowing use of the OR - operator to combine WHERE or HAVING clauses. - - Example: - - Post.where('id = 1').or(Post.where('id = 2')) - # => SELECT * FROM posts WHERE (id = 1) OR (id = 2) - - *Sean Griffin*, *Matthew Draper*, *Gael Muller*, *Olivier El Mekki* - -* Don't define autosave association callbacks twice from - `accepts_nested_attributes_for`. - - Fixes #18704. - - *Sean Griffin* + *Xavier Noria* -* Integer types will no longer raise a `RangeError` when assigning an - attribute, but will instead raise when going to the database. +* Using `group` with an attribute that has a custom type will properly cast + the hash keys after calling a calculation method like `count`. - Fixes several vague issues which were never reported directly. See the - commit message from the commit which added this line for some examples. + Fixes #25595. *Sean Griffin* -* Values which would error while being sent to the database (such as an - ASCII-8BIT string with invalid UTF-8 bytes on SQLite3), no longer error on - assignment. They will still error when sent to the database, but you are - given the ability to re-assign it to a valid value. +* Fix the generated `#to_param` method to use `omission: ''` so that + the resulting output is actually up to 20 characters, not + effectively 17 to leave room for the default "...". + Also call `#parameterize` before `#truncate` and make the + `separator: /-/` to maximize the information included in the + output. - Fixes #18580. + Fixes #23635. - *Sean Griffin* - -* Don't remove join dependencies in `Relation#exists?` - - Fixes #18632. - - *Sean Griffin* - -* Invalid values assigned to a JSON column are assumed to be `nil`. - - Fixes #18629. - - *Sean Griffin* - -* Add `ActiveRecord::Base#accessed_fields`, which can be used to quickly - discover which fields were read from a model when you are looking to only - select the data you need from the database. - - *Sean Griffin* - -* Introduce the `:if_exists` option for `drop_table`. - - Example: - - drop_table(:posts, if_exists: true) - - That would execute: - - DROP TABLE IF EXISTS posts - - If the table doesn't exist, `if_exists: false` (the default) raises an - exception whereas `if_exists: true` does nothing. - - *Cody Cutrer*, *Stefan Kanev*, *Ryuta Kamizono* - -* Don't run SQL if attribute value is not changed for update_attribute method. - - *Prathamesh Sonpatki* + *Rob Biedenharn* -* `time` columns can now get affected by `time_zone_aware_attributes`. If you have - set `config.time_zone` to a value other than `'UTC'`, they will be treated - as in that time zone by default in Rails 5.1. If this is not the desired - behavior, you can set +* Ensure concurrent invocations of the connection reaper cannot allocate the + same connection to two threads. - ActiveRecord::Base.time_zone_aware_types = [:datetime] + Fixes #25585. - A deprecation warning will be emitted if you have a `:time` column, and have - not explicitly opted out. - - Fixes #3145. - - *Sean Griffin* - -* Tests now run after_commit callbacks. You no longer have to declare - `uses_transaction ‘test name’` to test the results of an after_commit. - - after_commit callbacks run after committing a transaction whose parent - is not `joinable?`: un-nested transactions, transactions within test cases, - and transactions in `console --sandbox`. - - *arthurnn*, *Ravil Bayramgalin*, *Matthew Draper* - -* `nil` as a value for a binary column in a query no longer logs as - "<NULL binary data>", and instead logs as just "nil". + *Matthew Draper* - *Sean Griffin* +* Inspecting an object with an associated array of over 10 elements no longer + truncates the array, preventing `inspect` from looping infinitely in some + cases. -* `attribute_will_change!` will no longer cause non-persistable attributes to - be sent to the database. + *Kevin McPhillips* - Fixes #18407. +* Removed the unused methods `ActiveRecord::Base.connection_id` and + `ActiveRecord::Base.connection_id=`. *Sean Griffin* -* Remove support for the `protected_attributes` gem. - - *Carlos Antonio da Silva*, *Roberto Miranda* - -* Fix accessing of fixtures having non-string labels like Fixnum. - - *Prathamesh Sonpatki* - -* Remove deprecated support to preload instance-dependent associations. - - *Yves Senn* - -* Remove deprecated support for PostgreSQL ranges with exclusive lower bounds. - - *Yves Senn* - -* Remove deprecation when modifying a relation with cached Arel. - This raises an `ImmutableRelation` error instead. - - *Yves Senn* - -* Added `ActiveRecord::SecureToken` in order to encapsulate generation of - unique tokens for attributes in a model using `SecureRandom`. - - *Roberto Miranda* - -* Change the behavior of boolean columns to be closer to Ruby's semantics. - - Before this change we had a small set of "truthy", and all others are "falsy". - - Now, we have a small set of "falsy" values and all others are "truthy" matching - Ruby's semantics. - - *Rafael Mendonça França* - -* Deprecate `ActiveRecord::Base.errors_in_transactional_callbacks=`. - - *Rafael Mendonça França* - -* Change transaction callbacks to not swallow errors. - - Before this change any errors raised inside a transaction callback - were getting rescued and printed in the logs. - - Now these errors are not rescued anymore and just bubble up, as the other callbacks. - - *Rafael Mendonça França* - -* Remove deprecated `sanitize_sql_hash_for_conditions`. - - *Rafael Mendonça França* - -* Remove deprecated `Reflection#source_macro`. - - *Rafael Mendonça França* - -* Remove deprecated `symbolized_base_class` and `symbolized_sti_name`. - - *Rafael Mendonça França* - -* Remove deprecated `ActiveRecord::Base.disable_implicit_join_references=`. - - *Rafael Mendonça França* - -* Remove deprecated access to connection specification using a string accessor. - - Now all strings will be handled as a URL. - - *Rafael Mendonça França* - -* Change the default `null` value for `timestamps` to `false`. - - *Rafael Mendonça França* - -* Return an array of pools from `connection_pools`. - - *Rafael Mendonça França* +* Ensure hashes can be assigned to attributes created using `composed_of`. -* Return a null column from `column_for_attribute` when no column exists. - - *Rafael Mendonça França* - -* Remove deprecated `serialized_attributes`. - - *Rafael Mendonça França* - -* Remove deprecated automatic counter caches on `has_many :through`. - - *Rafael Mendonça França* - -* Change the way in which callback chains can be halted. - - The preferred method to halt a callback chain from now on is to explicitly - `throw(:abort)`. - In the past, returning `false` in an Active Record `before_` callback had the - side effect of halting the callback chain. - This is not recommended anymore and, depending on the value of the - `ActiveSupport.halt_callback_chains_on_return_false` option, will - either not work at all or display a deprecation warning. - - *claudiob* - -* Clear query cache on rollback. - - *Florian Weingarten* - -* Fix setting of foreign_key for through associations when building a new record. - - Fixes #12698. - - *Ivan Antropov* - -* Improve dumping of the primary key. If it is not a default primary key, - correctly dump the type and options. - - Fixes #14169, #16599. - - *Ryuta Kamizono* - -* Format the datetime string according to the precision of the datetime field. - - Incompatible to rounding behavior between MySQL 5.6 and earlier. - - In 5.5, when you insert `2014-08-17 12:30:00.999999` the fractional part - is ignored. In 5.6, it's rounded to `2014-08-17 12:30:01`: - - http://bugs.mysql.com/bug.php?id=68760 - - *Ryuta Kamizono* - -* Allow a precision option for MySQL datetimes. - - *Ryuta Kamizono* - -* Fixed automatic `inverse_of` for models nested in a module. - - *Andrew McCloud* - -* Change `ActiveRecord::Relation#update` behavior so that it can - be called without passing ids of the records to be updated. - - This change allows updating multiple records returned by - `ActiveRecord::Relation` with callbacks and validations. - - # Before - # ArgumentError: wrong number of arguments (1 for 2) - Comment.where(group: 'expert').update(body: "Group of Rails Experts") - - # After - # Comments with group expert updated with body "Group of Rails Experts" - Comment.where(group: 'expert').update(body: "Group of Rails Experts") - - *Prathamesh Sonpatki* - -* Fix `reaping_frequency` option when the value is a string. - - This usually happens when it is configured using `DATABASE_URL`. - - *korbin* - -* Fix error message when trying to create an associated record and the foreign - key is missing. - - Before this fix the following exception was being raised: - - NoMethodError: undefined method `val' for #<Arel::Nodes::BindParam:0x007fc64d19c218> - - Now the message is: - - ActiveRecord::UnknownAttributeError: unknown attribute 'foreign_key' for Model. - - *Rafael Mendonça França* - -* Fix change detection problem for PostgreSQL bytea type and - `ArgumentError: string contains null byte` exception with pg-0.18. - - Fixes #17680. - - *Lars Kanis* - -* When a table has a composite primary key, the `primary_key` method for - SQLite3 and PostgreSQL adapters was only returning the first field of the key. - Ensures that it will return nil instead, as Active Record doesn't support - composite primary keys. - - Fixes #18070. - - *arthurnn* - -* `validates_size_of` / `validates_length_of` do not count records - which are `marked_for_destruction?`. - - Fixes #7247. - - *Yves Senn* - -* Ensure `first!` and friends work on loaded associations. - - Fixes #18237. + Fixes #25210. *Sean Griffin* -* `eager_load` preserves readonly flag for associations. - - Fixes #15853. - - *Takashi Kokubun* +* Fix logging edge case where if an attribute was of the binary type and + was provided as a Hash. -* Provide `:touch` option to `save()` to accommodate saving without updating - timestamps. + *Jon Moss* - Fixes #18202. +* Handle JSON deserialization correctly if the column default from database + adapter returns `''` instead of `nil`. - *Dan Olson* + *Johannes Opper* -* Provide a more helpful error message when an unsupported class is passed to - `serialize`. +* Introduce `ActiveRecord::TransactionSerializationError` for catching + transaction serialization failures or deadlocks. - Fixes #18224. + *Erol Fornoles* - *Sean Griffin* +* PostgreSQL: Fix `db:structure:load` silent failure on SQL error. -* Add bigint primary key support for MySQL. + The command line flag `-v ON_ERROR_STOP=1` should be used + when invoking `psql` to make sure errors are not suppressed. Example: - create_table :foos, id: :bigint do |t| - end - - *Ryuta Kamizono* - -* Support for any type of primary key. - - Fixes #14194. - - *Ryuta Kamizono* - -* Dump the default `nil` for PostgreSQL UUID primary key. - - *Ryuta Kamizono* - -* Add a `:foreign_key` option to `references` and associated migration - methods. The model and migration generators now use this option, rather than - the `add_foreign_key` form. - - *Sean Griffin* - -* Don't raise when writing an attribute with an out-of-range datetime passed - by the user. - - *Grey Baker* - -* Replace deprecated `ActiveRecord::Tasks::DatabaseTasks#load_schema` with - `ActiveRecord::Tasks::DatabaseTasks#load_schema_for`. - - *Yves Senn* - -* Fix bug with `ActiveRecord::Type::Numeric` that caused negative values to - be marked as having changed when set to the same negative value. - - Fixes #18161. - - *Daniel Fox* - -* Introduce `force: :cascade` option for `create_table`. Using this option - will recreate tables even if they have dependent objects (like foreign keys). - `db/schema.rb` now uses `force: :cascade`. This makes it possible to - reload the schema when foreign keys are in place. - - *Matthew Draper*, *Yves Senn* - -* `db:schema:load` and `db:structure:load` no longer purge the database - before loading the schema. This is left for the user to do. - `db:test:prepare` will still purge the database. - - Fixes #17945. - - *Yves Senn* - -* Fix undesirable RangeError by `Type::Integer`. Add `Type::UnsignedInteger`. - - *Ryuta Kamizono* - -* Add `foreign_type` option to `has_one` and `has_many` association macros. - - This option enables to define the column name of associated object's type for polymorphic associations. - - *Ulisses Almeida*, *Kassio Borges* - -* Remove deprecated behavior allowing nested arrays to be passed as query - values. - - *Melanie Gilman* - -* Deprecate passing a class as a value in a query. Users should pass strings - instead. - - *Melanie Gilman* - -* `add_timestamps` and `remove_timestamps` now properly reversible with - options. + psql -v ON_ERROR_STOP=1 -q -f awesome-file.sql my-app-db - *Noam Gagliardi-Rabinovich* + Fixes #23818. -* `ActiveRecord::ConnectionAdapters::ColumnDumper#column_spec` and - `ActiveRecord::ConnectionAdapters::ColumnDumper#prepare_column_options` no - longer have a `types` argument. They should access - `connection#native_database_types` directly. + *Ralin Chimev* - *Yves Senn* -Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activerecord/CHANGELOG.md) for previous changes. +Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/activerecord/CHANGELOG.md) for previous changes. diff --git a/activerecord/RUNNING_UNIT_TESTS.rdoc b/activerecord/RUNNING_UNIT_TESTS.rdoc index a74fcf2df7..cd22f76d01 100644 --- a/activerecord/RUNNING_UNIT_TESTS.rdoc +++ b/activerecord/RUNNING_UNIT_TESTS.rdoc @@ -7,11 +7,11 @@ http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#setting-up- To run a specific test: - $ ruby -Itest test/cases/base_test.rb -n method_name + $ bundle exec ruby -Itest test/cases/base_test.rb -n method_name To run a set of tests: - $ ruby -Itest test/cases/base_test.rb + $ bundle exec ruby -Itest test/cases/base_test.rb You can also run tests that depend upon a specific database backend. For example: @@ -41,7 +41,7 @@ parameters in +test/config.example.yml+ are. You can override the +connections:+ parameter in either file using the +ARCONN+ (Active Record CONNection) environment variable: - $ ARCONN=postgresql ruby -Itest test/cases/base_test.rb + $ ARCONN=postgresql bundle exec ruby -Itest test/cases/base_test.rb You can specify a custom location for the config file using the +ARCONFIG+ environment variable. diff --git a/activerecord/Rakefile b/activerecord/Rakefile index 46df733cfe..e077d345d6 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -1,4 +1,4 @@ -require 'rake/testtask' +require "rake/testtask" require File.expand_path(File.dirname(__FILE__)) + "/test/config" require File.expand_path(File.dirname(__FILE__)) + "/test/support/config" @@ -17,13 +17,12 @@ def run_without_aborting(*tasks) abort "Errors running #{errors.join(', ')}" if errors.any? end -desc 'Run mysql2, sqlite, and postgresql tests by default' -task :default => :test +desc "Run mysql2, sqlite, and postgresql tests by default" +task default: :test task :package -task "package:clean" -desc 'Run mysql2, sqlite, and postgresql tests' +desc "Run mysql2, sqlite, and postgresql tests" task :test do tasks = defined?(JRUBY_VERSION) ? %w(test_jdbcmysql test_jdbcsqlite3 test_jdbcpostgresql) : @@ -40,19 +39,19 @@ namespace :test do end end -desc 'Build MySQL and PostgreSQL test databases' +desc "Build MySQL and PostgreSQL test databases" namespace :db do - task :create => ['db:mysql:build', 'db:postgresql:build'] - task :drop => ['db:mysql:drop', 'db:postgresql:drop'] + task create: ["db:mysql:build", "db:postgresql:build"] + task drop: ["db:mysql:drop", "db:postgresql:drop"] end %w( mysql2 postgresql sqlite3 sqlite3_mem db2 oracle jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter| namespace :test do Rake::TestTask.new(adapter => "#{adapter}:env") { |t| - adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/] - t.libs << 'test' + adapter_short = adapter == "db2" ? adapter : adapter[/^[a-z0-9]+/] + t.libs << "test" t.test_files = (Dir.glob( "test/cases/**/*_test.rb" ).reject { - |x| x =~ /\/adapters\// + |x| x.include?("/adapters/") } + Dir.glob("test/cases/adapters/#{adapter_short}/**/*_test.rb")) t.warning = true @@ -62,23 +61,23 @@ end namespace :isolated do task adapter => "#{adapter}:env" do - adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/] + adapter_short = adapter == "db2" ? adapter : adapter[/^[a-z0-9]+/] puts [adapter, adapter_short].inspect (Dir["test/cases/**/*_test.rb"].reject { - |x| x =~ /\/adapters\// + |x| x.include?("/adapters/") } + Dir["test/cases/adapters/#{adapter_short}/**/*_test.rb"]).all? do |file| - sh(Gem.ruby, '-w' ,"-Itest", file) - end or raise "Failures" + sh(Gem.ruby, "-w" ,"-Itest", file) + end || raise("Failures") end end end namespace adapter do - task :test => "test_#{adapter}" - task :isolated_test => "isolated_test_#{adapter}" + task test: "test_#{adapter}" + task isolated_test: "isolated_test_#{adapter}" # Set the connection environment for the adapter - task(:env) { ENV['ARCONN'] = adapter } + task(:env) { ENV["ARCONN"] = adapter } end # Make sure the adapter test evaluates the env setting task @@ -88,30 +87,30 @@ end namespace :db do namespace :mysql do - desc 'Build the MySQL test databases' + desc "Build the MySQL test databases" task :build do - config = ARTest.config['connections']['mysql2'] - %x( mysql --user=#{config['arunit']['username']} --password=#{config['arunit']['password']} -e "create DATABASE #{config['arunit']['database']} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") - %x( mysql --user=#{config['arunit2']['username']} --password=#{config['arunit2']['password']} -e "create DATABASE #{config['arunit2']['database']} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") + config = ARTest.config["connections"]["mysql2"] + %x( mysql --user=#{config["arunit"]["username"]} --password=#{config["arunit"]["password"]} -e "create DATABASE #{config["arunit"]["database"]} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") + %x( mysql --user=#{config["arunit2"]["username"]} --password=#{config["arunit2"]["password"]} -e "create DATABASE #{config["arunit2"]["database"]} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") end - desc 'Drop the MySQL test databases' + desc "Drop the MySQL test databases" task :drop do - config = ARTest.config['connections']['mysql2'] - %x( mysqladmin --user=#{config['arunit']['username']} --password=#{config['arunit']['password']} -f drop #{config['arunit']['database']} ) - %x( mysqladmin --user=#{config['arunit2']['username']} --password=#{config['arunit2']['password']} -f drop #{config['arunit2']['database']} ) + config = ARTest.config["connections"]["mysql2"] + %x( mysqladmin --user=#{config["arunit"]["username"]} --password=#{config["arunit"]["password"]} -f drop #{config["arunit"]["database"]} ) + %x( mysqladmin --user=#{config["arunit2"]["username"]} --password=#{config["arunit2"]["password"]} -f drop #{config["arunit2"]["database"]} ) end - desc 'Rebuild the MySQL test databases' - task :rebuild => [:drop, :build] + desc "Rebuild the MySQL test databases" + task rebuild: [:drop, :build] end namespace :postgresql do - desc 'Build the PostgreSQL test databases' + desc "Build the PostgreSQL test databases" task :build do - config = ARTest.config['connections']['postgresql'] - %x( createdb -E UTF8 -T template0 #{config['arunit']['database']} ) - %x( createdb -E UTF8 -T template0 #{config['arunit2']['database']} ) + 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" @@ -119,28 +118,28 @@ namespace :db do end end - desc 'Drop the PostgreSQL test databases' + desc "Drop the PostgreSQL test databases" task :drop do - config = ARTest.config['connections']['postgresql'] - %x( dropdb #{config['arunit']['database']} ) - %x( dropdb #{config['arunit2']['database']} ) + config = ARTest.config["connections"]["postgresql"] + %x( dropdb #{config["arunit"]["database"]} ) + %x( dropdb #{config["arunit2"]["database"]} ) end - desc 'Rebuild the PostgreSQL test databases' - task :rebuild => [:drop, :build] + desc "Rebuild the PostgreSQL test databases" + task rebuild: [:drop, :build] end end -task :build_mysql_databases => 'db:mysql:build' -task :drop_mysql_databases => 'db:mysql:drop' -task :rebuild_mysql_databases => 'db:mysql:rebuild' +task build_mysql_databases: "db:mysql:build" +task drop_mysql_databases: "db:mysql:drop" +task rebuild_mysql_databases: "db:mysql:rebuild" -task :build_postgresql_databases => 'db:postgresql:build' -task :drop_postgresql_databases => 'db:postgresql:drop' -task :rebuild_postgresql_databases => 'db:postgresql:rebuild' +task build_postgresql_databases: "db:postgresql:build" +task drop_postgresql_databases: "db:postgresql:drop" +task rebuild_postgresql_databases: "db:postgresql:rebuild" task :lines do - load File.expand_path('..', File.dirname(__FILE__)) + '/tools/line_statistics' + load File.expand_path("..", File.dirname(__FILE__)) + "/tools/line_statistics" files = FileList["lib/active_record/**/*.rb"] CodeTools::LineStatistics.new(files).print_loc end diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index 4405da2812..2cd8f179dd 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -1,28 +1,28 @@ -version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip +version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY - s.name = 'activerecord' + s.name = "activerecord" s.version = version - s.summary = 'Object-relational mapper framework (part of Rails).' - s.description = 'Databases on Rails. Build a persistent domain model by mapping database tables to Ruby classes. Strong conventions for associations, validations, aggregations, migrations, and testing come baked-in.' + s.summary = "Object-relational mapper framework (part of Rails)." + s.description = "Databases on Rails. Build a persistent domain model by mapping database tables to Ruby classes. Strong conventions for associations, validations, aggregations, migrations, and testing come baked-in." - s.required_ruby_version = '>= 2.2.2' + s.required_ruby_version = ">= 2.2.2" - s.license = 'MIT' + s.license = "MIT" - s.author = 'David Heinemeier Hansson' - s.email = 'david@loudthinking.com' - s.homepage = 'http://www.rubyonrails.org' + s.author = "David Heinemeier Hansson" + s.email = "david@loudthinking.com" + s.homepage = "http://rubyonrails.org" - s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'examples/**/*', 'lib/**/*'] - s.require_path = 'lib' + s.files = Dir["CHANGELOG.md", "MIT-LICENSE", "README.rdoc", "examples/**/*", "lib/**/*"] + s.require_path = "lib" s.extra_rdoc_files = %w(README.rdoc) - s.rdoc_options.concat ['--main', 'README.rdoc'] + s.rdoc_options.concat ["--main", "README.rdoc"] - s.add_dependency 'activesupport', version - s.add_dependency 'activemodel', version + s.add_dependency "activesupport", version + s.add_dependency "activemodel", version - s.add_dependency 'arel', '~> 7.0' + s.add_dependency "arel", "~> 7.0" end diff --git a/activerecord/bin/test b/activerecord/bin/test index 7417b068bf..23add35d45 100755 --- a/activerecord/bin/test +++ b/activerecord/bin/test @@ -1,6 +1,8 @@ #!/usr/bin/env ruby -COMPONENT_ROOT = File.expand_path("../../", __FILE__) + +COMPONENT_ROOT = File.expand_path("..", __dir__) require File.expand_path("../tools/test", COMPONENT_ROOT) + module Minitest def self.plugin_active_record_options(opts, options) opts.separator "" @@ -14,6 +16,6 @@ module Minitest end end -Minitest.extensions.unshift 'active_record' +Minitest.extensions.unshift "active_record" exit Minitest.run(ARGV) diff --git a/activerecord/examples/performance.rb b/activerecord/examples/performance.rb index a5a1f284a0..f2fe8875b9 100644 --- a/activerecord/examples/performance.rb +++ b/activerecord/examples/performance.rb @@ -1,11 +1,10 @@ -require File.expand_path('../../../load_paths', __FILE__) require "active_record" -require 'benchmark/ips' +require "benchmark/ips" -TIME = (ENV['BENCHMARK_TIME'] || 20).to_i -RECORDS = (ENV['BENCHMARK_RECORDS'] || TIME*1000).to_i +TIME = (ENV["BENCHMARK_TIME"] || 20).to_i +RECORDS = (ENV["BENCHMARK_RECORDS"] || TIME*1000).to_i -conn = { adapter: 'sqlite3', database: ':memory:' } +conn = { adapter: "sqlite3", database: ":memory:" } ActiveRecord::Base.establish_connection(conn) @@ -45,24 +44,24 @@ end def progress_bar(int); print "." if (int%100).zero? ; end -puts 'Generating data...' +puts "Generating data..." module ActiveRecord class Faker - LOREM = %Q{Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse non aliquet diam. Curabitur vel urna metus, quis malesuada elit. + LOREM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse non aliquet diam. Curabitur vel urna metus, quis malesuada elit. Integer consequat tincidunt felis. Etiam non erat dolor. Vivamus imperdiet nibh sit amet diam eleifend id posuere diam malesuada. Mauris at accumsan sem. Donec id lorem neque. Fusce erat lorem, ornare eu congue vitae, malesuada quis neque. Maecenas vel urna a velit pretium fermentum. Donec tortor enim, tempor venenatis egestas a, tempor sed ipsum. Ut arcu justo, faucibus non imperdiet ac, interdum at diam. Pellentesque ipsum enim, venenatis ut iaculis vitae, varius vitae sem. Sed rutrum quam ac elit euismod bibendum. Donec ultricies ultricies magna, at lacinia libero mollis aliquam. Sed ac arcu in tortor elementum tincidunt vel interdum sem. Curabitur eget erat arcu. Praesent eget eros leo. Nam magna enim, sollicitudin vehicula scelerisque in, vulputate ut libero. - Praesent varius tincidunt commodo}.split + Praesent varius tincidunt commodo".split def self.name - LOREM.grep(/^\w*$/).sort_by { rand }.first(2).join ' ' + LOREM.grep(/^\w*$/).sort_by { rand }.first(2).join " " end def self.email - LOREM.grep(/^\w*$/).sort_by { rand }.first(2).join('@') + ".com" + LOREM.grep(/^\w*$/).sort_by { rand }.first(2).join("@") + ".com" end end end @@ -73,7 +72,7 @@ end # Using the same paragraph for all exhibits because it is very slow # to generate unique paragraphs for all exhibits. -notes = ActiveRecord::Faker::LOREM.join ' ' +notes = ActiveRecord::Faker::LOREM.join " " today = Date.today puts "Inserting #{RECORDS} users and exhibits..." @@ -96,9 +95,9 @@ puts "Done!\n" Benchmark.ips(TIME) do |x| ar_obj = Exhibit.find(1) - attrs = { name: 'sam' } - attrs_first = { name: 'sam' } - attrs_second = { name: 'tom' } + attrs = { name: "sam" } + attrs_first = { name: "sam" } + attrs_second = { name: "tom" } exhibit = { name: ActiveRecord::Faker.name, notes: notes, @@ -109,22 +108,22 @@ Benchmark.ips(TIME) do |x| ar_obj.id end - x.report 'Model.new (instantiation)' do + x.report "Model.new (instantiation)" do Exhibit.new end - x.report 'Model.new (setting attributes)' do + x.report "Model.new (setting attributes)" do Exhibit.new(attrs) end - x.report 'Model.first' do + x.report "Model.first" do Exhibit.first.look end - x.report 'Model.take' do + x.report "Model.take" do Exhibit.take end - + x.report("Model.all limit(100)") do Exhibit.look Exhibit.limit(100) end @@ -141,36 +140,36 @@ Benchmark.ips(TIME) do |x| Exhibit.look Exhibit.limit(10000) end - x.report 'Model.named_scope' do + x.report "Model.named_scope" do Exhibit.limit(10).with_name.with_notes end - x.report 'Model.create' do + x.report "Model.create" do Exhibit.create(exhibit) end - x.report 'Resource#attributes=' do + x.report "Resource#attributes=" do e = Exhibit.new(attrs_first) e.attributes = attrs_second end - x.report 'Resource#update' do - Exhibit.first.update(name: 'bob') + x.report "Resource#update" do + Exhibit.first.update(name: "bob") end - x.report 'Resource#destroy' do + x.report "Resource#destroy" do Exhibit.first.destroy end - x.report 'Model.transaction' do + x.report "Model.transaction" do Exhibit.transaction { Exhibit.new } end - x.report 'Model.find(id)' do + x.report "Model.find(id)" do User.find(1) end - x.report 'Model.find_by_sql' do + x.report "Model.find_by_sql" do Exhibit.find_by_sql("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}").first end diff --git a/activerecord/examples/simple.rb b/activerecord/examples/simple.rb index 4ed5d80eb2..c3648fee48 100644 --- a/activerecord/examples/simple.rb +++ b/activerecord/examples/simple.rb @@ -1,14 +1,13 @@ -require File.expand_path('../../../load_paths', __FILE__) -require 'active_record' +require "active_record" class Person < ActiveRecord::Base - establish_connection adapter: 'sqlite3', database: 'foobar.db' + establish_connection adapter: "sqlite3", database: "foobar.db" connection.create_table table_name, force: true do |t| t.string :name end end -bob = Person.create!(name: 'bob') +bob = Person.create!(name: "bob") puts Person.all.inspect bob.destroy puts Person.all.inspect diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index ab3846ae65..3b5dab16cb 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -21,13 +21,13 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ -require 'active_support' -require 'active_support/rails' -require 'active_model' -require 'arel' +require "active_support" +require "active_support/rails" +require "active_model" +require "arel" -require 'active_record/version' -require 'active_record/attribute_set' +require "active_record/version" +require "active_record/attribute_set" module ActiveRecord extend ActiveSupport::Autoload @@ -46,7 +46,7 @@ module ActiveRecord autoload :Integration autoload :LegacyYamlAdapter autoload :Migration - autoload :Migrator, 'active_record/migration' + autoload :Migrator, "active_record/migration" autoload :ModelSchema autoload :NestedAttributes autoload :NoTouching @@ -56,7 +56,7 @@ module ActiveRecord autoload :Querying autoload :CollectionCacheKey autoload :ReadonlyAttributes - autoload :RecordInvalid, 'active_record/validations' + autoload :RecordInvalid, "active_record/validations" autoload :Reflection autoload :RuntimeRegistry autoload :Sanitization @@ -68,7 +68,6 @@ module ActiveRecord autoload :StatementCache autoload :Store autoload :Suppressor - autoload :TableMetadata autoload :Timestamp autoload :Transactions autoload :Translation @@ -76,9 +75,9 @@ module ActiveRecord autoload :SecureToken eager_autoload do - autoload :ActiveRecordError, 'active_record/errors' - autoload :ConnectionNotEstablished, 'active_record/errors' - autoload :ConnectionAdapters, 'active_record/connection_adapters/abstract_adapter' + autoload :ActiveRecordError, "active_record/errors" + autoload :ConnectionNotEstablished, "active_record/errors" + autoload :ConnectionAdapters, "active_record/connection_adapters/abstract_adapter" autoload :Aggregations autoload :Associations @@ -90,7 +89,7 @@ module ActiveRecord autoload :AssociationRelation autoload :NullRelation - autoload_under 'relation' do + autoload_under "relation" do autoload :QueryMethods autoload :FinderMethods autoload :Calculations @@ -101,11 +100,12 @@ module ActiveRecord end autoload :Result + autoload :TableMetadata end module Coders - autoload :YAMLColumn, 'active_record/coders/yaml_column' - autoload :JSON, 'active_record/coders/json' + autoload :YAMLColumn, "active_record/coders/yaml_column" + autoload :JSON, "active_record/coders/json" end module AttributeMethods @@ -137,7 +137,6 @@ module ActiveRecord eager_autoload do autoload :AbstractAdapter - autoload :ConnectionManagement, "active_record/connection_adapters/abstract/connection_pool" end end @@ -154,13 +153,13 @@ module ActiveRecord extend ActiveSupport::Autoload autoload :DatabaseTasks - autoload :SQLiteDatabaseTasks, 'active_record/tasks/sqlite_database_tasks' - autoload :MySQLDatabaseTasks, 'active_record/tasks/mysql_database_tasks' + autoload :SQLiteDatabaseTasks, "active_record/tasks/sqlite_database_tasks" + autoload :MySQLDatabaseTasks, "active_record/tasks/mysql_database_tasks" autoload :PostgreSQLDatabaseTasks, - 'active_record/tasks/postgresql_database_tasks' + "active_record/tasks/postgresql_database_tasks" end - autoload :TestFixtures, 'active_record/fixtures' + autoload :TestFixtures, "active_record/fixtures" def self.eager_load! super @@ -177,5 +176,5 @@ ActiveSupport.on_load(:active_record) do end ActiveSupport.on_load(:i18n) do - I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml' + I18n.load_path << File.dirname(__FILE__) + "/active_record/locale/en.yml" end diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index 3ff41ed81b..5ca8fe576e 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -24,256 +24,259 @@ module ActiveRecord super end - # Active Record implements aggregation through a macro-like class method called #composed_of - # for representing attributes as value objects. It expresses relationships like "Account [is] - # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call - # to the macro adds a description of how the value objects are created from the attributes of - # the entity object (when the entity is initialized either as a new object or from finding an - # existing object) and how it can be turned back into attributes (when the entity is saved to - # the database). - # - # class Customer < ActiveRecord::Base - # composed_of :balance, class_name: "Money", mapping: %w(amount currency) - # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ] - # end - # - # The customer class now has the following methods to manipulate the value objects: - # * <tt>Customer#balance, Customer#balance=(money)</tt> - # * <tt>Customer#address, Customer#address=(address)</tt> - # - # These methods will operate with value objects like the ones described below: - # - # class Money - # include Comparable - # attr_reader :amount, :currency - # EXCHANGE_RATES = { "USD_TO_DKK" => 6 } - # - # def initialize(amount, currency = "USD") - # @amount, @currency = amount, currency - # end - # - # def exchange_to(other_currency) - # exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor - # Money.new(exchanged_amount, other_currency) - # end - # - # def ==(other_money) - # amount == other_money.amount && currency == other_money.currency - # end - # - # def <=>(other_money) - # if currency == other_money.currency - # amount <=> other_money.amount - # else - # amount <=> other_money.exchange_to(currency).amount - # end - # end - # end - # - # class Address - # attr_reader :street, :city - # def initialize(street, city) - # @street, @city = street, city - # end - # - # def close_to?(other_address) - # city == other_address.city - # end - # - # def ==(other_address) - # city == other_address.city && street == other_address.street - # end - # end - # - # Now it's possible to access attributes from the database through the value objects instead. If - # you choose to name the composition the same as the attribute's name, it will be the only way to - # access that attribute. That's the case with our +balance+ attribute. You interact with the value - # objects just like you would with any other attribute: - # - # customer.balance = Money.new(20) # sets the Money value object and the attribute - # customer.balance # => Money value object - # customer.balance.exchange_to("DKK") # => Money.new(120, "DKK") - # customer.balance > Money.new(10) # => true - # customer.balance == Money.new(20) # => true - # customer.balance < Money.new(5) # => false - # - # Value objects can also be composed of multiple attributes, such as the case of Address. The order - # of the mappings will determine the order of the parameters. - # - # customer.address_street = "Hyancintvej" - # customer.address_city = "Copenhagen" - # customer.address # => Address.new("Hyancintvej", "Copenhagen") - # - # customer.address = Address.new("May Street", "Chicago") - # customer.address_street # => "May Street" - # customer.address_city # => "Chicago" - # - # == Writing value objects - # - # Value objects are immutable and interchangeable objects that represent a given value, such as - # a Money object representing $5. Two Money objects both representing $5 should be equal (through - # methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking makes sense). This is - # unlike entity objects where equality is determined by identity. An entity class such as Customer can - # easily have two different objects that both have an address on Hyancintvej. Entity identity is - # determined by object or relational unique identifiers (such as primary keys). Normal - # ActiveRecord::Base classes are entity objects. - # - # It's also important to treat the value objects as immutable. Don't allow the Money object to have - # its amount changed after creation. Create a new Money object with the new value instead. The - # <tt>Money#exchange_to</tt> method is an example of this. It returns a new value object instead of changing - # its own values. Active Record won't persist value objects that have been changed through means - # other than the writer method. - # - # The immutable requirement is enforced by Active Record by freezing any object assigned as a value - # object. Attempting to change it afterwards will result in a +RuntimeError+. - # - # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not - # keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable - # - # == Custom constructors and converters - # - # By default value objects are initialized by calling the <tt>new</tt> constructor of the value - # class passing each of the mapped attributes, in the order specified by the <tt>:mapping</tt> - # option, as arguments. If the value class doesn't support this convention then #composed_of allows - # a custom constructor to be specified. - # - # When a new value is assigned to the value object, the default assumption is that the new value - # is an instance of the value class. Specifying a custom converter allows the new value to be automatically - # converted to an instance of value class if necessary. - # - # For example, the +NetworkResource+ model has +network_address+ and +cidr_range+ attributes that should be - # aggregated using the +NetAddr::CIDR+ value class (http://www.rubydoc.info/gems/netaddr/1.5.0/NetAddr/CIDR). - # The constructor for the value class is called +create+ and it expects a CIDR address string as a parameter. - # New values can be assigned to the value object using either another +NetAddr::CIDR+ object, a string - # or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet - # these requirements: - # - # class NetworkResource < ActiveRecord::Base - # composed_of :cidr, - # class_name: 'NetAddr::CIDR', - # mapping: [ %w(network_address network), %w(cidr_range bits) ], - # allow_nil: true, - # constructor: Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") }, - # converter: Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) } - # end - # - # # This calls the :constructor - # network_resource = NetworkResource.new(network_address: '192.168.0.1', cidr_range: 24) - # - # # These assignments will both use the :converter - # network_resource.cidr = [ '192.168.2.1', 8 ] - # network_resource.cidr = '192.168.0.1/24' - # - # # This assignment won't use the :converter as the value is already an instance of the value class - # network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8') - # - # # Saving and then reloading will use the :constructor on reload - # network_resource.save - # network_resource.reload - # - # == Finding records by a value object - # - # Once a #composed_of relationship is specified for a model, records can be loaded from the database - # by specifying an instance of the value object in the conditions hash. The following example - # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD": - # - # Customer.where(balance: Money.new(20, "USD")) - # - module ClassMethods - # Adds reader and writer methods for manipulating a value object: - # <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods. - # - # Options are: - # * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name - # can't be inferred from the part id. So <tt>composed_of :address</tt> will by default be linked - # to the Address class, but if the real class name is +CompanyAddress+, you'll have to specify it - # with this option. - # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value - # object. Each mapping is represented as an array where the first item is the name of the - # entity attribute and the second item is the name of the attribute in the value object. The - # order in which mappings are defined determines the order in which attributes are sent to the - # value class constructor. - # * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped - # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all - # mapped attributes. - # This defaults to +false+. - # * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that - # is called to initialize the value object. The constructor is passed all of the mapped attributes, - # in the order that they are defined in the <tt>:mapping option</tt>, as arguments and uses them - # to instantiate a <tt>:class_name</tt> object. - # The default is <tt>:new</tt>. - # * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt> - # or a Proc that is called when a new value is assigned to the value object. The converter is - # passed the single value that is used in the assignment and is only called if the new value is - # not an instance of <tt>:class_name</tt>. If <tt>:allow_nil</tt> is set to true, the converter - # can return nil to skip the assignment. - # - # Option examples: - # composed_of :temperature, mapping: %w(reading celsius) - # composed_of :balance, class_name: "Money", mapping: %w(balance amount), - # converter: Proc.new { |balance| balance.to_money } - # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ] - # composed_of :gps_location - # composed_of :gps_location, allow_nil: true - # composed_of :ip_address, - # class_name: 'IPAddr', - # mapping: %w(ip to_i), - # constructor: Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) }, - # converter: Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) } - # - def composed_of(part_id, options = {}) - options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter) + # Active Record implements aggregation through a macro-like class method called #composed_of + # for representing attributes as value objects. It expresses relationships like "Account [is] + # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call + # to the macro adds a description of how the value objects are created from the attributes of + # the entity object (when the entity is initialized either as a new object or from finding an + # existing object) and how it can be turned back into attributes (when the entity is saved to + # the database). + # + # class Customer < ActiveRecord::Base + # composed_of :balance, class_name: "Money", mapping: %w(amount currency) + # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ] + # end + # + # The customer class now has the following methods to manipulate the value objects: + # * <tt>Customer#balance, Customer#balance=(money)</tt> + # * <tt>Customer#address, Customer#address=(address)</tt> + # + # These methods will operate with value objects like the ones described below: + # + # class Money + # include Comparable + # attr_reader :amount, :currency + # EXCHANGE_RATES = { "USD_TO_DKK" => 6 } + # + # def initialize(amount, currency = "USD") + # @amount, @currency = amount, currency + # end + # + # def exchange_to(other_currency) + # exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor + # Money.new(exchanged_amount, other_currency) + # end + # + # def ==(other_money) + # amount == other_money.amount && currency == other_money.currency + # end + # + # def <=>(other_money) + # if currency == other_money.currency + # amount <=> other_money.amount + # else + # amount <=> other_money.exchange_to(currency).amount + # end + # end + # end + # + # class Address + # attr_reader :street, :city + # def initialize(street, city) + # @street, @city = street, city + # end + # + # def close_to?(other_address) + # city == other_address.city + # end + # + # def ==(other_address) + # city == other_address.city && street == other_address.street + # end + # end + # + # Now it's possible to access attributes from the database through the value objects instead. If + # you choose to name the composition the same as the attribute's name, it will be the only way to + # access that attribute. That's the case with our +balance+ attribute. You interact with the value + # objects just like you would with any other attribute: + # + # customer.balance = Money.new(20) # sets the Money value object and the attribute + # customer.balance # => Money value object + # customer.balance.exchange_to("DKK") # => Money.new(120, "DKK") + # customer.balance > Money.new(10) # => true + # customer.balance == Money.new(20) # => true + # customer.balance < Money.new(5) # => false + # + # Value objects can also be composed of multiple attributes, such as the case of Address. The order + # of the mappings will determine the order of the parameters. + # + # customer.address_street = "Hyancintvej" + # customer.address_city = "Copenhagen" + # customer.address # => Address.new("Hyancintvej", "Copenhagen") + # + # customer.address = Address.new("May Street", "Chicago") + # customer.address_street # => "May Street" + # customer.address_city # => "Chicago" + # + # == Writing value objects + # + # Value objects are immutable and interchangeable objects that represent a given value, such as + # a Money object representing $5. Two Money objects both representing $5 should be equal (through + # methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking makes sense). This is + # unlike entity objects where equality is determined by identity. An entity class such as Customer can + # easily have two different objects that both have an address on Hyancintvej. Entity identity is + # determined by object or relational unique identifiers (such as primary keys). Normal + # ActiveRecord::Base classes are entity objects. + # + # It's also important to treat the value objects as immutable. Don't allow the Money object to have + # its amount changed after creation. Create a new Money object with the new value instead. The + # <tt>Money#exchange_to</tt> method is an example of this. It returns a new value object instead of changing + # its own values. Active Record won't persist value objects that have been changed through means + # other than the writer method. + # + # The immutable requirement is enforced by Active Record by freezing any object assigned as a value + # object. Attempting to change it afterwards will result in a +RuntimeError+. + # + # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not + # keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable + # + # == Custom constructors and converters + # + # By default value objects are initialized by calling the <tt>new</tt> constructor of the value + # class passing each of the mapped attributes, in the order specified by the <tt>:mapping</tt> + # option, as arguments. If the value class doesn't support this convention then #composed_of allows + # a custom constructor to be specified. + # + # When a new value is assigned to the value object, the default assumption is that the new value + # is an instance of the value class. Specifying a custom converter allows the new value to be automatically + # converted to an instance of value class if necessary. + # + # For example, the +NetworkResource+ model has +network_address+ and +cidr_range+ attributes that should be + # aggregated using the +NetAddr::CIDR+ value class (http://www.rubydoc.info/gems/netaddr/1.5.0/NetAddr/CIDR). + # The constructor for the value class is called +create+ and it expects a CIDR address string as a parameter. + # New values can be assigned to the value object using either another +NetAddr::CIDR+ object, a string + # or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet + # these requirements: + # + # class NetworkResource < ActiveRecord::Base + # composed_of :cidr, + # class_name: 'NetAddr::CIDR', + # mapping: [ %w(network_address network), %w(cidr_range bits) ], + # allow_nil: true, + # constructor: Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") }, + # converter: Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) } + # end + # + # # This calls the :constructor + # network_resource = NetworkResource.new(network_address: '192.168.0.1', cidr_range: 24) + # + # # These assignments will both use the :converter + # network_resource.cidr = [ '192.168.2.1', 8 ] + # network_resource.cidr = '192.168.0.1/24' + # + # # This assignment won't use the :converter as the value is already an instance of the value class + # network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8') + # + # # Saving and then reloading will use the :constructor on reload + # network_resource.save + # network_resource.reload + # + # == Finding records by a value object + # + # Once a #composed_of relationship is specified for a model, records can be loaded from the database + # by specifying an instance of the value object in the conditions hash. The following example + # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD": + # + # Customer.where(balance: Money.new(20, "USD")) + # + module ClassMethods + # Adds reader and writer methods for manipulating a value object: + # <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods. + # + # Options are: + # * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name + # can't be inferred from the part id. So <tt>composed_of :address</tt> will by default be linked + # to the Address class, but if the real class name is +CompanyAddress+, you'll have to specify it + # with this option. + # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value + # object. Each mapping is represented as an array where the first item is the name of the + # entity attribute and the second item is the name of the attribute in the value object. The + # order in which mappings are defined determines the order in which attributes are sent to the + # value class constructor. + # * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped + # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all + # mapped attributes. + # This defaults to +false+. + # * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that + # is called to initialize the value object. The constructor is passed all of the mapped attributes, + # in the order that they are defined in the <tt>:mapping option</tt>, as arguments and uses them + # to instantiate a <tt>:class_name</tt> object. + # The default is <tt>:new</tt>. + # * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt> + # or a Proc that is called when a new value is assigned to the value object. The converter is + # passed the single value that is used in the assignment and is only called if the new value is + # not an instance of <tt>:class_name</tt>. If <tt>:allow_nil</tt> is set to true, the converter + # can return nil to skip the assignment. + # + # Option examples: + # composed_of :temperature, mapping: %w(reading celsius) + # composed_of :balance, class_name: "Money", mapping: %w(balance amount), + # converter: Proc.new { |balance| balance.to_money } + # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ] + # composed_of :gps_location + # composed_of :gps_location, allow_nil: true + # composed_of :ip_address, + # class_name: 'IPAddr', + # mapping: %w(ip to_i), + # constructor: Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) }, + # converter: Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) } + # + def composed_of(part_id, options = {}) + options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter) - name = part_id.id2name - class_name = options[:class_name] || name.camelize - mapping = options[:mapping] || [ name, name ] - mapping = [ mapping ] unless mapping.first.is_a?(Array) - allow_nil = options[:allow_nil] || false - constructor = options[:constructor] || :new - converter = options[:converter] + name = part_id.id2name + class_name = options[:class_name] || name.camelize + mapping = options[:mapping] || [ name, name ] + mapping = [ mapping ] unless mapping.first.is_a?(Array) + allow_nil = options[:allow_nil] || false + constructor = options[:constructor] || :new + converter = options[:converter] - reader_method(name, class_name, mapping, allow_nil, constructor) - writer_method(name, class_name, mapping, allow_nil, converter) + reader_method(name, class_name, mapping, allow_nil, constructor) + writer_method(name, class_name, mapping, allow_nil, converter) - reflection = ActiveRecord::Reflection.create(:composed_of, part_id, nil, options, self) - Reflection.add_aggregate_reflection self, part_id, reflection - end + reflection = ActiveRecord::Reflection.create(:composed_of, part_id, nil, options, self) + Reflection.add_aggregate_reflection self, part_id, reflection + end - private - def reader_method(name, class_name, mapping, allow_nil, constructor) - define_method(name) do - if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|key, _| !_read_attribute(key).nil? }) - attrs = mapping.collect {|key, _| _read_attribute(key)} - object = constructor.respond_to?(:call) ? - constructor.call(*attrs) : - class_name.constantize.send(constructor, *attrs) - @aggregation_cache[name] = object + private + def reader_method(name, class_name, mapping, allow_nil, constructor) + define_method(name) do + if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? { |key, _| !_read_attribute(key).nil? }) + attrs = mapping.collect { |key, _| _read_attribute(key) } + object = constructor.respond_to?(:call) ? + constructor.call(*attrs) : + class_name.constantize.send(constructor, *attrs) + @aggregation_cache[name] = object + end + @aggregation_cache[name] end - @aggregation_cache[name] end - end - def writer_method(name, class_name, mapping, allow_nil, converter) - define_method("#{name}=") do |part| - klass = class_name.constantize - if part.is_a?(Hash) - raise ArgumentError unless part.size == part.keys.max - part = klass.new(*part.sort.map(&:last)) - end + def writer_method(name, class_name, mapping, allow_nil, converter) + define_method("#{name}=") do |part| + klass = class_name.constantize - unless part.is_a?(klass) || converter.nil? || part.nil? - part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part) - end + unless part.is_a?(klass) || converter.nil? || part.nil? + part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part) + end + + hash_from_multiparameter_assignment = part.is_a?(Hash) && + part.each_key.all? { |k| k.is_a?(Integer) } + if hash_from_multiparameter_assignment + raise ArgumentError unless part.size == part.each_key.max + part = klass.new(*part.sort.map(&:last)) + end - if part.nil? && allow_nil - mapping.each { |key, _| self[key] = nil } - @aggregation_cache[name] = nil - else - mapping.each { |key, value| self[key] = part.send(value) } - @aggregation_cache[name] = part.freeze + if part.nil? && allow_nil + mapping.each { |key, _| self[key] = nil } + @aggregation_cache[name] = nil + else + mapping.each { |key, value| self[key] = part.send(value) } + @aggregation_cache[name] = part.freeze + end end end - end - end + end end end diff --git a/activerecord/lib/active_record/association_relation.rb b/activerecord/lib/active_record/association_relation.rb index ee0bb8fafe..de2d03cd0b 100644 --- a/activerecord/lib/active_record/association_relation.rb +++ b/activerecord/lib/active_record/association_relation.rb @@ -10,7 +10,7 @@ module ActiveRecord end def ==(other) - other == to_a + other == records end def build(*args, &block) @@ -28,8 +28,11 @@ module ActiveRecord private - def exec_queries - super.each { |r| @association.set_inverse_instance r } - end + def exec_queries + super do |r| + @association.set_inverse_instance r + yield r if block_given? + end + end end end diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index e13fe33b85..b5f1f1980a 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1,7 +1,7 @@ -require 'active_support/core_ext/enumerable' -require 'active_support/core_ext/string/conversions' -require 'active_support/core_ext/module/remove_method' -require 'active_record/errors' +require "active_support/core_ext/enumerable" +require "active_support/core_ext/string/conversions" +require "active_support/core_ext/module/remove_method" +require "active_record/errors" module ActiveRecord class AssociationNotFoundError < ConfigurationError #:nodoc: @@ -90,7 +90,7 @@ module ActiveRecord through_reflection = reflection.through_reflection source_reflection_names = reflection.source_reflection_names source_associations = reflection.through_reflection.klass._reflections.keys - super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?") + super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence(two_words_connector: ' or ', last_word_connector: ', or ', locale: :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(two_words_connector: ' or ', last_word_connector: ', or ', locale: :en)}?") else super("Could not find the source association(s).") end @@ -207,14 +207,14 @@ module ActiveRecord autoload :ThroughAssociation module Builder #:nodoc: - autoload :Association, 'active_record/associations/builder/association' - autoload :SingularAssociation, 'active_record/associations/builder/singular_association' - autoload :CollectionAssociation, 'active_record/associations/builder/collection_association' + autoload :Association, "active_record/associations/builder/association" + autoload :SingularAssociation, "active_record/associations/builder/singular_association" + autoload :CollectionAssociation, "active_record/associations/builder/collection_association" - autoload :BelongsTo, 'active_record/associations/builder/belongs_to' - autoload :HasOne, 'active_record/associations/builder/has_one' - autoload :HasMany, 'active_record/associations/builder/has_many' - autoload :HasAndBelongsToMany, 'active_record/associations/builder/has_and_belongs_to_many' + autoload :BelongsTo, "active_record/associations/builder/belongs_to" + autoload :HasOne, "active_record/associations/builder/has_one" + autoload :HasMany, "active_record/associations/builder/has_many" + autoload :HasAndBelongsToMany, "active_record/associations/builder/has_and_belongs_to_many" end eager_autoload do @@ -274,1556 +274,1561 @@ module ActiveRecord @association_cache[name] = association end - # \Associations are a set of macro-like class methods for tying objects together through - # foreign keys. They express relationships like "Project has one Project Manager" - # or "Project belongs to a Portfolio". Each macro adds a number of methods to the - # class which are specialized according to the collection or association symbol and the - # options hash. It works much the same way as Ruby's own <tt>attr*</tt> - # methods. - # - # class Project < ActiveRecord::Base - # belongs_to :portfolio - # has_one :project_manager - # has_many :milestones - # has_and_belongs_to_many :categories - # end - # - # The project class now has the following methods (and more) to ease the traversal and - # manipulation of its relationships: - # * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt> - # * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt> - # * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt> - # <tt>Project#milestones.delete(milestone), Project#milestones.destroy(milestone), Project#milestones.find(milestone_id),</tt> - # <tt>Project#milestones.build, Project#milestones.create</tt> - # * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt> - # <tt>Project#categories.delete(category1), Project#categories.destroy(category1)</tt> - # - # === A word of warning - # - # Don't create associations that have the same name as instance methods of - # ActiveRecord::Base. Since the association adds a method with that name to - # its model, it will override the inherited method and break things. - # For instance, +attributes+ and +connection+ would be bad choices for association names. - # - # == Auto-generated methods - # See also Instance Public methods below for more details. - # - # === Singular associations (one-to-one) - # | | belongs_to | - # generated methods | belongs_to | :polymorphic | has_one - # ----------------------------------+------------+--------------+--------- - # other(force_reload=false) | X | X | X - # other=(other) | X | X | X - # build_other(attributes={}) | X | | X - # create_other(attributes={}) | X | | X - # create_other!(attributes={}) | X | | X - # - # ===Collection associations (one-to-many / many-to-many) - # | | | has_many - # generated methods | habtm | has_many | :through - # ----------------------------------+-------+----------+---------- - # others(force_reload=false) | X | X | X - # others=(other,other,...) | X | X | X - # other_ids | X | X | X - # other_ids=(id,id,...) | X | X | X - # others<< | X | X | X - # others.push | X | X | X - # others.concat | X | X | X - # others.build(attributes={}) | X | X | X - # others.create(attributes={}) | X | X | X - # others.create!(attributes={}) | X | X | X - # others.size | X | X | X - # others.length | X | X | X - # others.count | X | X | X - # others.sum(*args) | X | X | X - # others.empty? | X | X | X - # others.clear | X | X | X - # others.delete(other,other,...) | X | X | X - # others.delete_all | X | X | X - # others.destroy(other,other,...) | X | X | X - # others.destroy_all | X | X | X - # others.find(*args) | X | X | X - # others.exists? | X | X | X - # others.distinct | X | X | X - # others.reset | X | X | X - # - # === 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: - # - # 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>, 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. - # - # == Cardinality and associations - # - # Active Record associations can be used to describe one-to-one, one-to-many and many-to-many - # relationships between models. Each model uses an association to describe its role in - # the relation. The #belongs_to association is always used in the model that has - # the foreign key. - # - # === One-to-one - # - # Use #has_one in the base, and #belongs_to in the associated model. - # - # class Employee < ActiveRecord::Base - # has_one :office - # end - # class Office < ActiveRecord::Base - # belongs_to :employee # foreign key - employee_id - # end - # - # === One-to-many - # - # Use #has_many in the base, and #belongs_to in the associated model. - # - # class Manager < ActiveRecord::Base - # has_many :employees - # end - # class Employee < ActiveRecord::Base - # belongs_to :manager # foreign key - manager_id - # end - # - # === Many-to-many - # - # There are two ways to build a many-to-many relationship. - # - # The first way uses a #has_many association with the <tt>:through</tt> option and a join model, so - # there are two stages of associations. - # - # class Assignment < ActiveRecord::Base - # belongs_to :programmer # foreign key - programmer_id - # belongs_to :project # foreign key - project_id - # end - # class Programmer < ActiveRecord::Base - # has_many :assignments - # has_many :projects, through: :assignments - # end - # class Project < ActiveRecord::Base - # has_many :assignments - # has_many :programmers, through: :assignments - # end - # - # For the second way, use #has_and_belongs_to_many in both models. This requires a join table - # that has no corresponding model or primary key. - # - # class Programmer < ActiveRecord::Base - # has_and_belongs_to_many :projects # foreign keys in the join table - # end - # class Project < ActiveRecord::Base - # has_and_belongs_to_many :programmers # foreign keys in the join table - # end - # - # Choosing which way to build a many-to-many relationship is not always simple. - # If you need to work with the relationship model as its own entity, - # use #has_many <tt>:through</tt>. Use #has_and_belongs_to_many when working with legacy schemas or when - # you never work directly with the relationship itself. - # - # == Is it a #belongs_to or #has_one association? - # - # Both express a 1-1 relationship. The difference is mostly where to place the foreign - # key, which goes on the table for the class declaring the #belongs_to relationship. - # - # class User < ActiveRecord::Base - # # I reference an account. - # belongs_to :account - # end - # - # class Account < ActiveRecord::Base - # # One user references me. - # has_one :user - # end - # - # The tables for these classes could look something like: - # - # CREATE TABLE users ( - # id int NOT NULL auto_increment, - # account_id int default NULL, - # name varchar default NULL, - # PRIMARY KEY (id) - # ) - # - # CREATE TABLE accounts ( - # id int NOT NULL auto_increment, - # name varchar default NULL, - # PRIMARY KEY (id) - # ) - # - # == Unsaved objects and associations - # - # You can manipulate objects and associations before they are saved to the database, but - # there is some special behavior you should be aware of, mostly involving the saving of - # associated objects. - # - # You can set the <tt>:autosave</tt> option on a #has_one, #belongs_to, - # #has_many, or #has_and_belongs_to_many association. Setting it - # to +true+ will _always_ save the members, whereas setting it to +false+ will - # _never_ save the members. More details about <tt>:autosave</tt> option is available at - # AutosaveAssociation. - # - # === One-to-one associations - # - # * Assigning an object to a #has_one association automatically saves that object and - # the object being replaced (if there is one), in order to update their foreign - # keys - except if the parent object is unsaved (<tt>new_record? == true</tt>). - # * If either of these saves fail (due to one of the objects being invalid), an - # ActiveRecord::RecordNotSaved exception is raised and the assignment is - # cancelled. - # * If you wish to assign an object to a #has_one association without saving it, - # use the <tt>#build_association</tt> method (documented below). The object being - # replaced will still be saved to update its foreign key. - # * Assigning an object to a #belongs_to association does not save the object, since - # the foreign key field belongs on the parent. It does not save the parent either. - # - # === Collections - # - # * Adding an object to a collection (#has_many or #has_and_belongs_to_many) automatically - # saves that object, except if the parent object (the owner of the collection) is not yet - # stored in the database. - # * If saving any of the objects being added to a collection (via <tt>push</tt> or similar) - # fails, then <tt>push</tt> returns +false+. - # * If saving fails while replacing the collection (via <tt>association=</tt>), an - # ActiveRecord::RecordNotSaved exception is raised and the assignment is - # cancelled. - # * You can add an object to a collection without automatically saving it by using the - # <tt>collection.build</tt> method (documented below). - # * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically - # saved when the parent is saved. - # - # == Customizing the query - # - # \Associations are built from <tt>Relation</tt>s, and you can use the Relation syntax - # to customize them. For example, to add a condition: - # - # class Blog < ActiveRecord::Base - # has_many :published_posts, -> { where(published: true) }, class_name: 'Post' - # end - # - # Inside the <tt>-> { ... }</tt> block you can use all of the usual Relation methods. - # - # === Accessing the owner object - # - # Sometimes it is useful to have access to the owner object when building the query. The owner - # is passed as a parameter to the block. For example, the following association would find all - # events that occur on the user's birthday: - # - # class User < ActiveRecord::Base - # has_many :birthday_events, ->(user) { where(starts_on: user.birthday) }, class_name: 'Event' - # end - # - # Note: Joining, eager loading and preloading of these associations is not fully possible. - # These operations happen before instance creation and the scope will be called with a +nil+ argument. - # This can lead to unexpected behavior and is deprecated. - # - # == Association callbacks - # - # Similar to the normal callbacks that hook into the life cycle of an Active Record object, - # you can also define callbacks that get triggered when you add an object to or remove an - # object from an association collection. - # - # class Project - # has_and_belongs_to_many :developers, after_add: :evaluate_velocity - # - # def evaluate_velocity(developer) - # ... - # end - # end - # - # It's possible to stack callbacks by passing them as an array. Example: - # - # class Project - # has_and_belongs_to_many :developers, - # after_add: [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}] - # end - # - # Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+. - # - # If any of the +before_add+ callbacks throw an exception, the object will not be - # added to the collection. - # - # Similarly, if any of the +before_remove+ callbacks throw an exception, the object - # will not be removed from the collection. - # - # == Association extensions - # - # The proxy objects that control the access to associations can be extended through anonymous - # modules. This is especially beneficial for adding new finders, creators, and other - # factory-type methods that are only used as part of this association. - # - # class Account < ActiveRecord::Base - # has_many :people do - # def find_or_create_by_name(name) - # first_name, last_name = name.split(" ", 2) - # find_or_create_by(first_name: first_name, last_name: last_name) - # end - # end - # end - # - # person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson") - # person.first_name # => "David" - # person.last_name # => "Heinemeier Hansson" - # - # If you need to share the same extensions between many associations, you can use a named - # extension module. - # - # module FindOrCreateByNameExtension - # def find_or_create_by_name(name) - # first_name, last_name = name.split(" ", 2) - # find_or_create_by(first_name: first_name, last_name: last_name) - # end - # end - # - # class Account < ActiveRecord::Base - # has_many :people, -> { extending FindOrCreateByNameExtension } - # end - # - # class Company < ActiveRecord::Base - # has_many :people, -> { extending FindOrCreateByNameExtension } - # end - # - # Some extensions can only be made to work with knowledge of the association's internals. - # Extensions can access relevant state using the following methods (where +items+ is the - # name of the association): - # - # * <tt>record.association(:items).owner</tt> - Returns the object the association is part of. - # * <tt>record.association(:items).reflection</tt> - Returns the reflection object that describes the association. - # * <tt>record.association(:items).target</tt> - Returns the associated object for #belongs_to and #has_one, or - # the collection of associated objects for #has_many and #has_and_belongs_to_many. - # - # However, inside the actual extension code, you will not have access to the <tt>record</tt> as - # above. In this case, you can access <tt>proxy_association</tt>. For example, - # <tt>record.association(:items)</tt> and <tt>record.items.proxy_association</tt> will return - # the same object, allowing you to make calls like <tt>proxy_association.owner</tt> inside - # association extensions. - # - # == Association Join Models - # - # Has Many associations can be configured with the <tt>:through</tt> option to use an - # explicit join model to retrieve the data. This operates similarly to a - # #has_and_belongs_to_many association. The advantage is that you're able to add validations, - # callbacks, and extra attributes on the join model. Consider the following schema: - # - # class Author < ActiveRecord::Base - # has_many :authorships - # has_many :books, through: :authorships - # end - # - # class Authorship < ActiveRecord::Base - # belongs_to :author - # belongs_to :book - # end - # - # @author = Author.first - # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to - # @author.books # selects all books by using the Authorship join model - # - # You can also go through a #has_many association on the join model: - # - # class Firm < ActiveRecord::Base - # has_many :clients - # has_many :invoices, through: :clients - # end - # - # class Client < ActiveRecord::Base - # belongs_to :firm - # has_many :invoices - # end - # - # class Invoice < ActiveRecord::Base - # belongs_to :client - # end - # - # @firm = Firm.first - # @firm.clients.flat_map { |c| c.invoices } # select all invoices for all clients of the firm - # @firm.invoices # selects all invoices by going through the Client join model - # - # Similarly you can go through a #has_one association on the join model: - # - # class Group < ActiveRecord::Base - # has_many :users - # has_many :avatars, through: :users - # end - # - # class User < ActiveRecord::Base - # belongs_to :group - # has_one :avatar - # end - # - # class Avatar < ActiveRecord::Base - # belongs_to :user - # end - # - # @group = Group.first - # @group.users.collect { |u| u.avatar }.compact # select all avatars for all users in the group - # @group.avatars # selects all avatars by going through the User join model. - # - # An important caveat with going through #has_one or #has_many associations on the - # join model is that these associations are *read-only*. For example, the following - # would not work following the previous example: - # - # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around - # @group.avatars.delete(@group.avatars.last) # so would this - # - # == Setting Inverses - # - # If you are using a #belongs_to on the join model, it is a good idea to set the - # <tt>:inverse_of</tt> option on the #belongs_to, which will mean that the following example - # works correctly (where <tt>tags</tt> is a #has_many <tt>:through</tt> association): - # - # @post = Post.first - # @tag = @post.tags.build name: "ruby" - # @tag.save - # - # The last line ought to save the through record (a <tt>Tagging</tt>). This will only work if the - # <tt>:inverse_of</tt> is set: - # - # class Tagging < ActiveRecord::Base - # belongs_to :post - # belongs_to :tag, inverse_of: :taggings - # end - # - # If you do not set the <tt>:inverse_of</tt> record, the association will - # do its best to match itself up with the correct inverse. Automatic - # inverse detection only works on #has_many, #has_one, and - # #belongs_to associations. - # - # Extra options on the associations, as defined in the - # <tt>AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS</tt> constant, will - # also prevent the association's inverse from being found automatically. - # - # The automatic guessing of the inverse association uses a heuristic based - # on the name of the class, so it may not work for all associations, - # especially the ones with non-standard names. - # - # You can turn off the automatic detection of inverse associations by setting - # the <tt>:inverse_of</tt> option to <tt>false</tt> like so: - # - # class Tagging < ActiveRecord::Base - # belongs_to :tag, inverse_of: false - # end - # - # == Nested \Associations - # - # You can actually specify *any* association with the <tt>:through</tt> option, including an - # association which has a <tt>:through</tt> option itself. For example: - # - # class Author < ActiveRecord::Base - # has_many :posts - # has_many :comments, through: :posts - # has_many :commenters, through: :comments - # end - # - # class Post < ActiveRecord::Base - # has_many :comments - # end - # - # class Comment < ActiveRecord::Base - # belongs_to :commenter - # end - # - # @author = Author.first - # @author.commenters # => People who commented on posts written by the author - # - # An equivalent way of setting up this association this would be: - # - # class Author < ActiveRecord::Base - # has_many :posts - # has_many :commenters, through: :posts - # end - # - # class Post < ActiveRecord::Base - # has_many :comments - # has_many :commenters, through: :comments - # end - # - # class Comment < ActiveRecord::Base - # belongs_to :commenter - # end - # - # When using a nested association, you will not be able to modify the association because there - # is not enough information to know what modification to make. For example, if you tried to - # add a <tt>Commenter</tt> in the example above, there would be no way to tell how to set up the - # intermediate <tt>Post</tt> and <tt>Comment</tt> objects. - # - # == Polymorphic \Associations - # - # Polymorphic associations on models are not restricted on what types of models they - # can be associated with. Rather, they specify an interface that a #has_many association - # must adhere to. - # - # class Asset < ActiveRecord::Base - # belongs_to :attachable, polymorphic: true - # end - # - # class Post < ActiveRecord::Base - # has_many :assets, as: :attachable # The :as option specifies the polymorphic interface to use. - # end - # - # @asset.attachable = @post - # - # This works by using a type column in addition to a foreign key to specify the associated - # record. In the Asset example, you'd need an +attachable_id+ integer column and an - # +attachable_type+ string column. - # - # Using polymorphic associations in combination with single table inheritance (STI) is - # a little tricky. In order for the associations to work as expected, ensure that you - # store the base model for the STI models in the type column of the polymorphic - # association. To continue with the asset example above, suppose there are guest posts - # and member posts that use the posts table for STI. In this case, there must be a +type+ - # column in the posts table. - # - # Note: The <tt>attachable_type=</tt> method is being called when assigning an +attachable+. - # The +class_name+ of the +attachable+ is passed as a String. - # - # class Asset < ActiveRecord::Base - # belongs_to :attachable, polymorphic: true - # - # def attachable_type=(class_name) - # super(class_name.constantize.base_class.to_s) - # end - # end - # - # class Post < ActiveRecord::Base - # # because we store "Post" in attachable_type now dependent: :destroy will work - # has_many :assets, as: :attachable, dependent: :destroy - # end - # - # class GuestPost < Post - # end - # - # class MemberPost < Post - # end - # - # == Caching - # - # All of the methods are built on a simple caching principle that will keep the result - # of the last query around unless specifically instructed not to. The cache is even - # shared across methods to make it even cheaper to use the macro-added methods without - # worrying too much about performance at the first go. - # - # project.milestones # fetches milestones from the database - # project.milestones.size # uses the milestone cache - # project.milestones.empty? # uses the milestone cache - # project.milestones(true).size # fetches milestones from the database - # project.milestones # uses the milestone cache - # - # == Eager loading of associations - # - # Eager loading is a way to find objects of a certain class and a number of named associations. - # It is one of the easiest ways to prevent the dreaded N+1 problem in which fetching 100 - # posts that each need to display their author triggers 101 database queries. Through the - # use of eager loading, the number of queries will be reduced from 101 to 2. - # - # class Post < ActiveRecord::Base - # belongs_to :author - # has_many :comments - # end - # - # Consider the following loop using the class above: - # - # Post.all.each do |post| - # puts "Post: " + post.title - # puts "Written by: " + post.author.name - # puts "Last comment on: " + post.comments.first.created_on - # end - # - # To iterate over these one hundred posts, we'll generate 201 database queries. Let's - # first just optimize it for retrieving the author: - # - # Post.includes(:author).each do |post| - # - # This references the name of the #belongs_to association that also used the <tt>:author</tt> - # symbol. After loading the posts, find will collect the +author_id+ from each one and load - # all the referenced authors with one query. Doing so will cut down the number of queries - # from 201 to 102. - # - # We can improve upon the situation further by referencing both associations in the finder with: - # - # Post.includes(:author, :comments).each do |post| - # - # This will load all comments with a single query. This reduces the total number of queries - # to 3. In general, the number of queries will be 1 plus the number of associations - # named (except if some of the associations are polymorphic #belongs_to - see below). - # - # To include a deep hierarchy of associations, use a hash: - # - # Post.includes(:author, { comments: { author: :gravatar } }).each do |post| - # - # The above code will load all the comments and all of their associated - # authors and gravatars. You can mix and match any combination of symbols, - # arrays, and hashes to retrieve the associations you want to load. - # - # All of this power shouldn't fool you into thinking that you can pull out huge amounts - # of data with no performance penalty just because you've reduced the number of queries. - # The database still needs to send all the data to Active Record and it still needs to - # be processed. So it's no catch-all for performance problems, but it's a great way to - # cut down on the number of queries in a situation as the one described above. - # - # Since only one table is loaded at a time, conditions or orders cannot reference tables - # other than the main one. If this is the case, Active Record falls back to the previously - # used LEFT OUTER JOIN based strategy. For example: - # - # Post.includes([:author, :comments]).where(['comments.approved = ?', true]) - # - # This will result in a single SQL query with joins along the lines of: - # <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and - # <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Note that using conditions - # like this can have unintended consequences. - # In the above example posts with no approved comments are not returned at all, because - # the conditions apply to the SQL statement as a whole and not just to the association. - # - # You must disambiguate column references for this fallback to happen, for example - # <tt>order: "author.name DESC"</tt> will work but <tt>order: "name DESC"</tt> will not. - # - # If you want to load all posts (including posts with no approved comments) then write - # your own LEFT OUTER JOIN query using ON - # - # Post.joins("LEFT OUTER JOIN comments ON comments.post_id = posts.id AND comments.approved = '1'") - # - # In this case it is usually more natural to include an association which has conditions defined on it: - # - # class Post < ActiveRecord::Base - # has_many :approved_comments, -> { where(approved: true) }, class_name: 'Comment' - # end - # - # Post.includes(:approved_comments) - # - # This will load posts and eager load the +approved_comments+ association, which contains - # only those comments that have been approved. - # - # If you eager load an association with a specified <tt>:limit</tt> option, it will be ignored, - # returning all the associated objects: - # - # class Picture < ActiveRecord::Base - # has_many :most_recent_comments, -> { order('id DESC').limit(10) }, class_name: 'Comment' - # end - # - # Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments. - # - # Eager loading is supported with polymorphic associations. - # - # class Address < ActiveRecord::Base - # belongs_to :addressable, polymorphic: true - # end - # - # A call that tries to eager load the addressable model - # - # Address.includes(:addressable) - # - # This will execute one query to load the addresses and load the addressables with one - # query per addressable type. - # For example if all the addressables are either of class Person or Company then a total - # of 3 queries will be executed. The list of addressable types to load is determined on - # the back of the addresses loaded. This is not supported if Active Record has to fallback - # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. - # The reason is that the parent model's type is a column value so its corresponding table - # name cannot be put in the +FROM+/+JOIN+ clauses of that query. - # - # == Table Aliasing - # - # Active Record uses table aliasing in the case that a table is referenced multiple times - # in a join. If a table is referenced only once, the standard table name is used. The - # second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>. - # Indexes are appended for any more successive uses of the table name. - # - # Post.joins(:comments) - # # => SELECT ... FROM posts INNER JOIN comments ON ... - # Post.joins(:special_comments) # STI - # # => SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment' - # Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name - # # => SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts - # - # Acts as tree example: - # - # TreeMixin.joins(:children) - # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... - # TreeMixin.joins(children: :parent) - # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... - # INNER JOIN parents_mixins ... - # TreeMixin.joins(children: {parent: :children}) - # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... - # INNER JOIN parents_mixins ... - # INNER JOIN mixins childrens_mixins_2 - # - # Has and Belongs to Many join tables use the same idea, but add a <tt>_join</tt> suffix: - # - # Post.joins(:categories) - # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... - # Post.joins(categories: :posts) - # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... - # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories - # Post.joins(categories: {posts: :categories}) - # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... - # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories - # INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2 - # - # If you wish to specify your own custom joins using ActiveRecord::QueryMethods#joins method, those table - # names will take precedence over the eager associations: - # - # Post.joins(:comments).joins("inner join comments ...") - # # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ... - # Post.joins(:comments, :special_comments).joins("inner join comments ...") - # # => SELECT ... FROM posts INNER JOIN comments comments_posts ON ... - # INNER JOIN comments special_comments_posts ... - # INNER JOIN comments ... - # - # Table aliases are automatically truncated according to the maximum length of table identifiers - # according to the specific database. - # - # == Modules - # - # By default, associations will look for objects within the current module scope. Consider: - # - # module MyApplication - # module Business - # class Firm < ActiveRecord::Base - # has_many :clients - # end - # - # class Client < ActiveRecord::Base; end - # end - # end - # - # When <tt>Firm#clients</tt> is called, it will in turn call - # <tt>MyApplication::Business::Client.find_all_by_firm_id(firm.id)</tt>. - # If you want to associate with a class in another module scope, this can be done by - # specifying the complete class name. - # - # module MyApplication - # module Business - # class Firm < ActiveRecord::Base; end - # end - # - # module Billing - # class Account < ActiveRecord::Base - # belongs_to :firm, class_name: "MyApplication::Business::Firm" - # end - # end - # end - # - # == Bi-directional associations - # - # When you specify an association there is usually an association on the associated model - # that specifies the same relationship in reverse. For example, with the following models: - # - # class Dungeon < ActiveRecord::Base - # has_many :traps - # has_one :evil_wizard - # end - # - # class Trap < ActiveRecord::Base - # belongs_to :dungeon - # end - # - # class EvilWizard < ActiveRecord::Base - # belongs_to :dungeon - # end - # - # The +traps+ association on +Dungeon+ and the +dungeon+ association on +Trap+ are - # the inverse of each other and the inverse of the +dungeon+ association on +EvilWizard+ - # is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default, - # Active Record can guess the inverse of the association based on the name - # of the class. The result is the following: - # - # d = Dungeon.first - # t = d.traps.first - # d.object_id == t.dungeon.object_id # => true - # - # The +Dungeon+ instances +d+ and <tt>t.dungeon</tt> in the above example refer to - # the same in-memory instance since the association matches the name of the class. - # The result would be the same if we added +:inverse_of+ to our model definitions: - # - # class Dungeon < ActiveRecord::Base - # has_many :traps, inverse_of: :dungeon - # has_one :evil_wizard, inverse_of: :dungeon - # end - # - # class Trap < ActiveRecord::Base - # belongs_to :dungeon, inverse_of: :traps - # end - # - # class EvilWizard < ActiveRecord::Base - # belongs_to :dungeon, inverse_of: :evil_wizard - # end - # - # There are limitations to <tt>:inverse_of</tt> support: - # - # * does not work with <tt>:through</tt> associations. - # * does not work with <tt>:polymorphic</tt> associations. - # * for #belongs_to associations #has_many inverse associations are ignored. - # - # For more information, see the documentation for the +:inverse_of+ option. - # - # == Deleting from associations - # - # === Dependent associations - # - # #has_many, #has_one and #belongs_to associations support the <tt>:dependent</tt> option. - # This allows you to specify that associated records should be deleted when the owner is - # deleted. - # - # For example: - # - # class Author - # has_many :posts, dependent: :destroy - # end - # Author.find(1).destroy # => Will destroy all of the author's posts, too - # - # The <tt>:dependent</tt> option can have different values which specify how the deletion - # is done. For more information, see the documentation for this option on the different - # specific association types. When no option is given, the behavior is to do nothing - # with the associated records when destroying a record. - # - # Note that <tt>:dependent</tt> is implemented using Rails' callback - # system, which works by processing callbacks in order. Therefore, other - # callbacks declared either before or after the <tt>:dependent</tt> option - # can affect what it does. - # - # Note that <tt>:dependent</tt> option is ignored for #has_one <tt>:through</tt> associations. - # - # === Delete or destroy? - # - # #has_many and #has_and_belongs_to_many associations have the methods <tt>destroy</tt>, - # <tt>delete</tt>, <tt>destroy_all</tt> and <tt>delete_all</tt>. - # - # For #has_and_belongs_to_many, <tt>delete</tt> and <tt>destroy</tt> are the same: they - # cause the records in the join table to be removed. - # - # For #has_many, <tt>destroy</tt> and <tt>destroy_all</tt> will always call the <tt>destroy</tt> method of the - # record(s) being removed so that callbacks are run. However <tt>delete</tt> and <tt>delete_all</tt> will either - # do the deletion according to the strategy specified by the <tt>:dependent</tt> option, or - # if no <tt>:dependent</tt> option is given, then it will follow the default strategy. - # The default strategy is to do nothing (leave the foreign keys with the parent ids set), except for - # #has_many <tt>:through</tt>, where the default strategy is <tt>delete_all</tt> (delete - # the join records, without running their callbacks). - # - # There is also a <tt>clear</tt> method which is the same as <tt>delete_all</tt>, except that - # it returns the association rather than the records which have been deleted. - # - # === What gets deleted? - # - # There is a potential pitfall here: #has_and_belongs_to_many and #has_many <tt>:through</tt> - # associations have records in join tables, as well as the associated records. So when we - # call one of these deletion methods, what exactly should be deleted? - # - # The answer is that it is assumed that deletion on an association is about removing the - # <i>link</i> between the owner and the associated object(s), rather than necessarily the - # associated objects themselves. So with #has_and_belongs_to_many and #has_many - # <tt>:through</tt>, the join records will be deleted, but the associated records won't. - # - # This makes sense if you think about it: if you were to call <tt>post.tags.delete(Tag.find_by(name: 'food'))</tt> - # you would want the 'food' tag to be unlinked from the post, rather than for the tag itself - # to be removed from the database. - # - # However, there are examples where this strategy doesn't make sense. For example, suppose - # a person has many projects, and each project has many tasks. If we deleted one of a person's - # tasks, we would probably not want the project to be deleted. In this scenario, the delete method - # won't actually work: it can only be used if the association on the join model is a - # #belongs_to. In other situations you are expected to perform operations directly on - # either the associated records or the <tt>:through</tt> association. - # - # With a regular #has_many there is no distinction between the "associated records" - # and the "link", so there is only one choice for what gets deleted. - # - # With #has_and_belongs_to_many and #has_many <tt>:through</tt>, if you want to delete the - # associated records themselves, you can always do something along the lines of - # <tt>person.tasks.each(&:destroy)</tt>. - # - # == Type safety with ActiveRecord::AssociationTypeMismatch - # - # If you attempt to assign an object to an association that doesn't match the inferred - # or specified <tt>:class_name</tt>, you'll get an ActiveRecord::AssociationTypeMismatch. - # - # == Options - # - # All of the association macros can be specialized through options. This makes cases - # more complex than the simple and guessable ones possible. - module ClassMethods - # Specifies a one-to-many association. The following methods for retrieval and query of - # collections of associated objects will be added: - # - # +collection+ is a placeholder for the symbol passed as the +name+ argument, so - # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>. - # - # [collection(force_reload = false)] - # Returns an array of all the associated objects. - # An empty array is returned if none are found. - # [collection<<(object, ...)] - # Adds one or more objects to the collection by setting their foreign keys to the collection's primary key. - # Note that this operation instantly fires update SQL without waiting for the save or update call on the - # parent object, unless the parent object is a new record. - # This will also run validations and callbacks of associated object(s). - # [collection.delete(object, ...)] - # Removes one or more objects from the collection by setting their foreign keys to +NULL+. - # Objects will be in addition destroyed if they're associated with <tt>dependent: :destroy</tt>, - # and deleted if they're associated with <tt>dependent: :delete_all</tt>. - # - # If the <tt>:through</tt> option is used, then the join records are deleted (rather than - # nullified) by default, but you can specify <tt>dependent: :destroy</tt> or - # <tt>dependent: :nullify</tt> to override this. - # [collection.destroy(object, ...)] - # Removes one or more objects from the collection by running <tt>destroy</tt> on - # each record, regardless of any dependent option, ensuring callbacks are run. - # - # If the <tt>:through</tt> option is used, then the join records are destroyed - # instead, not the objects themselves. - # [collection=objects] - # Replaces the collections content by deleting and adding objects as appropriate. If the <tt>:through</tt> - # option is true callbacks in the join models are triggered except destroy callbacks, since deletion is - # direct by default. You can specify <tt>dependent: :destroy</tt> or - # <tt>dependent: :nullify</tt> to override this. - # [collection_singular_ids] - # Returns an array of the associated objects' ids - # [collection_singular_ids=ids] - # Replace the collection with the objects identified by the primary keys in +ids+. This - # method loads the models and calls <tt>collection=</tt>. See above. - # [collection.clear] - # Removes every object from the collection. This destroys the associated objects if they - # are associated with <tt>dependent: :destroy</tt>, deletes them directly from the - # database if <tt>dependent: :delete_all</tt>, otherwise sets their foreign keys to +NULL+. - # If the <tt>:through</tt> option is true no destroy callbacks are invoked on the join models. - # Join models are directly deleted. - # [collection.empty?] - # Returns +true+ if there are no associated objects. - # [collection.size] - # Returns the number of associated objects. - # [collection.find(...)] - # Finds an associated object according to the same rules as ActiveRecord::FinderMethods#find. - # [collection.exists?(...)] - # Checks whether an associated object with the given conditions exists. - # Uses the same rules as ActiveRecord::FinderMethods#exists?. - # [collection.build(attributes = {}, ...)] - # Returns one or more new objects of the collection type that have been instantiated - # with +attributes+ and linked to this object through a foreign key, but have not yet - # been saved. - # [collection.create(attributes = {})] - # Returns a new object of the collection type that has been instantiated - # with +attributes+, linked to this object through a foreign key, and that has already - # been saved (if it passed the validation). *Note*: This only works if the base model - # already exists in the DB, not if it is a new (unsaved) record! - # [collection.create!(attributes = {})] - # Does the same as <tt>collection.create</tt>, but raises ActiveRecord::RecordInvalid - # if the record is invalid. - # - # === Example - # - # A <tt>Firm</tt> class declares <tt>has_many :clients</tt>, which will add: - # * <tt>Firm#clients</tt> (similar to <tt>Client.where(firm_id: id)</tt>) - # * <tt>Firm#clients<<</tt> - # * <tt>Firm#clients.delete</tt> - # * <tt>Firm#clients.destroy</tt> - # * <tt>Firm#clients=</tt> - # * <tt>Firm#client_ids</tt> - # * <tt>Firm#client_ids=</tt> - # * <tt>Firm#clients.clear</tt> - # * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>) - # * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>) - # * <tt>Firm#clients.find</tt> (similar to <tt>Client.where(firm_id: id).find(id)</tt>) - # * <tt>Firm#clients.exists?(name: 'ACME')</tt> (similar to <tt>Client.exists?(name: 'ACME', firm_id: firm.id)</tt>) - # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>) - # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>) - # * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save!</tt>) - # The declaration can also include an +options+ hash to specialize the behavior of the association. - # - # === Scopes - # - # You can pass a second argument +scope+ as a callable (i.e. proc or - # lambda) to retrieve a specific set of records or customize the generated - # query when you access the associated collection. - # - # Scope examples: - # has_many :comments, -> { where(author_id: 1) } - # has_many :employees, -> { joins(:address) } - # has_many :posts, ->(post) { where("max_post_length > ?", post.length) } - # - # === Extensions - # - # The +extension+ argument allows you to pass a block into a has_many - # association. This is useful for adding new finders, creators and other - # factory-type methods to be used as part of the association. - # - # Extension examples: - # has_many :employees do - # def find_or_create_by_name(name) - # first_name, last_name = name.split(" ", 2) - # find_or_create_by(first_name: first_name, last_name: last_name) + # \Associations are a set of macro-like class methods for tying objects together through + # foreign keys. They express relationships like "Project has one Project Manager" + # or "Project belongs to a Portfolio". Each macro adds a number of methods to the + # class which are specialized according to the collection or association symbol and the + # options hash. It works much the same way as Ruby's own <tt>attr*</tt> + # methods. + # + # class Project < ActiveRecord::Base + # belongs_to :portfolio + # has_one :project_manager + # has_many :milestones + # has_and_belongs_to_many :categories + # end + # + # The project class now has the following methods (and more) to ease the traversal and + # manipulation of its relationships: + # * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt> + # * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt> + # * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt> + # <tt>Project#milestones.delete(milestone), Project#milestones.destroy(milestone), Project#milestones.find(milestone_id),</tt> + # <tt>Project#milestones.build, Project#milestones.create</tt> + # * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt> + # <tt>Project#categories.delete(category1), Project#categories.destroy(category1)</tt> + # + # === A word of warning + # + # Don't create associations that have the same name as {instance methods}[rdoc-ref:ActiveRecord::Core] of + # <tt>ActiveRecord::Base</tt>. Since the association adds a method with that name to + # its model, using an association with the same name as one provided by <tt>ActiveRecord::Base</tt> will override the method inherited through <tt>ActiveRecord::Base</tt> and will break things. + # For instance, +attributes+ and +connection+ would be bad choices for association names, because those names already exist in the list of <tt>ActiveRecord::Base</tt> instance methods. + # + # == Auto-generated methods + # See also Instance Public methods below for more details. + # + # === Singular associations (one-to-one) + # | | belongs_to | + # generated methods | belongs_to | :polymorphic | has_one + # ----------------------------------+------------+--------------+--------- + # other(force_reload=false) | X | X | X + # other=(other) | X | X | X + # build_other(attributes={}) | X | | X + # create_other(attributes={}) | X | | X + # create_other!(attributes={}) | X | | X + # + # === Collection associations (one-to-many / many-to-many) + # | | | has_many + # generated methods | habtm | has_many | :through + # ----------------------------------+-------+----------+---------- + # others(force_reload=false) | X | X | X + # others=(other,other,...) | X | X | X + # other_ids | X | X | X + # other_ids=(id,id,...) | X | X | X + # others<< | X | X | X + # others.push | X | X | X + # others.concat | X | X | X + # others.build(attributes={}) | X | X | X + # others.create(attributes={}) | X | X | X + # others.create!(attributes={}) | X | X | X + # others.size | X | X | X + # others.length | X | X | X + # others.count | X | X | X + # others.sum(*args) | X | X | X + # others.empty? | X | X | X + # others.clear | X | X | X + # others.delete(other,other,...) | X | X | X + # others.delete_all | X | X | X + # others.destroy(other,other,...) | X | X | X + # others.destroy_all | X | X | X + # others.find(*args) | X | X | X + # others.exists? | X | X | X + # others.distinct | X | X | X + # others.reset | X | X | X + # + # === 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: + # + # class Car < ActiveRecord::Base + # belongs_to :owner + # belongs_to :old_owner + # def owner=(new_owner) + # self.old_owner = self.owner + # super # end # end # - # === Options - # [:class_name] - # Specify the class name of the association. Use it only if that name can't be inferred - # from the association name. So <tt>has_many :products</tt> will by default be linked - # to the +Product+ class, but if the real class name is +SpecialProduct+, you'll have to - # specify it with this option. - # [:foreign_key] - # Specify the foreign key used for the association. By default this is guessed to be the name - # of this class in lower-case and "_id" suffixed. So a Person class that makes a #has_many - # association will use "person_id" as the default <tt>:foreign_key</tt>. - # [:foreign_type] - # Specify the column used to store the associated object's type, if this is a polymorphic - # association. By default this is guessed to be the name of the polymorphic association - # specified on "as" option with a "_type" suffix. So a class that defines a - # <tt>has_many :tags, as: :taggable</tt> association will use "taggable_type" as the - # default <tt>:foreign_type</tt>. - # [:primary_key] - # Specify the name of the column to use as the primary key for the association. By default this is +id+. - # [:dependent] - # Controls what happens to the associated objects when - # their owner is destroyed. Note that these are implemented as - # callbacks, and Rails executes callbacks in order. Therefore, other - # similar callbacks may affect the <tt>:dependent</tt> behavior, and the - # <tt>:dependent</tt> behavior may affect other callbacks. - # - # * <tt>:destroy</tt> causes all the associated objects to also be destroyed. - # * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not be executed). - # * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Callbacks are not executed. - # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records. - # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects. - # - # If using with the <tt>:through</tt> option, the association on the join model must be - # a #belongs_to, and the records which get deleted are the join records, rather than - # the associated records. - # [:counter_cache] - # This option can be used to configure a custom named <tt>:counter_cache.</tt> You only need this option, - # when you customized the name of your <tt>:counter_cache</tt> on the #belongs_to association. - # [:as] - # Specifies a polymorphic interface (See #belongs_to). - # [:through] - # Specifies an association through which to perform the query. This can be any other type - # of association, including other <tt>:through</tt> associations. Options for <tt>:class_name</tt>, - # <tt>:primary_key</tt> and <tt>:foreign_key</tt> are ignored, as the association uses the - # source reflection. - # - # If the association on the join model is a #belongs_to, the collection can be modified - # and the records on the <tt>:through</tt> model will be automatically created and removed - # as appropriate. Otherwise, the collection is read-only, so you should manipulate the - # <tt>:through</tt> association directly. - # - # If you are going to modify the association (rather than just read from it), then it is - # a good idea to set the <tt>:inverse_of</tt> option on the source association on the - # join model. This allows associated records to be built which will automatically create - # the appropriate join model records when they are saved. (See the 'Association Join Models' - # section above.) - # [:source] - # Specifies the source association name used by #has_many <tt>:through</tt> queries. - # Only use it if the name cannot be inferred from the association. - # <tt>has_many :subscribers, through: :subscriptions</tt> will look for either <tt>:subscribers</tt> or - # <tt>:subscriber</tt> on Subscription, unless a <tt>:source</tt> is given. - # [:source_type] - # Specifies type of the source association used by #has_many <tt>:through</tt> queries where the source - # association is a polymorphic #belongs_to. - # [:validate] - # If +false+, don't validate the associated objects when saving the parent object. true by default. - # [:autosave] - # If true, always save the associated objects or destroy them if marked for destruction, - # when saving the parent object. If false, never save or destroy the associated objects. - # By default, only save associated objects that are new records. This option is implemented as a - # +before_save+ callback. Because callbacks are run in the order they are defined, associated objects - # may need to be explicitly saved in any user-defined +before_save+ callbacks. - # - # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets - # <tt>:autosave</tt> to <tt>true</tt>. - # [:inverse_of] - # Specifies the name of the #belongs_to association on the associated object - # that is the inverse of this #has_many association. Does not work in combination - # with <tt>:through</tt> or <tt>:as</tt> options. - # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. - # [:extend] - # Specifies a module or array of modules that will be extended into the association object returned. - # Useful for defining methods on associations, especially when they should be shared between multiple - # association objects. - # - # Option examples: - # has_many :comments, -> { order("posted_on") } - # has_many :comments, -> { includes(:author) } - # has_many :people, -> { where(deleted: false).order("name") }, class_name: "Person" - # has_many :tracks, -> { order("position") }, dependent: :destroy - # has_many :comments, dependent: :nullify - # has_many :tags, as: :taggable - # has_many :reports, -> { readonly } - # has_many :subscribers, through: :subscriptions, source: :user - def has_many(name, scope = nil, options = {}, &extension) - reflection = Builder::HasMany.build(self, name, scope, options, &extension) - Reflection.add_reflection self, name, reflection - end - - # Specifies a one-to-one association with another class. This method should only be used - # if the other class contains the foreign key. If the current class contains the foreign key, - # then you should use #belongs_to instead. See also ActiveRecord::Associations::ClassMethods's overview - # on when to use #has_one and when to use #belongs_to. - # - # The following methods for retrieval and query of a single associated object will be added: - # - # +association+ is a placeholder for the symbol passed as the +name+ argument, so - # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>. - # - # [association(force_reload = false)] - # Returns the associated object. +nil+ is returned if none is found. - # [association=(associate)] - # Assigns the associate object, extracts the primary key, sets it as the foreign key, - # and saves the associate object. To avoid database inconsistencies, permanently deletes an existing - # associated object when assigning a new one, even if the new one isn't saved to database. - # [build_association(attributes = {})] - # Returns a new object of the associated type that has been instantiated - # with +attributes+ and linked to this object through a foreign key, but has not - # yet been saved. - # [create_association(attributes = {})] - # Returns a new object of the associated type that has been instantiated - # with +attributes+, linked to this object through a foreign key, and that - # has already been saved (if it passed the validation). - # [create_association!(attributes = {})] - # Does the same as <tt>create_association</tt>, but raises ActiveRecord::RecordInvalid - # if the record is invalid. - # - # === Example - # - # An Account class declares <tt>has_one :beneficiary</tt>, which will add: - # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.where(account_id: id).first</tt>) - # * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>) - # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>) - # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>) - # * <tt>Account#create_beneficiary!</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save!; b</tt>) - # - # === Scopes - # - # You can pass a second argument +scope+ as a callable (i.e. proc or - # lambda) to retrieve a specific record or customize the generated query - # when you access the associated object. - # - # Scope examples: - # has_one :author, -> { where(comment_id: 1) } - # has_one :employer, -> { joins(:company) } - # has_one :dob, ->(dob) { where("Date.new(2000, 01, 01) > ?", dob) } - # - # === Options - # - # The declaration can also include an +options+ hash to specialize the behavior of the association. - # - # Options are: - # [:class_name] - # Specify the class name of the association. Use it only if that name can't be inferred - # from the association name. So <tt>has_one :manager</tt> will by default be linked to the Manager class, but - # if the real class name is Person, you'll have to specify it with this option. - # [:dependent] - # Controls what happens to the associated object when - # its owner is destroyed: - # - # * <tt>:destroy</tt> causes the associated object to also be destroyed - # * <tt>:delete</tt> causes the associated object to be deleted directly from the database (so callbacks will not execute) - # * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Callbacks are not executed. - # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there is an associated record - # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there is an associated object - # - # Note that <tt>:dependent</tt> option is ignored when using <tt>:through</tt> option. - # [:foreign_key] - # Specify the foreign key used for the association. By default this is guessed to be the name - # of this class in lower-case and "_id" suffixed. So a Person class that makes a #has_one association - # will use "person_id" as the default <tt>:foreign_key</tt>. - # [:foreign_type] - # Specify the column used to store the associated object's type, if this is a polymorphic - # association. By default this is guessed to be the name of the polymorphic association - # specified on "as" option with a "_type" suffix. So a class that defines a - # <tt>has_one :tag, as: :taggable</tt> association will use "taggable_type" as the - # default <tt>:foreign_type</tt>. - # [:primary_key] - # Specify the method that returns the primary key used for the association. By default this is +id+. - # [:as] - # Specifies a polymorphic interface (See #belongs_to). - # [:through] - # Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt>, - # <tt>:primary_key</tt>, and <tt>:foreign_key</tt> are ignored, as the association uses the - # source reflection. You can only use a <tt>:through</tt> query through a #has_one - # or #belongs_to association on the join model. - # [:source] - # Specifies the source association name used by #has_one <tt>:through</tt> queries. - # Only use it if the name cannot be inferred from the association. - # <tt>has_one :favorite, through: :favorites</tt> will look for a - # <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given. - # [:source_type] - # Specifies type of the source association used by #has_one <tt>:through</tt> queries where the source - # association is a polymorphic #belongs_to. - # [:validate] - # If +false+, don't validate the associated object when saving the parent object. +false+ by default. - # [:autosave] - # If true, always save the associated object or destroy it if marked for destruction, - # when saving the parent object. If false, never save or destroy the associated object. - # By default, only save the associated object if it's a new record. - # - # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets - # <tt>:autosave</tt> to <tt>true</tt>. - # [:inverse_of] - # Specifies the name of the #belongs_to association on the associated object - # that is the inverse of this #has_one association. Does not work in combination - # with <tt>:through</tt> or <tt>:as</tt> options. - # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. - # [:required] - # When set to +true+, the association will also have its presence validated. - # This will validate the association itself, not the id. You can use - # +:inverse_of+ to avoid an extra query during validation. - # - # Option examples: - # has_one :credit_card, dependent: :destroy # destroys the associated credit card - # has_one :credit_card, dependent: :nullify # updates the associated records foreign - # # key value to NULL rather than destroying it - # has_one :last_comment, -> { order('posted_on') }, class_name: "Comment" - # has_one :project_manager, -> { where(role: 'project_manager') }, class_name: "Person" - # has_one :attachment, as: :attachable - # has_one :boss, -> { readonly } - # has_one :club, through: :membership - # has_one :primary_address, -> { where(primary: true) }, through: :addressables, source: :addressable - # has_one :credit_card, required: true - def has_one(name, scope = nil, options = {}) - reflection = Builder::HasOne.build(self, name, scope, options) - Reflection.add_reflection self, name, reflection - end - - # Specifies a one-to-one association with another class. This method should only be used - # if this class contains the foreign key. If the other class contains the foreign key, - # then you should use #has_one instead. See also ActiveRecord::Associations::ClassMethods's overview - # on when to use #has_one and when to use #belongs_to. - # - # Methods will be added for retrieval and query for a single associated object, for which - # this object holds an id: - # - # +association+ is a placeholder for the symbol passed as the +name+ argument, so - # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>. - # - # [association(force_reload = false)] - # Returns the associated object. +nil+ is returned if none is found. - # [association=(associate)] - # Assigns the associate object, extracts the primary key, and sets it as the foreign key. - # [build_association(attributes = {})] - # Returns a new object of the associated type that has been instantiated - # with +attributes+ and linked to this object through a foreign key, but has not yet been saved. - # [create_association(attributes = {})] - # Returns a new object of the associated type that has been instantiated - # with +attributes+, linked to this object through a foreign key, and that - # has already been saved (if it passed the validation). - # [create_association!(attributes = {})] - # Does the same as <tt>create_association</tt>, but raises ActiveRecord::RecordInvalid - # if the record is invalid. - # - # === Example - # - # A Post class declares <tt>belongs_to :author</tt>, which will add: - # * <tt>Post#author</tt> (similar to <tt>Author.find(author_id)</tt>) - # * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>) - # * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>) - # * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>) - # * <tt>Post#create_author!</tt> (similar to <tt>post.author = Author.new; post.author.save!; post.author</tt>) - # The declaration can also include an +options+ hash to specialize the behavior of the association. - # - # === Scopes - # - # You can pass a second argument +scope+ as a callable (i.e. proc or - # lambda) to retrieve a specific record or customize the generated query - # when you access the associated object. - # - # Scope examples: - # belongs_to :firm, -> { where(id: 2) } - # belongs_to :user, -> { joins(:friends) } - # belongs_to :level, ->(level) { where("game_level > ?", level.current) } - # - # === Options - # - # [:class_name] - # Specify the class name of the association. Use it only if that name can't be inferred - # from the association name. So <tt>belongs_to :author</tt> will by default be linked to the Author class, but - # if the real class name is Person, you'll have to specify it with this option. - # [:foreign_key] - # Specify the foreign key used for the association. By default this is guessed to be the name - # of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :person</tt> - # association will use "person_id" as the default <tt>:foreign_key</tt>. Similarly, - # <tt>belongs_to :favorite_person, class_name: "Person"</tt> will use a foreign key - # of "favorite_person_id". - # [:foreign_type] - # Specify the column used to store the associated object's type, if this is a polymorphic - # association. By default this is guessed to be the name of the association with a "_type" - # suffix. So a class that defines a <tt>belongs_to :taggable, polymorphic: true</tt> - # association will use "taggable_type" as the default <tt>:foreign_type</tt>. - # [:primary_key] - # Specify the method that returns the primary key of associated object used for the association. - # By default this is id. - # [:dependent] - # If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to - # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method. - # This option should not be specified when #belongs_to is used in conjunction with - # a #has_many relationship on another class because of the potential to leave - # orphaned records behind. - # [:counter_cache] - # Caches the number of belonging objects on the associate class through the use of CounterCache::ClassMethods#increment_counter - # and CounterCache::ClassMethods#decrement_counter. The counter cache is incremented when an object of this - # class is created and decremented when it's destroyed. This requires that a column - # named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class) - # is used on the associate class (such as a Post class) - that is the migration for - # <tt>#{table_name}_count</tt> is created on the associate class (such that <tt>Post.comments_count</tt> will - # return the count cached, see note below). You can also specify a custom counter - # cache column by providing a column name instead of a +true+/+false+ value to this - # option (e.g., <tt>counter_cache: :my_custom_counter</tt>.) - # Note: Specifying a counter cache will add it to that model's list of readonly attributes - # using +attr_readonly+. - # [:polymorphic] - # Specify this association is a polymorphic association by passing +true+. - # Note: If you've enabled the counter cache, then you may want to add the counter cache attribute - # to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>). - # [:validate] - # If +false+, don't validate the associated objects when saving the parent object. +false+ by default. - # [:autosave] - # If true, always save the associated object or destroy it if marked for destruction, when - # saving the parent object. - # If false, never save or destroy the associated object. - # By default, only save the associated object if it's a new record. - # - # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for - # sets <tt>:autosave</tt> to <tt>true</tt>. - # [:touch] - # If true, the associated object will be touched (the updated_at/on attributes set to current time) - # when this record is either saved or destroyed. If you specify a symbol, that attribute - # will be updated with the current time in addition to the updated_at/on attribute. - # Please note that with touching no validation is performed and only the +after_touch+, - # +after_commit+ and +after_rollback+ callbacks are executed. - # [:inverse_of] - # Specifies the name of the #has_one or #has_many association on the associated - # object that is the inverse of this #belongs_to association. Does not work in - # combination with the <tt>:polymorphic</tt> options. - # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. - # [:optional] - # When set to +true+, the association will not have its presence validated. - # [:required] - # When set to +true+, the association will also have its presence validated. - # This will validate the association itself, not the id. You can use - # +:inverse_of+ to avoid an extra query during validation. - # NOTE: <tt>required</tt> is set to <tt>true</tt> by default and is deprecated. If - # you don't want to have association presence validated, use <tt>optional: true</tt>. - # - # Option examples: - # belongs_to :firm, foreign_key: "client_of" - # belongs_to :person, primary_key: "name", foreign_key: "person_name" - # belongs_to :author, class_name: "Person", foreign_key: "author_id" - # belongs_to :valid_coupon, ->(o) { where "discounts > ?", o.payments_count }, - # class_name: "Coupon", foreign_key: "coupon_id" - # belongs_to :attachable, polymorphic: true - # belongs_to :project, -> { readonly } - # belongs_to :post, counter_cache: true - # belongs_to :comment, touch: true - # belongs_to :company, touch: :employees_last_updated_at - # belongs_to :user, optional: true - def belongs_to(name, scope = nil, options = {}) - reflection = Builder::BelongsTo.build(self, name, scope, options) - Reflection.add_reflection self, name, reflection - end - - # Specifies a many-to-many relationship with another class. This associates two classes via an - # intermediate join table. Unless the join table is explicitly specified as an option, it is - # guessed using the lexical order of the class names. So a join between Developer and Project - # will give the default join table name of "developers_projects" because "D" precedes "P" alphabetically. - # Note that this precedence is calculated using the <tt><</tt> operator for String. This - # means that if the strings are of different lengths, and the strings are equal when compared - # up to the shortest length, then the longer string is considered of higher - # lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers" - # to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", - # but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the - # custom <tt>:join_table</tt> option if you need to. - # If your tables share a common prefix, it will only appear once at the beginning. For example, - # the tables "catalog_categories" and "catalog_products" generate a join table name of "catalog_categories_products". - # - # The join table should not have a primary key or a model associated with it. You must manually generate the - # join table with a migration such as this: - # - # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[5.0] - # def change - # create_join_table :developers, :projects + # 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. + # + # == Cardinality and associations + # + # Active Record associations can be used to describe one-to-one, one-to-many and many-to-many + # relationships between models. Each model uses an association to describe its role in + # the relation. The #belongs_to association is always used in the model that has + # the foreign key. + # + # === One-to-one + # + # Use #has_one in the base, and #belongs_to in the associated model. + # + # class Employee < ActiveRecord::Base + # has_one :office + # end + # class Office < ActiveRecord::Base + # belongs_to :employee # foreign key - employee_id + # end + # + # === One-to-many + # + # Use #has_many in the base, and #belongs_to in the associated model. + # + # class Manager < ActiveRecord::Base + # has_many :employees + # end + # class Employee < ActiveRecord::Base + # belongs_to :manager # foreign key - manager_id + # end + # + # === Many-to-many + # + # There are two ways to build a many-to-many relationship. + # + # The first way uses a #has_many association with the <tt>:through</tt> option and a join model, so + # there are two stages of associations. + # + # class Assignment < ActiveRecord::Base + # belongs_to :programmer # foreign key - programmer_id + # belongs_to :project # foreign key - project_id + # end + # class Programmer < ActiveRecord::Base + # has_many :assignments + # has_many :projects, through: :assignments + # end + # class Project < ActiveRecord::Base + # has_many :assignments + # has_many :programmers, through: :assignments + # end + # + # For the second way, use #has_and_belongs_to_many in both models. This requires a join table + # that has no corresponding model or primary key. + # + # class Programmer < ActiveRecord::Base + # has_and_belongs_to_many :projects # foreign keys in the join table + # end + # class Project < ActiveRecord::Base + # has_and_belongs_to_many :programmers # foreign keys in the join table + # end + # + # Choosing which way to build a many-to-many relationship is not always simple. + # If you need to work with the relationship model as its own entity, + # use #has_many <tt>:through</tt>. Use #has_and_belongs_to_many when working with legacy schemas or when + # you never work directly with the relationship itself. + # + # == Is it a #belongs_to or #has_one association? + # + # Both express a 1-1 relationship. The difference is mostly where to place the foreign + # key, which goes on the table for the class declaring the #belongs_to relationship. + # + # class User < ActiveRecord::Base + # # I reference an account. + # belongs_to :account + # end + # + # class Account < ActiveRecord::Base + # # One user references me. + # has_one :user + # end + # + # The tables for these classes could look something like: + # + # CREATE TABLE users ( + # id int NOT NULL auto_increment, + # account_id int default NULL, + # name varchar default NULL, + # PRIMARY KEY (id) + # ) + # + # CREATE TABLE accounts ( + # id int NOT NULL auto_increment, + # name varchar default NULL, + # PRIMARY KEY (id) + # ) + # + # == Unsaved objects and associations + # + # You can manipulate objects and associations before they are saved to the database, but + # there is some special behavior you should be aware of, mostly involving the saving of + # associated objects. + # + # You can set the <tt>:autosave</tt> option on a #has_one, #belongs_to, + # #has_many, or #has_and_belongs_to_many association. Setting it + # to +true+ will _always_ save the members, whereas setting it to +false+ will + # _never_ save the members. More details about <tt>:autosave</tt> option is available at + # AutosaveAssociation. + # + # === One-to-one associations + # + # * Assigning an object to a #has_one association automatically saves that object and + # the object being replaced (if there is one), in order to update their foreign + # keys - except if the parent object is unsaved (<tt>new_record? == true</tt>). + # * If either of these saves fail (due to one of the objects being invalid), an + # ActiveRecord::RecordNotSaved exception is raised and the assignment is + # cancelled. + # * If you wish to assign an object to a #has_one association without saving it, + # use the <tt>#build_association</tt> method (documented below). The object being + # replaced will still be saved to update its foreign key. + # * Assigning an object to a #belongs_to association does not save the object, since + # the foreign key field belongs on the parent. It does not save the parent either. + # + # === Collections + # + # * Adding an object to a collection (#has_many or #has_and_belongs_to_many) automatically + # saves that object, except if the parent object (the owner of the collection) is not yet + # stored in the database. + # * If saving any of the objects being added to a collection (via <tt>push</tt> or similar) + # fails, then <tt>push</tt> returns +false+. + # * If saving fails while replacing the collection (via <tt>association=</tt>), an + # ActiveRecord::RecordNotSaved exception is raised and the assignment is + # cancelled. + # * You can add an object to a collection without automatically saving it by using the + # <tt>collection.build</tt> method (documented below). + # * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically + # saved when the parent is saved. + # + # == Customizing the query + # + # \Associations are built from <tt>Relation</tt> objects, and you can use the Relation syntax + # to customize them. For example, to add a condition: + # + # class Blog < ActiveRecord::Base + # has_many :published_posts, -> { where(published: true) }, class_name: 'Post' + # end + # + # Inside the <tt>-> { ... }</tt> block you can use all of the usual Relation methods. + # + # === Accessing the owner object + # + # Sometimes it is useful to have access to the owner object when building the query. The owner + # is passed as a parameter to the block. For example, the following association would find all + # events that occur on the user's birthday: + # + # class User < ActiveRecord::Base + # has_many :birthday_events, ->(user) { where(starts_on: user.birthday) }, class_name: 'Event' + # end + # + # Note: Joining, eager loading and preloading of these associations is not fully possible. + # These operations happen before instance creation and the scope will be called with a +nil+ argument. + # This can lead to unexpected behavior and is deprecated. + # + # == Association callbacks + # + # Similar to the normal callbacks that hook into the life cycle of an Active Record object, + # you can also define callbacks that get triggered when you add an object to or remove an + # object from an association collection. + # + # class Project + # has_and_belongs_to_many :developers, after_add: :evaluate_velocity + # + # def evaluate_velocity(developer) + # ... # end # end # - # It's also a good idea to add indexes to each of those columns to speed up the joins process. - # However, in MySQL it is advised to add a compound index for both of the columns as MySQL only - # uses one index per table during the lookup. - # - # Adds the following methods for retrieval and query: - # - # +collection+ is a placeholder for the symbol passed as the +name+ argument, so - # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>. - # - # [collection(force_reload = false)] - # Returns an array of all the associated objects. - # An empty array is returned if none are found. - # [collection<<(object, ...)] - # Adds one or more objects to the collection by creating associations in the join table - # (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method). - # Note that this operation instantly fires update SQL without waiting for the save or update call on the - # parent object, unless the parent object is a new record. - # [collection.delete(object, ...)] - # Removes one or more objects from the collection by removing their associations from the join table. - # This does not destroy the objects. - # [collection.destroy(object, ...)] - # Removes one or more objects from the collection by running destroy on each association in the join table, overriding any dependent option. - # This does not destroy the objects. - # [collection=objects] - # Replaces the collection's content by deleting and adding objects as appropriate. - # [collection_singular_ids] - # Returns an array of the associated objects' ids. - # [collection_singular_ids=ids] - # Replace the collection by the objects identified by the primary keys in +ids+. - # [collection.clear] - # Removes every object from the collection. This does not destroy the objects. - # [collection.empty?] - # Returns +true+ if there are no associated objects. - # [collection.size] - # Returns the number of associated objects. - # [collection.find(id)] - # Finds an associated object responding to the +id+ and that - # meets the condition that it has to be associated with this object. - # Uses the same rules as ActiveRecord::FinderMethods#find. - # [collection.exists?(...)] - # Checks whether an associated object with the given conditions exists. - # Uses the same rules as ActiveRecord::FinderMethods#exists?. - # [collection.build(attributes = {})] - # Returns a new object of the collection type that has been instantiated - # with +attributes+ and linked to this object through the join table, but has not yet been saved. - # [collection.create(attributes = {})] - # Returns a new object of the collection type that has been instantiated - # with +attributes+, linked to this object through the join table, and that has already been - # saved (if it passed the validation). - # - # === Example - # - # A Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add: - # * <tt>Developer#projects</tt> - # * <tt>Developer#projects<<</tt> - # * <tt>Developer#projects.delete</tt> - # * <tt>Developer#projects.destroy</tt> - # * <tt>Developer#projects=</tt> - # * <tt>Developer#project_ids</tt> - # * <tt>Developer#project_ids=</tt> - # * <tt>Developer#projects.clear</tt> - # * <tt>Developer#projects.empty?</tt> - # * <tt>Developer#projects.size</tt> - # * <tt>Developer#projects.find(id)</tt> - # * <tt>Developer#projects.exists?(...)</tt> - # * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("developer_id" => id)</tt>) - # * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("developer_id" => id); c.save; c</tt>) - # The declaration may include an +options+ hash to specialize the behavior of the association. - # - # === Scopes - # - # You can pass a second argument +scope+ as a callable (i.e. proc or - # lambda) to retrieve a specific set of records or customize the generated - # query when you access the associated collection. - # - # Scope examples: - # has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) } - # has_and_belongs_to_many :categories, ->(category) { - # where("default_category = ?", category.name) - # } - # - # === Extensions - # - # The +extension+ argument allows you to pass a block into a - # has_and_belongs_to_many association. This is useful for adding new - # finders, creators and other factory-type methods to be used as part of - # the association. - # - # Extension examples: - # has_and_belongs_to_many :contractors do + # It's possible to stack callbacks by passing them as an array. Example: + # + # class Project + # has_and_belongs_to_many :developers, + # after_add: [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}] + # end + # + # Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+. + # + # If any of the +before_add+ callbacks throw an exception, the object will not be + # added to the collection. + # + # Similarly, if any of the +before_remove+ callbacks throw an exception, the object + # will not be removed from the collection. + # + # == Association extensions + # + # The proxy objects that control the access to associations can be extended through anonymous + # modules. This is especially beneficial for adding new finders, creators, and other + # factory-type methods that are only used as part of this association. + # + # class Account < ActiveRecord::Base + # has_many :people do + # def find_or_create_by_name(name) + # first_name, last_name = name.split(" ", 2) + # find_or_create_by(first_name: first_name, last_name: last_name) + # end + # end + # end + # + # person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson") + # person.first_name # => "David" + # person.last_name # => "Heinemeier Hansson" + # + # If you need to share the same extensions between many associations, you can use a named + # extension module. + # + # module FindOrCreateByNameExtension # def find_or_create_by_name(name) # first_name, last_name = name.split(" ", 2) # find_or_create_by(first_name: first_name, last_name: last_name) # end # end # - # === Options - # - # [:class_name] - # Specify the class name of the association. Use it only if that name can't be inferred - # from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the - # Project class, but if the real class name is SuperProject, you'll have to specify it with this option. - # [:join_table] - # Specify the name of the join table if the default based on lexical order isn't what you want. - # <b>WARNING:</b> If you're overwriting the table name of either class, the +table_name+ method - # MUST be declared underneath any #has_and_belongs_to_many declaration in order to work. - # [:foreign_key] - # Specify the foreign key used for the association. By default this is guessed to be the name - # of this class in lower-case and "_id" suffixed. So a Person class that makes - # a #has_and_belongs_to_many association to Project will use "person_id" as the - # default <tt>:foreign_key</tt>. - # [:association_foreign_key] - # Specify the foreign key used for the association on the receiving side of the association. - # By default this is guessed to be the name of the associated class in lower-case and "_id" suffixed. - # So if a Person class makes a #has_and_belongs_to_many association to Project, - # the association will use "project_id" as the default <tt>:association_foreign_key</tt>. - # [:validate] - # If +false+, don't validate the associated objects when saving the parent object. +true+ by default. - # [:autosave] - # If true, always save the associated objects or destroy them if marked for destruction, when - # saving the parent object. - # If false, never save or destroy the associated objects. - # By default, only save associated objects that are new records. - # - # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets - # <tt>:autosave</tt> to <tt>true</tt>. - # - # Option examples: - # has_and_belongs_to_many :projects - # has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) } - # has_and_belongs_to_many :nations, class_name: "Country" - # has_and_belongs_to_many :categories, join_table: "prods_cats" - # has_and_belongs_to_many :categories, -> { readonly } - def has_and_belongs_to_many(name, scope = nil, options = {}, &extension) - if scope.is_a?(Hash) - options = scope - scope = nil + # class Account < ActiveRecord::Base + # has_many :people, -> { extending FindOrCreateByNameExtension } + # end + # + # class Company < ActiveRecord::Base + # has_many :people, -> { extending FindOrCreateByNameExtension } + # end + # + # Some extensions can only be made to work with knowledge of the association's internals. + # Extensions can access relevant state using the following methods (where +items+ is the + # name of the association): + # + # * <tt>record.association(:items).owner</tt> - Returns the object the association is part of. + # * <tt>record.association(:items).reflection</tt> - Returns the reflection object that describes the association. + # * <tt>record.association(:items).target</tt> - Returns the associated object for #belongs_to and #has_one, or + # the collection of associated objects for #has_many and #has_and_belongs_to_many. + # + # However, inside the actual extension code, you will not have access to the <tt>record</tt> as + # above. In this case, you can access <tt>proxy_association</tt>. For example, + # <tt>record.association(:items)</tt> and <tt>record.items.proxy_association</tt> will return + # the same object, allowing you to make calls like <tt>proxy_association.owner</tt> inside + # association extensions. + # + # == Association Join Models + # + # Has Many associations can be configured with the <tt>:through</tt> option to use an + # explicit join model to retrieve the data. This operates similarly to a + # #has_and_belongs_to_many association. The advantage is that you're able to add validations, + # callbacks, and extra attributes on the join model. Consider the following schema: + # + # class Author < ActiveRecord::Base + # has_many :authorships + # has_many :books, through: :authorships + # end + # + # class Authorship < ActiveRecord::Base + # belongs_to :author + # belongs_to :book + # end + # + # @author = Author.first + # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to + # @author.books # selects all books by using the Authorship join model + # + # You can also go through a #has_many association on the join model: + # + # class Firm < ActiveRecord::Base + # has_many :clients + # has_many :invoices, through: :clients + # end + # + # class Client < ActiveRecord::Base + # belongs_to :firm + # has_many :invoices + # end + # + # class Invoice < ActiveRecord::Base + # belongs_to :client + # end + # + # @firm = Firm.first + # @firm.clients.flat_map { |c| c.invoices } # select all invoices for all clients of the firm + # @firm.invoices # selects all invoices by going through the Client join model + # + # Similarly you can go through a #has_one association on the join model: + # + # class Group < ActiveRecord::Base + # has_many :users + # has_many :avatars, through: :users + # end + # + # class User < ActiveRecord::Base + # belongs_to :group + # has_one :avatar + # end + # + # class Avatar < ActiveRecord::Base + # belongs_to :user + # end + # + # @group = Group.first + # @group.users.collect { |u| u.avatar }.compact # select all avatars for all users in the group + # @group.avatars # selects all avatars by going through the User join model. + # + # An important caveat with going through #has_one or #has_many associations on the + # join model is that these associations are *read-only*. For example, the following + # would not work following the previous example: + # + # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around + # @group.avatars.delete(@group.avatars.last) # so would this + # + # == Setting Inverses + # + # If you are using a #belongs_to on the join model, it is a good idea to set the + # <tt>:inverse_of</tt> option on the #belongs_to, which will mean that the following example + # works correctly (where <tt>tags</tt> is a #has_many <tt>:through</tt> association): + # + # @post = Post.first + # @tag = @post.tags.build name: "ruby" + # @tag.save + # + # The last line ought to save the through record (a <tt>Tagging</tt>). This will only work if the + # <tt>:inverse_of</tt> is set: + # + # class Tagging < ActiveRecord::Base + # belongs_to :post + # belongs_to :tag, inverse_of: :taggings + # end + # + # If you do not set the <tt>:inverse_of</tt> record, the association will + # do its best to match itself up with the correct inverse. Automatic + # inverse detection only works on #has_many, #has_one, and + # #belongs_to associations. + # + # Extra options on the associations, as defined in the + # <tt>AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS</tt> constant, will + # also prevent the association's inverse from being found automatically. + # + # The automatic guessing of the inverse association uses a heuristic based + # on the name of the class, so it may not work for all associations, + # especially the ones with non-standard names. + # + # You can turn off the automatic detection of inverse associations by setting + # the <tt>:inverse_of</tt> option to <tt>false</tt> like so: + # + # class Tagging < ActiveRecord::Base + # belongs_to :tag, inverse_of: false + # end + # + # == Nested \Associations + # + # You can actually specify *any* association with the <tt>:through</tt> option, including an + # association which has a <tt>:through</tt> option itself. For example: + # + # class Author < ActiveRecord::Base + # has_many :posts + # has_many :comments, through: :posts + # has_many :commenters, through: :comments + # end + # + # class Post < ActiveRecord::Base + # has_many :comments + # end + # + # class Comment < ActiveRecord::Base + # belongs_to :commenter + # end + # + # @author = Author.first + # @author.commenters # => People who commented on posts written by the author + # + # An equivalent way of setting up this association this would be: + # + # class Author < ActiveRecord::Base + # has_many :posts + # has_many :commenters, through: :posts + # end + # + # class Post < ActiveRecord::Base + # has_many :comments + # has_many :commenters, through: :comments + # end + # + # class Comment < ActiveRecord::Base + # belongs_to :commenter + # end + # + # When using a nested association, you will not be able to modify the association because there + # is not enough information to know what modification to make. For example, if you tried to + # add a <tt>Commenter</tt> in the example above, there would be no way to tell how to set up the + # intermediate <tt>Post</tt> and <tt>Comment</tt> objects. + # + # == Polymorphic \Associations + # + # Polymorphic associations on models are not restricted on what types of models they + # can be associated with. Rather, they specify an interface that a #has_many association + # must adhere to. + # + # class Asset < ActiveRecord::Base + # belongs_to :attachable, polymorphic: true + # end + # + # class Post < ActiveRecord::Base + # has_many :assets, as: :attachable # The :as option specifies the polymorphic interface to use. + # end + # + # @asset.attachable = @post + # + # This works by using a type column in addition to a foreign key to specify the associated + # record. In the Asset example, you'd need an +attachable_id+ integer column and an + # +attachable_type+ string column. + # + # Using polymorphic associations in combination with single table inheritance (STI) is + # a little tricky. In order for the associations to work as expected, ensure that you + # store the base model for the STI models in the type column of the polymorphic + # association. To continue with the asset example above, suppose there are guest posts + # and member posts that use the posts table for STI. In this case, there must be a +type+ + # column in the posts table. + # + # Note: The <tt>attachable_type=</tt> method is being called when assigning an +attachable+. + # The +class_name+ of the +attachable+ is passed as a String. + # + # class Asset < ActiveRecord::Base + # belongs_to :attachable, polymorphic: true + # + # def attachable_type=(class_name) + # super(class_name.constantize.base_class.to_s) + # end + # end + # + # class Post < ActiveRecord::Base + # # because we store "Post" in attachable_type now dependent: :destroy will work + # has_many :assets, as: :attachable, dependent: :destroy + # end + # + # class GuestPost < Post + # end + # + # class MemberPost < Post + # end + # + # == Caching + # + # All of the methods are built on a simple caching principle that will keep the result + # of the last query around unless specifically instructed not to. The cache is even + # shared across methods to make it even cheaper to use the macro-added methods without + # worrying too much about performance at the first go. + # + # project.milestones # fetches milestones from the database + # project.milestones.size # uses the milestone cache + # project.milestones.empty? # uses the milestone cache + # project.milestones(true).size # fetches milestones from the database + # project.milestones # uses the milestone cache + # + # == Eager loading of associations + # + # Eager loading is a way to find objects of a certain class and a number of named associations. + # It is one of the easiest ways to prevent the dreaded N+1 problem in which fetching 100 + # posts that each need to display their author triggers 101 database queries. Through the + # use of eager loading, the number of queries will be reduced from 101 to 2. + # + # class Post < ActiveRecord::Base + # belongs_to :author + # has_many :comments + # end + # + # Consider the following loop using the class above: + # + # Post.all.each do |post| + # puts "Post: " + post.title + # puts "Written by: " + post.author.name + # puts "Last comment on: " + post.comments.first.created_on + # end + # + # To iterate over these one hundred posts, we'll generate 201 database queries. Let's + # first just optimize it for retrieving the author: + # + # Post.includes(:author).each do |post| + # + # This references the name of the #belongs_to association that also used the <tt>:author</tt> + # symbol. After loading the posts, +find+ will collect the +author_id+ from each one and load + # all of the referenced authors with one query. Doing so will cut down the number of queries + # from 201 to 102. + # + # We can improve upon the situation further by referencing both associations in the finder with: + # + # Post.includes(:author, :comments).each do |post| + # + # This will load all comments with a single query. This reduces the total number of queries + # to 3. In general, the number of queries will be 1 plus the number of associations + # named (except if some of the associations are polymorphic #belongs_to - see below). + # + # To include a deep hierarchy of associations, use a hash: + # + # Post.includes(:author, { comments: { author: :gravatar } }).each do |post| + # + # The above code will load all the comments and all of their associated + # authors and gravatars. You can mix and match any combination of symbols, + # arrays, and hashes to retrieve the associations you want to load. + # + # All of this power shouldn't fool you into thinking that you can pull out huge amounts + # of data with no performance penalty just because you've reduced the number of queries. + # The database still needs to send all the data to Active Record and it still needs to + # be processed. So it's no catch-all for performance problems, but it's a great way to + # cut down on the number of queries in a situation as the one described above. + # + # Since only one table is loaded at a time, conditions or orders cannot reference tables + # other than the main one. If this is the case, Active Record falls back to the previously + # used <tt>LEFT OUTER JOIN</tt> based strategy. For example: + # + # Post.includes([:author, :comments]).where(['comments.approved = ?', true]) + # + # This will result in a single SQL query with joins along the lines of: + # <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and + # <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Note that using conditions + # like this can have unintended consequences. + # In the above example, posts with no approved comments are not returned at all because + # the conditions apply to the SQL statement as a whole and not just to the association. + # + # You must disambiguate column references for this fallback to happen, for example + # <tt>order: "author.name DESC"</tt> will work but <tt>order: "name DESC"</tt> will not. + # + # If you want to load all posts (including posts with no approved comments), then write + # your own <tt>LEFT OUTER JOIN</tt> query using <tt>ON</tt>: + # + # Post.joins("LEFT OUTER JOIN comments ON comments.post_id = posts.id AND comments.approved = '1'") + # + # In this case, it is usually more natural to include an association which has conditions defined on it: + # + # class Post < ActiveRecord::Base + # has_many :approved_comments, -> { where(approved: true) }, class_name: 'Comment' + # end + # + # Post.includes(:approved_comments) + # + # This will load posts and eager load the +approved_comments+ association, which contains + # only those comments that have been approved. + # + # If you eager load an association with a specified <tt>:limit</tt> option, it will be ignored, + # returning all the associated objects: + # + # class Picture < ActiveRecord::Base + # has_many :most_recent_comments, -> { order('id DESC').limit(10) }, class_name: 'Comment' + # end + # + # Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments. + # + # Eager loading is supported with polymorphic associations. + # + # class Address < ActiveRecord::Base + # belongs_to :addressable, polymorphic: true + # end + # + # A call that tries to eager load the addressable model + # + # Address.includes(:addressable) + # + # This will execute one query to load the addresses and load the addressables with one + # query per addressable type. + # For example, if all the addressables are either of class Person or Company, then a total + # of 3 queries will be executed. The list of addressable types to load is determined on + # the back of the addresses loaded. This is not supported if Active Record has to fallback + # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. + # The reason is that the parent model's type is a column value so its corresponding table + # name cannot be put in the +FROM+/+JOIN+ clauses of that query. + # + # == Table Aliasing + # + # Active Record uses table aliasing in the case that a table is referenced multiple times + # in a join. If a table is referenced only once, the standard table name is used. The + # second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>. + # Indexes are appended for any more successive uses of the table name. + # + # Post.joins(:comments) + # # => SELECT ... FROM posts INNER JOIN comments ON ... + # Post.joins(:special_comments) # STI + # # => SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment' + # Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name + # # => SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts + # + # Acts as tree example: + # + # TreeMixin.joins(:children) + # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... + # TreeMixin.joins(children: :parent) + # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... + # INNER JOIN parents_mixins ... + # TreeMixin.joins(children: {parent: :children}) + # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... + # INNER JOIN parents_mixins ... + # INNER JOIN mixins childrens_mixins_2 + # + # Has and Belongs to Many join tables use the same idea, but add a <tt>_join</tt> suffix: + # + # Post.joins(:categories) + # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... + # Post.joins(categories: :posts) + # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... + # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories + # Post.joins(categories: {posts: :categories}) + # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... + # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories + # INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2 + # + # If you wish to specify your own custom joins using ActiveRecord::QueryMethods#joins method, those table + # names will take precedence over the eager associations: + # + # Post.joins(:comments).joins("inner join comments ...") + # # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ... + # Post.joins(:comments, :special_comments).joins("inner join comments ...") + # # => SELECT ... FROM posts INNER JOIN comments comments_posts ON ... + # INNER JOIN comments special_comments_posts ... + # INNER JOIN comments ... + # + # Table aliases are automatically truncated according to the maximum length of table identifiers + # according to the specific database. + # + # == Modules + # + # By default, associations will look for objects within the current module scope. Consider: + # + # module MyApplication + # module Business + # class Firm < ActiveRecord::Base + # has_many :clients + # end + # + # class Client < ActiveRecord::Base; end + # end + # end + # + # When <tt>Firm#clients</tt> is called, it will in turn call + # <tt>MyApplication::Business::Client.find_all_by_firm_id(firm.id)</tt>. + # If you want to associate with a class in another module scope, this can be done by + # specifying the complete class name. + # + # module MyApplication + # module Business + # class Firm < ActiveRecord::Base; end + # end + # + # module Billing + # class Account < ActiveRecord::Base + # belongs_to :firm, class_name: "MyApplication::Business::Firm" + # end + # end + # end + # + # == Bi-directional associations + # + # When you specify an association, there is usually an association on the associated model + # that specifies the same relationship in reverse. For example, with the following models: + # + # class Dungeon < ActiveRecord::Base + # has_many :traps + # has_one :evil_wizard + # end + # + # class Trap < ActiveRecord::Base + # belongs_to :dungeon + # end + # + # class EvilWizard < ActiveRecord::Base + # belongs_to :dungeon + # end + # + # The +traps+ association on +Dungeon+ and the +dungeon+ association on +Trap+ are + # the inverse of each other, and the inverse of the +dungeon+ association on +EvilWizard+ + # is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default, + # Active Record can guess the inverse of the association based on the name + # of the class. The result is the following: + # + # d = Dungeon.first + # t = d.traps.first + # d.object_id == t.dungeon.object_id # => true + # + # The +Dungeon+ instances +d+ and <tt>t.dungeon</tt> in the above example refer to + # the same in-memory instance since the association matches the name of the class. + # The result would be the same if we added +:inverse_of+ to our model definitions: + # + # class Dungeon < ActiveRecord::Base + # has_many :traps, inverse_of: :dungeon + # has_one :evil_wizard, inverse_of: :dungeon + # end + # + # class Trap < ActiveRecord::Base + # belongs_to :dungeon, inverse_of: :traps + # end + # + # class EvilWizard < ActiveRecord::Base + # belongs_to :dungeon, inverse_of: :evil_wizard + # end + # + # There are limitations to <tt>:inverse_of</tt> support: + # + # * does not work with <tt>:through</tt> associations. + # * does not work with <tt>:polymorphic</tt> associations. + # * inverse associations for #belongs_to associations #has_many are ignored. + # + # For more information, see the documentation for the +:inverse_of+ option. + # + # == Deleting from associations + # + # === Dependent associations + # + # #has_many, #has_one, and #belongs_to associations support the <tt>:dependent</tt> option. + # This allows you to specify that associated records should be deleted when the owner is + # deleted. + # + # For example: + # + # class Author + # has_many :posts, dependent: :destroy + # end + # Author.find(1).destroy # => Will destroy all of the author's posts, too + # + # The <tt>:dependent</tt> option can have different values which specify how the deletion + # is done. For more information, see the documentation for this option on the different + # specific association types. When no option is given, the behavior is to do nothing + # with the associated records when destroying a record. + # + # Note that <tt>:dependent</tt> is implemented using Rails' callback + # system, which works by processing callbacks in order. Therefore, other + # callbacks declared either before or after the <tt>:dependent</tt> option + # can affect what it does. + # + # Note that <tt>:dependent</tt> option is ignored for #has_one <tt>:through</tt> associations. + # + # === Delete or destroy? + # + # #has_many and #has_and_belongs_to_many associations have the methods <tt>destroy</tt>, + # <tt>delete</tt>, <tt>destroy_all</tt> and <tt>delete_all</tt>. + # + # For #has_and_belongs_to_many, <tt>delete</tt> and <tt>destroy</tt> are the same: they + # cause the records in the join table to be removed. + # + # For #has_many, <tt>destroy</tt> and <tt>destroy_all</tt> will always call the <tt>destroy</tt> method of the + # record(s) being removed so that callbacks are run. However <tt>delete</tt> and <tt>delete_all</tt> will either + # do the deletion according to the strategy specified by the <tt>:dependent</tt> option, or + # if no <tt>:dependent</tt> option is given, then it will follow the default strategy. + # The default strategy is to do nothing (leave the foreign keys with the parent ids set), except for + # #has_many <tt>:through</tt>, where the default strategy is <tt>delete_all</tt> (delete + # the join records, without running their callbacks). + # + # There is also a <tt>clear</tt> method which is the same as <tt>delete_all</tt>, except that + # it returns the association rather than the records which have been deleted. + # + # === What gets deleted? + # + # There is a potential pitfall here: #has_and_belongs_to_many and #has_many <tt>:through</tt> + # associations have records in join tables, as well as the associated records. So when we + # call one of these deletion methods, what exactly should be deleted? + # + # The answer is that it is assumed that deletion on an association is about removing the + # <i>link</i> between the owner and the associated object(s), rather than necessarily the + # associated objects themselves. So with #has_and_belongs_to_many and #has_many + # <tt>:through</tt>, the join records will be deleted, but the associated records won't. + # + # This makes sense if you think about it: if you were to call <tt>post.tags.delete(Tag.find_by(name: 'food'))</tt> + # you would want the 'food' tag to be unlinked from the post, rather than for the tag itself + # to be removed from the database. + # + # However, there are examples where this strategy doesn't make sense. For example, suppose + # a person has many projects, and each project has many tasks. If we deleted one of a person's + # tasks, we would probably not want the project to be deleted. In this scenario, the delete method + # won't actually work: it can only be used if the association on the join model is a + # #belongs_to. In other situations you are expected to perform operations directly on + # either the associated records or the <tt>:through</tt> association. + # + # With a regular #has_many there is no distinction between the "associated records" + # and the "link", so there is only one choice for what gets deleted. + # + # With #has_and_belongs_to_many and #has_many <tt>:through</tt>, if you want to delete the + # associated records themselves, you can always do something along the lines of + # <tt>person.tasks.each(&:destroy)</tt>. + # + # == Type safety with ActiveRecord::AssociationTypeMismatch + # + # If you attempt to assign an object to an association that doesn't match the inferred + # or specified <tt>:class_name</tt>, you'll get an ActiveRecord::AssociationTypeMismatch. + # + # == Options + # + # All of the association macros can be specialized through options. This makes cases + # more complex than the simple and guessable ones possible. + module ClassMethods + # Specifies a one-to-many association. The following methods for retrieval and query of + # collections of associated objects will be added: + # + # +collection+ is a placeholder for the symbol passed as the +name+ argument, so + # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>. + # + # [collection(force_reload = false)] + # Returns an array of all the associated objects. + # An empty array is returned if none are found. + # [collection<<(object, ...)] + # Adds one or more objects to the collection by setting their foreign keys to the collection's primary key. + # Note that this operation instantly fires update SQL without waiting for the save or update call on the + # parent object, unless the parent object is a new record. + # This will also run validations and callbacks of associated object(s). + # [collection.delete(object, ...)] + # Removes one or more objects from the collection by setting their foreign keys to +NULL+. + # Objects will be in addition destroyed if they're associated with <tt>dependent: :destroy</tt>, + # and deleted if they're associated with <tt>dependent: :delete_all</tt>. + # + # If the <tt>:through</tt> option is used, then the join records are deleted (rather than + # nullified) by default, but you can specify <tt>dependent: :destroy</tt> or + # <tt>dependent: :nullify</tt> to override this. + # [collection.destroy(object, ...)] + # Removes one or more objects from the collection by running <tt>destroy</tt> on + # each record, regardless of any dependent option, ensuring callbacks are run. + # + # If the <tt>:through</tt> option is used, then the join records are destroyed + # instead, not the objects themselves. + # [collection=objects] + # Replaces the collections content by deleting and adding objects as appropriate. If the <tt>:through</tt> + # option is true callbacks in the join models are triggered except destroy callbacks, since deletion is + # direct by default. You can specify <tt>dependent: :destroy</tt> or + # <tt>dependent: :nullify</tt> to override this. + # [collection_singular_ids] + # Returns an array of the associated objects' ids + # [collection_singular_ids=ids] + # Replace the collection with the objects identified by the primary keys in +ids+. This + # method loads the models and calls <tt>collection=</tt>. See above. + # [collection.clear] + # Removes every object from the collection. This destroys the associated objects if they + # are associated with <tt>dependent: :destroy</tt>, deletes them directly from the + # database if <tt>dependent: :delete_all</tt>, otherwise sets their foreign keys to +NULL+. + # If the <tt>:through</tt> option is true no destroy callbacks are invoked on the join models. + # Join models are directly deleted. + # [collection.empty?] + # Returns +true+ if there are no associated objects. + # [collection.size] + # Returns the number of associated objects. + # [collection.find(...)] + # Finds an associated object according to the same rules as ActiveRecord::FinderMethods#find. + # [collection.exists?(...)] + # Checks whether an associated object with the given conditions exists. + # Uses the same rules as ActiveRecord::FinderMethods#exists?. + # [collection.build(attributes = {}, ...)] + # Returns one or more new objects of the collection type that have been instantiated + # with +attributes+ and linked to this object through a foreign key, but have not yet + # been saved. + # [collection.create(attributes = {})] + # Returns a new object of the collection type that has been instantiated + # with +attributes+, linked to this object through a foreign key, and that has already + # been saved (if it passed the validation). *Note*: This only works if the base model + # already exists in the DB, not if it is a new (unsaved) record! + # [collection.create!(attributes = {})] + # Does the same as <tt>collection.create</tt>, but raises ActiveRecord::RecordInvalid + # if the record is invalid. + # + # === Example + # + # A <tt>Firm</tt> class declares <tt>has_many :clients</tt>, which will add: + # * <tt>Firm#clients</tt> (similar to <tt>Client.where(firm_id: id)</tt>) + # * <tt>Firm#clients<<</tt> + # * <tt>Firm#clients.delete</tt> + # * <tt>Firm#clients.destroy</tt> + # * <tt>Firm#clients=</tt> + # * <tt>Firm#client_ids</tt> + # * <tt>Firm#client_ids=</tt> + # * <tt>Firm#clients.clear</tt> + # * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>) + # * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>) + # * <tt>Firm#clients.find</tt> (similar to <tt>Client.where(firm_id: id).find(id)</tt>) + # * <tt>Firm#clients.exists?(name: 'ACME')</tt> (similar to <tt>Client.exists?(name: 'ACME', firm_id: firm.id)</tt>) + # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>) + # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>) + # * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save!</tt>) + # The declaration can also include an +options+ hash to specialize the behavior of the association. + # + # === Scopes + # + # You can pass a second argument +scope+ as a callable (i.e. proc or + # lambda) to retrieve a specific set of records or customize the generated + # query when you access the associated collection. + # + # Scope examples: + # has_many :comments, -> { where(author_id: 1) } + # has_many :employees, -> { joins(:address) } + # has_many :posts, ->(post) { where("max_post_length > ?", post.length) } + # + # === Extensions + # + # The +extension+ argument allows you to pass a block into a has_many + # association. This is useful for adding new finders, creators and other + # factory-type methods to be used as part of the association. + # + # Extension examples: + # has_many :employees do + # def find_or_create_by_name(name) + # first_name, last_name = name.split(" ", 2) + # find_or_create_by(first_name: first_name, last_name: last_name) + # end + # end + # + # === Options + # [:class_name] + # Specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So <tt>has_many :products</tt> will by default be linked + # to the +Product+ class, but if the real class name is +SpecialProduct+, you'll have to + # specify it with this option. + # [:foreign_key] + # Specify the foreign key used for the association. By default this is guessed to be the name + # of this class in lower-case and "_id" suffixed. So a Person class that makes a #has_many + # association will use "person_id" as the default <tt>:foreign_key</tt>. + # [:foreign_type] + # Specify the column used to store the associated object's type, if this is a polymorphic + # association. By default this is guessed to be the name of the polymorphic association + # specified on "as" option with a "_type" suffix. So a class that defines a + # <tt>has_many :tags, as: :taggable</tt> association will use "taggable_type" as the + # default <tt>:foreign_type</tt>. + # [:primary_key] + # Specify the name of the column to use as the primary key for the association. By default this is +id+. + # [:dependent] + # Controls what happens to the associated objects when + # their owner is destroyed. Note that these are implemented as + # callbacks, and Rails executes callbacks in order. Therefore, other + # similar callbacks may affect the <tt>:dependent</tt> behavior, and the + # <tt>:dependent</tt> behavior may affect other callbacks. + # + # * <tt>:destroy</tt> causes all the associated objects to also be destroyed. + # * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not be executed). + # * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Callbacks are not executed. + # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records. + # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects. + # + # If using with the <tt>:through</tt> option, the association on the join model must be + # a #belongs_to, and the records which get deleted are the join records, rather than + # the associated records. + # + # If using <tt>dependent: :destroy</tt> on a scoped association, only the scoped objects are destroyed. + # For example, if a Post model defines + # <tt>has_many :comments, -> { where published: true }, dependent: :destroy</tt> and <tt>destroy</tt> is + # called on a post, only published comments are destroyed. This means that any unpublished comments in the + # database would still contain a foreign key pointing to the now deleted post. + # [:counter_cache] + # This option can be used to configure a custom named <tt>:counter_cache.</tt> You only need this option, + # when you customized the name of your <tt>:counter_cache</tt> on the #belongs_to association. + # [:as] + # Specifies a polymorphic interface (See #belongs_to). + # [:through] + # Specifies an association through which to perform the query. This can be any other type + # of association, including other <tt>:through</tt> associations. Options for <tt>:class_name</tt>, + # <tt>:primary_key</tt> and <tt>:foreign_key</tt> are ignored, as the association uses the + # source reflection. + # + # If the association on the join model is a #belongs_to, the collection can be modified + # and the records on the <tt>:through</tt> model will be automatically created and removed + # as appropriate. Otherwise, the collection is read-only, so you should manipulate the + # <tt>:through</tt> association directly. + # + # If you are going to modify the association (rather than just read from it), then it is + # a good idea to set the <tt>:inverse_of</tt> option on the source association on the + # join model. This allows associated records to be built which will automatically create + # the appropriate join model records when they are saved. (See the 'Association Join Models' + # section above.) + # [:source] + # Specifies the source association name used by #has_many <tt>:through</tt> queries. + # Only use it if the name cannot be inferred from the association. + # <tt>has_many :subscribers, through: :subscriptions</tt> will look for either <tt>:subscribers</tt> or + # <tt>:subscriber</tt> on Subscription, unless a <tt>:source</tt> is given. + # [:source_type] + # Specifies type of the source association used by #has_many <tt>:through</tt> queries where the source + # association is a polymorphic #belongs_to. + # [:validate] + # When set to +true+, validates new objects added to association when saving the parent object. +true+ by default. + # If you want to ensure associated objects are revalidated on every update, use +validates_associated+. + # [:autosave] + # If true, always save the associated objects or destroy them if marked for destruction, + # when saving the parent object. If false, never save or destroy the associated objects. + # By default, only save associated objects that are new records. This option is implemented as a + # +before_save+ callback. Because callbacks are run in the order they are defined, associated objects + # may need to be explicitly saved in any user-defined +before_save+ callbacks. + # + # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets + # <tt>:autosave</tt> to <tt>true</tt>. + # [:inverse_of] + # Specifies the name of the #belongs_to association on the associated object + # that is the inverse of this #has_many association. Does not work in combination + # with <tt>:through</tt> or <tt>:as</tt> options. + # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. + # [:extend] + # Specifies a module or array of modules that will be extended into the association object returned. + # Useful for defining methods on associations, especially when they should be shared between multiple + # association objects. + # + # Option examples: + # has_many :comments, -> { order("posted_on") } + # has_many :comments, -> { includes(:author) } + # has_many :people, -> { where(deleted: false).order("name") }, class_name: "Person" + # has_many :tracks, -> { order("position") }, dependent: :destroy + # has_many :comments, dependent: :nullify + # has_many :tags, as: :taggable + # has_many :reports, -> { readonly } + # has_many :subscribers, through: :subscriptions, source: :user + def has_many(name, scope = nil, options = {}, &extension) + reflection = Builder::HasMany.build(self, name, scope, options, &extension) + Reflection.add_reflection self, name, reflection end - habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(name, scope, options, self) + # Specifies a one-to-one association with another class. This method should only be used + # if the other class contains the foreign key. If the current class contains the foreign key, + # then you should use #belongs_to instead. See also ActiveRecord::Associations::ClassMethods's overview + # on when to use #has_one and when to use #belongs_to. + # + # The following methods for retrieval and query of a single associated object will be added: + # + # +association+ is a placeholder for the symbol passed as the +name+ argument, so + # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>. + # + # [association(force_reload = false)] + # Returns the associated object. +nil+ is returned if none is found. + # [association=(associate)] + # Assigns the associate object, extracts the primary key, sets it as the foreign key, + # and saves the associate object. To avoid database inconsistencies, permanently deletes an existing + # associated object when assigning a new one, even if the new one isn't saved to database. + # [build_association(attributes = {})] + # Returns a new object of the associated type that has been instantiated + # with +attributes+ and linked to this object through a foreign key, but has not + # yet been saved. + # [create_association(attributes = {})] + # Returns a new object of the associated type that has been instantiated + # with +attributes+, linked to this object through a foreign key, and that + # has already been saved (if it passed the validation). + # [create_association!(attributes = {})] + # Does the same as <tt>create_association</tt>, but raises ActiveRecord::RecordInvalid + # if the record is invalid. + # + # === Example + # + # An Account class declares <tt>has_one :beneficiary</tt>, which will add: + # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.where(account_id: id).first</tt>) + # * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>) + # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>) + # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>) + # * <tt>Account#create_beneficiary!</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save!; b</tt>) + # + # === Scopes + # + # You can pass a second argument +scope+ as a callable (i.e. proc or + # lambda) to retrieve a specific record or customize the generated query + # when you access the associated object. + # + # Scope examples: + # has_one :author, -> { where(comment_id: 1) } + # has_one :employer, -> { joins(:company) } + # has_one :dob, ->(dob) { where("Date.new(2000, 01, 01) > ?", dob) } + # + # === Options + # + # The declaration can also include an +options+ hash to specialize the behavior of the association. + # + # Options are: + # [:class_name] + # Specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So <tt>has_one :manager</tt> will by default be linked to the Manager class, but + # if the real class name is Person, you'll have to specify it with this option. + # [:dependent] + # Controls what happens to the associated object when + # its owner is destroyed: + # + # * <tt>:destroy</tt> causes the associated object to also be destroyed + # * <tt>:delete</tt> causes the associated object to be deleted directly from the database (so callbacks will not execute) + # * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Callbacks are not executed. + # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there is an associated record + # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there is an associated object + # + # Note that <tt>:dependent</tt> option is ignored when using <tt>:through</tt> option. + # [:foreign_key] + # Specify the foreign key used for the association. By default this is guessed to be the name + # of this class in lower-case and "_id" suffixed. So a Person class that makes a #has_one association + # will use "person_id" as the default <tt>:foreign_key</tt>. + # [:foreign_type] + # Specify the column used to store the associated object's type, if this is a polymorphic + # association. By default this is guessed to be the name of the polymorphic association + # specified on "as" option with a "_type" suffix. So a class that defines a + # <tt>has_one :tag, as: :taggable</tt> association will use "taggable_type" as the + # default <tt>:foreign_type</tt>. + # [:primary_key] + # Specify the method that returns the primary key used for the association. By default this is +id+. + # [:as] + # Specifies a polymorphic interface (See #belongs_to). + # [:through] + # Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt>, + # <tt>:primary_key</tt>, and <tt>:foreign_key</tt> are ignored, as the association uses the + # source reflection. You can only use a <tt>:through</tt> query through a #has_one + # or #belongs_to association on the join model. + # [:source] + # Specifies the source association name used by #has_one <tt>:through</tt> queries. + # Only use it if the name cannot be inferred from the association. + # <tt>has_one :favorite, through: :favorites</tt> will look for a + # <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given. + # [:source_type] + # Specifies type of the source association used by #has_one <tt>:through</tt> queries where the source + # association is a polymorphic #belongs_to. + # [:validate] + # When set to +true+, validates new objects added to association when saving the parent object. +false+ by default. + # If you want to ensure associated objects are revalidated on every update, use +validates_associated+. + # [:autosave] + # If true, always save the associated object or destroy it if marked for destruction, + # when saving the parent object. If false, never save or destroy the associated object. + # By default, only save the associated object if it's a new record. + # + # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets + # <tt>:autosave</tt> to <tt>true</tt>. + # [:inverse_of] + # Specifies the name of the #belongs_to association on the associated object + # that is the inverse of this #has_one association. Does not work in combination + # with <tt>:through</tt> or <tt>:as</tt> options. + # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. + # [:required] + # When set to +true+, the association will also have its presence validated. + # This will validate the association itself, not the id. You can use + # +:inverse_of+ to avoid an extra query during validation. + # + # Option examples: + # has_one :credit_card, dependent: :destroy # destroys the associated credit card + # has_one :credit_card, dependent: :nullify # updates the associated records foreign + # # key value to NULL rather than destroying it + # has_one :last_comment, -> { order('posted_on') }, class_name: "Comment" + # has_one :project_manager, -> { where(role: 'project_manager') }, class_name: "Person" + # has_one :attachment, as: :attachable + # has_one :boss, -> { readonly } + # has_one :club, through: :membership + # has_one :primary_address, -> { where(primary: true) }, through: :addressables, source: :addressable + # has_one :credit_card, required: true + def has_one(name, scope = nil, options = {}) + reflection = Builder::HasOne.build(self, name, scope, options) + Reflection.add_reflection self, name, reflection + end - builder = Builder::HasAndBelongsToMany.new name, self, options + # Specifies a one-to-one association with another class. This method should only be used + # if this class contains the foreign key. If the other class contains the foreign key, + # then you should use #has_one instead. See also ActiveRecord::Associations::ClassMethods's overview + # on when to use #has_one and when to use #belongs_to. + # + # Methods will be added for retrieval and query for a single associated object, for which + # this object holds an id: + # + # +association+ is a placeholder for the symbol passed as the +name+ argument, so + # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>. + # + # [association(force_reload = false)] + # Returns the associated object. +nil+ is returned if none is found. + # [association=(associate)] + # Assigns the associate object, extracts the primary key, and sets it as the foreign key. + # [build_association(attributes = {})] + # Returns a new object of the associated type that has been instantiated + # with +attributes+ and linked to this object through a foreign key, but has not yet been saved. + # [create_association(attributes = {})] + # Returns a new object of the associated type that has been instantiated + # with +attributes+, linked to this object through a foreign key, and that + # has already been saved (if it passed the validation). + # [create_association!(attributes = {})] + # Does the same as <tt>create_association</tt>, but raises ActiveRecord::RecordInvalid + # if the record is invalid. + # + # === Example + # + # A Post class declares <tt>belongs_to :author</tt>, which will add: + # * <tt>Post#author</tt> (similar to <tt>Author.find(author_id)</tt>) + # * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>) + # * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>) + # * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>) + # * <tt>Post#create_author!</tt> (similar to <tt>post.author = Author.new; post.author.save!; post.author</tt>) + # The declaration can also include an +options+ hash to specialize the behavior of the association. + # + # === Scopes + # + # You can pass a second argument +scope+ as a callable (i.e. proc or + # lambda) to retrieve a specific record or customize the generated query + # when you access the associated object. + # + # Scope examples: + # belongs_to :firm, -> { where(id: 2) } + # belongs_to :user, -> { joins(:friends) } + # belongs_to :level, ->(level) { where("game_level > ?", level.current) } + # + # === Options + # + # [:class_name] + # Specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So <tt>belongs_to :author</tt> will by default be linked to the Author class, but + # if the real class name is Person, you'll have to specify it with this option. + # [:foreign_key] + # Specify the foreign key used for the association. By default this is guessed to be the name + # of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :person</tt> + # association will use "person_id" as the default <tt>:foreign_key</tt>. Similarly, + # <tt>belongs_to :favorite_person, class_name: "Person"</tt> will use a foreign key + # of "favorite_person_id". + # [:foreign_type] + # Specify the column used to store the associated object's type, if this is a polymorphic + # association. By default this is guessed to be the name of the association with a "_type" + # suffix. So a class that defines a <tt>belongs_to :taggable, polymorphic: true</tt> + # association will use "taggable_type" as the default <tt>:foreign_type</tt>. + # [:primary_key] + # Specify the method that returns the primary key of associated object used for the association. + # By default this is id. + # [:dependent] + # If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to + # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method. + # This option should not be specified when #belongs_to is used in conjunction with + # a #has_many relationship on another class because of the potential to leave + # orphaned records behind. + # [:counter_cache] + # Caches the number of belonging objects on the associate class through the use of CounterCache::ClassMethods#increment_counter + # and CounterCache::ClassMethods#decrement_counter. The counter cache is incremented when an object of this + # class is created and decremented when it's destroyed. This requires that a column + # named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class) + # is used on the associate class (such as a Post class) - that is the migration for + # <tt>#{table_name}_count</tt> is created on the associate class (such that <tt>Post.comments_count</tt> will + # return the count cached, see note below). You can also specify a custom counter + # cache column by providing a column name instead of a +true+/+false+ value to this + # option (e.g., <tt>counter_cache: :my_custom_counter</tt>.) + # Note: Specifying a counter cache will add it to that model's list of readonly attributes + # using +attr_readonly+. + # [:polymorphic] + # Specify this association is a polymorphic association by passing +true+. + # Note: If you've enabled the counter cache, then you may want to add the counter cache attribute + # to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>). + # [:validate] + # When set to +true+, validates new objects added to association when saving the parent object. +false+ by default. + # If you want to ensure associated objects are revalidated on every update, use +validates_associated+. + # [:autosave] + # If true, always save the associated object or destroy it if marked for destruction, when + # saving the parent object. + # If false, never save or destroy the associated object. + # By default, only save the associated object if it's a new record. + # + # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for + # sets <tt>:autosave</tt> to <tt>true</tt>. + # [:touch] + # If true, the associated object will be touched (the updated_at/on attributes set to current time) + # when this record is either saved or destroyed. If you specify a symbol, that attribute + # will be updated with the current time in addition to the updated_at/on attribute. + # Please note that with touching no validation is performed and only the +after_touch+, + # +after_commit+ and +after_rollback+ callbacks are executed. + # [:inverse_of] + # Specifies the name of the #has_one or #has_many association on the associated + # object that is the inverse of this #belongs_to association. Does not work in + # combination with the <tt>:polymorphic</tt> options. + # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. + # [:optional] + # When set to +true+, the association will not have its presence validated. + # [:required] + # When set to +true+, the association will also have its presence validated. + # This will validate the association itself, not the id. You can use + # +:inverse_of+ to avoid an extra query during validation. + # NOTE: <tt>required</tt> is set to <tt>true</tt> by default and is deprecated. If + # you don't want to have association presence validated, use <tt>optional: true</tt>. + # + # Option examples: + # belongs_to :firm, foreign_key: "client_of" + # belongs_to :person, primary_key: "name", foreign_key: "person_name" + # belongs_to :author, class_name: "Person", foreign_key: "author_id" + # belongs_to :valid_coupon, ->(o) { where "discounts > ?", o.payments_count }, + # class_name: "Coupon", foreign_key: "coupon_id" + # belongs_to :attachable, polymorphic: true + # belongs_to :project, -> { readonly } + # belongs_to :post, counter_cache: true + # belongs_to :comment, touch: true + # belongs_to :company, touch: :employees_last_updated_at + # belongs_to :user, optional: true + def belongs_to(name, scope = nil, options = {}) + reflection = Builder::BelongsTo.build(self, name, scope, options) + Reflection.add_reflection self, name, reflection + end - join_model = builder.through_model + # Specifies a many-to-many relationship with another class. This associates two classes via an + # intermediate join table. Unless the join table is explicitly specified as an option, it is + # guessed using the lexical order of the class names. So a join between Developer and Project + # will give the default join table name of "developers_projects" because "D" precedes "P" alphabetically. + # Note that this precedence is calculated using the <tt><</tt> operator for String. This + # means that if the strings are of different lengths, and the strings are equal when compared + # up to the shortest length, then the longer string is considered of higher + # lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers" + # to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", + # but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the + # custom <tt>:join_table</tt> option if you need to. + # If your tables share a common prefix, it will only appear once at the beginning. For example, + # the tables "catalog_categories" and "catalog_products" generate a join table name of "catalog_categories_products". + # + # The join table should not have a primary key or a model associated with it. You must manually generate the + # join table with a migration such as this: + # + # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[5.0] + # def change + # create_join_table :developers, :projects + # end + # end + # + # It's also a good idea to add indexes to each of those columns to speed up the joins process. + # However, in MySQL it is advised to add a compound index for both of the columns as MySQL only + # uses one index per table during the lookup. + # + # Adds the following methods for retrieval and query: + # + # +collection+ is a placeholder for the symbol passed as the +name+ argument, so + # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>. + # + # [collection(force_reload = false)] + # Returns an array of all the associated objects. + # An empty array is returned if none are found. + # [collection<<(object, ...)] + # Adds one or more objects to the collection by creating associations in the join table + # (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method). + # Note that this operation instantly fires update SQL without waiting for the save or update call on the + # parent object, unless the parent object is a new record. + # [collection.delete(object, ...)] + # Removes one or more objects from the collection by removing their associations from the join table. + # This does not destroy the objects. + # [collection.destroy(object, ...)] + # Removes one or more objects from the collection by running destroy on each association in the join table, overriding any dependent option. + # This does not destroy the objects. + # [collection=objects] + # Replaces the collection's content by deleting and adding objects as appropriate. + # [collection_singular_ids] + # Returns an array of the associated objects' ids. + # [collection_singular_ids=ids] + # Replace the collection by the objects identified by the primary keys in +ids+. + # [collection.clear] + # Removes every object from the collection. This does not destroy the objects. + # [collection.empty?] + # Returns +true+ if there are no associated objects. + # [collection.size] + # Returns the number of associated objects. + # [collection.find(id)] + # Finds an associated object responding to the +id+ and that + # meets the condition that it has to be associated with this object. + # Uses the same rules as ActiveRecord::FinderMethods#find. + # [collection.exists?(...)] + # Checks whether an associated object with the given conditions exists. + # Uses the same rules as ActiveRecord::FinderMethods#exists?. + # [collection.build(attributes = {})] + # Returns a new object of the collection type that has been instantiated + # with +attributes+ and linked to this object through the join table, but has not yet been saved. + # [collection.create(attributes = {})] + # Returns a new object of the collection type that has been instantiated + # with +attributes+, linked to this object through the join table, and that has already been + # saved (if it passed the validation). + # + # === Example + # + # A Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add: + # * <tt>Developer#projects</tt> + # * <tt>Developer#projects<<</tt> + # * <tt>Developer#projects.delete</tt> + # * <tt>Developer#projects.destroy</tt> + # * <tt>Developer#projects=</tt> + # * <tt>Developer#project_ids</tt> + # * <tt>Developer#project_ids=</tt> + # * <tt>Developer#projects.clear</tt> + # * <tt>Developer#projects.empty?</tt> + # * <tt>Developer#projects.size</tt> + # * <tt>Developer#projects.find(id)</tt> + # * <tt>Developer#projects.exists?(...)</tt> + # * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("developer_id" => id)</tt>) + # * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("developer_id" => id); c.save; c</tt>) + # The declaration may include an +options+ hash to specialize the behavior of the association. + # + # === Scopes + # + # You can pass a second argument +scope+ as a callable (i.e. proc or + # lambda) to retrieve a specific set of records or customize the generated + # query when you access the associated collection. + # + # Scope examples: + # has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) } + # has_and_belongs_to_many :categories, ->(category) { + # where("default_category = ?", category.name) + # } + # + # === Extensions + # + # The +extension+ argument allows you to pass a block into a + # has_and_belongs_to_many association. This is useful for adding new + # finders, creators and other factory-type methods to be used as part of + # the association. + # + # Extension examples: + # has_and_belongs_to_many :contractors do + # def find_or_create_by_name(name) + # first_name, last_name = name.split(" ", 2) + # find_or_create_by(first_name: first_name, last_name: last_name) + # end + # end + # + # === Options + # + # [:class_name] + # Specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the + # Project class, but if the real class name is SuperProject, you'll have to specify it with this option. + # [:join_table] + # Specify the name of the join table if the default based on lexical order isn't what you want. + # <b>WARNING:</b> If you're overwriting the table name of either class, the +table_name+ method + # MUST be declared underneath any #has_and_belongs_to_many declaration in order to work. + # [:foreign_key] + # Specify the foreign key used for the association. By default this is guessed to be the name + # of this class in lower-case and "_id" suffixed. So a Person class that makes + # a #has_and_belongs_to_many association to Project will use "person_id" as the + # default <tt>:foreign_key</tt>. + # [:association_foreign_key] + # Specify the foreign key used for the association on the receiving side of the association. + # By default this is guessed to be the name of the associated class in lower-case and "_id" suffixed. + # So if a Person class makes a #has_and_belongs_to_many association to Project, + # the association will use "project_id" as the default <tt>:association_foreign_key</tt>. + # [:validate] + # When set to +true+, validates new objects added to association when saving the parent object. +true+ by default. + # If you want to ensure associated objects are revalidated on every update, use +validates_associated+. + # [:autosave] + # If true, always save the associated objects or destroy them if marked for destruction, when + # saving the parent object. + # If false, never save or destroy the associated objects. + # By default, only save associated objects that are new records. + # + # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets + # <tt>:autosave</tt> to <tt>true</tt>. + # + # Option examples: + # has_and_belongs_to_many :projects + # has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) } + # has_and_belongs_to_many :nations, class_name: "Country" + # has_and_belongs_to_many :categories, join_table: "prods_cats" + # has_and_belongs_to_many :categories, -> { readonly } + def has_and_belongs_to_many(name, scope = nil, **options, &extension) + habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(name, scope, options, self) - const_set join_model.name, join_model - private_constant join_model.name + builder = Builder::HasAndBelongsToMany.new name, self, options - middle_reflection = builder.middle_reflection join_model + join_model = builder.through_model - Builder::HasMany.define_callbacks self, middle_reflection - Reflection.add_reflection self, middle_reflection.name, middle_reflection - middle_reflection.parent_reflection = habtm_reflection + const_set join_model.name, join_model + private_constant join_model.name - include Module.new { - class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def destroy_associations - association(:#{middle_reflection.name}).delete_all(:delete_all) - association(:#{name}).reset - super - end - RUBY - } + middle_reflection = builder.middle_reflection join_model - hm_options = {} - hm_options[:through] = middle_reflection.name - hm_options[:source] = join_model.right_reflection.name + Builder::HasMany.define_callbacks self, middle_reflection + Reflection.add_reflection self, middle_reflection.name, middle_reflection + middle_reflection.parent_reflection = habtm_reflection - [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend].each do |k| - hm_options[k] = options[k] if options.key? k - end + include Module.new { + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def destroy_associations + association(:#{middle_reflection.name}).delete_all(:delete_all) + association(:#{name}).reset + super + end + RUBY + } + + hm_options = {} + hm_options[:through] = middle_reflection.name + hm_options[:source] = join_model.right_reflection.name + + [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend].each do |k| + 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 + has_many name, scope, hm_options, &extension + self._reflections[name.to_s].parent_reflection = habtm_reflection + end end - end end end diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb index 021bc32237..3963008a76 100644 --- a/activerecord/lib/active_record/associations/alias_tracker.rb +++ b/activerecord/lib/active_record/associations/alias_tracker.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/string/conversions' +require "active_support/core_ext/string/conversions" module ActiveRecord module Associations diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index d64ab64c99..f506614591 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/array/wrap' +require "active_support/core_ext/array/wrap" module ActiveRecord module Associations @@ -19,7 +19,7 @@ module ActiveRecord attr_reader :owner, :target, :reflection attr_accessor :inversed - delegate :options, :to => :reflection + delegate :options, to: :reflection def initialize(owner, reflection) reflection.check_validity! @@ -173,6 +173,14 @@ module ActiveRecord set_inverse_instance(record) end + def create(attributes = {}, &block) + _create_record(attributes, &block) + end + + def create!(attributes = {}, &block) + _create_record(attributes, true, &block) + end + private def find_target? @@ -217,7 +225,8 @@ module ActiveRecord unless record.is_a?(reflection.klass) fresh_class = reflection.class_name.safe_constantize unless fresh_class && record.is_a?(fresh_class) - message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})" + message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, "\ + "got #{record.inspect} which is an instance of #{record.class}(##{record.class.object_id})" raise ActiveRecord::AssociationTypeMismatch, message end end @@ -257,7 +266,7 @@ module ActiveRecord # Returns true if statement cache should be skipped on the association reader. def skip_statement_cache? - reflection.scope_chain.any?(&:any?) || + reflection.has_scope? || scope.eager_loading? || klass.scope_attributes? || reflection.source_reflection.active_record.default_scopes.any? diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 48437a1c9e..12f8c1ccd4 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -51,116 +51,115 @@ module ActiveRecord protected - attr_reader :value_transformation + attr_reader :value_transformation private - def join(table, constraint) - table.create_join(table, table.create_on(constraint), join_type) - end + def join(table, constraint) + table.create_join(table, table.create_on(constraint), join_type) + end + + def last_chain_scope(scope, table, reflection, owner, association_klass) + join_keys = reflection.join_keys(association_klass) + key = join_keys.key + foreign_key = join_keys.foreign_key - def last_chain_scope(scope, table, reflection, owner, association_klass) - join_keys = reflection.join_keys(association_klass) - key = join_keys.key - foreign_key = join_keys.foreign_key + value = transform_value(owner[foreign_key]) + scope = scope.where(table.name => { key => value }) - value = transform_value(owner[foreign_key]) - scope = scope.where(table.name => { key => value }) + if reflection.type + polymorphic_type = transform_value(owner.class.base_class.name) + scope = scope.where(table.name => { reflection.type => polymorphic_type }) + end - if reflection.type - polymorphic_type = transform_value(owner.class.base_class.name) - scope = scope.where(table.name => { reflection.type => polymorphic_type }) + scope end - scope - end + def transform_value(value) + value_transformation.call(value) + end - def transform_value(value) - value_transformation.call(value) - end + def next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection) + join_keys = reflection.join_keys(association_klass) + key = join_keys.key + foreign_key = join_keys.foreign_key - def next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection) - join_keys = reflection.join_keys(association_klass) - key = join_keys.key - foreign_key = join_keys.foreign_key + constraint = table[key].eq(foreign_table[foreign_key]) - constraint = table[key].eq(foreign_table[foreign_key]) + if reflection.type + value = transform_value(next_reflection.klass.base_class.name) + scope = scope.where(table.name => { reflection.type => value }) + end - if reflection.type - value = transform_value(next_reflection.klass.base_class.name) - scope = scope.where(table.name => { reflection.type => value }) + scope = scope.joins(join(foreign_table, constraint)) end - scope = scope.joins(join(foreign_table, constraint)) - end + class ReflectionProxy < SimpleDelegator # :nodoc: + attr_accessor :next + attr_reader :alias_name - class ReflectionProxy < SimpleDelegator # :nodoc: - attr_accessor :next - attr_reader :alias_name + def initialize(reflection, alias_name) + super(reflection) + @alias_name = alias_name + end - def initialize(reflection, alias_name) - super(reflection) - @alias_name = alias_name + def all_includes; nil; end end - def all_includes; nil; end - end - - def get_chain(reflection, association, tracker) - name = reflection.name - runtime_reflection = Reflection::RuntimeReflection.new(reflection, association) - previous_reflection = runtime_reflection - reflection.chain.drop(1).each do |refl| - alias_name = tracker.aliased_table_for(refl.table_name, refl.alias_candidate(name)) - proxy = ReflectionProxy.new(refl, alias_name) - previous_reflection.next = proxy - previous_reflection = proxy + def get_chain(reflection, association, tracker) + name = reflection.name + runtime_reflection = Reflection::RuntimeReflection.new(reflection, association) + previous_reflection = runtime_reflection + reflection.chain.drop(1).each do |refl| + alias_name = tracker.aliased_table_for(refl.table_name, refl.alias_candidate(name)) + proxy = ReflectionProxy.new(refl, alias_name) + previous_reflection.next = proxy + previous_reflection = proxy + end + [runtime_reflection, previous_reflection] end - [runtime_reflection, previous_reflection] - end - def add_constraints(scope, owner, association_klass, refl, chain_head, chain_tail) - owner_reflection = chain_tail - table = owner_reflection.alias_name - scope = last_chain_scope(scope, table, owner_reflection, owner, association_klass) + def add_constraints(scope, owner, association_klass, refl, chain_head, chain_tail) + owner_reflection = chain_tail + table = owner_reflection.alias_name + scope = last_chain_scope(scope, table, owner_reflection, owner, association_klass) - reflection = chain_head - loop do - break unless reflection - table = reflection.alias_name + reflection = chain_head + while reflection + table = reflection.alias_name - 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 + 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 - # 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) + # 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) - if scope_chain_item == refl.scope - scope.merge! item.except(:where, :includes) - end + if scope_chain_item == refl.scope + scope.merge! item.except(:where, :includes) + end + + reflection.all_includes do + scope.includes! item.includes_values + end - reflection.all_includes do - scope.includes! item.includes_values + scope.unscope!(*item.unscope_values) + scope.where_clause += item.where_clause + scope.order_values |= item.order_values end - scope.unscope!(*item.unscope_values) - scope.where_clause += item.where_clause - scope.order_values |= item.order_values + reflection = reflection.next end - reflection = reflection.next + scope end - scope - end - - def eval_scope(klass, scope, owner) - klass.unscoped.instance_exec(owner, &scope) - end + def eval_scope(klass, scope, owner) + klass.unscoped.instance_exec(owner, &scope) + end end end end diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index 41698c5360..64b2311911 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -2,7 +2,6 @@ module ActiveRecord # = Active Record Belongs To Association module Associations class BelongsToAssociation < SingularAssociation #:nodoc: - def handle_dependency target.send(options[:dependent]) if load_target end @@ -61,6 +60,7 @@ module ActiveRecord def update_counters_on_replace(record) if require_counter_update? && different_target?(record) + owner.instance_variable_set :@_after_replace_counter_called, true record.increment!(reflection.counter_cache_column) decrement_counters end diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 346329c610..3121e70a04 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -33,6 +33,8 @@ module ActiveRecord::Associations::Builder # :nodoc: if (@_after_create_counter_called ||= false) @_after_create_counter_called = false + elsif (@_after_replace_counter_called ||= false) + @_after_replace_counter_called = false elsif attribute_changed?(foreign_key) && !new_record? if reflection.polymorphic? model = attribute(reflection.foreign_type).try(:constantize) diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb index 56a8dc4e18..edeb6491bd 100644 --- a/activerecord/lib/active_record/associations/builder/collection_association.rb +++ b/activerecord/lib/active_record/associations/builder/collection_association.rb @@ -1,10 +1,9 @@ # This class is inherited by the has_many and has_many_and_belongs_to_many association classes -require 'active_record/associations' +require "active_record/associations" module ActiveRecord::Associations::Builder # :nodoc: class CollectionAssociation < Association #:nodoc: - CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove] def self.valid_options(options) @@ -70,7 +69,11 @@ module ActiveRecord::Associations::Builder # :nodoc: def self.wrap_scope(scope, mod) if scope - proc { |owner| instance_exec(owner, &scope).extending(mod) } + if scope.arity > 0 + proc { |owner| instance_exec(owner, &scope).extending(mod) } + else + proc { instance_exec(&scope).extending(mod) } + end else proc { extending(mod) } 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 b888148841..42a90b449c 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 @@ -16,9 +16,9 @@ module ActiveRecord::Associations::Builder # :nodoc: private - def klass - @lhs_class.send(:compute_type, @rhs_class_name) - end + def klass + @lhs_class.send(:compute_type, @rhs_class_name) + end end def self.build(lhs_class, name, options) @@ -28,7 +28,7 @@ module ActiveRecord::Associations::Builder # :nodoc: class_name = options.fetch(:class_name) { name.to_s.camelize.singularize } - KnownClass.new lhs_class, class_name + KnownClass.new lhs_class, class_name.to_s end end end @@ -76,6 +76,11 @@ module ActiveRecord::Associations::Builder # :nodoc: left_model.retrieve_connection end + private + + def self.suppress_composite_primary_key(pk) + pk unless pk.is_a?(Array) + end } join_model.name = "HABTM_#{association_name.to_s.camelize}" @@ -89,7 +94,7 @@ module ActiveRecord::Associations::Builder # :nodoc: def middle_reflection(join_model) middle_name = [lhs_model.name.downcase.pluralize, - association_name].join('_'.freeze).gsub('::'.freeze, '_'.freeze).to_sym + association_name].join("_".freeze).gsub("::".freeze, "_".freeze).to_sym middle_options = middle_options join_model HasMany.create_reflection(lhs_model, @@ -100,29 +105,29 @@ module ActiveRecord::Associations::Builder # :nodoc: private - def middle_options(join_model) - middle_options = {} - middle_options[:class_name] = "#{lhs_model.name}::#{join_model.name}" - middle_options[:source] = join_model.left_reflection.name - if options.key? :foreign_key - middle_options[:foreign_key] = options[:foreign_key] + def middle_options(join_model) + middle_options = {} + middle_options[:class_name] = "#{lhs_model.name}::#{join_model.name}" + middle_options[:source] = join_model.left_reflection.name + if options.key? :foreign_key + middle_options[:foreign_key] = options[:foreign_key] + end + middle_options end - middle_options - end - def belongs_to_options(options) - rhs_options = {} + def belongs_to_options(options) + rhs_options = {} - if options.key? :class_name - rhs_options[:foreign_key] = options[:class_name].to_s.foreign_key - rhs_options[:class_name] = options[:class_name] - end + if options.key? :class_name + rhs_options[:foreign_key] = options[:class_name].to_s.foreign_key + rhs_options[:class_name] = options[:class_name] + end - if options.key? :association_foreign_key - rhs_options[:foreign_key] = options[:association_foreign_key] - end + if options.key? :association_foreign_key + rhs_options[:foreign_key] = options[:association_foreign_key] + end - rhs_options - end + rhs_options + end end end diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 2dca6b612e..278c95e27b 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -10,9 +10,9 @@ module ActiveRecord # HasManyAssociation => has_many # HasManyThroughAssociation + ThroughAssociation => has_many :through # - # CollectionAssociation class provides common methods to the collections + # The CollectionAssociation class provides common methods to the collections # defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with - # +:through association+ option. + # the +:through association+ option. # # You need to be careful with assumptions regarding the target: The proxy # does not fetch records from the database until it needs them, but new @@ -24,7 +24,6 @@ module ActiveRecord # If you need to work on all current children, new and existing records, # +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 @@ -83,14 +82,6 @@ module ActiveRecord @target = [] end - def select(*fields) - if block_given? - load_target.select.each { |e| yield e } - else - scope.select(*fields) - end - end - def find(*args) if block_given? load_target.find(*args) { |*block_args| yield(*block_args) } @@ -112,52 +103,6 @@ module ActiveRecord end end - def first(*args) - first_nth_or_last(:first, *args) - end - - def second(*args) - first_nth_or_last(:second, *args) - end - - def third(*args) - first_nth_or_last(:third, *args) - end - - def fourth(*args) - first_nth_or_last(:fourth, *args) - end - - def fifth(*args) - first_nth_or_last(:fifth, *args) - end - - def forty_two(*args) - first_nth_or_last(:forty_two, *args) - end - - def third_to_last(*args) - first_nth_or_last(:third_to_last, *args) - end - - def second_to_last(*args) - first_nth_or_last(:second_to_last, *args) - end - - def last(*args) - first_nth_or_last(:last, *args) - end - - def take(n = nil) - if loaded? - n ? target.take(n) : target.first - else - scope.take(n).tap do |record| - set_inverse_instance record if record.is_a? ActiveRecord::Base - end - end - end - def build(attributes = {}, &block) if attributes.is_a?(Array) attributes.collect { |attr| build(attr, &block) } @@ -168,14 +113,6 @@ module ActiveRecord end end - def create(attributes = {}, &block) - _create_record(attributes, &block) - end - - def create!(attributes = {}, &block) - _create_record(attributes, true, &block) - end - # Add +records+ to this association. Returns +self+ so method calls may # be chained. Since << flattens its argument list and inserts each record, # +push+ and +concat+ behave identically. @@ -223,12 +160,12 @@ module ActiveRecord end dependent = if dependent - dependent - elsif options[:dependent] == :destroy - :delete_all - else - options[:dependent] - end + dependent + elsif options[:dependent] == :destroy + :delete_all + else + options[:dependent] + end delete_or_nullify_all_records(dependent).tap do reset @@ -246,28 +183,6 @@ module ActiveRecord end end - # Count all records using SQL. Construct options and pass them with - # scope to the target class's +count+. - def count(column_name = nil) - relation = scope - if association_scope.distinct_value - # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL. - column_name ||= reflection.klass.primary_key - relation = relation.distinct - end - - value = relation.count(column_name) - - limit = options[:limit] - offset = options[:offset] - - if limit || offset - [ [value - offset.to_i, 0].max, limit.to_i ].min - else - value - end - end - # Removes +records+ from this association calling +before_remove+ and # +after_remove+ callbacks. # @@ -280,7 +195,7 @@ module ActiveRecord _options = records.extract_options! dependent = _options[:dependent] || options[:dependent] - records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) } + records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) } delete_or_destroy(records, dependent) end @@ -291,7 +206,7 @@ module ActiveRecord # +:dependent+ option. def destroy(*records) return if records.empty? - records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) } + records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) } delete_or_destroy(records, :destroy) end @@ -312,9 +227,9 @@ module ActiveRecord else target.size end - elsif !loaded? && !association_scope.group_values.empty? + elsif !association_scope.group_values.empty? load_target.size - elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array) + elsif !association_scope.distinct_value && target.is_a?(Array) unsaved_records = target.select(&:new_record?) unsaved_records.size + count_records else @@ -322,15 +237,6 @@ module ActiveRecord end end - # Returns the size of the collection calling +size+ on the target. - # - # If the collection has been already loaded +length+ and +size+ are - # equivalent. If not and you are going to need the records anyway this - # method will take one less query. Otherwise +size+ is more efficient. - def length - load_target.size - end - # Returns true if the collection is empty. # # If the collection has been loaded @@ -347,35 +253,12 @@ module ActiveRecord end end - # Returns true if the collections is not empty. - # If block given, loads all records and checks for one or more matches. - # Otherwise, equivalent to +!collection.empty?+. - def any? - if block_given? - load_target.any? { |*block_args| yield(*block_args) } - else - !empty? - end - end - - # Returns true if the collection has more than 1 record. - # If block given, loads all records and checks for two or more matches. - # Otherwise, equivalent to +collection.size > 1+. - def many? - if block_given? - load_target.many? { |*block_args| yield(*block_args) } - else - size > 1 - end - end - def distinct seen = {} load_target.find_all do |record| seen[record.id] = true unless seen.key?(record.id) end end - alias uniq distinct # Replace this collection with +other_array+. This will perform a diff # and delete/add only records that have changed. @@ -426,15 +309,12 @@ module ActiveRecord def replace_on_target(record, index, skip_callbacks) callback(:before_add, record) unless skip_callbacks - was_loaded = loaded? yield(record) if block_given? - unless !was_loaded && loaded? - if index - @target[index] = record - else - @target << record - end + if index + @target[index] = record + else + append_record(record) end callback(:after_add, record) unless skip_callbacks @@ -453,26 +333,29 @@ module ActiveRecord owner.new_record? && !foreign_key_present? end - private - def get_records - return scope.to_a if skip_statement_cache? - - conn = klass.connection - sc = reflection.association_scope_cache(conn, owner) do - StatementCache.create(conn) { |params| - as = AssociationScope.create { params.bind } - target_scope.merge as.scope(self, conn) - } - end - - binds = AssociationScope.get_bind_values(owner, reflection.chain) - sc.execute binds, klass, klass.connection + def find_from_target? + loaded? || + owner.new_record? || + target.any? { |record| record.new_record? || record.changed? } end + private + def find_target - records = get_records - records.each { |record| set_inverse_instance(record) } - records + return scope.to_a if skip_statement_cache? + + conn = klass.connection + sc = reflection.association_scope_cache(conn, owner) do + StatementCache.create(conn) { |params| + as = AssociationScope.create { params.bind } + target_scope.merge as.scope(self, conn) + } + end + + binds = AssociationScope.get_bind_values(owner, reflection.chain) + sc.execute(binds, klass, conn) do |record| + set_inverse_instance(record) + end end # We have some records loaded from the database (persisted) and some that are @@ -602,25 +485,6 @@ module ActiveRecord owner.class.send(full_callback_name) end - # Should we deal with assoc.first or assoc.last by issuing an independent query to - # the database, or by getting the target, and then taking the first/last item from that? - # - # If the args is just a non-empty options hash, go to the database. - # - # Otherwise, go to the database only if none of the following are true: - # * target already loaded - # * owner is new record - # * target contains new or changed record(s) - def fetch_first_nth_or_last_using_find?(args) - if args.first.is_a?(Hash) - true - else - !(loaded? || - owner.new_record? || - target.any? { |record| record.new_record? || record.changed? }) - end - end - def include_in_memory?(record) if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection) assoc = owner.association(reflection.through_reflection.name) @@ -648,14 +512,8 @@ module ActiveRecord end end - # Fetches the first/last using SQL if possible, otherwise from the target array. - def first_nth_or_last(type, *args) - args.shift if args.first.is_a?(Hash) && args.first.empty? - - collection = fetch_first_nth_or_last_using_find?(args) ? scope : load_target - collection.send(type, *args).tap do |record| - set_inverse_instance record if record.is_a? ActiveRecord::Base - end + def append_record(record) + @target << record unless @target.include?(record) end end end diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 2a9627a474..0800639c24 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -28,8 +28,7 @@ module ActiveRecord # is computed directly through SQL and does not trigger by itself the # instantiation of the actual post records. class CollectionProxy < Relation - delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope) - delegate :find_nth, to: :scope + delegate :exists?, :update_all, :arel, to: :scope def initialize(klass, association) #:nodoc: @association = association @@ -54,6 +53,12 @@ module ActiveRecord @association.loaded? end + ## + # :method: select + # + # :call-seq: + # select(*fields, &block) + # # Works in two ways. # # *First:* Specify a subset of fields to be selected from the result set. @@ -101,15 +106,6 @@ module ActiveRecord # # #<Pet id: 2, name: "Spook", person_id: 1>, # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> # # ] - # - # person.pets.select(:name) { |pet| pet.name =~ /oo/ } - # # => [ - # # #<Pet id: 2, name: "Spook">, - # # #<Pet id: 3, name: "Choo-Choo"> - # # ] - def select(*fields, &block) - @association.select(*fields, &block) - end # Finds an object in the collection responding to the +id+. Uses the same # rules as ActiveRecord::Base.find. Returns ActiveRecord::RecordNotFound @@ -141,6 +137,12 @@ module ActiveRecord @association.find(*args, &block) end + ## + # :method: first + # + # :call-seq: + # first(limit = nil) + # # Returns the first record, or the first +n+ records, from the collection. # If the collection is empty, the first form returns +nil+, and the second # form returns an empty array. @@ -167,45 +169,63 @@ module ActiveRecord # another_person_without.pets # => [] # another_person_without.pets.first # => nil # another_person_without.pets.first(3) # => [] - def first(*args) - @association.first(*args) - end + ## + # :method: second + # + # :call-seq: + # second() + # # Same as #first except returns only the second record. - def second(*args) - @association.second(*args) - end + ## + # :method: third + # + # :call-seq: + # third() + # # Same as #first except returns only the third record. - def third(*args) - @association.third(*args) - end + ## + # :method: fourth + # + # :call-seq: + # fourth() + # # Same as #first except returns only the fourth record. - def fourth(*args) - @association.fourth(*args) - end + ## + # :method: fifth + # + # :call-seq: + # fifth() + # # Same as #first except returns only the fifth record. - def fifth(*args) - @association.fifth(*args) - end + ## + # :method: forty_two + # + # :call-seq: + # forty_two() + # # Same as #first except returns only the forty second record. # Also known as accessing "the reddit". - def forty_two(*args) - @association.forty_two(*args) - end + ## + # :method: third_to_last + # + # :call-seq: + # third_to_last() + # # Same as #first except returns only the third-to-last record. - def third_to_last(*args) - @association.third_to_last(*args) - end + ## + # :method: second_to_last + # + # :call-seq: + # second_to_last() + # # Same as #first except returns only the second-to-last record. - def second_to_last(*args) - @association.second_to_last(*args) - end # Returns the last record, or the last +n+ records, from the collection. # If the collection is empty, the first form returns +nil+, and the second @@ -233,8 +253,9 @@ module ActiveRecord # another_person_without.pets # => [] # another_person_without.pets.last # => nil # another_person_without.pets.last(3) # => [] - def last(*args) - @association.last(*args) + def last(limit = nil) + load_target if find_from_target? + super end # Gives a record (or N records if a parameter is supplied) from the collection @@ -262,8 +283,9 @@ module ActiveRecord # another_person_without.pets # => [] # another_person_without.pets.take # => nil # another_person_without.pets.take(2) # => [] - def take(n = nil) - @association.take(n) + def take(limit = nil) + load_target if find_from_target? + super end # Returns a new object of the collection type that has been instantiated @@ -597,7 +619,7 @@ module ActiveRecord # Pet.find(1) # # => ActiveRecord::RecordNotFound: Couldn't find Pet with 'id'=1 # - # You can pass +Fixnum+ or +String+ values, it finds the records + # You can pass +Integer+ or +String+ values, it finds the records # responding to the +id+ and executes delete on them. # # class Person < ActiveRecord::Base @@ -661,7 +683,7 @@ module ActiveRecord # # Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3) # - # You can pass +Fixnum+ or +String+ values, it finds the records + # You can pass +Integer+ or +String+ values, it finds the records # responding to the +id+ and then deletes them from the database. # # person.pets.size # => 3 @@ -715,12 +737,27 @@ module ActiveRecord end alias uniq distinct - # Count all records using SQL. + def calculate(operation, column_name) + null_scope? ? scope.calculate(operation, column_name) : super + end + + def pluck(*column_names) + null_scope? ? scope.pluck(*column_names) : super + end + + ## + # :method: count + # + # :call-seq: + # count(column_name = nil, &block) + # + # Count all records. # # class Person < ActiveRecord::Base # has_many :pets # end # + # # This will perform the count using SQL. # person.pets.count # => 3 # person.pets # # => [ @@ -728,9 +765,11 @@ module ActiveRecord # # #<Pet id: 2, name: "Spook", person_id: 1>, # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> # # ] - def count(column_name = nil) - @association.count(column_name) - end + # + # Passing a block will select all of a person's pets in SQL and then + # perform the count using Ruby. + # + # person.pets.count { |pet| pet.name.include?('-') } # => 2 # Returns the size of the collection. If the collection hasn't been loaded, # it executes a <tt>SELECT COUNT(*)</tt> query. Else it calls <tt>collection.size</tt>. @@ -760,6 +799,12 @@ module ActiveRecord @association.size end + ## + # :method: length + # + # :call-seq: + # length() + # # Returns the size of the collection calling +size+ on the target. # If the collection has been already loaded, +length+ and +size+ are # equivalent. If not and you are going to need the records anyway this @@ -780,14 +825,11 @@ module ActiveRecord # # #<Pet id: 2, name: "Spook", person_id: 1>, # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> # # ] - def length - @association.length - end # Returns +true+ if the collection is empty. If the collection has been # loaded it is equivalent # to <tt>collection.size.zero?</tt>. If the collection has not been loaded, - # it is equivalent to <tt>collection.exists?</tt>. If the collection has + # it is equivalent to <tt>!collection.exists?</tt>. If the collection has # not already been loaded and you are going to fetch the records anyway it # is better to check <tt>collection.length.zero?</tt>. # @@ -806,6 +848,12 @@ module ActiveRecord @association.empty? end + ## + # :method: any? + # + # :call-seq: + # any?() + # # Returns +true+ if the collection is not empty. # # class Person < ActiveRecord::Base @@ -835,10 +883,13 @@ module ActiveRecord # pet.group == 'dogs' # end # # => true - def any?(&block) - @association.any?(&block) - end + ## + # :method: many? + # + # :call-seq: + # many?() + # # Returns true if the collection has more than one record. # Equivalent to <tt>collection.size > 1</tt>. # @@ -873,9 +924,6 @@ module ActiveRecord # pet.group == 'cats' # end # # => true - def many?(&block) - @association.many?(&block) - end # Returns +true+ if the given +record+ is present in the collection. # @@ -891,10 +939,6 @@ module ActiveRecord !!@association.include?(record) end - def arel #:nodoc: - scope.arel - end - def proxy_association @association end @@ -979,6 +1023,10 @@ module ActiveRecord end alias_method :to_a, :to_ary + def records # :nodoc: + load_target + end + # Adds one or more +records+ to the collection by setting their foreign keys # to the association's primary key. Returns +self+, so several appends may be # chained together. @@ -1064,6 +1112,32 @@ module ActiveRecord proxy_association.reset_scope self end + + protected + + def find_nth_with_limit(index, limit) + load_target if find_from_target? + super + end + + def find_nth_from_last(index) + load_target if find_from_target? + super + end + + private + + def null_scope? + @association.null_scope? + end + + def find_from_target? + @association.find_from_target? + end + + def exec_queries + load_target + end end end end diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index a9f6aaafef..d1d0cc4c49 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -27,14 +27,12 @@ module ActiveRecord throw(:abort) end + when :destroy + # No point in executing the counter update since we're going to destroy the parent anyway + load_target.each { |t| t.destroyed_by_association = reflection } + destroy_all else - if options[:dependent] == :destroy - # No point in executing the counter update since we're going to destroy the parent anyway - load_target.each { |t| t.destroyed_by_association = reflection } - destroy_all - else - delete_all - end + delete_all end end @@ -43,9 +41,9 @@ module ActiveRecord set_inverse_instance(record) if raise - record.save!(:validate => validate) + record.save!(validate: validate) else - record.save(:validate => validate) + record.save(validate: validate) end end @@ -82,7 +80,7 @@ module ActiveRecord # If there's nothing in the database and @target has no new records # we are certain the current target is an empty array. This is a # documented side-effect of the method that may avoid an extra SELECT. - @target ||= [] and loaded! if count == 0 + (@target ||= []) && loaded! if count == 0 [association_scope.limit_value, count].compact.min 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 7ddb96db13..8c90aea975 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -39,9 +39,9 @@ module ActiveRecord ensure_not_nested if raise - record.save!(:validate => validate) + record.save!(validate: validate) else - return unless record.save(:validate => validate) + return unless record.save(validate: validate) end save_through_record(record) @@ -149,7 +149,7 @@ module ActiveRecord stmt.from scope.klass.arel_table stmt.wheres = arel.constraints - count = scope.klass.connection.delete(stmt, 'SQL', scope.bound_attributes) + count = scope.klass.connection.delete(stmt, "SQL", scope.bound_attributes) end when :nullify count = scope.update_all(source_reflection.foreign_key => nil) @@ -199,13 +199,17 @@ module ActiveRecord def find_target return [] unless target_reflection_has_associated_record? - get_records + super end # NOTE - not sure that we can actually cope with inverses here 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 0fe9b2e81b..5ea9577301 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -32,7 +32,7 @@ module ActiveRecord raise_on_type_mismatch!(record) if record load_target - return self.target if !(target || record) + return target unless target || record assigning_another_record = target != record if assigning_another_record || record.changed? @@ -60,12 +60,12 @@ module ActiveRecord def delete(method = options[:dependent]) if load_target case method - when :delete - target.delete - when :destroy - target.destroy - when :nullify - target.update_columns(reflection.foreign_key => nil) if target.persisted? + when :delete + target.delete + when :destroy + target.destroy + when :nullify + target.update_columns(reflection.foreign_key => nil) if target.persisted? end end end @@ -82,18 +82,18 @@ module ActiveRecord def remove_target!(method) case method - when :delete - target.delete - when :destroy - target.destroy + when :delete + target.delete + when :destroy + target.destroy else - nullify_owner_attributes(target) + nullify_owner_attributes(target) - if target.persisted? && owner.persisted? && !target.save - set_owner_attributes(target) - raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " + - "The record failed to save after its foreign key was set to nil." - end + if target.persisted? && owner.persisted? && !target.save + set_owner_attributes(target) + 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 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 08e0ec691f..604904abcc 100644 --- a/activerecord/lib/active_record/associations/has_one_through_association.rb +++ b/activerecord/lib/active_record/associations/has_one_through_association.rb @@ -15,7 +15,7 @@ module ActiveRecord ensure_not_nested through_proxy = owner.association(through_reflection.name) - through_record = through_proxy.send(:load_target) + through_record = through_proxy.load_target if through_record && !record through_record.destroy diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index 0e4e951269..c26c469c1e 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -1,8 +1,8 @@ module ActiveRecord module Associations class JoinDependency # :nodoc: - autoload :JoinBase, 'active_record/associations/join_dependency/join_base' - autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association' + autoload :JoinBase, "active_record/associations/join_dependency/join_base" + autoload :JoinAssociation, "active_record/associations/join_dependency/join_association" class Aliases # :nodoc: def initialize(tables) @@ -92,8 +92,9 @@ module ActiveRecord # associations # => [:appointments] # joins # => [] # - def initialize(base, associations, joins) + def initialize(base, associations, joins, eager_loading: true) @alias_tracker = AliasTracker.create_with_joins(base.connection, base.table_name, joins, base.type_caster) + @eager_loading = eager_loading tree = self.class.make_tree associations @join_root = JoinBase.new base, build(tree, base) @join_root.children.each { |child| construct_tables! @join_root, child } @@ -153,7 +154,7 @@ module ActiveRecord class_name: join_root.base_klass.name } - message_bus.instrument('instantiation.active_record', payload) do + message_bus.instrument("instantiation.active_record", payload) do result_set.each { |row_hash| parent_key = primary_key ? row_hash[primary_key] : row_hash parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases) @@ -166,133 +167,140 @@ module ActiveRecord private - def make_constraints(parent, child, tables, join_type) - 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) - end + def make_constraints(parent, child, tables, join_type) + 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) + end - def make_outer_joins(parent, child) - tables = table_aliases_for(parent, child) - join_type = Arel::Nodes::OuterJoin - info = make_constraints parent, child, tables, join_type + def make_outer_joins(parent, child) + tables = table_aliases_for(parent, child) + join_type = Arel::Nodes::OuterJoin + info = make_constraints parent, child, tables, join_type - [info] + child.children.flat_map { |c| make_outer_joins(child, c) } - end + [info] + child.children.flat_map { |c| make_outer_joins(child, c) } + end - def make_left_outer_joins(parent, child) - tables = child.tables - join_type = Arel::Nodes::OuterJoin - info = make_constraints parent, child, tables, join_type + def make_left_outer_joins(parent, child) + tables = child.tables + join_type = Arel::Nodes::OuterJoin + info = make_constraints parent, child, tables, join_type - [info] + child.children.flat_map { |c| make_left_outer_joins(child, c) } - end + [info] + child.children.flat_map { |c| make_left_outer_joins(child, c) } + end - def make_inner_joins(parent, child) - tables = child.tables - join_type = Arel::Nodes::InnerJoin - info = make_constraints parent, child, tables, join_type + def make_inner_joins(parent, child) + tables = child.tables + join_type = Arel::Nodes::InnerJoin + info = make_constraints parent, child, tables, join_type - [info] + child.children.flat_map { |c| make_inner_joins(child, c) } - end + [info] + child.children.flat_map { |c| make_inner_joins(child, c) } + end - def table_aliases_for(parent, node) - node.reflection.chain.map { |reflection| - alias_tracker.aliased_table_for( - reflection.table_name, - table_alias_for(reflection, parent, reflection != node.reflection) - ) - } - end + def table_aliases_for(parent, node) + node.reflection.chain.map { |reflection| + alias_tracker.aliased_table_for( + reflection.table_name, + table_alias_for(reflection, parent, reflection != node.reflection) + ) + } + end - def construct_tables!(parent, node) - node.tables = table_aliases_for(parent, node) - node.children.each { |child| construct_tables! node, child } - end + def construct_tables!(parent, node) + node.tables = table_aliases_for(parent, node) + node.children.each { |child| construct_tables! node, child } + end - def table_alias_for(reflection, parent, join) - name = "#{reflection.plural_name}_#{parent.table_name}" - name << "_join" if join - name - end + def table_alias_for(reflection, parent, join) + name = "#{reflection.plural_name}_#{parent.table_name}" + name << "_join" if join + name + end - def walk(left, right) - intersection, missing = right.children.map { |node1| - [left.children.find { |node2| node1.match? node2 }, node1] - }.partition(&:first) + def walk(left, right) + intersection, missing = right.children.map { |node1| + [left.children.find { |node2| node1.match? node2 }, node1] + }.partition(&:first) - ojs = missing.flat_map { |_,n| make_outer_joins left, n } - intersection.flat_map { |l,r| walk l, r }.concat ojs - end + ojs = missing.flat_map { |_,n| make_outer_joins left, n } + intersection.flat_map { |l,r| walk l, r }.concat ojs + end - def find_reflection(klass, name) - klass._reflect_on_association(name) or - raise ConfigurationError, "Association named '#{ name }' was not found on #{ klass.name }; perhaps you misspelled it?" - end + def find_reflection(klass, name) + klass._reflect_on_association(name) || + raise(ConfigurationError, "Can't join '#{klass.name}' to association named '#{name}'; perhaps you misspelled it?") + end - def build(associations, base_klass) - associations.map do |name, right| - reflection = find_reflection base_klass, name - reflection.check_validity! - reflection.check_eager_loadable! + def build(associations, base_klass) + associations.map do |name, right| + reflection = find_reflection base_klass, name + reflection.check_validity! + reflection.check_eager_loadable! - if reflection.polymorphic? - raise EagerLoadPolymorphicError.new(reflection) - end + if reflection.polymorphic? + next unless @eager_loading + raise EagerLoadPolymorphicError.new(reflection) + end - JoinAssociation.new reflection, build(right, reflection.klass) + JoinAssociation.new reflection, build(right, reflection.klass) + end.compact end - end - - def construct(ar_parent, parent, row, rs, seen, model_cache, aliases) - return if ar_parent.nil? - parent.children.each do |node| - if node.reflection.collection? - other = ar_parent.association(node.reflection.name) - other.loaded! - elsif ar_parent.association_cached?(node.reflection.name) - model = ar_parent.association(node.reflection.name).target - construct(model, node, row, rs, seen, model_cache, aliases) - next + def construct(ar_parent, parent, row, rs, seen, model_cache, aliases) + return if ar_parent.nil? + + parent.children.each do |node| + if node.reflection.collection? + other = ar_parent.association(node.reflection.name) + other.loaded! + elsif ar_parent.association_cached?(node.reflection.name) + model = ar_parent.association(node.reflection.name).target + construct(model, node, row, rs, seen, model_cache, aliases) + next + end + + key = aliases.column_alias(node, node.primary_key) + id = row[key] + if id.nil? + nil_association = ar_parent.association(node.reflection.name) + nil_association.loaded! + next + end + + model = seen[ar_parent.object_id][node.base_klass][id] + + if model + construct(model, node, row, rs, seen, model_cache, aliases) + else + model = construct_model(ar_parent, node, row, model_cache, id, aliases) + + if node.reflection.scope_for(node.base_klass).readonly_value + model.readonly! + end + + seen[ar_parent.object_id][node.base_klass][id] = model + construct(model, node, row, rs, seen, model_cache, aliases) + end end + end - key = aliases.column_alias(node, node.primary_key) - id = row[key] - if id.nil? - nil_association = ar_parent.association(node.reflection.name) - nil_association.loaded! - next - end + def construct_model(record, node, row, model_cache, id, aliases) + other = record.association(node.reflection.name) - model = seen[ar_parent.object_id][node.base_klass][id] + model = model_cache[node][id] ||= + node.instantiate(row, aliases.column_aliases(node)) do |m| + other.set_inverse_instance(m) + end - if model - construct(model, node, row, rs, seen, model_cache, aliases) + if node.reflection.collection? + other.target.push(model) else - model = construct_model(ar_parent, node, row, model_cache, id, aliases) - model.readonly! - seen[ar_parent.object_id][node.base_klass][id] = model - construct(model, node, row, rs, seen, model_cache, aliases) + other.target = model end - end - end - def construct_model(record, node, row, model_cache, id, aliases) - model = model_cache[node][id] ||= node.instantiate(row, - aliases.column_aliases(node)) - other = record.association(node.reflection.name) - - if node.reflection.collection? - other.target.push(model) - else - other.target = model + model end - - other.set_inverse_instance(model) - model - end end end end 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 c5fbe0d1d1..a5705951f3 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -1,4 +1,4 @@ -require 'active_record/associations/join_dependency/join_part' +require "active_record/associations/join_dependency/join_part" module ActiveRecord module Associations @@ -56,7 +56,9 @@ module ActiveRecord klass_scope = if klass.current_scope - klass.current_scope.clone + klass.current_scope.clone.tap { |scope| + scope.joins_values = [] + } else relation = ActiveRecord::Relation.create( klass, diff --git a/activerecord/lib/active_record/associations/join_dependency/join_base.rb b/activerecord/lib/active_record/associations/join_dependency/join_base.rb index 3a26c25737..fca20514d1 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_base.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_base.rb @@ -1,4 +1,4 @@ -require 'active_record/associations/join_dependency/join_part' +require "active_record/associations/join_dependency/join_part" module ActiveRecord module Associations diff --git a/activerecord/lib/active_record/associations/join_dependency/join_part.rb b/activerecord/lib/active_record/associations/join_dependency/join_part.rb index 9c6573f913..61cec5403a 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_part.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_part.rb @@ -15,7 +15,7 @@ module ActiveRecord # association. attr_reader :base_klass, :children - delegate :table_name, :column_names, :primary_key, :to => :base_klass + delegate :table_name, :column_names, :primary_key, to: :base_klass def initialize(base_klass, children) @base_klass = base_klass @@ -62,8 +62,8 @@ module ActiveRecord hash end - def instantiate(row, aliases) - base_klass.instantiate(extract_record(row, aliases)) + def instantiate(row, aliases, &block) + base_klass.instantiate(extract_record(row, aliases), &block) end end end diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index ecf6fb8643..9f77f38b35 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -42,16 +42,16 @@ module ActiveRecord extend ActiveSupport::Autoload eager_autoload do - autoload :Association, 'active_record/associations/preloader/association' - autoload :SingularAssociation, 'active_record/associations/preloader/singular_association' - autoload :CollectionAssociation, 'active_record/associations/preloader/collection_association' - autoload :ThroughAssociation, 'active_record/associations/preloader/through_association' - - autoload :HasMany, 'active_record/associations/preloader/has_many' - autoload :HasManyThrough, 'active_record/associations/preloader/has_many_through' - autoload :HasOne, 'active_record/associations/preloader/has_one' - autoload :HasOneThrough, 'active_record/associations/preloader/has_one_through' - autoload :BelongsTo, 'active_record/associations/preloader/belongs_to' + autoload :Association, "active_record/associations/preloader/association" + autoload :SingularAssociation, "active_record/associations/preloader/singular_association" + autoload :CollectionAssociation, "active_record/associations/preloader/collection_association" + autoload :ThroughAssociation, "active_record/associations/preloader/through_association" + + autoload :HasMany, "active_record/associations/preloader/has_many" + autoload :HasManyThrough, "active_record/associations/preloader/has_many_through" + autoload :HasOne, "active_record/associations/preloader/has_one" + autoload :HasOneThrough, "active_record/associations/preloader/has_one_through" + autoload :BelongsTo, "active_record/associations/preloader/belongs_to" end NULL_RELATION = Struct.new(:values, :where_clause, :joins_values).new({}, Relation::WhereClause.empty, []) @@ -106,107 +106,108 @@ module ActiveRecord private - # Loads all the given data into +records+ for the +association+. - def preloaders_on(association, records, scope) - case association - when Hash - preloaders_for_hash(association, records, scope) - when Symbol - preloaders_for_one(association, records, scope) - when String - preloaders_for_one(association.to_sym, records, scope) - else - raise ArgumentError, "#{association.inspect} was not recognized for preload" + # Loads all the given data into +records+ for the +association+. + def preloaders_on(association, records, scope) + case association + when Hash + preloaders_for_hash(association, records, scope) + when Symbol + preloaders_for_one(association, records, scope) + when String + preloaders_for_one(association.to_sym, records, scope) + else + raise ArgumentError, "#{association.inspect} was not recognized for preload" + end end - end - def preloaders_for_hash(association, records, scope) - association.flat_map { |parent, child| - loaders = preloaders_for_one parent, records, scope + def preloaders_for_hash(association, records, scope) + association.flat_map { |parent, child| + loaders = preloaders_for_one parent, records, scope - recs = loaders.flat_map(&:preloaded_records).uniq - loaders.concat Array.wrap(child).flat_map { |assoc| - preloaders_on assoc, recs, scope + recs = loaders.flat_map(&:preloaded_records).uniq + loaders.concat Array.wrap(child).flat_map { |assoc| + preloaders_on assoc, recs, scope + } + loaders } - loaders - } - end + end - # Loads all the given data into +records+ for a singular +association+. - # - # Functions by instantiating a preloader class such as Preloader::HasManyThrough and - # call the +run+ method for each passed in class in the +records+ argument. - # - # Not all records have the same class, so group then preload group on the reflection - # itself so that if various subclass share the same association then we do not split - # them unnecessarily - # - # Additionally, polymorphic belongs_to associations can have multiple associated - # classes, depending on the polymorphic_type field. So we group by the classes as - # well. - def preloaders_for_one(association, records, scope) - grouped_records(association, records).flat_map do |reflection, klasses| - klasses.map do |rhs_klass, rs| - loader = preloader_for(reflection, rs, rhs_klass).new(rhs_klass, rs, reflection, scope) - loader.run self - loader + # Loads all the given data into +records+ for a singular +association+. + # + # Functions by instantiating a preloader class such as Preloader::HasManyThrough and + # call the +run+ method for each passed in class in the +records+ argument. + # + # Not all records have the same class, so group then preload group on the reflection + # itself so that if various subclass share the same association then we do not split + # them unnecessarily + # + # Additionally, polymorphic belongs_to associations can have multiple associated + # classes, depending on the polymorphic_type field. So we group by the classes as + # well. + def preloaders_for_one(association, records, scope) + grouped_records(association, records).flat_map do |reflection, klasses| + klasses.map do |rhs_klass, rs| + loader = preloader_for(reflection, rs, rhs_klass).new(rhs_klass, rs, reflection, scope) + loader.run self + loader + end end end - end - def grouped_records(association, records) - h = {} - records.each do |record| - next unless record - assoc = record.association(association) - klasses = h[assoc.reflection] ||= {} - (klasses[assoc.klass] ||= []) << record + def grouped_records(association, records) + h = {} + records.each do |record| + next unless record + assoc = record.association(association) + klasses = h[assoc.reflection] ||= {} + (klasses[assoc.klass] ||= []) << record + end + h end - h - end - class AlreadyLoaded # :nodoc: - attr_reader :owners, :reflection + class AlreadyLoaded # :nodoc: + attr_reader :owners, :reflection - def initialize(klass, owners, reflection, preload_scope) - @owners = owners - @reflection = reflection - end + def initialize(klass, owners, reflection, preload_scope) + @owners = owners + @reflection = reflection + end - def run(preloader); end + def run(preloader); end - def preloaded_records - owners.flat_map { |owner| owner.association(reflection.name).target } + def preloaded_records + owners.flat_map { |owner| owner.association(reflection.name).target } + end end - end - class NullPreloader # :nodoc: - def self.new(klass, owners, reflection, preload_scope); self; end - def self.run(preloader); end - def self.preloaded_records; []; end - end + class NullPreloader # :nodoc: + def self.new(klass, owners, reflection, preload_scope); self; end + def self.run(preloader); end + def self.preloaded_records; []; end + def self.owners; []; end + end - # Returns a class containing the logic needed to load preload the data - # and attach it to a relation. For example +Preloader::Association+ or - # +Preloader::HasManyThrough+. The class returned implements a `run` method - # that accepts a preloader. - def preloader_for(reflection, owners, rhs_klass) - return NullPreloader unless rhs_klass + # Returns a class containing the logic needed to load preload the data + # and attach it to a relation. For example +Preloader::Association+ or + # +Preloader::HasManyThrough+. The class returned implements a `run` method + # that accepts a preloader. + def preloader_for(reflection, owners, rhs_klass) + return NullPreloader unless rhs_klass - if owners.first.association(reflection.name).loaded? - return AlreadyLoaded - end - reflection.check_preloadable! - - case reflection.macro - when :has_many - reflection.options[:through] ? HasManyThrough : HasMany - when :has_one - reflection.options[:through] ? HasOneThrough : HasOne - when :belongs_to - BelongsTo + if owners.first.association(reflection.name).loaded? + return AlreadyLoaded + end + reflection.check_preloadable! + + case reflection.macro + when :has_many + reflection.options[:through] ? HasManyThrough : HasMany + when :has_one + reflection.options[:through] ? HasOneThrough : HasOne + when :belongs_to + BelongsTo + end end - end end end end diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 3032bc786e..c79efca920 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -28,10 +28,6 @@ module ActiveRecord end def records_for(ids) - query_scope(ids) - end - - def query_scope(ids) scope.where(association_key_name => ids) end @@ -61,99 +57,113 @@ module ActiveRecord private - def associated_records_by_owner(preloader) - records = load_records - owners.each_with_object({}) do |owner, result| - result[owner] = records[convert_key(owner[owner_key_name])] || [] + def associated_records_by_owner(preloader) + records = load_records do |record| + owner = owners_by_key[convert_key(record[association_key_name])] + association = owner.association(reflection.name) + association.set_inverse_instance(record) + end + + owners.each_with_object({}) do |owner, result| + result[owner] = records[convert_key(owner[owner_key_name])] || [] + end end - end - def owner_keys - unless defined?(@owner_keys) - @owner_keys = owners.map do |owner| - owner[owner_key_name] + def owner_keys + unless defined?(@owner_keys) + @owner_keys = owners.map do |owner| + owner[owner_key_name] + end + @owner_keys.uniq! + @owner_keys.compact! end - @owner_keys.uniq! - @owner_keys.compact! + @owner_keys end - @owner_keys - end - def key_conversion_required? - @key_conversion_required ||= association_key_type != owner_key_type - end + def owners_by_key + unless defined?(@owners_by_key) + @owners_by_key = owners.each_with_object({}) do |owner, h| + h[convert_key(owner[owner_key_name])] = owner + end + end + @owners_by_key + end - def convert_key(key) - if key_conversion_required? - key.to_s - else - key + def key_conversion_required? + @key_conversion_required ||= association_key_type != owner_key_type end - end - def association_key_type - @klass.type_for_attribute(association_key_name.to_s).type - end + def convert_key(key) + if key_conversion_required? + key.to_s + else + key + end + end - def owner_key_type - @model.type_for_attribute(owner_key_name.to_s).type - end + def association_key_type + @klass.type_for_attribute(association_key_name.to_s).type + end + + def owner_key_type + @model.type_for_attribute(owner_key_name.to_s).type + end - def load_records - return {} if owner_keys.empty? - # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000) - # Make several smaller queries if necessary or make one query if the adapter supports it - slices = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size) - @preloaded_records = slices.flat_map do |slice| - records_for(slice) + def load_records(&block) + return {} if owner_keys.empty? + # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000) + # Make several smaller queries if necessary or make one query if the adapter supports it + slices = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size) + @preloaded_records = slices.flat_map do |slice| + records_for(slice).load(&block) + end + @preloaded_records.group_by do |record| + convert_key(record[association_key_name]) + end end - @preloaded_records.group_by do |record| - convert_key(record[association_key_name]) + + def reflection_scope + @reflection_scope ||= reflection.scope_for(klass) end - end - def reflection_scope - @reflection_scope ||= reflection.scope ? klass.unscoped.instance_exec(nil, &reflection.scope) : klass.unscoped - end + def build_scope + scope = klass.unscoped - def build_scope - scope = klass.unscoped + values = reflection_scope.values + preload_values = preload_scope.values - values = reflection_scope.values - preload_values = preload_scope.values + scope.where_clause = reflection_scope.where_clause + preload_scope.where_clause + scope.references_values = Array(values[:references]) + Array(preload_values[:references]) - scope.where_clause = reflection_scope.where_clause + preload_scope.where_clause - scope.references_values = Array(values[:references]) + Array(preload_values[:references]) + if preload_values[:select] || values[:select] + scope._select!(preload_values[:select] || values[:select]) + end + scope.includes! preload_values[:includes] || values[:includes] + if preload_scope.joins_values.any? + scope.joins!(preload_scope.joins_values) + else + scope.joins!(reflection_scope.joins_values) + end - if preload_values[:select] || values[:select] - scope._select!(preload_values[:select] || values[:select]) - end - scope.includes! preload_values[:includes] || values[:includes] - if preload_scope.joins_values.any? - scope.joins!(preload_scope.joins_values) - else - scope.joins!(reflection_scope.joins_values) - end + if order_values = preload_values[:order] || values[:order] + scope.order!(order_values) + end - if order_values = preload_values[:order] || values[:order] - scope.order!(order_values) - end + if preload_values[:reordering] || values[:reordering] + scope.reordering_value = true + end - if preload_values[:reordering] || values[:reordering] - scope.reordering_value = true - end + if preload_values[:readonly] || values[:readonly] + scope.readonly! + end - if preload_values[:readonly] || values[:readonly] - scope.readonly! - end + if options[:as] + scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name }) + end - if options[:as] - scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name }) + scope.unscope_values = Array(values[:unscope]) + Array(preload_values[:unscope]) + klass.default_scoped.merge(scope) end - - scope.unscope_values = Array(values[:unscope]) + Array(preload_values[:unscope]) - klass.default_scoped.merge(scope) - end end end end diff --git a/activerecord/lib/active_record/associations/preloader/belongs_to.rb b/activerecord/lib/active_record/associations/preloader/belongs_to.rb index 5091d4717a..38e231826c 100644 --- a/activerecord/lib/active_record/associations/preloader/belongs_to.rb +++ b/activerecord/lib/active_record/associations/preloader/belongs_to.rb @@ -2,7 +2,6 @@ module ActiveRecord module Associations class Preloader class BelongsTo < SingularAssociation #:nodoc: - def association_key_name reflection.options[:primary_key] || klass && klass.primary_key end @@ -10,7 +9,6 @@ module ActiveRecord def owner_key_name reflection.foreign_key end - end end end diff --git a/activerecord/lib/active_record/associations/preloader/collection_association.rb b/activerecord/lib/active_record/associations/preloader/collection_association.rb index 9939280fa4..26690bf16d 100644 --- a/activerecord/lib/active_record/associations/preloader/collection_association.rb +++ b/activerecord/lib/active_record/associations/preloader/collection_association.rb @@ -4,14 +4,13 @@ module ActiveRecord class CollectionAssociation < Association #:nodoc: private - def preload(preloader) - associated_records_by_owner(preloader).each do |owner, records| - association = owner.association(reflection.name) - association.loaded! - association.target.concat(records) - records.each { |record| association.set_inverse_instance(record) } + def preload(preloader) + associated_records_by_owner(preloader).each do |owner, records| + association = owner.association(reflection.name) + association.loaded! + association.target.concat(records) + end end - end end end end diff --git a/activerecord/lib/active_record/associations/preloader/has_many.rb b/activerecord/lib/active_record/associations/preloader/has_many.rb index 3ea91a8c11..20df1cc19a 100644 --- a/activerecord/lib/active_record/associations/preloader/has_many.rb +++ b/activerecord/lib/active_record/associations/preloader/has_many.rb @@ -2,7 +2,6 @@ module ActiveRecord module Associations class Preloader class HasMany < CollectionAssociation #:nodoc: - def association_key_name reflection.foreign_key end @@ -10,7 +9,6 @@ module ActiveRecord def owner_key_name reflection.active_record_primary_key end - end end end diff --git a/activerecord/lib/active_record/associations/preloader/singular_association.rb b/activerecord/lib/active_record/associations/preloader/singular_association.rb index f60647a81e..5c5828262e 100644 --- a/activerecord/lib/active_record/associations/preloader/singular_association.rb +++ b/activerecord/lib/active_record/associations/preloader/singular_association.rb @@ -2,19 +2,16 @@ module ActiveRecord module Associations class Preloader class SingularAssociation < Association #:nodoc: - private - def preload(preloader) - associated_records_by_owner(preloader).each do |owner, associated_records| - record = associated_records.first + def preload(preloader) + associated_records_by_owner(preloader).each do |owner, associated_records| + record = associated_records.first - association = owner.association(reflection.name) - association.target = record - association.set_inverse_instance(record) if record + association = owner.association(reflection.name) + association.target = record + end end - end - end end end diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index 6c83058202..be9dfe7686 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -38,12 +38,7 @@ module ActiveRecord } end - record_offset = {} - @preloaded_records.each_with_index do |record,i| - record_offset[record] = i - end - - through_records.each_with_object({}) { |(lhs,center),records_by_owner| + through_records.each_with_object({}) do |(lhs,center), records_by_owner| pl_to_middle = center.group_by { |record| middle_to_pl[record] } records_by_owner[lhs] = pl_to_middle.flat_map do |pl, middles| @@ -53,49 +48,60 @@ module ActiveRecord target_records_from_association(association) }.compact - rhs_records.sort_by { |rhs| record_offset[rhs] } + # Respect the order on `reflection_scope` if it exists, else use the natural order. + if reflection_scope.values[:order].present? + @id_map ||= id_to_index_map @preloaded_records + rhs_records.sort_by { |rhs| @id_map[rhs] } + else + rhs_records + end end - } + end end private - def reset_association(owners, association_name) - should_reset = (through_scope != through_reflection.klass.unscoped) || - (reflection.options[:source_type] && through_reflection.collection?) - - # Don't cache the association - we would only be caching a subset - if should_reset - owners.each { |owner| - owner.association(association_name).reset - } + def id_to_index_map(ids) + id_map = {} + ids.each_with_index { |id, index| id_map[id] = index } + id_map end - end - - def through_scope - scope = through_reflection.klass.unscoped + def reset_association(owners, association_name) + should_reset = (through_scope != through_reflection.klass.unscoped) || + (reflection.options[:source_type] && through_reflection.collection?) - if options[:source_type] - scope.where! reflection.foreign_type => options[:source_type] - else - unless reflection_scope.where_clause.empty? - scope.includes_values = Array(reflection_scope.values[:includes] || options[:source]) - scope.where_clause = reflection_scope.where_clause + # Don't cache the association - we would only be caching a subset + if should_reset + owners.each { |owner| + owner.association(association_name).reset + } end + end - scope.references! reflection_scope.values[:references] - if scope.eager_loading? && order_values = reflection_scope.values[:order] - scope = scope.order(order_values) + def through_scope + scope = through_reflection.klass.unscoped + + if options[:source_type] + scope.where! reflection.foreign_type => options[:source_type] + else + unless reflection_scope.where_clause.empty? + scope.includes_values = Array(reflection_scope.values[:includes] || options[:source]) + scope.where_clause = reflection_scope.where_clause + end + + scope.references! reflection_scope.values[:references] + if scope.eager_loading? && order_values = reflection_scope.values[:order] + scope = scope.order(order_values) + end end - end - scope - end + scope + end - def target_records_from_association(association) - association.loaded? ? association.target : association.reader - end + def target_records_from_association(association) + association.loaded? ? association.target : association.reader + end end end end diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb index c7cc48ba16..e386cc0e4c 100644 --- a/activerecord/lib/active_record/associations/singular_association.rb +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -23,14 +23,6 @@ module ActiveRecord replace(record) end - def create(attributes = {}, &block) - _create_record(attributes, &block) - end - - def create!(attributes = {}, &block) - _create_record(attributes, true, &block) - end - def build(attributes = {}) record = build_record(attributes) yield(record) if block_given? @@ -44,8 +36,8 @@ module ActiveRecord scope.scope_for_create.stringify_keys.except(klass.primary_key) end - def get_records - return scope.limit(1).to_a if skip_statement_cache? + def find_target + return scope.take if skip_statement_cache? conn = klass.connection sc = reflection.association_scope_cache(conn, owner) do @@ -56,13 +48,9 @@ module ActiveRecord end binds = AssociationScope.get_bind_values(owner, reflection.chain) - sc.execute binds, klass, klass.connection - end - - def find_target - if record = get_records.first + sc.execute(binds, klass, conn) do |record| set_inverse_instance record - end + end.first 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 d0ec3e8015..f4129edc5a 100644 --- a/activerecord/lib/active_record/associations/through_association.rb +++ b/activerecord/lib/active_record/associations/through_association.rb @@ -2,8 +2,7 @@ module ActiveRecord # = Active Record Through Association module Associations module ThroughAssociation #:nodoc: - - delegate :source_reflection, :through_reflection, :to => :reflection + delegate :source_reflection, :through_reflection, to: :reflection protected diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb index 3c4c8f10ec..0b08c2a39b 100644 --- a/activerecord/lib/active_record/attribute.rb +++ b/activerecord/lib/active_record/attribute.rb @@ -65,7 +65,7 @@ module ActiveRecord def with_value_from_user(value) type.assert_valid_value(value) - self.class.from_user(name, value, type, self) + self.class.from_user(name, value, type, original_attribute || self) end def with_value_from_database(value) @@ -77,7 +77,11 @@ module ActiveRecord end def with_type(type) - self.class.new(name, value_before_type_cast, type, original_attribute) + if changed_in_place? + with_value_from_user(value).with_type(type) + else + self.class.new(name, value_before_type_cast, type, original_attribute) + end end def type_cast(*) @@ -108,100 +112,126 @@ module ActiveRecord [self.class, name, value_before_type_cast, type].hash end - protected - - attr_reader :original_attribute - alias_method :assigned?, :original_attribute - - def initialize_dup(other) - if defined?(@value) && @value.duplicable? - @value = @value.dup - end + def init_with(coder) + @name = coder["name"] + @value_before_type_cast = coder["value_before_type_cast"] + @type = coder["type"] + @original_attribute = coder["original_attribute"] + @value = coder["value"] if coder.map.key?("value") end - def changed_from_assignment? - assigned? && type.changed?(original_value, value, value_before_type_cast) + def encode_with(coder) + coder["name"] = name + coder["value_before_type_cast"] = value_before_type_cast if value_before_type_cast + coder["type"] = type if type + coder["original_attribute"] = original_attribute if original_attribute + coder["value"] = value if defined?(@value) end - def original_value_for_database - if assigned? - original_attribute.original_value_for_database - else - _original_value_for_database - end - end + protected - def _original_value_for_database - value_for_database - end + attr_reader :original_attribute + alias_method :assigned?, :original_attribute - class FromDatabase < Attribute # :nodoc: - def type_cast(value) - type.deserialize(value) + def initialize_dup(other) + if defined?(@value) && @value.duplicable? + @value = @value.dup + end end - def _original_value_for_database - value_before_type_cast + def changed_from_assignment? + assigned? && type.changed?(original_value, value, value_before_type_cast) end - end - class FromUser < Attribute # :nodoc: - def type_cast(value) - type.cast(value) + def original_value_for_database + if assigned? + original_attribute.original_value_for_database + else + _original_value_for_database + end end - def came_from_user? - true + def _original_value_for_database + type.serialize(original_value) end - end - class WithCastValue < Attribute # :nodoc: - def type_cast(value) - value - end + class FromDatabase < Attribute # :nodoc: + def type_cast(value) + type.deserialize(value) + end - def changed_in_place_from?(old_value) - false + def _original_value_for_database + value_before_type_cast + end end - end - class Null < Attribute # :nodoc: - def initialize(name) - super(name, nil, Type::Value.new) - end + class FromUser < Attribute # :nodoc: + def type_cast(value) + type.cast(value) + end - def value - nil + def came_from_user? + true + end end - def with_type(type) - self.class.with_cast_value(name, nil, type) - end + class WithCastValue < Attribute # :nodoc: + def type_cast(value) + value + end - def with_value_from_database(value) - raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`" + def changed_in_place? + false + end end - alias_method :with_value_from_user, :with_value_from_database - end - class Uninitialized < Attribute # :nodoc: - def initialize(name, type) - super(name, nil, type) - end + class Null < Attribute # :nodoc: + def initialize(name) + super(name, nil, Type.default_value) + end - def value - if block_given? - yield name + def type_cast(*) + nil + end + + def with_type(type) + self.class.with_cast_value(name, nil, type) end - end - def value_for_database + def with_value_from_database(value) + raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`" + end + alias_method :with_value_from_user, :with_value_from_database end - def initialized? - false + class Uninitialized < Attribute # :nodoc: + UNINITIALIZED_ORIGINAL_VALUE = Object.new + + def initialize(name, type) + super(name, nil, type) + end + + def value + if block_given? + yield name + end + end + + def original_value + UNINITIALIZED_ORIGINAL_VALUE + end + + def value_for_database + end + + def initialized? + false + end + + def with_type(type) + self.class.new(name, type) + end end - end - private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue + private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue end end diff --git a/activerecord/lib/active_record/attribute/user_provided_default.rb b/activerecord/lib/active_record/attribute/user_provided_default.rb index 6dbd92ce28..a4e2c2ec85 100644 --- a/activerecord/lib/active_record/attribute/user_provided_default.rb +++ b/activerecord/lib/active_record/attribute/user_provided_default.rb @@ -1,23 +1,28 @@ -require 'active_record/attribute' +require "active_record/attribute" module ActiveRecord class Attribute # :nodoc: class UserProvidedDefault < FromUser # :nodoc: def initialize(name, value, type, database_default) + @user_provided_value = value super(name, value, type, database_default) end - def type_cast(value) - if value.is_a?(Proc) - super(value.call) + def value_before_type_cast + if user_provided_value.is_a?(Proc) + @memoized_value_before_type_cast ||= user_provided_value.call else - super + @user_provided_value end end def with_type(type) - self.class.new(name, value_before_type_cast, type, original_attribute) + self.class.new(name, user_provided_value, type, original_attribute) end + + protected + + attr_reader :user_provided_value end end end diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index a6d81c82b4..9843e0ca66 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -1,4 +1,4 @@ -require 'active_model/forbidden_attributes_protection' +require "active_model/forbidden_attributes_protection" module ActiveRecord module AttributeAssignment @@ -12,88 +12,80 @@ module ActiveRecord private - def _assign_attributes(attributes) # :nodoc: - multi_parameter_attributes = {} - nested_parameter_attributes = {} + def _assign_attributes(attributes) # :nodoc: + multi_parameter_attributes = {} + nested_parameter_attributes = {} - attributes.each do |k, v| - if k.include?("(") - multi_parameter_attributes[k] = attributes.delete(k) - elsif v.is_a?(Hash) - nested_parameter_attributes[k] = attributes.delete(k) + attributes.each do |k, v| + if k.include?("(") + multi_parameter_attributes[k] = attributes.delete(k) + elsif v.is_a?(Hash) + nested_parameter_attributes[k] = attributes.delete(k) + end end - end - super(attributes) - - assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty? - assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty? - end + super(attributes) - # Tries to assign given value to given attribute. - # In case of an error, re-raises with the ActiveRecord constant. - def _assign_attribute(k, v) # :nodoc: - super - rescue ActiveModel::UnknownAttributeError - raise UnknownAttributeError.new(self, k) - end + assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty? + assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty? + end - # Assign any deferred nested attributes after the base attributes have been set. - def assign_nested_parameter_attributes(pairs) - pairs.each { |k, v| _assign_attribute(k, v) } - end + # Assign any deferred nested attributes after the base attributes have been set. + def assign_nested_parameter_attributes(pairs) + pairs.each { |k, v| _assign_attribute(k, v) } + end - # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done - # by calling new on the column type or aggregation type (through composed_of) object with these parameters. - # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate - # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the - # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum and - # f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+. - def assign_multiparameter_attributes(pairs) - execute_callstack_for_multiparameter_attributes( - extract_callstack_for_multiparameter_attributes(pairs) - ) - end + # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done + # by calling new on the column type or aggregation type (through composed_of) object with these parameters. + # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate + # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the + # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and + # f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+. + def assign_multiparameter_attributes(pairs) + execute_callstack_for_multiparameter_attributes( + extract_callstack_for_multiparameter_attributes(pairs) + ) + end - def execute_callstack_for_multiparameter_attributes(callstack) - errors = [] - callstack.each do |name, values_with_empty_parameters| - begin - if values_with_empty_parameters.each_value.all?(&:nil?) - values = nil - else - values = values_with_empty_parameters + def execute_callstack_for_multiparameter_attributes(callstack) + errors = [] + callstack.each do |name, values_with_empty_parameters| + begin + if values_with_empty_parameters.each_value.all?(&:nil?) + values = nil + else + values = values_with_empty_parameters + end + send("#{name}=", values) + rescue => ex + errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name) end - send("#{name}=", values) - rescue => ex - errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name) + end + unless errors.empty? + error_descriptions = errors.map(&:message).join(",") + raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]" end end - unless errors.empty? - error_descriptions = errors.map(&:message).join(",") - raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]" - end - end - def extract_callstack_for_multiparameter_attributes(pairs) - attributes = {} + def extract_callstack_for_multiparameter_attributes(pairs) + attributes = {} - pairs.each do |(multiparameter_name, value)| - attribute_name = multiparameter_name.split("(").first - attributes[attribute_name] ||= {} + pairs.each do |(multiparameter_name, value)| + attribute_name = multiparameter_name.split("(").first + attributes[attribute_name] ||= {} - parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value) - attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value - end + parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value) + attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value + end - attributes - end + attributes + end - def type_cast_attribute_value(multiparameter_name, value) - multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value - end + def type_cast_attribute_value(multiparameter_name, value) + multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value + end - def find_parameter_position(multiparameter_name) - multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i - end + def find_parameter_position(multiparameter_name) + multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i + end end end diff --git a/activerecord/lib/active_record/attribute_decorators.rb b/activerecord/lib/active_record/attribute_decorators.rb index 7d0ae32411..340dfe11cf 100644 --- a/activerecord/lib/active_record/attribute_decorators.rb +++ b/activerecord/lib/active_record/attribute_decorators.rb @@ -24,13 +24,13 @@ module ActiveRecord private - def load_schema! - super - attribute_types.each do |name, type| - decorated_type = attribute_type_decorations.apply(name, type) - define_attribute(name, decorated_type) + def load_schema! + super + attribute_types.each do |name, type| + decorated_type = attribute_type_decorations.apply(name, type) + define_attribute(name, decorated_type) + end end - end end class TypeDecorator # :nodoc: @@ -53,15 +53,15 @@ module ActiveRecord private - def decorators_for(name, type) - matching(name, type).map(&:last) - end + def decorators_for(name, type) + matching(name, type).map(&:last) + end - def matching(name, type) - @decorations.values.select do |(matcher, _)| - matcher.call(name, type) + def matching(name, type) + @decorations.values.select do |(matcher, _)| + matcher.call(name, type) + end end - end end end end diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index e902eb7531..1ed1deec55 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -1,7 +1,7 @@ -require 'active_support/core_ext/enumerable' -require 'active_support/core_ext/string/filters' -require 'mutex_m' -require 'concurrent/map' +require "active_support/core_ext/enumerable" +require "active_support/core_ext/string/filters" +require "mutex_m" +require "concurrent/map" module ActiveRecord # = Active Record Attribute Methods @@ -148,7 +148,7 @@ module ActiveRecord # Person.attribute_method?(:age=) # => true # Person.attribute_method?(:nothing) # => false def attribute_method?(attribute) - super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, ''))) + super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, ""))) end # Returns an array of column names as strings if it's not an abstract class and @@ -161,10 +161,10 @@ module ActiveRecord # # => ["id", "created_at", "updated_at", "name", "age"] def attribute_names @attribute_names ||= if !abstract_class? && table_exists? - attribute_types.keys - else - [] - end + attribute_types.keys + else + [] + end end # Returns true if the given attribute exists, otherwise false. @@ -209,13 +209,13 @@ module ActiveRecord # end # # person = Person.new - # person.respond_to(:name) # => true - # person.respond_to(:name=) # => true - # person.respond_to(:name?) # => true - # person.respond_to('age') # => true - # person.respond_to('age=') # => true - # person.respond_to('age?') # => true - # person.respond_to(:nothing) # => false + # person.respond_to?(:name) # => true + # person.respond_to?(:name=) # => true + # person.respond_to?(:name?) # => true + # person.respond_to?('age') # => true + # person.respond_to?('age=') # => true + # person.respond_to?('age?') # => true + # person.respond_to?(:nothing) # => false def respond_to?(name, include_private = false) return false unless super @@ -279,9 +279,8 @@ module ActiveRecord # Returns an <tt>#inspect</tt>-like string for the value of the # attribute +attr_name+. String attributes are truncated up to 50 # characters, Date and Time attributes are returned in the - # <tt>:db</tt> format, Array attributes are truncated up to 10 values. - # Other attributes return the value of <tt>#inspect</tt> without - # modification. + # <tt>:db</tt> format. Other attributes return the value of + # <tt>#inspect</tt> without modification. # # person = Person.create!(name: 'David Heinemeier Hansson ' * 3) # @@ -292,7 +291,7 @@ module ActiveRecord # # => "\"2012-10-22 00:15:07\"" # # person.attribute_for_inspect(:tag_ids) - # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]" + # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]" def attribute_for_inspect(attr_name) value = read_attribute(attr_name) @@ -300,9 +299,6 @@ module ActiveRecord "#{value[0, 50]}...".inspect elsif value.is_a?(Date) || value.is_a?(Time) %("#{value.to_s(:db)}") - elsif value.is_a?(Array) && value.size > 10 - inspected = value.first(10).inspect - %(#{inspected[0...-1]}, ...]) else value.inspect end @@ -334,8 +330,6 @@ module ActiveRecord # # Note: +:id+ is always present. # - # Alias for the #read_attribute method. - # # class Person < ActiveRecord::Base # belongs_to :organization # end @@ -360,7 +354,7 @@ module ActiveRecord # person = Person.new # person[:age] = '22' # person[:age] # => 22 - # person[:age] # => Fixnum + # person[:age].class # => Integer def []=(attr_name, value) write_attribute(attr_name, value) end @@ -400,65 +394,65 @@ 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 - end + def clone_attribute_value(reader_method, attribute_name) # :nodoc: + value = send(reader_method, attribute_name) + value.duplicable? ? value.clone : value + rescue TypeError, NoMethodError + value + end - def arel_attributes_with_values_for_create(attribute_names) # :nodoc: - arel_attributes_with_values(attributes_for_create(attribute_names)) - end + def arel_attributes_with_values_for_create(attribute_names) # :nodoc: + arel_attributes_with_values(attributes_for_create(attribute_names)) + end - def arel_attributes_with_values_for_update(attribute_names) # :nodoc: - arel_attributes_with_values(attributes_for_update(attribute_names)) - end + def arel_attributes_with_values_for_update(attribute_names) # :nodoc: + 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 + 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) - attrs = {} - arel_table = self.class.arel_table + # 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) + attrs = {} + arel_table = self.class.arel_table - attribute_names.each do |name| - attrs[arel_table[name]] = typecasted_attribute_value(name) + attribute_names.each do |name| + attrs[arel_table[name]] = typecasted_attribute_value(name) + end + attrs end - attrs - end - # Filters the primary keys and readonly attributes from the attribute names. - def attributes_for_update(attribute_names) - attribute_names.reject do |name| - readonly_attribute?(name) + # Filters the primary keys and readonly attributes from the attribute names. + def attributes_for_update(attribute_names) + attribute_names.reject do |name| + readonly_attribute?(name) + end end - end - # Filters out the primary keys, from the attribute names, when the primary - # key is to be generated (e.g. the id attribute has no value). - def attributes_for_create(attribute_names) - attribute_names.reject do |name| - pk_attribute?(name) && id.nil? + # Filters out the primary keys, from the attribute names, when the primary + # key is to be generated (e.g. the id attribute has no value). + def attributes_for_create(attribute_names) + attribute_names.reject do |name| + pk_attribute?(name) && id.nil? + end end - end - def readonly_attribute?(name) - self.class.readonly_attributes.include?(name) - end + def readonly_attribute?(name) + self.class.readonly_attributes.include?(name) + end - def pk_attribute?(name) - name == self.class.primary_key - end + def pk_attribute?(name) + name == self.class.primary_key + end - def typecasted_attribute_value(name) - _read_attribute(name) - end + def typecasted_attribute_value(name) + _read_attribute(name) + end end end diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb index 1db6776688..115eb1ef3f 100644 --- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb +++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb @@ -63,14 +63,14 @@ module ActiveRecord private - # Handle *_before_type_cast for method_missing. - def attribute_before_type_cast(attribute_name) - read_attribute_before_type_cast(attribute_name) - end + # Handle *_before_type_cast for method_missing. + def attribute_before_type_cast(attribute_name) + read_attribute_before_type_cast(attribute_name) + end - def attribute_came_from_user?(attribute_name) - @attributes[attribute_name].came_from_user? - end + def attribute_came_from_user?(attribute_name) + @attributes[attribute_name].came_from_user? + end end end end diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 0bcfa5f00d..c9638bf70b 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -1,5 +1,5 @@ -require 'active_support/core_ext/module/attribute_accessors' -require 'active_record/attribute_mutation_tracker' +require "active_support/core_ext/module/attribute_accessors" +require "active_record/attribute_mutation_tracker" module ActiveRecord module AttributeMethods @@ -100,52 +100,52 @@ module ActiveRecord private - def mutation_tracker - unless defined?(@mutation_tracker) - @mutation_tracker = nil + def mutation_tracker + unless defined?(@mutation_tracker) + @mutation_tracker = nil + end + @mutation_tracker ||= AttributeMutationTracker.new(@attributes) end - @mutation_tracker ||= AttributeMutationTracker.new(@attributes) - end - def changes_include?(attr_name) - super || mutation_tracker.changed?(attr_name) - end + def changes_include?(attr_name) + super || mutation_tracker.changed?(attr_name) + end - def clear_attribute_change(attr_name) - mutation_tracker.forget_change(attr_name) - end + def clear_attribute_change(attr_name) + mutation_tracker.forget_change(attr_name) + end - def _update_record(*) - partial_writes? ? super(keys_for_partial_write) : super - end + def _update_record(*) + partial_writes? ? super(keys_for_partial_write) : super + end - def _create_record(*) - partial_writes? ? super(keys_for_partial_write) : super - end + def _create_record(*) + partial_writes? ? super(keys_for_partial_write) : super + end - def keys_for_partial_write - changed & self.class.column_names - end + def keys_for_partial_write + changed & self.class.column_names + end - def store_original_attributes - @attributes = @attributes.map(&:forgetting_assignment) - @mutation_tracker = nil - end + def store_original_attributes + @attributes = @attributes.map(&:forgetting_assignment) + @mutation_tracker = nil + end - def previous_mutation_tracker - @previous_mutation_tracker ||= NullMutationTracker.instance - end + def previous_mutation_tracker + @previous_mutation_tracker ||= NullMutationTracker.instance + end - def cache_changed_attributes - @cached_changed_attributes = changed_attributes - yield - ensure - clear_changed_attributes_cache - end + def cache_changed_attributes + @cached_changed_attributes = changed_attributes + yield + ensure + clear_changed_attributes_cache + end - def clear_changed_attributes_cache - remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes) - end + def clear_changed_attributes_cache + remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes) + end end end end diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index 0d5cb8b37c..6243398a52 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -1,4 +1,4 @@ -require 'set' +require "set" module ActiveRecord module AttributeMethods @@ -47,82 +47,95 @@ module ActiveRecord protected - def attribute_method?(attr_name) - attr_name == 'id' || super - end + def attribute_method?(attr_name) + attr_name == "id" || super + end - module ClassMethods - def define_method_attribute(attr_name) - super + module ClassMethods + def define_method_attribute(attr_name) + super - if attr_name == primary_key && attr_name != 'id' - generated_attribute_methods.send(:alias_method, :id, primary_key) + if attr_name == primary_key && attr_name != "id" + generated_attribute_methods.send(:alias_method, :id, primary_key) + end end - end - ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was).to_set + ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was).to_set - def dangerous_attribute_method?(method_name) - super && !ID_ATTRIBUTE_METHODS.include?(method_name) - end + def dangerous_attribute_method?(method_name) + super && !ID_ATTRIBUTE_METHODS.include?(method_name) + end - # Defines the primary key field -- can be overridden in subclasses. - # Overwriting will negate any effect of the +primary_key_prefix_type+ - # setting, though. - def primary_key - @primary_key = reset_primary_key unless defined? @primary_key - @primary_key - end + # Defines the primary key field -- can be overridden in subclasses. + # Overwriting will negate any effect of the +primary_key_prefix_type+ + # setting, though. + def primary_key + @primary_key = reset_primary_key unless defined? @primary_key + @primary_key + end - # Returns a quoted version of the primary key name, used to construct - # SQL statements. - def quoted_primary_key - @quoted_primary_key ||= connection.quote_column_name(primary_key) - end + # Returns a quoted version of the primary key name, used to construct + # SQL statements. + def quoted_primary_key + @quoted_primary_key ||= connection.quote_column_name(primary_key) + end - def reset_primary_key #:nodoc: - if self == base_class - self.primary_key = get_primary_key(base_class.name) - else - self.primary_key = base_class.primary_key + def reset_primary_key #:nodoc: + if self == base_class + self.primary_key = get_primary_key(base_class.name) + else + self.primary_key = base_class.primary_key + end end - end - def get_primary_key(base_name) #:nodoc: - if base_name && primary_key_prefix_type == :table_name - base_name.foreign_key(false) - elsif base_name && primary_key_prefix_type == :table_name_with_underscore - base_name.foreign_key - else - if ActiveRecord::Base != self && table_exists? - connection.schema_cache.primary_keys(table_name) + def get_primary_key(base_name) #:nodoc: + if base_name && primary_key_prefix_type == :table_name + base_name.foreign_key(false) + elsif base_name && primary_key_prefix_type == :table_name_with_underscore + base_name.foreign_key else - 'id' + if ActiveRecord::Base != self && table_exists? + pk = connection.schema_cache.primary_keys(table_name) + suppress_composite_primary_key(pk) + else + "id" + end end end - end - # Sets the name of the primary key column. - # - # class Project < ActiveRecord::Base - # self.primary_key = 'sysid' - # end - # - # You can also define the #primary_key method yourself: - # - # class Project < ActiveRecord::Base - # def self.primary_key - # 'foo_' + super - # end - # end - # - # Project.primary_key # => "foo_id" - def primary_key=(value) - @primary_key = value && value.to_s - @quoted_primary_key = nil - @attributes_builder = nil + # Sets the name of the primary key column. + # + # class Project < ActiveRecord::Base + # self.primary_key = 'sysid' + # end + # + # You can also define the #primary_key method yourself: + # + # class Project < ActiveRecord::Base + # def self.primary_key + # 'foo_' + super + # end + # end + # + # Project.primary_key # => "foo_id" + def primary_key=(value) + @primary_key = value && value.to_s + @quoted_primary_key = nil + @attributes_builder = nil + end + + private + + def suppress_composite_primary_key(pk) + return pk unless pk.is_a?(Array) + + warn <<-WARNING.strip_heredoc + WARNING: Active Record does not support composite primary key. + + #{table_name} has composite primary key. Composite primary key is ignored. + WARNING + end end - end end end end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index ab2ecaa7c5..30f7750884 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -6,42 +6,42 @@ module ActiveRecord module ClassMethods protected - # We want to generate the methods via module_eval rather than - # define_method, because define_method is slower on dispatch. - # Evaluating many similar methods may use more memory as the instruction - # sequences are duplicated and cached (in MRI). define_method may - # be slower on dispatch, but if you're careful about the closure - # created, then define_method will consume much less memory. - # - # But sometimes the database might return columns with - # characters that are not allowed in normal method names (like - # 'my_column(omg)'. So to work around this we first define with - # the __temp__ identifier, and then use alias method to rename - # it to what we want. - # - # We are also defining a constant to hold the frozen string of - # the attribute name. Using a constant means that we do not have - # to allocate an object on each call to the attribute method. - # Making it frozen means that it doesn't get duped when used to - # key the @attributes in read_attribute. - def define_method_attribute(name) - safe_name = name.unpack('h*'.freeze).first - temp_method = "__temp__#{safe_name}" + # We want to generate the methods via module_eval rather than + # define_method, because define_method is slower on dispatch. + # Evaluating many similar methods may use more memory as the instruction + # sequences are duplicated and cached (in MRI). define_method may + # be slower on dispatch, but if you're careful about the closure + # created, then define_method will consume much less memory. + # + # But sometimes the database might return columns with + # characters that are not allowed in normal method names (like + # 'my_column(omg)'. So to work around this we first define with + # the __temp__ identifier, and then use alias method to rename + # it to what we want. + # + # We are also defining a constant to hold the frozen string of + # the attribute name. Using a constant means that we do not have + # to allocate an object on each call to the attribute method. + # Making it frozen means that it doesn't get duped when used to + # key the @attributes in read_attribute. + def define_method_attribute(name) + safe_name = name.unpack("h*".freeze).first + temp_method = "__temp__#{safe_name}" - ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name + ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name - generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 - def #{temp_method} - name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} - _read_attribute(name) { |n| missing_attribute(n, caller) } - end - STR + generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 + def #{temp_method} + name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} + _read_attribute(name) { |n| missing_attribute(n, caller) } + end + STR - generated_attribute_methods.module_eval do - alias_method name, temp_method - undef_method temp_method + generated_attribute_methods.module_eval do + alias_method name, temp_method + undef_method temp_method + end end - end end # Returns the value of the attribute identified by <tt>attr_name</tt> after @@ -49,7 +49,7 @@ module ActiveRecord # 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 = self.class.primary_key if name == "id".freeze _read_attribute(name, &block) end @@ -69,7 +69,6 @@ module ActiveRecord alias :attribute :_read_attribute private :attribute - end end end diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 65978aea2a..945192fe04 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -26,7 +26,7 @@ module ActiveRecord # ==== Parameters # # * +attr_name+ - The field name that should be serialized. - # * +class_name_or_coder+ - Optional, a coder object, which responds to `.load` / `.dump` + # * +class_name_or_coder+ - Optional, a coder object, which responds to +.load+ and +.dump+ # or a class name that the object type should be equal to. # # ==== Example @@ -50,12 +50,12 @@ module ActiveRecord # to ensure special objects (e.g. Active Record models) are dumped correctly # using the #as_json hook. coder = if class_name_or_coder == ::JSON - Coders::JSON - elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) } - class_name_or_coder - else - Coders::YAMLColumn.new(class_name_or_coder) - end + Coders::JSON + elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) } + class_name_or_coder + else + Coders::YAMLColumn.new(class_name_or_coder) + end decorate_attribute_type(attr_name, :serialize) do |type| Type::Serialized.new(type, coder) 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 ebaaa54b2b..ce9985e2e1 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/string/strip' +require "active_support/core_ext/string/strip" module ActiveRecord module AttributeMethods @@ -26,31 +26,31 @@ module ActiveRecord private - def convert_time_to_time_zone(value) - return if value.nil? + def convert_time_to_time_zone(value) + return if value.nil? - if value.acts_like?(:time) - value.in_time_zone - elsif value.is_a?(::Float) - value - else - map_avoiding_infinite_recursion(value) { |v| convert_time_to_time_zone(v) } + if value.acts_like?(:time) + value.in_time_zone + elsif value.is_a?(::Float) + value + else + map_avoiding_infinite_recursion(value) { |v| convert_time_to_time_zone(v) } + end end - end - def set_time_zone_without_conversion(value) - ::Time.zone.local_to_utc(value).in_time_zone - end + def set_time_zone_without_conversion(value) + ::Time.zone.local_to_utc(value).in_time_zone if value + end - def map_avoiding_infinite_recursion(value) - map(value) do |v| - if value.equal?(v) - nil - else - yield(v) + def map_avoiding_infinite_recursion(value) + map(value) do |v| + if value.equal?(v) + nil + else + yield(v) + end end end - end end extend ActiveSupport::Concern @@ -69,47 +69,47 @@ module ActiveRecord module ClassMethods private - def inherited(subclass) - # We need to apply this decorator here, rather than on module inclusion. The closure - # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the - # sub class being decorated. As such, changes to `time_zone_aware_attributes`, or - # `skip_time_zone_conversion_for_attributes` would not be picked up. - subclass.class_eval do - matcher = ->(name, type) { create_time_zone_conversion_attribute?(name, type) } - decorate_matching_attribute_types(matcher, :_time_zone_conversion) do |type| - TimeZoneConverter.new(type) + def inherited(subclass) + super + # We need to apply this decorator here, rather than on module inclusion. The closure + # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the + # sub class being decorated. As such, changes to `time_zone_aware_attributes`, or + # `skip_time_zone_conversion_for_attributes` would not be picked up. + subclass.class_eval do + matcher = ->(name, type) { create_time_zone_conversion_attribute?(name, type) } + decorate_matching_attribute_types(matcher, :_time_zone_conversion) do |type| + TimeZoneConverter.new(type) + end end end - super - end - def create_time_zone_conversion_attribute?(name, cast_type) - enabled_for_column = time_zone_aware_attributes && - !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) - result = enabled_for_column && - time_zone_aware_types.include?(cast_type.type) + 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`. + 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: + To keep the old behavior, you must add the following to your initializer: - config.active_record.time_zone_aware_types = [:datetime] + config.active_record.time_zone_aware_types = [:datetime] - To silence this deprecation warning, add the following: + To silence this deprecation warning, add the following: - config.active_record.time_zone_aware_types << :time - MESSAGE - end + config.active_record.time_zone_aware_types = [:datetime, :time] + MESSAGE + end - result - end + result + end end end end diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index 5599b590ca..f65c297e01 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -10,23 +10,23 @@ module ActiveRecord module ClassMethods protected - def define_method_attribute=(name) - safe_name = name.unpack('h*'.freeze).first - ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name - - generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 - def __temp__#{safe_name}=(value) - name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} - write_attribute(name, value) - end - alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= - undef_method :__temp__#{safe_name}= - STR - end + def define_method_attribute=(name) + safe_name = name.unpack("h*".freeze).first + ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name + + generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 + def __temp__#{safe_name}=(value) + name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} + write_attribute(name, value) + end + alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= + undef_method :__temp__#{safe_name}= + STR + end end # Updates the attribute identified by <tt>attr_name</tt> with the - # specified +value+. Empty strings for fixnum and float columns are + # 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) @@ -37,23 +37,23 @@ module ActiveRecord end private - # Handle *= for method_missing. - def attribute=(attribute_name, value) - write_attribute(attribute_name, value) - end + # Handle *= for method_missing. + def attribute=(attribute_name, value) + write_attribute(attribute_name, value) + end - def write_attribute_with_type_cast(attr_name, value, should_type_cast) - attr_name = attr_name.to_s - attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key + def write_attribute_with_type_cast(attr_name, value, should_type_cast) + attr_name = attr_name.to_s + attr_name = self.class.primary_key if attr_name == "id" && self.class.primary_key - if should_type_cast - @attributes.write_from_user(attr_name, value) - else - @attributes.write_cast_value(attr_name, value) - end + if should_type_cast + @attributes.write_from_user(attr_name, value) + else + @attributes.write_cast_value(attr_name, value) + end - value - end + value + end end end end diff --git a/activerecord/lib/active_record/attribute_mutation_tracker.rb b/activerecord/lib/active_record/attribute_mutation_tracker.rb index 0133b4d0be..c257aef52f 100644 --- a/activerecord/lib/active_record/attribute_mutation_tracker.rb +++ b/activerecord/lib/active_record/attribute_mutation_tracker.rb @@ -36,13 +36,13 @@ module ActiveRecord protected - attr_reader :attributes + attr_reader :attributes private - def attr_names - attributes.keys - end + def attr_names + attributes.keys + end end class NullMutationTracker # :nodoc: diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb index be581ac2a9..5bde1f107c 100644 --- a/activerecord/lib/active_record/attribute_set.rb +++ b/activerecord/lib/active_record/attribute_set.rb @@ -1,7 +1,10 @@ -require 'active_record/attribute_set/builder' +require "active_record/attribute_set/builder" +require "active_record/attribute_set/yaml_encoder" module ActiveRecord class AttributeSet # :nodoc: + delegate :each_value, :fetch, to: :attributes + def initialize(attributes) @attributes = attributes end @@ -97,12 +100,12 @@ module ActiveRecord protected - attr_reader :attributes + attr_reader :attributes private - def initialized_attributes - attributes.select { |_, attr| attr.initialized? } - end + def initialized_attributes + attributes.select { |_, attr| attr.initialized? } + end end end diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb index 3bd7c7997b..661f996e1a 100644 --- a/activerecord/lib/active_record/attribute_set/builder.rb +++ b/activerecord/lib/active_record/attribute_set/builder.rb @@ -1,13 +1,14 @@ -require 'active_record/attribute' +require "active_record/attribute" module ActiveRecord class AttributeSet # :nodoc: class Builder # :nodoc: - attr_reader :types, :always_initialized + attr_reader :types, :always_initialized, :default - def initialize(types, always_initialized = nil) + def initialize(types, always_initialized = nil, &default) @types = types @always_initialized = always_initialized + @default = default end def build_from_database(values = {}, additional_types = {}) @@ -15,21 +16,22 @@ module ActiveRecord values[always_initialized] = nil end - attributes = LazyAttributeHash.new(types, values, additional_types) + attributes = LazyAttributeHash.new(types, values, additional_types, &default) AttributeSet.new(attributes) end end end class LazyAttributeHash # :nodoc: - delegate :transform_values, :each_key, to: :materialize + delegate :transform_values, :each_key, :each_value, :fetch, to: :materialize - def initialize(types, values, additional_types) + def initialize(types, values, additional_types, &default) @types = types @values = values @additional_types = additional_types @materialized = false @delegate_hash = {} + @default = default || proc {} end def key?(key) @@ -76,33 +78,45 @@ module ActiveRecord end end + def marshal_dump + materialize + end + + def marshal_load(delegate_hash) + @delegate_hash = delegate_hash + @types = {} + @values = {} + @additional_types = {} + @materialized = true + end + protected - attr_reader :types, :values, :additional_types, :delegate_hash + attr_reader :types, :values, :additional_types, :delegate_hash, :default - def materialize - unless @materialized - values.each_key { |key| self[key] } - types.each_key { |key| self[key] } - unless frozen? - @materialized = true + def materialize + unless @materialized + values.each_key { |key| self[key] } + types.each_key { |key| self[key] } + unless frozen? + @materialized = true + end end + delegate_hash end - delegate_hash - end private - def assign_default_value(name) - type = additional_types.fetch(name, types[name]) - value_present = true - value = values.fetch(name) { value_present = false } + def assign_default_value(name) + type = additional_types.fetch(name, types[name]) + value_present = true + value = values.fetch(name) { value_present = false } - if value_present - delegate_hash[name] = Attribute.from_database(name, value, type) - elsif types.key?(name) - delegate_hash[name] = Attribute.uninitialized(name, type) + if value_present + delegate_hash[name] = Attribute.from_database(name, value, type) + elsif types.key?(name) + delegate_hash[name] = default.call(name) || Attribute.uninitialized(name, type) + end end - end end end diff --git a/activerecord/lib/active_record/attribute_set/yaml_encoder.rb b/activerecord/lib/active_record/attribute_set/yaml_encoder.rb new file mode 100644 index 0000000000..c86cfc4263 --- /dev/null +++ b/activerecord/lib/active_record/attribute_set/yaml_encoder.rb @@ -0,0 +1,39 @@ +module ActiveRecord + class AttributeSet + # Attempts to do more intelligent YAML dumping of an + # ActiveRecord::AttributeSet to reduce the size of the resulting string + class YAMLEncoder # :nodoc: + def initialize(default_types) + @default_types = default_types + end + + def encode(attribute_set, coder) + coder["concise_attributes"] = attribute_set.each_value.map do |attr| + if attr.type.equal?(default_types[attr.name]) + attr.with_type(nil) + else + attr + end + end + end + + def decode(coder) + if coder["attributes"] + coder["attributes"] + else + attributes_hash = Hash[coder["concise_attributes"].map do |attr| + if attr.type.nil? + attr = attr.with_type(default_types[attr.name]) + end + [attr.name, attr] + end] + AttributeSet.new(attributes_hash) + end + end + + protected + + attr_reader :default_types + end + end +end diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb index 5d0405c3be..dcbfca1c04 100644 --- a/activerecord/lib/active_record/attributes.rb +++ b/activerecord/lib/active_record/attributes.rb @@ -1,4 +1,4 @@ -require 'active_record/attribute/user_provided_default' +require "active_record/attribute/user_provided_default" module ActiveRecord # See ActiveRecord::Attributes::ClassMethods for documentation @@ -34,10 +34,10 @@ module ActiveRecord # is not passed, the previous default value (if any) will be used. # Otherwise, the default will be +nil+. # - # +array+ (PG only) specifies that the type should be an array (see the + # +array+ (PostgreSQL only) specifies that the type should be an array (see the # examples below). # - # +range+ (PG only) specifies that the type should be a range (see the + # +range+ (PostgreSQL only) specifies that the type should be a range (see the # examples below). # # ==== Examples @@ -67,12 +67,14 @@ module ActiveRecord # # A default can also be provided. # + # # db/schema.rb # create_table :store_listings, force: true do |t| # t.string :my_string, default: "original default" # end # # StoreListing.new.my_string # => "original default" # + # # app/models/store_listing.rb # class StoreListing < ActiveRecord::Base # attribute :my_string, :string, default: "new default" # end @@ -89,6 +91,7 @@ module ActiveRecord # # \Attributes do not need to be backed by a database column. # + # # app/models/my_model.rb # class MyModel < ActiveRecord::Base # attribute :my_string, :string # attribute :my_int_array, :integer, array: true @@ -113,13 +116,13 @@ module ActiveRecord # Users may also define their own custom types, as long as they respond # to the methods defined on the value type. The method +deserialize+ or # +cast+ will be called on your type object, with raw input from the - # database or from your controllers. See ActiveRecord::Type::Value for the + # database or from your controllers. See ActiveModel::Type::Value for the # expected API. It is recommended that your type objects inherit from an # existing type, or from ActiveRecord::Type::Value # # class MoneyType < ActiveRecord::Type::Integer # def cast(value) - # if !value.kind_of(Numeric) && value.include?('$') + # if !value.kind_of?(Numeric) && value.include?('$') # price_in_dollars = value.gsub(/\$/, '').to_f # super(price_in_dollars * 100) # else @@ -131,7 +134,7 @@ module ActiveRecord # # config/initializers/types.rb # ActiveRecord::Type.register(:money, MoneyType) # - # # /app/models/store_listing.rb + # # app/models/store_listing.rb # class StoreListing < ActiveRecord::Base # attribute :price_in_cents, :money # end @@ -140,7 +143,7 @@ module ActiveRecord # store_listing.price_in_cents # => 1000 # # For more details on creating custom types, see the documentation for - # ActiveRecord::Type::Value. For more details on registering your types + # ActiveModel::Type::Value. For more details on registering your types # to be referenced by a symbol, see ActiveRecord::Type.register. You can # also pass a type object directly, in place of a symbol. # @@ -154,7 +157,7 @@ module ActiveRecord # end # # class MoneyType < Type::Value - # def initialize(currency_converter) + # def initialize(currency_converter:) # @currency_converter = currency_converter # end # @@ -167,11 +170,13 @@ module ActiveRecord # end # end # + # # config/initializers/types.rb # ActiveRecord::Type.register(:money, MoneyType) # + # # app/models/product.rb # class Product < ActiveRecord::Base # currency_converter = ConversionRatesFromTheInternet.new - # attribute :price_in_bitcoins, :money, currency_converter + # attribute :price_in_bitcoins, :money, currency_converter: currency_converter # end # # Product.where(price_in_bitcoins: Money.new(5, "USD")) @@ -185,7 +190,7 @@ module ActiveRecord # The type of an attribute is given the opportunity to change how dirty # tracking is performed. The methods +changed?+ and +changed_in_place?+ # will be called from ActiveModel::Dirty. See the documentation for those - # methods in ActiveRecord::Type::Value for more details. + # methods in ActiveModel::Type::Value for more details. def attribute(name, cast_type, **options) name = name.to_s reload_schema_from_cache @@ -237,24 +242,24 @@ module ActiveRecord private - NO_DEFAULT_PROVIDED = Object.new # :nodoc: - private_constant :NO_DEFAULT_PROVIDED + NO_DEFAULT_PROVIDED = Object.new # :nodoc: + private_constant :NO_DEFAULT_PROVIDED - def define_default_attribute(name, value, type, from_user:) - if value == NO_DEFAULT_PROVIDED - default_attribute = _default_attributes[name].with_type(type) - elsif from_user - default_attribute = Attribute::UserProvidedDefault.new( - name, - value, - type, - _default_attributes[name], - ) - else - default_attribute = Attribute.from_database(name, value, type) + def define_default_attribute(name, value, type, from_user:) + if value == NO_DEFAULT_PROVIDED + default_attribute = _default_attributes[name].with_type(type) + elsif from_user + default_attribute = Attribute::UserProvidedDefault.new( + name, + value, + type, + _default_attributes.fetch(name.to_s) { nil }, + ) + else + default_attribute = Attribute.from_database(name, value, type) + end + _default_attributes[name] = default_attribute end - _default_attributes[name] = default_attribute - end end end end diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index bac5a38a5d..d3e0dee731 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -22,7 +22,7 @@ module ActiveRecord # # == Validation # - # Children records are validated unless <tt>:validate</tt> is +false+. + # Child records are validated unless <tt>:validate</tt> is +false+. # # == Callbacks # @@ -329,26 +329,20 @@ module ActiveRecord return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?) validation_context = self.validation_context unless [:create, :update].include?(self.validation_context) + unless valid = record.valid?(validation_context) if reflection.options[:autosave] indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors) record.errors.each do |attribute, message| - if indexed_attribute - attribute = "#{reflection.name}[#{index}].#{attribute}" - else - attribute = "#{reflection.name}.#{attribute}" - end + attribute = normalize_reflection_attribute(indexed_attribute, reflection, index, attribute) errors[attribute] << message errors[attribute].uniq! end record.errors.details.each_key do |attribute| - if indexed_attribute - reflection_attribute = "#{reflection.name}[#{index}].#{attribute}" - else - reflection_attribute = "#{reflection.name}.#{attribute}" - end + reflection_attribute = + normalize_reflection_attribute(indexed_attribute, reflection, index, attribute).to_sym record.errors.details[attribute].each do |error| errors.details[reflection_attribute] << error @@ -362,6 +356,14 @@ module ActiveRecord valid end + def normalize_reflection_attribute(indexed_attribute, reflection, index, attribute) + if indexed_attribute + "#{reflection.name}[#{index}].#{attribute}" + else + "#{reflection.name}.#{attribute}" + end + end + # Is used as a before_save callback to check while saving a collection # association whether or not the parent was a new record before saving. def before_save_collection_association @@ -400,7 +402,7 @@ module ActiveRecord association.insert_record(record) unless reflection.nested? end elsif autosave - saved = record.save(:validate => false) + saved = record.save(validate: false) end raise ActiveRecord::Rollback unless saved @@ -437,7 +439,7 @@ module ActiveRecord record[reflection.foreign_key] = key end - saved = record.save(:validate => !autosave) + saved = record.save(validate: !autosave) raise ActiveRecord::Rollback if !saved && autosave saved end @@ -457,7 +459,9 @@ module ActiveRecord # In addition, it will destroy the association if it was marked for destruction. def save_belongs_to_association(reflection) association = association_instance_get(reflection.name) - record = association && association.load_target + return unless association && association.loaded? && !association.stale_target? + + record = association.load_target if record && !record.destroyed? autosave = reflection.options[:autosave] @@ -465,7 +469,7 @@ module ActiveRecord self[reflection.foreign_key] = nil record.destroy elsif autosave != false - saved = record.save(:validate => !autosave) if record.new_record? || (autosave && record.changed_for_autosave?) + saved = record.save(validate: !autosave) if record.new_record? || (autosave && record.changed_for_autosave?) if association.updated? association_id = record.send(reflection.options[:primary_key] || :id) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 7ed2fe48be..1e7e939097 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1,25 +1,25 @@ -require 'yaml' -require 'active_support/benchmarkable' -require 'active_support/dependencies' -require 'active_support/descendants_tracker' -require 'active_support/time' -require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/core_ext/array/extract_options' -require 'active_support/core_ext/hash/deep_merge' -require 'active_support/core_ext/hash/slice' -require 'active_support/core_ext/hash/transform_values' -require 'active_support/core_ext/string/behavior' -require 'active_support/core_ext/kernel/singleton_class' -require 'active_support/core_ext/module/introspection' -require 'active_support/core_ext/object/duplicable' -require 'active_support/core_ext/class/subclasses' -require 'active_record/attribute_decorators' -require 'active_record/errors' -require 'active_record/log_subscriber' -require 'active_record/explain_subscriber' -require 'active_record/relation/delegation' -require 'active_record/attributes' -require 'active_record/type_caster' +require "yaml" +require "active_support/benchmarkable" +require "active_support/dependencies" +require "active_support/descendants_tracker" +require "active_support/time" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/hash/deep_merge" +require "active_support/core_ext/hash/slice" +require "active_support/core_ext/hash/transform_values" +require "active_support/core_ext/string/behavior" +require "active_support/core_ext/kernel/singleton_class" +require "active_support/core_ext/module/introspection" +require "active_support/core_ext/object/duplicable" +require "active_support/core_ext/class/subclasses" +require "active_record/attribute_decorators" +require "active_record/errors" +require "active_record/log_subscriber" +require "active_record/explain_subscriber" +require "active_record/relation/delegation" +require "active_record/attributes" +require "active_record/type_caster" module ActiveRecord #:nodoc: # = Active Record @@ -169,7 +169,8 @@ module ActiveRecord #:nodoc: # ActiveRecord::RecordNotFound error if they do not return any records, # like <tt>Person.find_by_last_name!</tt>. # - # It's also possible to use multiple attributes in the same find by separating them with "_and_". + # It's also possible to use multiple attributes in the same <tt>find_by_</tt> by separating them with + # "_and_". # # Person.find_by(user_name: user_name, password: password) # Person.find_by_user_name_and_password(user_name, password) # with dynamic finder @@ -311,8 +312,8 @@ module ActiveRecord #:nodoc: include NestedAttributes include Aggregations include Transactions - include NoTouching include TouchLater + include NoTouching include Reflection include Serialization include Store diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 1f1b11eb68..c616733aa4 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -53,9 +53,9 @@ module ActiveRecord # end # # class Firm < ActiveRecord::Base - # # Destroys the associated clients and people when the firm is destroyed - # before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" } - # before_destroy { |record| Client.destroy_all "client_of = #{record.id}" } + # # Disables access to the system, for associated clients and people when the firm is destroyed + # before_destroy { |record| Person.where(firm_id: record.id).update_all(access: 'disabled') } + # before_destroy { |record| Client.where(client_of: record.id).update_all(access: 'disabled') } # end # # == Inheritable callback queues @@ -272,7 +272,7 @@ module ActiveRecord included do include ActiveModel::Validations::Callbacks - define_model_callbacks :initialize, :find, :touch, :only => :after + define_model_callbacks :initialize, :find, :touch, only: :after define_model_callbacks :save, :create, :update, :destroy end diff --git a/activerecord/lib/active_record/coders/json.rb b/activerecord/lib/active_record/coders/json.rb index 75d3bfe625..cb185a881e 100644 --- a/activerecord/lib/active_record/coders/json.rb +++ b/activerecord/lib/active_record/coders/json.rb @@ -6,7 +6,7 @@ module ActiveRecord end def self.load(json) - ActiveSupport::JSON.decode(json) unless json.nil? + ActiveSupport::JSON.decode(json) unless json.blank? end end end diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb index 2456b8ad8c..3a04a10fc9 100644 --- a/activerecord/lib/active_record/coders/yaml_column.rb +++ b/activerecord/lib/active_record/coders/yaml_column.rb @@ -1,9 +1,8 @@ -require 'yaml' +require "yaml" module ActiveRecord module Coders # :nodoc: class YAMLColumn # :nodoc: - attr_accessor :object_class def initialize(object_class = Object) @@ -20,7 +19,7 @@ module ActiveRecord def load(yaml) return object_class.new if object_class != Object && yaml.nil? - return yaml unless yaml.is_a?(String) && yaml =~ /^---/ + return yaml unless yaml.is_a?(String) && /^---/.match?(yaml) obj = YAML.load(yaml) assert_valid_value(obj) @@ -38,13 +37,13 @@ module ActiveRecord 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." + 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 end - end end end end diff --git a/activerecord/lib/active_record/collection_cache_key.rb b/activerecord/lib/active_record/collection_cache_key.rb index 5dcc98424a..43784b70e3 100644 --- a/activerecord/lib/active_record/collection_cache_key.rb +++ b/activerecord/lib/active_record/collection_cache_key.rb @@ -1,6 +1,5 @@ module ActiveRecord module CollectionCacheKey - def collection_cache_key(collection = all, timestamp_column = :updated_at) # :nodoc: query_signature = Digest::MD5.hexdigest(collection.to_sql) key = "#{collection.model_name.cache_key}/query-#{query_signature}" @@ -16,7 +15,7 @@ module ActiveRecord query = collection .unscope(:select) - .select("COUNT(*) AS size", "MAX(#{column}) AS timestamp") + .select("COUNT(*) AS #{connection.quote_column_name("size")}", "MAX(#{column}) AS timestamp") .unscope(:order) result = connection.select_one(query) 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 ccd2899489..2d62fd8d50 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -1,6 +1,6 @@ -require 'thread' -require 'concurrent/map' -require 'monitor' +require "thread" +require "concurrent/map" +require "monitor" module ActiveRecord # Raised when a connection could not be obtained within the connection @@ -74,7 +74,7 @@ module ActiveRecord #-- # Synchronization policy: # * all public methods can be called outside +synchronize+ - # * access to these i-vars needs to be in +synchronize+: + # * access to these instance variables needs to be in +synchronize+: # * @connections # * @now_connecting # * private methods that require being called in a +synchronize+ blocks @@ -150,61 +150,61 @@ module ActiveRecord private - def internal_poll(timeout) - no_wait_poll || (timeout && wait_poll(timeout)) - end + def internal_poll(timeout) + no_wait_poll || (timeout && wait_poll(timeout)) + end - def synchronize(&block) - @lock.synchronize(&block) - end + def synchronize(&block) + @lock.synchronize(&block) + end - # Test if the queue currently contains any elements. - def any? - !@queue.empty? - end + # Test if the queue currently contains any elements. + def any? + !@queue.empty? + end - # A thread can remove an element from the queue without - # waiting if and only if the number of currently available - # connections is strictly greater than the number of waiting - # threads. - def can_remove_no_wait? - @queue.size > @num_waiting - end + # A thread can remove an element from the queue without + # waiting if and only if the number of currently available + # connections is strictly greater than the number of waiting + # threads. + def can_remove_no_wait? + @queue.size > @num_waiting + end - # Removes and returns the head of the queue if possible, or nil. - def remove - @queue.shift - end + # Removes and returns the head of the queue if possible, or nil. + def remove + @queue.shift + end - # Remove and return the head the queue if the number of - # available elements is strictly greater than the number of - # threads currently waiting. Otherwise, return nil. - def no_wait_poll - remove if can_remove_no_wait? - end + # Remove and return the head the queue if the number of + # available elements is strictly greater than the number of + # threads currently waiting. Otherwise, return nil. + def no_wait_poll + remove if can_remove_no_wait? + end - # Waits on the queue up to +timeout+ seconds, then removes and - # returns the head of the queue. - def wait_poll(timeout) - @num_waiting += 1 + # Waits on the queue up to +timeout+ seconds, then removes and + # returns the head of the queue. + def wait_poll(timeout) + @num_waiting += 1 - t0 = Time.now - elapsed = 0 - loop do - @cond.wait(timeout - elapsed) + t0 = Time.now + elapsed = 0 + loop do + @cond.wait(timeout - elapsed) - return remove if any? + return remove if any? - elapsed = Time.now - t0 - if elapsed >= timeout - msg = 'could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use' % - [timeout, elapsed] - raise ConnectionTimeoutError, msg + elapsed = Time.now - t0 + if elapsed >= timeout + msg = "could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use" % + [timeout, elapsed] + raise ConnectionTimeoutError, msg + end end + ensure + @num_waiting -= 1 end - ensure - @num_waiting -= 1 - end end # Adds the ability to turn a basic fair FIFO queue into one @@ -274,11 +274,11 @@ module ActiveRecord include BiasableQueue private - def internal_poll(timeout) - conn = super - conn.lease if conn - conn - end + def internal_poll(timeout) + conn = super + conn.lease if conn + conn + end end # Every +frequency+ seconds, the reaper will call +reap+ on +pool+. @@ -298,7 +298,7 @@ module ActiveRecord def run return unless frequency Thread.new(frequency, pool) { |t, p| - while true + loop do sleep t p.reap end @@ -329,17 +329,17 @@ module ActiveRecord # default max pool size to 5 @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5 - # The cache of threads mapped to reserved connections, the sole purpose - # of the cache is to speed-up +connection+ method, it is not the authoritative - # registry of which thread owns which connection, that is tracked by - # +connection.owner+ attr on each +connection+ instance. + # This variable tracks the cache of threads mapped to reserved connections, with the + # sole purpose of speeding up the +connection+ method. It is not the authoritative + # registry of which thread owns which connection. Connection ownership is tracked by + # the +connection.owner+ attr on each +connection+ instance. # The invariant works like this: if there is mapping of <tt>thread => conn</tt>, - # then that +thread+ does indeed own that +conn+, however an absence of a such - # mapping does not mean that the +thread+ doesn't own the said connection, in + # then that +thread+ does indeed own that +conn+. However, an absence of a such + # mapping does not mean that the +thread+ doesn't own the said connection. In # that case +conn.owner+ attr should be consulted. # Access and modification of +@thread_cached_conns+ does not require # synchronization. - @thread_cached_conns = Concurrent::Map.new(:initial_capacity => @size) + @thread_cached_conns = Concurrent::Map.new(initial_capacity: @size) @connections = [] @automatic_reconnect = true @@ -364,10 +364,10 @@ module ActiveRecord @thread_cached_conns[connection_cache_key(Thread.current)] ||= checkout end - # Is there an open connection that is being used for the current thread? + # Returns true if there is an open connection being used for the current thread. # # This method only works for connections that have been obtained through - # #connection or #with_connection methods, connections obtained through + # #connection or #with_connection methods. Connections obtained through # #checkout will not be detected by #active_connection? def active_connection? @thread_cached_conns[connection_cache_key(Thread.current)] @@ -415,7 +415,10 @@ module ActiveRecord with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do synchronize do @connections.each do |conn| - checkin conn + if conn.in_use? + conn.steal! + checkin conn + end conn.disconnect! end @connections = [] @@ -426,9 +429,9 @@ module ActiveRecord # Disconnects all connections in the pool, and clears the pool. # - # The pool first tries to gain ownership of all connections, if unable to + # The pool first tries to gain ownership of all connections. If unable to # do so within a timeout interval (default duration is - # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), the pool is forcefully + # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), then the pool is forcefully # disconnected without any regard for other connection owning threads. def disconnect! disconnect(false) @@ -447,7 +450,10 @@ module ActiveRecord with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do synchronize do @connections.each do |conn| - checkin conn + if conn.in_use? + conn.steal! + checkin conn + end conn.disconnect! if conn.requires_reloading? end @connections.delete_if(&:requires_reloading?) @@ -474,9 +480,9 @@ module ActiveRecord # Clears the cache which maps classes and re-connects connections that # require reloading. # - # The pool first tries to gain ownership of all connections, if unable to + # The pool first tries to gain ownership of all connections. If unable to # do so within a timeout interval (default duration is - # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), the pool forcefully + # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), then the pool forcefully # clears the cache and reloads connections without any regard for other # connection owning threads. def clear_reloadable_connections! @@ -530,20 +536,20 @@ module ActiveRecord @available.delete conn # @available.any_waiting? => true means that prior to removing this - # conn, the pool was at its max size (@connections.size == @size) - # this would mean that any threads stuck waiting in the queue wouldn't + # conn, the pool was at its max size (@connections.size == @size). + # This would mean that any threads stuck waiting in the queue wouldn't # know they could checkout_new_connection, so let's do it for them. # Because condition-wait loop is encapsulated in the Queue class # (that in turn is oblivious to ConnectionPool implementation), threads - # that are "stuck" there are helpless, they have no way of creating + # that are "stuck" there are helpless. They have no way of creating # new connections and are completely reliant on us feeding available # connections into the Queue. needs_new_connection = @available.any_waiting? end # This is intentionally done outside of the synchronized section as we - # would like not to hold the main mutex while checking out new connections, - # thus there is some chance that needs_new_connection information is now + # would like not to hold the main mutex while checking out new connections. + # Thus there is some chance that needs_new_connection information is now # stale, we can live with that (bulk_make_new_connections will make # sure not to exceed the pool's @size limit). bulk_make_new_connections(1) if needs_new_connection @@ -556,17 +562,17 @@ module ActiveRecord stale_connections = synchronize do @connections.select do |conn| conn.in_use? && !conn.owner.alive? + end.each do |conn| + conn.steal! end end stale_connections.each do |conn| - synchronize do - if conn.active? - conn.reset! - checkin conn - else - remove conn - end + if conn.active? + conn.reset! + checkin conn + else + remove conn end end end @@ -576,210 +582,209 @@ module ActiveRecord end private - #-- - # this is unfortunately not concurrent - def bulk_make_new_connections(num_new_conns_needed) - num_new_conns_needed.times do - # try_to_checkout_new_connection will not exceed pool's @size limit - if new_conn = try_to_checkout_new_connection - # make the new_conn available to the starving threads stuck @available Queue - checkin(new_conn) + #-- + # this is unfortunately not concurrent + def bulk_make_new_connections(num_new_conns_needed) + num_new_conns_needed.times do + # try_to_checkout_new_connection will not exceed pool's @size limit + if new_conn = try_to_checkout_new_connection + # make the new_conn available to the starving threads stuck @available Queue + checkin(new_conn) + end end end - end - #-- - # From the discussion on GitHub: - # https://github.com/rails/rails/pull/14938#commitcomment-6601951 - # This hook-in method allows for easier monkey-patching fixes needed by - # JRuby users that use Fibers. - def connection_cache_key(thread) - thread - end - - # Take control of all existing connections so a "group" action such as - # reload/disconnect can be performed safely. It is no longer enough to - # wrap it in +synchronize+ because some pool's actions are allowed - # to be performed outside of the main +synchronize+ block. - def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true) - with_new_connections_blocked do - attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout) - yield + #-- + # From the discussion on GitHub: + # https://github.com/rails/rails/pull/14938#commitcomment-6601951 + # This hook-in method allows for easier monkey-patching fixes needed by + # JRuby users that use Fibers. + def connection_cache_key(thread) + thread end - end - def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true) - collected_conns = synchronize do - # account for our own connections - @connections.select {|conn| conn.owner == Thread.current} + # Take control of all existing connections so a "group" action such as + # reload/disconnect can be performed safely. It is no longer enough to + # wrap it in +synchronize+ because some pool's actions are allowed + # to be performed outside of the main +synchronize+ block. + def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true) + with_new_connections_blocked do + attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout) + yield + end end - newly_checked_out = [] - timeout_time = Time.now + (@checkout_timeout * 2) + def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true) + collected_conns = synchronize do + # account for our own connections + @connections.select { |conn| conn.owner == Thread.current } + end - @available.with_a_bias_for(Thread.current) do - while true - synchronize do - return if collected_conns.size == @connections.size && @now_connecting == 0 - remaining_timeout = timeout_time - Time.now - remaining_timeout = 0 if remaining_timeout < 0 - conn = checkout_for_exclusive_access(remaining_timeout) - collected_conns << conn - newly_checked_out << conn + newly_checked_out = [] + timeout_time = Time.now + (@checkout_timeout * 2) + + @available.with_a_bias_for(Thread.current) do + loop do + synchronize do + return if collected_conns.size == @connections.size && @now_connecting == 0 + remaining_timeout = timeout_time - Time.now + remaining_timeout = 0 if remaining_timeout < 0 + conn = checkout_for_exclusive_access(remaining_timeout) + collected_conns << conn + newly_checked_out << conn + end end end - end - rescue ExclusiveConnectionTimeoutError - # <tt>raise_on_acquisition_timeout == false</tt> means we are directed to ignore any - # timeouts and are expected to just give up: we've obtained as many connections - # as possible, note that in a case like that we don't return any of the - # +newly_checked_out+ connections. - - if raise_on_acquisition_timeout + rescue ExclusiveConnectionTimeoutError + # <tt>raise_on_acquisition_timeout == false</tt> means we are directed to ignore any + # timeouts and are expected to just give up: we've obtained as many connections + # as possible, note that in a case like that we don't return any of the + # +newly_checked_out+ connections. + + if raise_on_acquisition_timeout + release_newly_checked_out = true + raise + end + rescue Exception # if something else went wrong + # this can't be a "naked" rescue, because we have should return conns + # even for non-StandardErrors release_newly_checked_out = true raise + ensure + if release_newly_checked_out && newly_checked_out + # releasing only those conns that were checked out in this method, conns + # checked outside this method (before it was called) are not for us to release + newly_checked_out.each { |conn| checkin(conn) } + end end - rescue Exception # if something else went wrong - # this can't be a "naked" rescue, because we have should return conns - # even for non-StandardErrors - release_newly_checked_out = true - raise - ensure - if release_newly_checked_out && newly_checked_out - # releasing only those conns that were checked out in this method, conns - # checked outside this method (before it was called) are not for us to release - newly_checked_out.each {|conn| checkin(conn)} - end - end - - #-- - # Must be called in a synchronize block. - def checkout_for_exclusive_access(checkout_timeout) - checkout(checkout_timeout) - rescue ConnectionTimeoutError - # this block can't be easily moved into attempt_to_checkout_all_existing_connections's - # rescue block, because doing so would put it outside of synchronize section, without - # being in a critical section thread_report might become inaccurate - msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds" - thread_report = [] - @connections.each do |conn| - unless conn.owner == Thread.current - thread_report << "#{conn} is owned by #{conn.owner}" + #-- + # Must be called in a synchronize block. + def checkout_for_exclusive_access(checkout_timeout) + checkout(checkout_timeout) + rescue ConnectionTimeoutError + # this block can't be easily moved into attempt_to_checkout_all_existing_connections's + # rescue block, because doing so would put it outside of synchronize section, without + # being in a critical section thread_report might become inaccurate + msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds" + + thread_report = [] + @connections.each do |conn| + unless conn.owner == Thread.current + thread_report << "#{conn} is owned by #{conn.owner}" + end end - end - msg << " (#{thread_report.join(', ')})" if thread_report.any? + msg << " (#{thread_report.join(', ')})" if thread_report.any? - raise ExclusiveConnectionTimeoutError, msg - end + raise ExclusiveConnectionTimeoutError, msg + end - def with_new_connections_blocked - previous_value = nil - synchronize do - previous_value, @new_cons_enabled = @new_cons_enabled, false + def with_new_connections_blocked + previous_value = nil + synchronize do + previous_value, @new_cons_enabled = @new_cons_enabled, false + end + yield + ensure + synchronize { @new_cons_enabled = previous_value } end - yield - ensure - synchronize { @new_cons_enabled = previous_value } - end - # Acquire a connection by one of 1) immediately removing one - # from the queue of available connections, 2) creating a new - # connection if the pool is not at capacity, 3) waiting on the - # queue for a connection to become available. - # - # Raises: - # - ActiveRecord::ConnectionTimeoutError if a connection could not be acquired - # - #-- - # Implementation detail: the connection returned by +acquire_connection+ - # will already be "+connection.lease+ -ed" to the current thread. - def acquire_connection(checkout_timeout) - # NOTE: we rely on +@available.poll+ and +try_to_checkout_new_connection+ to - # +conn.lease+ the returned connection (and to do this in a +synchronized+ - # section), this is not the cleanest implementation, as ideally we would - # <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to +@available.poll+ - # and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections - # of the said methods and avoid an additional +synchronize+ overhead. - if conn = @available.poll || try_to_checkout_new_connection - conn - else - reap - @available.poll(checkout_timeout) + # Acquire a connection by one of 1) immediately removing one + # from the queue of available connections, 2) creating a new + # connection if the pool is not at capacity, 3) waiting on the + # queue for a connection to become available. + # + # Raises: + # - ActiveRecord::ConnectionTimeoutError if a connection could not be acquired + # + #-- + # Implementation detail: the connection returned by +acquire_connection+ + # will already be "+connection.lease+ -ed" to the current thread. + def acquire_connection(checkout_timeout) + # NOTE: we rely on +@available.poll+ and +try_to_checkout_new_connection+ to + # +conn.lease+ the returned connection (and to do this in a +synchronized+ + # section). This is not the cleanest implementation, as ideally we would + # <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to +@available.poll+ + # and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections + # of the said methods and avoid an additional +synchronize+ overhead. + if conn = @available.poll || try_to_checkout_new_connection + conn + else + reap + @available.poll(checkout_timeout) + end end - end - #-- - # if owner_thread param is omitted, this must be called in synchronize block - def remove_connection_from_thread_cache(conn, owner_thread = conn.owner) - @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn) - end - alias_method :release, :remove_connection_from_thread_cache + #-- + # if owner_thread param is omitted, this must be called in synchronize block + def remove_connection_from_thread_cache(conn, owner_thread = conn.owner) + @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn) + end + alias_method :release, :remove_connection_from_thread_cache - def new_connection - Base.send(spec.adapter_method, spec.config).tap do |conn| - conn.schema_cache = schema_cache.dup if schema_cache + def new_connection + Base.send(spec.adapter_method, spec.config).tap do |conn| + conn.schema_cache = schema_cache.dup if schema_cache + end end - end - # If the pool is not at a +@size+ limit, establish new connection. Connecting - # to the DB is done outside main synchronized section. - #-- - # Implementation constraint: a newly established connection returned by this - # method must be in the +.leased+ state. - def try_to_checkout_new_connection - # first in synchronized section check if establishing new conns is allowed - # and increment @now_connecting, to prevent overstepping this pool's @size - # constraint - do_checkout = synchronize do - if @new_cons_enabled && (@connections.size + @now_connecting) < @size - @now_connecting += 1 - end - end - if do_checkout - begin - # if successfully incremented @now_connecting establish new connection - # outside of synchronized section - conn = checkout_new_connection - ensure - synchronize do - if conn - adopt_connection(conn) - # returned conn needs to be already leased - conn.lease + # If the pool is not at a +@size+ limit, establish new connection. Connecting + # to the DB is done outside main synchronized section. + #-- + # Implementation constraint: a newly established connection returned by this + # method must be in the +.leased+ state. + def try_to_checkout_new_connection + # first in synchronized section check if establishing new conns is allowed + # and increment @now_connecting, to prevent overstepping this pool's @size + # constraint + do_checkout = synchronize do + if @new_cons_enabled && (@connections.size + @now_connecting) < @size + @now_connecting += 1 + end + end + if do_checkout + begin + # if successfully incremented @now_connecting establish new connection + # outside of synchronized section + conn = checkout_new_connection + ensure + synchronize do + if conn + adopt_connection(conn) + # returned conn needs to be already leased + conn.lease + end + @now_connecting -= 1 end - @now_connecting -= 1 end end end - end - def adopt_connection(conn) - conn.pool = self - @connections << conn - end + def adopt_connection(conn) + conn.pool = self + @connections << conn + end - def checkout_new_connection - raise ConnectionNotEstablished unless @automatic_reconnect - new_connection - end + def checkout_new_connection + raise ConnectionNotEstablished unless @automatic_reconnect + new_connection + end - def checkout_and_verify(c) - c._run_checkout_callbacks do - c.verify! + def checkout_and_verify(c) + c._run_checkout_callbacks do + c.verify! + end + c + rescue + remove c + c.disconnect! + raise end - c - rescue - remove c - c.disconnect! - raise - end end # ConnectionHandler is a collection of ConnectionPool objects. It is used - # for keeping separate connection pools for Active Record models that connect - # to different databases. + # for keeping separate connection pools that connect to different databases. # # For example, suppose that you have 5 models, with the following hierarchy: # @@ -821,16 +826,15 @@ module ActiveRecord # ConnectionHandler accessible via ActiveRecord::Base.connection_handler. # All Active Record models use this handler to determine the connection pool that they # should use. + # + # The ConnectionHandler class is not coupled with the Active models, as it has no knowlodge + # about the model. The model needs to pass a specification name to the handler, + # in order to lookup the correct connection pool. class ConnectionHandler def initialize - # These caches are keyed by klass.name, NOT klass. Keying them by klass - # alone would lead to memory leaks in development mode as all previous - # instances of the class would stay in memory. - @owner_to_pool = Concurrent::Map.new(:initial_capacity => 2) do |h,k| - h[k] = Concurrent::Map.new(:initial_capacity => 2) - end - @class_to_pool = Concurrent::Map.new(:initial_capacity => 2) do |h,k| - h[k] = Concurrent::Map.new + # These caches are keyed by spec.name (ConnectionSpecification#name). + @owner_to_pool = Concurrent::Map.new(initial_capacity: 2) do |h,k| + h[k] = Concurrent::Map.new(initial_capacity: 2) end end @@ -839,10 +843,26 @@ module ActiveRecord end alias :connection_pools :connection_pool_list - def establish_connection(owner, spec) - @class_to_pool.clear - raise RuntimeError, "Anonymous class is not allowed." unless owner.name - owner_to_pool[owner.name] = ConnectionAdapters::ConnectionPool.new(spec) + def establish_connection(config) + resolver = ConnectionSpecification::Resolver.new(Base.configurations) + spec = resolver.spec(config) + + remove_connection(spec.name) + + message_bus = ActiveSupport::Notifications.instrumenter + payload = { + connection_id: object_id + } + if spec + payload[:spec_name] = spec.name + payload[:config] = spec.config + end + + message_bus.instrument("!connection.active_record", payload) do + owner_to_pool[spec.name] = ConnectionAdapters::ConnectionPool.new(spec) + end + + owner_to_pool[spec.name] end # Returns true if there are any active connections among the connection @@ -873,102 +893,63 @@ module ActiveRecord # active or defined connection: if it is the latter, it will be # opened and set as the active connection for the class it was defined # for (not necessarily the current class). - def retrieve_connection(klass) #:nodoc: - pool = retrieve_connection_pool(klass) - raise ConnectionNotEstablished, "No connection pool for #{klass}" unless pool + def retrieve_connection(spec_name) #:nodoc: + pool = retrieve_connection_pool(spec_name) + raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found." unless pool conn = pool.connection - raise ConnectionNotEstablished, "No connection for #{klass} in connection pool" unless conn + raise ConnectionNotEstablished, "No connection for '#{spec_name}' in connection pool" unless conn conn end # Returns true if a connection that's accessible to this class has # already been opened. - def connected?(klass) - conn = retrieve_connection_pool(klass) + def connected?(spec_name) + conn = retrieve_connection_pool(spec_name) conn && conn.connected? end # Remove the connection for this class. This will close the active # connection and the defined connection (if they exist). The result - # can be used as an argument for establish_connection, for easily + # can be used as an argument for #establish_connection, for easily # re-establishing the connection. - def remove_connection(owner) - if pool = owner_to_pool.delete(owner.name) - @class_to_pool.clear + def remove_connection(spec_name) + if pool = owner_to_pool.delete(spec_name) pool.automatic_reconnect = false pool.disconnect! pool.spec.config end end - # Retrieving the connection pool happens a lot so we cache it in @class_to_pool. + # Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool. # This makes retrieving the connection pool O(1) once the process is warm. # When a connection is established or removed, we invalidate the cache. - # - # Ideally we would use #fetch here, as class_to_pool[klass] may sometimes be nil. - # However, benchmarking (https://gist.github.com/jonleighton/3552829) showed that - # #fetch is significantly slower than #[]. So in the nil case, no caching will - # take place, but that's ok since the nil case is not the common one that we wish - # to optimise for. - def retrieve_connection_pool(klass) - class_to_pool[klass.name] ||= begin - until pool = pool_for(klass) - klass = klass.superclass - break unless klass <= Base - end - - class_to_pool[klass.name] = pool - end - end - - private - - def owner_to_pool - @owner_to_pool[Process.pid] - end - - def class_to_pool - @class_to_pool[Process.pid] - end - - def pool_for(owner) - owner_to_pool.fetch(owner.name) { - if ancestor_pool = pool_from_any_process_for(owner) + def retrieve_connection_pool(spec_name) + owner_to_pool.fetch(spec_name) do + # Check if a connection was previously established in an ancestor process, + # which may have been forked. + if ancestor_pool = pool_from_any_process_for(spec_name) # A connection was established in an ancestor process that must have # subsequently forked. We can't reuse the connection, but we can copy # the specification and establish a new connection with it. - establish_connection(owner, ancestor_pool.spec).tap do |pool| + establish_connection(ancestor_pool.spec.to_hash).tap do |pool| pool.schema_cache = ancestor_pool.schema_cache if ancestor_pool.schema_cache end else - owner_to_pool[owner.name] = nil + owner_to_pool[spec_name] = nil end - } - end - - def pool_from_any_process_for(owner) - owner_to_pool = @owner_to_pool.values.find { |v| v[owner.name] } - owner_to_pool && owner_to_pool[owner.name] + end end - end - class ConnectionManagement - def initialize(app) - @app = app - end + private - def call(env) - testing = env['rack.test'] + def owner_to_pool + @owner_to_pool[Process.pid] + end - status, headers, body = @app.call(env) - proxy = ::Rack::BodyProxy.new(body) do - ActiveRecord::Base.clear_active_connections! unless testing + 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[spec_name] end - [status, headers, proxy] - rescue Exception - ActiveRecord::Base.clear_active_connections! unless testing - raise - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb index 6711049588..95c72f1e20 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb @@ -1,7 +1,6 @@ module ActiveRecord module ConnectionAdapters # :nodoc: module DatabaseLimits - # Returns the maximum length of a table alias. def table_alias_length 255 @@ -61,7 +60,6 @@ module ActiveRecord def joins_per_query 256 end - end 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 bb5119d64e..aa2dfdd573 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -10,7 +10,7 @@ module ActiveRecord def to_sql(arel, binds = []) if arel.respond_to?(:ast) collected = visitor.accept(arel.ast, collector) - collected.compile(binds.dup, self) + collected.compile(binds, self) else arel end @@ -18,11 +18,12 @@ module ActiveRecord # This is used in the StatementCache object. It returns an object that # can be used to query the database repeatedly. - def cacheable_query(arel) # :nodoc: + def cacheable_query(klass, arel) # :nodoc: + collected = visitor.accept(arel.ast, collector) if prepared_statements - ActiveRecord::StatementCache.query visitor, arel.ast + klass.query(collected.value) else - ActiveRecord::StatementCache.partial_query visitor, arel.ast, collector + klass.partial_query(collected.value) end end @@ -30,7 +31,7 @@ module ActiveRecord def select_all(arel, name = nil, binds = [], preparable: nil) arel, binds = binds_from_relation arel, binds sql = to_sql(arel, binds) - if arel.is_a?(String) && preparable.nil? + if !prepared_statements || (arel.is_a?(String) && preparable.nil?) preparable = false else preparable = visitor.preparable @@ -66,8 +67,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 = []) + exec_query(sql, name, binds).rows end - undef_method :select_rows # Executes the SQL statement in the context of this connection and returns # the raw result from the connection adapter. @@ -75,26 +76,28 @@ module ActiveRecord # method may be manually memory managed. Consider using the exec_query # wrapper instead. def execute(sql, name = nil) + raise NotImplementedError end - undef_method :execute # Executes +sql+ statement in the context of this connection using # +binds+ as the bind substitutes. +name+ is logged along with # the executed +sql+ statement. - def exec_query(sql, name = 'SQL', binds = [], prepare: false) + def exec_query(sql, name = "SQL", binds = [], prepare: false) + raise NotImplementedError end # Executes insert +sql+ statement in the context of this connection using # +binds+ as the bind substitutes. +name+ is logged along with # the executed +sql+ statement. - def exec_insert(sql, name, binds, pk = nil, sequence_name = nil) + def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil) + sql, binds = sql_for_insert(sql, pk, nil, sequence_name, binds) exec_query(sql, name, binds) end # Executes delete +sql+ statement in the context of this connection using # +binds+ as the bind substitutes. +name+ is logged along with # the executed +sql+ statement. - def exec_delete(sql, name, binds) + def exec_delete(sql, name = nil, binds = []) exec_query(sql, name, binds) end @@ -106,7 +109,7 @@ module ActiveRecord # Executes update +sql+ statement in the context of this connection using # +binds+ as the bind substitutes. +name+ is logged along with # the executed +sql+ statement. - def exec_update(sql, name, binds) + def exec_update(sql, name = nil, binds = []) exec_query(sql, name, binds) end @@ -119,24 +122,26 @@ module ActiveRecord # If the next id was calculated in advance (as in Oracle), it should be # passed in as +id_value+. def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = []) - sql, binds, pk, sequence_name = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds) - value = exec_insert(sql, name, binds, pk, sequence_name) + value = exec_insert(to_sql(arel, binds), name, binds, pk, sequence_name) 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+ @@ -217,9 +222,7 @@ module ActiveRecord # * You are creating a nested (savepoint) transaction # # The mysql2 and postgresql adapters support setting the transaction - # isolation level. However, support is disabled for MySQL versions below 5, - # because they are affected by a bug[http://bugs.mysql.com/bug.php?id=39170] - # which means the isolation level gets persisted outside the transaction. + # isolation level. def transaction(requires_new: nil, isolation: nil, joinable: true) if !requires_new && current_transaction.joinable? if isolation @@ -242,7 +245,7 @@ module ActiveRecord end def reset_transaction #:nodoc: - @transaction_manager = TransactionManager.new(self) + @transaction_manager = ConnectionAdapters::TransactionManager.new(self) end # Register a record with the current transaction so that its after_commit and after_rollback callbacks @@ -289,9 +292,6 @@ module ActiveRecord exec_rollback_to_savepoint(name) end - def exec_rollback_to_savepoint(name = nil) #:nodoc: - end - def default_sequence_name(table, column) nil end @@ -316,7 +316,7 @@ module ActiveRecord end end key_list = fixture.keys.map { |name| quote_column_name(name) } - value_list = prepare_binds_for_database(binds).map do |value| + value_list = binds.map(&:value_for_database).map do |value| begin quote(value) rescue TypeError @@ -324,7 +324,7 @@ module ActiveRecord end end - execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", 'Fixture Insert' + execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", "Fixture Insert" end def empty_insert_statement_value @@ -343,8 +343,8 @@ module ActiveRecord 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(',') + elsif limit.to_s.include?(",") + Arel.sql limit.to_s.split(",").map { |i| Integer(i) }.join(",") else Integer(limit) end @@ -379,7 +379,7 @@ module ActiveRecord end def sql_for_insert(sql, pk, id_value, sequence_name, binds) - [sql, binds, pk, sequence_name] + [sql, binds] end def last_inserted_id(result) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index 33dbab41cb..2f8a89e88e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -65,7 +65,7 @@ module ActiveRecord if @query_cache_enabled && !locked?(arel) arel, binds = binds_from_relation arel, binds sql = to_sql(arel, binds) - cache_sql(sql, binds) { super(sql, name, binds, preparable: visitor.preparable) } + cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable) } else super end @@ -73,23 +73,29 @@ module ActiveRecord private - def cache_sql(sql, binds) - result = - if @query_cache[sql].key?(binds) - ActiveSupport::Notifications.instrument("sql.active_record", - :sql => sql, :binds => binds, :name => "CACHE", :connection_id => object_id) - @query_cache[sql][binds] - else - @query_cache[sql][binds] = yield - end - result.dup - end + def cache_sql(sql, name, binds) + result = + if @query_cache[sql].key?(binds) + ActiveSupport::Notifications.instrument( + "sql.active_record", + sql: sql, + binds: binds, + name: name, + connection_id: object_id, + cached: true, + ) + @query_cache[sql][binds] + else + @query_cache[sql][binds] = yield + end + result.dup + end - # If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such - # queries should not be cached. - def locked?(arel) - arel.respond_to?(:locked) && arel.locked - end + # If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such + # queries should not be cached. + def locked?(arel) + arel.respond_to?(:locked) && arel.locked + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 7e3760d34b..bbd52b8a91 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/big_decimal/conversions' +require "active_support/core_ext/big_decimal/conversions" module ActiveRecord module ConnectionAdapters # :nodoc: @@ -82,7 +82,7 @@ module ActiveRecord # Quotes the column name. Defaults to no quoting. def quote_column_name(column_name) - column_name + column_name.to_s end # Quotes the table name. Defaults to column name quoting. @@ -112,19 +112,19 @@ module ActiveRecord end def quoted_true - "'t'" + "'t'".freeze end def unquoted_true - 't' + "t".freeze end def quoted_false - "'f'" + "'f'".freeze end def unquoted_false - 'f' + "f".freeze end # Quote date/time values for use in SQL input. Includes microseconds @@ -146,47 +146,53 @@ module ActiveRecord end end - def prepare_binds_for_database(binds) # :nodoc: - binds.map(&:value_for_database) + def quoted_time(value) # :nodoc: + quoted_date(value).sub(/\A2000-01-01 /, "") end private - def types_which_need_no_typecasting - [nil, Numeric, String] - end - - def _quote(value) - case value - when String, ActiveSupport::Multibyte::Chars, Type::Binary::Data - "'#{quote_string(value.to_s)}'" - when true then quoted_true - when false then quoted_false - when nil then "NULL" - # 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 Date, Time then "'#{quoted_date(value)}'" - when Symbol then "'#{quote_string(value.to_s)}'" - when Class then "'#{value}'" - else raise TypeError, "can't quote #{value.class.name}" + def type_casted_binds(binds) + binds.map { |attr| type_cast(attr.value_for_database) } end - end - def _type_cast(value) - case value - when Symbol, ActiveSupport::Multibyte::Chars, Type::Binary::Data - value.to_s - when true then unquoted_true - when false then unquoted_false - # BigDecimals need to be put in a non-normalized form and quoted. - when BigDecimal then value.to_s('F') - when Date, Time then quoted_date(value) - when *types_which_need_no_typecasting - value - else raise TypeError + def types_which_need_no_typecasting + [nil, Numeric, String] + end + + def _quote(value) + case value + when String, ActiveSupport::Multibyte::Chars, Type::Binary::Data + "'#{quote_string(value.to_s)}'" + when true then quoted_true + when false then quoted_false + when nil then "NULL" + # 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::Time::Value then "'#{quoted_time(value)}'" + when Date, Time then "'#{quoted_date(value)}'" + when Symbol then "'#{quote_string(value.to_s)}'" + when Class then "'#{value}'" + else raise TypeError, "can't quote #{value.class.name}" + end + end + + def _type_cast(value) + case value + when Symbol, ActiveSupport::Multibyte::Chars, Type::Binary::Data + value.to_s + when true then unquoted_true + when false then unquoted_false + # BigDecimals need to be put in a non-normalized form and quoted. + when BigDecimal then value.to_s("F") + when Type::Time::Value then quoted_time(value) + when Date, Time then quoted_date(value) + when *types_which_need_no_typecasting + value + else raise TypeError + end end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb b/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb index c0662f8473..3a06f75292 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb @@ -1,8 +1,8 @@ module ActiveRecord module ConnectionAdapters - module Savepoints #:nodoc: - def supports_savepoints? - true + module Savepoints + def current_savepoint_name + current_transaction.savepoint_name end def create_savepoint(name = current_savepoint_name) 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 0ba4d94e3c..322684672f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/string/strip' +require "active_support/core_ext/string/strip" module ActiveRecord module ConnectionAdapters @@ -23,9 +23,9 @@ module ActiveRecord def visit_AlterTable(o) sql = "ALTER TABLE #{quote_table_name(o.name)} " - sql << o.adds.map { |col| accept col }.join(' ') - sql << o.foreign_key_adds.map { |fk| visit_AddForeignKey fk }.join(' ') - sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(' ') + sql << o.adds.map { |col| accept col }.join(" ") + sql << o.foreign_key_adds.map { |fk| visit_AddForeignKey fk }.join(" ") + sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(" ") end def visit_ColumnDefinition(o) @@ -53,8 +53,8 @@ module ActiveRecord statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) }) end - create_sql << "(#{statements.join(', ')}) " if statements.present? - create_sql << "#{o.options}" + create_sql << "(#{statements.join(', ')})" if statements.present? + add_table_options!(create_sql, table_options(o)) create_sql << " AS #{@conn.to_sql(o.as)}" if o.as create_sql end @@ -82,6 +82,19 @@ module ActiveRecord "DROP CONSTRAINT #{quote_column_name(name)}" end + def table_options(o) + table_options = {} + table_options[:comment] = o.comment + table_options[:options] = o.options + table_options + end + + def add_table_options!(create_sql, options) + if options_sql = options[:options] + create_sql << " #{options_sql}" + end + end + def column_options(o) column_options = {} column_options[:null] = o.null unless o.null.nil? @@ -92,6 +105,7 @@ module ActiveRecord 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 end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index cb10ca9929..83d1d7cd01 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -3,15 +3,14 @@ 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) #:nodoc: + class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment) #:nodoc: end # 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) #:nodoc: - + class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type, :comment) #:nodoc: def primary_key? primary_key || type.to_sym == :primary_key end @@ -51,18 +50,19 @@ module ActiveRecord options[:primary_key] != default_primary_key end - def defined_for?(options_or_to_table = {}) - if options_or_to_table.is_a?(Hash) - options_or_to_table.all? {|key, value| options[key].to_s == value.to_s } + def defined_for?(to_table_ord = nil, to_table: nil, **options) + if to_table_ord + self.to_table == to_table_ord.to_s else - to_table == options_or_to_table.to_s + (to_table.nil? || to_table.to_s == self.to_table) && + options.all? { |k, v| self.options[k].to_s == v.to_s } end end private - def default_primary_key - "id" - end + def default_primary_key + "id" + end end class ReferenceDefinition # :nodoc: @@ -102,51 +102,51 @@ module ActiveRecord protected - attr_reader :name, :polymorphic, :index, :foreign_key, :type, :options + attr_reader :name, :polymorphic, :index, :foreign_key, :type, :options private - def as_options(value, default = {}) - if value.is_a?(Hash) - value - else - default + def as_options(value, default = {}) + if value.is_a?(Hash) + value + else + default + end end - end - def polymorphic_options - as_options(polymorphic, options) - end + def polymorphic_options + as_options(polymorphic, options) + end - def index_options - as_options(index) - end + def index_options + as_options(index) + end - def foreign_key_options - as_options(foreign_key).merge(column: column_name) - end + def foreign_key_options + as_options(foreign_key).merge(column: column_name) + end - def columns - result = [[column_name, type, options]] - if polymorphic - result.unshift(["#{name}_type", :string, polymorphic_options]) + def columns + result = [[column_name, type, options]] + if polymorphic + result.unshift(["#{name}_type", :string, polymorphic_options]) + end + result end - result - end - def column_name - "#{name}_id" - end + def column_name + "#{name}_id" + end - def column_names - columns.map(&:first) - end + def column_names + columns.map(&:first) + end - def foreign_table_name - foreign_key_options.fetch(:to_table) do - Base.pluralize_table_names ? name.to_s.pluralize : name + def foreign_table_name + foreign_key_options.fetch(:to_table) do + Base.pluralize_table_names ? name.to_s.pluralize : name + end end - end end module ColumnMethods @@ -207,17 +207,18 @@ module ActiveRecord include ColumnMethods attr_accessor :indexes - attr_reader :name, :temporary, :options, :as, :foreign_keys + attr_reader :name, :temporary, :options, :as, :foreign_keys, :comment - def initialize(name, temporary, options, as = nil) + def initialize(name, temporary = false, options = nil, as = nil, comment: nil) @columns_hash = {} - @indexes = {} - @foreign_keys = {} + @indexes = [] + @foreign_keys = [] @primary_keys = nil @temporary = temporary @options = options @as = as @name = name + @comment = comment end def primary_keys(name = nil) # :nodoc: @@ -302,7 +303,7 @@ module ActiveRecord # end def column(name, type, options = {}) name = name.to_s - type = type.to_sym + type = type.to_sym if type options = options.dup if @columns_hash[name] && @columns_hash[name].primary_key? @@ -326,20 +327,21 @@ module ActiveRecord # # index(:account_id, name: 'index_projects_on_account_id') def index(column_name, options = {}) - indexes[column_name] = options + indexes << [column_name, options] end def foreign_key(table_name, options = {}) # :nodoc: - foreign_keys[table_name] = options + table_name_prefix = ActiveRecord::Base.table_name_prefix + table_name_suffix = ActiveRecord::Base.table_name_suffix + table_name = "#{table_name_prefix}#{table_name}#{table_name_suffix}" + foreign_keys.push([table_name, options]) end # Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and # <tt>:updated_at</tt> to the table. See {connection.add_timestamps}[rdoc-ref:SchemaStatements#add_timestamps] # # t.timestamps null: false - def timestamps(*args) - options = args.extract_options! - + def timestamps(**options) options[:null] = false if options[:null].nil? column(:created_at, :datetime, options) @@ -373,17 +375,18 @@ module ActiveRecord column.auto_increment = options[:auto_increment] column.primary_key = type == :primary_key || options[:primary_key] column.collation = options[:collation] + column.comment = options[:comment] column end private - def create_column_definition(name, type) - ColumnDefinition.new name, type - end + def create_column_definition(name, type) + ColumnDefinition.new name, type + end - def aliased_types(name, fallback) - 'timestamp' == name ? :datetime : fallback - end + def aliased_types(name, fallback) + "timestamp" == name ? :datetime : fallback + end end class AlterTable # :nodoc: @@ -472,7 +475,7 @@ module ActiveRecord # Checks to see if a column exists. # - # t.string(:name) unless t.column_exists?(:name, :string) + # t.string(:name) unless t.column_exists?(:name, :string) # # See {connection.column_exists?}[rdoc-ref:SchemaStatements#column_exists?] def column_exists?(column_name, type = nil, options = {}) @@ -493,9 +496,9 @@ module ActiveRecord # Checks to see if an index exists. # - # unless t.index_exists?(:branch_id) - # t.index(:branch_id) - # end + # unless t.index_exists?(:branch_id) + # t.index(:branch_id) + # end # # See {connection.index_exists?}[rdoc-ref:SchemaStatements#index_exists?] def index_exists?(column_name, options = {}) 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 b1b6044e72..06c89ca072 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -7,15 +7,13 @@ module ActiveRecord # Adapter level by over-writing this code inside the database specific adapters module ColumnDumper def column_spec(column) - spec = prepare_column_options(column) - (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k}: ")} - spec + [schema_type(column), prepare_column_options(column)] end def column_spec_for_primary_key(column) - return if column.type == :integer + return {} if default_primary_key?(column) spec = { id: schema_type(column).inspect } - spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type].include?(key) }) + spec.merge!(prepare_column_options(column).except!(:null)) end # This can be overridden on an Adapter level basis to support other @@ -23,9 +21,6 @@ module ActiveRecord # PostgreSQL::ColumnDumper) def prepare_column_options(column) spec = {} - spec[:name] = column.name.inspect - spec[:type] = schema_type(column).to_s - spec[:null] = 'false' unless column.null if limit = schema_limit(column) spec[:limit] = limit @@ -42,54 +37,66 @@ module ActiveRecord default = schema_default(column) if column.has_default? spec[:default] = default unless default.nil? + spec[:null] = "false" unless column.null + if collation = schema_collation(column) spec[:collation] = collation end + spec[:comment] = column.comment.inspect if column.comment.present? + spec end # Lists the valid migration options def migration_keys - [:name, :limit, :precision, :scale, :default, :null, :collation] + [:limit, :precision, :scale, :default, :null, :collation, :comment] end private - def schema_type(column) - column.type - end + def default_primary_key?(column) + schema_type(column) == :integer + end - def schema_limit(column) - limit = column.limit - limit.inspect if limit && limit != native_database_types[column.type][:limit] - end + def schema_type(column) + if column.bigint? + :bigint + else + column.type + end + end - def schema_precision(column) - column.precision.inspect if column.precision - end + def schema_limit(column) + limit = column.limit unless column.bigint? + limit.inspect if limit && limit != native_database_types[column.type][:limit] + end - def schema_scale(column) - column.scale.inspect if column.scale - end + def schema_precision(column) + column.precision.inspect if column.precision + end - def schema_default(column) - type = lookup_cast_type_from_column(column) - default = type.deserialize(column.default) - if default.nil? - schema_expression(column) - else - type.type_cast_for_schema(default) + def schema_scale(column) + column.scale.inspect if column.scale end - end - def schema_expression(column) - "-> { #{column.default_function.inspect} }" if column.default_function - end + def schema_default(column) + type = lookup_cast_type_from_column(column) + default = type.deserialize(column.default) + if default.nil? + schema_expression(column) + else + type.type_cast_for_schema(default) + end + end - def schema_collation(column) - column.collation.inspect if column.collation - end + def schema_expression(column) + "-> { #{column.default_function.inspect} }" if column.default_function + end + + def schema_collation(column) + column.collation.inspect if column.collation + end end end end 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 f0f855963a..151629b02a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -1,6 +1,6 @@ -require 'active_record/migration/join_table' -require 'active_support/core_ext/string/access' -require 'digest' +require "active_record/migration/join_table" +require "active_support/core_ext/string/access" +require "digest" module ActiveRecord module ConnectionAdapters # :nodoc: @@ -18,9 +18,14 @@ module ActiveRecord nil end + # Returns the table comment that's stored in database metadata. + def table_comment(table_name) + nil + end + # Truncates a table alias according to the limits of the current adapter. def table_alias_for(table_name) - table_name[0...table_alias_length].tr('.', '_') + table_name[0...table_alias_length].tr(".", "_") end # Returns the relation names useable to back Active Record models. @@ -115,7 +120,7 @@ module ActiveRecord checks = [] checks << lambda { |c| c.name == column_name } checks << lambda { |c| c.type == type } if type - (migration_keys - [:name]).each do |attr| + migration_keys.each do |attr| checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr) end @@ -124,14 +129,9 @@ module ActiveRecord # Returns just a table's primary key def primary_key(table_name) - pks = primary_keys(table_name) - warn <<-WARNING.strip_heredoc if pks.count > 1 - WARNING: Rails does not support composite primary key. - - #{table_name} has composite primary key. Composite primary key is ignored. - WARNING - - pks.first if pks.one? + pk = primary_keys(table_name) + pk = pk.first unless pk.size > 1 + pk end # Creates a new table with the name +table_name+. +table_name+ may either @@ -174,7 +174,7 @@ module ActiveRecord # A Symbol can be used to specify the type of the generated primary key column. # [<tt>:primary_key</tt>] # The name of the primary key, if one is to be added automatically. - # Defaults to +id+. If <tt>:id</tt> is false this option is ignored. + # Defaults to +id+. If <tt>:id</tt> is false, then this option is ignored. # # Note that Active Record models will automatically detect their # primary key. This can be avoided by using @@ -254,8 +254,8 @@ module ActiveRecord # SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id # # See also TableDefinition#column for details on how to create columns. - def create_table(table_name, options = {}) - td = create_table_definition table_name, options[:temporary], options[:options], options[:as] + def create_table(table_name, comment: nil, **options) + td = create_table_definition table_name, options[:temporary], options[:options], options[:as], comment: comment if options[:id] != false && !options[:as] pk = options.fetch(:primary_key) do @@ -278,11 +278,19 @@ module ActiveRecord result = execute schema_creation.accept td unless supports_indexes_in_create? - td.indexes.each_pair do |column_name, index_options| + td.indexes.each do |column_name, index_options| add_index(table_name, column_name, index_options) end end + if supports_comments? && !supports_comments_in_create? + change_table_comment(table_name, comment) if comment.present? + + td.columns.each do |column| + change_column_comment(table_name, column.name, column.comment) if column.comment.present? + end + end + result end @@ -292,9 +300,9 @@ module ActiveRecord # # Creates a table called 'assemblies_parts' with no id. # create_join_table(:assemblies, :parts) # - # You can pass a +options+ hash can include the following keys: + # You can pass an +options+ hash which can include the following keys: # [<tt>:table_name</tt>] - # Sets the table name overriding the default + # Sets the table name, overriding the default. # [<tt>:column_options</tt>] # Any extra options you want appended to the columns definition. # [<tt>:options</tt>] @@ -329,12 +337,13 @@ module ActiveRecord column_options = options.delete(:column_options) || {} column_options.reverse_merge!(null: false) + type = column_options.delete(:type) || :integer - t1_column, t2_column = [table_1, table_2].map{ |t| t.to_s.singularize.foreign_key } + t1_column, t2_column = [table_1, table_2].map { |t| t.to_s.singularize.foreign_key } create_table(join_table_name, options.merge!(id: false)) do |td| - td.integer t1_column, column_options - td.integer t2_column, column_options + td.send type, t1_column, column_options + td.send type, t2_column, column_options yield td if block_given? end end @@ -419,7 +428,7 @@ module ActiveRecord # t.remove_index :company_id # end # - # See also Table for details on all of the various column transformation. + # See also Table for details on all of the various column transformations. def change_table(table_name, options = {}) if supports_bulk_alter? && options[:bulk] recorder = ActiveRecord::Migration::CommandRecorder.new(self) @@ -469,10 +478,10 @@ module ActiveRecord # # Available options are (none of these exists by default): # * <tt>:limit</tt> - - # Requests a maximum column length. This is number of characters for a <tt>:string</tt> column + # Requests a maximum column length. This is the number of characters for a <tt>:string</tt> column # and number of bytes for <tt>:text</tt>, <tt>:binary</tt> and <tt>:integer</tt> columns. # * <tt>:default</tt> - - # The column's default value. Use nil for NULL. + # The column's default value. Use +nil+ for +NULL+. # * <tt>:null</tt> - # Allows or disallows +NULL+ values in the column. This option could # have been named <tt>:null_allowed</tt>. @@ -481,7 +490,7 @@ module ActiveRecord # * <tt>:scale</tt> - # Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns. # - # Note: The precision is the total number of significant digits + # Note: The precision is the total number of significant digits, # and the scale is the number of digits that can be stored following # the decimal point. For example, the number 123.45 has a precision of 5 # and a scale of 2. A decimal with a precision of 5 and a scale of 2 can @@ -502,7 +511,7 @@ module ActiveRecord # Default is (38,0). # * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62]. # Default unknown. - # * SqlServer?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38]. + # * SqlServer: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38]. # Default (38,0). # # == Examples @@ -524,6 +533,10 @@ module ActiveRecord # add_column(:measurements, :huge_integer, :decimal, precision: 30) # # ALTER TABLE "measurements" ADD "huge_integer" decimal(30) # + # # Defines a column that stores an array of a type. + # add_column(:users, :skills, :text, array: true) + # # ALTER TABLE "users" ADD "skills" text[] + # # # Defines a column with a database-specific type. # add_column(:shapes, :triangle, 'polygon') # # ALTER TABLE "shapes" ADD "triangle" polygon @@ -550,7 +563,7 @@ module ActiveRecord # # The +type+ and +options+ parameters will be ignored if present. It can be helpful # to provide these in a migration's +change+ method so it can be reverted. - # In that case, +type+ and +options+ will be used by add_column. + # In that case, +type+ and +options+ will be used by #add_column. def remove_column(table_name, column_name, type = nil, options = {}) execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}" end @@ -753,7 +766,7 @@ module ActiveRecord raise ArgumentError, "You must specify the index name" end else - index_name(table_name, :column => options) + index_name(table_name, column: options) end end @@ -776,7 +789,8 @@ module ActiveRecord # [<tt>:type</tt>] # The reference column type. Defaults to +:integer+. # [<tt>:index</tt>] - # Add an appropriate index. Defaults to false. + # Add an appropriate index. Defaults to true. + # See #add_index for usage of this option. # [<tt>:foreign_key</tt>] # Add an appropriate foreign key constraint. Defaults to false. # [<tt>:polymorphic</tt>] @@ -796,6 +810,14 @@ module ActiveRecord # # add_reference(:products, :supplier, polymorphic: true, index: true) # + # ====== Create a supplier_id column with a unique index + # + # add_reference(:products, :supplier, index: { unique: true }) + # + # ====== Create a supplier_id column with a named index + # + # add_reference(:products, :supplier, index: { name: "my_supplier_index" }) + # # ====== Create a supplier_id column and appropriate foreign key # # add_reference(:products, :supplier, foreign_key: true) @@ -824,14 +846,19 @@ module ActiveRecord # # remove_reference(:products, :user, index: true, foreign_key: true) # - def remove_reference(table_name, ref_name, options = {}) - if options[:foreign_key] + def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options) + if foreign_key reference_name = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name - remove_foreign_key(table_name, reference_name) + if foreign_key.is_a?(Hash) + foreign_key_options = foreign_key + else + foreign_key_options = { to_table: reference_name } + end + remove_foreign_key(table_name, **foreign_key_options) end remove_column(table_name, "#{ref_name}_id") - remove_column(table_name, "#{ref_name}_type") if options[:polymorphic] + remove_column(table_name, "#{ref_name}_type") if polymorphic end alias :remove_belongs_to :remove_reference @@ -854,7 +881,7 @@ module ActiveRecord # # generates: # - # ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id") + # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id") # # ====== Creating a foreign key on a specific column # @@ -870,7 +897,7 @@ module ActiveRecord # # generates: # - # ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE + # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE # # The +options+ hash can include the following keys: # [<tt>:column</tt>] @@ -924,13 +951,13 @@ module ActiveRecord # Checks to see if a foreign key exists on a table for a given foreign key definition. # - # # Check a foreign key exists + # # Checks to see if a foreign key exists. # foreign_key_exists?(:accounts, :branches) # - # # Check a foreign key on a specified column exists + # # Checks to see if a foreign key on a specified column exists. # foreign_key_exists?(:accounts, column: :owner_id) # - # # Check a foreign key with a custom name exists + # # Checks to see if a foreign key with a custom name exists. # foreign_key_exists?(:accounts, name: "special_fk_name") # def foreign_key_exists?(from_table, options_or_to_table = {}) @@ -939,12 +966,12 @@ module ActiveRecord def foreign_key_for(from_table, options_or_to_table = {}) # :nodoc: return unless supports_foreign_keys? - foreign_keys(from_table).detect {|fk| fk.defined_for? options_or_to_table } + foreign_keys(from_table).detect { |fk| fk.defined_for? options_or_to_table } end def foreign_key_for!(from_table, options_or_to_table = {}) # :nodoc: - foreign_key_for(from_table, options_or_to_table) or \ - raise ArgumentError, "Table '#{from_table}' has no foreign key for #{options_or_to_table}" + foreign_key_for(from_table, options_or_to_table) || \ + raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{options_or_to_table}") end def foreign_key_column_for(table_name) # :nodoc: @@ -962,11 +989,23 @@ module ActiveRecord end def dump_schema_information #:nodoc: + versions = ActiveRecord::SchemaMigration.order("version").pluck(:version) + insert_versions_sql(versions) + end + + def insert_versions_sql(versions) # :nodoc: sm_table = ActiveRecord::Migrator.schema_migrations_table_name - sql = "INSERT INTO #{sm_table} (version) VALUES " - sql << ActiveRecord::SchemaMigration.order('version').pluck(:version).map {|v| "('#{v}')" }.join(', ') - sql << ";\n\n" + if supports_multi_insert? + sql = "INSERT INTO #{sm_table} (version) VALUES\n" + sql << versions.map { |v| "('#{v}')" }.join(",\n") + sql << ";\n\n" + sql + else + versions.map { |version| + "INSERT INTO #{sm_table} (version) VALUES ('#{version}');" + }.join "\n\n" + end end # Should not be called normally, but this operation is non-destructive. @@ -989,26 +1028,27 @@ module ActiveRecord sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name) migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i) - paths = migrations_paths.map {|p| "#{p}/[0-9]*_*.rb" } + paths = migrations_paths.map { |p| "#{p}/[0-9]*_*.rb" } versions = Dir[*paths].map do |filename| - filename.split('/').last.split('_').first.to_i + filename.split("/").last.split("_").first.to_i end unless migrated.include?(version) execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')" end - inserting = (versions - migrated).select {|v| v < version} + inserting = (versions - migrated).select { |v| v < version } if inserting.any? - if (duplicate = inserting.detect {|v| inserting.count(v) > 1}) + if (duplicate = inserting.detect { |v| inserting.count(v) > 1 }) raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict." end - execute "INSERT INTO #{sm_table} (version) VALUES #{inserting.map {|v| "('#{v}')"}.join(', ') }" + execute insert_versions_sql(inserting) end end def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc: - if native = native_database_types[type.to_sym] + type = type.to_sym if type + if native = native_database_types[type] column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup if type == :decimal # ignore limit, use precision and scale @@ -1041,7 +1081,7 @@ module ActiveRecord end # Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT. - # PostgreSQL, MySQL, and Oracle overrides this for custom DISTINCT syntax - they + # PostgreSQL, MySQL, and Oracle override this for custom DISTINCT syntax - they # require the order columns appear in the SELECT. # # columns_for_distinct("posts.id", ["posts.created_at desc"]) @@ -1051,9 +1091,9 @@ module ActiveRecord end # Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+. - # Additional options (like <tt>null: false</tt>) are forwarded to #add_column. + # Additional options (like +:null+) are forwarded to #add_column. # - # add_timestamps(:suppliers, null: false) + # add_timestamps(:suppliers, null: true) # def add_timestamps(table_name, options = {}) options[:null] = false if options[:null].nil? @@ -1075,16 +1115,19 @@ module ActiveRecord Table.new(table_name, base) end - def add_index_options(table_name, column_name, options = {}) #:nodoc: - column_names = Array(column_name) + def add_index_options(table_name, column_name, comment: nil, **options) # :nodoc: + if column_name.is_a?(String) && /\W/.match?(column_name) + column_names = column_name + else + column_names = Array(column_name) + end options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type) index_type = options[:type].to_s if options.key?(:type) index_type ||= options[:unique] ? "UNIQUE" : "" index_name = options[:name].to_s if options.key?(:name) - index_name ||= index_name(table_name, column: column_names) - max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length + index_name ||= index_name(table_name, index_name_options(column_names)) if options.key?(:algorithm) algorithm = index_algorithms.fetch(options[:algorithm]) { @@ -1098,66 +1141,78 @@ module ActiveRecord index_options = options[:where] ? " WHERE #{options[:where]}" : "" end - if index_name.length > max_index_length - raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters" - end + 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) raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists" end index_columns = quoted_columns_for_index(column_names, options).join(", ") - [index_name, index_type, index_columns, index_options, algorithm, using] + [index_name, index_type, index_columns, index_options, algorithm, using, comment] end def options_include_default?(options) options.include?(:default) && !(options[:null] == false && options[:default].nil?) end + # Changes the comment for a table or removes it if +nil+. + def change_table_comment(table_name, comment) + raise NotImplementedError, "#{self.class} does not support changing table comments" + end + + # Changes the comment for a column or removes it if +nil+. + def change_column_comment(table_name, column_name, comment) #:nodoc: + raise NotImplementedError, "#{self.class} does not support changing column comments" + end + protected - def add_index_sort_order(option_strings, column_names, options = {}) - if options.is_a?(Hash) && order = options[:order] + + def add_index_sort_order(quoted_columns, **options) + if order = options[:order] case order when Hash - column_names.each {|name| option_strings[name] += " #{order[name].upcase}" if order.has_key?(name)} + quoted_columns.each { |name, column| column << " #{order[name].upcase}" if order[name].present? } when String - column_names.each {|name| option_strings[name] += " #{order.upcase}"} + quoted_columns.each { |name, column| column << " #{order.upcase}" if order.present? } end end - return option_strings + quoted_columns end # Overridden by the MySQL adapter for supporting index lengths - def quoted_columns_for_index(column_names, options = {}) - option_strings = Hash[column_names.map {|name| [name, '']}] - - # add index sort order if supported + def add_options_for_index_columns(quoted_columns, **options) if supports_index_sort_order? - option_strings = add_index_sort_order(option_strings, column_names, options) + quoted_columns = add_index_sort_order(quoted_columns, options) end - column_names.map {|name| quote_column_name(name) + option_strings[name]} + quoted_columns + end + + def quoted_columns_for_index(column_names, **options) + return [column_names] if column_names.is_a?(String) + + quoted_columns = Hash[column_names.map { |name| [name.to_sym, quote_column_name(name).dup] }] + add_options_for_index_columns(quoted_columns, options).values end def index_name_for_remove(table_name, options = {}) - # if the adapter doesn't support the indexes call the best we can do - # is return the default index name for the options provided - return index_name(table_name, options) unless respond_to?(:indexes) + return options[:name] if can_remove_index_by_name?(options) checks = [] if options.is_a?(Hash) - checks << lambda { |i| i.name == options[:name].to_s } if options.has_key?(:name) + checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name) column_names = Array(options[:column]).map(&:to_s) else column_names = Array(options).map(&:to_s) end if column_names.any? - checks << lambda { |i| i.columns.join('_and_') == column_names.join('_and_') } + checks << lambda { |i| i.columns.join("_and_") == column_names.join("_and_") } end - raise ArgumentError "No name or columns specified" if checks.none? + raise ArgumentError, "No name or columns specified" if checks.none? matching_indexes = indexes(table_name).select { |i| checks.all? { |check| check[i] } } @@ -1194,35 +1249,49 @@ module ActiveRecord end private - def create_table_definition(name, temporary = false, options = nil, as = nil) - TableDefinition.new(name, temporary, options, as) - end + def create_table_definition(*args) + TableDefinition.new(*args) + end - def create_alter_table(name) - AlterTable.new create_table_definition(name) - end + def create_alter_table(name) + AlterTable.new create_table_definition(name) + end + + def index_name_options(column_names) # :nodoc: + if column_names.is_a?(String) + column_names = column_names.scan(/\w+/).join("_") + end - def foreign_key_name(table_name, options) # :nodoc: - identifier = "#{table_name}_#{options.fetch(:column)}_fk" - hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10) - options.fetch(:name) do - "fk_rails_#{hashed_identifier}" + { column: column_names } end - end - def validate_index_length!(table_name, new_name) # :nodoc: - if new_name.length > allowed_index_name_length - raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters" + def foreign_key_name(table_name, options) # :nodoc: + identifier = "#{table_name}_#{options.fetch(:column)}_fk" + hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10) + options.fetch(:name) do + "fk_rails_#{hashed_identifier}" + end end - end - def extract_new_default_value(default_or_changes) - if default_or_changes.is_a?(Hash) && default_or_changes.has_key?(:from) && default_or_changes.has_key?(:to) - default_or_changes[:to] - else - default_or_changes + def validate_index_length!(table_name, new_name, internal = false) # :nodoc: + max_index_length = internal ? index_name_length : allowed_index_name_length + + if new_name.length > max_index_length + raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters" + end + end + + def extract_new_default_value(default_or_changes) + if default_or_changes.is_a?(Hash) && default_or_changes.has_key?(:from) && default_or_changes.has_key?(:to) + default_or_changes[:to] + else + default_or_changes + end + end + + def can_remove_index_by_name?(options) + options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty? end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index 6ecdab6eb0..6bb072dd73 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -41,7 +41,6 @@ module ActiveRecord end class Transaction #:nodoc: - attr_reader :connection, :state, :records, :savepoint_name attr_writer :joinable @@ -101,7 +100,6 @@ module ActiveRecord end class SavepointTransaction < Transaction - def initialize(connection, savepoint_name, options, *args) super(connection, options, *args) if options[:isolation] @@ -124,7 +122,6 @@ module ActiveRecord end class RealTransaction < Transaction - def initialize(connection, options, *args) super if options[:isolation] @@ -188,11 +185,14 @@ module ActiveRecord transaction = begin_transaction options yield rescue Exception => error - rollback_transaction if transaction + if transaction + rollback_transaction + after_failure_actions(transaction, error) + end raise ensure unless error - if Thread.current.status == 'aborting' + if Thread.current.status == "aborting" rollback_transaction if transaction else begin @@ -214,7 +214,15 @@ module ActiveRecord end private + NULL_TRANSACTION = NullTransaction.new + + # Deallocate invalidated prepared statements outside of the transaction + def after_failure_actions(transaction, error) + return unless transaction.is_a?(RealTransaction) + return unless error.is_a?(ActiveRecord::PreparedStatementCacheExpired) + @connection.clear_cache! + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index d9b42d4283..0c7197a002 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -1,12 +1,11 @@ -require 'active_record/type' -require 'active_support/core_ext/benchmark' -require 'active_record/connection_adapters/determine_if_preparable_visitor' -require 'active_record/connection_adapters/schema_cache' -require 'active_record/connection_adapters/sql_type_metadata' -require 'active_record/connection_adapters/abstract/schema_dumper' -require 'active_record/connection_adapters/abstract/schema_creation' -require 'arel/collectors/bind' -require 'arel/collectors/sql_string' +require "active_record/type" +require "active_record/connection_adapters/determine_if_preparable_visitor" +require "active_record/connection_adapters/schema_cache" +require "active_record/connection_adapters/sql_type_metadata" +require "active_record/connection_adapters/abstract/schema_dumper" +require "active_record/connection_adapters/abstract/schema_creation" +require "arel/collectors/bind" +require "arel/collectors/sql_string" module ActiveRecord module ConnectionAdapters # :nodoc: @@ -15,7 +14,7 @@ module ActiveRecord autoload :Column autoload :ConnectionSpecification - autoload_at 'active_record/connection_adapters/abstract/schema_definitions' do + autoload_at "active_record/connection_adapters/abstract/schema_definitions" do autoload :IndexDefinition autoload :ColumnDefinition autoload :ChangeColumnDefinition @@ -26,12 +25,11 @@ module ActiveRecord autoload :ReferenceDefinition end - autoload_at 'active_record/connection_adapters/abstract/connection_pool' do + autoload_at "active_record/connection_adapters/abstract/connection_pool" do autoload :ConnectionHandler - autoload :ConnectionManagement end - autoload_under 'abstract' do + autoload_under "abstract" do autoload :SchemaStatements autoload :DatabaseStatements autoload :DatabaseLimits @@ -41,7 +39,7 @@ module ActiveRecord autoload :Savepoints end - autoload_at 'active_record/connection_adapters/abstract/transaction' do + autoload_at "active_record/connection_adapters/abstract/transaction" do autoload :TransactionManager autoload :NullTransaction autoload :RealTransaction @@ -63,12 +61,13 @@ module ActiveRecord # Most of the methods in the adapter are useful during migrations. Most # notably, the instance methods provided by SchemaStatements are very useful. class AbstractAdapter - ADAPTER_NAME = 'Abstract'.freeze + ADAPTER_NAME = "Abstract".freeze include Quoting, DatabaseStatements, SchemaStatements include DatabaseLimits include QueryCache include ActiveSupport::Callbacks include ColumnDumper + include Savepoints SIMPLE_INT = /\A\d+\z/ @@ -106,25 +105,32 @@ module ActiveRecord @config = config @pool = nil @schema_cache = SchemaCache.new self - @visitor = nil - @prepared_statements = false + @quoted_column_names, @quoted_table_names = {}, {} + @visitor = arel_visitor + + if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) + @prepared_statements = true + @visitor.extend(DetermineIfPreparableVisitor) + else + @prepared_statements = false + end end class Version include Comparable def initialize(version_string) - @version = version_string.split('.').map(&:to_i) + @version = version_string.split(".").map(&:to_i) end def <=>(version_string) - @version <=> version_string.split('.').map(&:to_i) + @version <=> version_string.split(".").map(&:to_i) end end class BindCollector < Arel::Collectors::Bind def compile(bvs, conn) - casted_binds = conn.prepare_binds_for_database(bvs) + casted_binds = bvs.map(&:value_for_database) super(casted_binds.map { |value| conn.quote(value) }) end end @@ -143,8 +149,12 @@ module ActiveRecord end end + def arel_visitor # :nodoc: + Arel::Visitors::ToSql.new(self) + end + def valid_type?(type) - true + false end def schema_creation @@ -154,9 +164,9 @@ module ActiveRecord # this method must only be called while holding connection pool's mutex def lease if in_use? - msg = 'Cannot lease connection, ' + msg = "Cannot lease connection, " if @owner == Thread.current - msg << 'it is already leased by the current thread.' + msg << "it is already leased by the current thread." else msg << "it is already in use by a different thread: #{@owner}. " << "Current thread: #{Thread.current}." @@ -174,7 +184,30 @@ module ActiveRecord # this method must only be called while holding connection pool's mutex def expire - @owner = nil + if in_use? + if @owner != Thread.current + raise ActiveRecordError, "Cannot expire connection, " << + "it is owned by a different thread: #{@owner}. " << + "Current thread: #{Thread.current}." + end + + @owner = nil + else + raise ActiveRecordError, "Cannot expire connection, it is not currently leased." + end + end + + # this method must only be called while holding connection pool's mutex (and a desire for segfaults) + def steal! # :nodoc: + if in_use? + if @owner != Thread.current + pool.send :remove_connection_from_thread_cache, self, @owner + + @owner = Thread.current + end + else + raise ActiveRecordError, "Cannot steal connection, it is not currently leased." + end end def unprepared_statement @@ -238,6 +271,11 @@ module ActiveRecord false end + # Does this adapter support expression indices? + def supports_expression_index? + false + end + # Does this adapter support explain? def supports_explain? false @@ -279,6 +317,21 @@ module ActiveRecord false end + # Does this adapter support metadata comments on database objects (tables, columns, indexes)? + def supports_comments? + false + end + + # Can comments for tables, columns, and indexes be specified in create/alter table statements? + def supports_comments_in_create? + false + end + + # Does this adapter support multi-value insert? + def supports_multi_insert? + true + end + # This is meant to be implemented by the adapters that support extensions def disable_extension(name) end @@ -380,25 +433,15 @@ module ActiveRecord @connection end - def create_savepoint(name = nil) - end - - def release_savepoint(name = nil) - end - def case_sensitive_comparison(table, attribute, column, value) - if value.nil? - table[attribute].eq(value) - else - table[attribute].eq(Arel::Nodes::BindParam.new) - end + table[attribute].eq(Arel::Nodes::BindParam.new) end def case_insensitive_comparison(table, attribute, column, value) if can_perform_case_insensitive_comparison_for?(column) table[attribute].lower.eq(table.lower(Arel::Nodes::BindParam.new)) else - case_sensitive_comparison(table, attribute, column, value) + table[attribute].eq(Arel::Nodes::BindParam.new) end end @@ -407,10 +450,6 @@ module ActiveRecord end private :can_perform_case_insensitive_comparison_for? - def current_savepoint_name - current_transaction.savepoint_name - end - # Check the connection back in to the connection pool def close pool.checkin self @@ -422,8 +461,8 @@ module ActiveRecord end end - def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) - Column.new(name, default, sql_type_metadata, null, default_function, collation) + def new_column(name, default, sql_type_metadata, null, table_name, default_function = nil, collation = nil) # :nodoc: + Column.new(name, default, sql_type_metadata, null, table_name, default_function, collation) end def lookup_cast_type(sql_type) # :nodoc: @@ -434,116 +473,135 @@ module ActiveRecord visitor.accept(node, collector).value end + def combine_bind_parameters( + from_clause: [], + join_clause: [], + where_clause: [], + having_clause: [], + limit: nil, + offset: nil + ) # :nodoc: + result = from_clause + join_clause + where_clause + having_clause + if limit + result << limit + end + if offset + result << offset + end + result + end + protected - def initialize_type_map(m) # :nodoc: - 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 - register_class_with_limit m, %r(text)i, Type::Text - register_class_with_precision m, %r(date)i, Type::Date - register_class_with_precision m, %r(time)i, Type::Time - register_class_with_precision m, %r(datetime)i, Type::DateTime - register_class_with_limit m, %r(float)i, Type::Float - register_class_with_limit m, %r(int)i, Type::Integer - - m.alias_type %r(blob)i, 'binary' - m.alias_type %r(clob)i, 'text' - m.alias_type %r(timestamp)i, 'datetime' - m.alias_type %r(numeric)i, 'decimal' - m.alias_type %r(number)i, 'decimal' - m.alias_type %r(double)i, 'float' - - m.register_type(%r(decimal)i) do |sql_type| - scale = extract_scale(sql_type) - precision = extract_precision(sql_type) - - if scale == 0 - # FIXME: Remove this class as well - Type::DecimalWithoutScale.new(precision: precision) - else - Type::Decimal.new(precision: precision, scale: scale) + def initialize_type_map(m) # :nodoc: + 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 + register_class_with_limit m, %r(text)i, Type::Text + register_class_with_precision m, %r(date)i, Type::Date + register_class_with_precision m, %r(time)i, Type::Time + register_class_with_precision m, %r(datetime)i, Type::DateTime + register_class_with_limit m, %r(float)i, Type::Float + register_class_with_limit m, %r(int)i, Type::Integer + + m.alias_type %r(blob)i, "binary" + m.alias_type %r(clob)i, "text" + m.alias_type %r(timestamp)i, "datetime" + m.alias_type %r(numeric)i, "decimal" + m.alias_type %r(number)i, "decimal" + m.alias_type %r(double)i, "float" + + m.register_type(%r(decimal)i) do |sql_type| + scale = extract_scale(sql_type) + precision = extract_precision(sql_type) + + if scale == 0 + # FIXME: Remove this class as well + Type::DecimalWithoutScale.new(precision: precision) + else + Type::Decimal.new(precision: precision, scale: scale) + end end end - end - def reload_type_map # :nodoc: - type_map.clear - initialize_type_map(type_map) - end + def reload_type_map # :nodoc: + type_map.clear + initialize_type_map(type_map) + end - def register_class_with_limit(mapping, key, klass) # :nodoc: - mapping.register_type(key) do |*args| - limit = extract_limit(args.last) - klass.new(limit: limit) + def register_class_with_limit(mapping, key, klass) # :nodoc: + mapping.register_type(key) do |*args| + limit = extract_limit(args.last) + klass.new(limit: limit) + end end - end - def register_class_with_precision(mapping, key, klass) # :nodoc: - mapping.register_type(key) do |*args| - precision = extract_precision(args.last) - klass.new(precision: precision) + def register_class_with_precision(mapping, key, klass) # :nodoc: + mapping.register_type(key) do |*args| + precision = extract_precision(args.last) + klass.new(precision: precision) + end end - end - def extract_scale(sql_type) # :nodoc: - case sql_type + def extract_scale(sql_type) # :nodoc: + case sql_type when /\((\d+)\)/ then 0 when /\((\d+)(,(\d+))\)/ then $3.to_i + end end - end - - def extract_precision(sql_type) # :nodoc: - $1.to_i if sql_type =~ /\((\d+)(,\d+)?\)/ - end - def extract_limit(sql_type) # :nodoc: - case sql_type - when /^bigint/i - 8 - when /\((.*)\)/ - $1.to_i + def extract_precision(sql_type) # :nodoc: + $1.to_i if sql_type =~ /\((\d+)(,\d+)?\)/ end - end - def translate_exception_class(e, sql) - begin - message = "#{e.class.name}: #{e.message}: #{sql}" - rescue Encoding::CompatibilityError - message = "#{e.class.name}: #{e.message.force_encoding sql.encoding}: #{sql}" + def extract_limit(sql_type) # :nodoc: + case sql_type + when /^bigint/i + 8 + when /\((.*)\)/ + $1.to_i + end end - exception = translate_exception(e, message) - exception.set_backtrace e.backtrace - exception - end + def translate_exception_class(e, sql) + begin + message = "#{e.class.name}: #{e.message}: #{sql}" + rescue Encoding::CompatibilityError + message = "#{e.class.name}: #{e.message.force_encoding sql.encoding}: #{sql}" + end - def log(sql, name = "SQL", binds = [], statement_name = nil) - @instrumenter.instrument( - "sql.active_record", - :sql => sql, - :name => name, - :connection_id => object_id, - :statement_name => statement_name, - :binds => binds) { yield } - rescue => e - raise translate_exception_class(e, sql) - end + exception = translate_exception(e, message) + exception.set_backtrace e.backtrace + exception + end - def translate_exception(exception, message) - # override in derived class - ActiveRecord::StatementInvalid.new(message) - end + def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil) + @instrumenter.instrument( + "sql.active_record", + sql: sql, + name: name, + binds: binds, + type_casted_binds: type_casted_binds, + statement_name: statement_name, + connection_id: object_id) { yield } + rescue => e + raise translate_exception_class(e, sql) + end - def without_prepared_statement?(binds) - !prepared_statements || binds.empty? - end + def translate_exception(exception, message) + # override in derived class + ActiveRecord::StatementInvalid.new(message) + end - def column_for(table_name, column_name) # :nodoc: - column_name = column_name.to_s - columns(table_name).detect { |c| c.name == column_name } || - raise(ActiveRecordError, "No such column: #{table_name}.#{column_name}") - end + def without_prepared_statement?(binds) + !prepared_statements || binds.empty? + end + + def column_for(table_name, column_name) # :nodoc: + column_name = column_name.to_s + columns(table_name).detect { |c| c.name == column_name } || + raise(ActiveRecordError, "No such column: #{table_name}.#{column_name}") + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 8751b6da4b..3bee0e2c62 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -1,27 +1,33 @@ -require 'active_record/connection_adapters/abstract_adapter' -require 'active_record/connection_adapters/mysql/column' -require 'active_record/connection_adapters/mysql/explain_pretty_printer' -require 'active_record/connection_adapters/mysql/schema_creation' -require 'active_record/connection_adapters/mysql/schema_definitions' -require 'active_record/connection_adapters/mysql/schema_dumper' -require 'active_record/connection_adapters/mysql/type_metadata' - -require 'active_support/core_ext/string/strip' +require "active_record/connection_adapters/abstract_adapter" +require "active_record/connection_adapters/statement_pool" +require "active_record/connection_adapters/mysql/column" +require "active_record/connection_adapters/mysql/explain_pretty_printer" +require "active_record/connection_adapters/mysql/quoting" +require "active_record/connection_adapters/mysql/schema_creation" +require "active_record/connection_adapters/mysql/schema_definitions" +require "active_record/connection_adapters/mysql/schema_dumper" +require "active_record/connection_adapters/mysql/type_metadata" + +require "active_support/core_ext/string/strip" module ActiveRecord module ConnectionAdapters class AbstractMysqlAdapter < AbstractAdapter + include MySQL::Quoting include MySQL::ColumnDumper - include Savepoints def update_table_definition(table_name, base) # :nodoc: MySQL::Table.new(table_name, base) end - def schema_creation + def schema_creation # :nodoc: MySQL::SchemaCreation.new(self) end + def arel_visitor # :nodoc: + Arel::Visitors::MySQL.new(self) + end + ## # :singleton-method: # By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt> @@ -32,19 +38,17 @@ module ActiveRecord class_attribute :emulate_booleans self.emulate_booleans = true - QUOTED_TRUE, QUOTED_FALSE = '1', '0' - NATIVE_DATABASE_TYPES = { primary_key: "int auto_increment PRIMARY KEY", string: { name: "varchar", limit: 255 }, - text: { name: "text" }, + text: { name: "text", limit: 65535 }, integer: { name: "int", limit: 4 }, float: { name: "float" }, decimal: { name: "decimal" }, datetime: { name: "datetime" }, time: { name: "time" }, date: { name: "date" }, - binary: { name: "blob" }, + binary: { name: "blob", limit: 65535 }, boolean: { name: "tinyint", limit: 1 }, json: { name: "json" }, } @@ -52,36 +56,38 @@ module ActiveRecord INDEX_TYPES = [:fulltext, :spatial] INDEX_USINGS = [:btree, :hash] + class StatementPool < ConnectionAdapters::StatementPool + private def dealloc(stmt) + stmt[:stmt].close + end + end + def initialize(connection, logger, connection_options, config) super(connection, logger, config) - @quoted_column_names, @quoted_table_names = {}, {} - @visitor = Arel::Visitors::MySQL.new self + @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit])) - if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) - @prepared_statements = true - @visitor.extend(DetermineIfPreparableVisitor) - else - @prepared_statements = false - end - - if version < '5.0.0' + 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." end end - CHARSETS_OF_4BYTES_MAXLEN = ['utf8mb4', 'utf16', 'utf16le', 'utf32'] + CHARSETS_OF_4BYTES_MAXLEN = ["utf8mb4", "utf16", "utf16le", "utf32"] def internal_string_options_for_primary_key # :nodoc: super.tap { |options| - options[:collation] = collation.sub(/\A[^_]+/, 'utf8') if CHARSETS_OF_4BYTES_MAXLEN.include?(charset) + options[:collation] = collation.sub(/\A[^_]+/, "utf8") if CHARSETS_OF_4BYTES_MAXLEN.include?(charset) } end - def version + def version #:nodoc: @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0]) end + def mariadb? # :nodoc: + /mariadb/i.match?(full_version) + end + # Returns true, since this connection adapter supports migrations. def supports_migrations? true @@ -95,6 +101,12 @@ module ActiveRecord true end + # Returns true, since this connection adapter supports prepared statement + # caching. + def supports_statement_cache? + true + end + # Technically MySQL allows to create indexes with the sort order syntax # but at the moment (5.5) it doesn't yet implement them def supports_index_sort_order? @@ -122,7 +134,11 @@ module ActiveRecord end def supports_datetime_with_precision? - version >= '5.6.4' + if mariadb? + version >= "5.3.0" + else + version >= "5.6.4" + end end def supports_advisory_locks? @@ -130,11 +146,11 @@ module ActiveRecord end def get_advisory_lock(lock_name, timeout = 0) # :nodoc: - select_value("SELECT GET_LOCK('#{lock_name}', #{timeout});").to_s == '1' + select_value("SELECT GET_LOCK(#{quote(lock_name)}, #{timeout})") == 1 end def release_advisory_lock(lock_name) # :nodoc: - select_value("SELECT RELEASE_LOCK('#{lock_name}')").to_s == '1' + select_value("SELECT RELEASE_LOCK(#{quote(lock_name)})") == 1 end def native_database_types @@ -142,7 +158,7 @@ module ActiveRecord end def index_algorithms - { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' } + { default: "ALGORITHM = DEFAULT", copy: "ALGORITHM = COPY", inplace: "ALGORITHM = INPLACE" } end # HELPER METHODS =========================================== @@ -153,8 +169,8 @@ module ActiveRecord raise NotImplementedError end - def new_column(field, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc: - MySQL::Column.new(field, default, sql_type_metadata, null, default_function, collation) + def new_column(*args) #:nodoc: + MySQL::Column.new(*args) end # Must return the MySQL error number from the exception, if the exception has an @@ -163,48 +179,6 @@ module ActiveRecord raise NotImplementedError end - # QUOTING ================================================== - - def _quote(value) # :nodoc: - if value.is_a?(Type::Binary::Data) - "x'#{value.hex}'" - else - super - end - end - - def quote_column_name(name) #:nodoc: - @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`" - end - - def quote_table_name(name) #:nodoc: - @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`') - end - - def quoted_true - QUOTED_TRUE - end - - def unquoted_true - 1 - end - - def quoted_false - QUOTED_FALSE - end - - def unquoted_false - 0 - end - - def quoted_date(value) - if supports_datetime_with_precision? - super - else - super.sub(/\.\d{6}\z/, '') - end - end - # REFERENTIAL INTEGRITY ==================================== def disable_referential_integrity #:nodoc: @@ -218,6 +192,14 @@ module ActiveRecord end end + # CONNECTION MANAGEMENT ==================================== + + # Clears the prepared statements cache. + def clear_cache! + reload_type_map + @statements.clear + end + #-- # DATABASE STATEMENTS ====================================== #++ @@ -225,20 +207,19 @@ module ActiveRecord def explain(arel, binds = []) sql = "EXPLAIN #{to_sql(arel, binds)}" start = Time.now - result = exec_query(sql, 'EXPLAIN', binds) + result = exec_query(sql, "EXPLAIN", binds) elapsed = Time.now - start MySQL::ExplainPrettyPrinter.new.pp(result, elapsed) end - def clear_cache! - super - reload_type_map - end - # Executes the SQL statement in the context of this connection. def execute(sql, name = nil) - log(sql, name) { @connection.query(sql) } + log(sql, name) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @connection.query(sql) + end + end end # Mysql2Adapter doesn't have to free a result after using it, but we use this method @@ -301,9 +282,9 @@ module ActiveRecord # create_database 'matt_development', charset: :big5 def create_database(name, options = {}) if options[:collation] - execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`" + execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')} COLLATE #{quote_table_name(options[:collation])}" else - execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`" + execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}" end end @@ -312,21 +293,21 @@ module ActiveRecord # Example: # drop_database('sebastian_development') def drop_database(name) #:nodoc: - execute "DROP DATABASE IF EXISTS `#{name}`" + execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}" end def current_database - select_value 'SELECT DATABASE() as db' + select_value "SELECT DATABASE() as db" end # Returns the database character set. def charset - show_variable 'character_set_database' + show_variable "character_set_database" end # Returns the database collation strategy. def collation - show_variable 'collation_database' + show_variable "collation_database" end def tables(name = nil) # :nodoc: @@ -349,7 +330,7 @@ module ActiveRecord sql = "SELECT table_name FROM information_schema.tables " sql << "WHERE table_schema = #{quote(@config[:database])}" - select_values(sql, 'SCHEMA') + select_values(sql, "SCHEMA") end def truncate(table_name, name = nil) @@ -370,49 +351,47 @@ module ActiveRecord def data_source_exists?(table_name) return false unless table_name.present? - schema, name = table_name.to_s.split('.', 2) - schema, name = @config[:database], schema unless name # A table was provided without a schema + schema, name = extract_schema_qualified_name(table_name) sql = "SELECT table_name FROM information_schema.tables " sql << "WHERE table_schema = #{quote(schema)} AND table_name = #{quote(name)}" - select_values(sql, 'SCHEMA').any? + select_values(sql, "SCHEMA").any? end def views # :nodoc: - select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", 'SCHEMA') + select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", "SCHEMA") end def view_exists?(view_name) # :nodoc: return false unless view_name.present? - schema, name = view_name.to_s.split('.', 2) - schema, name = @config[:database], schema unless name # A view was provided without a schema + schema, name = extract_schema_qualified_name(view_name) sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'VIEW'" sql << " AND table_schema = #{quote(schema)} AND table_name = #{quote(name)}" - select_values(sql, 'SCHEMA').any? + select_values(sql, "SCHEMA").any? end # Returns an array of indexes for the given table. def indexes(table_name, name = nil) #:nodoc: indexes = [] current_index = nil - execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result| + execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result| each_hash(result) do |row| if current_index != row[:Key_name] - next if row[:Key_name] == 'PRIMARY' # skip the primary key + next if row[:Key_name] == "PRIMARY" # skip the primary key current_index = row[:Key_name] mysql_index_type = row[:Index_type].downcase.to_sym index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil - indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using) + indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], {}, nil, nil, index_type, index_using, row[:Index_comment].presence) end indexes.last.columns << row[:Column_name] - indexes.last.lengths << row[:Sub_part] + indexes.last.lengths.merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part] end end @@ -421,21 +400,31 @@ module ActiveRecord # Returns an array of +Column+ objects for the table specified by +table_name+. def columns(table_name) # :nodoc: - sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}" - execute_and_free(sql, 'SCHEMA') do |result| - each_hash(result).map do |field| - type_metadata = fetch_type_metadata(field[:Type], field[:Extra]) - if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP" - new_column(field[:Field], nil, type_metadata, field[:Null] == "YES", field[:Default], field[:Collation]) - else - new_column(field[:Field], field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation]) - end + table_name = table_name.to_s + column_definitions(table_name).map do |field| + type_metadata = fetch_type_metadata(field[:Type], field[:Extra]) + if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP" + default, default_function = nil, field[:Default] + else + default, default_function = field[:Default], nil end + new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation], comment: field[:Comment].presence) end end - def create_table(table_name, options = {}) #:nodoc: - super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB")) + def table_comment(table_name) # :nodoc: + schema, name = extract_schema_qualified_name(table_name) + + select_value(<<-SQL.strip_heredoc, "SCHEMA") + SELECT table_comment + FROM information_schema.tables + WHERE table_schema = #{quote(schema)} + AND table_name = #{quote(name)} + SQL + end + + def create_table(table_name, **options) #:nodoc: + super(table_name, options: "ENGINE=InnoDB", **options) end def bulk_change_table(table_name, operations) #:nodoc: @@ -478,7 +467,6 @@ module ActiveRecord # it can be helpful to provide these in a migration's +change+ method so it can be reverted. # In that case, +options+ and the block will be used by create_table. def drop_table(table_name, options = {}) - create_table_info_cache.delete(table_name) if create_table_info_cache.key?(table_name) execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}" end @@ -495,7 +483,7 @@ module ActiveRecord def change_column_default(table_name, column_name, default_or_changes) #:nodoc: default = extract_new_default_value(default_or_changes) column = column_for(table_name, column_name) - change_column table_name, column_name, column.sql_type, :default => default + change_column table_name, column_name, column.sql_type, default: default end def change_column_null(table_name, column_name, null, default = nil) #:nodoc: @@ -505,7 +493,7 @@ module ActiveRecord execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") end - change_column table_name, column_name, column.sql_type, :null => null + change_column table_name, column_name, column.sql_type, null: null end def change_column(table_name, column_name, type, options = {}) #:nodoc: @@ -518,75 +506,98 @@ module ActiveRecord end def add_index(table_name, column_name, options = {}) #:nodoc: - index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options) - execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}" + index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options) + sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}" + execute add_sql_comment!(sql, comment) + end + + def add_sql_comment!(sql, comment) # :nodoc: + sql << " COMMENT #{quote(comment)}" if comment.present? + sql end def foreign_keys(table_name) - fk_info = select_all <<-SQL.strip_heredoc - SELECT fk.referenced_table_name as 'to_table' - ,fk.referenced_column_name as 'primary_key' - ,fk.column_name as 'column' - ,fk.constraint_name as 'name' + raise ArgumentError unless table_name.present? + + schema, name = extract_schema_qualified_name(table_name) + + fk_info = select_all(<<-SQL.strip_heredoc, "SCHEMA") + SELECT fk.referenced_table_name AS 'to_table', + fk.referenced_column_name AS 'primary_key', + fk.column_name AS 'column', + fk.constraint_name AS 'name', + rc.update_rule AS 'on_update', + rc.delete_rule AS 'on_delete' FROM information_schema.key_column_usage fk - WHERE fk.referenced_column_name is not null - AND fk.table_schema = '#{@config[:database]}' - AND fk.table_name = '#{table_name}' + JOIN information_schema.referential_constraints rc + USING (constraint_schema, constraint_name) + WHERE fk.referenced_column_name IS NOT NULL + AND fk.table_schema = #{quote(schema)} + AND fk.table_name = #{quote(name)} SQL - create_table_info = create_table_info(table_name) - fk_info.map do |row| options = { - column: row['column'], - name: row['name'], - primary_key: row['primary_key'] + column: row["column"], + name: row["name"], + primary_key: row["primary_key"] } - options[:on_update] = extract_foreign_key_action(create_table_info, row['name'], "UPDATE") - options[:on_delete] = extract_foreign_key_action(create_table_info, row['name'], "DELETE") + options[:on_update] = extract_foreign_key_action(row["on_update"]) + options[:on_delete] = extract_foreign_key_action(row["on_delete"]) - ForeignKeyDefinition.new(table_name, row['to_table'], options) + ForeignKeyDefinition.new(table_name, row["to_table"], options) end end - def table_options(table_name) + def table_options(table_name) # :nodoc: + table_options = {} + create_table_info = create_table_info(table_name) # strip create_definitions and partition_options - raw_table_options = create_table_info.sub(/\A.*\n\) /m, '').sub(/\n\/\*!.*\*\/\n\z/m, '').strip + raw_table_options = create_table_info.sub(/\A.*\n\) /m, "").sub(/\n\/\*!.*\*\/\n\z/m, "").strip # strip AUTO_INCREMENT - raw_table_options.sub(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1') + raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1') + + table_options[:options] = raw_table_options + + # strip COMMENT + if raw_table_options.sub!(/ COMMENT='.+'/, "") + table_options[:comment] = table_comment(table_name) + end + + table_options end # Maps logical Rails types to MySQL-specific data types. def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil) - sql = case type.to_s - when 'integer' - integer_to_sql(limit) - when 'text' - text_to_sql(limit) - when 'blob' - binary_to_sql(limit) - when 'binary' - if (0..0xfff) === limit - "varbinary(#{limit})" - else + sql = \ + case type.to_s + when "integer" + integer_to_sql(limit) + when "text" + text_to_sql(limit) + when "blob" binary_to_sql(limit) + when "binary" + if (0..0xfff) === limit + "varbinary(#{limit})" + else + binary_to_sql(limit) + end + else + super(type, limit, precision, scale) end - else - super(type, limit, precision, scale) - end - sql << ' unsigned' if unsigned && type != :primary_key + sql << " unsigned" if unsigned && type != :primary_key sql end # SHOW VARIABLES LIKE 'name' def show_variable(name) - variables = select_all("select @@#{name} as 'Value'", 'SCHEMA') - variables.first['Value'] unless variables.empty? + select_value("SELECT @@#{name}", "SCHEMA") rescue ActiveRecord::StatementInvalid nil end @@ -594,10 +605,9 @@ module ActiveRecord def primary_keys(table_name) # :nodoc: raise ArgumentError unless table_name.present? - schema, name = table_name.to_s.split('.', 2) - schema, name = @config[:database], schema unless name # A table was provided without a schema + schema, name = extract_schema_qualified_name(table_name) - select_values(<<-SQL.strip_heredoc, 'SCHEMA') + select_values(<<-SQL.strip_heredoc, "SCHEMA") SELECT column_name FROM information_schema.key_column_usage WHERE constraint_name = 'PRIMARY' @@ -608,20 +618,17 @@ module ActiveRecord end def case_sensitive_comparison(table, attribute, column, value) - if value.nil? || column.case_sensitive? - super - else + if column.collation && !column.case_sensitive? table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new)) + else + super end end - def case_insensitive_comparison(table, attribute, column, value) - if column.case_sensitive? - super - else - table[attribute].eq(Arel::Nodes::BindParam.new) - end + def can_perform_case_insensitive_comparison_for?(column) + column.case_sensitive? end + private :can_perform_case_insensitive_comparison_for? # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for @@ -632,10 +639,10 @@ module ActiveRecord # Convert Arel node to string s = s.to_sql unless s.is_a?(String) # Remove any ASC/DESC modifiers - s.gsub(/\s+(?:ASC|DESC)\b/i, '') + s.gsub(/\s+(?:ASC|DESC)\b/i, "") }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" } - [super, *order_columns].join(', ') + [super, *order_columns].join(", ") end def strict_mode? @@ -648,318 +655,333 @@ module ActiveRecord protected - def initialize_type_map(m) # :nodoc: - super - - register_class_with_limit m, %r(char)i, MysqlString + def initialize_type_map(m) # :nodoc: + super - m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1) - m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1) - m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1) - m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1) - m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1) - m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1) - m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1) - m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1) - m.register_type %r(^float)i, Type::Float.new(limit: 24) - m.register_type %r(^double)i, Type::Float.new(limit: 53) - m.register_type %r(^json)i, MysqlJson.new + register_class_with_limit m, %r(char)i, MysqlString + + m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1) + m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1) + m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1) + m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1) + m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1) + m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1) + m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1) + m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1) + m.register_type %r(^float)i, Type::Float.new(limit: 24) + m.register_type %r(^double)i, Type::Float.new(limit: 53) + m.register_type %r(^json)i, MysqlJson.new + + register_integer_type m, %r(^bigint)i, limit: 8 + register_integer_type m, %r(^int)i, limit: 4 + register_integer_type m, %r(^mediumint)i, limit: 3 + register_integer_type m, %r(^smallint)i, limit: 2 + register_integer_type m, %r(^tinyint)i, limit: 1 + + m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans + m.alias_type %r(year)i, "integer" + m.alias_type %r(bit)i, "binary" + + m.register_type(%r(enum)i) do |sql_type| + limit = sql_type[/^enum\((.+)\)/i, 1] + .split(",").map { |enum| enum.strip.length - 2 }.max + MysqlString.new(limit: limit) + end - register_integer_type m, %r(^bigint)i, limit: 8 - register_integer_type m, %r(^int)i, limit: 4 - register_integer_type m, %r(^mediumint)i, limit: 3 - register_integer_type m, %r(^smallint)i, limit: 2 - register_integer_type m, %r(^tinyint)i, limit: 1 + m.register_type(%r(^set)i) do |sql_type| + limit = sql_type[/^set\((.+)\)/i, 1] + .split(",").map { |set| set.strip.length - 1 }.sum - 1 + MysqlString.new(limit: limit) + end + end - m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans - m.alias_type %r(year)i, 'integer' - m.alias_type %r(bit)i, 'binary' + def register_integer_type(mapping, key, options) # :nodoc: + mapping.register_type(key) do |sql_type| + if /\bunsigned\z/.match?(sql_type) + Type::UnsignedInteger.new(options) + else + Type::Integer.new(options) + end + end + end - m.register_type(%r(enum)i) do |sql_type| - limit = sql_type[/^enum\((.+)\)/i, 1] - .split(',').map{|enum| enum.strip.length - 2}.max - MysqlString.new(limit: limit) + def extract_precision(sql_type) + if /time/.match?(sql_type) + super || 0 + else + super + end end - m.register_type(%r(^set)i) do |sql_type| - limit = sql_type[/^set\((.+)\)/i, 1] - .split(',').map{|set| set.strip.length - 1}.sum - 1 - MysqlString.new(limit: limit) + def fetch_type_metadata(sql_type, extra = "") + MySQL::TypeMetadata.new(super(sql_type), extra: extra) end - end - def register_integer_type(mapping, key, options) # :nodoc: - mapping.register_type(key) do |sql_type| - if /\bunsigned\z/ === sql_type - Type::UnsignedInteger.new(options) - else - Type::Integer.new(options) + def add_index_length(quoted_columns, **options) + if length = options[:length] + case length + when Hash + quoted_columns.each { |name, column| column << "(#{length[name]})" if length[name].present? } + when Integer + quoted_columns.each { |name, column| column << "(#{length})" } + end end + + quoted_columns end - end - def extract_precision(sql_type) - if /time/ === sql_type - super || 0 - else + def add_options_for_index_columns(quoted_columns, **options) + quoted_columns = add_index_length(quoted_columns, options) super end - end - def fetch_type_metadata(sql_type, extra = "") - MySQL::TypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?) - end - - def add_index_length(option_strings, column_names, options = {}) - if options.is_a?(Hash) && length = options[:length] - case length - when Hash - column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?} - when Fixnum - column_names.each {|name| option_strings[name] += "(#{length})"} + # See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html + ER_DUP_ENTRY = 1062 + ER_NO_REFERENCED_ROW_2 = 1452 + ER_DATA_TOO_LONG = 1406 + ER_LOCK_DEADLOCK = 1213 + + def translate_exception(exception, message) + case error_number(exception) + when ER_DUP_ENTRY + RecordNotUnique.new(message) + when ER_NO_REFERENCED_ROW_2 + InvalidForeignKey.new(message) + when ER_DATA_TOO_LONG + ValueTooLong.new(message) + when ER_LOCK_DEADLOCK + Deadlocked.new(message) + else + super end end - return option_strings - end - - def quoted_columns_for_index(column_names, options = {}) - option_strings = Hash[column_names.map {|name| [name, '']}] + def add_column_sql(table_name, column_name, type, options = {}) + td = create_table_definition(table_name) + cd = td.new_column_definition(column_name, type, options) + schema_creation.accept(AddColumnDefinition.new(cd)) + end - # add index length - option_strings = add_index_length(option_strings, column_names, options) + def change_column_sql(table_name, column_name, type, options = {}) + column = column_for(table_name, column_name) - # add index sort order - option_strings = add_index_sort_order(option_strings, column_names, options) + unless options_include_default?(options) + options[:default] = column.default + end - column_names.map {|name| quote_column_name(name) + option_strings[name]} - end + unless options.has_key?(:null) + options[:null] = column.null + end - def translate_exception(exception, message) - case error_number(exception) - when 1062 - RecordNotUnique.new(message) - when 1452 - InvalidForeignKey.new(message) - else - super + td = create_table_definition(table_name) + cd = td.new_column_definition(column.name, type, options) + schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) end - end - - def add_column_sql(table_name, column_name, type, options = {}) - td = create_table_definition(table_name) - cd = td.new_column_definition(column_name, type, options) - schema_creation.accept(AddColumnDefinition.new(cd)) - end - def change_column_sql(table_name, column_name, type, options = {}) - column = column_for(table_name, column_name) + def rename_column_sql(table_name, column_name, new_column_name) + column = column_for(table_name, column_name) + options = { + default: column.default, + null: column.null, + auto_increment: column.auto_increment? + } - unless options_include_default?(options) - options[:default] = column.default + current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", "SCHEMA")["Type"] + td = create_table_definition(table_name) + cd = td.new_column_definition(new_column_name, current_type, options) + schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) end - unless options.has_key?(:null) - options[:null] = column.null + def remove_column_sql(table_name, column_name, type = nil, options = {}) + "DROP #{quote_column_name(column_name)}" end - td = create_table_definition(table_name) - cd = td.new_column_definition(column.name, type, options) - schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) - end - - def rename_column_sql(table_name, column_name, new_column_name) - column = column_for(table_name, column_name) - options = { - default: column.default, - null: column.null, - auto_increment: column.auto_increment? - } - - current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"] - td = create_table_definition(table_name) - cd = td.new_column_definition(new_column_name, current_type, options) - schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) - end - - def remove_column_sql(table_name, column_name, type = nil, options = {}) - "DROP #{quote_column_name(column_name)}" - end - - def remove_columns_sql(table_name, *column_names) - column_names.map {|column_name| remove_column_sql(table_name, column_name) } - end + def remove_columns_sql(table_name, *column_names) + column_names.map { |column_name| remove_column_sql(table_name, column_name) } + end - def add_index_sql(table_name, column_name, options = {}) - index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options) - index_algorithm[0, 0] = ", " if index_algorithm.present? - "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}" - end + def add_index_sql(table_name, column_name, options = {}) + index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options) + index_algorithm[0, 0] = ", " if index_algorithm.present? + "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}" + end - def remove_index_sql(table_name, options = {}) - index_name = index_name_for_remove(table_name, options) - "DROP INDEX #{index_name}" - end + def remove_index_sql(table_name, options = {}) + index_name = index_name_for_remove(table_name, options) + "DROP INDEX #{index_name}" + end - def add_timestamps_sql(table_name, options = {}) - [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)] - end + def add_timestamps_sql(table_name, options = {}) + [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)] + end - def remove_timestamps_sql(table_name, options = {}) - [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)] - end + def remove_timestamps_sql(table_name, options = {}) + [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) - subsubselect = select.clone - subsubselect.projections = [key] + # 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) + subsubselect = select.clone + subsubselect.projections = [key] - # Materialize subquery by adding distinct - # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on' - subsubselect.distinct unless select.limit || select.offset || select.orders.any? + # Materialize subquery by adding distinct + # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on' + subsubselect.distinct unless select.limit || select.offset || select.orders.any? - subselect = Arel::SelectManager.new(select.engine) - subselect.project Arel.sql(key.name) - subselect.from subsubselect.as('__active_record_temp') - end + subselect = Arel::SelectManager.new(select.engine) + subselect.project Arel.sql(key.name) + subselect.from subsubselect.as("__active_record_temp") + end - def mariadb? - full_version =~ /mariadb/i - end + def supports_rename_index? + mariadb? ? false : version >= "5.7.6" + end - def supports_rename_index? - mariadb? ? false : version >= '5.7.6' - end + def configure_connection + variables = @config.fetch(:variables, {}).stringify_keys - def configure_connection - variables = @config.fetch(:variables, {}).stringify_keys + # By default, MySQL 'where id is null' selects the last inserted id; Turn this off. + variables["sql_auto_is_null"] = 0 - # By default, MySQL 'where id is null' selects the last inserted id; Turn this off. - variables['sql_auto_is_null'] = 0 + # Increase timeout so the server doesn't disconnect us. + wait_timeout = @config[:wait_timeout] + wait_timeout = 2147483 unless wait_timeout.is_a?(Integer) + variables["wait_timeout"] = self.class.type_cast_config_to_integer(wait_timeout) - # Increase timeout so the server doesn't disconnect us. - wait_timeout = @config[:wait_timeout] - wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum) - variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout) + defaults = [":default", :default].to_set - defaults = [':default', :default].to_set + # Make MySQL reject illegal values rather than truncating or blanking them, see + # http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables + # If the user has provided another value for sql_mode, don't replace it. + if sql_mode = variables.delete("sql_mode") + sql_mode = quote(sql_mode) + elsif !defaults.include?(strict_mode?) + if strict_mode? + sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')" + else + sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')" + sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')" + sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')" + end + sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')" + end + sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode + + # NAMES does not have an equals sign, see + # http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430 + # (trailing comma because variable_assignments will always have content) + if @config[:encoding] + encoding = "NAMES #{@config[:encoding]}" + encoding << " COLLATE #{@config[:collation]}" if @config[:collation] + encoding << ", " + end - # Make MySQL reject illegal values rather than truncating or blanking them, see - # http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables - # If the user has provided another value for sql_mode, don't replace it. - unless variables.has_key?('sql_mode') || defaults.include?(@config[:strict]) - variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : '' - end + # Gather up all of the SET variables... + variable_assignments = variables.map do |k, v| + if defaults.include?(v) + "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default + elsif !v.nil? + "@@SESSION.#{k} = #{quote(v)}" + end + # or else nil; compact to clear nils out + end.compact.join(", ") - # NAMES does not have an equals sign, see - # http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430 - # (trailing comma because variable_assignments will always have content) - if @config[:encoding] - encoding = "NAMES #{@config[:encoding]}" - encoding << " COLLATE #{@config[:collation]}" if @config[:collation] - encoding << ", " + # ...and send them all in one query + @connection.query "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}" end - # Gather up all of the SET variables... - variable_assignments = variables.map do |k, v| - if defaults.include?(v) - "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default - elsif !v.nil? - "@@SESSION.#{k} = #{quote(v)}" + def column_definitions(table_name) # :nodoc: + execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result| + each_hash(result) end - # or else nil; compact to clear nils out - end.compact.join(', ') - - # ...and send them all in one query - @connection.query "SET #{encoding} #{variable_assignments}" - end + end - def extract_foreign_key_action(structure, name, action) # :nodoc: - if structure =~ /CONSTRAINT #{quote_column_name(name)} FOREIGN KEY .* REFERENCES .* ON #{action} (CASCADE|SET NULL|RESTRICT)/ - case $1 - when 'CASCADE'; :cascade - when 'SET NULL'; :nullify + def extract_foreign_key_action(specifier) # :nodoc: + case specifier + when "CASCADE"; :cascade + when "SET NULL"; :nullify end end - end - - def create_table_info_cache # :nodoc: - @create_table_info_cache ||= {} - end - - def create_table_info(table_name) # :nodoc: - create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"] - end - def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc: - MySQL::TableDefinition.new(name, temporary, options, as) - end + def create_table_info(table_name) # :nodoc: + select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"] + end - def integer_to_sql(limit) # :nodoc: - case limit - when 1; 'tinyint' - when 2; 'smallint' - when 3; 'mediumint' - when nil, 4; 'int' - when 5..8; 'bigint' - else raise(ActiveRecordError, "No integer type has byte size #{limit}") + def create_table_definition(*args) # :nodoc: + MySQL::TableDefinition.new(*args) end - end - def text_to_sql(limit) # :nodoc: - case limit - when 0..0xff; 'tinytext' - when nil, 0x100..0xffff; 'text' - when 0x10000..0xffffff; 'mediumtext' - when 0x1000000..0xffffffff; 'longtext' - else raise(ActiveRecordError, "No text type has byte length #{limit}") + def extract_schema_qualified_name(string) # :nodoc: + schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/) + schema, name = @config[:database], schema unless name + [schema, name] end - end - def binary_to_sql(limit) # :nodoc: - case limit - when 0..0xff; 'tinyblob' - when nil, 0x100..0xffff; 'blob' - when 0x10000..0xffffff; 'mediumblob' - when 0x1000000..0xffffffff; 'longblob' - else raise(ActiveRecordError, "No binary type has byte length #{limit}") + def integer_to_sql(limit) # :nodoc: + case limit + when 1; "tinyint" + when 2; "smallint" + when 3; "mediumint" + when nil, 4; "int" + when 5..8; "bigint" + else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a decimal with scale 0 instead.") + end end - end - class MysqlJson < Type::Internal::AbstractJson # :nodoc: - def changed_in_place?(raw_old_value, new_value) - # Normalization is required because MySQL JSON data format includes - # the space between the elements. - super(serialize(deserialize(raw_old_value)), new_value) + def text_to_sql(limit) # :nodoc: + case limit + when 0..0xff; "tinytext" + when nil, 0x100..0xffff; "text" + when 0x10000..0xffffff; "mediumtext" + when 0x1000000..0xffffffff; "longtext" + else raise(ActiveRecordError, "No text type has byte length #{limit}") + end end - end - class MysqlString < Type::String # :nodoc: - def serialize(value) - case value - when true then "1" - when false then "0" - else super + def binary_to_sql(limit) # :nodoc: + case limit + when 0..0xff; "tinyblob" + when nil, 0x100..0xffff; "blob" + when 0x10000..0xffffff; "mediumblob" + when 0x1000000..0xffffffff; "longblob" + else raise(ActiveRecordError, "No binary type has byte length #{limit}") end end - private + class MysqlJson < Type::Internal::AbstractJson # :nodoc: + def changed_in_place?(raw_old_value, new_value) + # Normalization is required because MySQL JSON data format includes + # the space between the elements. + super(serialize(deserialize(raw_old_value)), new_value) + end + end - def cast_value(value) - case value - when true then "1" - when false then "0" - else super + class MysqlString < Type::String # :nodoc: + def serialize(value) + case value + when true then MySQL::Quoting::QUOTED_TRUE + when false then MySQL::Quoting::QUOTED_FALSE + else super + end end + + private + + def cast_value(value) + case value + when true then MySQL::Quoting::QUOTED_TRUE + when false then MySQL::Quoting::QUOTED_FALSE + else super + end + end end - end - ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2) - ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2) - ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2) + ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2) + ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2) + ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2) end end end diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 10f908538f..02d546209d 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -1,11 +1,9 @@ -require 'set' - module ActiveRecord # :stopdoc: module ConnectionAdapters # An abstract definition of a column in a table. class Column - attr_reader :name, :null, :sql_type_metadata, :default, :default_function, :collation + attr_reader :name, :default, :sql_type_metadata, :null, :table_name, :default_function, :collation, :comment delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true @@ -15,14 +13,15 @@ module ActiveRecord # +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>. # +sql_type_metadata+ is various information about the type of the column # +null+ determines if this column allows +NULL+ values. - def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) + def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil, collation = nil, comment: nil) @name = name.freeze + @table_name = table_name @sql_type_metadata = sql_type_metadata @null = null @default = default @default_function = default_function @collation = collation - @table_name = nil + @comment = comment end def has_default? @@ -30,7 +29,7 @@ module ActiveRecord end def bigint? - /\Abigint\b/ === sql_type + /\Abigint\b/.match?(sql_type) end # Returns the human name of the column name. @@ -53,9 +52,9 @@ module ActiveRecord protected - def attributes_for_hash - [self.class, name, default, sql_type_metadata, null, default_function, collation] - end + def attributes_for_hash + [self.class, name, default, sql_type_metadata, null, table_name, default_function, collation] + end end class NullColumn < Column diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index 4bc6447368..849130ba43 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -1,21 +1,24 @@ -require 'uri' +require "uri" module ActiveRecord module ConnectionAdapters class ConnectionSpecification #:nodoc: - attr_reader :config, :adapter_method + attr_reader :name, :config, :adapter_method - def initialize(config, adapter_method) - @config, @adapter_method = config, adapter_method + def initialize(name, config, adapter_method) + @name, @config, @adapter_method = name, config, adapter_method end def initialize_dup(original) @config = original.config.dup end + def to_hash + @config.merge(name: @name) + end + # Expands a connection string into a hash. class ConnectionUrlResolver # :nodoc: - # == Example # # url = "postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000" @@ -33,11 +36,11 @@ module ActiveRecord def initialize(url) raise "Database URL cannot be empty" if url.blank? @uri = uri_parser.parse(url) - @adapter = @uri.scheme && @uri.scheme.tr('-', '_') + @adapter = @uri.scheme && @uri.scheme.tr("-", "_") @adapter = "postgresql" if @adapter == "postgres" if @uri.opaque - @uri.opaque, @query = @uri.opaque.split('?', 2) + @uri.opaque, @query = @uri.opaque.split("?", 2) else @query = @uri.query end @@ -52,58 +55,58 @@ module ActiveRecord private - def uri - @uri - end + def uri + @uri + end - def uri_parser - @uri_parser ||= URI::Parser.new - end + def uri_parser + @uri_parser ||= URI::Parser.new + end - # Converts the query parameters of the URI into a hash. - # - # "localhost?pool=5&reaping_frequency=2" - # # => { "pool" => "5", "reaping_frequency" => "2" } - # - # returns empty hash if no query present. - # - # "localhost" - # # => {} - def query_hash - Hash[(@query || '').split("&").map { |pair| pair.split("=") }] - end + # Converts the query parameters of the URI into a hash. + # + # "localhost?pool=5&reaping_frequency=2" + # # => { "pool" => "5", "reaping_frequency" => "2" } + # + # returns empty hash if no query present. + # + # "localhost" + # # => {} + def query_hash + Hash[(@query || "").split("&").map { |pair| pair.split("=") }] + end - def raw_config - if uri.opaque - query_hash.merge({ - "adapter" => @adapter, - "database" => uri.opaque }) - else - query_hash.merge({ - "adapter" => @adapter, - "username" => uri.user, - "password" => uri.password, - "port" => uri.port, - "database" => database_from_path, - "host" => uri.hostname }) + def raw_config + if uri.opaque + query_hash.merge( + "adapter" => @adapter, + "database" => uri.opaque) + else + query_hash.merge( + "adapter" => @adapter, + "username" => uri.user, + "password" => uri.password, + "port" => uri.port, + "database" => database_from_path, + "host" => uri.hostname) + end end - end - # Returns name of the database. - def database_from_path - if @adapter == 'sqlite3' - # 'sqlite3:/foo' is absolute, because that makes sense. The - # corresponding relative version, 'sqlite3:foo', is handled - # elsewhere, as an "opaque". + # Returns name of the database. + def database_from_path + if @adapter == "sqlite3" + # 'sqlite3:/foo' is absolute, because that makes sense. The + # corresponding relative version, 'sqlite3:foo', is handled + # elsewhere, as an "opaque". - uri.path - else - # Only SQLite uses a filename as the "database" name; for - # anything else, a leading slash would be silly. + uri.path + else + # Only SQLite uses a filename as the "database" name; for + # anything else, a leading slash would be silly. - uri.path.sub(%r{^/}, "") + uri.path.sub(%r{^/}, "") + end end - end end ## @@ -179,77 +182,82 @@ module ActiveRecord end adapter_method = "#{spec[:adapter]}_connection" - ConnectionSpecification.new(spec, adapter_method) + + unless ActiveRecord::Base.respond_to?(adapter_method) + raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter" + end + + ConnectionSpecification.new(spec.delete(:name) || "primary", spec, adapter_method) end private - # Returns fully resolved connection, accepts hash, string or symbol. - # Always returns a hash. - # - # == Examples - # - # Symbol representing current environment. - # - # Resolver.new("production" => {}).resolve_connection(:production) - # # => {} - # - # One layer deep hash of connection values. - # - # Resolver.new({}).resolve_connection("adapter" => "sqlite3") - # # => { "adapter" => "sqlite3" } - # - # Connection URL. - # - # Resolver.new({}).resolve_connection("postgresql://localhost/foo") - # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } - # - def resolve_connection(spec) - case spec - when Symbol - resolve_symbol_connection spec - when String - resolve_url_connection spec - when Hash - resolve_hash_connection spec + # Returns fully resolved connection, accepts hash, string or symbol. + # Always returns a hash. + # + # == Examples + # + # Symbol representing current environment. + # + # Resolver.new("production" => {}).resolve_connection(:production) + # # => {} + # + # One layer deep hash of connection values. + # + # Resolver.new({}).resolve_connection("adapter" => "sqlite3") + # # => { "adapter" => "sqlite3" } + # + # Connection URL. + # + # Resolver.new({}).resolve_connection("postgresql://localhost/foo") + # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } + # + def resolve_connection(spec) + case spec + when Symbol + resolve_symbol_connection spec + when String + resolve_url_connection spec + when Hash + resolve_hash_connection spec + end end - end - # Takes the environment such as +:production+ or +:development+. - # This requires that the @configurations was initialized with a key that - # matches. - # - # Resolver.new("production" => {}).resolve_symbol_connection(:production) - # # => {} - # - def resolve_symbol_connection(spec) - if config = configurations[spec.to_s] - resolve_connection(config) - else - raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available: #{configurations.keys.inspect}") + # Takes the environment such as +:production+ or +:development+. + # This requires that the @configurations was initialized with a key that + # matches. + # + # Resolver.new("production" => {}).resolve_symbol_connection(:production) + # # => {} + # + def resolve_symbol_connection(spec) + if config = configurations[spec.to_s] + resolve_connection(config).merge("name" => spec.to_s) + else + raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available: #{configurations.keys.inspect}") + end end - end - # Accepts a hash. Expands the "url" key that contains a - # URL database connection to a full connection - # hash and merges with the rest of the hash. - # Connection details inside of the "url" key win any merge conflicts - def resolve_hash_connection(spec) - if spec["url"] && spec["url"] !~ /^jdbc:/ - connection_hash = resolve_url_connection(spec.delete("url")) - spec.merge!(connection_hash) + # Accepts a hash. Expands the "url" key that contains a + # URL database connection to a full connection + # hash and merges with the rest of the hash. + # Connection details inside of the "url" key win any merge conflicts + def resolve_hash_connection(spec) + if spec["url"] && spec["url"] !~ /^jdbc:/ + connection_hash = resolve_url_connection(spec.delete("url")) + spec.merge!(connection_hash) + end + spec end - spec - end - # Takes a connection URL. - # - # Resolver.new({}).resolve_url_connection("postgresql://localhost/foo") - # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } - # - def resolve_url_connection(url) - ConnectionUrlResolver.new(url).to_hash - end + # Takes a connection URL. + # + # Resolver.new({}).resolve_url_connection("postgresql://localhost/foo") + # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } + # + def resolve_url_connection(url) + ConnectionUrlResolver.new(url).to_hash + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/column.rb b/activerecord/lib/active_record/connection_adapters/mysql/column.rb index 9c45fdd44a..f82c556a6f 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/column.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/column.rb @@ -2,47 +2,18 @@ module ActiveRecord module ConnectionAdapters module MySQL class Column < ConnectionAdapters::Column # :nodoc: - delegate :strict, :extra, to: :sql_type_metadata, allow_nil: true - - def initialize(*) - super - assert_valid_default - extract_default - end - - def has_default? - return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns - super - end - - def blob_or_text_column? - /\A(?:tiny|medium|long)?blob\b/ === sql_type || type == :text - end + delegate :extra, to: :sql_type_metadata, allow_nil: true def unsigned? - /\bunsigned\z/ === sql_type + /\bunsigned\z/.match?(sql_type) end def case_sensitive? - collation && collation !~ /_ci\z/ + collation && !/_ci\z/.match?(collation) end def auto_increment? - extra == 'auto_increment' - end - - private - - def extract_default - if blob_or_text_column? - @default = null || strict ? nil : '' - end - end - - def assert_valid_default - if blob_or_text_column? && default.present? - raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}" - end + extra == "auto_increment" 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 new file mode 100644 index 0000000000..274753a8a5 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb @@ -0,0 +1,110 @@ +module ActiveRecord + module ConnectionAdapters + module MySQL + module DatabaseStatements + # Returns an ActiveRecord::Result instance. + def select_all(arel, name = nil, binds = [], preparable: nil) + result = if ExplainRegistry.collect? && prepared_statements + unprepared_statement { super } + else + super + end + @connection.next_result while @connection.more_results? + result + 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 = []) + select_result(sql, name, binds) do |result| + @connection.next_result while @connection.more_results? + result.to_a + end + end + + # Executes the SQL statement in the context of this connection. + def execute(sql, name = nil) + # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been + # made since we established the connection + @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone + + super + end + + def exec_query(sql, name = "SQL", binds = [], prepare: false) + if without_prepared_statement?(binds) + execute_and_free(sql, name) do |result| + ActiveRecord::Result.new(result.fields, result.to_a) if result + end + else + exec_stmt_and_free(sql, name, binds, cache_stmt: prepare) do |_, result| + ActiveRecord::Result.new(result.fields, result.to_a) if result + end + end + end + + def exec_delete(sql, name = nil, binds = []) + if without_prepared_statement?(binds) + execute_and_free(sql, name) { @connection.affected_rows } + else + exec_stmt_and_free(sql, name, binds) { |stmt| stmt.affected_rows } + end + end + alias :exec_update :exec_delete + + protected + + def last_inserted_id(result) + @connection.last_id + end + + private + + def select_result(sql, name = nil, binds = []) + if without_prepared_statement?(binds) + execute_and_free(sql, name) { |result| yield result } + else + exec_stmt_and_free(sql, name, binds, cache_stmt: true) { |_, result| yield result } + end + end + + def exec_stmt_and_free(sql, name, binds, cache_stmt: false) + # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been + # made since we established the connection + @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone + + type_casted_binds = type_casted_binds(binds) + + log(sql, name, binds, type_casted_binds) do + if cache_stmt + cache = @statements[sql] ||= { + stmt: @connection.prepare(sql) + } + stmt = cache[:stmt] + else + stmt = @connection.prepare(sql) + end + + begin + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + result = stmt.execute(*type_casted_binds) + end + rescue Mysql2::Error => e + if cache_stmt + @statements.delete(sql) + else + stmt.close + end + raise e + end + + ret = yield stmt, result + result.free if result + stmt.close unless cache_stmt + ret + end + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb b/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb index 1820853196..925555703d 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb @@ -36,34 +36,34 @@ module ActiveRecord private - def compute_column_widths(result) - [].tap do |widths| - result.columns.each_with_index do |column, i| - cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s} - widths << cells_in_column.map(&:length).max + def compute_column_widths(result) + [].tap do |widths| + result.columns.each_with_index do |column, i| + cells_in_column = [column] + result.rows.map { |r| r[i].nil? ? "NULL" : r[i].to_s } + widths << cells_in_column.map(&:length).max + end end end - end - def build_separator(widths) - padding = 1 - '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+' - end + def build_separator(widths) + padding = 1 + "+" + widths.map { |w| "-" * (w + (padding*2)) }.join("+") + "+" + end - def build_cells(items, widths) - cells = [] - items.each_with_index do |item, i| - item = 'NULL' if item.nil? - justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust' - cells << item.to_s.send(justifier, widths[i]) + def build_cells(items, widths) + cells = [] + items.each_with_index do |item, i| + item = "NULL" if item.nil? + justifier = item.is_a?(Numeric) ? "rjust" : "ljust" + cells << item.to_s.send(justifier, widths[i]) + end + "| " + cells.join(" | ") + " |" end - '| ' + cells.join(' | ') + ' |' - end - def build_footer(nrows, elapsed) - rows_label = nrows == 1 ? 'row' : 'rows' - "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed - end + def build_footer(nrows, elapsed) + rows_label = nrows == 1 ? "row" : "rows" + "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb new file mode 100644 index 0000000000..9d11ad28d4 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb @@ -0,0 +1,51 @@ +module ActiveRecord + module ConnectionAdapters + module MySQL + module Quoting # :nodoc: + QUOTED_TRUE, QUOTED_FALSE = "1".freeze, "0".freeze + + def quote_column_name(name) + @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`".freeze + end + + def quote_table_name(name) + @quoted_table_names[name] ||= super.gsub(".", "`.`").freeze + end + + def quoted_true + QUOTED_TRUE + end + + def unquoted_true + 1 + end + + def quoted_false + QUOTED_FALSE + end + + def unquoted_false + 0 + end + + def quoted_date(value) + if supports_datetime_with_precision? + super + else + super.sub(/\.\d{6}\z/, "") + end + end + + private + + def _quote(value) + if value.is_a?(Type::Binary::Data) + "x'#{value.hex}'" + else + super + end + end + 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 1e2c859af9..d808b50332 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb @@ -2,55 +2,65 @@ module ActiveRecord module ConnectionAdapters module MySQL class SchemaCreation < AbstractAdapter::SchemaCreation + delegate :add_sql_comment!, to: :@conn + private :add_sql_comment! + private - def visit_DropForeignKey(name) - "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 - - def visit_ChangeColumnDefinition(o) - change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}" - add_column_position!(change_column_sql, column_options(o.column)) - end - - def column_options(o) - column_options = super - column_options[:charset] = o.charset - column_options - end - - def add_column_options!(sql, options) - if options[:charset] - sql << " CHARACTER SET #{options[:charset]}" - end - if options[:collation] - sql << " COLLATE #{options[:collation]}" - end - super - end - - def add_column_position!(sql, options) - if options[:first] - sql << " FIRST" - elsif options[:after] - sql << " AFTER #{quote_column_name(options[:after])}" - end - sql - end - - def index_in_create(table_name, column_name, options) - index_name, index_type, index_columns, _, _, index_using = @conn.add_index_options(table_name, column_name, options) - "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns}) " - end + def visit_DropForeignKey(name) + "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 + + def visit_ChangeColumnDefinition(o) + change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}" + add_column_position!(change_column_sql, column_options(o.column)) + end + + def add_table_options!(create_sql, options) + 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}" + end + + if collation = options[:collation] + sql << " COLLATE #{collation}" + end + + add_sql_comment!(super, options[:comment]) + end + + def add_column_position!(sql, options) + if options[:first] + sql << " FIRST" + elsif options[:after] + sql << " AFTER #{quote_column_name(options[:after])}" + end + + sql + end + + def index_in_create(table_name, column_name, options) + index_name, index_type, index_columns, _, _, index_using, comment = @conn.add_index_options(table_name, column_name, options) + add_sql_comment!("#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})", comment) + end end end 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 157e75dbf7..ce773ed75b 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb @@ -80,9 +80,9 @@ module ActiveRecord private - def create_column_definition(name, type) - MySQL::ColumnDefinition.new(name, type) - end + def create_column_definition(name, type) + MySQL::ColumnDefinition.new(name, type) + 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 ccf5b6cadc..9b02d8a34b 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb @@ -3,24 +3,19 @@ module ActiveRecord module MySQL module ColumnDumper def column_spec_for_primary_key(column) - spec = {} if column.bigint? - spec[:id] = ':bigint' - spec[:default] = schema_default(column) || 'nil' unless column.auto_increment? - spec[:unsigned] = 'true' if column.unsigned? - elsif column.auto_increment? - spec[:unsigned] = 'true' if column.unsigned? - return if spec.empty? + spec = { id: :bigint.inspect } + spec[:default] = schema_default(column) || "nil" unless column.auto_increment? else - spec[:id] = schema_type(column).inspect - spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) }) + spec = super end + spec[:unsigned] = "true" if column.unsigned? spec end def prepare_column_options(column) spec = super - spec[:unsigned] = 'true' if column.unsigned? + spec[:unsigned] = "true" if column.unsigned? spec end @@ -30,29 +25,29 @@ module ActiveRecord private - def schema_type(column) - if column.sql_type == 'tinyblob' - :blob - else - super + def default_primary_key?(column) + super && column.auto_increment? end - end - def schema_limit(column) - super unless column.type == :boolean - end + def schema_type(column) + if column.sql_type == "tinyblob" + :blob + else + super + end + end - def schema_precision(column) - super unless /time/ === column.sql_type && column.precision == 0 - end + def schema_precision(column) + super unless /time/.match?(column.sql_type) && column.precision == 0 + end - def schema_collation(column) - if column.collation && table_name = column.instance_variable_get(:@table_name) - @table_collation_cache ||= {} - @table_collation_cache[table_name] ||= select_one("SHOW TABLE STATUS LIKE '#{table_name}'")["Collation"] - column.collation.inspect if column.collation != @table_collation_cache[table_name] + def schema_collation(column) + if column.collation && table_name = column.table_name + @table_collation_cache ||= {} + @table_collation_cache[table_name] ||= select_one("SHOW TABLE STATUS LIKE '#{table_name}'")["Collation"] + column.collation.inspect if column.collation != @table_collation_cache[table_name] + end end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb index e1e3f7b472..24dcf852e1 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb @@ -2,13 +2,12 @@ module ActiveRecord module ConnectionAdapters module MySQL class TypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc: - attr_reader :extra, :strict + attr_reader :extra - def initialize(type_metadata, extra: "", strict: false) + def initialize(type_metadata, extra: "") super(type_metadata) @type_metadata = type_metadata @extra = extra - @strict = strict end def ==(other) @@ -23,9 +22,9 @@ module ActiveRecord protected - def attributes_for_hash - [self.class, @type_metadata, extra, strict] - end + def attributes_for_hash + [self.class, @type_metadata, extra] + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 23b33a3555..a3e2c913c5 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -1,7 +1,9 @@ -require 'active_record/connection_adapters/abstract_mysql_adapter' +require "active_record/connection_adapters/abstract_mysql_adapter" +require "active_record/connection_adapters/mysql/database_statements" -gem 'mysql2', '>= 0.3.18', '< 0.5' -require 'mysql2' +gem "mysql2", ">= 0.3.18", "< 0.5" +require "mysql2" +raise "mysql2 0.4.3 is not supported. Please upgrade to 0.4.4+" if Mysql2::VERSION == "0.4.3" module ActiveRecord module ConnectionHandling # :nodoc: @@ -9,14 +11,14 @@ module ActiveRecord def mysql2_connection(config) config = config.symbolize_keys - config[:username] = 'root' if config[:username].nil? + config[:username] = "root" if config[:username].nil? config[:flags] ||= 0 if Mysql2::Client.const_defined? :FOUND_ROWS if config[:flags].kind_of? Array config[:flags].push "FOUND_ROWS".freeze else - config[:flags] |= Mysql2::Client::FOUND_ROWS + config[:flags] |= Mysql2::Client::FOUND_ROWS end end @@ -33,23 +35,37 @@ module ActiveRecord module ConnectionAdapters class Mysql2Adapter < AbstractMysqlAdapter - ADAPTER_NAME = 'Mysql2'.freeze + ADAPTER_NAME = "Mysql2".freeze + + include MySQL::DatabaseStatements def initialize(connection, logger, connection_options, config) super - @prepared_statements = false + @prepared_statements = false unless config.key?(:prepared_statements) configure_connection end def supports_json? - !mariadb? && version >= '5.7.8' + !mariadb? && version >= "5.7.8" + end + + def supports_comments? + true + end + + def supports_comments_in_create? + true + end + + def supports_savepoints? + true end # HELPER METHODS =========================================== def each_hash(result) # :nodoc: if block_given? - result.each(:as => :hash, :symbolize_keys => true) do |row| + result.each(as: :hash, symbolize_keys: true) do |row| yield row end else @@ -74,7 +90,6 @@ module ActiveRecord #++ def active? - return false unless @connection @connection.ping end @@ -89,82 +104,24 @@ module ActiveRecord # Otherwise, this method does nothing. def disconnect! super - unless @connection.nil? - @connection.close - @connection = nil - end + @connection.close end - #-- - # DATABASE STATEMENTS ====================================== - #++ + private - # Returns a record hash with the column names as keys and column values - # as values. - def select_one(arel, name = nil, binds = []) - arel, binds = binds_from_relation(arel, binds) - execute(to_sql(arel, binds), name).each(as: :hash) do |row| - @connection.next_result while @connection.more_results? - return row + def connect + @connection = Mysql2::Client.new(@config) + configure_connection end - 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 = []) - result = execute(sql, name) - @connection.next_result while @connection.more_results? - result.to_a - end - # Executes the SQL statement in the context of this connection. - def execute(sql, name = nil) - if @connection - # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been - # made since we established the connection - @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone + def configure_connection + @connection.query_options.merge!(as: :array) + super end - super - end - - def exec_query(sql, name = 'SQL', binds = [], prepare: false) - result = execute(sql, name) - @connection.next_result while @connection.more_results? - ActiveRecord::Result.new(result.fields, result.to_a) - end - - alias exec_without_stmt exec_query - - def exec_insert(sql, name, binds, pk = nil, sequence_name = nil) - execute to_sql(sql, binds), name - end - - def exec_delete(sql, name, binds) - execute to_sql(sql, binds), name - @connection.affected_rows - end - alias :exec_update :exec_delete - - def last_inserted_id(result) - @connection.last_id - end - - private - - def connect - @connection = Mysql2::Client.new(@config) - configure_connection - end - - def configure_connection - @connection.query_options.merge!(:as => :array) - super - end - - def full_version - @full_version ||= @connection.server_info[:version] - end + def full_version + @full_version ||= @connection.server_info[:version] + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb index bfa03fa136..3ad1911a28 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb @@ -8,7 +8,6 @@ module ActiveRecord def serial? return unless default_function - table_name = @table_name || '(?<table_name>.+)' %r{\Anextval\('"?#{table_name}_#{name}_seq"?'::regclass\)\z} === default_function 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 6aa264d766..520a50506f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -4,7 +4,7 @@ module ActiveRecord module DatabaseStatements def explain(arel, binds = []) sql = "EXPLAIN #{to_sql(arel, binds)}" - PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds)) + PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", binds)) end def select_value(arel, name = nil, binds = []) @@ -74,9 +74,9 @@ module ActiveRecord # (2) $12.345.678,12 case data when /^-?\D+[\d,]+\.\d{2}$/ # (1) - data.gsub!(/[^-\d.]/, '') + data.gsub!(/[^-\d.]/, "") when /^-?\D+[\d.]+,\d{2}$/ # (2) - data.gsub!(/[^-\d,]/, '').sub!(/,/, '.') + data.gsub!(/[^-\d,]/, "").sub!(/,/, ".") end end end @@ -85,21 +85,25 @@ module ActiveRecord # Queries the database and returns the results in an Array-like object def query(sql, name = nil) #:nodoc: log(sql, name) do - result_as_array @connection.async_exec(sql) + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + result_as_array @connection.async_exec(sql) + end end end - # Executes an SQL statement, returning a PGresult object on success - # or raising a PGError exception otherwise. - # Note: the PGresult object is manually memory managed; if you don't - # need it specifically, you many want consider the exec_query wrapper. + # Executes an SQL statement, returning a PG::Result object on success + # or raising a PG::Error exception otherwise. + # Note: the PG::Result object is manually memory managed; if you don't + # need it specifically, you may want consider the <tt>exec_query</tt> wrapper. def execute(sql, name = nil) log(sql, name) do - @connection.async_exec(sql) + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @connection.async_exec(sql) + end end end - def exec_query(sql, name = 'SQL', binds = [], prepare: false) + def exec_query(sql, name = "SQL", binds = [], prepare: false) execute_and_clear(sql, name, binds, prepare: prepare) do |result| types = {} fields = result.fields @@ -112,36 +116,41 @@ module ActiveRecord end end - def exec_delete(sql, name = 'SQL', binds = []) - execute_and_clear(sql, name, binds) {|result| result.cmd_tuples } + def exec_delete(sql, name = nil, binds = []) + execute_and_clear(sql, name, binds) { |result| result.cmd_tuples } end alias :exec_update :exec_delete def sql_for_insert(sql, pk, id_value, sequence_name, binds) # :nodoc: - unless pk + if pk.nil? # Extract the table from the insert sql. Yuck. table_ref = extract_table_ref_from_insert_sql(sql) pk = primary_key(table_ref) if table_ref end - if pk && use_insert_returning? + if pk = suppress_composite_primary_key(pk) sql = "#{sql} RETURNING #{quote_column_name(pk)}" end super end + protected :sql_for_insert - def exec_insert(sql, name, binds, pk = nil, sequence_name = nil) - val = exec_query(sql, name, binds) - if !use_insert_returning? && pk + def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil) + if use_insert_returning? || pk == false + super + else + result = exec_query(sql, name, binds) unless sequence_name table_ref = extract_table_ref_from_insert_sql(sql) - sequence_name = default_sequence_name(table_ref, pk) - return val unless sequence_name + if table_ref + pk = primary_key(table_ref) if pk.nil? + pk = suppress_composite_primary_key(pk) + sequence_name = default_sequence_name(table_ref, pk) + end + return result unless sequence_name end last_insert_id_result(sequence_name) - else - val end end @@ -164,6 +173,12 @@ module ActiveRecord def exec_rollback_db_transaction execute "ROLLBACK" end + + private + + def suppress_composite_primary_key(pk) + pk unless pk.is_a?(Array) + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb index 789b88912c..99f3a5bbdf 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb @@ -26,12 +26,12 @@ module ActiveRecord pp = [] pp << header.center(width).rstrip - pp << '-' * width + pp << "-" * width - pp += lines.map {|line| " #{line}"} + pp += lines.map { |line| " #{line}" } nrows = result.rows.length - rows_label = nrows == 1 ? 'row' : 'rows' + rows_label = nrows == 1 ? "row" : "rows" pp << "(#{nrows} #{rows_label})" pp.join("\n") + "\n" diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index 68752cdd80..0e526f6201 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -1,25 +1,25 @@ -require 'active_record/connection_adapters/postgresql/oid/array' -require 'active_record/connection_adapters/postgresql/oid/bit' -require 'active_record/connection_adapters/postgresql/oid/bit_varying' -require 'active_record/connection_adapters/postgresql/oid/bytea' -require 'active_record/connection_adapters/postgresql/oid/cidr' -require 'active_record/connection_adapters/postgresql/oid/date_time' -require 'active_record/connection_adapters/postgresql/oid/decimal' -require 'active_record/connection_adapters/postgresql/oid/enum' -require 'active_record/connection_adapters/postgresql/oid/hstore' -require 'active_record/connection_adapters/postgresql/oid/inet' -require 'active_record/connection_adapters/postgresql/oid/json' -require 'active_record/connection_adapters/postgresql/oid/jsonb' -require 'active_record/connection_adapters/postgresql/oid/money' -require 'active_record/connection_adapters/postgresql/oid/point' -require 'active_record/connection_adapters/postgresql/oid/rails_5_1_point' -require 'active_record/connection_adapters/postgresql/oid/range' -require 'active_record/connection_adapters/postgresql/oid/specialized_string' -require 'active_record/connection_adapters/postgresql/oid/uuid' -require 'active_record/connection_adapters/postgresql/oid/vector' -require 'active_record/connection_adapters/postgresql/oid/xml' +require "active_record/connection_adapters/postgresql/oid/array" +require "active_record/connection_adapters/postgresql/oid/bit" +require "active_record/connection_adapters/postgresql/oid/bit_varying" +require "active_record/connection_adapters/postgresql/oid/bytea" +require "active_record/connection_adapters/postgresql/oid/cidr" +require "active_record/connection_adapters/postgresql/oid/date_time" +require "active_record/connection_adapters/postgresql/oid/decimal" +require "active_record/connection_adapters/postgresql/oid/enum" +require "active_record/connection_adapters/postgresql/oid/hstore" +require "active_record/connection_adapters/postgresql/oid/inet" +require "active_record/connection_adapters/postgresql/oid/json" +require "active_record/connection_adapters/postgresql/oid/jsonb" +require "active_record/connection_adapters/postgresql/oid/money" +require "active_record/connection_adapters/postgresql/oid/point" +require "active_record/connection_adapters/postgresql/oid/legacy_point" +require "active_record/connection_adapters/postgresql/oid/range" +require "active_record/connection_adapters/postgresql/oid/specialized_string" +require "active_record/connection_adapters/postgresql/oid/uuid" +require "active_record/connection_adapters/postgresql/oid/vector" +require "active_record/connection_adapters/postgresql/oid/xml" -require 'active_record/connection_adapters/postgresql/oid/type_map_initializer' +require "active_record/connection_adapters/postgresql/oid/type_map_initializer" module ActiveRecord module ConnectionAdapters 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 87593ef704..b969503178 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb @@ -8,7 +8,7 @@ module ActiveRecord attr_reader :subtype, :delimiter delegate :type, :user_input_in_time_zone, :limit, to: :subtype - def initialize(subtype, delimiter = ',') + def initialize(subtype, delimiter = ",") @subtype = subtype @delimiter = delimiter @@ -33,7 +33,11 @@ module ActiveRecord def serialize(value) if value.is_a?(::Array) - @pg_encoder.encode(type_cast_array(value, :serialize)) + result = @pg_encoder.encode(type_cast_array(value, :serialize)) + if encoding = determine_encoding_of_strings(value) + result.encode!(encoding) + end + result else super end @@ -56,13 +60,20 @@ module ActiveRecord private - def type_cast_array(value, method) - if value.is_a?(::Array) - value.map { |item| type_cast_array(item, method) } - else - @subtype.public_send(method, value) + def type_cast_array(value, method) + if value.is_a?(::Array) + value.map { |item| type_cast_array(item, method) } + else + @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 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 ea0fa2517f..302d393277 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb @@ -7,7 +7,7 @@ module ActiveRecord :bit end - def cast(value) + def cast_value(value) if ::String === value case value when /^0x/i @@ -16,7 +16,7 @@ module ActiveRecord value # Bit-string notation end else - value + value.to_s end end @@ -34,16 +34,16 @@ module ActiveRecord end def binary? - /\A[01]*\Z/ === value + /\A[01]*\Z/.match?(value) end def hex? - /\A[0-9A-F]*\Z/i === value + /\A[0-9A-F]*\Z/i.match?(value) end protected - attr_reader :value + attr_reader :value end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb index eeccb09bdf..5225609e37 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb @@ -1,3 +1,5 @@ +require "ipaddr" + module ActiveRecord module ConnectionAdapters module PostgreSQL diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb index 424769f765..b7acbf7178 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb @@ -5,8 +5,8 @@ module ActiveRecord class DateTime < Type::DateTime # :nodoc: def cast_value(value) case value - when 'infinity' then ::Float::INFINITY - when '-infinity' then -::Float::INFINITY + when "infinity" then ::Float::INFINITY + when "-infinity" then -::Float::INFINITY when / BC$/ astronomical_year = format("%04d", -value[/^\d+/].to_i + 1) super(value.sub(/ BC$/, "").sub(/^\d+/, astronomical_year)) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb index 91d339f32c..950d23d516 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb @@ -9,9 +9,9 @@ module ActiveRecord private - def cast_value(value) - value.to_s - end + def cast_value(value) + value.to_s + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb index 9270fc9f21..2d3e6a925d 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb @@ -12,7 +12,7 @@ module ActiveRecord def deserialize(value) if value.is_a?(::String) ::Hash[value.scan(HstorePair).map { |k, v| - v = v.upcase == 'NULL' ? nil : v.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1') + v = v.upcase == "NULL" ? nil : v.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1') k = k.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1') [k, v] }] @@ -23,7 +23,7 @@ module ActiveRecord def serialize(value) if value.is_a?(::Hash) - value.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(', ') + value.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(", ") else value end @@ -35,23 +35,23 @@ module ActiveRecord private - HstorePair = begin - quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/ - unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/ - /(#{quoted_string}|#{unquoted_string})\s*=>\s*(#{quoted_string}|#{unquoted_string})/ - end + HstorePair = begin + quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/ + unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/ + /(#{quoted_string}|#{unquoted_string})\s*=>\s*(#{quoted_string}|#{unquoted_string})/ + end - def escape_hstore(value) - if value.nil? - 'NULL' - else - if value == "" - '""' + def escape_hstore(value) + if value.nil? + "NULL" else - '"%s"' % value.to_s.gsub(/(["\\])/, '\\\\\1') + if value == "" + '""' + else + '"%s"' % value.to_s.gsub(/(["\\])/, '\\\\\1') + end end end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb index 7427a25ad5..775eecaf85 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb @@ -1,10 +1,8 @@ module ActiveRecord - Point = Struct.new(:x, :y) - module ConnectionAdapters module PostgreSQL module OID # :nodoc: - class Rails51Point < Type::Value # :nodoc: + class LegacyPoint < Type::Value # :nodoc: include Type::Helpers::Mutable def type @@ -14,21 +12,20 @@ module ActiveRecord def cast(value) case value when ::String - if value[0] == '(' && value[-1] == ')' + if value[0] == "(" && value[-1] == ")" value = value[1...-1] end - x, y = value.split(",") - build_point(x, y) + cast(value.split(",")) when ::Array - build_point(*value) + value.map { |v| Float(v) } else value end end def serialize(value) - if value.is_a?(ActiveRecord::Point) - "(#{number_for_point(value.x)},#{number_for_point(value.y)})" + if value.is_a?(::Array) + "(#{number_for_point(value[0])},#{number_for_point(value[1])})" else super end @@ -36,13 +33,9 @@ module ActiveRecord private - def number_for_point(number) - number.to_s.gsub(/\.0$/, '') - end - - def build_point(x, y) - ActiveRecord::Point.new(Float(x), Float(y)) - end + def number_for_point(number) + number.to_s.gsub(/\.0$/, "") + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb index dcc12ae2a4..7a91272d1c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb @@ -25,9 +25,9 @@ module ActiveRecord value.sub!(/^\((.+)\)$/, '-\1') # (4) case value when /^-?\D+[\d,]+\.\d{2}$/ # (1) - value.gsub!(/[^-\d.]/, '') + value.gsub!(/[^-\d.]/, "") when /^-?\D+[\d.]+,\d{2}$/ # (2) - value.gsub!(/[^-\d,]/, '').sub!(/,/, '.') + value.gsub!(/[^-\d,]/, "").sub!(/,/, ".") end super(value) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb index bf565bcf47..7c764e7287 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb @@ -1,4 +1,6 @@ module ActiveRecord + Point = Struct.new(:x, :y) + module ConnectionAdapters module PostgreSQL module OID # :nodoc: @@ -12,20 +14,34 @@ module ActiveRecord def cast(value) case value when ::String - if value[0] == '(' && value[-1] == ')' + return if value.blank? + + if value[0] == "(" && value[-1] == ")" value = value[1...-1] end - cast(value.split(',')) + x, y = value.split(",") + build_point(x, y) when ::Array - value.map { |v| Float(v) } + build_point(*value) else value end end def serialize(value) - if value.is_a?(::Array) - "(#{number_for_point(value[0])},#{number_for_point(value[1])})" + case value + when ActiveRecord::Point + "(#{number_for_point(value.x)},#{number_for_point(value.y)})" + when ::Array + serialize(build_point(*value)) + else + super + end + end + + def type_cast_for_schema(value) + if ActiveRecord::Point === value + [value.x, value.y] else super end @@ -33,9 +49,13 @@ module ActiveRecord private - def number_for_point(number) - number.to_s.gsub(/\.0$/, '') - end + def number_for_point(number) + number.to_s.gsub(/\.0$/, "") + end + + def build_point(x, y) + ActiveRecord::Point.new(Float(x), Float(y)) + 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 a8d2310035..2c714f4018 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/string/filters' +require "active_support/core_ext/string/filters" module ActiveRecord module ConnectionAdapters @@ -14,11 +14,11 @@ module ActiveRecord end def type_cast_for_schema(value) - value.inspect.gsub('Infinity', '::Float::INFINITY') + value.inspect.gsub("Infinity", "::Float::INFINITY") end def cast_value(value) - return if value == 'empty' + return if value == "empty" return value unless value.is_a?(::String) extracted = extract_bounds(value) @@ -55,37 +55,37 @@ module ActiveRecord private - def type_cast_single(value) - infinity?(value) ? value : @subtype.deserialize(value) - end + def type_cast_single(value) + infinity?(value) ? value : @subtype.deserialize(value) + end - def type_cast_single_for_database(value) - infinity?(value) ? '' : @subtype.serialize(value) - end + def type_cast_single_for_database(value) + infinity?(value) ? "" : @subtype.serialize(value) + end - def extract_bounds(value) - from, to = value[1..-2].split(',') - { - from: (value[1] == ',' || from == '-infinity') ? infinity(negative: true) : from, - to: (value[-2] == ',' || to == 'infinity') ? infinity : to, - exclude_start: (value[0] == '('), - exclude_end: (value[-1] == ')') - } - end + def extract_bounds(value) + from, to = value[1..-2].split(",") + { + from: (value[1] == "," || from == "-infinity") ? infinity(negative: true) : from, + to: (value[-2] == "," || to == "infinity") ? infinity : to, + exclude_start: (value[0] == "("), + exclude_end: (value[-1] == ")") + } + end - def infinity(negative: false) - if subtype.respond_to?(:infinity) - subtype.infinity(negative: negative) - elsif negative - -::Float::INFINITY - else - ::Float::INFINITY + def infinity(negative: false) + if subtype.respond_to?(:infinity) + subtype.infinity(negative: negative) + elsif negative + -::Float::INFINITY + else + ::Float::INFINITY + end end - end - def infinity?(value) - value.respond_to?(:infinite?) && value.infinite? - end + def infinity?(value) + value.respond_to?(:infinite?) && value.infinite? + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb index 6155e53632..d9ae1aa7a2 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb @@ -13,13 +13,13 @@ module ActiveRecord end def run(records) - nodes = records.reject { |row| @store.key? row['oid'].to_i } - mapped, nodes = nodes.partition { |row| @store.key? row['typname'] } - ranges, nodes = nodes.partition { |row| row['typtype'] == 'r'.freeze } - enums, nodes = nodes.partition { |row| row['typtype'] == 'e'.freeze } - domains, nodes = nodes.partition { |row| row['typtype'] == 'd'.freeze } - arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in'.freeze } - composites, nodes = nodes.partition { |row| row['typelem'].to_i != 0 } + nodes = records.reject { |row| @store.key? row["oid"].to_i } + mapped, nodes = nodes.partition { |row| @store.key? row["typname"] } + ranges, nodes = nodes.partition { |row| row["typtype"] == "r".freeze } + enums, nodes = nodes.partition { |row| row["typtype"] == "e".freeze } + domains, nodes = nodes.partition { |row| row["typtype"] == "d".freeze } + arrays, nodes = nodes.partition { |row| row["typinput"] == "array_in".freeze } + composites, nodes = nodes.partition { |row| row["typelem"].to_i != 0 } mapped.each { |row| register_mapped_type(row) } enums.each { |row| register_enum_type(row) } @@ -42,66 +42,66 @@ module ActiveRecord end private - def register_mapped_type(row) - alias_type row['oid'], row['typname'] - end + def register_mapped_type(row) + alias_type row["oid"], row["typname"] + end - def register_enum_type(row) - register row['oid'], OID::Enum.new - end + def register_enum_type(row) + register row["oid"], OID::Enum.new + end - def register_array_type(row) - register_with_subtype(row['oid'], row['typelem'].to_i) do |subtype| - OID::Array.new(subtype, row['typdelim']) + def register_array_type(row) + register_with_subtype(row["oid"], row["typelem"].to_i) do |subtype| + OID::Array.new(subtype, row["typdelim"]) + end end - end - def register_range_type(row) - register_with_subtype(row['oid'], row['rngsubtype'].to_i) do |subtype| - OID::Range.new(subtype, row['typname'].to_sym) + def register_range_type(row) + register_with_subtype(row["oid"], row["rngsubtype"].to_i) do |subtype| + OID::Range.new(subtype, row["typname"].to_sym) + end end - end - def register_domain_type(row) - if base_type = @store.lookup(row["typbasetype"].to_i) - register row['oid'], base_type - else - warn "unknown base type (OID: #{row["typbasetype"]}) for domain #{row["typname"]}." + def register_domain_type(row) + if base_type = @store.lookup(row["typbasetype"].to_i) + register row["oid"], base_type + else + warn "unknown base type (OID: #{row["typbasetype"]}) for domain #{row["typname"]}." + end end - end - def register_composite_type(row) - if subtype = @store.lookup(row['typelem'].to_i) - register row['oid'], OID::Vector.new(row['typdelim'], subtype) + def register_composite_type(row) + if subtype = @store.lookup(row["typelem"].to_i) + register row["oid"], OID::Vector.new(row["typdelim"], subtype) + end end - end - def register(oid, oid_type = nil, &block) - oid = assert_valid_registration(oid, oid_type || block) - if block_given? - @store.register_type(oid, &block) - else - @store.register_type(oid, oid_type) + def register(oid, oid_type = nil, &block) + oid = assert_valid_registration(oid, oid_type || block) + if block_given? + @store.register_type(oid, &block) + else + @store.register_type(oid, oid_type) + end end - end - def alias_type(oid, target) - oid = assert_valid_registration(oid, target) - @store.alias_type(oid, target) - end + def alias_type(oid, target) + oid = assert_valid_registration(oid, target) + @store.alias_type(oid, target) + end - def register_with_subtype(oid, target_oid) - if @store.key?(target_oid) - register(oid) do |_, *args| - yield @store.lookup(target_oid, *args) + def register_with_subtype(oid, target_oid) + if @store.key?(target_oid) + register(oid) do |_, *args| + yield @store.lookup(target_oid, *args) + end end end - end - def assert_valid_registration(oid, oid_type) - raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil? - oid.to_i - end + def assert_valid_registration(oid, oid_type) + raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil? + oid.to_i + end 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 c1c77a967e..b5031d890f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -27,8 +27,8 @@ module ActiveRecord # - schema_name."table.name" # - "schema.name".table_name # - "schema.name"."table.name" - def quote_table_name(name) - Utils.extract_schema_qualified_name(name.to_s).quoted + def quote_table_name(name) # :nodoc: + @quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze end # Quotes schema names for use in SQL queries. @@ -41,8 +41,8 @@ module ActiveRecord end # Quotes column names for use in SQL queries. - def quote_column_name(name) #:nodoc: - PGconn.quote_ident(name.to_s) + def quote_column_name(name) # :nodoc: + @quoted_column_names[name] ||= PGconn.quote_ident(super).freeze end # Quote date/time values for use in SQL input. @@ -58,7 +58,7 @@ module ActiveRecord def quote_default_expression(value, column) # :nodoc: if value.is_a?(Proc) value.call - elsif column.type == :uuid && value =~ /\(\)/ + elsif column.type == :uuid && value.include?("()") value # Does not quote function default values for UUID columns elsif column.respond_to?(:array?) value = type_cast_from_column(column, value) @@ -74,42 +74,42 @@ module ActiveRecord private - 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 - if value.binary? - "B'#{value}'" - elsif value.hex? - "X'#{value}'" - end - when Float - if value.infinite? || value.nan? - "'#{value}'" + 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 + if value.binary? + "B'#{value}'" + elsif value.hex? + "X'#{value}'" + end + when Float + if value.infinite? || value.nan? + "'#{value}'" + else + super + end else super end - else - super end - end - def _type_cast(value) - case value - when Type::Binary::Data - # Return a bind param hash with format as binary. - # See http://deveiate.org/code/pg/PGconn.html#method-i-exec_prepared-doc - # for more information - { value: value.to_s, format: 1 } - when OID::Xml::Data, OID::Bit::Data - value.to_s - else - super + def _type_cast(value) + case value + when Type::Binary::Data + # Return a bind param hash with format as binary. + # See http://deveiate.org/code/pg/PGconn.html#method-i-exec_prepared-doc + # for more information + { value: value.to_s, format: 1 } + when OID::Xml::Data, OID::Bit::Data + value.to_s + else + super + end 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 6399bddbee..a11dbe7dce 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb @@ -31,7 +31,7 @@ module ActiveRecord # a record (as primary keys cannot be +nil+). This might be done via the # +SecureRandom.uuid+ method and a +before_save+ callback, for instance. def primary_key(name, type = :primary_key, **options) - options[:default] = options.fetch(:default, 'uuid_generate_v4()') if type == :uuid + options[:default] = options.fetch(:default, "uuid_generate_v4()") if type == :uuid super end 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 b82bdb8b0c..c20baf655c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb @@ -3,16 +3,9 @@ module ActiveRecord module PostgreSQL module ColumnDumper def column_spec_for_primary_key(column) - spec = {} - if column.serial? - return unless column.bigint? - spec[:id] = ':bigserial' - elsif column.type == :uuid - spec[:id] = ':uuid' - spec[:default] = schema_default(column) || 'nil' - else - spec[:id] = schema_type(column).inspect - spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) }) + spec = super + if schema_type(column) == :uuid + spec[:default] ||= "nil" end spec end @@ -20,7 +13,7 @@ module ActiveRecord # Adds +:array+ option to the default set def prepare_column_options(column) spec = super - spec[:array] = 'true' if column.array? + spec[:array] = "true" if column.array? spec end @@ -31,19 +24,23 @@ module ActiveRecord private - def schema_type(column) - return super unless column.serial? + def default_primary_key?(column) + schema_type(column) == :serial + end + + def schema_type(column) + return super unless column.serial? - if column.bigint? - :bigserial - else - :serial + if column.bigint? + :bigserial + else + :serial + end end - end - def schema_expression(column) - super unless column.serial? - end + def schema_expression(column) + super unless column.serial? + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index 67e727d8ed..69f797da3a 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -1,20 +1,22 @@ +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 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]}\"" + def add_column_options!(sql, options) + if options[:collation] + sql << " COLLATE \"#{options[:collation]}\"" + end + super end - super - end end module SchemaStatements @@ -34,26 +36,26 @@ module ActiveRecord # create_database config[:database], config # create_database 'foo_development', encoding: 'unicode' def create_database(name, options = {}) - options = { encoding: 'utf8' }.merge!(options.symbolize_keys) + options = { encoding: "utf8" }.merge!(options.symbolize_keys) option_string = options.inject("") do |memo, (key, value)| memo += case key - when :owner - " OWNER = \"#{value}\"" - when :template - " TEMPLATE = \"#{value}\"" - when :encoding - " ENCODING = '#{value}'" - when :collation - " LC_COLLATE = '#{value}'" - when :ctype - " LC_CTYPE = '#{value}'" - when :tablespace - " TABLESPACE = \"#{value}\"" - when :connection_limit - " CONNECTION LIMIT = #{value}" + when :owner + " OWNER = \"#{value}\"" + when :template + " TEMPLATE = \"#{value}\"" + when :encoding + " ENCODING = '#{value}'" + when :collation + " LC_COLLATE = '#{value}'" + when :ctype + " LC_CTYPE = '#{value}'" + when :tablespace + " TABLESPACE = \"#{value}\"" + when :connection_limit + " CONNECTION LIMIT = #{value}" else - "" + "" end end @@ -76,15 +78,15 @@ module ActiveRecord MSG end - select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", 'SCHEMA') + select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", "SCHEMA") end def data_sources # :nodoc - select_values(<<-SQL, 'SCHEMA') + 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 ('r', 'v','m') -- (r)elation/table, (v)iew, (m)aterialized view + WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view AND n.nspname = ANY (current_schemas(false)) SQL end @@ -106,18 +108,18 @@ module ActiveRecord name = Utils.extract_schema_qualified_name(name.to_s) return false unless name.identifier - select_value(<<-SQL, 'SCHEMA').to_i > 0 - SELECT COUNT(*) + 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 = '#{name.identifier}' - AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'} + AND c.relname = #{quote(name.identifier)} + AND n.nspname = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"} SQL end def views # :nodoc: - select_values(<<-SQL, 'SCHEMA') + select_values(<<-SQL, "SCHEMA") SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace @@ -130,13 +132,13 @@ module ActiveRecord name = Utils.extract_schema_qualified_name(view_name.to_s) return false unless name.identifier - select_values(<<-SQL, 'SCHEMA').any? + select_values(<<-SQL, "SCHEMA").any? SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view - AND c.relname = '#{name.identifier}' - AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'} + AND c.relname = #{quote(name.identifier)} + AND n.nspname = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"} SQL end @@ -146,7 +148,7 @@ module ActiveRecord # Returns true if schema exists. def schema_exists?(name) - select_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = '#{name}'", 'SCHEMA').to_i > 0 + select_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = '#{name}'", "SCHEMA").to_i > 0 end # Verifies existence of an index with a given name. @@ -154,7 +156,7 @@ module ActiveRecord table = Utils.extract_schema_qualified_name(table_name.to_s) index = Utils.extract_schema_qualified_name(index_name.to_s) - select_value(<<-SQL, 'SCHEMA').to_i > 0 + select_value(<<-SQL, "SCHEMA").to_i > 0 SELECT COUNT(*) FROM pg_class t INNER JOIN pg_index d ON t.oid = d.indrelid @@ -171,8 +173,12 @@ module ActiveRecord def indexes(table_name, name = nil) table = Utils.extract_schema_qualified_name(table_name.to_s) - result = query(<<-SQL, 'SCHEMA') - SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid + result = query(<<-SQL, "SCHEMA") + SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid, + pg_catalog.obj_description(i.oid, 'pg_class') AS comment, + (SELECT COUNT(*) FROM pg_opclass o + JOIN (SELECT unnest(string_to_array(d.indclass::text, ' '))::int oid) c + ON o.oid = c.oid WHERE o.opcdefault = 'f') FROM pg_class t INNER JOIN pg_index d ON t.oid = d.indrelid INNER JOIN pg_class i ON d.indexrelid = i.oid @@ -190,73 +196,97 @@ module ActiveRecord indkey = row[2].split(" ").map(&:to_i) inddef = row[3] oid = row[4] + comment = row[5] + opclass = row[6] - columns = Hash[query(<<-SQL, "SCHEMA")] - SELECT a.attnum, a.attname - FROM pg_attribute a - WHERE a.attrelid = #{oid} - AND a.attnum IN (#{indkey.join(",")}) - SQL + using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/).flatten - column_names = columns.values_at(*indkey).compact + if indkey.include?(0) || opclass > 0 + columns = expressions + else + columns = Hash[query(<<-SQL.strip_heredoc, "SCHEMA")].values_at(*indkey).compact + SELECT a.attnum, a.attname + FROM pg_attribute a + WHERE a.attrelid = #{oid} + AND a.attnum IN (#{indkey.join(",")}) + SQL - unless column_names.empty? # add info on sort order for columns (only desc order is explicitly specified, asc is the default) - desc_order_columns = inddef.scan(/(\w+) DESC/).flatten - orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {} - where = inddef.scan(/WHERE (.+)$/).flatten[0] - using = inddef.scan(/USING (.+?) /).flatten[0].to_sym - - IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using) + orders = Hash[ + expressions.scan(/(\w+) DESC/).flatten.map { |order_column| [order_column, :desc] } + ] end + + IndexDefinition.new(table_name, index_name, unique, columns, [], orders, where, nil, using.to_sym, comment.presence) end.compact end # Returns the list of all column definitions for a table. - def columns(table_name) - # Limit, precision, and scale are all handled by the superclass. - column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation| + def columns(table_name) # :nodoc: + table_name = table_name.to_s + column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation, comment| oid = oid.to_i fmod = fmod.to_i type_metadata = fetch_type_metadata(column_name, type, oid, fmod) default_value = extract_value_from_default(default) default_function = extract_default_function(default_value, default) - new_column(column_name, default_value, type_metadata, !notnull, default_function, collation) + new_column(column_name, default_value, type_metadata, !notnull, table_name, default_function, collation, comment: comment.presence) end end - def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc: - PostgreSQLColumn.new(name, default, sql_type_metadata, null, default_function, collation) + def new_column(*args) # :nodoc: + PostgreSQLColumn.new(*args) + end + + def table_options(table_name) # :nodoc: + if comment = table_comment(table_name) + { comment: comment } + end + end + + # Returns a comment stored in database for given table + def table_comment(table_name) # :nodoc: + name = Utils.extract_schema_qualified_name(table_name.to_s) + if name.identifier + select_value(<<-SQL.strip_heredoc, "SCHEMA") + SELECT pg_catalog.obj_description(c.oid, 'pg_class') + FROM pg_catalog.pg_class c + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relname = #{quote(name.identifier)} + AND c.relkind IN ('r') -- (r)elation/table + AND n.nspname = #{name.schema ? quote(name.schema) : 'ANY (current_schemas(false))'} + SQL + end end # Returns the current database name. def current_database - select_value('select current_database()', 'SCHEMA') + select_value("select current_database()", "SCHEMA") end # Returns the current schema name. def current_schema - select_value('SELECT current_schema', 'SCHEMA') + select_value("SELECT current_schema", "SCHEMA") end # Returns the current database encoding format. def encoding - select_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA') + select_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname LIKE '#{current_database}'", "SCHEMA") end # Returns the current database collation. def collation - select_value("SELECT datcollate FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA') + select_value("SELECT datcollate FROM pg_database WHERE datname LIKE '#{current_database}'", "SCHEMA") end # Returns the current database ctype. def ctype - select_value("SELECT datctype FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA') + select_value("SELECT datctype FROM pg_database WHERE datname LIKE '#{current_database}'", "SCHEMA") end # Returns an array of schema names. def schema_names - select_values(<<-SQL, 'SCHEMA') + select_values(<<-SQL, "SCHEMA") SELECT nspname FROM pg_namespace WHERE nspname !~ '^pg_.*' @@ -266,7 +296,7 @@ module ActiveRecord end # Creates a schema for the given schema name. - def create_schema schema_name + def create_schema(schema_name) execute "CREATE SCHEMA #{quote_schema_name(schema_name)}" end @@ -282,37 +312,37 @@ module ActiveRecord # This should be not be called manually but set in database.yml. def schema_search_path=(schema_csv) if schema_csv - execute("SET search_path TO #{schema_csv}", 'SCHEMA') + execute("SET search_path TO #{schema_csv}", "SCHEMA") @schema_search_path = schema_csv end end # Returns the active schema search path. def schema_search_path - @schema_search_path ||= select_value('SHOW search_path', 'SCHEMA') + @schema_search_path ||= select_value("SHOW search_path", "SCHEMA") end # Returns the current client message level. def client_min_messages - select_value('SHOW client_min_messages', 'SCHEMA') + select_value("SHOW client_min_messages", "SCHEMA") end # Set the client message level. def client_min_messages=(level) - execute("SET client_min_messages TO '#{level}'", 'SCHEMA') + execute("SET client_min_messages TO '#{level}'", "SCHEMA") end # Returns the sequence name for a table's primary key or some other specified key. - def default_sequence_name(table_name, pk = nil) #:nodoc: - result = serial_sequence(table_name, pk || 'id') + def default_sequence_name(table_name, pk = "id") #:nodoc: + result = serial_sequence(table_name, pk) return nil unless result Utils.extract_schema_qualified_name(result).to_s rescue ActiveRecord::StatementInvalid - PostgreSQL::Name.new(nil, "#{table_name}_#{pk || 'id'}_seq").to_s + PostgreSQL::Name.new(nil, "#{table_name}_#{pk}_seq").to_s end def serial_sequence(table, column) - select_value("SELECT pg_get_serial_sequence('#{table}', '#{column}')", 'SCHEMA') + select_value("SELECT pg_get_serial_sequence('#{table}', '#{column}')", "SCHEMA") end # Sets the sequence of a table's primary key to the specified value. @@ -323,16 +353,16 @@ module ActiveRecord if sequence quoted_sequence = quote_table_name(sequence) - select_value("SELECT setval('#{quoted_sequence}', #{value})", 'SCHEMA') + select_value("SELECT setval('#{quoted_sequence}', #{value})", "SCHEMA") else - @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger + @logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger end end end # Resets the sequence of a table's primary key to the maximum value. def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc: - unless pk and sequence + unless pk && sequence default_pk, default_sequence = pk_and_sequence_for(table) pk ||= default_pk @@ -340,13 +370,13 @@ module ActiveRecord end if @logger && pk && !sequence - @logger.warn "#{table} has primary key #{pk} with no default sequence" + @logger.warn "#{table} has primary key #{pk} with no default sequence." end if pk && sequence quoted_sequence = quote_table_name(sequence) - select_value(<<-end_sql, 'SCHEMA') + select_value(<<-end_sql, "SCHEMA") SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false) end_sql end @@ -356,7 +386,7 @@ module ActiveRecord def pk_and_sequence_for(table) #:nodoc: # First try looking for a sequence with a dependency on the # given table's primary key. - result = query(<<-end_sql, 'SCHEMA')[0] + result = query(<<-end_sql, "SCHEMA")[0] SELECT attr.attname, nsp.nspname, seq.relname FROM pg_class seq, pg_attribute attr, @@ -375,8 +405,8 @@ module ActiveRecord AND dep.refobjid = '#{quote_table_name(table)}'::regclass end_sql - if result.nil? or result.empty? - result = query(<<-end_sql, 'SCHEMA')[0] + if result.nil? || result.empty? + result = query(<<-end_sql, "SCHEMA")[0] SELECT attr.attname, nsp.nspname, CASE WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL @@ -407,11 +437,11 @@ module ActiveRecord end def primary_keys(table_name) # :nodoc: - select_values(<<-SQL.strip_heredoc, 'SCHEMA') + select_values(<<-SQL.strip_heredoc, "SCHEMA") WITH pk_constraint AS ( SELECT conrelid, unnest(conkey) AS connum FROM pg_constraint WHERE contype = 'p' - AND conrelid = '#{quote_table_name(table_name)}'::regclass + AND conrelid = #{quote(quote_table_name(table_name))}::regclass ), cons AS ( SELECT conrelid, connum, row_number() OVER() AS rownum FROM pk_constraint ) @@ -445,6 +475,7 @@ module ActiveRecord def add_column(table_name, column_name, type, options = {}) #:nodoc: clear_cache! super + change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment) end def change_column(table_name, column_name, type, options = {}) #:nodoc: @@ -466,6 +497,7 @@ module ActiveRecord change_column_default(table_name, column_name, options[:default]) if options_include_default?(options) change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null) + change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment) end # Changes the default value of a table column. @@ -494,6 +526,18 @@ module ActiveRecord execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL") end + # Adds comment for given table column or drops it if +comment+ is a +nil+ + def change_column_comment(table_name, column_name, comment) # :nodoc: + clear_cache! + execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS #{quote(comment)}" + end + + # Adds comment for given table or drops it if +comment+ is a +nil+ + def change_table_comment(table_name, comment) # :nodoc: + clear_cache! + execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS #{quote(comment)}" + end + # Renames a column in a table. def rename_column(table_name, column_name, new_column_name) #:nodoc: clear_cache! @@ -502,8 +546,10 @@ module ActiveRecord end def add_index(table_name, column_name, options = {}) #:nodoc: - index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options) - execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}" + index_name, index_type, index_columns, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options) + execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}").tap do + execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment + end end def remove_index(table_name, options = {}) #:nodoc: @@ -539,7 +585,7 @@ module ActiveRecord end def foreign_keys(table_name) - fk_info = select_all <<-SQL.strip_heredoc + fk_info = select_all(<<-SQL.strip_heredoc, "SCHEMA") SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete FROM pg_constraint c JOIN pg_class t1 ON c.conrelid = t1.oid @@ -555,23 +601,23 @@ module ActiveRecord fk_info.map do |row| options = { - column: row['column'], - name: row['name'], - primary_key: row['primary_key'] + column: row["column"], + name: row["name"], + primary_key: row["primary_key"] } - options[:on_delete] = extract_foreign_key_action(row['on_delete']) - options[:on_update] = extract_foreign_key_action(row['on_update']) + options[:on_delete] = extract_foreign_key_action(row["on_delete"]) + options[:on_update] = extract_foreign_key_action(row["on_update"]) - ForeignKeyDefinition.new(table_name, row['to_table'], options) + ForeignKeyDefinition.new(table_name, row["to_table"], options) end end def extract_foreign_key_action(specifier) # :nodoc: case specifier - when 'c'; :cascade - when 'n'; :nullify - when 'r'; :restrict + when "c"; :cascade + when "n"; :nullify + when "r"; :restrict end end @@ -581,48 +627,49 @@ module ActiveRecord # Maps logical Rails types to PostgreSQL-specific data types. def type_to_sql(type, limit = nil, precision = nil, scale = nil, array = nil) - sql = case type.to_s - when 'binary' - # PostgreSQL doesn't support limits on binary (bytea) columns. - # The hard limit is 1GB, because of a 32-bit size field, and TOAST. - case limit - when nil, 0..0x3fffffff; super(type) - else raise(ActiveRecordError, "No binary type has byte size #{limit}.") - end - when 'text' - # PostgreSQL doesn't support limits on text columns. - # The hard limit is 1GB, according to section 8.3 in the manual. - case limit - when nil, 0..0x3fffffff; super(type) - else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.") - end - when 'integer' - case limit - when 1, 2; 'smallint' - when nil, 3, 4; 'integer' - when 5..8; 'bigint' - else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.") + sql = \ + case type.to_s + when "binary" + # PostgreSQL doesn't support limits on binary (bytea) columns. + # The hard limit is 1GB, because of a 32-bit size field, and TOAST. + case limit + when nil, 0..0x3fffffff; super(type) + else raise(ActiveRecordError, "No binary type has byte size #{limit}.") + end + when "text" + # PostgreSQL doesn't support limits on text columns. + # The hard limit is 1GB, according to section 8.3 in the manual. + case limit + when nil, 0..0x3fffffff; super(type) + else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.") + end + when "integer" + case limit + when 1, 2; "smallint" + when nil, 3, 4; "integer" + when 5..8; "bigint" + else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead.") + end + else + super(type, limit, precision, scale) end - else - super(type, limit, precision, scale) - end - sql << '[]' if array && type != :primary_key + sql << "[]" if array && type != :primary_key sql end # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and # requires that the ORDER BY include the distinct column. def columns_for_distinct(columns, orders) #:nodoc: - order_columns = orders.reject(&:blank?).map{ |s| + order_columns = orders.reject(&:blank?).map { |s| # Convert Arel node to string s = s.to_sql unless s.is_a?(String) # Remove any ASC/DESC modifiers - s.gsub(/\s+(?:ASC|DESC)\b/i, '') - .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, '') + s.gsub(/\s+(?:ASC|DESC)\b/i, "") + .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "") }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" } - [super, *order_columns].join(', ') + [super, *order_columns].join(", ") end def fetch_type_metadata(column_name, sql_type, oid, fmod) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb index b2c49989a4..311988625f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb @@ -8,7 +8,7 @@ module ActiveRecord @type_metadata = type_metadata @oid = oid @fmod = fmod - @array = /\[\]$/ === type_metadata.sql_type + @array = /\[\]$/.match?(type_metadata.sql_type) end def sql_type @@ -27,9 +27,9 @@ module ActiveRecord protected - def attributes_for_hash - [self.class, @type_metadata, oid, fmod] - end + def attributes_for_hash + [self.class, @type_metadata, oid, fmod] + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index beaeef3c78..710b5cd887 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -1,6 +1,6 @@ # Make sure we're using pg high enough for type casts and Ruby 2.2+ compatibility -gem 'pg', '~> 0.18' -require 'pg' +gem "pg", "~> 0.18" +require "pg" require "active_record/connection_adapters/abstract_adapter" require "active_record/connection_adapters/postgresql/column" @@ -16,8 +16,6 @@ require "active_record/connection_adapters/postgresql/type_metadata" require "active_record/connection_adapters/postgresql/utils" require "active_record/connection_adapters/statement_pool" -require 'ipaddr' - module ActiveRecord module ConnectionHandling # :nodoc: # Establishes a connection to the database that's used by all Active Record objects @@ -69,7 +67,7 @@ module ActiveRecord # In addition, default connection parameters of libpq can be set per environment variables. # See http://www.postgresql.org/docs/current/static/libpq-envars.html . class PostgreSQLAdapter < AbstractAdapter - ADAPTER_NAME = 'PostgreSQL'.freeze + ADAPTER_NAME = "PostgreSQL".freeze NATIVE_DATABASE_TYPES = { primary_key: "serial primary key", @@ -119,12 +117,15 @@ module ActiveRecord include PostgreSQL::SchemaStatements include PostgreSQL::DatabaseStatements include PostgreSQL::ColumnDumper - include Savepoints def schema_creation # :nodoc: PostgreSQL::SchemaCreation.new self end + def arel_visitor # :nodoc: + Arel::Visitors::PostgreSQL.new(self) + end + # Returns true, since this connection adapter supports prepared statement # caching. def supports_statement_cache? @@ -139,6 +140,10 @@ module ActiveRecord true end + def supports_expression_index? + true + end + def supports_transaction_isolation? true end @@ -159,8 +164,16 @@ module ActiveRecord postgresql_version >= 90200 end + def supports_comments? + true + end + + def supports_savepoints? + true + end + def index_algorithms - { concurrently: 'CONCURRENTLY' } + { concurrently: "CONCURRENTLY" } end class StatementPool < ConnectionAdapters::StatementPool @@ -195,14 +208,6 @@ module ActiveRecord def initialize(connection, logger, connection_parameters, config) super(connection, logger, config) - @visitor = Arel::Visitors::PostgreSQL.new self - if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) - @prepared_statements = true - @visitor.extend(DetermineIfPreparableVisitor) - else - @prepared_statements = false - end - @connection_parameters = connection_parameters # @local_tz is initialized as nil to avoid warnings when connect tries to use it @@ -212,7 +217,7 @@ module ActiveRecord connect add_pg_encoders @statements = StatementPool.new @connection, - self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }) + self.class.type_cast_config_to_integer(config[:statement_limit]) if postgresql_version < 90100 raise "Your version of PostgreSQL (#{postgresql_version}) is too old. Active Record supports PostgreSQL >= 9.1." @@ -222,7 +227,7 @@ module ActiveRecord @type_map = Type::HashLookupTypeMap.new initialize_type_map(type_map) - @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"] + @local_tz = execute("SHOW TIME ZONE", "SCHEMA").first["TimeZone"] @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true end @@ -237,7 +242,7 @@ module ActiveRecord # Is this connection alive and ready for queries? def active? - @connection.query 'SELECT 1' + @connection.query "SELECT 1" true rescue PGError false @@ -254,9 +259,9 @@ module ActiveRecord clear_cache! reset_transaction unless @connection.transaction_status == ::PG::PQTRANS_IDLE - @connection.query 'ROLLBACK' + @connection.query "ROLLBACK" end - @connection.query 'DISCARD ALL' + @connection.query "DISCARD ALL" configure_connection end @@ -282,7 +287,7 @@ module ActiveRecord end def set_standard_conforming_strings - execute('SET standard_conforming_strings = on', 'SCHEMA') + execute("SET standard_conforming_strings = on", "SCHEMA") end def supports_ddl_transactions? @@ -339,7 +344,7 @@ module ActiveRecord def extension_enabled?(name) if supports_extensions? res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled", - 'SCHEMA' + "SCHEMA" res.cast_values.first end end @@ -354,7 +359,7 @@ module ActiveRecord # Returns the configured supported identifier length supported by PostgreSQL def table_alias_length - @table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i + @table_alias_length ||= query("SHOW max_identifier_length", "SCHEMA")[0][0].to_i end # Set the authorized user for this session @@ -376,7 +381,7 @@ module ActiveRecord end def lookup_cast_type(sql_type) # :nodoc: - oid = execute("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").first['oid'].to_i + oid = execute("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").first["oid"].to_i super(oid) end @@ -398,8 +403,11 @@ module ActiveRecord protected # See http://www.postgresql.org/docs/current/static/errcodes-appendix.html + VALUE_LIMIT_VIOLATION = "22001" FOREIGN_KEY_VIOLATION = "23503" UNIQUE_VIOLATION = "23505" + SERIALIZATION_FAILURE = "40001" + DEADLOCK_DETECTED = "40P01" def translate_exception(exception, message) return exception unless exception.respond_to?(:result) @@ -409,6 +417,12 @@ module ActiveRecord RecordNotUnique.new(message) when FOREIGN_KEY_VIOLATION InvalidForeignKey.new(message) + when VALUE_LIMIT_VIOLATION + ValueTooLong.new(message) + when SERIALIZATION_FAILURE + SerializationFailure.new(message) + when DEADLOCK_DETECTED + Deadlocked.new(message) else super end @@ -416,65 +430,65 @@ module ActiveRecord private - def get_oid_type(oid, fmod, column_name, sql_type = '') # :nodoc: + def get_oid_type(oid, fmod, column_name, sql_type = "") # :nodoc: if !type_map.key?(oid) load_additional_types(type_map, [oid]) end type_map.fetch(oid, fmod, sql_type) { warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String." - Type::Value.new.tap do |cast_type| + Type.default_value.tap do |cast_type| type_map.register_type(oid, cast_type) end } end def initialize_type_map(m) # :nodoc: - 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 'float4', Type::Float.new - m.alias_type 'float8', 'float4' - m.register_type 'text', Type::Text.new - register_class_with_limit m, 'varchar', Type::String - m.alias_type 'char', 'varchar' - m.alias_type 'name', 'varchar' - m.alias_type 'bpchar', 'varchar' - m.register_type 'bool', Type::Boolean.new - register_class_with_limit m, 'bit', OID::Bit - register_class_with_limit m, 'varbit', OID::BitVarying - m.alias_type 'timestamptz', 'timestamp' - m.register_type 'date', Type::Date.new - - m.register_type 'money', OID::Money.new - m.register_type 'bytea', OID::Bytea.new - m.register_type 'point', OID::Point.new - m.register_type 'hstore', OID::Hstore.new - m.register_type 'json', OID::Json.new - m.register_type 'jsonb', OID::Jsonb.new - m.register_type 'cidr', OID::Cidr.new - m.register_type 'inet', OID::Inet.new - m.register_type 'uuid', OID::Uuid.new - m.register_type 'xml', OID::Xml.new - m.register_type 'tsvector', OID::SpecializedString.new(:tsvector) - m.register_type 'macaddr', OID::SpecializedString.new(:macaddr) - m.register_type 'citext', OID::SpecializedString.new(:citext) - m.register_type 'ltree', OID::SpecializedString.new(:ltree) - m.register_type 'line', OID::SpecializedString.new(:line) - m.register_type 'lseg', OID::SpecializedString.new(:lseg) - m.register_type 'box', OID::SpecializedString.new(:box) - m.register_type 'path', OID::SpecializedString.new(:path) - m.register_type 'polygon', OID::SpecializedString.new(:polygon) - m.register_type 'circle', OID::SpecializedString.new(:circle) + 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 "float4", Type::Float.new + m.alias_type "float8", "float4" + m.register_type "text", Type::Text.new + register_class_with_limit m, "varchar", Type::String + m.alias_type "char", "varchar" + m.alias_type "name", "varchar" + m.alias_type "bpchar", "varchar" + m.register_type "bool", Type::Boolean.new + register_class_with_limit m, "bit", OID::Bit + register_class_with_limit m, "varbit", OID::BitVarying + m.alias_type "timestamptz", "timestamp" + m.register_type "date", Type::Date.new + + m.register_type "money", OID::Money.new + m.register_type "bytea", OID::Bytea.new + m.register_type "point", OID::Point.new + m.register_type "hstore", OID::Hstore.new + m.register_type "json", OID::Json.new + m.register_type "jsonb", OID::Jsonb.new + m.register_type "cidr", OID::Cidr.new + m.register_type "inet", OID::Inet.new + m.register_type "uuid", OID::Uuid.new + m.register_type "xml", OID::Xml.new + m.register_type "tsvector", OID::SpecializedString.new(:tsvector) + m.register_type "macaddr", OID::SpecializedString.new(:macaddr) + m.register_type "citext", OID::SpecializedString.new(:citext) + m.register_type "ltree", OID::SpecializedString.new(:ltree) + m.register_type "line", OID::SpecializedString.new(:line) + m.register_type "lseg", OID::SpecializedString.new(:lseg) + m.register_type "box", OID::SpecializedString.new(:box) + m.register_type "path", OID::SpecializedString.new(:path) + 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.alias_type "interval", "varchar" - register_class_with_precision m, 'time', Type::Time - register_class_with_precision m, 'timestamp', OID::DateTime + register_class_with_precision m, "time", Type::Time + register_class_with_precision m, "timestamp", OID::DateTime - m.register_type 'numeric' do |_, fmod, sql_type| + m.register_type "numeric" do |_, fmod, sql_type| precision = extract_precision(sql_type) scale = extract_scale(sql_type) @@ -512,26 +526,26 @@ module ActiveRecord def extract_value_from_default(default) # :nodoc: case default # Quoted types - when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m - # The default 'now'::date is CURRENT_DATE - if $1 == "now".freeze && $2 == "date".freeze - nil - else - $1.gsub("''".freeze, "'".freeze) - end + when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m + # The default 'now'::date is CURRENT_DATE + if $1 == "now".freeze && $2 == "date".freeze + nil + else + $1.gsub("''".freeze, "'".freeze) + end # Boolean types - when 'true'.freeze, 'false'.freeze - default + when "true".freeze, "false".freeze + default # Numeric types - when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/ - $1 + when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/ + $1 # Object identifier types - when /\A-?\d+\z/ - $1 - else - # Anything else is blank, some user type, or some function - # and we can't know the value of that, so return nil. - nil + when /\A-?\d+\z/ + $1 + else + # Anything else is blank, some user type, or some function + # and we can't know the value of that, so return nil. + nil end end @@ -565,7 +579,7 @@ module ActiveRecord query += initializer.query_conditions_for_initial_load(type_map) end - execute_and_clear(query, 'SCHEMA', []) do |records| + execute_and_clear(query, "SCHEMA", []) do |records| initializer.run(records) end end @@ -586,37 +600,59 @@ module ActiveRecord end def exec_no_cache(sql, name, binds) - type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) } - log(sql, name, binds) { @connection.async_exec(sql, type_casted_binds) } + type_casted_binds = type_casted_binds(binds) + log(sql, name, binds, type_casted_binds) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @connection.async_exec(sql, type_casted_binds) + end + end end def exec_cache(sql, name, binds) stmt_key = prepare_statement(sql) - type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) } + type_casted_binds = type_casted_binds(binds) - log(sql, name, binds, stmt_key) do - @connection.exec_prepared(stmt_key, type_casted_binds) + log(sql, name, binds, type_casted_binds, stmt_key) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @connection.exec_prepared(stmt_key, type_casted_binds) + end end rescue ActiveRecord::StatementInvalid => e - pgerror = e.cause + raise unless is_cached_plan_failure?(e) - # Get the PG code for the failure. Annoyingly, the code for - # prepared statements whose return value may have changed is - # FEATURE_NOT_SUPPORTED. Check here for more details: - # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573 - begin - code = pgerror.result.result_error_field(PGresult::PG_DIAG_SQLSTATE) - rescue - raise e - end - if FEATURE_NOT_SUPPORTED == code + # Nothing we can do if we are in a transaction because all commands + # will raise InFailedSQLTransaction + if in_transaction? + raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message) + else + # outside of transactions we can simply flush this query and retry @statements.delete sql_key(sql) retry - else - raise e end end + # Annoyingly, the code for prepared statements whose return value may + # have changed is FEATURE_NOT_SUPPORTED. + # + # This covers various different error types so we need to do additional + # work to classify the exception definitively as a + # ActiveRecord::PreparedStatementCacheExpired + # + # Check here for more details: + # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573 + CACHED_PLAN_HEURISTIC = "cached plan must not change result type".freeze + def is_cached_plan_failure?(e) + pgerror = e.cause + code = pgerror.result.result_error_field(PGresult::PG_DIAG_SQLSTATE) + code == FEATURE_NOT_SUPPORTED && pgerror.message.include?(CACHED_PLAN_HEURISTIC) + rescue + false + end + + def in_transaction? + open_transactions > 0 + end + # Returns the statement identifier for the client side cache # of statements def sql_key(sql) @@ -660,7 +696,7 @@ module ActiveRecord if @config[:encoding] @connection.set_client_encoding(@config[:encoding]) end - self.client_min_messages = @config[:min_messages] || 'warning' + self.client_min_messages = @config[:min_messages] || "warning" self.schema_search_path = @config[:schema_search_path] || @config[:schema_order] # Use standard-conforming strings so we don't have to do the E'...' dance. @@ -670,33 +706,33 @@ module ActiveRecord # TIMESTAMP WITH ZONE types in UTC. # (SET TIME ZONE does not use an equals sign like other SET variables) if ActiveRecord::Base.default_timezone == :utc - execute("SET time zone 'UTC'", 'SCHEMA') + execute("SET time zone 'UTC'", "SCHEMA") elsif @local_tz - execute("SET time zone '#{@local_tz}'", 'SCHEMA') + execute("SET time zone '#{@local_tz}'", "SCHEMA") end # SET statements from :variables config hash # http://www.postgresql.org/docs/current/static/sql-set.html variables = @config[:variables] || {} variables.map do |k, v| - if v == ':default' || v == :default + if v == ":default" || v == :default # Sets the value to the global or compile default - execute("SET SESSION #{k} TO DEFAULT", 'SCHEMA') + execute("SET SESSION #{k} TO DEFAULT", "SCHEMA") elsif !v.nil? - execute("SET SESSION #{k} TO #{quote(v)}", 'SCHEMA') + execute("SET SESSION #{k} TO #{quote(v)}", "SCHEMA") end end end # Returns the current ID of a table's sequence. def last_insert_id_result(sequence_name) # :nodoc: - exec_query("SELECT currval('#{sequence_name}')", 'SQL') + exec_query("SELECT currval('#{sequence_name}')", "SQL") end # Returns the list of a table's column names, data types, and default values. # # The underlying query is roughly: - # SELECT column.name, column.type, default.value + # SELECT column.name, column.type, default.value, column.comment # FROM column LEFT JOIN default # ON column.table_id = default.table_id # AND column.num = default.column_num @@ -712,14 +748,15 @@ module ActiveRecord # - 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: - query(<<-end_sql, 'SCHEMA') + 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) + 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 - WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass + WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum end_sql @@ -730,8 +767,8 @@ module ActiveRecord $1.strip if $1 end - def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc: - PostgreSQL::TableDefinition.new(name, temporary, options, as) + def create_table_definition(*args) # :nodoc: + PostgreSQL::TableDefinition.new(*args) end def can_perform_case_insensitive_comparison_for?(column) @@ -740,10 +777,14 @@ module ActiveRecord sql = <<-end_sql SELECT exists( SELECT * FROM pg_proc + WHERE proname = 'lower' + AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector + ) OR exists( + SELECT * FROM pg_proc INNER JOIN pg_cast - ON casttarget::text::oidvector = proargtypes + ON ARRAY[casttarget]::oidvector = proargtypes WHERE proname = 'lower' - AND castsource = '#{column.sql_type}'::regtype::oid + AND castsource = #{quote column.sql_type}::regtype ) end_sql execute_and_clear(sql, "SCHEMA", []) do |result| @@ -763,13 +804,13 @@ module ActiveRecord def add_pg_decoders coders_by_name = { - 'int2' => PG::TextDecoder::Integer, - 'int4' => PG::TextDecoder::Integer, - 'int8' => PG::TextDecoder::Integer, - 'oid' => PG::TextDecoder::Integer, - 'float4' => PG::TextDecoder::Float, - 'float8' => PG::TextDecoder::Float, - 'bool' => PG::TextDecoder::Boolean, + "int2" => PG::TextDecoder::Integer, + "int4" => PG::TextDecoder::Integer, + "int8" => PG::TextDecoder::Integer, + "oid" => PG::TextDecoder::Integer, + "float4" => PG::TextDecoder::Float, + "float8" => PG::TextDecoder::Float, + "bool" => PG::TextDecoder::Boolean, } known_coder_types = coders_by_name.keys.map { |n| quote(n) } query = <<-SQL % known_coder_types.join(", ") @@ -779,7 +820,7 @@ module ActiveRecord SQL coders = execute_and_clear(query, "SCHEMA", []) do |result| result - .map { |row| construct_coder(row, coders_by_name[row['typname']]) } + .map { |row| construct_coder(row, coders_by_name[row["typname"]]) } .compact end @@ -790,7 +831,7 @@ module ActiveRecord def construct_coder(row, coder_class) return unless coder_class - coder_class.new(oid: row['oid'].to_i, name: row['typname']) + coder_class.new(oid: row["oid"].to_i, name: row["typname"]) end ActiveRecord::Type.add_modifier({ array: true }, OID::Array, adapter: :postgresql) @@ -799,7 +840,7 @@ module ActiveRecord ActiveRecord::Type.register(:bit_varying, OID::BitVarying, adapter: :postgresql) ActiveRecord::Type.register(:binary, OID::Bytea, adapter: :postgresql) ActiveRecord::Type.register(:cidr, OID::Cidr, adapter: :postgresql) - ActiveRecord::Type.register(:date_time, OID::DateTime, adapter: :postgresql) + ActiveRecord::Type.register(:datetime, OID::DateTime, adapter: :postgresql) ActiveRecord::Type.register(:decimal, OID::Decimal, adapter: :postgresql) ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql) ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql) @@ -807,8 +848,8 @@ module ActiveRecord ActiveRecord::Type.register(:json, OID::Json, adapter: :postgresql) ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql) ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql) - ActiveRecord::Type.register(:point, OID::Rails51Point, adapter: :postgresql) - ActiveRecord::Type.register(:legacy_point, OID::Point, adapter: :postgresql) + ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql) + ActiveRecord::Type.register(:legacy_point, OID::LegacyPoint, adapter: :postgresql) ActiveRecord::Type.register(:uuid, OID::Uuid, adapter: :postgresql) ActiveRecord::Type.register(:vector, OID::Vector, adapter: :postgresql) ActiveRecord::Type.register(:xml, OID::Xml, adapter: :postgresql) diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb index eee142378c..8219f132c3 100644 --- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb @@ -33,8 +33,7 @@ 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" - + deprecate table_exists?: "use #data_source_exists? instead" # Add internal cache for table with +table_name+. def add(table_name) @@ -49,7 +48,7 @@ module ActiveRecord @data_sources[name] end alias tables data_sources - deprecate :tables => "use #data_sources instead" + deprecate tables: "use #data_sources instead" # Get the columns for a table def columns(table_name) @@ -85,7 +84,7 @@ module ActiveRecord @data_sources.delete name end alias clear_table_cache! clear_data_source_cache! - deprecate :clear_table_cache! => "use #clear_data_source_cache! instead" + 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/sql_type_metadata.rb b/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb index ccb7e154ee..9e12ae0de8 100644 --- a/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb +++ b/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb @@ -24,9 +24,9 @@ module ActiveRecord protected - def attributes_for_hash - [self.class, sql_type, type, limit, precision, scale] - end + def attributes_for_hash + [self.class, sql_type, type, limit, precision, scale] + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb index a946f5ebd0..6fe3e1211e 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb @@ -10,7 +10,7 @@ module ActiveRecord # def pp(result) result.rows.map do |row| - row.join('|') + row.join("|") end.join("\n") + "\n" end end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb new file mode 100644 index 0000000000..f01ed67b0f --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb @@ -0,0 +1,48 @@ +module ActiveRecord + module ConnectionAdapters + module SQLite3 + module Quoting # :nodoc: + def quote_string(s) + @connection.class.quote(s) + end + + def quote_table_name_for_assignment(table, attr) + quote_column_name(attr) + end + + def quote_column_name(name) + @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}").freeze + end + + def quoted_time(value) + quoted_date(value) + end + + private + + def _quote(value) + if value.is_a?(Type::Binary::Data) + "x'#{value.hex}'" + else + super + end + end + + def _type_cast(value) + case value + when BigDecimal + value.to_f + when String + if value.encoding == Encoding::ASCII_8BIT + super(value.encode(Encoding::UTF_8)) + else + super + end + else + super + end + end + end + end + end +end 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 fe1dcbd710..70c0d28830 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb @@ -3,6 +3,13 @@ module ActiveRecord module SQLite3 class SchemaCreation < AbstractAdapter::SchemaCreation 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_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index c65d33ccb3..bd73668359 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -1,10 +1,11 @@ -require 'active_record/connection_adapters/abstract_adapter' -require 'active_record/connection_adapters/statement_pool' -require 'active_record/connection_adapters/sqlite3/explain_pretty_printer' -require 'active_record/connection_adapters/sqlite3/schema_creation' +require "active_record/connection_adapters/abstract_adapter" +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" -gem 'sqlite3', '~> 1.3.6' -require 'sqlite3' +gem "sqlite3", "~> 1.3.6" +require "sqlite3" module ActiveRecord module ConnectionHandling # :nodoc: @@ -17,7 +18,7 @@ module ActiveRecord # Allow database path relative to Rails.root, but only if the database # path is not the special path that tells sqlite to build a database only # in memory. - if ':memory:' != config[:database] + if ":memory:" != config[:database] config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root) dirname = File.dirname(config[:database]) Dir.mkdir(dirname) unless File.directory?(dirname) @@ -25,7 +26,7 @@ module ActiveRecord db = SQLite3::Database.new( config[:database].to_s, - :results_as_hash => true + results_as_hash: true ) db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout] @@ -48,11 +49,12 @@ module ActiveRecord # # * <tt>:database</tt> - Path to the database file. class SQLite3Adapter < AbstractAdapter - ADAPTER_NAME = 'SQLite'.freeze - include Savepoints + ADAPTER_NAME = "SQLite".freeze + + include SQLite3::Quoting NATIVE_DATABASE_TYPES = { - primary_key: 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL', + primary_key: "INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL", string: { name: "varchar" }, text: { name: "text" }, integer: { name: "integer" }, @@ -68,30 +70,24 @@ module ActiveRecord class StatementPool < ConnectionAdapters::StatementPool private - def dealloc(stmt) - stmt[:stmt].close unless stmt[:stmt].closed? - end + def dealloc(stmt) + stmt[:stmt].close unless stmt[:stmt].closed? + end end def schema_creation # :nodoc: SQLite3::SchemaCreation.new self end + def arel_visitor # :nodoc: + Arel::Visitors::SQLite.new(self) + end + def initialize(connection, logger, connection_options, config) super(connection, logger, config) @active = nil - @statements = StatementPool.new(self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })) - - @visitor = Arel::Visitors::SQLite.new self - @quoted_column_names = {} - - if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) - @prepared_statements = true - @visitor.extend(DetermineIfPreparableVisitor) - else - @prepared_statements = false - end + @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit])) end def supports_ddl_transactions? @@ -103,7 +99,7 @@ module ActiveRecord end def supports_partial_index? - sqlite_version >= '3.8.0' + sqlite_version >= "3.8.0" end # Returns true, since this connection adapter supports prepared statement @@ -133,6 +129,10 @@ module ActiveRecord true end + def supports_multi_insert? + sqlite_version >= "3.7.11" + end + def active? @active != false end @@ -154,8 +154,12 @@ module ActiveRecord true end + def valid_type?(type) + true + end + # Returns 62. SQLite supports index names up to 64 - # characters. The rest is used by rails internally to perform + # characters. The rest is used by Rails internally to perform # temporary rename operations def allowed_index_name_length index_name_length - 2 @@ -174,85 +178,49 @@ module ActiveRecord true end - # QUOTING ================================================== - - def _quote(value) # :nodoc: - case value - when Type::Binary::Data - "x'#{value.hex}'" - else - super - end - end - - def _type_cast(value) # :nodoc: - case value - when BigDecimal - value.to_f - when String - if value.encoding == Encoding::ASCII_8BIT - super(value.encode(Encoding::UTF_8)) - else - super - end - else - super - end - end - - def quote_string(s) #:nodoc: - @connection.class.quote(s) - end - - def quote_table_name_for_assignment(table, attr) - quote_column_name(attr) - end - - def quote_column_name(name) #:nodoc: - @quoted_column_names[name] ||= %Q("#{name.to_s.gsub('"', '""')}") - end - #-- # DATABASE STATEMENTS ====================================== #++ def explain(arel, binds = []) sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}" - SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', [])) + SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", [])) end def exec_query(sql, name = nil, binds = [], prepare: false) - type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) } - - log(sql, name, binds) do - # Don't cache statements if they are not prepared - unless prepare - stmt = @connection.prepare(sql) - begin - cols = stmt.columns - unless without_prepared_statement?(binds) - stmt.bind_params(type_casted_binds) + type_casted_binds = type_casted_binds(binds) + + log(sql, name, binds, type_casted_binds) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + # Don't cache statements if they are not prepared + unless prepare + stmt = @connection.prepare(sql) + begin + cols = stmt.columns + unless without_prepared_statement?(binds) + stmt.bind_params(type_casted_binds) + end + records = stmt.to_a + ensure + stmt.close end + else + cache = @statements[sql] ||= { + stmt: @connection.prepare(sql) + } + stmt = cache[:stmt] + cols = cache[:cols] ||= stmt.columns + stmt.reset! + stmt.bind_params(type_casted_binds) records = stmt.to_a - ensure - stmt.close end - stmt = records - else - cache = @statements[sql] ||= { - :stmt => @connection.prepare(sql) - } - stmt = cache[:stmt] - cols = cache[:cols] ||= stmt.columns - stmt.reset! - stmt.bind_params(type_casted_binds) end - ActiveRecord::Result.new(cols, stmt.to_a) + ActiveRecord::Result.new(cols, records) end end - def exec_delete(sql, name = 'SQL', binds = []) + def exec_delete(sql, name = "SQL", binds = []) exec_query(sql, name, binds) @connection.changes end @@ -263,23 +231,23 @@ module ActiveRecord end def execute(sql, name = nil) #:nodoc: - log(sql, name) { @connection.execute(sql) } - end - - def select_rows(sql, name = nil, binds = []) - exec_query(sql, name, binds).rows + log(sql, name) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @connection.execute(sql) + end + end end def begin_db_transaction #:nodoc: - log('begin transaction',nil) { @connection.transaction } + log("begin transaction",nil) { @connection.transaction } end def commit_db_transaction #:nodoc: - log('commit transaction',nil) { @connection.commit } + log("commit transaction",nil) { @connection.commit } end def exec_rollback_db_transaction #:nodoc: - log('rollback transaction',nil) { @connection.rollback } + log("rollback transaction",nil) { @connection.rollback } end # SCHEMA STATEMENTS ======================================== @@ -301,7 +269,7 @@ module ActiveRecord end def data_sources - select_values("SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'", 'SCHEMA') + select_values("SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'", "SCHEMA") end def table_exists?(table_name) @@ -320,11 +288,11 @@ module ActiveRecord 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? + select_values(sql, "SCHEMA").any? end def views # :nodoc: - select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", 'SCHEMA') + select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", "SCHEMA") end def view_exists?(view_name) # :nodoc: @@ -333,11 +301,12 @@ module ActiveRecord sql = "SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'" sql << " AND name = #{quote(view_name)}" - select_values(sql, 'SCHEMA').any? + select_values(sql, "SCHEMA").any? end # Returns an array of +Column+ objects for the table specified by +table_name+. - def columns(table_name) #:nodoc: + def columns(table_name) # :nodoc: + table_name = table_name.to_s table_structure(table_name).map do |field| case field["dflt_value"] when /^null$/i @@ -348,16 +317,16 @@ module ActiveRecord field["dflt_value"] = $1.gsub('""', '"') end - collation = field['collation'] - sql_type = field['type'] + collation = field["collation"] + sql_type = field["type"] type_metadata = fetch_type_metadata(sql_type) - new_column(field['name'], field['dflt_value'], type_metadata, field['notnull'].to_i == 0, nil, collation) + new_column(field["name"], field["dflt_value"], type_metadata, field["notnull"].to_i == 0, table_name, nil, collation) end end # Returns an array of indexes for the given table. def indexes(table_name, name = nil) #:nodoc: - exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", 'SCHEMA').map do |row| + exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row| sql = <<-SQL SELECT sql FROM sqlite_master @@ -367,22 +336,22 @@ module ActiveRecord FROM sqlite_temp_master WHERE name=#{quote(row['name'])} AND type='index' SQL - index_sql = exec_query(sql).first['sql'] + index_sql = exec_query(sql).first["sql"] match = /\sWHERE\s+(.+)$/i.match(index_sql) where = match[1] if match IndexDefinition.new( table_name, - row['name'], - row['unique'] != 0, + row["name"], + row["unique"] != 0, exec_query("PRAGMA index_info('#{row['name']}')", "SCHEMA").map { |col| - col['name'] + col["name"] }, nil, nil, where) end end def primary_keys(table_name) # :nodoc: - pks = table_structure(table_name).select { |f| f['pk'] > 0 } - pks.sort_by { |f| f['pk'] }.map { |f| f['name'] } + pks = table_structure(table_name).select { |f| f["pk"] > 0 } + pks.sort_by { |f| f["pk"] }.map { |f| f["name"] } end def remove_index(table_name, options = {}) #:nodoc: @@ -455,25 +424,25 @@ module ActiveRecord def rename_column(table_name, column_name, new_column_name) #:nodoc: column = column_for(table_name, column_name) - alter_table(table_name, rename: {column.name => new_column_name.to_s}) + alter_table(table_name, rename: { column.name => new_column_name.to_s }) rename_column_indexes(table_name, column.name, new_column_name) end protected def table_structure(table_name) - structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA') + 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 def alter_table(table_name, options = {}) #:nodoc: altered_table_name = "a#{table_name}" - caller = lambda {|definition| yield definition if block_given?} + caller = lambda { |definition| yield definition if block_given? } transaction do move_table(table_name, altered_table_name, - options.merge(:temporary => true)) + options.merge(temporary: true)) move_table(altered_table_name, table_name, &caller) end end @@ -497,9 +466,9 @@ module ActiveRecord next if column_name == from_primary_key @definition.column(column_name, column.type, - :limit => column.limit, :default => column.default, - :precision => column.precision, :scale => column.scale, - :null => column.null, collation: column.collation) + limit: column.limit, default: column.default, + precision: column.precision, scale: column.scale, + null: column.null, collation: column.collation) end yield @definition if block_given? end @@ -519,7 +488,7 @@ module ActiveRecord end to_column_names = columns(to).map(&:name) - columns = index.columns.map {|c| rename[c] || c }.select do |column| + columns = index.columns.map { |c| rename[c] || c }.select do |column| to_column_names.include?(column) end @@ -533,20 +502,20 @@ module ActiveRecord end def copy_table_contents(from, to, columns, rename = {}) #:nodoc: - column_mappings = Hash[columns.map {|name| [name, name]}] + column_mappings = Hash[columns.map { |name| [name, name] }] rename.each { |a| column_mappings[a.last] = a.first } from_columns = columns(from).collect(&:name) - columns = columns.find_all{|col| from_columns.include?(column_mappings[col])} + columns = columns.find_all { |col| from_columns.include?(column_mappings[col]) } from_columns_to_copy = columns.map { |col| column_mappings[col] } - quoted_columns = columns.map { |col| quote_column_name(col) } * ',' - quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ',' + quoted_columns = columns.map { |col| quote_column_name(col) } * "," + quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * "," exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns}) SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}") end def sqlite_version - @sqlite_version ||= SQLite3Adapter::Version.new(select_value('select sqlite_version(*)')) + @sqlite_version ||= SQLite3Adapter::Version.new(select_value("select sqlite_version(*)")) end def translate_exception(exception, message) @@ -567,32 +536,34 @@ module ActiveRecord def table_structure_with_collation(table_name, basic_structure) collation_hash = {} - sql = "SELECT sql FROM - (SELECT * FROM sqlite_master UNION ALL - SELECT * FROM sqlite_temp_master) - WHERE type='table' and name='#{ table_name }' \;" + sql = <<-SQL + SELECT sql FROM + (SELECT * FROM sqlite_master UNION ALL + SELECT * FROM sqlite_temp_master) + WHERE type = 'table' AND name = #{quote(table_name)} + SQL # Result will have following sample string # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, # "password_digest" varchar COLLATE "NOCASE"); - result = exec_query(sql, 'SCHEMA').first + result = exec_query(sql, "SCHEMA").first if result - # Splitting with left parantheses and picking up last will return all + # Splitting with left parentheses and picking up last will return all # columns separated with comma(,). - columns_string = result["sql"].split('(').last + columns_string = result["sql"].split("(").last - columns_string.split(',').each do |column_string| + columns_string.split(",").each do |column_string| # This regex will match the column name and collation type and will save # the value in $1 and $2 respectively. - collation_hash[$1] = $2 if (COLLATE_REGEX =~ column_string) + collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string end basic_structure.map! do |column| - column_name = column['name'] + column_name = column["name"] if collation_hash.has_key? column_name - column['collation'] = collation_hash[column_name] + column["collation"] = collation_hash[column_name] end column diff --git a/activerecord/lib/active_record/connection_adapters/statement_pool.rb b/activerecord/lib/active_record/connection_adapters/statement_pool.rb index 57463dd749..273b1b0b5c 100644 --- a/activerecord/lib/active_record/connection_adapters/statement_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/statement_pool.rb @@ -1,11 +1,13 @@ module ActiveRecord module ConnectionAdapters - class StatementPool + class StatementPool # :nodoc: include Enumerable - def initialize(max = 1000) + DEFAULT_STATEMENT_LIMIT = 1000 + + def initialize(statement_limit = nil) @cache = Hash.new { |h,pid| h[pid] = {} } - @max = max + @statement_limit = statement_limit || DEFAULT_STATEMENT_LIMIT end def each(&block) @@ -25,7 +27,7 @@ module ActiveRecord end def []=(sql, stmt) - while @max <= cache.size + while @statement_limit <= cache.size dealloc(cache.shift.last) end cache[sql] = stmt @@ -45,13 +47,13 @@ module ActiveRecord private - def cache - @cache[Process.pid] - end + def cache + @cache[Process.pid] + end - def dealloc(stmt) - raise NotImplementedError - end + def dealloc(stmt) + raise NotImplementedError + end end end end diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb index a8b3d03ba5..2ede92feff 100644 --- a/activerecord/lib/active_record/connection_handling.rb +++ b/activerecord/lib/active_record/connection_handling.rb @@ -44,17 +44,18 @@ module ActiveRecord # # The exceptions AdapterNotSpecified, AdapterNotFound and +ArgumentError+ # may be returned on an error. - def establish_connection(spec = nil) - spec ||= DEFAULT_ENV.call.to_sym - resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new configurations - spec = resolver.spec(spec) + def establish_connection(config = nil) + raise "Anonymous class is not allowed." unless name - unless respond_to?(spec.adapter_method) - raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter" - end + config ||= DEFAULT_ENV.call.to_sym + spec_name = self == Base ? "primary" : name + self.connection_specification_name = spec_name + + resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations) + spec = resolver.resolve(config).symbolize_keys + spec[:name] = spec_name - remove_connection - connection_handler.establish_connection self, spec + connection_handler.establish_connection(spec) end class MergeAndResolveDefaultUrlConfig # :nodoc: @@ -72,7 +73,7 @@ module ActiveRecord private def config @raw_config.dup.tap do |cfg| - if url = ENV['DATABASE_URL'] + if url = ENV["DATABASE_URL"] cfg[@env] ||= {} cfg[@env]["url"] ||= url end @@ -87,12 +88,14 @@ module ActiveRecord retrieve_connection end - def connection_id - ActiveRecord::RuntimeRegistry.connection_id ||= Thread.current.object_id - end + attr_writer :connection_specification_name - def connection_id=(connection_id) - ActiveRecord::RuntimeRegistry.connection_id = connection_id + # Return the specification name from the current class or its parent. + def connection_specification_name + if !defined?(@connection_specification_name) || @connection_specification_name.nil? + return self == Base ? "primary" : superclass.connection_specification_name + end + @connection_specification_name end # Returns the configuration of the associated connection as a hash: @@ -106,20 +109,28 @@ module ActiveRecord end def connection_pool - connection_handler.retrieve_connection_pool(self) or raise ConnectionNotEstablished + connection_handler.retrieve_connection_pool(connection_specification_name) || raise(ConnectionNotEstablished) end def retrieve_connection - connection_handler.retrieve_connection(self) + connection_handler.retrieve_connection(connection_specification_name) end # Returns +true+ if Active Record is connected. def connected? - connection_handler.connected?(self) + connection_handler.connected?(connection_specification_name) end - def remove_connection(klass = self) - connection_handler.remove_connection(klass) + def remove_connection(name = nil) + name ||= @connection_specification_name if defined?(@connection_specification_name) + # if removing a connection that has a pool, we reset the + # connection_specification_name so it will use the parent + # pool. + if connection_handler.retrieve_connection_pool(name) + self.connection_specification_name = nil + end + + connection_handler.remove_connection(name) end def clear_cache! # :nodoc: @@ -127,6 +138,6 @@ module ActiveRecord end delegate :clear_active_connections!, :clear_reloadable_connections!, - :clear_all_connections!, :to => :connection_handler + :clear_all_connections!, to: :connection_handler end end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 24fd0aaecf..622df0cfc1 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -1,7 +1,7 @@ -require 'thread' -require 'active_support/core_ext/hash/indifferent_access' -require 'active_support/core_ext/object/duplicable' -require 'active_support/core_ext/string/filters' +require "thread" +require "active_support/core_ext/hash/indifferent_access" +require "active_support/core_ext/object/duplicable" +require "active_support/core_ext/string/filters" module ActiveRecord module Core @@ -72,6 +72,34 @@ module ActiveRecord ## # :singleton-method: + # Specifies if an error should be raised if the query has an order being + # ignored when doing batch queries. Useful in applications where the + # scope being ignored is error-worthy, rather than a warning. + mattr_accessor :error_on_ignored_order, instance_writer: false + self.error_on_ignored_order = false + + def self.error_on_ignored_order_or_limit + ActiveSupport::Deprecation.warn(<<-MSG.squish) + The flag error_on_ignored_order_or_limit is deprecated. Limits are + now supported. Please use error_on_ignored_order instead. + MSG + error_on_ignored_order + end + + def error_on_ignored_order_or_limit + self.class.error_on_ignored_order_or_limit + end + + def self.error_on_ignored_order_or_limit=(value) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + The flag error_on_ignored_order_or_limit is deprecated. Limits are + now supported. Please use error_on_ignored_order= instead. + MSG + self.error_on_ignored_order = value + end + + ## + # :singleton-method: # Specify whether or not to use timestamps for migration versions mattr_accessor :timestamped_migrations, instance_writer: false self.timestamped_migrations = true @@ -128,7 +156,7 @@ module ActiveRecord end def initialize_find_by_cache # :nodoc: - @find_by_statement_cache = {}.extend(Mutex_m) + @find_by_statement_cache = { true => {}.extend(Mutex_m), false => {}.extend(Mutex_m) } end def inherited(child_class) # :nodoc: @@ -151,7 +179,7 @@ module ActiveRecord 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` + Please pass the id of the object by calling `.id`. MSG end @@ -201,7 +229,7 @@ module ActiveRecord end def find_by!(*args) # :nodoc: - find_by(*args) or raise RecordNotFound.new("Couldn't find #{name}", name) + find_by(*args) || raise(RecordNotFound.new("Couldn't find #{name}", name)) end def initialize_generated_modules # :nodoc: @@ -225,7 +253,7 @@ module ActiveRecord elsif !connected? "#{super} (call '#{super}.connection' to establish a connection)" elsif table_exists? - attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ', ' + attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ", " "#{super}(#{attr_list})" else "#{super}(Table doesn't exist)" @@ -240,7 +268,7 @@ module ActiveRecord # Returns an instance of <tt>Arel::Table</tt> loaded with the current table name. # # class Post < ActiveRecord::Base - # scope :published_and_commented, -> { published.and(self.arel_table[:comments_count].gt(0)) } + # scope :published_and_commented, -> { published.and(arel_table[:comments_count].gt(0)) } # end def arel_table # :nodoc: @arel_table ||= Arel::Table.new(table_name, type_caster: type_caster) @@ -249,7 +277,7 @@ module ActiveRecord # Returns the Arel engine. def arel_engine # :nodoc: @arel_engine ||= - if Base == self || connection_handler.retrieve_connection_pool(self) + if Base == self || connection_handler.retrieve_connection_pool(connection_specification_name) self else superclass.arel_engine @@ -271,25 +299,26 @@ module ActiveRecord private - def cached_find_by_statement(key, &block) # :nodoc: - @find_by_statement_cache[key] || @find_by_statement_cache.synchronize { - @find_by_statement_cache[key] ||= StatementCache.create(connection, &block) - } - end + def cached_find_by_statement(key, &block) # :nodoc: + cache = @find_by_statement_cache[connection.prepared_statements] + cache[key] || cache.synchronize { + cache[key] ||= StatementCache.create(connection, &block) + } + end - def relation # :nodoc: - relation = Relation.create(self, arel_table, predicate_builder) + def relation # :nodoc: + 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) - else - relation + if finder_needs_type_condition? && !ignore_default_scope? + relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name) + else + relation + end end - end - def table_metadata # :nodoc: - TableMetadata.new(self, arel_table) - end + def table_metadata # :nodoc: + TableMetadata.new(self, arel_table) + end end # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with @@ -329,14 +358,16 @@ module ActiveRecord # post.title # => 'hello world' def init_with(coder) coder = LegacyYamlAdapter.convert(self.class, coder) - @attributes = coder['attributes'] + @attributes = self.class.yaml_encoder.decode(coder) init_internals - @new_record = coder['new_record'] + @new_record = coder["new_record"] self.class.define_attribute_methods + yield self if block_given? + _run_find_callbacks _run_initialize_callbacks @@ -395,11 +426,9 @@ module ActiveRecord # Post.new.encode_with(coder) # coder # => {"attributes" => {"id" => nil, ... }} def encode_with(coder) - # FIXME: Remove this when we better serialize attributes - coder['raw_attributes'] = attributes_before_type_cast - coder['attributes'] = @attributes - coder['new_record'] = new_record? - coder['active_record_yaml_version'] = 1 + self.class.yaml_encoder.encode(@attributes, coder) + coder["new_record"] = new_record? + coder["active_record_yaml_version"] = 2 end # Returns true if +comparison_object+ is the same exact object, or +comparison_object+ @@ -423,7 +452,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 - id.hash + self.class.hash ^ self.id.hash else super end @@ -471,14 +500,15 @@ module ActiveRecord # We check defined?(@attributes) not to issue warnings if the object is # allocated but not initialized. inspection = if defined?(@attributes) && @attributes - self.class.column_names.collect { |name| - if has_attribute?(name) - "#{name}: #{attribute_for_inspect(name)}" - end - }.compact.join(", ") - else - "not initialized" - end + self.class.attribute_names.collect do |name| + if has_attribute?(name) + "#{name}: #{attribute_for_inspect(name)}" + end + end.compact.join(", ") + else + "not initialized" + end + "#<#{self.class} #{inspection}>" end @@ -489,19 +519,19 @@ module ActiveRecord pp.object_address_group(self) do if defined?(@attributes) && @attributes column_names = self.class.column_names.select { |name| has_attribute?(name) || new_record? } - pp.seplist(column_names, proc { pp.text ',' }) do |column_name| + pp.seplist(column_names, proc { pp.text "," }) do |column_name| column_value = read_attribute(column_name) - pp.breakable ' ' + pp.breakable " " pp.group(1) do pp.text column_name - pp.text ':' + pp.text ":" pp.breakable pp.pp column_value end end else - pp.breakable ' ' - pp.text 'not initialized' + pp.breakable " " + pp.text "not initialized" end end end @@ -513,40 +543,40 @@ module ActiveRecord private - # Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements - # of the array, and then rescues from the possible NoMethodError. If those elements are - # ActiveRecord::Base's, then this triggers the various method_missing's that we have, - # which significantly impacts upon performance. - # - # So we can avoid the method_missing hit by explicitly defining #to_ary as nil here. - # - # See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary.html - def to_ary # :nodoc: - nil - end + # +Array#flatten+ will call +#to_ary+ (recursively) on each of the elements of + # the array, and then rescues from the possible +NoMethodError+. If those elements are + # +ActiveRecord::Base+'s, then this triggers the various +method_missing+'s that we have, + # which significantly impacts upon performance. + # + # So we can avoid the +method_missing+ hit by explicitly defining +#to_ary+ as +nil+ here. + # + # See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary.html + def to_ary + nil + end - def init_internals - @readonly = false - @destroyed = false - @marked_for_destruction = false - @destroyed_by_association = nil - @new_record = true - @txn = nil - @_start_transaction_state = {} - @transaction_state = nil - end + def init_internals + @readonly = false + @destroyed = false + @marked_for_destruction = false + @destroyed_by_association = nil + @new_record = true + @txn = nil + @_start_transaction_state = {} + @transaction_state = nil + end - def initialize_internals_callback - end + def initialize_internals_callback + end - def thaw - if frozen? - @attributes = @attributes.dup + def thaw + if frozen? + @attributes = @attributes.dup + end end - end - def custom_inspect_method_defined? - self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner - end + def custom_inspect_method_defined? + self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner + end end end diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index 1b6817554d..e2da512813 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -26,7 +26,7 @@ module ActiveRecord has_many_association = has_many.find { |association| association.counter_cache_column && association.counter_cache_column.to_sym == counter_association.to_sym } counter_association = has_many_association.plural_name if has_many_association end - raise ArgumentError, "'#{self.name}' has no association called '#{counter_association}'" unless has_many_association + raise ArgumentError, "'#{name}' has no association called '#{counter_association}'" unless has_many_association if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection has_many_association = has_many_association.through_reflection @@ -75,12 +75,12 @@ module ActiveRecord # # WHERE id IN (10, 15) def update_counters(id, counters) updates = counters.map do |counter_name, value| - operator = value < 0 ? '-' : '+' + operator = value < 0 ? "-" : "+" quoted_column = connection.quote_column_name(counter_name) "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}" end - unscoped.where(primary_key => id).update_all updates.join(', ') + unscoped.where(primary_key => id).update_all updates.join(", ") end # Increment a numeric field by one, via a direct SQL update. @@ -159,6 +159,5 @@ module ActiveRecord yield association(name.to_sym) if reflection.belongs_to? && reflection.counter_cache_column end end - end end diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb index b6dd6814db..08d42f3dd4 100644 --- a/activerecord/lib/active_record/dynamic_matchers.rb +++ b/activerecord/lib/active_record/dynamic_matchers.rb @@ -1,6 +1,7 @@ + module ActiveRecord module DynamicMatchers #:nodoc: - def respond_to?(name, include_private = false) + def respond_to_missing?(name, include_private = false) if self == Base super else @@ -11,111 +12,111 @@ module ActiveRecord private - def method_missing(name, *arguments, &block) - match = Method.match(self, name) + def method_missing(name, *arguments, &block) + match = Method.match(self, name) - if match && match.valid? - match.define - send(name, *arguments, &block) - else - super + if match && match.valid? + match.define + send(name, *arguments, &block) + else + super + end end - end - class Method - @matchers = [] + class Method + @matchers = [] - class << self - attr_reader :matchers + class << self + attr_reader :matchers - def match(model, name) - klass = matchers.find { |k| name =~ k.pattern } - klass.new(model, name) if klass - end + def match(model, name) + klass = matchers.find { |k| k.pattern.match?(name) } + klass.new(model, name) if klass + end - def pattern - @pattern ||= /\A#{prefix}_([_a-zA-Z]\w*)#{suffix}\Z/ - end + def pattern + @pattern ||= /\A#{prefix}_([_a-zA-Z]\w*)#{suffix}\Z/ + end - def prefix - raise NotImplementedError - end + def prefix + raise NotImplementedError + end - def suffix - '' + def suffix + "" + end end - end - attr_reader :model, :name, :attribute_names + attr_reader :model, :name, :attribute_names - def initialize(model, name) - @model = model - @name = name.to_s - @attribute_names = @name.match(self.class.pattern)[1].split('_and_') - @attribute_names.map! { |n| @model.attribute_aliases[n] || n } - end + def initialize(model, name) + @model = model + @name = name.to_s + @attribute_names = @name.match(self.class.pattern)[1].split("_and_") + @attribute_names.map! { |n| @model.attribute_aliases[n] || n } + end - def valid? - attribute_names.all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) } - end + def valid? + attribute_names.all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) } + end - def define - model.class_eval <<-CODE, __FILE__, __LINE__ + 1 - def self.#{name}(#{signature}) - #{body} - end - CODE - end + def define + model.class_eval <<-CODE, __FILE__, __LINE__ + 1 + def self.#{name}(#{signature}) + #{body} + end + CODE + end - private + private - def body - "#{finder}(#{attributes_hash})" - end + def body + "#{finder}(#{attributes_hash})" + end - # The parameters in the signature may have reserved Ruby words, in order - # to prevent errors, we start each param name with `_`. - def signature - attribute_names.map { |name| "_#{name}" }.join(', ') - end + # The parameters in the signature may have reserved Ruby words, in order + # to prevent errors, we start each param name with `_`. + def signature + attribute_names.map { |name| "_#{name}" }.join(", ") + end - # Given that the parameters starts with `_`, the finder needs to use the - # same parameter name. - def attributes_hash - "{" + attribute_names.map { |name| ":#{name} => _#{name}" }.join(',') + "}" - end + # Given that the parameters starts with `_`, the finder needs to use the + # same parameter name. + def attributes_hash + "{" + attribute_names.map { |name| ":#{name} => _#{name}" }.join(",") + "}" + end - def finder - raise NotImplementedError + def finder + raise NotImplementedError + end end - end - class FindBy < Method - Method.matchers << self + class FindBy < Method + Method.matchers << self - def self.prefix - "find_by" - end + def self.prefix + "find_by" + end - def finder - "find_by" + def finder + "find_by" + end end - end - class FindByBang < Method - Method.matchers << self + class FindByBang < Method + Method.matchers << self - def self.prefix - "find_by" - end + def self.prefix + "find_by" + end - def self.suffix - "!" - end + def self.suffix + "!" + end - def finder - "find_by!" + def finder + "find_by!" + end end - end end end diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index 903c63a7db..0a94ab58dd 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/object/deep_dup' +require "active_support/core_ext/object/deep_dup" module ActiveRecord # Declare an enum attribute where the values map to integers in the database, @@ -105,6 +105,8 @@ module ActiveRecord end class EnumType < Type::Value # :nodoc: + delegate :type, to: :subtype + def initialize(name, mapping, subtype) @name = name @mapping = mapping @@ -140,7 +142,7 @@ module ActiveRecord protected - attr_reader :name, :mapping, :subtype + attr_reader :name, :mapping, :subtype end def enum(definitions) @@ -152,15 +154,16 @@ module ActiveRecord enum_values = ActiveSupport::HashWithIndifferentAccess.new name = name.to_sym - # def self.statuses statuses end + # def self.statuses() statuses end detect_enum_conflict!(name, name.to_s.pluralize, true) klass.singleton_class.send(:define_method, name.to_s.pluralize) { enum_values } detect_enum_conflict!(name, name) detect_enum_conflict!(name, "#{name}=") - decorate_attribute_type(name, :enum) do |subtype| - EnumType.new(name, enum_values, subtype) + attr = attribute_alias?(name) ? attribute_alias(name) : name + decorate_attribute_type(attr, :enum) do |subtype| + EnumType.new(attr, enum_values, subtype) end _enum_methods_module.module_eval do @@ -182,15 +185,15 @@ module ActiveRecord # def active?() status == 0 end klass.send(:detect_enum_conflict!, name, "#{value_method_name}?") - define_method("#{value_method_name}?") { self[name] == value.to_s } + define_method("#{value_method_name}?") { self[attr] == value.to_s } # def active!() update! status: :active end klass.send(:detect_enum_conflict!, name, "#{value_method_name}!") - define_method("#{value_method_name}!") { update! name => value } + define_method("#{value_method_name}!") { update!(attr => value) } # scope :active, -> { where status: 0 } klass.send(:detect_enum_conflict!, name, value_method_name, true) - klass.scope value_method_name, -> { where(name => value) } + klass.scope value_method_name, -> { where(attr => value) } end end defined_enums[name.to_s] = enum_values @@ -213,18 +216,18 @@ module ActiveRecord def detect_enum_conflict!(enum_name, method_name, klass_method = false) if klass_method && dangerous_class_method?(method_name) - raise_conflict_error(enum_name, method_name, type: 'class') + raise_conflict_error(enum_name, method_name, type: "class") elsif !klass_method && dangerous_attribute_method?(method_name) raise_conflict_error(enum_name, method_name) elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module) - raise_conflict_error(enum_name, method_name, source: 'another enum') + raise_conflict_error(enum_name, method_name, source: "another enum") end end - def raise_conflict_error(enum_name, method_name, type: 'instance', source: 'Active Record') + def raise_conflict_error(enum_name, method_name, type: "instance", source: "Active Record") raise ArgumentError, ENUM_CONFLICT_MESSAGE % { enum: enum_name, - klass: self.name, + klass: name, type: type, method: method_name, source: source diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 87f32c042c..8fbe43e3ec 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -1,5 +1,4 @@ module ActiveRecord - # = Active Record Errors # # Generic Active Record exception class. @@ -96,7 +95,6 @@ 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. " \ @@ -125,6 +123,10 @@ module ActiveRecord class InvalidForeignKey < WrappedDatabaseException 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 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. @@ -139,6 +141,11 @@ module ActiveRecord class NoDatabaseError < StatementInvalid end + # Raised when Postgres returns 'cached plan must not change result type' and + # we cannot retry gracefully (e.g. inside a transaction) + class PreparedStatementCacheExpired < StatementInvalid + end + # Raised on attempt to save stale record. Record is stale when it's being saved in another query after # instantiation, for example, when two users edit the same wiki page and one starts editing and saves # the page before the other. @@ -157,7 +164,6 @@ module ActiveRecord super("Stale object error.") end end - end # Raised when association is being configured improperly or user tries to use @@ -276,6 +282,26 @@ module ActiveRecord class TransactionIsolationError < ActiveRecordError end + # TransactionRollbackError will be raised when a transaction is rolled + # back by the database due to a serialization failure or a deadlock. + # + # See the following: + # + # * http://www.postgresql.org/docs/current/static/transaction-iso.html + # * https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html#error_er_lock_deadlock + class TransactionRollbackError < StatementInvalid + end + + # SerializationFailure will be raised when a transaction is rolled + # back by the database due to a serialization failure. + class SerializationFailure < TransactionRollbackError + end + + # Deadlocked will be raised when a transaction is rolled + # back by the database when a deadlock is encountered. + class Deadlocked < TransactionRollbackError + end + # IrreversibleOrderError is raised when a relation's order is too complex for # +reverse_order+ to automatically reverse. class IrreversibleOrderError < ActiveRecordError diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb index 727a9befc1..980b8e1baa 100644 --- a/activerecord/lib/active_record/explain.rb +++ b/activerecord/lib/active_record/explain.rb @@ -1,5 +1,5 @@ -require 'active_support/lazy_load_hooks' -require 'active_record/explain_registry' +require "active_support/lazy_load_hooks" +require "active_record/explain_registry" module ActiveRecord module Explain @@ -16,15 +16,14 @@ module ActiveRecord # Makes the adapter execute EXPLAIN for the tuples of queries and bindings. # Returns a formatted string ready to be logged. def exec_explain(queries) # :nodoc: - str = queries.map do |sql, bind| - [].tap do |msg| - msg << "EXPLAIN for: #{sql}" - unless bind.empty? - bind_msg = bind.map {|col, val| [col.name, val]}.inspect - msg.last << " #{bind_msg}" - end - msg << connection.explain(sql, bind) - end.join("\n") + str = queries.map do |sql, binds| + msg = "EXPLAIN for: #{sql}" + unless binds.empty? + msg << " " + msg << binds.map { |attr| render_bind(attr) }.inspect + end + msg << "\n" + msg << connection.explain(sql, binds) end.join("\n") # Overriding inspect to be more human readable, especially in the console. @@ -34,5 +33,17 @@ module ActiveRecord str end + + private + + def render_bind(attr) + value = if attr.type.binary? && attr.value + "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>" + else + connection.type_cast(attr.value_for_database) + end + + [attr.name, value] + end end end diff --git a/activerecord/lib/active_record/explain_registry.rb b/activerecord/lib/active_record/explain_registry.rb index b652932f9c..ef1ce3dc85 100644 --- a/activerecord/lib/active_record/explain_registry.rb +++ b/activerecord/lib/active_record/explain_registry.rb @@ -1,4 +1,4 @@ -require 'active_support/per_thread_registry' +require "active_support/per_thread_registry" module ActiveRecord # This is a thread locals registry for EXPLAIN. For example diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb index 90bcf5a205..abd8cfc8f2 100644 --- a/activerecord/lib/active_record/explain_subscriber.rb +++ b/activerecord/lib/active_record/explain_subscriber.rb @@ -1,5 +1,5 @@ -require 'active_support/notifications' -require 'active_record/explain_registry' +require "active_support/notifications" +require "active_record/explain_registry" module ActiveRecord class ExplainSubscriber # :nodoc: @@ -18,10 +18,13 @@ module ActiveRecord # # On the other hand, we want to monitor the performance of our real database # queries, not the performance of the access to the query cache. - IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN CACHE) + IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN) EXPLAINED_SQLS = /\A\s*(with|select|update|delete|insert)\b/i def ignore_payload?(payload) - payload[:exception] || IGNORED_PAYLOADS.include?(payload[:name]) || payload[:sql] !~ EXPLAINED_SQLS + payload[:exception] || + payload[:cached] || + IGNORED_PAYLOADS.include?(payload[:name]) || + payload[:sql] !~ EXPLAINED_SQLS end ActiveSupport::Notifications.subscribe("sql.active_record", new) diff --git a/activerecord/lib/active_record/fixture_set/file.rb b/activerecord/lib/active_record/fixture_set/file.rb index f969556c50..5ba354d758 100644 --- a/activerecord/lib/active_record/fixture_set/file.rb +++ b/activerecord/lib/active_record/fixture_set/file.rb @@ -1,5 +1,5 @@ -require 'erb' -require 'yaml' +require "erb" +require "yaml" module ActiveRecord class FixtureSet @@ -24,21 +24,21 @@ module ActiveRecord end def model_class - config_row['model_class'] + config_row["model_class"] end private def rows - @rows ||= raw_rows.reject { |fixture_name, _| fixture_name == '_fixture' } + @rows ||= raw_rows.reject { |fixture_name, _| fixture_name == "_fixture" } end def config_row @config_row ||= begin - row = raw_rows.find { |fixture_name, _| fixture_name == '_fixture' } + row = raw_rows.find { |fixture_name, _| fixture_name == "_fixture" } if row row.last else - {'model_class': nil} + { 'model_class': nil } end end end @@ -52,15 +52,21 @@ module ActiveRecord end end + def prepare_erb(content) + erb = ERB.new(content) + erb.filename = @file + erb + end + def render(content) context = ActiveRecord::FixtureSet::RenderContext.create_subclass.new - ERB.new(content).result(context.get_binding) + prepare_erb(content).result(context.get_binding) end # 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" end raise Fixture::FormatError unless data.all? { |name, row| Hash === row } diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index ed1bbf5dcd..8b47fbdbe4 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -1,11 +1,11 @@ -require 'erb' -require 'yaml' -require 'zlib' -require 'set' -require 'active_support/dependencies' -require 'active_support/core_ext/digest/uuid' -require 'active_record/fixture_set/file' -require 'active_record/errors' +require "erb" +require "yaml" +require "zlib" +require "set" +require "active_support/dependencies" +require "active_support/core_ext/digest/uuid" +require "active_record/fixture_set/file" +require "active_record/errors" module ActiveRecord class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc: @@ -66,7 +66,7 @@ module ActiveRecord # By default, +test_helper.rb+ will load all of your fixtures into your test # database, so this test will succeed. # - # The testing environment will automatically load the all fixtures into the database before each + # The testing environment will automatically load all the fixtures into the database before each # test. To ensure consistent data, the environment deletes the fixtures before running the load. # # In addition to being available in the database, the fixture's data may also be accessed by @@ -426,9 +426,9 @@ module ActiveRecord end def self.default_fixture_table_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc: - "#{ config.table_name_prefix }"\ - "#{ fixture_set_name.tr('/', '_') }"\ - "#{ config.table_name_suffix }".to_sym + "#{ config.table_name_prefix }"\ + "#{ fixture_set_name.tr('/', '_') }"\ + "#{ config.table_name_suffix }".to_sym end def self.reset_cache @@ -494,18 +494,18 @@ module ActiveRecord private - def insert_class(class_names, name, klass) - # We only want to deal with AR objects. - if klass && klass < ActiveRecord::Base - class_names[name] = klass - else - class_names[name] = nil + def insert_class(class_names, name, klass) + # We only want to deal with AR objects. + if klass && klass < ActiveRecord::Base + class_names[name] = klass + else + class_names[name] = nil + end end - end - def default_fixture_model(fs_name, config) - ActiveRecord::FixtureSet.default_fixture_model_name(fs_name, config) - end + def default_fixture_model(fs_name, config) + ActiveRecord::FixtureSet.default_fixture_model_name(fs_name, config) + end end def self.create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base) @@ -535,7 +535,7 @@ module ActiveRecord update_all_loaded_fixtures fixtures_map - connection.transaction(:requires_new => true) do + connection.transaction(requires_new: true) do deleted_tables = Set.new fixture_sets.each do |fs| conn = fs.model_class.respond_to?(:connection) ? fs.model_class.connection : connection @@ -543,7 +543,7 @@ module ActiveRecord table_rows.each_key do |table| unless deleted_tables.include? table - conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete' + conn.delete "DELETE FROM #{conn.quote_table_name(table)}", "Fixture Delete" end deleted_tables << table end @@ -626,7 +626,7 @@ module ActiveRecord now = config.default_timezone == :utc ? Time.now.utc : Time.now # allow a standard key to be used for doing defaults in YAML - fixtures.delete('DEFAULTS') + fixtures.delete("DEFAULTS") # track any join tables we need to insert later rows = Hash.new { |h,table| h[table] = [] } @@ -799,7 +799,6 @@ module ActiveRecord def yaml_file_path(path) "#{path}.yml" end - end class Fixture #:nodoc: @@ -859,7 +858,7 @@ module ActiveRecord end included do - class_attribute :fixture_path, :instance_writer => false + class_attribute :fixture_path, instance_writer: false class_attribute :fixture_table_names class_attribute :fixture_class_names class_attribute :use_transactional_tests @@ -868,7 +867,7 @@ module ActiveRecord class_attribute :pre_loaded_fixtures class_attribute :config - singleton_class.deprecate 'use_transactional_fixtures=' => 'use use_transactional_tests= instead' + singleton_class.deprecate "use_transactional_fixtures=" => "use use_transactional_tests= instead" self.fixture_table_names = [] self.use_instantiated_fixtures = false @@ -903,7 +902,7 @@ module ActiveRecord def fixtures(*fixture_set_names) if fixture_set_names.first == :all - fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"] + fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"].uniq fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] } else fixture_set_names = fixture_set_names.flatten.map(&:to_s) @@ -918,7 +917,7 @@ module ActiveRecord methods = Module.new do fixture_set_names.each do |fs_name| fs_name = fs_name.to_s - accessor_name = fs_name.tr('/', '_').to_sym + accessor_name = fs_name.tr("/", "_").to_sym define_method(accessor_name) do |*fixture_names| force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload @@ -962,12 +961,13 @@ module ActiveRecord def setup_fixtures(config = ActiveRecord::Base) if pre_loaded_fixtures && !use_transactional_tests - raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_tests' + raise RuntimeError, "pre_loaded_fixtures requires use_transactional_tests" end @fixture_cache = {} @fixture_connections = [] @@already_loaded_fixtures ||= {} + @connection_subscriber = nil # Load fixtures once and begin transaction. if run_in_transaction? @@ -977,10 +977,31 @@ module ActiveRecord @loaded_fixtures = load_fixtures(config) @@already_loaded_fixtures[self.class] = @loaded_fixtures end + + # Begin transactions for connections already established @fixture_connections = enlist_fixture_connections @fixture_connections.each do |connection| connection.begin_transaction joinable: false end + + # When connections are established in the future, begin a transaction too + @connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload| + spec_name = payload[:spec_name] if payload.key?(:spec_name) + + if spec_name + begin + connection = ActiveRecord::Base.connection_handler.retrieve_connection(spec_name) + rescue ConnectionNotEstablished + connection = nil + end + + if connection && !@fixture_connections.include?(connection) + connection.begin_transaction joinable: false + @fixture_connections << connection + end + end + end + # Load fixtures for every test. else ActiveRecord::FixtureSet.reset_cache @@ -995,6 +1016,7 @@ module ActiveRecord def teardown_fixtures # Rollback changes if a transaction is active. if run_in_transaction? + ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber @fixture_connections.each do |connection| connection.rollback_transaction if connection.transaction_open? end @@ -1018,10 +1040,10 @@ module ActiveRecord def instantiate_fixtures if pre_loaded_fixtures - raise RuntimeError, 'Load fixtures before instantiating them.' if ActiveRecord::FixtureSet.all_loaded_fixtures.empty? + raise RuntimeError, "Load fixtures before instantiating them." if ActiveRecord::FixtureSet.all_loaded_fixtures.empty? ActiveRecord::FixtureSet.instantiate_all_loaded_fixtures(self, load_instances?) else - raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil? + raise RuntimeError, "Load fixtures before instantiating them." if @loaded_fixtures.nil? @loaded_fixtures.each_value do |fixture_set| ActiveRecord::FixtureSet.instantiate_fixtures(self, fixture_set, load_instances?) end diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb index aa1f5c4fb4..f33456a744 100644 --- a/activerecord/lib/active_record/gem_version.rb +++ b/activerecord/lib/active_record/gem_version.rb @@ -6,9 +6,9 @@ module ActiveRecord module VERSION MAJOR = 5 - MINOR = 0 + MINOR = 1 TINY = 0 - PRE = "beta2" + PRE = "alpha" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index 899683ee4f..a1d4f47372 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/hash/indifferent_access' +require "active_support/core_ext/hash/indifferent_access" module ActiveRecord # == Single table inheritance @@ -19,7 +19,7 @@ module ActiveRecord # Be aware that because the type column is an attribute on the record every new # subclass will instantly be marked as dirty and the type column will be included # in the list of changed attributes on the record. This is different from non - # STI classes: + # Single Table Inheritance(STI) classes: # # Company.new.changed? # => false # Firm.new.changed? # => true @@ -37,6 +37,7 @@ module ActiveRecord included do # Determines whether to store the full constant name including namespace when using STI. + # This is true, by default. class_attribute :store_full_sti_class, instance_writer: false self.store_full_sti_class = true end @@ -131,85 +132,85 @@ module ActiveRecord 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 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 - # Build a list of candidates to search for - candidates = [] - name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" } - candidates << type_name - - candidates.each do |candidate| - constant = ActiveSupport::Dependencies.safe_constantize(candidate) - return constant if candidate == constant.to_s - end + # 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 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 + # Build a list of candidates to search for + candidates = [] + name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" } + candidates << type_name - raise NameError.new("uninitialized constant #{candidates.first}", candidates.first) + candidates.each do |candidate| + constant = ActiveSupport::Dependencies.safe_constantize(candidate) + return constant if candidate == constant.to_s + end + + raise NameError.new("uninitialized constant #{candidates.first}", candidates.first) + end end - end private - # Called by +instantiate+ to decide which class to use for a new - # record instance. For single-table inheritance, we check the record - # for a +type+ column and return the corresponding class. - def discriminate_class_for_record(record) - if using_single_table_inheritance?(record) - find_sti_class(record[inheritance_column]) - else - super + # Called by +instantiate+ to decide which class to use for a new + # record instance. For single-table inheritance, we check the record + # for a +type+ column and return the corresponding class. + def discriminate_class_for_record(record) + if using_single_table_inheritance?(record) + find_sti_class(record[inheritance_column]) + else + super + end end - end - def using_single_table_inheritance?(record) - record[inheritance_column].present? && has_attribute?(inheritance_column) - end + def using_single_table_inheritance?(record) + record[inheritance_column].present? && has_attribute?(inheritance_column) + end - def find_sti_class(type_name) - type_name = base_class.type_for_attribute(inheritance_column).cast(type_name) - subclass = begin - if store_full_sti_class - ActiveSupport::Dependencies.constantize(type_name) - else - compute_type(type_name) + def find_sti_class(type_name) + type_name = base_class.type_for_attribute(inheritance_column).cast(type_name) + subclass = begin + if store_full_sti_class + ActiveSupport::Dependencies.constantize(type_name) + else + compute_type(type_name) + end + rescue NameError + raise SubclassNotFound, + "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \ + "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " \ + "Please rename this column if you didn't intend it to be used for storing the inheritance class " \ + "or overwrite #{name}.inheritance_column to use another column for that information." end - rescue NameError - raise SubclassNotFound, - "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \ - "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " \ - "Please rename this column if you didn't intend it to be used for storing the inheritance class " \ - "or overwrite #{name}.inheritance_column to use another column for that information." - end - unless subclass == self || descendants.include?(subclass) - raise SubclassNotFound, "Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}" + unless subclass == self || descendants.include?(subclass) + raise SubclassNotFound, "Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}" + end + subclass end - subclass - end - def type_condition(table = arel_table) - sti_column = arel_attribute(inheritance_column, table) - sti_names = ([self] + descendants).map(&:sti_name) + def type_condition(table = arel_table) + sti_column = arel_attribute(inheritance_column, table) + sti_names = ([self] + descendants).map(&:sti_name) - sti_column.in(sti_names) - end + sti_column.in(sti_names) + end - # Detect the subclass from the inheritance column of attrs. If the inheritance column value - # is not self or a valid subclass, raises ActiveRecord::SubclassNotFound - def subclass_from_attributes(attrs) - attrs = attrs.to_h if attrs.respond_to?(:permitted?) - if attrs.is_a?(Hash) - subclass_name = attrs.with_indifferent_access[inheritance_column] + # Detect the subclass from the inheritance column of attrs. If the inheritance column value + # is not self or a valid subclass, raises ActiveRecord::SubclassNotFound + def subclass_from_attributes(attrs) + attrs = attrs.to_h if attrs.respond_to?(:permitted?) + if attrs.is_a?(Hash) + subclass_name = attrs.with_indifferent_access[inheritance_column] - if subclass_name.present? - find_sti_class(subclass_name) + if subclass_name.present? + find_sti_class(subclass_name) + end end end - end end def initialize_dup(other) @@ -219,21 +220,21 @@ module ActiveRecord private - def initialize_internals_callback - super - ensure_proper_type - end + def initialize_internals_callback + super + ensure_proper_type + end - # Sets the attribute used for single table inheritance to this class name if this is not the - # ActiveRecord::Base descendant. - # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to - # do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself. - # No such attribute would be set for objects of the Message class in that example. - def ensure_proper_type - klass = self.class - if klass.finder_needs_type_condition? - write_attribute(klass.inheritance_column, klass.sti_name) + # Sets the attribute used for single table inheritance to this class name if this is not the + # ActiveRecord::Base descendant. + # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to + # do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself. + # No such attribute would be set for objects of the Message class in that example. + def ensure_proper_type + klass = self.class + if klass.finder_needs_type_condition? + write_attribute(klass.inheritance_column, klass.sti_name) + end end - end end end diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb index 466c8509a4..3c54c6048d 100644 --- a/activerecord/lib/active_record/integration.rb +++ b/activerecord/lib/active_record/integration.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/string/filters' +require "active_support/core_ext/string/filters" module ActiveRecord module Integration @@ -11,7 +11,7 @@ module ActiveRecord # Accepts any of the symbols in <tt>Time::DATE_FORMATS</tt>. # # This is +:usec+, by default. - class_attribute :cache_timestamp_format, :instance_writer => false + class_attribute :cache_timestamp_format, instance_writer: false self.cache_timestamp_format = :usec end @@ -53,18 +53,21 @@ module ActiveRecord # # Person.find(5).cache_key(:updated_at, :last_reviewed_at) def cache_key(*timestamp_names) - case - when new_record? + if new_record? "#{model_name.cache_key}/new" - when timestamp_names.any? - timestamp = max_updated_column_timestamp(timestamp_names) - timestamp = timestamp.utc.to_s(cache_timestamp_format) - "#{model_name.cache_key}/#{id}-#{timestamp}" - when timestamp = max_updated_column_timestamp - timestamp = timestamp.utc.to_s(cache_timestamp_format) - "#{model_name.cache_key}/#{id}-#{timestamp}" else - "#{model_name.cache_key}/#{id}" + timestamp = if timestamp_names.any? + max_updated_column_timestamp(timestamp_names) + else + max_updated_column_timestamp + end + + if timestamp + timestamp = timestamp.utc.to_s(cache_timestamp_format) + "#{model_name.cache_key}/#{id}-#{timestamp}" + else + "#{model_name.cache_key}/#{id}" + end end end @@ -86,7 +89,7 @@ module ActiveRecord # # user = User.find_by(name: 'David Heinemeier Hansson') # user.id # => 125 - # user_path(user) # => "/users/125-david" + # user_path(user) # => "/users/125-david-heinemeier" # # Because the generated param begins with the record's +id+, it is # suitable for passing to +find+. In a controller, for example: @@ -100,7 +103,7 @@ module ActiveRecord define_method :to_param do if (default = super()) && (result = send(method_name).to_s).present? && - (param = result.squish.truncate(20, separator: /\s/, omission: nil).parameterize).present? + (param = result.squish.parameterize.truncate(20, separator: /-/, omission: "")).present? "#{default}-#{param}" else default diff --git a/activerecord/lib/active_record/internal_metadata.rb b/activerecord/lib/active_record/internal_metadata.rb index 81db96bffd..20d61dba67 100644 --- a/activerecord/lib/active_record/internal_metadata.rb +++ b/activerecord/lib/active_record/internal_metadata.rb @@ -1,5 +1,5 @@ -require 'active_record/scoping/default' -require 'active_record/scoping/named' +require "active_record/scoping/default" +require "active_record/scoping/named" module ActiveRecord # This class is used to create a table that keeps track of values and keys such @@ -14,12 +14,8 @@ module ActiveRecord "#{table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{table_name_suffix}" end - def original_table_name - "#{table_name_prefix}active_record_internal_metadatas#{table_name_suffix}" - end - def []=(key, value) - first_or_initialize(key: key).update_attributes!(value: value) + find_or_initialize_by(key: key).update_attributes!(value: value) end def [](key) @@ -30,17 +26,8 @@ module ActiveRecord ActiveSupport::Deprecation.silence { connection.table_exists?(table_name) } end - def original_table_exists? - # This method will be removed in Rails 5.1 - # Since it is only necessary when `active_record_internal_metadatas` could exist - ActiveSupport::Deprecation.silence { connection.table_exists?(original_table_name) } - end - # Creates an internal metadata table with columns +key+ and +value+ def create_table - if original_table_exists? - connection.rename_table(original_table_name, table_name) - end unless table_exists? key_options = connection.internal_string_options_for_primary_key diff --git a/activerecord/lib/active_record/legacy_yaml_adapter.rb b/activerecord/lib/active_record/legacy_yaml_adapter.rb index 89dee58423..c7683f68c7 100644 --- a/activerecord/lib/active_record/legacy_yaml_adapter.rb +++ b/activerecord/lib/active_record/legacy_yaml_adapter.rb @@ -4,7 +4,7 @@ module ActiveRecord return coder unless coder.is_a?(Psych::Coder) case coder["active_record_yaml_version"] - when 1 then coder + when 1, 2 then coder else if coder["attributes"].is_a?(AttributeSet) Rails420.convert(klass, coder) diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 2336d23a1c..82882469e3 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -47,6 +47,8 @@ module ActiveRecord # self.locking_column = :lock_person # end # + # Please note that the optimistic locking will be ignored if you update the + # locking column's value. module Optimistic extend ActiveSupport::Concern @@ -60,10 +62,11 @@ module ActiveRecord end private + def increment_lock lock_col = self.class.locking_column previous_lock_value = send(lock_col).to_i - send(lock_col + '=', previous_lock_value + 1) + send(lock_col + "=", previous_lock_value + 1) end def _create_record(attribute_names = self.attribute_names, *) # :nodoc: @@ -77,21 +80,24 @@ module ActiveRecord def _update_record(attribute_names = self.attribute_names) #:nodoc: return super unless locking_enabled? - return 0 if attribute_names.empty? lock_col = self.class.locking_column - previous_lock_value = send(lock_col).to_i - increment_lock - attribute_names += [lock_col] - attribute_names.uniq! + return super if attribute_names.include?(lock_col) + return 0 if attribute_names.empty? begin + previous_lock_value = read_attribute_before_type_cast(lock_col) + + increment_lock + + attribute_names.push(lock_col) + relation = self.class.unscoped affected_rows = relation.where( self.class.primary_key => id, - lock_col => previous_lock_value, + lock_col => previous_lock_value ).update_all( attributes_for_update(attribute_names).map do |name| [name, _read_attribute(name)] @@ -104,9 +110,9 @@ module ActiveRecord affected_rows - # If something went wrong, revert the version. + # If something went wrong, revert the locking_column value. rescue Exception - send(lock_col + '=', previous_lock_value) + send(lock_col + "=", previous_lock_value.to_i) raise end end @@ -132,70 +138,76 @@ module ActiveRecord relation end - module ClassMethods - DEFAULT_LOCKING_COLUMN = 'lock_version' + module ClassMethods + DEFAULT_LOCKING_COLUMN = "lock_version" - # Returns true if the +lock_optimistically+ flag is set to true - # (which it is, by default) and the table includes the - # +locking_column+ column (defaults to +lock_version+). - def locking_enabled? - lock_optimistically && columns_hash[locking_column] - end + # Returns true if the +lock_optimistically+ flag is set to true + # (which it is, by default) and the table includes the + # +locking_column+ column (defaults to +lock_version+). + def locking_enabled? + lock_optimistically && columns_hash[locking_column] + end - # Set the column to use for optimistic locking. Defaults to +lock_version+. - def locking_column=(value) - reload_schema_from_cache - @locking_column = value.to_s - end + # Set the column to use for optimistic locking. Defaults to +lock_version+. + def locking_column=(value) + reload_schema_from_cache + @locking_column = value.to_s + end - # The version column used for optimistic locking. Defaults to +lock_version+. - def locking_column - reset_locking_column unless defined?(@locking_column) - @locking_column - end + # The version column used for optimistic locking. Defaults to +lock_version+. + def locking_column + @locking_column = DEFAULT_LOCKING_COLUMN unless defined?(@locking_column) + @locking_column + end - # Reset the column used for optimistic locking back to the +lock_version+ default. - def reset_locking_column - self.locking_column = DEFAULT_LOCKING_COLUMN - end + # Reset the column used for optimistic locking back to the +lock_version+ default. + def reset_locking_column + self.locking_column = DEFAULT_LOCKING_COLUMN + end - # Make sure the lock version column gets updated when counters are - # updated. - def update_counters(id, counters) - counters = counters.merge(locking_column => 1) if locking_enabled? - super - end + # Make sure the lock version column gets updated when counters are + # updated. + def update_counters(id, counters) + counters = counters.merge(locking_column => 1) if locking_enabled? + super + end - private - - # We need to apply this decorator here, rather than on module inclusion. The closure - # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the - # sub class being decorated. As such, changes to `lock_optimistically`, or - # `locking_column` would not be picked up. - def inherited(subclass) - subclass.class_eval do - is_lock_column = ->(name, _) { lock_optimistically && name == locking_column } - decorate_matching_attribute_types(is_lock_column, :_optimistic_locking) do |type| - LockingType.new(type) + private + + # We need to apply this decorator here, rather than on module inclusion. The closure + # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the + # sub class being decorated. As such, changes to `lock_optimistically`, or + # `locking_column` would not be picked up. + def inherited(subclass) + subclass.class_eval do + is_lock_column = ->(name, _) { lock_optimistically && name == locking_column } + decorate_matching_attribute_types(is_lock_column, :_optimistic_locking) do |type| + LockingType.new(type) + end + end + super end - end - super end - end end + # In de/serialize we change `nil` to 0, so that we can allow passing + # `nil` values to `lock_version`, and not result in `ActiveRecord::StaleObjectError` + # during update record. class LockingType < DelegateClass(Type::Value) # :nodoc: def deserialize(value) - # `nil` *should* be changed to 0 + super.to_i + end + + def serialize(value) super.to_i end def init_with(coder) - __setobj__(coder['subtype']) + __setobj__(coder["subtype"]) end def encode_with(coder) - coder['subtype'] = __getobj__ + coder["subtype"] = __getobj__ end end end diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb index 8ecdf76b72..e73cb4fc12 100644 --- a/activerecord/lib/active_record/locking/pessimistic.rb +++ b/activerecord/lib/active_record/locking/pessimistic.rb @@ -59,7 +59,7 @@ 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? + reload(lock: lock) if persisted? self end diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb index b63caa4473..4b8d8d9105 100644 --- a/activerecord/lib/active_record/log_subscriber.rb +++ b/activerecord/lib/active_record/log_subscriber.rb @@ -15,36 +15,24 @@ module ActiveRecord rt end - def initialize - super - @odd = false - end - - def render_bind(attribute) - value = if attribute.type.binary? && attribute.value - "<#{attribute.value.bytesize} bytes of binary data>" - else - attribute.value_for_database - end - - [attribute.name, value] - end - def sql(event) - return unless logger.debug? - self.class.runtime += event.duration + return unless logger.debug? payload = event.payload return if IGNORE_PAYLOAD_NAMES.include?(payload[:name]) name = "#{payload[:name]} (#{event.duration.round(1)}ms)" + name = "CACHE #{name}" if payload[:cached] sql = payload[:sql] binds = nil unless (payload[:binds] || []).empty? - binds = " " + payload[:binds].map { |attr| render_bind(attr) }.inspect + casted_params = type_casted_binds(payload[:binds], payload[:type_casted_binds]) + binds = " " + payload[:binds].zip(casted_params).map { |attr, value| + render_bind(attr, value) + }.inspect end name = colorize_payload_name(name, payload[:name]) @@ -55,19 +43,33 @@ module ActiveRecord private - def colorize_payload_name(name, payload_name) - if payload_name.blank? || payload_name == "SQL" # SQL vs Model Load/Exists - color(name, MAGENTA, true) - else - color(name, CYAN, true) + def type_casted_binds(binds, casted_binds) + casted_binds || binds.map { |attr| type_cast attr.value_for_database } end - end - def sql_color(sql) - case sql + def render_bind(attr, type_casted_value) + value = if attr.type.binary? && attr.value + "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>" + else + type_casted_value + end + + [attr.name, value] + end + + def colorize_payload_name(name, payload_name) + if payload_name.blank? || payload_name == "SQL" # SQL vs Model Load/Exists + color(name, MAGENTA, true) + else + color(name, CYAN, true) + end + end + + def sql_color(sql) + case sql when /\A\s*rollback/mi RED - when /\s*.*?select .*for update/mi, /\A\s*lock/mi + when /select .*for update/mi, /\A\s*lock/mi WHITE when /\A\s*select/i BLUE @@ -79,14 +81,18 @@ module ActiveRecord RED when /transaction\s*\Z/i CYAN - else + else MAGENTA + end end - end - def logger - ActiveRecord::Base.logger - end + def logger + ActiveRecord::Base.logger + end + + def type_cast(value) + ActiveRecord::Base.connection.type_cast(value) + end end end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 4419a7b1e7..9c3901e736 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -1,5 +1,6 @@ +require "set" +require "zlib" require "active_support/core_ext/module/attribute_accessors" -require 'set' module ActiveRecord class MigrationError < ActiveRecordError#:nodoc: @@ -126,9 +127,9 @@ module ActiveRecord class PendingMigrationError < MigrationError#:nodoc: def initialize(message = nil) if !message && defined?(Rails.env) - super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rails db:migrate RAILS_ENV=#{::Rails.env}") + super("Migrations are pending. To resolve this issue, run:\n\n bin/rails db:migrate RAILS_ENV=#{::Rails.env}") elsif !message - super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rails db:migrate") + super("Migrations are pending. To resolve this issue, run:\n\n bin/rails db:migrate") else super end @@ -145,7 +146,7 @@ module ActiveRecord class NoEnvironmentInSchemaError < MigrationError #:nodoc: def initialize - msg = "Environment data not found in the schema. To resolve this issue, run: \n\n\tbin/rails db:environment:set" + msg = "Environment data not found in the schema. To resolve this issue, run: \n\n bin/rails db:environment:set" if defined?(Rails.env) super("#{msg} RAILS_ENV=#{::Rails.env}") else @@ -156,8 +157,8 @@ module ActiveRecord class ProtectedEnvironmentError < ActiveRecordError #:nodoc: def initialize(env = "production") - msg = "You are attempting to run a destructive action against your '#{env}' database\n" - msg << "If you are sure you want to continue, run the same command with the environment variable\n" + msg = "You are attempting to run a destructive action against your '#{env}' database.\n" + msg << "If you are sure you want to continue, run the same command with the environment variable:\n" msg << "DISABLE_DATABASE_ENVIRONMENT_CHECK=1" super(msg) end @@ -166,13 +167,13 @@ module ActiveRecord class EnvironmentMismatchError < ActiveRecordError def initialize(current: nil, stored: nil) msg = "You are attempting to modify a database that was last run in `#{ stored }` environment.\n" - msg << "You are running in `#{ current }` environment." + msg << "You are running in `#{ current }` environment. " msg << "If you are sure you want to continue, first set the environment using:\n\n" - msg << "\tbin/rails db:environment:set" + msg << " bin/rails db:environment:set" if defined?(Rails.env) - super("#{msg} RAILS_ENV=#{::Rails.env}") + super("#{msg} RAILS_ENV=#{::Rails.env}\n\n") else - super(msg) + super("#{msg}\n\n") end end end @@ -276,8 +277,10 @@ module ActiveRecord # # * <tt>change_column(table_name, column_name, type, options)</tt>: Changes # the column to a different type using the same parameters as add_column. - # * <tt>change_column_default(table_name, column_name, default)</tt>: Sets a - # default value for +column_name+ definded by +default+ on +table_name+. + # * <tt>change_column_default(table_name, column_name, default_or_changes)</tt>: + # Sets a default value for +column_name+ defined by +default_or_changes+ on + # +table_name+. Passing a hash containing <tt>:from</tt> and <tt>:to</tt> + # as +default_or_changes+ will make this change reversible in the migration. # * <tt>change_column_null(table_name, column_name, null, default = nil)</tt>: # Sets or removes a +NOT NULL+ constraint on +column_name+. The +null+ flag # indicates whether the value can be +NULL+. See @@ -509,8 +512,8 @@ module ActiveRecord # Remember that you can still open your own transactions, even if you # are in a Migration with <tt>self.disable_ddl_transaction!</tt>. class Migration - autoload :CommandRecorder, 'active_record/migration/command_recorder' - autoload :Compatibility, 'active_record/migration/compatibility' + autoload :CommandRecorder, "active_record/migration/command_recorder" + autoload :Compatibility, "active_record/migration/compatibility" # This must be defined before the inherited hook, below class Current < Migration # :nodoc: @@ -524,23 +527,17 @@ module ActiveRecord end def self.[](version) - version = version.to_s - name = "V#{version.tr('.', '_')}" - unless Compatibility.const_defined?(name) - versions = Compatibility.constants.grep(/\AV[0-9_]+\z/).map { |s| s.to_s.delete('V').tr('_', '.').inspect } - raise "Unknown migration version #{version.inspect}; expected one of #{versions.sort.join(', ')}" - end - Compatibility.const_get(name) + Compatibility.find(version) end def self.current_version - Rails.version.to_f + ActiveRecord::VERSION::STRING.to_f end MigrationFilenameRegexp = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/ # :nodoc: # This class is used to verify that all migrations have been run before - # loading a web page if config.active_record.migration_error is set to :page_load + # loading a web page if <tt>config.active_record.migration_error</tt> is set to :page_load class CheckPending def initialize(app) @app = app @@ -560,9 +557,9 @@ module ActiveRecord private - def connection - ActiveRecord::Base.connection - end + def connection + ActiveRecord::Base.connection + end end class << self @@ -730,7 +727,7 @@ module ActiveRecord # end def reversible helper = ReversibleBlockHelper.new(reverting?) - execute_block{ yield helper } + execute_block { yield helper } end # Runs the given migration classes. @@ -835,7 +832,7 @@ module ActiveRecord end def method_missing(method, *arguments, &block) - arg_list = arguments.map(&:inspect) * ', ' + arg_list = arguments.map(&:inspect) * ", " say_with_time "#{method}(#{arg_list})" do unless connection.respond_to? :revert @@ -927,19 +924,18 @@ module ActiveRecord end private - def execute_block - if connection.respond_to? :execute_block - super # use normal delegation to record the block - else - yield + def execute_block + if connection.respond_to? :execute_block + super # use normal delegation to record the block + else + yield + end end - end end # MigrationProxy is used to defer loading of the actual migration classes # until they are needed class MigrationProxy < Struct.new(:name, :version, :filename, :scope) - def initialize(name, version, filename, scope) super @migration = nil @@ -965,7 +961,6 @@ module ActiveRecord require(File.expand_path(filename)) name.constantize.new(name, version) end - end class NullMigration < MigrationProxy #:nodoc: @@ -1057,13 +1052,13 @@ module ActiveRecord end def migrations_paths - @migrations_paths ||= ['db/migrate'] + @migrations_paths ||= ["db/migrate"] # just to not break things if someone uses: migrations_path = some_string Array(@migrations_paths) end def match_to_migration_filename?(filename) # :nodoc: - File.basename(filename) =~ Migration::MigrationFilenameRegexp + Migration::MigrationFilenameRegexp.match?(File.basename(filename)) end def parse_migration_filename(filename) # :nodoc: @@ -1170,146 +1165,146 @@ module ActiveRecord private - # Used for running a specific migration. - 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) - - record_environment - end + # Used for running a specific migration. + 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) - # Used for running multiple migrations up to or down to a certain value. - def migrate_without_lock - if invalid_target? - raise UnknownMigrationVersionError.new(@target_version) + record_environment end - runnable.each do |migration| - execute_migration_in_transaction(migration, @direction) - end + # Used for running multiple migrations up to or down to a certain value. + def migrate_without_lock + if invalid_target? + raise UnknownMigrationVersionError.new(@target_version) + end - record_environment - end + runnable.each do |migration| + execute_migration_in_transaction(migration, @direction) + end - # Stores the current environment in the database. - def record_environment - return if down? - ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment - end + record_environment + end - def ran?(migration) - migrated.include?(migration.version.to_i) - end + # Stores the current environment in the database. + def record_environment + return if down? + ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment + end - # Return true if a valid version is not provided. - def invalid_target? - !target && @target_version && @target_version > 0 - end + def ran?(migration) + migrated.include?(migration.version.to_i) + end - def execute_migration_in_transaction(migration, direction) - return if down? && !migrated.include?(migration.version.to_i) - return if up? && migrated.include?(migration.version.to_i) + # Return true if a valid version is not provided. + def invalid_target? + !target && @target_version && @target_version > 0 + end + + def execute_migration_in_transaction(migration, direction) + return if down? && !migrated.include?(migration.version.to_i) + return if up? && migrated.include?(migration.version.to_i) - Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger + Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger - ddl_transaction(migration) do - migration.migrate(direction) - record_version_state_after_migrating(migration.version) + ddl_transaction(migration) do + migration.migrate(direction) + record_version_state_after_migrating(migration.version) + end + rescue => e + msg = "An error has occurred, " + msg << "this and " if use_transaction?(migration) + msg << "all later migrations canceled:\n\n#{e}" + raise StandardError, msg, e.backtrace end - rescue => e - msg = "An error has occurred, " - msg << "this and " if use_transaction?(migration) - msg << "all later migrations canceled:\n\n#{e}" - raise StandardError, msg, e.backtrace - end - def target - migrations.detect { |m| m.version == @target_version } - end + def target + migrations.detect { |m| m.version == @target_version } + end - def finish - migrations.index(target) || migrations.size - 1 - end + def finish + migrations.index(target) || migrations.size - 1 + end - def start - up? ? 0 : (migrations.index(current) || 0) - end + def start + up? ? 0 : (migrations.index(current) || 0) + end - def validate(migrations) - name ,= migrations.group_by(&:name).find { |_,v| v.length > 1 } - raise DuplicateMigrationNameError.new(name) if name + def validate(migrations) + name ,= migrations.group_by(&:name).find { |_,v| v.length > 1 } + raise DuplicateMigrationNameError.new(name) if name - version ,= migrations.group_by(&:version).find { |_,v| v.length > 1 } - raise DuplicateMigrationVersionError.new(version) if version - end + version ,= migrations.group_by(&:version).find { |_,v| v.length > 1 } + raise DuplicateMigrationVersionError.new(version) if version + end - def record_version_state_after_migrating(version) - if down? - migrated.delete(version) - ActiveRecord::SchemaMigration.where(:version => version.to_s).delete_all - else - migrated << version - ActiveRecord::SchemaMigration.create!(version: version.to_s) + def record_version_state_after_migrating(version) + if down? + migrated.delete(version) + ActiveRecord::SchemaMigration.where(version: version.to_s).delete_all + else + migrated << version + ActiveRecord::SchemaMigration.create!(version: version.to_s) + end end - end - def self.last_stored_environment - return nil if current_version == 0 - raise NoEnvironmentInSchemaError unless ActiveRecord::InternalMetadata.table_exists? + def self.last_stored_environment + return nil if current_version == 0 + raise NoEnvironmentInSchemaError unless ActiveRecord::InternalMetadata.table_exists? - environment = ActiveRecord::InternalMetadata[:environment] - raise NoEnvironmentInSchemaError unless environment - environment - end + environment = ActiveRecord::InternalMetadata[:environment] + raise NoEnvironmentInSchemaError unless environment + environment + end - def self.current_environment - ActiveRecord::ConnectionHandling::DEFAULT_ENV.call - end + def self.current_environment + ActiveRecord::ConnectionHandling::DEFAULT_ENV.call + end - def self.protected_environment? - ActiveRecord::Base.protected_environments.include?(last_stored_environment) if last_stored_environment - end + def self.protected_environment? + ActiveRecord::Base.protected_environments.include?(last_stored_environment) if last_stored_environment + end - def up? - @direction == :up - end + def up? + @direction == :up + end - def down? - @direction == :down - end + def down? + @direction == :down + end - # Wrap the migration in a transaction only if supported by the adapter. - def ddl_transaction(migration) - if use_transaction?(migration) - Base.transaction { yield } - else - yield + # Wrap the migration in a transaction only if supported by the adapter. + def ddl_transaction(migration) + if use_transaction?(migration) + Base.transaction { yield } + else + yield + end end - end - def use_transaction?(migration) - !migration.disable_ddl_transaction && Base.connection.supports_ddl_transactions? - end + def use_transaction?(migration) + !migration.disable_ddl_transaction && Base.connection.supports_ddl_transactions? + end - def use_advisory_lock? - Base.connection.supports_advisory_locks? - end + def use_advisory_lock? + Base.connection.supports_advisory_locks? + end - def with_advisory_lock - lock_id = generate_migrator_advisory_lock_id - got_lock = Base.connection.get_advisory_lock(lock_id) - raise ConcurrentMigrationError unless got_lock - load_migrated # reload schema_migrations to be sure it wasn't changed by another process before we got the lock - yield - ensure - Base.connection.release_advisory_lock(lock_id) if got_lock - end + def with_advisory_lock + lock_id = generate_migrator_advisory_lock_id + got_lock = Base.connection.get_advisory_lock(lock_id) + raise ConcurrentMigrationError unless got_lock + load_migrated # reload schema_migrations to be sure it wasn't changed by another process before we got the lock + yield + ensure + Base.connection.release_advisory_lock(lock_id) if got_lock + end - MIGRATOR_SALT = 2053462845 - def generate_migrator_advisory_lock_id - db_name_hash = Zlib.crc32(Base.connection.current_database) - MIGRATOR_SALT * db_name_hash - end + MIGRATOR_SALT = 2053462845 + def generate_migrator_advisory_lock_id + db_name_hash = Zlib.crc32(Base.connection.current_database) + MIGRATOR_SALT * db_name_hash + end end end diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb index 0fa665c7e0..03103bba98 100644 --- a/activerecord/lib/active_record/migration/command_recorder.rb +++ b/activerecord/lib/active_record/migration/command_recorder.rb @@ -92,7 +92,7 @@ module ActiveRecord send(method, args, &block) end - def respond_to?(*args) # :nodoc: + def respond_to_missing?(*args) # :nodoc: super || delegate.respond_to?(*args) end @@ -112,127 +112,127 @@ module ActiveRecord private - module StraightReversions - private - { transaction: :transaction, - execute_block: :execute_block, - create_table: :drop_table, - create_join_table: :drop_join_table, - add_column: :remove_column, - add_timestamps: :remove_timestamps, - add_reference: :remove_reference, - enable_extension: :disable_extension - }.each do |cmd, inv| - [[inv, cmd], [cmd, inv]].uniq.each do |method, inverse| - class_eval <<-EOV, __FILE__, __LINE__ + 1 - def invert_#{method}(args, &block) # def invert_create_table(args, &block) - [:#{inverse}, args, block] # [:drop_table, args, block] - end # end - EOV - end + module StraightReversions + private + { transaction: :transaction, + execute_block: :execute_block, + create_table: :drop_table, + create_join_table: :drop_join_table, + add_column: :remove_column, + add_timestamps: :remove_timestamps, + add_reference: :remove_reference, + enable_extension: :disable_extension + }.each do |cmd, inv| + [[inv, cmd], [cmd, inv]].uniq.each do |method, inverse| + class_eval <<-EOV, __FILE__, __LINE__ + 1 + def invert_#{method}(args, &block) # def invert_create_table(args, &block) + [:#{inverse}, args, block] # [:drop_table, args, block] + end # end + EOV + end + end end - end - include StraightReversions + include StraightReversions - def invert_drop_table(args, &block) - if args.size == 1 && block == nil - raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given options or a block (can be empty)." + def invert_drop_table(args, &block) + if args.size == 1 && block == nil + raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given options or a block (can be empty)." + end + super end - super - end - def invert_rename_table(args) - [:rename_table, args.reverse] - end + def invert_rename_table(args) + [:rename_table, args.reverse] + end - def invert_remove_column(args) - raise ActiveRecord::IrreversibleMigration, "remove_column is only reversible if given a type." if args.size <= 2 - super - end + def invert_remove_column(args) + raise ActiveRecord::IrreversibleMigration, "remove_column is only reversible if given a type." if args.size <= 2 + super + end - def invert_rename_index(args) - [:rename_index, [args.first] + args.last(2).reverse] - end + def invert_rename_index(args) + [:rename_index, [args.first] + args.last(2).reverse] + end - def invert_rename_column(args) - [:rename_column, [args.first] + args.last(2).reverse] - end + def invert_rename_column(args) + [:rename_column, [args.first] + args.last(2).reverse] + end - def invert_add_index(args) - table, columns, options = *args - options ||= {} + def invert_add_index(args) + table, columns, options = *args + options ||= {} - index_name = options[:name] - options_hash = index_name ? { name: index_name } : { column: columns } + index_name = options[:name] + options_hash = index_name ? { name: index_name } : { column: columns } - [:remove_index, [table, options_hash]] - end + [:remove_index, [table, options_hash]] + end - def invert_remove_index(args) - table, options_or_column = *args - if (options = options_or_column).is_a?(Hash) - unless options[:column] - raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option." + def invert_remove_index(args) + table, options_or_column = *args + if (options = options_or_column).is_a?(Hash) + unless options[:column] + raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option." + end + options = options.dup + [:add_index, [table, options.delete(:column), options]] + elsif (column = options_or_column).present? + [:add_index, [table, column]] end - options = options.dup - [:add_index, [table, options.delete(:column), options]] - elsif (column = options_or_column).present? - [:add_index, [table, column]] end - end - alias :invert_add_belongs_to :invert_add_reference - alias :invert_remove_belongs_to :invert_remove_reference + alias :invert_add_belongs_to :invert_add_reference + alias :invert_remove_belongs_to :invert_remove_reference + + def invert_change_column_default(args) + table, column, options = *args - def invert_change_column_default(args) - table, column, options = *args + unless options && options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to) + raise ActiveRecord::IrreversibleMigration, "change_column_default is only reversible if given a :from and :to option." + end - unless options && options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to) - raise ActiveRecord::IrreversibleMigration, "change_column_default is only reversible if given a :from and :to option." + [:change_column_default, [table, column, from: options[:to], to: options[:from]]] end - [:change_column_default, [table, column, from: options[:to], to: options[:from]]] - end + def invert_change_column_null(args) + args[2] = !args[2] + [:change_column_null, args] + end - def invert_change_column_null(args) - args[2] = !args[2] - [:change_column_null, args] - end + def invert_add_foreign_key(args) + from_table, to_table, add_options = args + add_options ||= {} - def invert_add_foreign_key(args) - from_table, to_table, add_options = args - add_options ||= {} + if add_options[:name] + options = { name: add_options[:name] } + elsif add_options[:column] + options = { column: add_options[:column] } + else + options = to_table + end - if add_options[:name] - options = { name: add_options[:name] } - elsif add_options[:column] - options = { column: add_options[:column] } - else - options = to_table + [:remove_foreign_key, [from_table, options]] end - [:remove_foreign_key, [from_table, options]] - end - - def invert_remove_foreign_key(args) - from_table, to_table, remove_options = args - raise ActiveRecord::IrreversibleMigration, "remove_foreign_key is only reversible if given a second table" if to_table.nil? || to_table.is_a?(Hash) + def invert_remove_foreign_key(args) + from_table, to_table, remove_options = args + raise ActiveRecord::IrreversibleMigration, "remove_foreign_key is only reversible if given a second table" if to_table.nil? || to_table.is_a?(Hash) - reversed_args = [from_table, to_table] - reversed_args << remove_options if remove_options + reversed_args = [from_table, to_table] + reversed_args << remove_options if remove_options - [:add_foreign_key, reversed_args] - end + [:add_foreign_key, reversed_args] + end - # Forwards any missing method call to the \target. - def method_missing(method, *args, &block) - if @delegate.respond_to?(method) - @delegate.send(method, *args, &block) - else - super + # Forwards any missing method call to the \target. + def method_missing(method, *args, &block) + if @delegate.respond_to?(method) + @delegate.send(method, *args, &block) + else + super + end end - end end end end diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb index 45e35a4f71..04e538baa5 100644 --- a/activerecord/lib/active_record/migration/compatibility.rb +++ b/activerecord/lib/active_record/migration/compatibility.rb @@ -1,7 +1,17 @@ module ActiveRecord class Migration module Compatibility # :nodoc: all - V5_0 = Current + def self.find(version) + version = version.to_s + name = "V#{version.tr('.', '_')}" + unless const_defined?(name) + versions = constants.grep(/\AV[0-9_]+\z/).map { |s| s.to_s.delete("V").tr("_", ".").inspect } + raise ArgumentError, "Unknown migration version #{version.inspect}; expected one of #{versions.sort.join(', ')}" + end + const_get(name) + end + + V5_1 = Current module FourTwoShared module TableDefinition @@ -11,7 +21,7 @@ module ActiveRecord end alias :belongs_to :references - def timestamps(*, **options) + def timestamps(**options) options[:null] = true if options[:null].nil? super end @@ -49,7 +59,7 @@ module ActiveRecord end alias :add_belongs_to :add_reference - def add_timestamps(*, **options) + def add_timestamps(_, **options) options[:null] = true if options[:null].nil? super end @@ -57,7 +67,7 @@ module ActiveRecord def index_exists?(table_name, column_name, options = {}) column_names = Array(column_name).map(&:to_s) options[:name] = - if options.key?(:name).present? + if options[:name].present? options[:name].to_s else index_name(table_name, column: column_names) @@ -73,23 +83,26 @@ module ActiveRecord private - def index_name_for_remove(table_name, options = {}) - index_name = index_name(table_name, options) + def index_name_for_remove(table_name, options = {}) + index_name = index_name(table_name, options) - unless index_name_exists?(table_name, index_name, true) - 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) + unless index_name_exists?(table_name, index_name, true) + 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, false) + end + + raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist" end - raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist" + index_name end + end - index_name - end + class V5_0 < V5_1 end class V4_2 < V5_0 @@ -102,7 +115,7 @@ module ActiveRecord module Legacy include FourTwoShared - def run(*) + def migrate(*) ActiveSupport::Deprecation.warn \ "Directly inheriting from ActiveRecord::Migration is deprecated. " \ "Please specify the Rails release the migration was written for:\n" \ diff --git a/activerecord/lib/active_record/migration/join_table.rb b/activerecord/lib/active_record/migration/join_table.rb index 05569fadbd..89789f00ea 100644 --- a/activerecord/lib/active_record/migration/join_table.rb +++ b/activerecord/lib/active_record/migration/join_table.rb @@ -3,13 +3,13 @@ module ActiveRecord module JoinTable #:nodoc: private - def find_join_table_name(table_1, table_2, options = {}) - options.delete(:table_name) || join_table_name(table_1, table_2) - end + def find_join_table_name(table_1, table_2, options = {}) + options.delete(:table_name) || join_table_name(table_1, table_2) + end - def join_table_name(table_1, table_2) - ModelSchema.derive_join_table_name(table_1, table_2).to_sym - end + def join_table_name(table_1, table_2) + ModelSchema.derive_join_table_name(table_1, table_2).to_sym + end end end end diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index ee52c3ae02..525f7444a5 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -2,75 +2,154 @@ module ActiveRecord module ModelSchema extend ActiveSupport::Concern + ## + # :singleton-method: primary_key_prefix_type + # :call-seq: primary_key_prefix_type + # + # The prefix type that will be prepended to every primary key column name. + # The options are +:table_name+ and +:table_name_with_underscore+. If the first is specified, + # the Product class will look for "productid" instead of "id" as the primary column. If the + # latter is specified, the Product class will look for "product_id" instead of "id". Remember + # that this is a global setting for all Active Records. + + ## + # :singleton-method: primary_key_prefix_type= + # :call-seq: primary_key_prefix_type=(prefix_type) + # + # Sets the prefix type that will be prepended to every primary key column name. + # The options are +:table_name+ and +:table_name_with_underscore+. If the first is specified, + # the Product class will look for "productid" instead of "id" as the primary column. If the + # latter is specified, the Product class will look for "product_id" instead of "id". Remember + # that this is a global setting for all Active Records. + + ## + # :singleton-method: table_name_prefix + # :call-seq: table_name_prefix + # + # The prefix string to prepend to every table name. + + ## + # :singleton-method: table_name_prefix= + # :call-seq: table_name_prefix=(prefix) + # + # Sets the prefix string to prepend to every table name. So if set to "basecamp_", all table + # names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient + # way of creating a namespace for tables in a shared database. By default, the prefix is the + # empty string. + # + # If you are organising your models within modules you can add a prefix to the models within + # a namespace by defining a singleton method in the parent module called table_name_prefix which + # returns your chosen prefix. + + ## + # :singleton-method: table_name_suffix + # :call-seq: table_name_suffix + # + # The suffix string to append to every table name. + + ## + # :singleton-method: table_name_suffix= + # :call-seq: table_name_suffix=(suffix) + # + # Works like +table_name_prefix=+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp", + # "people_basecamp"). By default, the suffix is the empty string. + # + # If you are organising your models within modules, you can add a suffix to the models within + # a namespace by defining a singleton method in the parent module called table_name_suffix which + # returns your chosen suffix. + + ## + # :singleton-method: schema_migrations_table_name + # :call-seq: schema_migrations_table_name + # + # The name of the schema migrations table. By default, the value is <tt>"schema_migrations"</tt>. + + ## + # :singleton-method: schema_migrations_table_name= + # :call-seq: schema_migrations_table_name=(table_name) + # + # Sets the name of the schema migrations table. + + ## + # :singleton-method: internal_metadata_table_name + # :call-seq: internal_metadata_table_name + # + # The name of the internal metadata table. By default, the value is <tt>"ar_internal_metadata"</tt>. + + ## + # :singleton-method: internal_metadata_table_name= + # :call-seq: internal_metadata_table_name=(table_name) + # + # Sets the name of the internal metadata table. + + ## + # :singleton-method: protected_environments + # :call-seq: protected_environments + # + # The array of names of environments where destructive actions should be prohibited. By default, + # the value is <tt>["production"]</tt>. + + ## + # :singleton-method: protected_environments= + # :call-seq: protected_environments=(environments) + # + # Sets an array of names of environments where destructive actions should be prohibited. + + ## + # :singleton-method: pluralize_table_names + # :call-seq: pluralize_table_names + # + # Indicates whether table names should be the pluralized versions of the corresponding class names. + # If true, the default table name for a Product class will be "products". If false, it would just be "product". + # See table_name for the full rules on table/class naming. This is true, by default. + + ## + # :singleton-method: pluralize_table_names= + # :call-seq: pluralize_table_names=(value) + # + # Set whether table names should be the pluralized versions of the corresponding class names. + # If true, the default table name for a Product class will be "products". If false, it would just be "product". + # See table_name for the full rules on table/class naming. This is true, by default. + + ## + # :singleton-method: ignored_columns + # :call-seq: ignored_columns + # + # The list of columns names the model should ignore. Ignored columns won't have attribute + # accessors defined, and won't be referenced in SQL queries. + + ## + # :singleton-method: ignored_columns= + # :call-seq: ignored_columns=(columns) + # + # Sets the columns names the model should ignore. Ignored columns won't have attribute + # accessors defined, and won't be referenced in SQL queries. + included do - ## - # :singleton-method: - # Accessor for the prefix type that will be prepended to every primary key column name. - # The options are :table_name and :table_name_with_underscore. If the first is specified, - # the Product class will look for "productid" instead of "id" as the primary column. If the - # latter is specified, the Product class will look for "product_id" instead of "id". Remember - # that this is a global setting for all Active Records. mattr_accessor :primary_key_prefix_type, instance_writer: false - ## - # :singleton-method: - # Accessor for the name of the prefix string to prepend to every table name. So if set - # to "basecamp_", all table names will be named like "basecamp_projects", "basecamp_people", - # etc. This is a convenient way of creating a namespace for tables in a shared database. - # By default, the prefix is the empty string. - # - # If you are organising your models within modules you can add a prefix to the models within - # a namespace by defining a singleton method in the parent module called table_name_prefix which - # returns your chosen prefix. class_attribute :table_name_prefix, instance_writer: false self.table_name_prefix = "" - ## - # :singleton-method: - # Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp", - # "people_basecamp"). By default, the suffix is the empty string. - # - # If you are organising your models within modules, you can add a suffix to the models within - # a namespace by defining a singleton method in the parent module called table_name_suffix which - # returns your chosen suffix. class_attribute :table_name_suffix, instance_writer: false self.table_name_suffix = "" - ## - # :singleton-method: - # Accessor for the name of the schema migrations table. By default, the value is "schema_migrations" class_attribute :schema_migrations_table_name, instance_accessor: false self.schema_migrations_table_name = "schema_migrations" - ## - # :singleton-method: - # Accessor for the name of the internal metadata table. By default, the value is "ar_internal_metadata" class_attribute :internal_metadata_table_name, instance_accessor: false self.internal_metadata_table_name = "ar_internal_metadata" - ## - # :singleton-method: - # Accessor for an array of names of environments where destructive actions should be prohibited. By default, - # the value is ["production"] class_attribute :protected_environments, instance_accessor: false self.protected_environments = ["production"] - ## - # :singleton-method: - # Indicates whether table names should be the pluralized versions of the corresponding class names. - # If true, the default table name for a Product class will be +products+. If false, it would just be +product+. - # See table_name for the full rules on table/class naming. This is true, by default. class_attribute :pluralize_table_names, instance_writer: false self.pluralize_table_names = true - ## - # :singleton-method: - # Accessor for the list of columns names the model should ignore. Ignored columns won't have attribute - # accessors defined, and won't be referenced in SQL queries. class_attribute :ignored_columns, instance_accessor: false self.ignored_columns = [].freeze - self.inheritance_column = 'type' + self.inheritance_column = "type" delegate :type_for_attribute, to: :class end @@ -173,11 +252,11 @@ module ActiveRecord end def full_table_name_prefix #:nodoc: - (parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix + (parents.detect { |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix end def full_table_name_suffix #:nodoc: - (parents.detect {|p| p.respond_to?(:table_name_suffix) } || self).table_name_suffix + (parents.detect { |p| p.respond_to?(:table_name_suffix) } || self).table_name_suffix end # Defines the name of the table column which will store the class name on single-table @@ -231,13 +310,29 @@ module ActiveRecord @explicit_sequence_name = true end + # Determines if the primary key values should be selected from their + # corresponding sequence before the insert statement. + def prefetch_primary_key? + connection.prefetch_primary_key?(table_name) + end + + # Returns the next value that will be used as the primary key on + # an insert statement. + def next_sequence_value + connection.next_sequence_value(sequence_name) + end + # Indicates whether the table associated with this class exists def table_exists? connection.schema_cache.data_source_exists?(table_name) end def attributes_builder # :nodoc: - @attributes_builder ||= AttributeSet::Builder.new(attribute_types, primary_key) + @attributes_builder ||= AttributeSet::Builder.new(attribute_types, primary_key) do |name| + unless columns_hash.key?(name) + _default_attributes[name].dup + end + end end def columns_hash # :nodoc: @@ -252,7 +347,11 @@ module ActiveRecord def attribute_types # :nodoc: load_schema - @attribute_types ||= Hash.new(Type::Value.new) + @attribute_types ||= Hash.new(Type.default_value) + end + + def yaml_encoder # :nodoc: + @yaml_encoder ||= AttributeSet::YAMLEncoder.new(attribute_types) end # Returns the type of the attribute with the given name, after applying @@ -266,8 +365,12 @@ module ActiveRecord # # +attr_name+ The name of the attribute to retrieve the type for. Must be # a string - def type_for_attribute(attr_name) - attribute_types[attr_name] + def type_for_attribute(attr_name, &block) + if block + attribute_types.fetch(attr_name, &block) + else + attribute_types[attr_name] + end end # Returns a hash where the keys are column names and the values are @@ -289,7 +392,12 @@ module ActiveRecord # Returns an array of column objects where the primary id, all columns ending in "_id" or "_count", # and columns used for single table inheritance have been removed. def content_columns - @content_columns ||= columns.reject { |c| c.name == primary_key || c.name =~ /(_id|_count)$/ || c.name == inheritance_column } + @content_columns ||= columns.reject do |c| + c.name == primary_key || + c.name == inheritance_column || + c.name.end_with?("_id") || + c.name.end_with?("_count") + end end # Resets all the cached information about columns, which will cause them @@ -328,91 +436,69 @@ module ActiveRecord private - def schema_loaded? - defined?(@columns_hash) && @columns_hash - end - - def load_schema - unless schema_loaded? - load_schema! + def schema_loaded? + defined?(@columns_hash) && @columns_hash end - end - def load_schema! - @columns_hash = connection.schema_cache.columns_hash(table_name).except(*ignored_columns) - @columns_hash.each do |name, column| - warn_if_deprecated_type(column) - define_attribute( - name, - connection.lookup_cast_type_from_column(column), - default: column.default, - user_provided_default: false - ) + def load_schema + unless schema_loaded? + load_schema! + end end - end - def reload_schema_from_cache - @arel_engine = nil - @arel_table = nil - @column_names = nil - @attribute_types = nil - @content_columns = nil - @default_attributes = nil - @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column - @attributes_builder = nil - @columns = nil - @columns_hash = nil - @attribute_names = nil - direct_descendants.each do |descendant| - descendant.send(:reload_schema_from_cache) + def load_schema! + @columns_hash = connection.schema_cache.columns_hash(table_name).except(*ignored_columns) + @columns_hash.each do |name, column| + define_attribute( + name, + connection.lookup_cast_type_from_column(column), + default: column.default, + user_provided_default: false + ) + end end - end - - # Guesses the table name, but does not decorate it with prefix and suffix information. - def undecorated_table_name(class_name = base_class.name) - table_name = class_name.to_s.demodulize.underscore - pluralize_table_names ? table_name.pluralize : table_name - end - # Computes and returns a table name according to default conventions. - def compute_table_name - base = base_class - if self == base - # Nested classes are prefixed with singular parent table name. - if parent < Base && !parent.abstract_class? - contained = parent.table_name - contained = contained.singularize if parent.pluralize_table_names - contained += '_' + def reload_schema_from_cache + @arel_engine = nil + @arel_table = nil + @column_names = nil + @attribute_types = nil + @content_columns = nil + @default_attributes = nil + @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column + @attributes_builder = nil + @columns = nil + @columns_hash = nil + @attribute_names = nil + @yaml_encoder = nil + direct_descendants.each do |descendant| + descendant.send(:reload_schema_from_cache) end + end - "#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{full_table_name_suffix}" - else - # STI subclasses always use their superclass' table. - base.table_name + # Guesses the table name, but does not decorate it with prefix and suffix information. + def undecorated_table_name(class_name = base_class.name) + table_name = class_name.to_s.demodulize.underscore + pluralize_table_names ? table_name.pluralize : table_name end - end - def warn_if_deprecated_type(column) - return if attributes_to_define_after_schema_loads.key?(column.name) - if column.respond_to?(:oid) && column.sql_type.start_with?("point") - if column.array? - array_arguments = ", array: true" + # Computes and returns a table name according to default conventions. + def compute_table_name + base = base_class + if self == base + # Nested classes are prefixed with singular parent table name. + if parent < Base && !parent.abstract_class? + contained = parent.table_name + contained = contained.singularize if parent.pluralize_table_names + contained += "_" + end + + "#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{full_table_name_suffix}" else - array_arguments = "" + # STI subclasses always use their superclass' table. + base.table_name end - ActiveSupport::Deprecation.warn(<<-WARNING.strip_heredoc) - The behavior of the `:point` type will be changing in Rails 5.1 to - return a `Point` object, rather than an `Array`. If you'd like to - keep the old behavior, you can add this line to #{self.name}: - - attribute :#{column.name}, :legacy_point#{array_arguments} - - If you'd like the new behavior today, you can add this line: - - attribute :#{column.name}, :point#{array_arguments} - WARNING end - end end end end diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 0d5a8e6f25..e983026961 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -1,6 +1,6 @@ -require 'active_support/core_ext/hash/except' -require 'active_support/core_ext/object/try' -require 'active_support/core_ext/hash/indifferent_access' +require "active_support/core_ext/hash/except" +require "active_support/core_ext/object/try" +require "active_support/core_ext/hash/indifferent_access" module ActiveRecord module NestedAttributes #:nodoc: @@ -195,19 +195,27 @@ module ActiveRecord # Nested attributes for an associated collection can also be passed in # the form of a hash of hashes instead of an array of hashes: # - # Member.create(name: 'joe', - # posts_attributes: { first: { title: 'Foo' }, - # second: { title: 'Bar' } }) + # Member.create( + # name: 'joe', + # posts_attributes: { + # first: { title: 'Foo' }, + # second: { title: 'Bar' } + # } + # ) # # has the same effect as # - # Member.create(name: 'joe', - # posts_attributes: [ { title: 'Foo' }, - # { title: 'Bar' } ]) + # Member.create( + # name: 'joe', + # posts_attributes: [ + # { title: 'Foo' }, + # { title: 'Bar' } + # ] + # ) # # The keys of the hash which is the value for +:posts_attributes+ are # ignored in this case. - # However, it is not allowed to use +'id'+ or +:id+ for one of + # However, it is not allowed to use <tt>'id'</tt> or <tt>:id</tt> for one of # such keys, otherwise the hash will be wrapped in an array and # interpreted as an attribute hash for a single post. # @@ -259,7 +267,7 @@ module ActiveRecord # member.avatar_attributes = {icon: 'sad'} # member.avatar.width # => 200 module ClassMethods - REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } } + REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == "_destroy" || value.blank? } } # Defines an attributes writer for the specified association(s). # @@ -309,7 +317,7 @@ module ActiveRecord # # creates avatar_attributes= and posts_attributes= # accepts_nested_attributes_for :avatar, :posts, allow_destroy: true def accepts_nested_attributes_for(*attr_names) - options = { :allow_destroy => false, :update_only => false } + options = { allow_destroy: false, update_only: false } options.update(attr_names.extract_options!) options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only) options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank @@ -333,27 +341,27 @@ module ActiveRecord private - # Generates a writer method for this association. Serves as a point for - # accessing the objects in the association. For example, this method - # could generate the following: - # - # def pirate_attributes=(attributes) - # assign_nested_attributes_for_one_to_one_association(:pirate, attributes) - # end - # - # This redirects the attempts to write objects in an association through - # the helper methods defined below. Makes it seem like the nested - # associations are just regular associations. - def generate_association_writer(association_name, type) - generated_association_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1 - if method_defined?(:#{association_name}_attributes=) - remove_method(:#{association_name}_attributes=) - end - def #{association_name}_attributes=(attributes) - assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes) - end - eoruby - end + # Generates a writer method for this association. Serves as a point for + # accessing the objects in the association. For example, this method + # could generate the following: + # + # def pirate_attributes=(attributes) + # assign_nested_attributes_for_one_to_one_association(:pirate, attributes) + # end + # + # This redirects the attempts to write objects in an association through + # the helper methods defined below. Makes it seem like the nested + # associations are just regular associations. + def generate_association_writer(association_name, type) + generated_association_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1 + if method_defined?(:#{association_name}_attributes=) + remove_method(:#{association_name}_attributes=) + end + def #{association_name}_attributes=(attributes) + assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes) + end + eoruby + end end # Returns ActiveRecord::AutosaveAssociation::marked_for_destruction? It's @@ -367,213 +375,214 @@ module ActiveRecord private - # Attribute hash keys that should not be assigned as normal attributes. - # These hash keys are nested attributes implementation details. - UNASSIGNABLE_KEYS = %w( id _destroy ) - - # Assigns the given attributes to the association. - # - # If an associated record does not yet exist, one will be instantiated. If - # an associated record already exists, the method's behavior depends on - # the value of the update_only option. If update_only is +false+ and the - # given attributes include an <tt>:id</tt> that matches the existing record's - # id, then the existing record will be modified. If no <tt>:id</tt> is provided - # it will be replaced with a new record. If update_only is +true+ the existing - # record will be modified regardless of whether an <tt>:id</tt> is provided. - # - # If the given attributes include a matching <tt>:id</tt> attribute, or - # 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] - if attributes.respond_to?(:permitted?) - attributes = attributes.to_h - end - attributes = attributes.with_indifferent_access - existing_record = send(association_name) + # Attribute hash keys that should not be assigned as normal attributes. + # These hash keys are nested attributes implementation details. + UNASSIGNABLE_KEYS = %w( id _destroy ) + + # Assigns the given attributes to the association. + # + # If an associated record does not yet exist, one will be instantiated. If + # an associated record already exists, the method's behavior depends on + # the value of the update_only option. If update_only is +false+ and the + # given attributes include an <tt>:id</tt> that matches the existing record's + # id, then the existing record will be modified. If no <tt>:id</tt> is provided + # it will be replaced with a new record. If update_only is +true+ the existing + # record will be modified regardless of whether an <tt>:id</tt> is provided. + # + # If the given attributes include a matching <tt>:id</tt> attribute, or + # 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] + if attributes.respond_to?(:permitted?) + attributes = attributes.to_h + end + attributes = attributes.with_indifferent_access + existing_record = send(association_name) - if (options[:update_only] || !attributes['id'].blank?) && existing_record && - (options[:update_only] || existing_record.id.to_s == attributes['id'].to_s) - assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes) + if (options[:update_only] || !attributes["id"].blank?) && existing_record && + (options[:update_only] || existing_record.id.to_s == attributes["id"].to_s) + assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes) - elsif attributes['id'].present? - raise_nested_attributes_record_not_found!(association_name, attributes['id']) + elsif attributes["id"].present? + raise_nested_attributes_record_not_found!(association_name, attributes["id"]) - elsif !reject_new_record?(association_name, attributes) - assignable_attributes = attributes.except(*UNASSIGNABLE_KEYS) + elsif !reject_new_record?(association_name, attributes) + assignable_attributes = attributes.except(*UNASSIGNABLE_KEYS) - if existing_record && existing_record.new_record? - existing_record.assign_attributes(assignable_attributes) - association(association_name).initialize_attributes(existing_record) - else - method = "build_#{association_name}" - if respond_to?(method) - send(method, assignable_attributes) + if existing_record && existing_record.new_record? + existing_record.assign_attributes(assignable_attributes) + association(association_name).initialize_attributes(existing_record) else - raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?" + method = "build_#{association_name}" + if respond_to?(method) + send(method, assignable_attributes) + else + raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?" + end end end end - end - # Assigns the given attributes to the collection association. - # - # Hashes with an <tt>:id</tt> value matching an existing associated record - # will update that record. Hashes without an <tt>:id</tt> value will build - # a new record for the association. Hashes with a matching <tt>:id</tt> - # value and a <tt>:_destroy</tt> key set to a truthy value will mark the - # matched record for destruction. - # - # For example: - # - # assign_nested_attributes_for_collection_association(:people, { - # '1' => { id: '1', name: 'Peter' }, - # '2' => { name: 'John' }, - # '3' => { id: '2', _destroy: true } - # }) - # - # Will update the name of the Person with ID 1, build a new associated - # person with the name 'John', and mark the associated Person with ID 2 - # for destruction. - # - # Also accepts an Array of attribute hashes: - # - # assign_nested_attributes_for_collection_association(:people, [ - # { id: '1', name: 'Peter' }, - # { name: 'John' }, - # { id: '2', _destroy: true } - # ]) - def assign_nested_attributes_for_collection_association(association_name, attributes_collection) - options = self.nested_attributes_options[association_name] - if attributes_collection.respond_to?(:permitted?) - attributes_collection = attributes_collection.to_h - end + # Assigns the given attributes to the collection association. + # + # Hashes with an <tt>:id</tt> value matching an existing associated record + # will update that record. Hashes without an <tt>:id</tt> value will build + # a new record for the association. Hashes with a matching <tt>:id</tt> + # value and a <tt>:_destroy</tt> key set to a truthy value will mark the + # matched record for destruction. + # + # For example: + # + # assign_nested_attributes_for_collection_association(:people, { + # '1' => { id: '1', name: 'Peter' }, + # '2' => { name: 'John' }, + # '3' => { id: '2', _destroy: true } + # }) + # + # Will update the name of the Person with ID 1, build a new associated + # person with the name 'John', and mark the associated Person with ID 2 + # for destruction. + # + # Also accepts an Array of attribute hashes: + # + # assign_nested_attributes_for_collection_association(:people, [ + # { id: '1', name: 'Peter' }, + # { name: 'John' }, + # { id: '2', _destroy: true } + # ]) + def assign_nested_attributes_for_collection_association(association_name, attributes_collection) + options = self.nested_attributes_options[association_name] + if attributes_collection.respond_to?(:permitted?) + attributes_collection = attributes_collection.to_h + end - unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array) - raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})" - end + unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array) + raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})" + end - check_record_limit!(options[:limit], attributes_collection) + check_record_limit!(options[:limit], attributes_collection) - if attributes_collection.is_a? Hash - keys = attributes_collection.keys - attributes_collection = if keys.include?('id') || keys.include?(:id) - [attributes_collection] - else - attributes_collection.values + if attributes_collection.is_a? Hash + keys = attributes_collection.keys + attributes_collection = if keys.include?("id") || keys.include?(:id) + [attributes_collection] + else + attributes_collection.values + end end - end - - association = association(association_name) - existing_records = if association.loaded? - association.target - else - attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact - attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids) - end + association = association(association_name) - attributes_collection.each do |attributes| - if attributes.respond_to?(:permitted?) - attributes = attributes.to_h + existing_records = if association.loaded? + association.target + else + attribute_ids = attributes_collection.map { |a| a["id"] || a[:id] }.compact + attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids) end - attributes = attributes.with_indifferent_access - if attributes['id'].blank? - unless reject_new_record?(association_name, attributes) - association.build(attributes.except(*UNASSIGNABLE_KEYS)) + attributes_collection.each do |attributes| + if attributes.respond_to?(:permitted?) + attributes = attributes.to_h end - elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s } - unless call_reject_if(association_name, attributes) - # Make sure we are operating on the actual object which is in the association's - # proxy_target array (either by finding it, or adding it if not found) - # Take into account that the proxy_target may have changed due to callbacks - target_record = association.target.detect { |record| record.id.to_s == attributes['id'].to_s } - if target_record - existing_record = target_record - else - association.add_to_target(existing_record, :skip_callbacks) - end + attributes = attributes.with_indifferent_access - assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) + if attributes["id"].blank? + unless reject_new_record?(association_name, attributes) + association.build(attributes.except(*UNASSIGNABLE_KEYS)) + end + elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes["id"].to_s } + unless call_reject_if(association_name, attributes) + # Make sure we are operating on the actual object which is in the association's + # proxy_target array (either by finding it, or adding it if not found) + # Take into account that the proxy_target may have changed due to callbacks + target_record = association.target.detect { |record| record.id.to_s == attributes["id"].to_s } + if target_record + existing_record = target_record + else + association.add_to_target(existing_record, :skip_callbacks) + end + + assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) + end + else + raise_nested_attributes_record_not_found!(association_name, attributes["id"]) end - else - raise_nested_attributes_record_not_found!(association_name, attributes['id']) end end - end - # Takes in a limit and checks if the attributes_collection has too many - # records. It accepts limit in the form of symbol, proc, or - # number-like object (anything that can be compared with an integer). - # - # Raises TooManyRecords error if the attributes_collection is - # larger than the limit. - def check_record_limit!(limit, attributes_collection) - if limit - limit = case limit - when Symbol - send(limit) - when Proc - limit.call - else - limit - end + # Takes in a limit and checks if the attributes_collection has too many + # records. It accepts limit in the form of symbol, proc, or + # number-like object (anything that can be compared with an integer). + # + # Raises TooManyRecords error if the attributes_collection is + # larger than the limit. + def check_record_limit!(limit, attributes_collection) + if limit + limit = \ + case limit + when Symbol + send(limit) + when Proc + limit.call + else + limit + end - if limit && attributes_collection.size > limit - raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead." + if limit && attributes_collection.size > limit + raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead." + end end end - end - # Updates a record with the +attributes+ or marks it for destruction if - # +allow_destroy+ is +true+ and has_destroy_flag? returns +true+. - def assign_to_or_mark_for_destruction(record, attributes, allow_destroy) - record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS)) - record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy - end + # Updates a record with the +attributes+ or marks it for destruction if + # +allow_destroy+ is +true+ and has_destroy_flag? returns +true+. + def assign_to_or_mark_for_destruction(record, attributes, allow_destroy) + record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS)) + record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy + end - # Determines if a hash contains a truthy _destroy key. - def has_destroy_flag?(hash) - Type::Boolean.new.cast(hash['_destroy']) - end + # Determines if a hash contains a truthy _destroy key. + def has_destroy_flag?(hash) + Type::Boolean.new.cast(hash["_destroy"]) + end - # Determines if a new record should be rejected by checking - # has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this - # association and evaluates to +true+. - def reject_new_record?(association_name, attributes) - will_be_destroyed?(association_name, attributes) || call_reject_if(association_name, attributes) - end + # Determines if a new record should be rejected by checking + # has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this + # association and evaluates to +true+. + def reject_new_record?(association_name, attributes) + will_be_destroyed?(association_name, attributes) || call_reject_if(association_name, attributes) + end - # Determines if a record with the particular +attributes+ should be - # rejected by calling the reject_if Symbol or Proc (if defined). - # The reject_if option is defined by +accepts_nested_attributes_for+. - # - # Returns false if there is a +destroy_flag+ on the attributes. - def call_reject_if(association_name, attributes) - return false if will_be_destroyed?(association_name, attributes) + # Determines if a record with the particular +attributes+ should be + # rejected by calling the reject_if Symbol or Proc (if defined). + # The reject_if option is defined by +accepts_nested_attributes_for+. + # + # Returns false if there is a +destroy_flag+ on the attributes. + 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] - when Symbol - method(callback).arity == 0 ? send(callback) : send(callback, attributes) - when Proc - callback.call(attributes) + case callback = self.nested_attributes_options[association_name][:reject_if] + when Symbol + method(callback).arity == 0 ? send(callback) : send(callback, attributes) + when Proc + callback.call(attributes) + end end - end - # Only take into account the destroy flag if <tt>:allow_destroy</tt> is true - def will_be_destroyed?(association_name, attributes) - allow_destroy?(association_name) && has_destroy_flag?(attributes) - end + # Only take into account the destroy flag if <tt>:allow_destroy</tt> is true + def will_be_destroyed?(association_name, attributes) + allow_destroy?(association_name) && has_destroy_flag?(attributes) + end - def allow_destroy?(association_name) - self.nested_attributes_options[association_name][:allow_destroy] - end + def allow_destroy?(association_name) + nested_attributes_options[association_name][:allow_destroy] + end - def raise_nested_attributes_record_not_found!(association_name, record_id) - model = self.class._reflect_on_association(association_name).klass.name - raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}", - model, 'id', record_id) - end + def raise_nested_attributes_record_not_found!(association_name, record_id) + model = self.class._reflect_on_association(association_name).klass.name + raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}", + model, "id", record_id) + end end end diff --git a/activerecord/lib/active_record/no_touching.rb b/activerecord/lib/active_record/no_touching.rb index edb5066fa0..4059020e25 100644 --- a/activerecord/lib/active_record/no_touching.rb +++ b/activerecord/lib/active_record/no_touching.rb @@ -45,6 +45,10 @@ module ActiveRecord NoTouching.applied_to?(self.class) end + def touch_later(*) # :nodoc: + super unless no_touching? + end + def touch(*) # :nodoc: super unless no_touching? end diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb index 0b500346bc..254550c378 100644 --- a/activerecord/lib/active_record/null_relation.rb +++ b/activerecord/lib/active_record/null_relation.rb @@ -1,9 +1,5 @@ module ActiveRecord module NullRelation # :nodoc: - def exec_queries - @records = [] - end - def pluck(*column_names) [] end @@ -20,10 +16,6 @@ module ActiveRecord 0 end - def size - calculate :size, nil - end - def empty? true end @@ -48,28 +40,8 @@ module ActiveRecord "" end - def count(*) - calculate :count, nil - end - - def sum(*) - calculate :sum, nil - end - - def average(*) - calculate :average, nil - end - - def minimum(*) - calculate :minimum, nil - end - - def maximum(*) - calculate :maximum, nil - end - def calculate(operation, _column_name) - if [:count, :sum, :size].include? operation + if [:count, :sum].include? operation group_values.any? ? Hash.new : 0 elsif [:average, :minimum, :maximum].include?(operation) && group_values.any? Hash.new @@ -85,5 +57,11 @@ module ActiveRecord def or(other) other.spawn end + + private + + def exec_queries + @records = [].freeze + end end end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index d9a394fb71..6933f3f9b8 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -61,12 +61,12 @@ module ActiveRecord # +instantiate+ instead of +new+, finder methods ensure they get new # instances of the appropriate class for each record. # - # See +ActiveRecord::Inheritance#discriminate_class_for_record+ to see + # See <tt>ActiveRecord::Inheritance#discriminate_class_for_record</tt> to see # how this "single-table" inheritance mapping is implemented. - def instantiate(attributes, column_types = {}) + def instantiate(attributes, column_types = {}, &block) klass = discriminate_class_for_record(attributes) attributes = klass.attributes_builder.build_from_database(attributes, column_types) - klass.allocate.init_with('attributes' => attributes, 'new_record' => false) + klass.allocate.init_with("attributes" => attributes, "new_record" => false, &block) end private @@ -107,7 +107,7 @@ module ActiveRecord # # By default, save always runs validations. If any of them fail the action # is cancelled and #save returns +false+, and the record won't be saved. However, if you supply - # validate: false, validations are bypassed altogether. See + # <tt>validate: false</tt>, validations are bypassed altogether. See # ActiveRecord::Validations for more information. # # By default, #save also sets the +updated_at+/+updated_on+ attributes to @@ -134,7 +134,7 @@ module ActiveRecord # # By default, #save! always runs validations. If any of them fail # ActiveRecord::RecordInvalid gets raised, and the record won't be saved. However, if you supply - # validate: false, validations are bypassed altogether. See + # <tt>validate: false</tt>, validations are bypassed altogether. See # ActiveRecord::Validations for more information. # # By default, #save! also sets the +updated_at+/+updated_on+ attributes to @@ -178,7 +178,7 @@ module ActiveRecord # and #destroy returns +false+. # See ActiveRecord::Callbacks for further details. def destroy - raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly? + _raise_readonly_record_error if readonly? destroy_associations self.class.connection.add_transaction_record(self) destroy_row if persisted? @@ -252,7 +252,8 @@ module ActiveRecord name = name.to_s verify_readonly_attribute(name) public_send("#{name}=", value) - save(validate: false) if changed? + + changed? ? save(validate: false) : true end # Updates the attributes of the model from the passed-in hash and saves the @@ -439,7 +440,7 @@ module ActiveRecord self.class.unscoped { self.class.find(id) } end - @attributes = fresh_object.instance_variable_get('@attributes') + @attributes = fresh_object.instance_variable_get("@attributes") @new_record = false self end @@ -479,7 +480,12 @@ module ActiveRecord # ball.touch(:updated_at) # => raises ActiveRecordError # def touch(*names, time: nil) - raise ActiveRecordError, "cannot touch on a new record object" unless persisted? + unless persisted? + raise ActiveRecordError, <<-MSG.squish + cannot touch on a new or destroyed record object. Consider using + persisted?, new_record?, or destroyed? before touching + MSG + end time ||= current_time_from_proper_timezone attributes = timestamp_attributes_for_update_in_model @@ -493,7 +499,6 @@ module ActiveRecord changes[column] = write_attribute(column, time) end - clear_attribute_changes(changes.keys) primary_key = self.class.primary_key scope = self.class.unscoped.where(primary_key => _read_attribute(primary_key)) @@ -503,6 +508,7 @@ module ActiveRecord changes[locking_column] = increment_lock end + clear_attribute_changes(changes.keys) result = scope.update_all(changes) == 1 if !result && locking_enabled? @@ -530,7 +536,7 @@ module ActiveRecord end def create_or_update(*args) - raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly? + _raise_readonly_record_error if readonly? result = new_record? ? _create_record : _update_record(*args) result != false end @@ -572,5 +578,9 @@ module ActiveRecord def belongs_to_touch_method :touch end + + def _raise_readonly_record_error + raise ReadOnlyRecord, "#{self.class} is marked as readonly" + end end end diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb index dcb2bd3d84..c42c22ab09 100644 --- a/activerecord/lib/active_record/query_cache.rb +++ b/activerecord/lib/active_record/query_cache.rb @@ -5,7 +5,7 @@ module ActiveRecord # Enable the query cache within the block if Active Record is configured. # If it's not, it will execute the given block. def cache(&block) - if ActiveRecord::Base.connected? + if connected? connection.cache(&block) else yield @@ -15,7 +15,7 @@ module ActiveRecord # Disable the query cache within the block if Active Record is configured. # If it's not, it will execute the given block. def uncached(&block) - if ActiveRecord::Base.connected? + if connected? connection.uncached(&block) else yield @@ -23,34 +23,25 @@ module ActiveRecord end end - def initialize(app) - @app = app - end - - def call(env) + def self.run connection = ActiveRecord::Base.connection enabled = connection.query_cache_enabled - connection_id = ActiveRecord::Base.connection_id connection.enable_query_cache! - response = @app.call(env) - response[2] = Rack::BodyProxy.new(response[2]) do - restore_query_cache_settings(connection_id, enabled) - end - - response - rescue Exception => e - restore_query_cache_settings(connection_id, enabled) - raise e + [connection, enabled] end - private + def self.complete((connection, enabled)) + connection.clear_query_cache + connection.disable_query_cache! unless enabled - def restore_query_cache_settings(connection_id, enabled) - ActiveRecord::Base.connection_id = connection_id - ActiveRecord::Base.connection.clear_query_cache - ActiveRecord::Base.connection.disable_query_cache! unless enabled + unless ActiveRecord::Base.connected? && ActiveRecord::Base.connection.transaction_open? + ActiveRecord::Base.clear_active_connections! + end end + def self.install_executor_hooks(executor = ActiveSupport::Executor) + executor.register_hook(self) + end end end diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index de5b42e987..36689f6559 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -1,6 +1,6 @@ module ActiveRecord module Querying - delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, to: :all + delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :none?, :one?, to: :all delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!, to: :all delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all @@ -9,7 +9,7 @@ module ActiveRecord delegate :find_each, :find_in_batches, :in_batches, to: :all delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :left_joins, :left_outer_joins, :or, :where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly, - :having, :create_with, :uniq, :distinct, :references, :none, :unscope, to: :all + :having, :create_with, :uniq, :distinct, :references, :none, :unscope, :merge, to: :all delegate :count, :average, :minimum, :maximum, :sum, :calculate, to: :all delegate :pluck, :ids, to: :all @@ -35,7 +35,7 @@ module ActiveRecord # # Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date] # Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }] - def find_by_sql(sql, binds = [], preparable: nil) + def find_by_sql(sql, binds = [], preparable: nil, &block) result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable) column_types = result_set.column_types.dup columns_hash.each_key { |k| column_types.delete k } @@ -46,8 +46,8 @@ module ActiveRecord class_name: name } - message_bus.instrument('instantiation.active_record', payload) do - result_set.map { |record| instantiate(record, column_types) } + message_bus.instrument("instantiation.active_record", payload) do + result_set.map { |record| instantiate(record, column_types, &block) } end end @@ -62,8 +62,7 @@ module ActiveRecord # # * +sql+ - An SQL statement which should return a count query from the database, see the example above. def count_by_sql(sql) - sql = sanitize_conditions(sql) - connection.select_value(sql, "#{name} Count").to_i + connection.select_value(sanitize_sql(sql), "#{name} Count").to_i end end end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index f4200e96b7..989d23bc37 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -3,7 +3,7 @@ require "rails" require "active_model/railtie" # For now, action_controller must always be present with -# rails, so let's make sure that it gets required before +# Rails, so let's make sure that it gets required before # here. This is needed for correctly setting up the middleware. # In the future, this might become an optional require. require "action_controller/railtie" @@ -13,23 +13,16 @@ module ActiveRecord class Railtie < Rails::Railtie # :nodoc: config.active_record = ActiveSupport::OrderedOptions.new - config.app_generators.orm :active_record, :migration => true, - :timestamps => true - - config.app_middleware.insert_after ::ActionDispatch::Callbacks, - ActiveRecord::QueryCache - - config.app_middleware.insert_after ::ActionDispatch::Callbacks, - ActiveRecord::ConnectionAdapters::ConnectionManagement + config.app_generators.orm :active_record, migration: true, + timestamps: true config.action_dispatch.rescue_responses.merge!( - 'ActiveRecord::RecordNotFound' => :not_found, - 'ActiveRecord::StaleObjectError' => :conflict, - 'ActiveRecord::RecordInvalid' => :unprocessable_entity, - 'ActiveRecord::RecordNotSaved' => :unprocessable_entity + "ActiveRecord::RecordNotFound" => :not_found, + "ActiveRecord::StaleObjectError" => :conflict, + "ActiveRecord::RecordInvalid" => :unprocessable_entity, + "ActiveRecord::RecordNotSaved" => :unprocessable_entity ) - config.active_record.use_schema_cache_dump = true config.active_record.maintain_test_schema = true @@ -41,8 +34,8 @@ module ActiveRecord ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration if defined?(ENGINE_ROOT) && engine = Rails::Engine.find(ENGINE_ROOT) - if engine.paths['db/migrate'].existent - ActiveRecord::Tasks::DatabaseTasks.migrations_paths += engine.paths['db/migrate'].to_a + if engine.paths["db/migrate"].existent + ActiveRecord::Tasks::DatabaseTasks.migrations_paths += engine.paths["db/migrate"].to_a end end end @@ -71,7 +64,6 @@ module ActiveRecord ActiveSupport.on_load(:active_record) do self.time_zone_aware_attributes = true self.default_timezone = :utc - self.time_zone_aware_types = ActiveRecord::Base.time_zone_aware_types end end @@ -109,7 +101,7 @@ module ActiveRecord initializer "active_record.warn_on_records_fetched_greater_than" do if config.active_record.warn_on_records_fetched_greater_than ActiveSupport.on_load(:active_record) do - require 'active_record/relation/record_fetch_warning' + require "active_record/relation/record_fetch_warning" end end end @@ -153,11 +145,9 @@ end_warning end end - initializer "active_record.set_reloader_hooks" do |app| - hook = app.config.reload_classes_only_on_change ? :to_prepare : :to_cleanup - + initializer "active_record.set_reloader_hooks" do ActiveSupport.on_load(:active_record) do - ActionDispatch::Reloader.send(hook) do + ActiveSupport::Reloader.before_class_unload do if ActiveRecord::Base.connected? ActiveRecord::Base.clear_cache! ActiveRecord::Base.clear_reloadable_connections! @@ -166,6 +156,12 @@ end_warning end end + initializer "active_record.set_executor_hooks" do + ActiveSupport.on_load(:active_record) do + ActiveRecord::QueryCache.install_executor_hooks + end + end + initializer "active_record.add_watchable_files" do |app| path = app.paths["db"].first config.watchable_files.concat ["#{path}/schema.rb", "#{path}/structure.sql"] diff --git a/activerecord/lib/active_record/railties/controller_runtime.rb b/activerecord/lib/active_record/railties/controller_runtime.rb index 8727e46cb3..adb3c6c4e6 100644 --- a/activerecord/lib/active_record/railties/controller_runtime.rb +++ b/activerecord/lib/active_record/railties/controller_runtime.rb @@ -1,5 +1,5 @@ -require 'active_support/core_ext/module/attr_internal' -require 'active_record/log_subscriber' +require "active_support/core_ext/module/attr_internal" +require "active_record/log_subscriber" module ActiveRecord module Railties # :nodoc: @@ -19,7 +19,7 @@ module ActiveRecord end def cleanup_view_runtime - if logger.info? && ActiveRecord::Base.connected? + if logger && logger.info? && ActiveRecord::Base.connected? db_rt_before_render = ActiveRecord::LogSubscriber.reset_runtime self.db_runtime = (db_runtime || 0) + db_rt_before_render runtime = super diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 69a7838001..46235ab922 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -1,4 +1,4 @@ -require 'active_record' +require "active_record" db_namespace = namespace :db do desc "Set the environment value for the database" @@ -7,7 +7,7 @@ db_namespace = namespace :db do ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment end - task :check_protected_environments => [:environment, :load_config] do + task check_protected_environments: [:environment, :load_config] do ActiveRecord::Tasks::DatabaseTasks.check_protected_environments! end @@ -17,24 +17,24 @@ db_namespace = namespace :db do end namespace :create do - task :all => :load_config do + task all: :load_config do ActiveRecord::Tasks::DatabaseTasks.create_all end end - desc 'Creates the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:create:all to create all databases in the config). Without RAILS_ENV, it defaults to creating the development and test databases.' - task :create => [:load_config] do + desc "Creates the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:create:all to create all databases in the config). Without RAILS_ENV or when RAILS_ENV is development, it defaults to creating the development and test databases." + task create: [:load_config] do ActiveRecord::Tasks::DatabaseTasks.create_current end namespace :drop do - task :all => [:load_config, :check_protected_environments] do + task all: [:load_config, :check_protected_environments] do ActiveRecord::Tasks::DatabaseTasks.drop_all end end - desc 'Drops the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV, it defaults to dropping the development and test databases.' - task :drop => [:load_config, :check_protected_environments] do + desc "Drops the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV or when RAILS_ENV is development, it defaults to dropping the development and test databases." + task drop: [:load_config, :check_protected_environments] do db_namespace["drop:_unsafe"].invoke end @@ -43,20 +43,20 @@ db_namespace = namespace :db do end namespace :purge do - task :all => [:load_config, :check_protected_environments] do + task all: [:load_config, :check_protected_environments] do ActiveRecord::Tasks::DatabaseTasks.purge_all end end # desc "Empty the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:purge:all to purge all databases in the config). Without RAILS_ENV it defaults to purging the development and test databases." - task :purge => [:load_config, :check_protected_environments] do + task purge: [:load_config, :check_protected_environments] do ActiveRecord::Tasks::DatabaseTasks.purge_current end desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)." - task :migrate => [:environment, :load_config] do + task migrate: [:environment, :load_config] do ActiveRecord::Tasks::DatabaseTasks.migrate - db_namespace['_dump'].invoke + db_namespace["_dump"].invoke end # IMPORTANT: This task won't dump the schema if ActiveRecord::Base.dump_schema_after_migration is set to false @@ -71,44 +71,44 @@ db_namespace = namespace :db do end # Allow this task to be called as many times as required. An example is the # migrate:redo task, which calls other two internally that depend on this one. - db_namespace['_dump'].reenable + db_namespace["_dump"].reenable end namespace :migrate do # desc 'Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x).' - task :redo => [:environment, :load_config] do - if ENV['VERSION'] - db_namespace['migrate:down'].invoke - db_namespace['migrate:up'].invoke + task redo: [:environment, :load_config] do + if ENV["VERSION"] + db_namespace["migrate:down"].invoke + db_namespace["migrate:up"].invoke else - db_namespace['rollback'].invoke - db_namespace['migrate'].invoke + db_namespace["rollback"].invoke + db_namespace["migrate"].invoke end end # desc 'Resets your database using your migrations for the current environment' - task :reset => ['db:drop', 'db:create', 'db:migrate'] + task reset: ["db:drop", "db:create", "db:migrate"] # desc 'Runs the "up" for a given migration VERSION.' - task :up => [:environment, :load_config] do - version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil - raise 'VERSION is required' unless version + task up: [:environment, :load_config] do + version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil + raise "VERSION is required" unless version ActiveRecord::Migrator.run(:up, ActiveRecord::Tasks::DatabaseTasks.migrations_paths, version) - db_namespace['_dump'].invoke + db_namespace["_dump"].invoke end # desc 'Runs the "down" for a given migration VERSION.' - task :down => [:environment, :load_config] do - version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil - raise 'VERSION is required - To go down one migration, run db:rollback' unless version + task down: [:environment, :load_config] do + version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil + raise "VERSION is required - To go down one migration, run db:rollback" unless version ActiveRecord::Migrator.run(:down, ActiveRecord::Tasks::DatabaseTasks.migrations_paths, version) - db_namespace['_dump'].invoke + db_namespace["_dump"].invoke end - desc 'Display status of migrations' - task :status => [:environment, :load_config] do + desc "Display status of migrations" + task status: [:environment, :load_config] do unless ActiveRecord::SchemaMigration.table_exists? - abort 'Schema migrations table does not exist yet.' + abort "Schema migrations table does not exist yet." end db_list = ActiveRecord::SchemaMigration.normalized_versions @@ -119,13 +119,13 @@ db_namespace = namespace :db do version, name, scope = ActiveRecord::Migrator.parse_migration_filename(file) version = ActiveRecord::SchemaMigration.normalize_migration_number(version) - status = db_list.delete(version) ? 'up' : 'down' + status = db_list.delete(version) ? "up" : "down" [status, version, (name + scope).humanize] end.compact end db_list.map! do |version| - ['up', version, '********** NO FILE **********'] + ["up", version, "********** NO FILE **********"] end # output puts "\ndatabase: #{ActiveRecord::Base.connection_config[:database]}\n\n" @@ -138,93 +138,93 @@ db_namespace = namespace :db do end end - desc 'Rolls the schema back to the previous version (specify steps w/ STEP=n).' - task :rollback => [:environment, :load_config] do - step = ENV['STEP'] ? ENV['STEP'].to_i : 1 + desc "Rolls the schema back to the previous version (specify steps w/ STEP=n)." + task rollback: [:environment, :load_config] do + step = ENV["STEP"] ? ENV["STEP"].to_i : 1 ActiveRecord::Migrator.rollback(ActiveRecord::Tasks::DatabaseTasks.migrations_paths, step) - db_namespace['_dump'].invoke + db_namespace["_dump"].invoke end # desc 'Pushes the schema to the next version (specify steps w/ STEP=n).' - task :forward => [:environment, :load_config] do - step = ENV['STEP'] ? ENV['STEP'].to_i : 1 + task forward: [:environment, :load_config] do + step = ENV["STEP"] ? ENV["STEP"].to_i : 1 ActiveRecord::Migrator.forward(ActiveRecord::Tasks::DatabaseTasks.migrations_paths, step) - db_namespace['_dump'].invoke + db_namespace["_dump"].invoke end # desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.' - task :reset => [ 'db:drop', 'db:setup' ] + task reset: [ "db:drop", "db:setup" ] # desc "Retrieves the charset for the current environment's database" - task :charset => [:environment, :load_config] do + task charset: [:environment, :load_config] do puts ActiveRecord::Tasks::DatabaseTasks.charset_current end # desc "Retrieves the collation for the current environment's database" - task :collation => [:environment, :load_config] do + task collation: [:environment, :load_config] do begin puts ActiveRecord::Tasks::DatabaseTasks.collation_current rescue NoMethodError - $stderr.puts 'Sorry, your database adapter is not supported yet. Feel free to submit a patch.' + $stderr.puts "Sorry, your database adapter is not supported yet. Feel free to submit a patch." end end - desc 'Retrieves the current schema version number' - task :version => [:environment, :load_config] do + desc "Retrieves the current schema version number" + task version: [:environment, :load_config] do puts "Current version: #{ActiveRecord::Migrator.current_version}" end # desc "Raises an error if there are pending migrations" - task :abort_if_pending_migrations => [:environment, :load_config] do + task abort_if_pending_migrations: [:environment, :load_config] do pending_migrations = ActiveRecord::Migrator.open(ActiveRecord::Tasks::DatabaseTasks.migrations_paths).pending_migrations if pending_migrations.any? puts "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}" pending_migrations.each do |pending_migration| - puts ' %4d %s' % [pending_migration.version, pending_migration.name] + puts " %4d %s" % [pending_migration.version, pending_migration.name] end abort %{Run `rails db:migrate` to update your database then try again.} end end - desc 'Creates the database, loads the schema, and initializes with the seed data (use db:reset to also drop the database first)' - task :setup => ['db:schema:load_if_ruby', 'db:structure:load_if_sql', :seed] + desc "Creates the database, loads the schema, and initializes with the seed data (use db:reset to also drop the database first)" + task setup: ["db:schema:load_if_ruby", "db:structure:load_if_sql", :seed] - desc 'Loads the seed data from db/seeds.rb' + desc "Loads the seed data from db/seeds.rb" task :seed do - db_namespace['abort_if_pending_migrations'].invoke + db_namespace["abort_if_pending_migrations"].invoke ActiveRecord::Tasks::DatabaseTasks.load_seed end namespace :fixtures do desc "Loads fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures." - task :load => [:environment, :load_config] do - require 'active_record/fixtures' + task load: [:environment, :load_config] do + require "active_record/fixtures" base_dir = ActiveRecord::Tasks::DatabaseTasks.fixtures_path - fixtures_dir = if ENV['FIXTURES_DIR'] - File.join base_dir, ENV['FIXTURES_DIR'] - else - base_dir - end + fixtures_dir = if ENV["FIXTURES_DIR"] + File.join base_dir, ENV["FIXTURES_DIR"] + else + base_dir + end - fixture_files = if ENV['FIXTURES'] - ENV['FIXTURES'].split(',') - else - # The use of String#[] here is to support namespaced fixtures - Dir["#{fixtures_dir}/**/*.yml"].map {|f| f[(fixtures_dir.size + 1)..-5] } - end + fixture_files = if ENV["FIXTURES"] + ENV["FIXTURES"].split(",") + else + # The use of String#[] here is to support namespaced fixtures. + Dir["#{fixtures_dir}/**/*.yml"].map { |f| f[(fixtures_dir.size + 1)..-5] } + end ActiveRecord::FixtureSet.create_fixtures(fixtures_dir, fixture_files) end # desc "Search for a fixture given a LABEL or ID. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures." - task :identify => [:environment, :load_config] do - require 'active_record/fixtures' + task identify: [:environment, :load_config] do + require "active_record/fixtures" - label, id = ENV['LABEL'], ENV['ID'] - raise 'LABEL or ID required' if label.blank? && id.blank? + label, id = ENV["LABEL"], ENV["ID"] + raise "LABEL or ID required" if label.blank? && id.blank? puts %Q(The fixture ID for "#{label}" is #{ActiveRecord::FixtureSet.identify(label)}.) if label @@ -245,49 +245,49 @@ db_namespace = namespace :db do end namespace :schema do - desc 'Creates a db/schema.rb file that is portable against any DB supported by Active Record' - task :dump => [:environment, :load_config] do - require 'active_record/schema_dumper' - filename = ENV['SCHEMA'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, 'schema.rb') + desc "Creates a db/schema.rb file that is portable against any DB supported by Active Record" + task dump: [:environment, :load_config] do + require "active_record/schema_dumper" + filename = ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema.rb") File.open(filename, "w:utf-8") do |file| ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file) end - db_namespace['schema:dump'].reenable + db_namespace["schema:dump"].reenable end - desc 'Loads a schema.rb file into the database' - task :load => [:environment, :load_config] do - ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:ruby, ENV['SCHEMA']) + desc "Loads a schema.rb file into the database" + task load: [:environment, :load_config, :check_protected_environments] do + ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:ruby, ENV["SCHEMA"]) end - task :load_if_ruby => ['db:create', :environment] do + task load_if_ruby: ["db:create", :environment] do db_namespace["schema:load"].invoke if ActiveRecord::Base.schema_format == :ruby end namespace :cache do - desc 'Creates a db/schema_cache.dump file.' - task :dump => [:environment, :load_config] do + desc "Creates a db/schema_cache.dump 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)) } + open(filename, "wb") { |f| f.write(Marshal.dump(con.schema_cache)) } end - desc 'Clears a db/schema_cache.dump file.' - task :clear => [:environment, :load_config] do + desc "Clears a db/schema_cache.dump file." + task clear: [:environment, :load_config] do filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump") - FileUtils.rm(filename) if File.exist?(filename) + rm_f filename, verbose: false end end end namespace :structure do - desc 'Dumps the database structure to db/structure.sql. Specify another file with SCHEMA=db/my_structure.sql' - task :dump => [:environment, :load_config] do - filename = ENV['SCHEMA'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql") + desc "Dumps the database structure to db/structure.sql. Specify another file with SCHEMA=db/my_structure.sql" + task dump: [:environment, :load_config] do + filename = ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql") current_config = ActiveRecord::Tasks::DatabaseTasks.current_config ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename) @@ -298,15 +298,15 @@ db_namespace = namespace :db do f.print "\n" end end - db_namespace['structure:dump'].reenable + db_namespace["structure:dump"].reenable end desc "Recreates the databases from the structure.sql file" - task :load => [:environment, :load_config] do - ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:sql, ENV['SCHEMA']) + task load: [:environment, :load_config, :check_protected_environments] do + ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:sql, ENV["SCHEMA"]) end - task :load_if_sql => ['db:create', :environment] do + task load_if_sql: ["db:create", :environment] do db_namespace["structure:load"].invoke if ActiveRecord::Base.schema_format == :sql end end @@ -321,21 +321,21 @@ db_namespace = namespace :db do end # desc "Recreate the test database from the current schema" - task :load => %w(db:test:purge) do + task load: %w(db:test:purge) do case ActiveRecord::Base.schema_format - when :ruby - db_namespace["test:load_schema"].invoke - when :sql - db_namespace["test:load_structure"].invoke + when :ruby + db_namespace["test:load_schema"].invoke + when :sql + db_namespace["test:load_structure"].invoke end end # desc "Recreate the test database from an existent schema.rb file" - task :load_schema => %w(db:test:purge) do + task load_schema: %w(db:test:purge) do begin should_reconnect = ActiveRecord::Base.connection_pool.active_connection? ActiveRecord::Schema.verbose = false - ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations['test'], :ruby, ENV['SCHEMA'] + ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations["test"], :ruby, ENV["SCHEMA"] ensure if should_reconnect ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env]) @@ -344,35 +344,35 @@ db_namespace = namespace :db do end # desc "Recreate the test database from an existent structure.sql file" - task :load_structure => %w(db:test:purge) do - ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations['test'], :sql, ENV['SCHEMA'] + task load_structure: %w(db:test:purge) 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 + 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 + 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) + 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) + 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'] + task purge: %w(environment load_config check_protected_environments) do + ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations["test"] end # desc 'Load the test schema' - task :prepare => %w(environment load_config) do + task prepare: %w(environment load_config) do unless ActiveRecord::Base.configurations.blank? - db_namespace['test:load'].invoke + db_namespace["test:load"].invoke end end end @@ -381,13 +381,13 @@ end namespace :railties do namespace :install do # desc "Copies missing migrations from Railties (e.g. engines). You can specify Railties to use with FROM=railtie1,railtie2" - task :migrations => :'db:load_config' do - to_load = ENV['FROM'].blank? ? :all : ENV['FROM'].split(",").map(&:strip) + task migrations: :'db:load_config' do + to_load = ENV["FROM"].blank? ? :all : ENV["FROM"].split(",").map(&:strip) railties = {} Rails.application.migration_railties.each do |railtie| next unless to_load == :all || to_load.include?(railtie.railtie_name) - if railtie.respond_to?(:paths) && (path = railtie.paths['db/migrate'].first) + if railtie.respond_to?(:paths) && (path = railtie.paths["db/migrate"].first) railties[railtie.railtie_name] = path end end @@ -401,7 +401,7 @@ namespace :railties do end ActiveRecord::Migration.copy(ActiveRecord::Tasks::DatabaseTasks.migrations_paths.first, railties, - :on_skip => on_skip, :on_copy => on_copy) + on_skip: on_skip, on_copy: on_copy) end end end diff --git a/activerecord/lib/active_record/railties/jdbcmysql_error.rb b/activerecord/lib/active_record/railties/jdbcmysql_error.rb index 6a38211bff..d7cf4df339 100644 --- a/activerecord/lib/active_record/railties/jdbcmysql_error.rb +++ b/activerecord/lib/active_record/railties/jdbcmysql_error.rb @@ -3,7 +3,7 @@ module ArJdbcMySQL #:nodoc: class Error < StandardError #:nodoc: attr_accessor :error_number, :sql_state - def initialize msg + def initialize(msg) super @error_number = nil @sql_state = nil diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb index ce78f1756d..8ff265bdfa 100644 --- a/activerecord/lib/active_record/readonly_attributes.rb +++ b/activerecord/lib/active_record/readonly_attributes.rb @@ -16,7 +16,7 @@ module ActiveRecord # Returns an array of all the attributes that have been specified as readonly. def readonly_attributes - self._attr_readonly + _attr_readonly end end end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 956fe7c51e..ce70c29093 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -1,5 +1,5 @@ -require 'thread' -require 'active_support/core_ext/string/filters' +require "thread" +require "active_support/core_ext/string/filters" module ActiveRecord # = Active Record Reflection @@ -14,18 +14,19 @@ module ActiveRecord end def self.create(macro, name, scope, options, ar) - klass = case macro - when :composed_of - AggregateReflection - when :has_many - HasManyReflection - when :has_one - HasOneReflection - when :belongs_to - BelongsToReflection - else - raise "Unsupported Macro: #{macro}" - end + klass = \ + case macro + when :composed_of + AggregateReflection + when :has_many + HasManyReflection + when :has_one + HasOneReflection + when :belongs_to + BelongsToReflection + else + raise "Unsupported Macro: #{macro}" + end reflection = klass.new(name, scope, options, ar) options[:through] ? ThroughReflection.new(reflection) : reflection @@ -135,9 +136,13 @@ module ActiveRecord # BelongsToReflection # HasAndBelongsToManyReflection # ThroughReflection - # PolymorphicReflection - # RuntimeReflection + # PolymorphicReflection + # RuntimeReflection class AbstractReflection # :nodoc: + def through_reflection? + false + end + def table_name klass.table_name end @@ -277,6 +282,10 @@ module ActiveRecord end def autosave=(autosave) + # autosave and inverse_of do not get along together nowadays. They may + # for example cause double saves. Thus, we disable this flag. If in the + # future those two flags are known to work well together, this could be + # removed. @automatic_inverse_of = false @options[:autosave] = autosave parent_reflection = self.parent_reflection @@ -307,13 +316,16 @@ module ActiveRecord active_record == other_aggregation.active_record end + def scope_for(klass) + scope ? klass.unscoped.instance_exec(nil, &scope) : klass.unscoped + end + private def derive_class_name name.to_s.camelize end end - # Holds all the meta-data about an aggregation as it was specified in the # Active Record class. class AggregateReflection < MacroReflection #:nodoc: @@ -445,6 +457,10 @@ module ActiveRecord scope ? [[scope]] : [[]] end + def has_scope? + scope + end + def has_inverse? inverse_name end @@ -692,7 +708,7 @@ module ActiveRecord class ThroughReflection < AbstractReflection #:nodoc: attr_reader :delegate_reflection delegate :foreign_key, :foreign_type, :association_foreign_key, - :active_record_primary_key, :type, :to => :source_reflection + :active_record_primary_key, :type, to: :source_reflection def initialize(delegate_reflection) @delegate_reflection = delegate_reflection @@ -700,6 +716,10 @@ module ActiveRecord @source_reflection_name = delegate_reflection.options[:source] end + def through_reflection? + true + end + def klass @klass ||= delegate_reflection.compute_class(class_name) end @@ -765,7 +785,6 @@ module ActiveRecord # This is for clearing cache on the reflection. Useful for tests that need to compare # SQL queries on associations. def clear_association_scope_cache # :nodoc: - @chain = nil delegate_reflection.clear_association_scope_cache source_reflection.clear_association_scope_cache through_reflection.clear_association_scope_cache @@ -812,13 +831,19 @@ module ActiveRecord end end + def has_scope? + scope || options[:source_type] || + source_reflection.has_scope? || + through_reflection.has_scope? + end + def join_keys(association_klass) source_reflection.join_keys(association_klass) end # A through association is nested if there would be more than one join table def nested? - chain.length > 2 + source_reflection.through_reflection? || through_reflection.through_reflection? end # We want to use the klass from this reflection, rather than just delegate straight to @@ -857,7 +882,7 @@ module ActiveRecord example_options = options.dup example_options[:source] = source_reflection_names.first ActiveSupport::Deprecation.warn \ - "Ambiguous source reflection for through association. Please " \ + "Ambiguous source reflection for through association. Please " \ "specify a :source directive on your declaration like:\n" \ "\n" \ " class #{active_record.name} < ActiveRecord::Base\n" \ @@ -961,10 +986,9 @@ module ActiveRecord public_instance_methods delegate(*delegate_methods, to: :delegate_reflection) - end - class PolymorphicReflection < ThroughReflection # :nodoc: + class PolymorphicReflection < AbstractReflection # :nodoc: def initialize(reflection, previous_reflection) @reflection = reflection @previous_reflection = previous_reflection @@ -995,7 +1019,7 @@ module ActiveRecord end def constraints - [source_type_info] + @reflection.constraints + [source_type_info] end def source_type_info diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 7e842668c6..ef629dcb3b 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -1,5 +1,3 @@ -require "arel/collectors/bind" - module ActiveRecord # = Active Record \Relation class Relation @@ -31,9 +29,7 @@ module ActiveRecord end def initialize_copy(other) - # This method is a hot spot, so for now, use Hash[] to dup the hash. - # https://bugs.ruby-lang.org/issues/7166 - @values = Hash[@values] + @values = @values.dup reset end @@ -45,8 +41,8 @@ module ActiveRecord k.name == primary_key }] - if !primary_key_value && connection.prefetch_primary_key?(klass.table_name) - primary_key_value = connection.next_sequence_value(klass.sequence_name) + if !primary_key_value && klass.prefetch_primary_key? + primary_key_value = klass.next_sequence_value values[arel_attribute(klass.primary_key)] = primary_key_value end end @@ -64,8 +60,8 @@ module ActiveRecord @klass.connection.insert( im, - 'SQL', - primary_key, + "SQL", + primary_key || false, primary_key_value, nil, binds) @@ -88,18 +84,18 @@ module ActiveRecord @klass.connection.update( um, - 'SQL', + "SQL", bvs, ) end def substitute_values(values) # :nodoc: - binds = values.map do |arel_attr, value| - QueryAttribute.new(arel_attr.name, value, klass.type_for_attribute(arel_attr.name)) - end + binds = [] + substitutes = [] - substitutes = values.map do |(arel_attr, _)| - [arel_attr, Arel::Nodes::BindParam.new] + values.each do |arel_attr, value| + binds.push QueryAttribute.new(arel_attr.name, value, klass.type_for_attribute(arel_attr.name)) + substitutes.push [arel_attr, Arel::Nodes::BindParam.new] end [substitutes, binds] @@ -247,23 +243,26 @@ module ActiveRecord # Please see further details in the # {Active Record Query Interface guide}[http://guides.rubyonrails.org/active_record_querying.html#running-explain]. def explain - #TODO: Fix for binds. exec_explain(collecting_queries_for_explain { exec_queries }) end # Converts relation objects to Array. def to_a + records.dup + end + + def records # :nodoc: load @records end # Serializes the relation objects Array. def encode_with(coder) - coder.represent_seq(nil, to_a) + coder.represent_seq(nil, records) end def as_json(options = nil) #:nodoc: - to_a.as_json(options) + records.as_json(options) end # Returns size of the records. @@ -275,12 +274,7 @@ module ActiveRecord def empty? return @records.empty? if loaded? - if limit_value == 0 - true - else - c = count(:all) - c.respond_to?(:zero?) ? c.zero? : c.empty? - end + limit_value == 0 || !exists? end # Returns true if there are no records. @@ -298,13 +292,13 @@ module ActiveRecord # Returns true if there is exactly one record. def one? return super if block_given? - limit_value ? to_a.one? : size == 1 + limit_value ? records.one? : size == 1 end # Returns true if there is more than one record. def many? return super if block_given? - limit_value ? to_a.many? : size > 1 + limit_value ? records.many? : size > 1 end # Returns a cache key that can be used to identify the records fetched by @@ -385,7 +379,7 @@ module ActiveRecord stmt.wheres = arel.constraints end - @klass.connection.update stmt, 'SQL', bound_attributes + @klass.connection.update stmt, "SQL", bound_attributes end # Updates an object (or multiple objects) and saves it to the database, if validations pass. @@ -418,13 +412,13 @@ module ActiveRecord if id.is_a?(Array) id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) } elsif id == :all - to_a.each { |record| record.update(attributes) } + records.each { |record| record.update(attributes) } else if ActiveRecord::Base === id id = id.id ActiveSupport::Deprecation.warn(<<-MSG.squish) You are passing an instance of ActiveRecord::Base to `update`. - Please pass the id of the object by calling `.id` + Please pass the id of the object by calling `.id`. MSG end object = find(id) @@ -453,11 +447,11 @@ module ActiveRecord 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 + To achieve the same use where(conditions).destroy_all. MESSAGE where(conditions).destroy_all else - to_a.each(&:destroy).tap { reset } + records.each(&:destroy).tap { reset } end end @@ -507,15 +501,10 @@ module ActiveRecord # Post.limit(100).delete_all # # => ActiveRecord::ActiveRecordError: delete_all doesn't support limit def delete_all(conditions = nil) - invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select { |method| - if MULTI_VALUE_METHODS.include?(method) - send("#{method}_values").any? - elsif SINGLE_VALUE_METHODS.include?(method) - send("#{method}_value") - elsif CLAUSE_METHODS.include?(method) - send("#{method}_clause").any? - end - } + invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method| + value = get_value(method) + SINGLE_VALUE_METHODS.include?(method) ? value : value.any? + end if invalid_methods.any? raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}") end @@ -523,7 +512,7 @@ module ActiveRecord 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 + To achieve the same use where(conditions).delete_all. MESSAGE where(conditions).delete_all else @@ -536,7 +525,7 @@ module ActiveRecord stmt.wheres = arel.constraints end - affected = @klass.connection.delete(stmt, 'SQL', bound_attributes) + affected = @klass.connection.delete(stmt, "SQL", bound_attributes) reset affected @@ -572,8 +561,8 @@ module ActiveRecord # return value is the relation itself, not the records. # # Post.where(published: true).load # => #<ActiveRecord::Relation> - def load - exec_queries unless loaded? + def load(&block) + exec_queries(&block) unless loaded? self end @@ -587,7 +576,7 @@ module ActiveRecord def reset @last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil @should_eager_load = @join_dependency = nil - @records = [] + @records = [].freeze @offsets = {} self end @@ -598,19 +587,16 @@ module ActiveRecord # # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar' def to_sql @to_sql ||= begin - relation = self - connection = klass.connection - visitor = connection.visitor + relation = self if eager_loading? find_with_associations { |rel| relation = rel } end - binds = relation.bound_attributes - binds = connection.prepare_binds_for_database(binds) - binds.map! { |value| connection.quote(value) } - collect = visitor.accept(relation.arel.ast, Arel::Collectors::Bind.new) - collect.substitute_binds(binds).join + conn = klass.connection + conn.unprepared_statement { + conn.to_sql(relation.arel, relation.bound_attributes) + } end end @@ -654,30 +640,30 @@ module ActiveRecord def ==(other) case other when Associations::CollectionProxy, AssociationRelation - self == other.to_a + self == other.records when Relation other.to_sql == to_sql when Array - to_a == other + records == other end end def pretty_print(q) - q.pp(self.to_a) + q.pp(records) end # Returns true if relation is blank. def blank? - to_a.blank? + records.blank? end def values - Hash[@values] + @values.dup end def inspect - entries = to_a.take([limit_value, 11].compact.min).map!(&:inspect) - entries[10] = '...' if entries.size == 11 + entries = records.take([limit_value, 11].compact.min).map!(&:inspect) + entries[10] = "..." if entries.size == 11 "#<#{self.class.name} [#{entries.join(', ')}]>" end @@ -685,54 +671,54 @@ module ActiveRecord protected def load_records(records) - @records = records + @records = records.freeze @loaded = true end private - def exec_queries - @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bound_attributes) + def exec_queries(&block) + @records = eager_loading? ? find_with_associations.freeze : @klass.find_by_sql(arel, bound_attributes, &block).freeze - preload = preload_values - preload += includes_values unless eager_loading? - preloader = build_preloader - preload.each do |associations| - preloader.preload @records, associations - end + preload = preload_values + preload += includes_values unless eager_loading? + preloader = build_preloader + preload.each do |associations| + preloader.preload @records, associations + end - @records.each(&:readonly!) if readonly_value + @records.each(&:readonly!) if readonly_value - @loaded = true - @records - end + @loaded = true + @records + end - def build_preloader - ActiveRecord::Associations::Preloader.new - end + def build_preloader + ActiveRecord::Associations::Preloader.new + end - def references_eager_loaded_tables? - joined_tables = arel.join_sources.map do |join| - if join.is_a?(Arel::Nodes::StringJoin) - tables_in_string(join.left) - else - [join.left.table_name, join.left.table_alias] + def references_eager_loaded_tables? + joined_tables = arel.join_sources.map do |join| + if join.is_a?(Arel::Nodes::StringJoin) + tables_in_string(join.left) + else + [join.left.table_name, join.left.table_alias] + end end - end - joined_tables += [table.name, table.table_alias] + joined_tables += [table.name, table.table_alias] - # always convert table names to downcase as in Oracle quoted table names are in uppercase - joined_tables = joined_tables.flatten.compact.map(&:downcase).uniq + # always convert table names to downcase as in Oracle quoted table names are in uppercase + joined_tables = joined_tables.flatten.compact.map(&:downcase).uniq - (references_values - joined_tables).any? - end + (references_values - joined_tables).any? + end - def tables_in_string(string) - return [] if string.blank? - # always convert table names to downcase as in Oracle quoted table names are in uppercase - # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries - string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map(&:downcase).uniq - ['raw_sql_'] - end + def tables_in_string(string) + return [] if string.blank? + # always convert table names to downcase as in Oracle quoted table names are in uppercase + # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries + string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map(&:downcase).uniq - ["raw_sql_"] + end end end diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index de005e2810..4b2987ac6d 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -2,6 +2,8 @@ require "active_record/relation/batches/batch_enumerator" module ActiveRecord module Batches + ORDER_IGNORE_MESSAGE = "Scoped order is ignored, it's forced to be batch order." + # Looping through a collection of records from the database # (using the Scoping::Named::ClassMethods.all method, for example) # is very inefficient since it will try to instantiate all the objects at once. @@ -31,13 +33,20 @@ module ActiveRecord # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000. # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value. # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value. - # This is especially useful if you want multiple workers dealing with - # the same processing queue. You can make worker 1 handle all the records - # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond - # (by setting the +:start+ and +:finish+ option on each worker). + # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when + # an order is present in the relation. + # + # Limits are honored, and if present there is no requirement for the batch + # size, it can be less than, equal, or greater than the limit. + # + # The options +start+ and +finish+ are especially useful if you want + # multiple workers dealing with the same processing queue. You can make + # worker 1 handle all the records between id 1 and 9999 and worker 2 + # handle from 10000 and beyond by setting the +:start+ and +:finish+ + # option on each worker. # - # # Let's process for a batch of 2000 records, skipping the first 2000 rows - # Person.find_each(start: 2000, batch_size: 2000) do |person| + # # Let's process from record 10_000 on. + # Person.find_each(start: 10_000) do |person| # person.party_all_night! # end # @@ -46,15 +55,15 @@ module ActiveRecord # work. This also means that this method only works when the primary key is # orderable (e.g. an integer or string). # - # NOTE: You can't set the limit either, that's used to control - # the batch sizes. - def find_each(start: nil, finish: nil, batch_size: 1000) + # NOTE: By its nature, batch processing is subject to race conditions if + # other processes are modifying the database. + def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil) if block_given? - find_in_batches(start: start, finish: finish, batch_size: batch_size) do |records| + find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records| records.each { |record| yield record } end else - enum_for(:find_each, start: start, finish: finish, batch_size: batch_size) do + enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do relation = self apply_limits(relation, start, finish).size end @@ -83,13 +92,20 @@ module ActiveRecord # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000. # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value. # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value. - # This is especially useful if you want multiple workers dealing with - # the same processing queue. You can make worker 1 handle all the records - # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond - # (by setting the +:start+ and +:finish+ option on each worker). + # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when + # an order is present in the relation. + # + # Limits are honored, and if present there is no requirement for the batch + # size, it can be less than, equal, or greater than the limit. + # + # The options +start+ and +finish+ are especially useful if you want + # multiple workers dealing with the same processing queue. You can make + # worker 1 handle all the records between id 1 and 9999 and worker 2 + # handle from 10000 and beyond by setting the +:start+ and +:finish+ + # option on each worker. # - # # Let's process the next 2000 records - # Person.find_in_batches(start: 2000, batch_size: 2000) do |group| + # # Let's process from record 10_000 on. + # Person.find_in_batches(start: 10_000) do |group| # group.each { |person| person.party_all_night! } # end # @@ -98,18 +114,18 @@ module ActiveRecord # work. This also means that this method only works when the primary key is # orderable (e.g. an integer or string). # - # NOTE: You can't set the limit either, that's used to control - # the batch sizes. - def find_in_batches(start: nil, finish: nil, batch_size: 1000) + # NOTE: By its nature, batch processing is subject to race conditions if + # other processes are modifying the database. + def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil) relation = self unless block_given? - return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size) do + return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do total = apply_limits(relation, start, finish).size (total - 1).div(batch_size) + 1 end end - in_batches(of: batch_size, start: start, finish: finish, load: true) do |batch| + in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore) do |batch| yield batch.to_a end end @@ -140,16 +156,20 @@ module ActiveRecord # * <tt>:load</tt> - Specifies if the relation should be loaded. Default to false. # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value. # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value. + # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when + # an order is present in the relation. # - # This is especially useful if you want to work with the - # ActiveRecord::Relation object instead of the array of records, or if - # you want multiple workers dealing with the same processing queue. You can - # make worker 1 handle all the records between id 0 and 10,000 and worker 2 - # handle from 10,000 and beyond (by setting the +:start+ and +:finish+ - # option on each worker). + # Limits are honored, and if present there is no requirement for the batch + # size, it can be less than, equal, or greater than the limit. # - # # Let's process the next 2000 records - # Person.in_batches(of: 2000, start: 2000).update_all(awesome: true) + # The options +start+ and +finish+ are especially useful if you want + # multiple workers dealing with the same processing queue. You can make + # worker 1 handle all the records between id 1 and 9999 and worker 2 + # handle from 10000 and beyond by setting the +:start+ and +:finish+ + # option on each worker. + # + # # Let's process from record 10_000 on. + # Person.in_batches(start: 10_000).update_all(awesome: true) # # An example of calling where query method on the relation: # @@ -169,31 +189,37 @@ module ActiveRecord # consistent. Therefore the primary key must be orderable, e.g an integer # or a string. # - # NOTE: You can't set the limit either, that's used to control the batch - # sizes. - def in_batches(of: 1000, start: nil, finish: nil, load: false) + # NOTE: By its nature, batch processing is subject to race conditions if + # other processes are modifying the database. + def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil) relation = self unless block_given? return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self) end - if logger && (arel.orders.present? || arel.taken.present?) - logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size") + if arel.orders.present? + act_on_ignored_order(error_on_ignore) + end + + batch_limit = of + if limit_value + remaining = limit_value + batch_limit = remaining if remaining < batch_limit end - relation = relation.reorder(batch_order).limit(of) + relation = relation.reorder(batch_order).limit(batch_limit) relation = apply_limits(relation, start, finish) batch_relation = relation loop do if load - records = batch_relation.to_a + records = batch_relation.records ids = records.map(&:id) - yielded_relation = self.where(primary_key => ids) + yielded_relation = where(primary_key => ids) yielded_relation.load_records(records) else ids = batch_relation.pluck(primary_key) - yielded_relation = self.where(primary_key => ids) + yielded_relation = where(primary_key => ids) end break if ids.empty? @@ -203,21 +229,44 @@ module ActiveRecord yield yielded_relation - break if ids.length < of + break if ids.length < batch_limit + + if limit_value + remaining -= ids.length + + if remaining == 0 + # Saves a useless iteration when the limit is a multiple of the + # batch size. + break + elsif remaining < batch_limit + relation = relation.limit(remaining) + end + end + batch_relation = relation.where(arel_attribute(primary_key).gt(primary_key_offset)) end end private - def apply_limits(relation, start, finish) - relation = relation.where(arel_attribute(primary_key).gteq(start)) if start - relation = relation.where(arel_attribute(primary_key).lteq(finish)) if finish - relation - end + def apply_limits(relation, start, finish) + relation = relation.where(arel_attribute(primary_key).gteq(start)) if start + relation = relation.where(arel_attribute(primary_key).lteq(finish)) if finish + relation + end - def batch_order - "#{quoted_table_name}.#{quoted_primary_key} ASC" - end + def batch_order + "#{quoted_table_name}.#{quoted_primary_key} ASC" + end + + def act_on_ignored_order(error_on_ignore) + raise_error = (error_on_ignore.nil? ? self.klass.error_on_ignored_order : error_on_ignore) + + if raise_error + raise ArgumentError.new(ORDER_IGNORE_MESSAGE) + elsif logger + logger.warn(ORDER_IGNORE_MESSAGE) + end + end end end diff --git a/activerecord/lib/active_record/relation/batches/batch_enumerator.rb b/activerecord/lib/active_record/relation/batches/batch_enumerator.rb index c6e39814dd..333b3a63cf 100644 --- a/activerecord/lib/active_record/relation/batches/batch_enumerator.rb +++ b/activerecord/lib/active_record/relation/batches/batch_enumerator.rb @@ -35,14 +35,14 @@ module ActiveRecord return to_enum(:each_record) unless block_given? @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true).each do |relation| - relation.to_a.each { |record| yield record } + relation.records.each { |record| yield record } end end # Delegates #delete_all, #update_all, #destroy_all methods to each batch. # # People.in_batches.delete_all - # People.in_batches.destroy_all('age < 10') + # People.where('age < 10').in_batches.destroy_all # People.in_batches.update_all('age = age + 1') [:delete_all, :update_all, :destroy_all].each do |method| define_method(method) do |*args, &block| diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 54c9af4898..e4676f79a5 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -37,7 +37,11 @@ module ActiveRecord # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ # between databases. In invalid cases, an error from the database is thrown. def count(column_name = nil) - calculate(:count, column_name) + if block_given? + to_a.count { |*block_args| yield(*block_args) } + else + calculate(:count, column_name) + end end # Calculates the average value on a given column. Returns +nil+ if there's @@ -89,7 +93,7 @@ module ActiveRecord # # There are two basic forms of output: # - # * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float + # * Single aggregate value: The single value is type cast to Integer for COUNT, Float # for AVG, and the given column's type for everything else. # # * Grouped values: This returns an ordered hash of the values and groups them. It @@ -108,12 +112,11 @@ module ActiveRecord # ... # end def calculate(operation, column_name) - if column_name.is_a?(Symbol) && attribute_alias?(column_name) - column_name = attribute_alias(column_name) - end - if has_include?(column_name) - construct_relation_for_association_calculations.calculate(operation, column_name) + relation = construct_relation_for_association_calculations + relation = relation.distinct if operation.to_s.downcase == "count" + + relation.calculate(operation, column_name) else perform_calculation(operation, column_name) end @@ -156,7 +159,7 @@ module ActiveRecord # def pluck(*column_names) if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty? - return @records.pluck(*column_names) + return records.pluck(*column_names) end if has_include?(column_names.first) @@ -181,201 +184,196 @@ module ActiveRecord private - def has_include?(column_name) - eager_loading? || (includes_values.present? && column_name && column_name != :all) - end - - def perform_calculation(operation, column_name) - operation = operation.to_s.downcase + def has_include?(column_name) + eager_loading? || (includes_values.present? && column_name && column_name != :all) + end - # If #count is used with #distinct (i.e. `relation.distinct.count`) it is - # considered distinct. - distinct = self.distinct_value + def perform_calculation(operation, column_name) + operation = operation.to_s.downcase - if operation == "count" - column_name ||= select_for_count + # If #count is used with #distinct (i.e. `relation.distinct.count`) it is + # considered distinct. + distinct = self.distinct_value - unless arel.ast.grep(Arel::Nodes::OuterJoin).empty? - distinct = true + if operation == "count" + column_name ||= select_for_count + column_name = primary_key if column_name == :all && distinct + distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i end - column_name = primary_key if column_name == :all && distinct - distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i - end - - if group_values.any? - execute_grouped_calculation(operation, column_name, distinct) - else - execute_simple_calculation(operation, column_name, distinct) + if group_values.any? + execute_grouped_calculation(operation, column_name, distinct) + else + execute_simple_calculation(operation, column_name, distinct) + end end - end - def aggregate_column(column_name) - return column_name if Arel::Expressions === column_name + def aggregate_column(column_name) + return column_name if Arel::Expressions === column_name - if @klass.column_names.include?(column_name.to_s) - Arel::Attribute.new(@klass.unscoped.table, column_name) - else - Arel.sql(column_name == :all ? "*" : column_name.to_s) + if @klass.has_attribute?(column_name.to_s) || @klass.attribute_alias?(column_name.to_s) + @klass.arel_attribute(column_name) + else + Arel.sql(column_name == :all ? "*" : column_name.to_s) + end end - end - - def operation_over_aggregate_column(column, operation, distinct) - operation == 'count' ? column.count(distinct) : column.send(operation) - end - - def execute_simple_calculation(operation, column_name, distinct) #:nodoc: - # PostgreSQL doesn't like ORDER BY when there are no GROUP BY - relation = unscope(:order) - column_alias = column_name + def operation_over_aggregate_column(column, operation, distinct) + operation == "count" ? column.count(distinct) : column.send(operation) + end - if operation == "count" && (relation.limit_value || relation.offset_value) - # Shortcut when limit is zero. - return 0 if relation.limit_value == 0 + def execute_simple_calculation(operation, column_name, distinct) #:nodoc: + # PostgreSQL doesn't like ORDER BY when there are no GROUP BY + relation = unscope(:order) - query_builder = build_count_subquery(relation, column_name, distinct) - else - column = aggregate_column(column_name) + column_alias = column_name - select_value = operation_over_aggregate_column(column, operation, distinct) + if operation == "count" && (relation.limit_value || relation.offset_value) + # Shortcut when limit is zero. + return 0 if relation.limit_value == 0 - column_alias = select_value.alias - column_alias ||= @klass.connection.column_name_for_operation(operation, select_value) - relation.select_values = [select_value] + query_builder = build_count_subquery(relation, column_name, distinct) + else + column = aggregate_column(column_name) - query_builder = relation.arel - end + select_value = operation_over_aggregate_column(column, operation, distinct) - result = @klass.connection.select_all(query_builder, nil, bound_attributes) - row = result.first - value = row && row.values.first - column = result.column_types.fetch(column_alias) do - type_for(column_name) - end + column_alias = select_value.alias + column_alias ||= @klass.connection.column_name_for_operation(operation, select_value) + relation.select_values = [select_value] - type_cast_calculated_value(value, column, operation) - end + query_builder = relation.arel + end - def execute_grouped_calculation(operation, column_name, distinct) #:nodoc: - group_attrs = group_values + result = @klass.connection.select_all(query_builder, nil, bound_attributes) + row = result.first + value = row && row.values.first + type = result.column_types.fetch(column_alias) do + type_for(column_name) + end - if group_attrs.first.respond_to?(:to_sym) - association = @klass._reflect_on_association(group_attrs.first) - associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations - group_fields = Array(associated ? association.foreign_key : group_attrs) - else - group_fields = group_attrs + type_cast_calculated_value(value, type, operation) end - group_fields = arel_columns(group_fields) - group_aliases = group_fields.map { |field| column_alias_for(field) } - group_columns = group_aliases.zip(group_fields) + def execute_grouped_calculation(operation, column_name, distinct) #:nodoc: + group_attrs = group_values - if operation == 'count' && column_name == :all - aggregate_alias = 'count_all' - else - aggregate_alias = column_alias_for([operation, column_name].join(' ')) - end - - select_values = [ - operation_over_aggregate_column( - aggregate_column(column_name), - operation, - distinct).as(aggregate_alias) - ] - select_values += select_values unless having_clause.empty? - - select_values.concat group_columns.map { |aliaz, field| - if field.respond_to?(:as) - field.as(aliaz) + if group_attrs.first.respond_to?(:to_sym) + association = @klass._reflect_on_association(group_attrs.first) + associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations + group_fields = Array(associated ? association.foreign_key : group_attrs) else - "#{field} AS #{aliaz}" + group_fields = group_attrs end - } - - relation = except(:group) - relation.group_values = group_fields - relation.select_values = select_values + group_fields = arel_columns(group_fields) - calculated_data = @klass.connection.select_all(relation, nil, relation.bound_attributes) + group_aliases = group_fields.map { |field| column_alias_for(field) } + group_columns = group_aliases.zip(group_fields) - if association - key_ids = calculated_data.collect { |row| row[group_aliases.first] } - key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids) - key_records = Hash[key_records.map { |r| [r.id, r] }] - end + if operation == "count" && column_name == :all + aggregate_alias = "count_all" + else + aggregate_alias = column_alias_for([operation, column_name].join(" ")) + end - Hash[calculated_data.map do |row| - key = group_columns.map { |aliaz, col_name| - column = calculated_data.column_types.fetch(aliaz) do - type_for(col_name) + select_values = [ + operation_over_aggregate_column( + aggregate_column(column_name), + operation, + distinct).as(aggregate_alias) + ] + select_values += select_values unless having_clause.empty? + + select_values.concat group_columns.map { |aliaz, field| + if field.respond_to?(:as) + field.as(aliaz) + else + "#{field} AS #{aliaz}" end - type_cast_calculated_value(row[aliaz], column) } - key = key.first if key.size == 1 - key = key_records[key] if associated - column_type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) } - [key, type_cast_calculated_value(row[aggregate_alias], column_type, operation)] - end] - end + relation = except(:group) + relation.group_values = group_fields + relation.select_values = select_values - # Converts the given keys to the value that the database adapter returns as - # a usable column name: - # - # column_alias_for("users.id") # => "users_id" - # column_alias_for("sum(id)") # => "sum_id" - # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id" - # column_alias_for("count(*)") # => "count_all" - def column_alias_for(keys) - if keys.respond_to? :name - keys = "#{keys.relation.name}.#{keys.name}" + calculated_data = @klass.connection.select_all(relation, nil, relation.bound_attributes) + + if association + key_ids = calculated_data.collect { |row| row[group_aliases.first] } + key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids) + key_records = Hash[key_records.map { |r| [r.id, r] }] + end + + Hash[calculated_data.map do |row| + key = group_columns.map { |aliaz, col_name| + type = type_for(col_name) do + calculated_data.column_types.fetch(aliaz, Type.default_value) + end + type_cast_calculated_value(row[aliaz], type) + } + key = key.first if key.size == 1 + key = key_records[key] if associated + + type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) } + [key, type_cast_calculated_value(row[aggregate_alias], type, operation)] + end] end - table_name = keys.to_s.downcase - table_name.gsub!(/\*/, 'all') - table_name.gsub!(/\W+/, ' ') - table_name.strip! - table_name.gsub!(/ +/, '_') + # Converts the given keys to the value that the database adapter returns as + # a usable column name: + # + # column_alias_for("users.id") # => "users_id" + # column_alias_for("sum(id)") # => "sum_id" + # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id" + # column_alias_for("count(*)") # => "count_all" + def column_alias_for(keys) + if keys.respond_to? :name + keys = "#{keys.relation.name}.#{keys.name}" + end - @klass.connection.table_alias_for(table_name) - end + table_name = keys.to_s.downcase + table_name.gsub!(/\*/, "all") + table_name.gsub!(/\W+/, " ") + table_name.strip! + table_name.gsub!(/ +/, "_") - def type_for(field) - field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last - @klass.type_for_attribute(field_name) - end + @klass.connection.table_alias_for(table_name) + end - def type_cast_calculated_value(value, type, operation = nil) - case operation - when 'count' then value.to_i - when 'sum' then type.deserialize(value || 0) - when 'average' then value.respond_to?(:to_d) ? value.to_d : value + def type_for(field, &block) + field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last + @klass.type_for_attribute(field_name, &block) + end + + def type_cast_calculated_value(value, type, operation = nil) + case operation + when "count" then value.to_i + when "sum" then type.deserialize(value || 0) + when "average" then value.respond_to?(:to_d) ? value.to_d : value else type.deserialize(value) + end end - end - def select_for_count - if select_values.present? - return select_values.first if select_values.one? - select_values.join(", ") - else - :all + def select_for_count + if select_values.present? + return select_values.first if select_values.one? + select_values.join(", ") + else + :all + end end - end - def build_count_subquery(relation, column_name, distinct) - column_alias = Arel.sql('count_column') - subquery_alias = Arel.sql('subquery_for_count') + def build_count_subquery(relation, column_name, distinct) + column_alias = Arel.sql("count_column") + subquery_alias = Arel.sql("subquery_for_count") - aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias) - relation.select_values = [aliased_column] - subquery = relation.arel.as(subquery_alias) + aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias) + relation.select_values = [aliased_column] + subquery = relation.arel.as(subquery_alias) - sm = Arel::SelectManager.new relation.engine - select_value = operation_over_aggregate_column(column_alias, 'count', distinct) - sm.project(select_value).from(subquery) - end + sm = Arel::SelectManager.new relation.engine + select_value = operation_over_aggregate_column(column_alias, "count", distinct) + sm.project(select_value).from(subquery) + end end end diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index e4e5d63006..4b9310b225 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -1,6 +1,3 @@ -require 'set' -require 'active_support/concern' - module ActiveRecord module Delegation # :nodoc: module DelegateCache # :nodoc: @@ -18,7 +15,7 @@ module ActiveRecord delegate = Class.new(klass) { include ClassSpecificRelation } - const_set klass.name.gsub('::'.freeze, '_'.freeze), delegate + const_set klass.name.gsub("::".freeze, "_".freeze), delegate cache[klass] = delegate end end @@ -36,11 +33,12 @@ module ActiveRecord # may vary depending on the klass of a relation, so we create a subclass of Relation # for each different klass, and the delegations are compiled into that subclass only. - delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join, - :[], :&, :|, :+, :-, :sample, :shuffle, :reverse, :compact, to: :to_a + delegate :to_xml, :encode_with, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join, + :[], :&, :|, :+, :-, :sample, :reverse, :compact, :in_groups, :in_groups_of, + :shuffle, :split, :index, to: :records delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, - :connection, :columns_hash, :to => :klass + :connection, :columns_hash, to: :klass module ClassSpecificRelation # :nodoc: extend ActiveSupport::Concern @@ -58,7 +56,7 @@ module ActiveRecord @delegation_mutex.synchronize do return if method_defined?(method) - if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/ + if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) module_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{method}(*args, &block) scoping { @klass.#{method}(*args, &block) } @@ -82,17 +80,17 @@ module ActiveRecord protected - def method_missing(method, *args, &block) - if @klass.respond_to?(method) - self.class.delegate_to_scoped_klass(method) - scoping { @klass.public_send(method, *args, &block) } - elsif arel.respond_to?(method) - self.class.delegate method, :to => :arel - arel.public_send(method, *args, &block) - else - super + def method_missing(method, *args, &block) + if @klass.respond_to?(method) + self.class.delegate_to_scoped_klass(method) + scoping { @klass.public_send(method, *args, &block) } + elsif arel.respond_to?(method) + self.class.delegate method, to: :arel + arel.public_send(method, *args, &block) + else + super + end end - end end module ClassMethods # :nodoc: @@ -102,26 +100,26 @@ module ActiveRecord private - def relation_class_for(klass) - klass.relation_delegate_class(self) - end + def relation_class_for(klass) + klass.relation_delegate_class(self) + end end - def respond_to?(method, include_private = false) + def respond_to_missing?(method, include_private = false) super || @klass.respond_to?(method, include_private) || arel.respond_to?(method, include_private) end protected - def method_missing(method, *args, &block) - if @klass.respond_to?(method) - scoping { @klass.public_send(method, *args, &block) } - elsif arel.respond_to?(method) - arel.public_send(method, *args, &block) - else - super + def method_missing(method, *args, &block) + if @klass.respond_to?(method) + scoping { @klass.public_send(method, *args, &block) } + elsif arel.respond_to?(method) + arel.public_send(method, *args, &block) + else + super + end end - end end end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 8b114c6bdf..97a819c5af 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -1,8 +1,8 @@ -require 'active_support/core_ext/string/filters' +require "active_support/core_ext/string/filters" module ActiveRecord module FinderMethods - ONE_AS_ONE = '1 AS one' + ONE_AS_ONE = "1 AS one" # Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]). # If one or more records can not be found for the requested ids, then RecordNotFound will be raised. If the primary key @@ -42,10 +42,10 @@ module ActiveRecord # Person.find_by(name: 'Spartacus', rating: 4) # # returns the first item or nil. # - # Person.where(name: 'Spartacus', rating: 4).first_or_initialize + # Person.find_or_initialize_by(name: 'Spartacus', rating: 4) # # returns the first item or returns a new instance (requires you call .save to persist against the database). # - # Person.where(name: 'Spartacus', rating: 4).first_or_create + # Person.find_or_create_by(name: 'Spartacus', rating: 4) # # returns the first item or creates it and returns it. # # ==== Alternatives for #find @@ -97,13 +97,13 @@ module ActiveRecord # Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5 # Person.where(["name LIKE '%?'", name]).take def take(limit = nil) - limit ? limit(limit).to_a : find_take + limit ? find_take_with_limit(limit) : find_take end # Same as #take but raises ActiveRecord::RecordNotFound if no record # is found. Note that #take! accepts no arguments. def take! - take or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]") + take || raise_record_not_found_exception! end # Find the first record (or first N records if a parameter is supplied). @@ -117,7 +117,7 @@ module ActiveRecord # def first(limit = nil) if limit - find_nth_with_limit_and_offset(0, limit, offset: offset_index) + find_nth_with_limit(0, limit) else find_nth 0 end @@ -126,7 +126,7 @@ module ActiveRecord # Same as #first but raises ActiveRecord::RecordNotFound if no record # is found. Note that #first! accepts no arguments. def first! - find_nth! 0 + first || raise_record_not_found_exception! end # Find the last record (or last N records if a parameter is supplied). @@ -165,7 +165,7 @@ module ActiveRecord # Same as #last but raises ActiveRecord::RecordNotFound if no record # is found. Note that #last! accepts no arguments. def last! - last or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]") + last || raise_record_not_found_exception! end # Find the second record. @@ -181,7 +181,7 @@ module ActiveRecord # Same as #second but raises ActiveRecord::RecordNotFound if no record # is found. def second! - find_nth! 1 + second || raise_record_not_found_exception! end # Find the third record. @@ -197,7 +197,7 @@ module ActiveRecord # Same as #third but raises ActiveRecord::RecordNotFound if no record # is found. def third! - find_nth! 2 + third || raise_record_not_found_exception! end # Find the fourth record. @@ -213,7 +213,7 @@ module ActiveRecord # Same as #fourth but raises ActiveRecord::RecordNotFound if no record # is found. def fourth! - find_nth! 3 + fourth || raise_record_not_found_exception! end # Find the fifth record. @@ -229,7 +229,7 @@ module ActiveRecord # Same as #fifth but raises ActiveRecord::RecordNotFound if no record # is found. def fifth! - find_nth! 4 + fifth || raise_record_not_found_exception! end # Find the forty-second record. Also known as accessing "the reddit". @@ -245,7 +245,7 @@ module ActiveRecord # Same as #forty_two but raises ActiveRecord::RecordNotFound if no record # is found. def forty_two! - find_nth! 41 + forty_two || raise_record_not_found_exception! end # Find the third-to-last record. @@ -255,13 +255,13 @@ module ActiveRecord # Person.offset(3).third_to_last # returns the third-to-last object from OFFSET 3 # Person.where(["user_name = :u", { u: user_name }]).third_to_last def third_to_last - find_nth(-3) + find_nth_from_last 3 end # Same as #third_to_last but raises ActiveRecord::RecordNotFound if no record # is found. def third_to_last! - find_nth!(-3) + third_to_last || raise_record_not_found_exception! end # Find the second-to-last record. @@ -271,13 +271,13 @@ module ActiveRecord # Person.offset(3).second_to_last # returns the second-to-last object from OFFSET 3 # Person.where(["user_name = :u", { u: user_name }]).second_to_last def second_to_last - find_nth(-2) + find_nth_from_last 2 end # Same as #second_to_last but raises ActiveRecord::RecordNotFound if no record # is found. def second_to_last! - find_nth!(-2) + second_to_last || raise_record_not_found_exception! end # Returns true if a record exists in the table that matches the +id+ or @@ -312,13 +312,13 @@ module ActiveRecord conditions = conditions.id ActiveSupport::Deprecation.warn(<<-MSG.squish) You are passing an instance of ActiveRecord::Base to `exists?`. - Please pass the id of the object by calling `.id` + Please pass the id of the object by calling `.id`. MSG end return false if !conditions - relation = apply_join_dependency(self, construct_join_dependency) + relation = apply_join_dependency(self, construct_join_dependency(eager_loading: false)) return false if ActiveRecord::NullRelation === relation relation = relation.except(:select, :order).select(ONE_AS_ONE).limit(1) @@ -333,6 +333,8 @@ module ActiveRecord end connection.select_value(relation, "#{name} Exists", relation.bound_attributes) ? true : false + rescue RangeError + false end # This method is called whenever no records are found with either a single @@ -343,250 +345,237 @@ module ActiveRecord # of results obtained should be provided in the +result_size+ argument and # the expected number of results should be provided in the +expected_size+ # argument. - def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc: + def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil) # :nodoc: conditions = arel.where_sql(@klass.arel_engine) conditions = " [#{conditions}]" if conditions - - if Array(ids).size == 1 - error = "Couldn't find #{@klass.name} with '#{primary_key}'=#{ids}#{conditions}" + name = @klass.name + + if ids.nil? + error = "Couldn't find #{name}" + error << " with#{conditions}" if conditions + raise RecordNotFound.new(error, name) + elsif Array(ids).size == 1 + error = "Couldn't find #{name} with '#{primary_key}'=#{ids}#{conditions}" + raise RecordNotFound.new(error, name, primary_key, ids) else - error = "Couldn't find all #{@klass.name.pluralize} with '#{primary_key}': " + error = "Couldn't find all #{name.pluralize} with '#{primary_key}': " error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})" - end - raise RecordNotFound, error + raise RecordNotFound.new(error, name, primary_key, ids) + end end private - def offset_index - offset_value || 0 - end + def offset_index + offset_value || 0 + end - def find_with_associations - # NOTE: the JoinDependency constructed here needs to know about - # any joins already present in `self`, so pass them in - # - # failing to do so means that in cases like activerecord/test/cases/associations/inner_join_association_test.rb:136 - # incorrect SQL is generated. In that case, the join dependency for - # SpecialCategorizations is constructed without knowledge of the - # preexisting join in joins_values to categorizations (by way of - # the `has_many :through` for categories). - # - join_dependency = construct_join_dependency(joins_values) - - aliases = join_dependency.aliases - relation = select aliases.columns - relation = apply_join_dependency(relation, join_dependency) - - if block_given? - yield relation - else - if ActiveRecord::NullRelation === relation - [] + def find_with_associations + # NOTE: the JoinDependency constructed here needs to know about + # any joins already present in `self`, so pass them in + # + # failing to do so means that in cases like activerecord/test/cases/associations/inner_join_association_test.rb:136 + # incorrect SQL is generated. In that case, the join dependency for + # SpecialCategorizations is constructed without knowledge of the + # preexisting join in joins_values to categorizations (by way of + # the `has_many :through` for categories). + # + join_dependency = construct_join_dependency(joins_values) + + aliases = join_dependency.aliases + relation = select aliases.columns + relation = apply_join_dependency(relation, join_dependency) + + if block_given? + yield relation else - arel = relation.arel - rows = connection.select_all(arel, 'SQL', relation.bound_attributes) - join_dependency.instantiate(rows, aliases) + if ActiveRecord::NullRelation === relation + [] + else + arel = relation.arel + rows = connection.select_all(arel, "SQL", relation.bound_attributes) + join_dependency.instantiate(rows, aliases) + end end end - end - def construct_join_dependency(joins = []) - including = eager_load_values + includes_values - ActiveRecord::Associations::JoinDependency.new(@klass, including, joins) - end + def construct_join_dependency(joins = [], eager_loading: true) + including = eager_load_values + includes_values + ActiveRecord::Associations::JoinDependency.new(@klass, including, joins, eager_loading: eager_loading) + end - def construct_relation_for_association_calculations - from = arel.froms.first - if Arel::Table === from + def construct_relation_for_association_calculations apply_join_dependency(self, construct_join_dependency(joins_values)) - else - # FIXME: as far as I can tell, `from` will always be an Arel::Table. - # There are no tests that test this branch, but presumably it's - # possible for `from` to be a list? - apply_join_dependency(self, construct_join_dependency(from)) end - end - def apply_join_dependency(relation, join_dependency) - relation = relation.except(:includes, :eager_load, :preload) - relation = relation.joins join_dependency + def apply_join_dependency(relation, join_dependency) + relation = relation.except(:includes, :eager_load, :preload) + relation = relation.joins join_dependency - if using_limitable_reflections?(join_dependency.reflections) - relation - else - if relation.limit_value - limited_ids = limited_ids_for(relation) - limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids) + if using_limitable_reflections?(join_dependency.reflections) + relation + else + if relation.limit_value + limited_ids = limited_ids_for(relation) + limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids) + end + relation.except(:limit, :offset) end - relation.except(:limit, :offset) end - end - def limited_ids_for(relation) - values = @klass.connection.columns_for_distinct( - "#{quoted_table_name}.#{quoted_primary_key}", relation.order_values) + def limited_ids_for(relation) + values = @klass.connection.columns_for_distinct( + "#{quoted_table_name}.#{quoted_primary_key}", relation.order_values) - relation = relation.except(:select).select(values).distinct! - arel = relation.arel + relation = relation.except(:select).select(values).distinct! + arel = relation.arel - id_rows = @klass.connection.select_all(arel, 'SQL', relation.bound_attributes) - id_rows.map {|row| row[primary_key]} - end + id_rows = @klass.connection.select_all(arel, "SQL", relation.bound_attributes) + id_rows.map { |row| row[primary_key] } + end - def using_limitable_reflections?(reflections) - reflections.none?(&:collection?) - end + def using_limitable_reflections?(reflections) + reflections.none?(&:collection?) + end protected - 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 + 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 - 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).to_a + 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).to_a.first + def find_take + if loaded? + records.first + else + @take ||= limit(1).records.first + end end - end - def find_nth(index, offset = nil) - # TODO: once the offset argument is removed we rely on offset_index - # within find_nth_with_limit, rather than pass it in via - # find_nth_with_limit_and_offset - if offset - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Passing an offset argument to find_nth is deprecated, - please use Relation#offset instead. - MSG + def find_take_with_limit(limit) + if loaded? + records.take(limit) + else + limit(limit).to_a + end end - if loaded? - @records[index] - else - offset ||= offset_index - @offsets[offset + index] ||= find_nth_with_limit_and_offset(index, 1, offset: offset).first + + def find_nth(index) + @offsets[offset_index + index] ||= find_nth_with_limit(index, 1).first end - end - def find_nth!(index) - find_nth(index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]") - 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) + else + self + end - def find_nth_with_limit(index, limit) - # TODO: once the offset argument is removed from find_nth, - # find_nth_with_limit_and_offset can be merged into this method - relation = if order_values.empty? && primary_key - order(arel_attribute(primary_key).asc) - else - self - end - - relation = relation.offset(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_last - if loaded? - @records.last - else - @last ||= - if limit_value - to_a.last + def find_nth_from_last(index) + if loaded? + records[-index] + else + relation = if order_values.empty? && primary_key + order(arel_attribute(primary_key).asc) else - reverse_order.limit(1).to_a.first + 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 end - end private - def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc: - if loaded? - @records[index, limit] - else - index += offset - find_nth_with_limit(index, limit) + def find_last(limit) + limit ? records.last(limit) : records.last end - end - - def find_last(limit) - limit ? to_a.last(limit) : to_a.last - end end end diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index 396638d74d..5dac00724a 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/hash/keys' +require "active_support/core_ext/hash/keys" module ActiveRecord class Relation @@ -83,85 +83,81 @@ module ActiveRecord private - def merge_preloads - return if other.preload_values.empty? && other.includes_values.empty? + def merge_preloads + return if other.preload_values.empty? && other.includes_values.empty? - if other.klass == relation.klass - relation.preload!(*other.preload_values) unless other.preload_values.empty? - relation.includes!(other.includes_values) unless other.includes_values.empty? - else - reflection = relation.klass.reflect_on_all_associations.find do |r| - r.class_name == other.klass.name - end || return + if other.klass == relation.klass + relation.preload!(*other.preload_values) unless other.preload_values.empty? + relation.includes!(other.includes_values) unless other.includes_values.empty? + else + reflection = relation.klass.reflect_on_all_associations.find do |r| + r.class_name == other.klass.name + end || return - unless other.preload_values.empty? - relation.preload! reflection.name => other.preload_values - end + unless other.preload_values.empty? + relation.preload! reflection.name => other.preload_values + end - unless other.includes_values.empty? - relation.includes! reflection.name => other.includes_values + unless other.includes_values.empty? + relation.includes! reflection.name => other.includes_values + end end end - end - def merge_joins - return if other.joins_values.blank? + def merge_joins + return if other.joins_values.blank? - if other.klass == relation.klass - relation.joins!(*other.joins_values) - else - joins_dependency, rest = other.joins_values.partition do |join| - case join - when Hash, Symbol, Array - true - else - false + if other.klass == relation.klass + relation.joins!(*other.joins_values) + else + joins_dependency, rest = other.joins_values.partition do |join| + case join + when Hash, Symbol, Array + true + else + false + end end - end - - join_dependency = ActiveRecord::Associations::JoinDependency.new(other.klass, - joins_dependency, - []) - relation.joins! rest - @relation = relation.joins join_dependency - end - end + join_dependency = ActiveRecord::Associations::JoinDependency.new(other.klass, + joins_dependency, + []) + relation.joins! rest - def merge_multi_values - if other.reordering_value - # override any order specified in the original relation - relation.reorder! other.order_values - elsif other.order_values - # merge in order_values from relation - relation.order! other.order_values + @relation = relation.joins join_dependency + end end - relation.extend(*other.extending_values) unless other.extending_values.blank? - end + def merge_multi_values + if other.reordering_value + # override any order specified in the original relation + relation.reorder! other.order_values + elsif other.order_values + # merge in order_values from relation + relation.order! other.order_values + end - def merge_single_values - if relation.from_clause.empty? - relation.from_clause = other.from_clause + relation.extend(*other.extending_values) unless other.extending_values.blank? end - relation.lock_value ||= other.lock_value - unless other.create_with_value.blank? - relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value) - end - end + def merge_single_values + if relation.from_clause.empty? + relation.from_clause = other.from_clause + end + relation.lock_value ||= other.lock_value - CLAUSE_METHOD_NAMES = CLAUSE_METHODS.map do |name| - ["#{name}_clause", "#{name}_clause="] - end + unless other.create_with_value.blank? + relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value) + end + end - def merge_clauses - CLAUSE_METHOD_NAMES.each do |(reader, writer)| - clause = relation.send(reader) - other_clause = other.send(reader) - relation.send(writer, clause.merge(other_clause)) + def merge_clauses + CLAUSE_METHODS.each do |method| + clause = relation.get_value(method) + other_clause = other.get_value(method) + relation.set_value(method, clause.merge(other_clause)) + end end - end end end end diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 0f88791d92..780a1ee422 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -1,12 +1,13 @@ module ActiveRecord class PredicateBuilder # :nodoc: - require 'active_record/relation/predicate_builder/array_handler' - 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/range_handler' - require 'active_record/relation/predicate_builder/relation_handler' + require "active_record/relation/predicate_builder/array_handler" + 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" delegate :resolve_column_aliases, to: :table @@ -14,14 +15,15 @@ module ActiveRecord @table = table @handlers = [] - register_handler(BasicObject, BasicObjectHandler.new(self)) + register_handler(BasicObject, BasicObjectHandler.new) register_handler(Class, ClassHandler.new(self)) register_handler(Base, BaseHandler.new(self)) - register_handler(Range, RangeHandler.new(self)) - register_handler(RangeHandler::RangeWithBinds, RangeHandler.new(self)) + register_handler(Range, RangeHandler.new) + register_handler(RangeHandler::RangeWithBinds, RangeHandler.new) register_handler(Relation, RelationHandler.new) register_handler(Array, ArrayHandler.new(self)) register_handler(AssociationQueryValue, AssociationQueryHandler.new(self)) + register_handler(PolymorphicArrayValue, PolymorphicArrayHandler.new(self)) end def build_from_hash(attributes) @@ -34,26 +36,13 @@ module ActiveRecord create_binds_for_hash(attributes) end - def expand(column, value) - # Find the foreign key when using queries such as: - # Post.where(author: author) - # - # For polymorphic relationships, find the foreign key and type: - # PriceEstimate.where(estimate_of: treasure) - if table.associated_with?(column) - value = AssociationQueryValue.new(table.associated_table(column), value) - end - - build(table.arel_attribute(column), value) - end - def self.references(attributes) attributes.map do |key, value| if value.is_a?(Hash) key else key = key.to_s - key.split('.'.freeze).first if key.include?('.'.freeze) + key.split(".".freeze).first if key.include?(".".freeze) end end.compact end @@ -79,89 +68,104 @@ module ActiveRecord protected - attr_reader :table + attr_reader :table - def expand_from_hash(attributes) - return ["1=0"] if attributes.empty? + def expand_from_hash(attributes) + return ["1=0"] if attributes.empty? - attributes.flat_map do |key, value| - if value.is_a?(Hash) - associated_predicate_builder(key).expand_from_hash(value) - else - expand(key, value) + attributes.flat_map do |key, value| + if value.is_a?(Hash) && !table.has_column?(key) + associated_predicate_builder(key).expand_from_hash(value) + else + build(table.arel_attribute(key), value) + end end end - end - - def create_binds_for_hash(attributes) - result = attributes.dup - binds = [] - - attributes.each do |column_name, value| - case value - when Hash - attrs, bvs = associated_predicate_builder(column_name).create_binds_for_hash(value) - result[column_name] = attrs - binds += bvs - when Relation - binds += value.bound_attributes - when Range - first = value.begin - last = value.end - unless first.respond_to?(:infinite?) && first.infinite? - binds << build_bind_param(column_name, first) - first = Arel::Nodes::BindParam.new - end - unless last.respond_to?(:infinite?) && last.infinite? - binds << build_bind_param(column_name, last) - last = Arel::Nodes::BindParam.new + def create_binds_for_hash(attributes) + result = attributes.dup + binds = [] + + attributes.each do |column_name, value| + case + when value.is_a?(Hash) && !table.has_column?(column_name) + attrs, bvs = associated_predicate_builder(column_name).create_binds_for_hash(value) + result[column_name] = attrs + binds += bvs + next + when value.is_a?(Relation) + binds += value.bound_attributes + when value.is_a?(Range) && !table.type(column_name).respond_to?(:subtype) + first = value.begin + last = value.end + unless first.respond_to?(:infinite?) && first.infinite? + binds << build_bind_param(column_name, first) + first = Arel::Nodes::BindParam.new + end + unless last.respond_to?(:infinite?) && last.infinite? + binds << build_bind_param(column_name, last) + last = Arel::Nodes::BindParam.new + end + + result[column_name] = RangeHandler::RangeWithBinds.new(first, last, value.exclude_end?) + else + if can_be_bound?(column_name, value) + result[column_name] = Arel::Nodes::BindParam.new + binds << build_bind_param(column_name, value) + end end - result[column_name] = RangeHandler::RangeWithBinds.new(first, last, value.exclude_end?) - else - if can_be_bound?(column_name, value) - result[column_name] = Arel::Nodes::BindParam.new - binds << build_bind_param(column_name, value) + # Find the foreign key when using queries such as: + # Post.where(author: author) + # + # For polymorphic relationships, find the foreign key and type: + # PriceEstimate.where(estimate_of: treasure) + if table.associated_with?(column_name) + result[column_name] = AssociationQueryHandler.value_for(table, column_name, value) end end - end - [result, binds] - end + [result, binds] + end private - def associated_predicate_builder(association_name) - self.class.new(table.associated_table(association_name)) - end + def associated_predicate_builder(association_name) + self.class.new(table.associated_table(association_name)) + end - def convert_dot_notation_to_hash(attributes) - dot_notation = attributes.keys.select { |s| s.include?(".".freeze) } + def convert_dot_notation_to_hash(attributes) + dot_notation = attributes.select do |k, v| + k.include?(".".freeze) && !v.is_a?(Hash) + end - dot_notation.each do |key| - table_name, column_name = key.split(".".freeze) - value = attributes.delete(key) - attributes[table_name] ||= {} + dot_notation.each_key do |key| + table_name, column_name = key.split(".".freeze) + value = attributes.delete(key) + attributes[table_name] ||= {} - attributes[table_name] = attributes[table_name].merge(column_name => value) - end + attributes[table_name] = attributes[table_name].merge(column_name => value) + end - attributes - end + attributes + end - def handler_for(object) - @handlers.detect { |klass, _| klass === object }.last - end + def handler_for(object) + @handlers.detect { |klass, _| klass === object }.last + end - def can_be_bound?(column_name, value) - !value.nil? && - handler_for(value).is_a?(BasicObjectHandler) && - !table.associated_with?(column_name) - end + def can_be_bound?(column_name, value) + return if table.associated_with?(column_name) + case value + when Array, Range + table.type(column_name).respond_to?(:subtype) + else + !value.nil? && handler_for(value).is_a?(BasicObjectHandler) + end + end - def build_bind_param(column_name, value) - Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name)) - end + def build_bind_param(column_name, value) + Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name)) + end end end 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 95dbd6a77f..6400caba06 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb @@ -31,13 +31,13 @@ module ActiveRecord protected - attr_reader :predicate_builder + attr_reader :predicate_builder - module NullPredicate # :nodoc: - def self.or(other) - other + module NullPredicate # :nodoc: + def self.or(other) + other + end end - end end end end 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 e81be63cd3..7e20cb2c63 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 @@ -1,6 +1,17 @@ module ActiveRecord class PredicateBuilder class AssociationQueryHandler # :nodoc: + def self.value_for(table, column, value) + associated_table = table.associated_table(column) + klass = if associated_table.polymorphic_association? && ::Array === value && value.first.is_a?(Base) + PolymorphicArrayValue + else + AssociationQueryValue + end + + klass.new(associated_table, value) + end + def initialize(predicate_builder) @predicate_builder = predicate_builder end @@ -19,7 +30,7 @@ module ActiveRecord protected - attr_reader :predicate_builder + attr_reader :predicate_builder end class AssociationQueryValue # :nodoc: @@ -49,30 +60,30 @@ module ActiveRecord private - def primary_key - associated_table.association_primary_key(base_class) - end + def primary_key + associated_table.association_primary_key(base_class) + end - def polymorphic_base_class_from_value - 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 + def polymorphic_base_class_from_value + 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 end - end - def convert_to_id(value) - case value - when Base - value._read_attribute(primary_key) - else - value + def convert_to_id(value) + case value + when Base + value._read_attribute(primary_key) + else + value + end end - end end end 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 6fa5b16f73..65c5159704 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb @@ -11,7 +11,7 @@ module ActiveRecord protected - attr_reader :predicate_builder + attr_reader :predicate_builder end end end diff --git a/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb index 6cec75dc0a..79cde00303 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb @@ -1,17 +1,9 @@ module ActiveRecord class PredicateBuilder class BasicObjectHandler # :nodoc: - def initialize(predicate_builder) - @predicate_builder = predicate_builder - end - def call(attribute, value) attribute.eq(value) end - - protected - - attr_reader :predicate_builder end end end diff --git a/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb index ed313fc9d4..0a6574fcf1 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb @@ -12,16 +12,16 @@ module ActiveRecord protected - attr_reader :predicate_builder + 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 + 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 new file mode 100644 index 0000000000..0c7f92b3d0 --- /dev/null +++ b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb @@ -0,0 +1,57 @@ +module ActiveRecord + class PredicateBuilder + class PolymorphicArrayHandler # :nodoc: + def initialize(predicate_builder) + @predicate_builder = predicate_builder + end + + def call(attribute, value) + table = value.associated_table + queries = value.type_to_ids_mapping.map do |type, ids| + { table.association_foreign_type.to_s => type, table.association_foreign_key.to_s => ids } + end + + predicates = queries.map { |query| predicate_builder.build_from_hash(query) } + + if predicates.size > 1 + type_and_ids_predicates = predicates.map { |type_predicate, id_predicate| Arel::Nodes::Grouping.new(type_predicate.and(id_predicate)) } + type_and_ids_predicates.inject(&:or) + else + predicates.first + end + end + + protected + + attr_reader :predicate_builder + end + + class PolymorphicArrayValue # :nodoc: + attr_reader :associated_table, :values + + def initialize(associated_table, values) + @associated_table = associated_table + @values = values + end + + def type_to_ids_mapping + default_hash = Hash.new { |hsh, key| hsh[key] = [] } + values.each_with_object(default_hash) { |value, hash| hash[base_class(value).name] << convert_to_id(value) } + end + + private + + def primary_key(value) + associated_table.association_primary_key(base_class(value)) + end + + def base_class(value) + value.class.base_class + end + + def convert_to_id(value) + value._read_attribute(primary_key(value)) + end + end + end +end diff --git a/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb index 306d4694ae..5db778e19c 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb @@ -3,10 +3,6 @@ module ActiveRecord class RangeHandler # :nodoc: RangeWithBinds = Struct.new(:begin, :end, :exclude_end?) - def initialize(predicate_builder) - @predicate_builder = predicate_builder - end - def call(attribute, value) if value.begin.respond_to?(:infinite?) && value.begin.infinite? if value.end.respond_to?(:infinite?) && value.end.infinite? @@ -24,10 +20,6 @@ module ActiveRecord attribute.between(value) end end - - protected - - attr_reader :predicate_builder end end end diff --git a/activerecord/lib/active_record/relation/query_attribute.rb b/activerecord/lib/active_record/relation/query_attribute.rb index 7ba964e802..a68e508fcc 100644 --- a/activerecord/lib/active_record/relation/query_attribute.rb +++ b/activerecord/lib/active_record/relation/query_attribute.rb @@ -1,4 +1,4 @@ -require 'active_record/attribute' +require "active_record/attribute" module ActiveRecord class Relation diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 91d486e902..2a0dd1c10f 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -2,8 +2,8 @@ require "active_record/relation/from_clause" 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' +require "active_model/forbidden_attributes_protection" +require "active_support/core_ext/string/filters" module ActiveRecord module QueryMethods @@ -55,71 +55,49 @@ module ActiveRecord end FROZEN_EMPTY_ARRAY = [].freeze - Relation::MULTI_VALUE_METHODS.each do |name| - class_eval <<-CODE, __FILE__, __LINE__ + 1 - def #{name}_values - @values[:#{name}] || FROZEN_EMPTY_ARRAY - end + FROZEN_EMPTY_HASH = {}.freeze - def #{name}_values=(values) - assert_mutability! - @values[:#{name}] = values + Relation::VALUE_METHODS.each do |name| + method_name = \ + case name + when *Relation::MULTI_VALUE_METHODS then "#{name}_values" + when *Relation::SINGLE_VALUE_METHODS then "#{name}_value" + when *Relation::CLAUSE_METHODS then "#{name}_clause" end - CODE - end - - (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |name| class_eval <<-CODE, __FILE__, __LINE__ + 1 - def #{name}_value # def readonly_value - @values[:#{name}] # @values[:readonly] + def #{method_name} # def includes_values + get_value(#{name.inspect}) # get_value(:includes) end # end - CODE - end - Relation::SINGLE_VALUE_METHODS.each do |name| - class_eval <<-CODE, __FILE__, __LINE__ + 1 - def #{name}_value=(value) # def readonly_value=(value) - assert_mutability! # assert_mutability! - @values[:#{name}] = value # @values[:readonly] = value + def #{method_name}=(value) # def includes_values=(value) + set_value(#{name.inspect}, value) # set_value(:includes, value) end # end CODE end - Relation::CLAUSE_METHODS.each do |name| - class_eval <<-CODE, __FILE__, __LINE__ + 1 - def #{name}_clause # def where_clause - @values[:#{name}] || new_#{name}_clause # @values[:where] || new_where_clause - end # end - # - def #{name}_clause=(value) # def where_clause=(value) - assert_mutability! # assert_mutability! - @values[:#{name}] = value # @values[:where] = value - end # end - CODE - end - def bound_attributes - result = from_clause.binds + arel.bind_values + where_clause.binds + having_clause.binds if limit_value && !string_containing_comma?(limit_value) - result << Attribute.with_cast_value( + limit_bind = Attribute.with_cast_value( "LIMIT".freeze, connection.sanitize_limit(limit_value), - Type::Value.new, + Type.default_value, ) end if offset_value - result << Attribute.with_cast_value( + offset_bind = Attribute.with_cast_value( "OFFSET".freeze, offset_value.to_i, - Type::Value.new, + Type.default_value, ) end - result - end - - FROZEN_EMPTY_HASH = {}.freeze - def create_with_value # :nodoc: - @values[:create_with] || FROZEN_EMPTY_HASH + connection.combine_bind_parameters( + from_clause: from_clause.binds, + join_clause: arel.bind_values, + where_clause: where_clause.binds, + having_clause: having_clause.binds, + limit: limit_bind, + offset: offset_bind, + ) end alias extensions extending_values @@ -263,8 +241,17 @@ module ActiveRecord # Model.select(:field).first.other_field # # => ActiveModel::MissingAttributeError: missing attribute: other_field def select(*fields) - return super if block_given? - raise ArgumentError, 'Call this with at least one field' if fields.empty? + 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 + end + + return super() + end + + raise ArgumentError, "Call this with at least one field" if fields.empty? spawn._select!(*fields) end @@ -411,7 +398,10 @@ module ActiveRecord args.each do |scope| case scope when Symbol - symbol_unscoping(scope) + if !VALID_UNSCOPING_VALUES.include?(scope) + raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}." + end + set_value(scope, nil) when Hash scope.each do |key, target_value| if key != :where @@ -489,7 +479,6 @@ module ActiveRecord self.left_outer_joins_values += args self end - alias :left_joins! :left_outer_joins! # Returns a new relation, which is the result of filtering the current relation # according to the conditions in the arguments. @@ -652,9 +641,13 @@ module ActiveRecord # present). Neither relation may have a #limit, #offset, or #distinct set. # # Post.where("id = 1").or(Post.where("author_id = 3")) - # # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'author_id = 3')) + # # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3)) # def or(other) + unless other.is_a? Relation + raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead." + end + spawn.or!(other) end @@ -939,294 +932,287 @@ module ActiveRecord @arel ||= build_arel end - private - - def assert_mutability! - raise ImmutableRelation if @loaded - raise ImmutableRelation if defined?(@arel) && @arel + # Returns a relation value with a given name + def get_value(name) # :nodoc: + @values[name] || default_value_for(name) end - def build_arel - arel = Arel::SelectManager.new(table) + # Sets the relation value with the given name + def set_value(name, value) # :nodoc: + assert_mutability! + @values[name] = value + end - build_joins(arel, joins_values.flatten) unless joins_values.empty? - build_left_outer_joins(arel, left_outer_joins_values.flatten) unless left_outer_joins_values.empty? + private - 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 + def assert_mutability! + raise ImmutableRelation if @loaded + raise ImmutableRelation if defined?(@arel) && @arel end - arel.skip(Arel::Nodes::BindParam.new) if offset_value - arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty? - - build_order(arel) - - build_select(arel) - arel.distinct(distinct_value) - arel.from(build_from) unless from_clause.empty? - arel.lock(lock_value) if lock_value + def build_arel + arel = Arel::SelectManager.new(table) - arel - end - - def symbol_unscoping(scope) - if !VALID_UNSCOPING_VALUES.include?(scope) - raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}." - end + build_joins(arel, joins_values.flatten) unless joins_values.empty? + build_left_outer_joins(arel, left_outer_joins_values.flatten) unless left_outer_joins_values.empty? - clause_method = Relation::CLAUSE_METHODS.include?(scope) - multi_val_method = Relation::MULTI_VALUE_METHODS.include?(scope) - if clause_method - unscope_code = "#{scope}_clause=" - else - unscope_code = "#{scope}_value#{'s' if multi_val_method}=" - end + 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.skip(Arel::Nodes::BindParam.new) if offset_value + arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty? - case scope - when :order - result = [] - else - result = [] if multi_val_method - end + build_order(arel) - self.send(unscope_code, result) - end + build_select(arel) - def association_for_table(table_name) - table_name = table_name.to_s - @klass._reflect_on_association(table_name) || - @klass._reflect_on_association(table_name.singularize) - end + arel.distinct(distinct_value) + arel.from(build_from) unless from_clause.empty? + arel.lock(lock_value) if lock_value - def build_from - opts = from_clause.value - name = from_clause.name - case opts - when Relation - name ||= 'subquery' - opts.arel.as(name.to_s) - else - opts + arel end - end - def build_left_outer_joins(manager, outer_joins) - buckets = outer_joins.group_by do |join| - case join - when Hash, Symbol, Array - :association_join + def build_from + opts = from_clause.value + name = from_clause.name + case opts + when Relation + name ||= "subquery" + opts.arel.as(name.to_s) else - raise ArgumentError, 'only Hash, Symbol and Array are allowed' + opts end end - build_join_query(manager, buckets, Arel::Nodes::OuterJoin) - end - - def build_joins(manager, joins) - buckets = joins.group_by do |join| - case join - when String - :string_join - when Hash, Symbol, Array - :association_join - when ActiveRecord::Associations::JoinDependency - :stashed_join - when Arel::Nodes::Join - :join_node - else - raise 'unknown class: %s' % join.class.name + def build_left_outer_joins(manager, outer_joins) + buckets = outer_joins.group_by do |join| + case join + when Hash, Symbol, Array + :association_join + else + raise ArgumentError, "only Hash, Symbol and Array are allowed" + end end + + build_join_query(manager, buckets, Arel::Nodes::OuterJoin) end - build_join_query(manager, buckets, Arel::Nodes::InnerJoin) - end + def build_joins(manager, joins) + buckets = joins.group_by do |join| + case join + when String + :string_join + when Hash, Symbol, Array + :association_join + when ActiveRecord::Associations::JoinDependency + :stashed_join + when Arel::Nodes::Join + :join_node + else + raise "unknown class: %s" % join.class.name + end + end - def build_join_query(manager, buckets, join_type) - buckets.default = [] + build_join_query(manager, buckets, Arel::Nodes::InnerJoin) + end - association_joins = buckets[:association_join] - stashed_association_joins = buckets[:stashed_join] - join_nodes = buckets[:join_node].uniq - string_joins = buckets[:string_join].map(&:strip).uniq + def build_join_query(manager, buckets, join_type) + buckets.default = [] - join_list = join_nodes + convert_join_strings_to_ast(manager, string_joins) + association_joins = buckets[:association_join] + stashed_association_joins = buckets[:stashed_join] + join_nodes = buckets[:join_node].uniq + string_joins = buckets[:string_join].map(&:strip).uniq - join_dependency = ActiveRecord::Associations::JoinDependency.new( - @klass, - association_joins, - join_list - ) + join_list = join_nodes + convert_join_strings_to_ast(manager, string_joins) - join_infos = join_dependency.join_constraints stashed_association_joins, join_type + join_dependency = ActiveRecord::Associations::JoinDependency.new( + @klass, + association_joins, + join_list + ) - join_infos.each do |info| - info.joins.each { |join| manager.from(join) } - manager.bind_values.concat info.binds - end + join_infos = join_dependency.join_constraints stashed_association_joins, join_type - manager.join_sources.concat(join_list) + join_infos.each do |info| + info.joins.each { |join| manager.from(join) } + manager.bind_values.concat info.binds + end - manager - end + manager.join_sources.concat(join_list) - def convert_join_strings_to_ast(table, joins) - joins - .flatten - .reject(&:blank?) - .map { |join| table.create_string_join(Arel.sql(join)) } - end + manager + end - def build_select(arel) - if select_values.any? - arel.project(*arel_columns(select_values.uniq)) - else - arel.project(@klass.arel_table[Arel.star]) + def convert_join_strings_to_ast(table, joins) + joins + .flatten + .reject(&:blank?) + .map { |join| table.create_string_join(Arel.sql(join)) } end - end - def arel_columns(columns) - columns.map do |field| - if (Symbol === field || String === field) && (klass.has_attribute?(field) || klass.attribute_alias?(field)) && !from_clause.value - arel_attribute(field) - elsif Symbol === field - connection.quote_table_name(field.to_s) + def build_select(arel) + if select_values.any? + arel.project(*arel_columns(select_values.uniq)) else - field + arel.project(@klass.arel_table[Arel.star]) end end - end - def reverse_sql_order(order_query) - if order_query.empty? - return [arel_attribute(primary_key).desc] if primary_key - raise IrreversibleOrderError, - "Relation has no current order and table has no primary key to be used as default order" + def arel_columns(columns) + columns.map do |field| + if (Symbol === field || String === field) && (klass.has_attribute?(field) || klass.attribute_alias?(field)) && !from_clause.value + arel_attribute(field) + elsif Symbol === field + connection.quote_table_name(field.to_s) + else + field + end + end end - order_query.flat_map do |o| - case o - when Arel::Attribute - o.desc - when Arel::Nodes::Ordering - o.reverse - when String - if does_not_support_reverse?(o) - raise IrreversibleOrderError, "Order #{o.inspect} can not be reversed automatically" - end - o.split(',').map! do |s| - s.strip! - s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC') + def reverse_sql_order(order_query) + if order_query.empty? + return [arel_attribute(primary_key).desc] if primary_key + raise IrreversibleOrderError, + "Relation has no current order and table has no primary key to be used as default order" + end + + order_query.flat_map do |o| + case o + when Arel::Attribute + o.desc + when Arel::Nodes::Ordering + o.reverse + when String + if does_not_support_reverse?(o) + raise IrreversibleOrderError, "Order #{o.inspect} can not be reversed automatically" + end + o.split(",").map! do |s| + s.strip! + s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || s.concat(" DESC") + end + else + o end - else - o end end - end - def does_not_support_reverse?(order) - #uses sql function with multiple arguments - order =~ /\([^()]*,[^()]*\)/ || - # uses "nulls first" like construction - order =~ /nulls (first|last)\Z/i - end + def does_not_support_reverse?(order) + # Uses SQL function with multiple arguments. + (order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) || + # Uses "nulls first" like construction. + /nulls (first|last)\Z/i.match?(order) + end - def build_order(arel) - orders = order_values.uniq - orders.reject!(&:blank?) + def build_order(arel) + orders = order_values.uniq + orders.reject!(&:blank?) - arel.order(*orders) unless orders.empty? - end + arel.order(*orders) unless orders.empty? + end - VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC, - 'asc', 'desc', 'ASC', 'DESC'] # :nodoc: + VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC, + "asc", "desc", "ASC", "DESC"] # :nodoc: - def validate_order_args(args) - args.each do |arg| - next unless arg.is_a?(Hash) - arg.each do |_key, value| - raise ArgumentError, "Direction \"#{value}\" is invalid. Valid " \ - "directions are: #{VALID_DIRECTIONS.inspect}" unless VALID_DIRECTIONS.include?(value) + def validate_order_args(args) + args.each do |arg| + next unless arg.is_a?(Hash) + arg.each do |_key, value| + raise ArgumentError, "Direction \"#{value}\" is invalid. Valid " \ + "directions are: #{VALID_DIRECTIONS.inspect}" unless VALID_DIRECTIONS.include?(value) + end end end - end - def preprocess_order_args(order_args) - order_args.map! do |arg| - klass.send(:sanitize_sql_for_order, arg) + def preprocess_order_args(order_args) + order_args.map! do |arg| + klass.send(:sanitize_sql_for_order, arg) + end + order_args.flatten! + validate_order_args(order_args) + + references = order_args.grep(String) + references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact! + references!(references) if references.any? + + # if a symbol is given we prepend the quoted table name + order_args.map! do |arg| + case arg + when Symbol + arel_attribute(arg).asc + when Hash + arg.map { |field, dir| + arel_attribute(field).send(dir.downcase) + } + else + arg + end + end.flatten! end - order_args.flatten! - validate_order_args(order_args) - - references = order_args.grep(String) - references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact! - references!(references) if references.any? - # if a symbol is given we prepend the quoted table name - order_args.map! do |arg| - case arg - when Symbol - arel_attribute(arg).asc - when Hash - arg.map { |field, dir| - arel_attribute(field).send(dir.downcase) - } - else - arg + # Checks to make sure that the arguments are not blank. Note that if some + # blank-like object were initially passed into the query method, then this + # method will not raise an error. + # + # Example: + # + # Post.references() # raises an error + # Post.references([]) # does not raise an error + # + # This particular method should be called with a method_name and the args + # passed into that method as an input. For example: + # + # def references(*args) + # check_if_method_has_arguments!("references", args) + # ... + # end + def check_if_method_has_arguments!(method_name, args) + if args.blank? + raise ArgumentError, "The method .#{method_name}() must contain arguments." end - end.flatten! - end - - # Checks to make sure that the arguments are not blank. Note that if some - # blank-like object were initially passed into the query method, then this - # method will not raise an error. - # - # Example: - # - # Post.references() # raises an error - # Post.references([]) # does not raise an error - # - # This particular method should be called with a method_name and the args - # passed into that method as an input. For example: - # - # def references(*args) - # check_if_method_has_arguments!("references", args) - # ... - # end - def check_if_method_has_arguments!(method_name, args) - if args.blank? - raise ArgumentError, "The method .#{method_name}() must contain arguments." end - end - def structurally_incompatible_values_for_or(other) - Relation::SINGLE_VALUE_METHODS.reject { |m| send("#{m}_value") == other.send("#{m}_value") } + - (Relation::MULTI_VALUE_METHODS - [:extending]).reject { |m| send("#{m}_values") == other.send("#{m}_values") } + - (Relation::CLAUSE_METHODS - [:having, :where]).reject { |m| send("#{m}_clause") == other.send("#{m}_clause") } - end - - def new_where_clause - Relation::WhereClause.empty - end - alias new_having_clause new_where_clause + STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having] + def structurally_incompatible_values_for_or(other) + STRUCTURAL_OR_METHODS.reject do |method| + get_value(method) == other.get_value(method) + end + end - def where_clause_factory - @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder) - end - alias having_clause_factory where_clause_factory + def where_clause_factory + @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder) + end + alias having_clause_factory where_clause_factory - def new_from_clause - Relation::FromClause.empty - end + def string_containing_comma?(value) + ::String === value && value.include?(",") + end - def string_containing_comma?(value) - ::String === value && value.include?(",") - end + def default_value_for(name) + case name + when :create_with + FROZEN_EMPTY_HASH + when :readonly + false + when :where, :having + Relation::WhereClause.empty + when :from + Relation::FromClause.empty + when *Relation::MULTI_VALUE_METHODS + FROZEN_EMPTY_ARRAY + when *Relation::SINGLE_VALUE_METHODS + nil + else + raise ArgumentError, "unknown relation value #{name.inspect}" + end + end end end diff --git a/activerecord/lib/active_record/relation/record_fetch_warning.rb b/activerecord/lib/active_record/relation/record_fetch_warning.rb index dbd08811fa..31544c730e 100644 --- a/activerecord/lib/active_record/relation/record_fetch_warning.rb +++ b/activerecord/lib/active_record/relation/record_fetch_warning.rb @@ -2,15 +2,15 @@ module ActiveRecord class Relation module RecordFetchWarning # When this module is prepended to ActiveRecord::Relation and - # `config.active_record.warn_on_records_fetched_greater_than` is + # +config.active_record.warn_on_records_fetched_greater_than+ is # set to an integer, if the number of records a query returns is - # greater than the value of `warn_on_records_fetched_greater_than`, + # greater than the value of +warn_on_records_fetched_greater_than+, # a warning is logged. This allows for the detection of queries that # return a large number of records, which could cause memory bloat. # # In most cases, fetching large number of records can be performed # efficiently using the ActiveRecord::Batches methods. - # See active_record/lib/relation/batches.rb for more information. + # See ActiveRecord::Batches for more information. def exec_queries QueryRegistry.reset diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 67d7f83cb4..190e339ea8 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -1,10 +1,9 @@ -require 'active_support/core_ext/hash/except' -require 'active_support/core_ext/hash/slice' -require 'active_record/relation/merger' +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/slice" +require "active_record/relation/merger" module ActiveRecord module SpawnMethods - # This is overridden by Associations::CollectionProxy def spawn #:nodoc: clone @@ -29,7 +28,7 @@ module ActiveRecord # This is mainly intended for sharing common conditions between multiple associations. def merge(other) if other.is_a?(Array) - to_a & other + records & other elsif other spawn.merge!(other) else diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb index 2c2d6cfa47..402f8acfd1 100644 --- a/activerecord/lib/active_record/relation/where_clause.rb +++ b/activerecord/lib/active_record/relation/where_clause.rb @@ -86,88 +86,89 @@ module ActiveRecord protected - attr_reader :predicates + attr_reader :predicates - def referenced_columns - @referenced_columns ||= begin - equality_nodes = predicates.select { |n| equality_node?(n) } - Set.new(equality_nodes, &:left) + def referenced_columns + @referenced_columns ||= begin + equality_nodes = predicates.select { |n| equality_node?(n) } + Set.new(equality_nodes, &:left) + end end - end private - def predicates_unreferenced_by(other) - predicates.reject do |n| - equality_node?(n) && other.referenced_columns.include?(n.left) + def predicates_unreferenced_by(other) + predicates.reject do |n| + equality_node?(n) && other.referenced_columns.include?(n.left) + end end - end - def equality_node?(node) - node.respond_to?(:operator) && node.operator == :== - end - - def non_conflicting_binds(other) - conflicts = referenced_columns & other.referenced_columns - conflicts.map! { |node| node.name.to_s } - binds.reject { |attr| conflicts.include?(attr.name) } - end + def equality_node?(node) + node.respond_to?(:operator) && node.operator == :== + end - def inverted_predicates - predicates.map { |node| invert_predicate(node) } - end + def non_conflicting_binds(other) + conflicts = referenced_columns & other.referenced_columns + conflicts.map! { |node| node.name.to_s } + binds.reject { |attr| conflicts.include?(attr.name) } + end - def invert_predicate(node) - case node - when NilClass - raise ArgumentError, 'Invalid argument for .where.not(), got nil.' - when Arel::Nodes::In - Arel::Nodes::NotIn.new(node.left, node.right) - when Arel::Nodes::Equality - Arel::Nodes::NotEqual.new(node.left, node.right) - when String - Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(node)) - else - Arel::Nodes::Not.new(node) + def inverted_predicates + predicates.map { |node| invert_predicate(node) } end - end - def predicates_except(columns) - predicates.reject do |node| + def invert_predicate(node) case node - when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual - subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right) - columns.include?(subrelation.name.to_s) + when NilClass + raise ArgumentError, "Invalid argument for .where.not(), got nil." + when Arel::Nodes::In + Arel::Nodes::NotIn.new(node.left, node.right) + when Arel::Nodes::Equality + Arel::Nodes::NotEqual.new(node.left, node.right) + when String + Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(node)) + else + Arel::Nodes::Not.new(node) end end - end - def binds_except(columns) - binds.reject do |attr| - columns.include?(attr.name) + def predicates_except(columns) + predicates.reject do |node| + case node + when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual + subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right) + columns.include?(subrelation.name.to_s) + end + end end - end - def predicates_with_wrapped_sql_literals - non_empty_predicates.map do |node| - if Arel::Nodes::Equality === node - node - else - wrap_sql_literal(node) + def binds_except(columns) + binds.reject do |attr| + columns.include?(attr.name) end end - end - def non_empty_predicates - predicates - [''] - end + def predicates_with_wrapped_sql_literals + non_empty_predicates.map do |node| + if Arel::Nodes::Equality === node + node + else + wrap_sql_literal(node) + end + end + end - def wrap_sql_literal(node) - if ::String === node - node = Arel.sql(node) + ARRAY_WITH_EMPTY_STRING = [""] + def non_empty_predicates + predicates - ARRAY_WITH_EMPTY_STRING + end + + def wrap_sql_literal(node) + if ::String === node + node = Arel.sql(node) + end + Arel::Nodes::Grouping.new(node) end - Arel::Nodes::Grouping.new(node) - end end end end diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb index dbf172a577..1e7deeffad 100644 --- a/activerecord/lib/active_record/relation/where_clause_factory.rb +++ b/activerecord/lib/active_record/relation/where_clause_factory.rb @@ -7,8 +7,6 @@ module ActiveRecord end def build(opts, other) - binds = [] - case opts when String, Array parts = [klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))] @@ -22,17 +20,16 @@ module ActiveRecord parts = predicate_builder.build_from_hash(attributes) when Arel::Nodes::Node parts = [opts] - binds = other else raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})" end - WhereClause.new(parts, binds) + WhereClause.new(parts, binds || []) end protected - attr_reader :klass, :predicate_builder + attr_reader :klass, :predicate_builder end end end diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb index 8e6cd6c82f..9ed70a9c2b 100644 --- a/activerecord/lib/active_record/result.rb +++ b/activerecord/lib/active_record/result.rb @@ -32,8 +32,6 @@ module ActiveRecord class Result include Enumerable - IDENTITY_TYPE = Type::Value.new # :nodoc: - attr_reader :columns, :rows, :column_types def initialize(columns, rows, column_types = {}) @@ -75,8 +73,14 @@ module ActiveRecord hash_rows[idx] end + def first + return nil if @rows.empty? + Hash[@columns.zip(@rows.first)] + end + def last - hash_rows.last + return nil if @rows.empty? + Hash[@columns.zip(@rows.last)] end def cast_values(type_overrides = {}) # :nodoc: @@ -97,36 +101,36 @@ module ActiveRecord private - def column_type(name, type_overrides = {}) - type_overrides.fetch(name) do - column_types.fetch(name, IDENTITY_TYPE) + def column_type(name, type_overrides = {}) + type_overrides.fetch(name) do + column_types.fetch(name, Type.default_value) + end end - end - def hash_rows - @hash_rows ||= - begin - # We freeze the strings to prevent them getting duped when - # used as keys in ActiveRecord::Base's @attributes hash - columns = @columns.map { |c| c.dup.freeze } - @rows.map { |row| - # In the past we used Hash[columns.zip(row)] - # though elegant, the verbose way is much more efficient - # both time and memory wise cause it avoids a big array allocation - # this method is called a lot and needs to be micro optimised - hash = {} - - index = 0 - length = columns.length - - while index < length - hash[columns[index]] = row[index] - index += 1 - end - - hash - } - end - end + def hash_rows + @hash_rows ||= + begin + # We freeze the strings to prevent them getting duped when + # used as keys in ActiveRecord::Base's @attributes hash + columns = @columns.map { |c| c.dup.freeze } + @rows.map { |row| + # In the past we used Hash[columns.zip(row)] + # though elegant, the verbose way is much more efficient + # both time and memory wise cause it avoids a big array allocation + # this method is called a lot and needs to be micro optimised + hash = {} + + index = 0 + length = columns.length + + while index < length + hash[columns[index]] = row[index] + index += 1 + end + + hash + } + end + end end end diff --git a/activerecord/lib/active_record/runtime_registry.rb b/activerecord/lib/active_record/runtime_registry.rb index 56e88bc661..b79eb2263f 100644 --- a/activerecord/lib/active_record/runtime_registry.rb +++ b/activerecord/lib/active_record/runtime_registry.rb @@ -1,4 +1,4 @@ -require 'active_support/per_thread_registry' +require "active_support/per_thread_registry" module ActiveRecord # This is a thread locals registry for Active Record. For example: @@ -12,9 +12,9 @@ module ActiveRecord class RuntimeRegistry # :nodoc: extend ActiveSupport::PerThreadRegistry - attr_accessor :connection_handler, :sql_runtime, :connection_id + attr_accessor :connection_handler, :sql_runtime - [:connection_handler, :sql_runtime, :connection_id].each do |val| + [:connection_handler, :sql_runtime].each do |val| class_eval %{ def self.#{val}; instance.#{val}; end }, __FILE__, __LINE__ class_eval %{ def self.#{val}=(x); instance.#{val}=x; end }, __FILE__, __LINE__ end diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index a9e1fd0dad..3d52dc44cf 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -1,220 +1,215 @@ + module ActiveRecord module Sanitization extend ActiveSupport::Concern module ClassMethods - # Used to sanitize objects before they're used in an SQL SELECT statement. - # Delegates to {connection.quote}[rdoc-ref:ConnectionAdapters::Quoting#quote]. - def sanitize(object) # :nodoc: - connection.quote(object) - end - alias_method :quote_value, :sanitize - protected - # Accepts an array or string of SQL conditions and sanitizes - # them into a valid SQL fragment for a WHERE clause. - # - # sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4]) - # # => "name='foo''bar' and group_id=4" - # - # sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4]) - # # => "name='foo''bar' and group_id='4'" - # - # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4]) - # # => "name='foo''bar' and group_id='4'" - # - # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'") - # # => "name='foo''bar' and group_id='4'" - def sanitize_sql_for_conditions(condition) - return nil if condition.blank? - - case condition - when Array; sanitize_sql_array(condition) - else condition + # Accepts an array or string of SQL conditions and sanitizes + # them into a valid SQL fragment for a WHERE clause. + # + # sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4]) + # # => "name='foo''bar' and group_id=4" + # + # sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4]) + # # => "name='foo''bar' and group_id='4'" + # + # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4]) + # # => "name='foo''bar' and group_id='4'" + # + # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'") + # # => "name='foo''bar' and group_id='4'" + def sanitize_sql_for_conditions(condition) + return nil if condition.blank? + + case condition + when Array; sanitize_sql_array(condition) + else condition + end end - end - alias_method :sanitize_sql, :sanitize_sql_for_conditions - alias_method :sanitize_conditions, :sanitize_sql - - # Accepts an array, hash, or string of SQL conditions and sanitizes - # them into a valid SQL fragment for a SET clause. - # - # sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4]) - # # => "name=NULL and group_id=4" - # - # sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4]) - # # => "name=NULL and group_id=4" - # - # Post.send(:sanitize_sql_for_assignment, { name: nil, group_id: 4 }) - # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4" - # - # 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) - case assignments - when Array; sanitize_sql_array(assignments) - when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name) - else assignments + alias :sanitize_sql :sanitize_sql_for_conditions + alias :sanitize_conditions :sanitize_sql + deprecate sanitize_conditions: :sanitize_sql + + # Accepts an array, hash, or string of SQL conditions and sanitizes + # them into a valid SQL fragment for a SET clause. + # + # sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4]) + # # => "name=NULL and group_id=4" + # + # sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4]) + # # => "name=NULL and group_id=4" + # + # Post.send(:sanitize_sql_for_assignment, { name: nil, group_id: 4 }) + # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4" + # + # 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) + case assignments + when Array; sanitize_sql_array(assignments) + when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name) + else assignments + end end - end - - # Accepts an array, or string of SQL conditions and sanitizes - # them into a valid SQL fragment for an ORDER clause. - # - # sanitize_sql_for_order(["field(id, ?)", [1,3,2]]) - # # => "field(id, 1,3,2)" - # - # sanitize_sql_for_order("id ASC") - # # => "id ASC" - def sanitize_sql_for_order(condition) - if condition.is_a?(Array) && condition.first.to_s.include?('?') - sanitize_sql_array(condition) - else - condition + + # Accepts an array, or string of SQL conditions and sanitizes + # them into a valid SQL fragment for an ORDER clause. + # + # sanitize_sql_for_order(["field(id, ?)", [1,3,2]]) + # # => "field(id, 1,3,2)" + # + # sanitize_sql_for_order("id ASC") + # # => "id ASC" + def sanitize_sql_for_order(condition) + if condition.is_a?(Array) && condition.first.to_s.include?("?") + sanitize_sql_array(condition) + else + condition + end end - end - - # Accepts a hash of SQL conditions and replaces those attributes - # that correspond to a {#composed_of}[rdoc-ref:Aggregations::ClassMethods#composed_of] - # relationship with their expanded aggregate attribute values. - # - # Given: - # - # class Person < ActiveRecord::Base - # composed_of :address, class_name: "Address", - # mapping: [%w(address_street street), %w(address_city city)] - # end - # - # Then: - # - # { address: Address.new("813 abc st.", "chicago") } - # # => { address_street: "813 abc st.", address_city: "chicago" } - def expand_hash_conditions_for_aggregates(attrs) - expanded_attrs = {} - attrs.each do |attr, value| - if aggregation = reflect_on_aggregation(attr.to_sym) - mapping = aggregation.mapping - mapping.each do |field_attr, aggregate_attr| - if mapping.size == 1 && !value.respond_to?(aggregate_attr) - expanded_attrs[field_attr] = value - else - expanded_attrs[field_attr] = value.send(aggregate_attr) + + # Accepts a hash of SQL conditions and replaces those attributes + # that correspond to a {#composed_of}[rdoc-ref:Aggregations::ClassMethods#composed_of] + # relationship with their expanded aggregate attribute values. + # + # Given: + # + # class Person < ActiveRecord::Base + # composed_of :address, class_name: "Address", + # mapping: [%w(address_street street), %w(address_city city)] + # end + # + # Then: + # + # { address: Address.new("813 abc st.", "chicago") } + # # => { address_street: "813 abc st.", address_city: "chicago" } + def expand_hash_conditions_for_aggregates(attrs) + expanded_attrs = {} + attrs.each do |attr, value| + if aggregation = reflect_on_aggregation(attr.to_sym) + mapping = aggregation.mapping + mapping.each do |field_attr, aggregate_attr| + if mapping.size == 1 && !value.respond_to?(aggregate_attr) + expanded_attrs[field_attr] = value + else + expanded_attrs[field_attr] = value.send(aggregate_attr) + end end + else + expanded_attrs[attr] = value end - else - expanded_attrs[attr] = value end + expanded_attrs end - expanded_attrs - end - - # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause. - # - # 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) - c = connection - attrs.map do |attr, value| - value = type_for_attribute(attr.to_s).serialize(value) - "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}" - end.join(', ') - end - - # Sanitizes a +string+ so that it is safe to use within an SQL - # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%". - # - # sanitize_sql_like("100%") - # # => "100\\%" - # - # sanitize_sql_like("snake_cased_string") - # # => "snake\\_cased\\_string" - # - # sanitize_sql_like("100%", "!") - # # => "100!%" - # - # sanitize_sql_like("snake_cased_string", "!") - # # => "snake!_cased!_string" - def sanitize_sql_like(string, escape_character = "\\") - pattern = Regexp.union(escape_character, "%", "_") - string.gsub(pattern) { |x| [escape_character, x].join } - end - - # Accepts an array of conditions. The array has each value - # sanitized and interpolated into the SQL statement. - # - # sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4]) - # # => "name='foo''bar' and group_id=4" - # - # sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4]) - # # => "name='foo''bar' and group_id=4" - # - # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4]) - # # => "name='foo''bar' and group_id='4'" - def sanitize_sql_array(ary) - statement, *values = ary - if values.first.is_a?(Hash) && statement =~ /:\w+/ - replace_named_bind_variables(statement, values.first) - elsif statement.include?('?') - replace_bind_variables(statement, values) - elsif statement.blank? - statement - else - statement % values.collect { |value| connection.quote_string(value.to_s) } + + # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause. + # + # 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) + c = connection + attrs.map do |attr, value| + value = type_for_attribute(attr.to_s).serialize(value) + "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}" + end.join(", ") end - end - - def replace_bind_variables(statement, values) # :nodoc: - raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size) - bound = values.dup - c = connection - statement.gsub(/\?/) do - replace_bind_variable(bound.shift, c) + + # Sanitizes a +string+ so that it is safe to use within an SQL + # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%". + # + # sanitize_sql_like("100%") + # # => "100\\%" + # + # sanitize_sql_like("snake_cased_string") + # # => "snake\\_cased\\_string" + # + # sanitize_sql_like("100%", "!") + # # => "100!%" + # + # sanitize_sql_like("snake_cased_string", "!") + # # => "snake!_cased!_string" + def sanitize_sql_like(string, escape_character = "\\") + pattern = Regexp.union(escape_character, "%", "_") + string.gsub(pattern) { |x| [escape_character, x].join } end - end - def replace_bind_variable(value, c = connection) # :nodoc: - if ActiveRecord::Relation === value - value.to_sql - else - quote_bound_value(value, c) + # Accepts an array of conditions. The array has each value + # sanitized and interpolated into the SQL statement. + # + # sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4]) + # # => "name='foo''bar' and group_id=4" + # + # sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4]) + # # => "name='foo''bar' and group_id=4" + # + # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4]) + # # => "name='foo''bar' and group_id='4'" + def sanitize_sql_array(ary) + statement, *values = ary + if values.first.is_a?(Hash) && /:\w+/.match?(statement) + replace_named_bind_variables(statement, values.first) + elsif statement.include?("?") + replace_bind_variables(statement, values) + elsif statement.blank? + statement + else + statement % values.collect { |value| connection.quote_string(value.to_s) } + end + end + + def replace_bind_variables(statement, values) # :nodoc: + raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size) + bound = values.dup + c = connection + statement.gsub(/\?/) do + replace_bind_variable(bound.shift, c) + end end - end - - def replace_named_bind_variables(statement, bind_vars) # :nodoc: - statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match| - if $1 == ':' # skip postgresql casts - match # return the whole match - elsif bind_vars.include?(match = $2.to_sym) - replace_bind_variable(bind_vars[match]) + + def replace_bind_variable(value, c = connection) # :nodoc: + if ActiveRecord::Relation === value + value.to_sql else - raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}" + quote_bound_value(value, c) end end - end - def quote_bound_value(value, c = connection) # :nodoc: - if value.respond_to?(:map) && !value.acts_like?(:string) - if value.respond_to?(:empty?) && value.empty? - c.quote(nil) + def replace_named_bind_variables(statement, bind_vars) # :nodoc: + statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match| + if $1 == ":" # skip postgresql casts + match # return the whole match + elsif bind_vars.include?(match = $2.to_sym) + replace_bind_variable(bind_vars[match]) + else + raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}" + end + end + end + + def quote_bound_value(value, c = connection) # :nodoc: + if value.respond_to?(:map) && !value.acts_like?(:string) + if value.respond_to?(:empty?) && value.empty? + c.quote(nil) + else + value.map { |v| c.quote(v) }.join(",") + end else - value.map { |v| c.quote(v) }.join(',') + c.quote(value) end - else - c.quote(value) end - end - def raise_if_bind_arity_mismatch(statement, expected, provided) # :nodoc: - unless expected == provided - raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}" + def raise_if_bind_arity_mismatch(statement, expected, provided) # :nodoc: + unless expected == provided + raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}" + end end - end end # TODO: Deprecate this def quoted_id # :nodoc: - self.class.quote_value(@attributes[self.class.primary_key].value_for_database) + self.class.connection.quote(@attributes[self.class.primary_key].value_for_database) end end end diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index f115c7542b..c1c6519cfa 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -1,4 +1,4 @@ -require 'stringio' +require "stringio" module ActiveRecord # = Active Record Schema Dumper @@ -50,10 +50,6 @@ module ActiveRecord def header(stream) define_params = @version ? "version: #{@version}" : "" - if stream.respond_to?(:external_encoding) && stream.external_encoding - stream.puts "# encoding: #{stream.external_encoding.name}" - end - stream.puts <<HEADER # This file is auto-generated from the current state of the database. Instead # of editing this file, please use the migrations feature of Active Record to @@ -104,32 +100,22 @@ HEADER end def table(table, stream) - columns = @connection.columns(table).map do |column| - column.instance_variable_set(:@table_name, table) - column - end + columns = @connection.columns(table) begin tbl = StringIO.new # first dump primary key column - if @connection.respond_to?(:primary_keys) - pk = @connection.primary_keys(table) - pk = pk.first unless pk.size > 1 - else - pk = @connection.primary_key(table) - end + pk = @connection.primary_key(table) tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}" case pk when String - tbl.print ", primary_key: #{pk.inspect}" unless pk == 'id' + tbl.print ", primary_key: #{pk.inspect}" unless pk == "id" pkcol = columns.detect { |c| c.name == pk } pkcolspec = @connection.column_spec_for_primary_key(pkcol) - if pkcolspec - pkcolspec.each do |key, value| - tbl.print ", #{key}: #{value}" - end + if pkcolspec.present? + tbl.print ", #{format_colspec(pkcolspec)}" end when Array tbl.print ", primary_key: #{pk.inspect}" @@ -139,50 +125,27 @@ HEADER tbl.print ", force: :cascade" table_options = @connection.table_options(table) - tbl.print ", options: #{table_options.inspect}" unless table_options.blank? + if table_options.present? + tbl.print ", #{format_options(table_options)}" + end tbl.puts " do |t|" # then dump all non-primary key columns - column_specs = columns.map do |column| + columns.each do |column| raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type) next if column.name == pk - @connection.column_spec(column) - end.compact - - # find all migration keys used in this table - keys = @connection.migration_keys - - # figure out the lengths for each column based on above keys - lengths = keys.map { |key| - column_specs.map { |spec| - spec[key] ? spec[key].length + 2 : 0 - }.max - } - - # the string we're going to sprintf our values against, with standardized column widths - format_string = lengths.map{ |len| "%-#{len}s" } - - # find the max length for the 'type' column, which is special - type_length = column_specs.map{ |column| column[:type].length }.max - - # add column type definition to our format string - format_string.unshift " t.%-#{type_length}s " - - format_string *= '' - - column_specs.each do |colspec| - values = keys.zip(lengths).map{ |key, len| colspec.key?(key) ? colspec[key] + ", " : " " * len } - values.unshift colspec[:type] - tbl.print((format_string % values).gsub(/,\s*$/, '')) + type, colspec = @connection.column_spec(column) + tbl.print " t.#{type} #{column.name.inspect}" + tbl.print ", #{format_colspec(colspec)}" if colspec.present? tbl.puts end + indexes_in_create(table, tbl) + tbl.puts " end" tbl.puts - indexes(table, tbl) - tbl.rewind stream.print tbl.read rescue => e @@ -194,26 +157,12 @@ HEADER stream end + # Keep it for indexing materialized views def indexes(table, stream) if (indexes = @connection.indexes(table)).any? add_index_statements = indexes.map do |index| - statement_parts = [ - "add_index #{remove_prefix_and_suffix(index.table).inspect}", - index.columns.inspect, - "name: #{index.name.inspect}", - ] - statement_parts << 'unique: true' if index.unique - - index_lengths = (index.lengths || []).compact - statement_parts << "length: #{Hash[index.columns.zip(index.lengths)].inspect}" if index_lengths.any? - - index_orders = index.orders || {} - statement_parts << "order: #{index.orders.inspect}" if index_orders.any? - statement_parts << "where: #{index.where.inspect}" if index.where - statement_parts << "using: #{index.using.inspect}" if index.using - statement_parts << "type: #{index.type.inspect}" if index.type - - " #{statement_parts.join(', ')}" + table_name = remove_prefix_and_suffix(index.table).inspect + " add_index #{([table_name]+index_parts(index)).join(', ')}" end stream.puts add_index_statements.sort.join("\n") @@ -221,6 +170,30 @@ HEADER end end + def indexes_in_create(table, stream) + if (indexes = @connection.indexes(table)).any? + index_statements = indexes.map do |index| + " t.index #{index_parts(index).join(', ')}" + end + stream.puts index_statements.sort.join("\n") + end + end + + def index_parts(index) + index_parts = [ + index.columns.inspect, + "name: #{index.name.inspect}", + ] + index_parts << "unique: true" if index.unique + index_parts << "length: { #{format_options(index.lengths)} }" if index.lengths.present? + index_parts << "order: { #{format_options(index.orders)} }" if index.orders.present? + index_parts << "where: #{index.where.inspect}" if index.where + index_parts << "using: #{index.using.inspect}" if index.using + index_parts << "type: #{index.type.inspect}" if index.type + index_parts << "comment: #{index.comment.inspect}" if index.comment + index_parts + end + def foreign_keys(table, stream) if (foreign_keys = @connection.foreign_keys(table)).any? add_foreign_key_statements = foreign_keys.map do |foreign_key| @@ -251,6 +224,14 @@ HEADER end end + def format_colspec(colspec) + colspec.map { |key, value| "#{key}: #{value}" }.join(", ") + end + + def format_options(options) + options.map { |key, value| "#{key}: #{value.inspect}" }.join(", ") + end + def remove_prefix_and_suffix(table) table.gsub(/^(#{@options[:table_name_prefix]})(.+)(#{@options[:table_name_suffix]})$/, "\\2") end diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb index b6cb233e03..99b23e5593 100644 --- a/activerecord/lib/active_record/schema_migration.rb +++ b/activerecord/lib/active_record/schema_migration.rb @@ -1,5 +1,5 @@ -require 'active_record/scoping/default' -require 'active_record/scoping/named' +require "active_record/scoping/default" +require "active_record/scoping/named" module ActiveRecord # This class is used to create a table that keeps track of which migrations diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb index 7794af8ca4..d1bd1cd89a 100644 --- a/activerecord/lib/active_record/scoping.rb +++ b/activerecord/lib/active_record/scoping.rb @@ -1,4 +1,4 @@ -require 'active_support/per_thread_registry' +require "active_support/per_thread_registry" module ActiveRecord module Scoping @@ -94,11 +94,11 @@ module ActiveRecord private - def raise_invalid_scope_type!(scope_type) - if !VALID_SCOPE_TYPES.include?(scope_type) - raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES" + def raise_invalid_scope_type!(scope_type) + if !VALID_SCOPE_TYPES.include?(scope_type) + raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES" + end end - end end end end diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index f6b6768ce3..9d8253faa3 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -46,102 +46,103 @@ module ActiveRecord protected - # Use this macro in your model to set a default scope for all operations on - # the model. - # - # class Article < ActiveRecord::Base - # default_scope { where(published: true) } - # end - # - # Article.all # => SELECT * FROM articles WHERE published = true - # - # The #default_scope is also applied while creating/building a record. - # It is not applied while updating a record. - # - # Article.new.published # => true - # Article.create.published # => true - # - # (You can also pass any object which responds to +call+ to the - # +default_scope+ macro, and it will be called when building the - # default scope.) - # - # If you use multiple #default_scope declarations in your model then - # they will be merged together: - # - # class Article < ActiveRecord::Base - # default_scope { where(published: true) } - # default_scope { where(rating: 'G') } - # end - # - # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G' - # - # This is also the case with inheritance and module includes where the - # parent or module defines a #default_scope and the child or including - # class defines a second one. - # - # If you need to do more complex things with a default scope, you can - # alternatively define it as a class method: - # - # class Article < ActiveRecord::Base - # def self.default_scope - # # Should return a scope, you can call 'super' here etc. - # end - # end - def default_scope(scope = nil) - scope = Proc.new if block_given? - - if scope.is_a?(Relation) || !scope.respond_to?(:call) - raise ArgumentError, - "Support for calling #default_scope without a block is removed. For example instead " \ - "of `default_scope where(color: 'red')`, please use " \ - "`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \ - "self.default_scope.)" - end + # Use this macro in your model to set a default scope for all operations on + # the model. + # + # class Article < ActiveRecord::Base + # default_scope { where(published: true) } + # end + # + # Article.all # => SELECT * FROM articles WHERE published = true + # + # The #default_scope is also applied while creating/building a record. + # It is not applied while updating a record. + # + # Article.new.published # => true + # Article.create.published # => true + # + # (You can also pass any object which responds to +call+ to the + # +default_scope+ macro, and it will be called when building the + # default scope.) + # + # If you use multiple #default_scope declarations in your model then + # they will be merged together: + # + # class Article < ActiveRecord::Base + # default_scope { where(published: true) } + # default_scope { where(rating: 'G') } + # end + # + # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G' + # + # This is also the case with inheritance and module includes where the + # parent or module defines a #default_scope and the child or including + # class defines a second one. + # + # If you need to do more complex things with a default scope, you can + # alternatively define it as a class method: + # + # class Article < ActiveRecord::Base + # def self.default_scope + # # Should return a scope, you can call 'super' here etc. + # end + # end + def default_scope(scope = nil) + scope = Proc.new if block_given? + + if scope.is_a?(Relation) || !scope.respond_to?(:call) + raise ArgumentError, + "Support for calling #default_scope without a block is removed. For example instead " \ + "of `default_scope where(color: 'red')`, please use " \ + "`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \ + "self.default_scope.)" + end - self.default_scopes += [scope] - end + self.default_scopes += [scope] + end - def build_default_scope(base_rel = nil) # :nodoc: - return if abstract_class? + def build_default_scope(base_rel = nil) # :nodoc: + return if abstract_class? - if self.default_scope_override.nil? - self.default_scope_override = !Base.is_a?(method(:default_scope).owner) - end + if default_scope_override.nil? + self.default_scope_override = !Base.is_a?(method(:default_scope).owner) + end - if self.default_scope_override - # The user has defined their own default scope method, so call that - evaluate_default_scope { default_scope } - elsif default_scopes.any? - base_rel ||= relation - evaluate_default_scope do - default_scopes.inject(base_rel) do |default_scope, scope| - default_scope.merge(base_rel.scoping { scope.call }) + if default_scope_override + # The user has defined their own default scope method, so call that + evaluate_default_scope { default_scope } + elsif default_scopes.any? + base_rel ||= relation + evaluate_default_scope do + default_scopes.inject(base_rel) do |default_scope, scope| + scope = scope.respond_to?(:to_proc) ? scope : scope.method(:call) + default_scope.merge(base_rel.instance_exec(&scope)) + end end end end - end - def ignore_default_scope? # :nodoc: - ScopeRegistry.value_for(:ignore_default_scope, base_class) - end + def ignore_default_scope? # :nodoc: + ScopeRegistry.value_for(:ignore_default_scope, base_class) + end - def ignore_default_scope=(ignore) # :nodoc: - ScopeRegistry.set_value_for(:ignore_default_scope, base_class, ignore) - end + def ignore_default_scope=(ignore) # :nodoc: + 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: - return if ignore_default_scope? - - begin - self.ignore_default_scope = true - yield - ensure - self.ignore_default_scope = false + # 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: + return if ignore_default_scope? + + begin + self.ignore_default_scope = true + yield + ensure + self.ignore_default_scope = false + end end - end end end end diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index 5395bd6076..094c0e9c6f 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -1,6 +1,6 @@ -require 'active_support/core_ext/array' -require 'active_support/core_ext/hash/except' -require 'active_support/core_ext/kernel/singleton_class' +require "active_support/core_ext/array" +require "active_support/core_ext/hash/except" +require "active_support/core_ext/kernel/singleton_class" module ActiveRecord # = Active Record \Named \Scopes @@ -142,7 +142,7 @@ module ActiveRecord # Article.featured.titles def scope(name, body, &block) unless body.respond_to?(:call) - raise ArgumentError, 'The scope body needs to be callable.' + raise ArgumentError, "The scope body needs to be callable." end if dangerous_class_method?(name) @@ -174,7 +174,7 @@ module ActiveRecord protected def valid_scope_name?(name) - if respond_to?(name, true) + if respond_to?(name, true) && logger logger.warn "Creating scope :#{name}. " \ "Overwriting existing method #{self.name}.#{name}." end diff --git a/activerecord/lib/active_record/secure_token.rb b/activerecord/lib/active_record/secure_token.rb index 8abda2ac49..7606961e2e 100644 --- a/activerecord/lib/active_record/secure_token.rb +++ b/activerecord/lib/active_record/secure_token.rb @@ -25,9 +25,9 @@ module ActiveRecord # You're encouraged to add a unique index in the database to deal with this even more unlikely scenario. def has_secure_token(attribute = :token) # Load securerandom only when has_secure_token is used. - require 'active_support/core_ext/securerandom' + 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 { self.send("#{attribute}=", self.class.generate_unique_secure_token) unless self.send("#{attribute}?") } end def generate_unique_secure_token diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb index 6c896ccea6..691940ab70 100644 --- a/activerecord/lib/active_record/statement_cache.rb +++ b/activerecord/lib/active_record/statement_cache.rb @@ -1,5 +1,4 @@ module ActiveRecord - # Statement cache is used to cache a single statement in order to avoid creating the AST again. # Initializing the cache is done by passing the statement in the create block: # @@ -8,12 +7,12 @@ module ActiveRecord # end # # The cached statement is executed by using the - # [connection.execute]{rdoc-ref:ConnectionAdapters::DatabaseStatements#execute} method: + # {connection.execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute] method: # # cache.execute([], Book, Book.connection) # # The relation returned by the block is cached, and for each - # [execute]{rdoc-ref:ConnectionAdapters::DatabaseStatements#execute} + # {execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute] # call the cached relation gets duped. Database is queried when +to_a+ is called on the relation. # # If you want to cache the statement without the values you can use the +bind+ method of the @@ -40,7 +39,7 @@ module ActiveRecord end class PartialQuery < Query # :nodoc: - def initialize values + def initialize(values) @values = values @indexes = values.each_with_index.find_all { |thing,i| Arel::Nodes::BindParam === thing @@ -49,19 +48,18 @@ module ActiveRecord def sql_for(binds, connection) val = @values.dup - binds = connection.prepare_binds_for_database(binds) - @indexes.each { |i| val[i] = connection.quote(binds.shift) } + casted_binds = binds.map(&:value_for_database) + @indexes.each { |i| val[i] = connection.quote(casted_binds.shift) } val.join end end - def self.query(visitor, ast) - Query.new visitor.accept(ast, Arel::Collectors::SQLString.new).value + def self.query(sql) + Query.new(sql) end - def self.partial_query(visitor, ast, collector) - collected = visitor.accept(ast, collector).value - PartialQuery.new collected + def self.partial_query(values) + PartialQuery.new(values) end class Params # :nodoc: @@ -92,7 +90,7 @@ module ActiveRecord def self.create(connection, block = Proc.new) relation = block.call Params.new bind_map = BindMap.new relation.bound_attributes - query_builder = connection.cacheable_query relation.arel + query_builder = connection.cacheable_query(self, relation.arel) new query_builder, bind_map end @@ -101,12 +99,12 @@ module ActiveRecord @bind_map = bind_map end - def execute(params, klass, connection) + def execute(params, klass, connection, &block) bind_values = bind_map.bind params sql = query_builder.sql_for bind_values, connection - klass.find_by_sql(sql, bind_values, preparable: true) + klass.find_by_sql(sql, bind_values, preparable: true, &block) end alias :call :execute end diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb index 1b407f7702..066573192e 100644 --- a/activerecord/lib/active_record/store.rb +++ b/activerecord/lib/active_record/store.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/hash/indifferent_access' +require "active_support/core_ext/hash/indifferent_access" module ActiveRecord # Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column. @@ -114,8 +114,8 @@ module ActiveRecord def stored_attributes parent = superclass.respond_to?(:stored_attributes) ? superclass.stored_attributes : {} - if self.local_stored_attributes - parent.merge!(self.local_stored_attributes) { |k, a, b| a | b } + if local_stored_attributes + parent.merge!(local_stored_attributes) { |k, a, b| a | b } end parent end @@ -177,34 +177,34 @@ module ActiveRecord end end - class IndifferentCoder # :nodoc: - def initialize(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) - end - end + class IndifferentCoder # :nodoc: + def initialize(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) + end + end - def dump(obj) - @coder.dump self.class.as_indifferent_hash(obj) - end + def dump(obj) + @coder.dump self.class.as_indifferent_hash(obj) + end - def load(yaml) - self.class.as_indifferent_hash(@coder.load(yaml || '')) - end + def load(yaml) + self.class.as_indifferent_hash(@coder.load(yaml || "")) + end - def self.as_indifferent_hash(obj) - case obj - when ActiveSupport::HashWithIndifferentAccess - obj - when Hash - obj.with_indifferent_access - else - ActiveSupport::HashWithIndifferentAccess.new + def self.as_indifferent_hash(obj) + case obj + when ActiveSupport::HashWithIndifferentAccess + obj + when Hash + obj.with_indifferent_access + else + ActiveSupport::HashWithIndifferentAccess.new + end end end - end end end diff --git a/activerecord/lib/active_record/suppressor.rb b/activerecord/lib/active_record/suppressor.rb index b3644bf569..d9acb1a1dc 100644 --- a/activerecord/lib/active_record/suppressor.rb +++ b/activerecord/lib/active_record/suppressor.rb @@ -30,14 +30,19 @@ module ActiveRecord module ClassMethods def suppress(&block) + previous_state = SuppressorRegistry.suppressed[name] SuppressorRegistry.suppressed[name] = true yield ensure - SuppressorRegistry.suppressed[name] = false + SuppressorRegistry.suppressed[name] = previous_state end end - def create_or_update(*args) # :nodoc: + def save(*) # :nodoc: + SuppressorRegistry.suppressed[self.class.name] ? true : super + end + + def save!(*) # :nodoc: SuppressorRegistry.suppressed[self.class.name] ? true : super end end diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb index 0faad48ce3..58184f3872 100644 --- a/activerecord/lib/active_record/table_metadata.rb +++ b/activerecord/lib/active_record/table_metadata.rb @@ -10,9 +10,7 @@ module ActiveRecord end def resolve_column_aliases(hash) - # This method is a hot spot, so for now, use Hash[] to dup the hash. - # https://bugs.ruby-lang.org/issues/7166 - new_hash = Hash[hash] + new_hash = hash.dup hash.each do |key, _| if (key.is_a?(Symbol)) && klass.attribute_alias?(key) new_hash[klass.attribute_alias(key)] = new_hash.delete(key) @@ -33,19 +31,24 @@ module ActiveRecord if klass klass.type_for_attribute(column_name.to_s) else - Type::Value.new + Type.default_value end end + def has_column?(column_name) + klass && klass.columns_hash.key?(column_name.to_s) + end + def associated_with?(association_name) klass && klass._reflect_on_association(association_name) end def associated_table(table_name) - return self if table_name == arel_table.name + association = klass._reflect_on_association(table_name) || klass._reflect_on_association(table_name.singularize) - association = klass._reflect_on_association(table_name) - if association && !association.polymorphic? + if !association && table_name == arel_table.name + return self + elsif association && !association.polymorphic? association_klass = association.klass arel_table = association_klass.arel_table.alias(table_name) else @@ -63,6 +66,6 @@ module ActiveRecord protected - attr_reader :klass, :arel_table, :association + attr_reader :klass, :arel_table, :association end end diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 8f52e9068a..a19913f2a8 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/string/filters' +require "active_support/core_ext/string/filters" module ActiveRecord module Tasks # :nodoc: @@ -40,10 +40,10 @@ module ActiveRecord attr_writer :current_config, :db_dir, :migrations_paths, :fixtures_path, :root, :env, :seed_loader attr_accessor :database_configuration - LOCAL_HOSTS = ['127.0.0.1', 'localhost'] + LOCAL_HOSTS = ["127.0.0.1", "localhost"] def check_protected_environments! - unless ENV['DISABLE_DATABASE_ENVIRONMENT_CHECK'] + unless ENV["DISABLE_DATABASE_ENVIRONMENT_CHECK"] current = ActiveRecord::Migrator.current_environment stored = ActiveRecord::Migrator.last_stored_environment @@ -72,15 +72,15 @@ module ActiveRecord end def migrations_paths - @migrations_paths ||= Rails.application.paths['db/migrate'].to_a + @migrations_paths ||= Rails.application.paths["db/migrate"].to_a end def fixtures_path - @fixtures_path ||= if ENV['FIXTURES_PATH'] - File.join(root, ENV['FIXTURES_PATH']) - else - File.join(root, 'test', 'fixtures') - end + @fixtures_path ||= if ENV["FIXTURES_PATH"] + File.join(root, ENV["FIXTURES_PATH"]) + else + File.join(root, "test", "fixtures") + end end def root @@ -96,7 +96,7 @@ module ActiveRecord end def current_config(options = {}) - options.reverse_merge! :env => env + options.reverse_merge! env: env if options.has_key?(:config) @current_config = options[:config] else @@ -106,9 +106,10 @@ module ActiveRecord def create(*arguments) configuration = arguments.first - class_for_adapter(configuration['adapter']).new(*arguments).create + class_for_adapter(configuration["adapter"]).new(*arguments).create + $stdout.puts "Created database '#{configuration['database']}'" rescue DatabaseAlreadyExists - $stderr.puts "#{configuration['database']} already exists" + $stderr.puts "Database '#{configuration['database']}' already exists" rescue Exception => error $stderr.puts error $stderr.puts "Couldn't create database for #{configuration.inspect}" @@ -116,7 +117,11 @@ module ActiveRecord end def create_all + old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.connection_specification_name) each_local_configuration { |configuration| create configuration } + if old_pool + ActiveRecord::Base.connection_handler.establish_connection(old_pool.spec.to_hash) + end end def create_current(environment = env) @@ -128,12 +133,13 @@ module ActiveRecord def drop(*arguments) configuration = arguments.first - class_for_adapter(configuration['adapter']).new(*arguments).drop + class_for_adapter(configuration["adapter"]).new(*arguments).drop + $stdout.puts "Dropped database '#{configuration['database']}'" rescue ActiveRecord::NoDatabaseError $stderr.puts "Database '#{configuration['database']}' does not exist" rescue Exception => error $stderr.puts error - $stderr.puts "Couldn't drop #{configuration['database']}" + $stderr.puts "Couldn't drop database '#{configuration['database']}'" raise end @@ -150,11 +156,12 @@ module ActiveRecord def migrate verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil - scope = ENV['SCOPE'] + scope = ENV["SCOPE"] verbose_was, Migration.verbose = Migration.verbose, verbose Migrator.migrate(migrations_paths, version) do |migration| scope.blank? || scope == migration.scope end + ActiveRecord::Base.clear_cache! ensure Migration.verbose = verbose_was end @@ -165,7 +172,7 @@ module ActiveRecord def charset(*arguments) configuration = arguments.first - class_for_adapter(configuration['adapter']).new(*arguments).charset + class_for_adapter(configuration["adapter"]).new(*arguments).charset end def collation_current(environment = env) @@ -174,11 +181,11 @@ module ActiveRecord def collation(*arguments) configuration = arguments.first - class_for_adapter(configuration['adapter']).new(*arguments).collation + class_for_adapter(configuration["adapter"]).new(*arguments).collation end def purge(configuration) - class_for_adapter(configuration['adapter']).new(configuration).purge + class_for_adapter(configuration["adapter"]).new(configuration).purge end def purge_all @@ -197,13 +204,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) 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) end def load_schema(configuration, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc: @@ -268,40 +275,39 @@ module ActiveRecord private - def class_for_adapter(adapter) - key = @tasks.keys.detect { |pattern| adapter[pattern] } - unless key - raise DatabaseNotSupported, "Rake tasks not supported by '#{adapter}' adapter" + def class_for_adapter(adapter) + key = @tasks.keys.detect { |pattern| adapter[pattern] } + unless key + raise DatabaseNotSupported, "Rake tasks not supported by '#{adapter}' adapter" + end + @tasks[key] end - @tasks[key] - end - def each_current_configuration(environment) - environments = [environment] - # add test environment only if no RAILS_ENV was specified. - environments << 'test' if environment == 'development' && ENV['RAILS_ENV'].nil? + def each_current_configuration(environment) + environments = [environment] + environments << "test" if environment == "development" - configurations = ActiveRecord::Base.configurations.values_at(*environments) - configurations.compact.each do |configuration| - yield configuration unless configuration['database'].blank? + configurations = ActiveRecord::Base.configurations.values_at(*environments) + configurations.compact.each do |configuration| + yield configuration unless configuration["database"].blank? + end end - end - def each_local_configuration - ActiveRecord::Base.configurations.each_value do |configuration| - next unless configuration['database'] + def each_local_configuration + ActiveRecord::Base.configurations.each_value do |configuration| + next unless configuration["database"] - if local_database?(configuration) - yield configuration - else - $stderr.puts "This task only modifies local databases. #{configuration['database']} is on a remote host." + if local_database?(configuration) + yield configuration + else + $stderr.puts "This task only modifies local databases. #{configuration['database']} is on a remote host." + end end end - end - def local_database?(configuration) - configuration['host'].blank? || LOCAL_HOSTS.include?(configuration['host']) - end + def local_database?(configuration) + configuration["host"].blank? || LOCAL_HOSTS.include?(configuration["host"]) + end end end end diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb index 7a49322e06..5cdb3d53f6 100644 --- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -11,10 +11,10 @@ module ActiveRecord def create establish_connection configuration_without_database - connection.create_database configuration['database'], creation_options + connection.create_database configuration["database"], creation_options establish_connection configuration rescue ActiveRecord::StatementInvalid => error - if /database exists/ === error.message + if error.message.include?("database exists") raise DatabaseAlreadyExists else raise @@ -23,26 +23,26 @@ module ActiveRecord if error.respond_to?(:errno) && error.errno == ACCESS_DENIED_ERROR $stdout.print error.message establish_connection root_configuration_without_database - connection.create_database configuration['database'], creation_options - if configuration['username'] != 'root' - connection.execute grant_statement.gsub(/\s+/, ' ').strip + connection.create_database configuration["database"], creation_options + if configuration["username"] != "root" + connection.execute grant_statement.gsub(/\s+/, " ").strip end establish_connection configuration else $stderr.puts error.inspect $stderr.puts "Couldn't create database for #{configuration.inspect}, #{creation_options.inspect}" - $stderr.puts "(If you set the charset manually, make sure you have a matching collation)" if configuration['encoding'] + $stderr.puts "(If you set the charset manually, make sure you have a matching collation)" if configuration["encoding"] end end def drop establish_connection configuration - connection.drop_database configuration['database'] + connection.drop_database configuration["database"] end def purge establish_connection configuration - connection.recreate_database configuration['database'], creation_options + connection.recreate_database configuration["database"], creation_options end def charset @@ -58,94 +58,95 @@ module ActiveRecord args.concat(["--result-file", "#{filename}"]) args.concat(["--no-data"]) args.concat(["--routines"]) + args.concat(["--skip-comments"]) args.concat(["#{configuration['database']}"]) - run_cmd('mysqldump', args, 'dumping') + run_cmd("mysqldump", args, "dumping") end def structure_load(filename) args = prepare_command_options - args.concat(['--execute', %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}]) + args.concat(["--execute", %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}]) args.concat(["--database", "#{configuration['database']}"]) - run_cmd('mysql', args, 'loading') + run_cmd("mysql", args, "loading") end private - def configuration - @configuration - end + def configuration + @configuration + end - def configuration_without_database - configuration.merge('database' => nil) - end + def configuration_without_database + configuration.merge("database" => nil) + end - def creation_options - Hash.new.tap do |options| - options[:charset] = configuration['encoding'] if configuration.include? 'encoding' - options[:collation] = configuration['collation'] if configuration.include? 'collation' + def creation_options + Hash.new.tap do |options| + options[:charset] = configuration["encoding"] if configuration.include? "encoding" + options[:collation] = configuration["collation"] if configuration.include? "collation" + end end - end - def error_class - if configuration['adapter'] =~ /jdbc/ - require 'active_record/railties/jdbcmysql_error' - ArJdbcMySQL::Error - elsif defined?(Mysql2) - Mysql2::Error - else - StandardError + def error_class + if configuration["adapter"].include?("jdbc") + require "active_record/railties/jdbcmysql_error" + ArJdbcMySQL::Error + elsif defined?(Mysql2) + Mysql2::Error + else + StandardError + end end - end - def grant_statement - <<-SQL + def grant_statement + <<-SQL GRANT ALL PRIVILEGES ON #{configuration['database']}.* TO '#{configuration['username']}'@'localhost' IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION; - SQL - end + SQL + end - def root_configuration_without_database - configuration_without_database.merge( - 'username' => 'root', - 'password' => root_password - ) - end + def root_configuration_without_database + configuration_without_database.merge( + "username" => "root", + "password" => root_password + ) + end - def root_password - $stdout.print "Please provide the root password for your MySQL installation\n>" - $stdin.gets.strip - end + def root_password + $stdout.print "Please provide the root password for your MySQL installation\n>" + $stdin.gets.strip + end - def prepare_command_options - args = { - 'host' => '--host', - 'port' => '--port', - 'socket' => '--socket', - 'username' => '--user', - 'password' => '--password', - 'encoding' => '--default-character-set', - 'sslca' => '--ssl-ca', - 'sslcert' => '--ssl-cert', - 'sslcapath' => '--ssl-capath', - 'sslcipher' => '--ssh-cipher', - 'sslkey' => '--ssl-key' - }.map { |opt, arg| "#{arg}=#{configuration[opt]}" if configuration[opt] }.compact - - args - end + def prepare_command_options + args = { + "host" => "--host", + "port" => "--port", + "socket" => "--socket", + "username" => "--user", + "password" => "--password", + "encoding" => "--default-character-set", + "sslca" => "--ssl-ca", + "sslcert" => "--ssl-cert", + "sslcapath" => "--ssl-capath", + "sslcipher" => "--ssl-cipher", + "sslkey" => "--ssl-key" + }.map { |opt, arg| "#{arg}=#{configuration[opt]}" if configuration[opt] }.compact + + args + end - def run_cmd(cmd, args, action) - fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args) - end + def run_cmd(cmd, args, action) + fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args) + end - def run_cmd_error(cmd, args, action) - msg = "failed to execute: `#{cmd}`\n" - msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n" - msg - end + def run_cmd_error(cmd, args, action) + msg = "failed to execute: `#{cmd}`\n" + msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n" + msg + end end end end diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb index 8b4874044c..03d3049001 100644 --- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb @@ -1,7 +1,8 @@ module ActiveRecord module Tasks # :nodoc: class PostgreSQLDatabaseTasks # :nodoc: - DEFAULT_ENCODING = ENV['CHARSET'] || 'utf8' + DEFAULT_ENCODING = ENV["CHARSET"] || "utf8" + ON_ERROR_STOP_1 = "ON_ERROR_STOP=1".freeze delegate :connection, :establish_connection, :clear_active_connections!, to: ActiveRecord::Base @@ -12,11 +13,11 @@ module ActiveRecord def create(master_established = false) establish_master_connection unless master_established - connection.create_database configuration['database'], - configuration.merge('encoding' => encoding) + connection.create_database configuration["database"], + configuration.merge("encoding" => encoding) establish_connection configuration rescue ActiveRecord::StatementInvalid => error - if /database .* already exists/ === error.message + if /database .* already exists/.match?(error.message) raise DatabaseAlreadyExists else raise @@ -25,7 +26,7 @@ module ActiveRecord def drop establish_master_connection - connection.drop_database configuration['database'] + connection.drop_database configuration["database"] end def charset @@ -45,66 +46,67 @@ module ActiveRecord def structure_dump(filename) set_psql_env - search_path = case ActiveRecord::Base.dump_schemas - when :schema_search_path - configuration['schema_search_path'] - when :all - nil - when String - ActiveRecord::Base.dump_schemas - end + search_path = \ + case ActiveRecord::Base.dump_schemas + when :schema_search_path + configuration["schema_search_path"] + when :all + nil + when String + ActiveRecord::Base.dump_schemas + end - args = ['-s', '-x', '-O', '-f', filename] + args = ["-s", "-x", "-O", "-f", filename] unless search_path.blank? - args += search_path.split(',').map do |part| + args += search_path.split(",").map do |part| "--schema=#{part.strip}" end end - args << configuration['database'] - run_cmd('pg_dump', args, 'dumping') + args << configuration["database"] + run_cmd("pg_dump", args, "dumping") File.open(filename, "a") { |f| f << "SET search_path TO #{connection.schema_search_path};\n\n" } end def structure_load(filename) set_psql_env - args = [ '-q', '-f', filename, configuration['database'] ] - run_cmd('psql', args, 'loading' ) + args = [ "-v", ON_ERROR_STOP_1, "-q", "-f", filename, configuration["database"] ] + run_cmd("psql", args, "loading" ) end private - def configuration - @configuration - end + def configuration + @configuration + end - def encoding - configuration['encoding'] || DEFAULT_ENCODING - end + def encoding + configuration["encoding"] || DEFAULT_ENCODING + end - def establish_master_connection - establish_connection configuration.merge( - 'database' => 'postgres', - 'schema_search_path' => 'public' - ) - end + def establish_master_connection + establish_connection configuration.merge( + "database" => "postgres", + "schema_search_path" => "public" + ) + end - def set_psql_env - ENV['PGHOST'] = configuration['host'] if configuration['host'] - ENV['PGPORT'] = configuration['port'].to_s if configuration['port'] - ENV['PGPASSWORD'] = configuration['password'].to_s if configuration['password'] - ENV['PGUSER'] = configuration['username'].to_s if configuration['username'] - end + def set_psql_env + ENV["PGHOST"] = configuration["host"] if configuration["host"] + ENV["PGPORT"] = configuration["port"].to_s if configuration["port"] + ENV["PGPASSWORD"] = configuration["password"].to_s if configuration["password"] + ENV["PGUSER"] = configuration["username"].to_s if configuration["username"] + end - def run_cmd(cmd, args, action) - fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args) - end + def run_cmd(cmd, args, action) + fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args) + end - def run_cmd_error(cmd, args, action) - msg = "failed to execute:\n" - msg << "#{cmd} #{args.join(' ')}\n\n" - msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n" - msg - end + def run_cmd_error(cmd, args, action) + msg = "failed to execute:\n" + msg << "#{cmd} #{args.join(' ')}\n\n" + msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n" + msg + end end end end diff --git a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb index 9ec3c8a94a..31f1b7efd4 100644 --- a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb @@ -8,15 +8,15 @@ module ActiveRecord end def create - raise DatabaseAlreadyExists if File.exist?(configuration['database']) + raise DatabaseAlreadyExists if File.exist?(configuration["database"]) establish_connection configuration connection end def drop - require 'pathname' - path = Pathname.new configuration['database'] + require "pathname" + path = Pathname.new configuration["database"] file = path.absolute? ? path.to_s : File.join(root, path) FileUtils.rm(file) @@ -36,24 +36,24 @@ module ActiveRecord end def structure_dump(filename) - dbfile = configuration['database'] + dbfile = configuration["database"] `sqlite3 #{dbfile} .schema > #{filename}` end def structure_load(filename) - dbfile = configuration['database'] + dbfile = configuration["database"] `sqlite3 #{dbfile} < "#{filename}"` end private - def configuration - @configuration - end + def configuration + @configuration + end - def root - @root - end + def root + @root + end end end end diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index d9c18a5e38..6641ab5df1 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -54,7 +54,7 @@ module ActiveRecord private def _create_record - if self.record_timestamps + if record_timestamps current_time = current_time_from_proper_timezone all_timestamp_attributes.each do |column| @@ -82,7 +82,7 @@ module ActiveRecord end def should_record_timestamps? - self.record_timestamps && (!partial_writes? || changed?) + record_timestamps && (!partial_writes? || changed?) end def timestamp_attributes_for_create_in_model diff --git a/activerecord/lib/active_record/touch_later.rb b/activerecord/lib/active_record/touch_later.rb index 9a80a63e28..c337a7532f 100644 --- a/activerecord/lib/active_record/touch_later.rb +++ b/activerecord/lib/active_record/touch_later.rb @@ -8,7 +8,12 @@ module ActiveRecord end def touch_later(*names) # :nodoc: - raise ActiveRecordError, "cannot touch on a new record object" unless persisted? + unless persisted? + raise ActiveRecordError, <<-MSG.squish + cannot touch on a new or destroyed record object. Consider using + persisted?, new_record?, or destroyed? before touching + MSG + end @_defer_touch_attrs ||= timestamp_attributes_for_update_in_model @_defer_touch_attrs |= names @@ -53,6 +58,5 @@ module ActiveRecord def belongs_to_touch_method :touch_later end - end end diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 77c2845d88..af3fc88282 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -189,8 +189,8 @@ module ActiveRecord # # === Caveats # - # If you're on MySQL, then do not use DDL operations in nested transactions - # blocks that are emulated with savepoints. That is, do not execute statements + # If you're on MySQL, then do not use Data Definition Language(DDL) operations in nested + # transactions blocks that are emulated with savepoints. That is, do not execute statements # like 'CREATE TABLE' inside such blocks. This is because MySQL automatically # releases all savepoints upon executing a DDL operation. When +transaction+ # is finished and tries to release the savepoint it created earlier, a @@ -275,34 +275,34 @@ module ActiveRecord end def raise_in_transactional_callbacks - ActiveSupport::Deprecation.warn('ActiveRecord::Base.raise_in_transactional_callbacks is deprecated and will be removed without replacement.') + 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.') + 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 = {}) - options = args.extract_options!.merge!(enforced_options) - args << options + def set_options_for_callbacks!(args, enforced_options = {}) + options = args.extract_options!.merge!(enforced_options) + args << options - if options[:on] - fire_on = Array(options[:on]) - assert_valid_transaction_action(fire_on) - options[:if] = Array(options[:if]) - options[:if] << "transaction_include_any_action?(#{fire_on})" + if options[:on] + fire_on = Array(options[:on]) + assert_valid_transaction_action(fire_on) + options[:if] = Array(options[:if]) + options[:if] << "transaction_include_any_action?(#{fire_on})" + end end - end - def assert_valid_transaction_action(actions) - if (actions - ACTIONS).any? - raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS}" + def assert_valid_transaction_action(actions) + if (actions - ACTIONS).any? + raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS}" + end end - end end # See ActiveRecord::Transactions::ClassMethods for detailed documentation. @@ -409,101 +409,101 @@ module ActiveRecord protected - # 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: - @_start_transaction_state[:id] = id - @_start_transaction_state.reverse_merge!( - new_record: @new_record, - destroyed: @destroyed, - frozen?: frozen?, - ) - @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1 - end + # 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: + @_start_transaction_state[:id] = id + @_start_transaction_state.reverse_merge!( + new_record: @new_record, + destroyed: @destroyed, + frozen?: frozen?, + ) + @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1 + end - # Clear the new record state and id of a record. - def clear_transaction_record_state #:nodoc: - @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1 - force_clear_transaction_record_state if @_start_transaction_state[:level] < 1 - end + # Clear the new record state and id of a record. + def clear_transaction_record_state #:nodoc: + @_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: - @_start_transaction_state.clear - end + # Force to clear the transaction record state. + def force_clear_transaction_record_state #:nodoc: + @_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: - unless @_start_transaction_state.empty? - transaction_level = (@_start_transaction_state[:level] || 0) - 1 - if transaction_level < 1 || force - restore_state = @_start_transaction_state - thaw - @new_record = restore_state[:new_record] - @destroyed = restore_state[:destroyed] - pk = self.class.primary_key - if pk && read_attribute(pk) != restore_state[:id] - write_attribute(pk, restore_state[:id]) + # 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: + unless @_start_transaction_state.empty? + transaction_level = (@_start_transaction_state[:level] || 0) - 1 + if transaction_level < 1 || force + restore_state = @_start_transaction_state + thaw + @new_record = restore_state[:new_record] + @destroyed = restore_state[:destroyed] + pk = self.class.primary_key + if pk && read_attribute(pk) != restore_state[:id] + write_attribute(pk, restore_state[:id]) + end + freeze if restore_state[:frozen?] end - freeze if restore_state[:frozen?] end end - 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: - @_start_transaction_state[state] - 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: + @_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: - actions.any? do |action| - case action - when :create - transaction_record_state(:new_record) - when :destroy - destroyed? - when :update - !(transaction_record_state(:new_record) || destroyed?) + # Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks. + def transaction_include_any_action?(actions) #:nodoc: + actions.any? do |action| + case action + when :create + transaction_record_state(:new_record) + when :destroy + destroyed? + when :update + !(transaction_record_state(:new_record) || destroyed?) + end end end - end private - def set_transaction_state(state) # :nodoc: - @transaction_state = state - end + def set_transaction_state(state) # :nodoc: + @transaction_state = state + end - def has_transactional_callbacks? # :nodoc: - !_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_before_commit_callbacks.empty? - end + def has_transactional_callbacks? # :nodoc: + !_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_before_commit_callbacks.empty? + end - # Updates the attributes on this particular Active Record object so that - # if it's associated with a transaction, then the state of the Active Record - # object will be updated to reflect the current state of the transaction - # - # The +@transaction_state+ variable stores the states of the associated - # transaction. This relies on the fact that a transaction can only be in - # one rollback or commit (otherwise a list of states would be required) - # Each Active Record object inside of a transaction carries that transaction's - # TransactionState. - # - # This method checks to see if the ActiveRecord object's state reflects - # the TransactionState, and rolls back or commits the Active Record object - # as appropriate. - # - # Since Active Record objects can be inside multiple transactions, this - # method recursively goes through the parent of the TransactionState and - # checks if the Active Record object reflects the state of the object. - def sync_with_transaction_state - update_attributes_from_transaction_state(@transaction_state) - end + # Updates the attributes on this particular Active Record object so that + # if it's associated with a transaction, then the state of the Active Record + # object will be updated to reflect the current state of the transaction. + # + # The +@transaction_state+ variable stores the states of the associated + # transaction. This relies on the fact that a transaction can only be in + # one rollback or commit (otherwise a list of states would be required). + # Each Active Record object inside of a transaction carries that transaction's + # TransactionState. + # + # This method checks to see if the ActiveRecord object's state reflects + # the TransactionState, and rolls back or commits the Active Record object + # as appropriate. + # + # Since Active Record objects can be inside multiple transactions, this + # method recursively goes through the parent of the TransactionState and + # checks if the Active Record object reflects the state of the object. + def sync_with_transaction_state + update_attributes_from_transaction_state(@transaction_state) + end - def update_attributes_from_transaction_state(transaction_state) - if transaction_state && transaction_state.finalized? - restore_transaction_record_state if transaction_state.rolledback? - clear_transaction_record_state + def update_attributes_from_transaction_state(transaction_state) + if transaction_state && transaction_state.finalized? + restore_transaction_record_state if transaction_state.rolledback? + clear_transaction_record_state + end end - end end end diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb index e210e94f00..0b48d2186a 100644 --- a/activerecord/lib/active_record/type.rb +++ b/activerecord/lib/active_record/type.rb @@ -1,17 +1,17 @@ -require 'active_model/type' +require "active_model/type" -require 'active_record/type/internal/abstract_json' -require 'active_record/type/internal/timezone' +require "active_record/type/internal/abstract_json" +require "active_record/type/internal/timezone" -require 'active_record/type/date' -require 'active_record/type/date_time' -require 'active_record/type/time' +require "active_record/type/date" +require "active_record/type/date_time" +require "active_record/type/time" -require 'active_record/type/serialized' -require 'active_record/type/adapter_specific_registry' +require "active_record/type/serialized" +require "active_record/type/adapter_specific_registry" -require 'active_record/type/type_map' -require 'active_record/type/hash_lookup_type_map' +require "active_record/type/type_map" +require "active_record/type/hash_lookup_type_map" module ActiveRecord module Type @@ -37,6 +37,10 @@ module ActiveRecord registry.lookup(*args, adapter: adapter, **kwargs) end + def default_value # :nodoc: + @default_value ||= Value.new + end + private def current_adapter_name @@ -61,7 +65,7 @@ module ActiveRecord register(:binary, Type::Binary, override: false) register(:boolean, Type::Boolean, override: false) register(:date, Type::Date, override: false) - register(:date_time, Type::DateTime, override: false) + register(:datetime, Type::DateTime, override: false) register(:decimal, Type::Decimal, override: false) register(:float, Type::Float, override: false) register(:integer, Type::Integer, 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 d440eac619..d0f9581576 100644 --- a/activerecord/lib/active_record/type/adapter_specific_registry.rb +++ b/activerecord/lib/active_record/type/adapter_specific_registry.rb @@ -1,4 +1,4 @@ -require 'active_model/type/registry' +require "active_model/type/registry" module ActiveRecord # :stopdoc: @@ -10,15 +10,15 @@ module ActiveRecord private - def registration_klass - Registration - end + def registration_klass + Registration + end - def find_registration(symbol, *args) - registrations - .select { |registration| registration.matches?(symbol, *args) } - .max - end + def find_registration(symbol, *args) + registrations + .select { |registration| registration.matches?(symbol, *args) } + .max + end end class Registration @@ -52,42 +52,42 @@ module ActiveRecord protected - attr_reader :name, :block, :adapter, :override - - def priority - result = 0 - if adapter - result |= 1 - end - if override - result |= 2 + attr_reader :name, :block, :adapter, :override + + def priority + result = 0 + if adapter + result |= 1 + end + if override + result |= 2 + end + result end - result - end - def priority_except_adapter - priority & 0b111111100 - end + def priority_except_adapter + priority & 0b111111100 + end private - def matches_adapter?(adapter: nil, **) - (self.adapter.nil? || adapter == self.adapter) - end + def matches_adapter?(adapter: nil, **) + (self.adapter.nil? || adapter == self.adapter) + end - def conflicts_with?(other) - same_priority_except_adapter?(other) && - has_adapter_conflict?(other) - end + def conflicts_with?(other) + same_priority_except_adapter?(other) && + has_adapter_conflict?(other) + end - def same_priority_except_adapter?(other) - priority_except_adapter == other.priority_except_adapter - end + def same_priority_except_adapter?(other) + priority_except_adapter == other.priority_except_adapter + end - def has_adapter_conflict?(other) - (override.nil? && other.adapter) || - (adapter && other.override.nil?) - end + def has_adapter_conflict?(other) + (override.nil? && other.adapter) || + (adapter && other.override.nil?) + end end class DecorationRegistration < Registration @@ -112,15 +112,15 @@ module ActiveRecord protected - attr_reader :options, :klass + attr_reader :options, :klass private - def matches_options?(**kwargs) - options.all? do |key, value| - kwargs[key] == value + def matches_options?(**kwargs) + options.all? do |key, value| + kwargs[key] == value + end end - end end end diff --git a/activerecord/lib/active_record/type/hash_lookup_type_map.rb b/activerecord/lib/active_record/type/hash_lookup_type_map.rb index 3b01e3f8ca..0145d5d6c1 100644 --- a/activerecord/lib/active_record/type/hash_lookup_type_map.rb +++ b/activerecord/lib/active_record/type/hash_lookup_type_map.rb @@ -15,9 +15,9 @@ module ActiveRecord private - def perform_fetch(type, *args, &block) - @mapping.fetch(type, block).call(type, *args) - end + def perform_fetch(type, *args, &block) + @mapping.fetch(type, block).call(type, *args) + end end end end diff --git a/activerecord/lib/active_record/type/internal/abstract_json.rb b/activerecord/lib/active_record/type/internal/abstract_json.rb index 097d1bd363..e19c5a14da 100644 --- a/activerecord/lib/active_record/type/internal/abstract_json.rb +++ b/activerecord/lib/active_record/type/internal/abstract_json.rb @@ -17,10 +17,10 @@ module ActiveRecord end def serialize(value) - if value.is_a?(::Array) || value.is_a?(::Hash) - ::ActiveSupport::JSON.encode(value) + if value.nil? + nil else - value + ::ActiveSupport::JSON.encode(value) end end diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb index 4ff0740cfb..ac9134bfcb 100644 --- a/activerecord/lib/active_record/type/serialized.rb +++ b/activerecord/lib/active_record/type/serialized.rb @@ -32,7 +32,7 @@ module ActiveRecord def changed_in_place?(raw_old_value, value) return false if value.nil? - raw_new_value = serialize(value) + raw_new_value = encoded(value) raw_old_value.nil? != raw_new_value.nil? || subtype.changed_in_place?(raw_old_value, raw_new_value) end @@ -49,9 +49,15 @@ module ActiveRecord private - def default_value?(value) - value == coder.load(nil) - end + def default_value?(value) + value == coder.load(nil) + end + + def encoded(value) + unless default_value?(value) + coder.dump(value) + end + end end end end diff --git a/activerecord/lib/active_record/type/time.rb b/activerecord/lib/active_record/type/time.rb index 70988d84ff..b9bac87c67 100644 --- a/activerecord/lib/active_record/type/time.rb +++ b/activerecord/lib/active_record/type/time.rb @@ -2,7 +2,18 @@ module ActiveRecord module Type class Time < ActiveModel::Type::Time include Internal::Timezone + + class Value < DelegateClass(::Time) # :nodoc: + end + + def serialize(value) + case value = super + when ::Time + Value.new(value) + else + value + end + end end end end - diff --git a/activerecord/lib/active_record/type/type_map.rb b/activerecord/lib/active_record/type/type_map.rb index 850a7a4e09..7bce82a1ff 100644 --- a/activerecord/lib/active_record/type/type_map.rb +++ b/activerecord/lib/active_record/type/type_map.rb @@ -1,4 +1,4 @@ -require 'concurrent/map' +require "concurrent/map" module ActiveRecord module Type @@ -11,7 +11,7 @@ module ActiveRecord end def lookup(lookup_key, *args) - fetch(lookup_key, *args) { default_value } + fetch(lookup_key, *args) { Type.default_value } end def fetch(lookup_key, *args, &block) @@ -44,21 +44,17 @@ module ActiveRecord private - def perform_fetch(lookup_key, *args) - matching_pair = @mapping.reverse_each.detect do |key, _| - key === lookup_key - end + def perform_fetch(lookup_key, *args) + matching_pair = @mapping.reverse_each.detect do |key, _| + key === lookup_key + end - if matching_pair - matching_pair.last.call(lookup_key, *args) - else - yield lookup_key, *args + if matching_pair + matching_pair.last.call(lookup_key, *args) + else + yield lookup_key, *args + end end - end - - def default_value - @default_value ||= ActiveModel::Type::Value.new - end end end end diff --git a/activerecord/lib/active_record/type_caster.rb b/activerecord/lib/active_record/type_caster.rb index accc339d00..f1686e4913 100644 --- a/activerecord/lib/active_record/type_caster.rb +++ b/activerecord/lib/active_record/type_caster.rb @@ -1,5 +1,5 @@ -require 'active_record/type_caster/map' -require 'active_record/type_caster/connection' +require "active_record/type_caster/map" +require "active_record/type_caster/connection" module ActiveRecord module TypeCaster # :nodoc: diff --git a/activerecord/lib/active_record/type_caster/connection.rb b/activerecord/lib/active_record/type_caster/connection.rb index 7ed8dcc313..6c54792e26 100644 --- a/activerecord/lib/active_record/type_caster/connection.rb +++ b/activerecord/lib/active_record/type_caster/connection.rb @@ -14,16 +14,16 @@ module ActiveRecord protected - attr_reader :table_name - delegate :connection, to: :@klass + attr_reader :table_name + delegate :connection, to: :@klass private - def column_for(attribute_name) - if connection.schema_cache.data_source_exists?(table_name) - connection.schema_cache.columns_hash(table_name)[attribute_name.to_s] + def column_for(attribute_name) + if connection.schema_cache.data_source_exists?(table_name) + connection.schema_cache.columns_hash(table_name)[attribute_name.to_s] + end end - end end end end diff --git a/activerecord/lib/active_record/type_caster/map.rb b/activerecord/lib/active_record/type_caster/map.rb index 3a367b3999..52529a6b42 100644 --- a/activerecord/lib/active_record/type_caster/map.rb +++ b/activerecord/lib/active_record/type_caster/map.rb @@ -13,7 +13,7 @@ module ActiveRecord protected - attr_reader :types + attr_reader :types end end end diff --git a/activerecord/lib/active_record/validations/absence.rb b/activerecord/lib/active_record/validations/absence.rb index 2e19e6dc5c..641d041f3d 100644 --- a/activerecord/lib/active_record/validations/absence.rb +++ b/activerecord/lib/active_record/validations/absence.rb @@ -2,7 +2,6 @@ module ActiveRecord module Validations class AbsenceValidator < ActiveModel::Validations::AbsenceValidator # :nodoc: def validate_each(record, attribute, association_or_value) - return unless should_validate?(record) if record.class._reflect_on_association(attribute) association_or_value = Array.wrap(association_or_value).reject(&:marked_for_destruction?) end diff --git a/activerecord/lib/active_record/validations/length.rb b/activerecord/lib/active_record/validations/length.rb index 69e048eef1..0e0cebce4a 100644 --- a/activerecord/lib/active_record/validations/length.rb +++ b/activerecord/lib/active_record/validations/length.rb @@ -2,23 +2,11 @@ module ActiveRecord module Validations class LengthValidator < ActiveModel::Validations::LengthValidator # :nodoc: def validate_each(record, attribute, association_or_value) - return unless should_validate?(record) || associations_are_dirty?(record) if association_or_value.respond_to?(:loaded?) && association_or_value.loaded? association_or_value = association_or_value.target.reject(&:marked_for_destruction?) end super end - - def associations_are_dirty?(record) - attributes.any? do |attribute| - value = record.read_attribute_for_validation(attribute) - if value.respond_to?(:loaded?) && value.loaded? - value.target.any?(&:marked_for_destruction?) - else - false - end - end - end end module ClassMethods diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb index 7e85ed43ac..ad82ea66c4 100644 --- a/activerecord/lib/active_record/validations/presence.rb +++ b/activerecord/lib/active_record/validations/presence.rb @@ -2,7 +2,6 @@ module ActiveRecord module Validations class PresenceValidator < ActiveModel::Validations::PresenceValidator # :nodoc: def validate_each(record, attribute, association_or_value) - return unless should_validate?(record) if record.class._reflect_on_association(attribute) association_or_value = Array.wrap(association_or_value).reject(&:marked_for_destruction?) end diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index f0aa4521b5..8c4930a81d 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -11,20 +11,18 @@ module ActiveRecord end def validate_each(record, attribute, value) - return unless should_validate?(record) finder_class = find_finder_class_for(record) - table = finder_class.arel_table value = map_enum_attribute(finder_class, attribute, value) - relation = build_relation(finder_class, table, attribute, value) - if record.persisted? && finder_class.primary_key.to_s != attribute.to_s + relation = build_relation(finder_class, attribute, value) + if record.persisted? if finder_class.primary_key - relation = relation.where.not(finder_class.primary_key => record.id_was) + relation = relation.where.not(finder_class.primary_key => record.id_was || record.id) else raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.") end end - relation = scope_relation(record, table, relation) + relation = scope_relation(record, relation) relation = relation.merge(options[:conditions]) if options[:conditions] if relation.exists? @@ -51,45 +49,41 @@ module ActiveRecord class_hierarchy.detect { |klass| !klass.abstract_class? } end - def build_relation(klass, table, attribute, value) #:nodoc: + 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 - attribute_name = attribute.to_s + if value.nil? + return klass.unscoped.where!(attribute => value) + end # the attribute may be an aliased attribute - if klass.attribute_aliases[attribute_name] - attribute = klass.attribute_aliases[attribute_name] - attribute_name = attribute.to_s + 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) - value = cast_type.serialize(value) - value = klass.connection.type_cast(value) - if value.is_a?(String) && column.limit - value = value.to_s[0, column.limit] - end - comparison = if !options[:case_sensitive] && !value.nil? + 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 - if value.nil? - klass.unscoped.where(comparison) - else - bind = Relation::QueryAttribute.new(attribute.to_s, value, Type::Value.new) - klass.unscoped.where(comparison, bind) + 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 - rescue RangeError - klass.none end - def scope_relation(record, table, relation) + def scope_relation(record, relation) Array(options[:scope]).each do |scope_item| if reflection = record.class._reflect_on_association(scope_item) scope_value = record.send(reflection.foreign_key) diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb index cf76a13b44..146cfacc18 100644 --- a/activerecord/lib/active_record/version.rb +++ b/activerecord/lib/active_record/version.rb @@ -1,4 +1,4 @@ -require_relative 'gem_version' +require_relative "gem_version" module ActiveRecord # Returns the version of the currently loaded ActiveRecord as a <tt>Gem::Version</tt> diff --git a/activerecord/lib/rails/generators/active_record.rb b/activerecord/lib/rails/generators/active_record.rb index dc29213235..68fca44e3b 100644 --- a/activerecord/lib/rails/generators/active_record.rb +++ b/activerecord/lib/rails/generators/active_record.rb @@ -1,7 +1,7 @@ -require 'rails/generators/named_base' -require 'rails/generators/active_model' -require 'rails/generators/active_record/migration' -require 'active_record' +require "rails/generators/named_base" +require "rails/generators/active_model" +require "rails/generators/active_record/migration" +require "active_record" module ActiveRecord module Generators # :nodoc: diff --git a/activerecord/lib/rails/generators/active_record/migration.rb b/activerecord/lib/rails/generators/active_record/migration.rb index c2b2209638..4263c11ffc 100644 --- a/activerecord/lib/rails/generators/active_record/migration.rb +++ b/activerecord/lib/rails/generators/active_record/migration.rb @@ -1,4 +1,4 @@ -require 'rails/generators/migration' +require "rails/generators/migration" module ActiveRecord module Generators # :nodoc: 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 4e5872b585..12d1f58f67 100644 --- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb +++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb @@ -1,9 +1,9 @@ -require 'rails/generators/active_record' +require "rails/generators/active_record" module ActiveRecord module Generators # :nodoc: class MigrationGenerator < Base # :nodoc: - argument :attributes, :type => :array, :default => [], :banner => "field[:type][:index] field[:type][:index]" + argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]" class_option :primary_key_type, type: :string, desc: "The type for primary key" @@ -14,43 +14,43 @@ module ActiveRecord end protected - attr_reader :migration_action, :join_tables + attr_reader :migration_action, :join_tables - # Sets the default migration template that is being used for the generation of the migration. - # Depending on command line arguments, the migration template and the table name instance - # variables are set up. - def set_local_assigns! - @migration_template = "migration.rb" - case file_name - when /^(add|remove)_.*_(?:to|from)_(.*)/ - @migration_action = $1 - @table_name = normalize_table_name($2) - when /join_table/ - if attributes.length == 2 - @migration_action = 'join' - @join_tables = pluralize_table_names? ? attributes.map(&:plural_name) : attributes.map(&:singular_name) + # Sets the default migration template that is being used for the generation of the migration. + # Depending on command line arguments, the migration template and the table name instance + # variables are set up. + def set_local_assigns! + @migration_template = "migration.rb" + case file_name + when /^(add|remove)_.*_(?:to|from)_(.*)/ + @migration_action = $1 + @table_name = normalize_table_name($2) + when /join_table/ + if attributes.length == 2 + @migration_action = "join" + @join_tables = pluralize_table_names? ? attributes.map(&:plural_name) : attributes.map(&:singular_name) - set_index_names + set_index_names + end + when /^create_(.+)/ + @table_name = normalize_table_name($1) + @migration_template = "create_table_migration.rb" end - when /^create_(.+)/ - @table_name = normalize_table_name($1) - @migration_template = "create_table_migration.rb" end - end - def set_index_names - attributes.each_with_index do |attr, i| - attr.index_name = [attr, attributes[i - 1]].map{ |a| index_name_for(a) } + def set_index_names + attributes.each_with_index do |attr, i| + attr.index_name = [attr, attributes[i - 1]].map { |a| index_name_for(a) } + end end - end - def index_name_for(attribute) - if attribute.foreign_key? - attribute.name - else - attribute.name.singularize.foreign_key - end.to_sym - end + def index_name_for(attribute) + if attribute.foreign_key? + attribute.name + else + attribute.name.singularize.foreign_key + end.to_sym + end private def attributes_with_index @@ -60,7 +60,7 @@ module ActiveRecord # A migration file name can only contain underscores (_), lowercase characters, # and numbers 0-9. Any other file name will raise an IllegalMigrationNameError. def validate_file_name! - unless file_name =~ /^[_a-z0-9]+$/ + unless /^[_a-z0-9]+$/.match?(file_name) raise IllegalMigrationNameError.new(file_name) end 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 7395839fca..f1ddc61688 100644 --- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb +++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb @@ -1,9 +1,9 @@ -require 'rails/generators/active_record' +require "rails/generators/active_record" module ActiveRecord module Generators # :nodoc: class ModelGenerator < Base # :nodoc: - argument :attributes, :type => :array, :default => [], :banner => "field[:type][:index] field[:type][:index]" + argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]" check_class_collision @@ -21,12 +21,14 @@ module ActiveRecord end def create_model_file - template 'model.rb', File.join('app/models', class_path, "#{file_name}.rb") + generate_application_record + template "model.rb", File.join("app/models", class_path, "#{file_name}.rb") end def create_module_file return if regular_class_path.empty? - template 'module.rb', File.join('app/models', "#{class_path.join('/')}.rb") if behavior == :invoke + generate_application_record + template "module.rb", File.join("app/models", "#{class_path.join('/')}.rb") if behavior == :invoke end hook_for :test_framework @@ -37,26 +39,29 @@ module ActiveRecord attributes.select { |a| !a.reference? && a.has_index? } end + # FIXME: Change this file to a symlink once RubyGems 2.5.0 is required. + def generate_application_record + if self.behavior == :invoke && !application_record_exist? + template "application_record.rb", application_record_file_name + end + end + # Used by the migration template to determine the parent name of the model def parent_class_name - options[:parent] || determine_default_parent_class + options[:parent] || "ApplicationRecord" end - def determine_default_parent_class - application_record = nil - - in_root do - application_record = if mountable_engine? - File.exist?("app/models/#{namespaced_path}/application_record.rb") - else - File.exist?('app/models/application_record.rb') - end - end + def application_record_exist? + file_exist = nil + in_root { file_exist = File.exist?(application_record_file_name) } + file_exist + end - if application_record - "ApplicationRecord" + def application_record_file_name + @application_record_file_name ||= if mountable_engine? + "app/models/#{namespaced_path}/application_record.rb" else - "ActiveRecord::Base" + "app/models/application_record.rb" end end end diff --git a/activerecord/lib/rails/generators/active_record/model/templates/application_record.rb b/activerecord/lib/rails/generators/active_record/model/templates/application_record.rb new file mode 100644 index 0000000000..60050e0bf8 --- /dev/null +++ b/activerecord/lib/rails/generators/active_record/model/templates/application_record.rb @@ -0,0 +1,5 @@ +<% module_namespacing do -%> +class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true +end +<% end -%> diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 0ee147cdba..8bcecf2ed3 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -2,6 +2,7 @@ require "cases/helper" require "models/book" require "models/post" require "models/author" +require "models/event" module ActiveRecord class AdapterTest < ActiveRecord::TestCase @@ -11,7 +12,8 @@ module ActiveRecord ## # PostgreSQL does not support null bytes in strings - unless current_adapter?(:PostgreSQLAdapter) + unless current_adapter?(:PostgreSQLAdapter) || + (current_adapter?(:SQLite3Adapter) && !ActiveRecord::Base.connection.prepared_statements) def test_update_prepared_statement b = Book.create(name: "my \x00 book") b.reload @@ -22,20 +24,28 @@ module ActiveRecord end end + def test_create_record_with_pk_as_zero + Book.create(id: 0) + assert_equal 0, Book.find(0).id + assert_nothing_raised { Book.destroy(0) } + end + def test_tables tables = nil ActiveSupport::Deprecation.silence { tables = @connection.tables } - assert tables.include?("accounts") - assert tables.include?("authors") - assert tables.include?("tasks") - assert tables.include?("topics") + assert_includes tables, "accounts" + assert_includes tables, "authors" + assert_includes tables, "tasks" + assert_includes tables, "topics" end def test_table_exists? ActiveSupport::Deprecation.silence do assert @connection.table_exists?("accounts") - assert !@connection.table_exists?("nonexistingtable") - assert !@connection.table_exists?(nil) + assert @connection.table_exists?(:accounts) + assert_not @connection.table_exists?("nonexistingtable") + assert_not @connection.table_exists?("'") + assert_not @connection.table_exists?(nil) end end @@ -45,67 +55,74 @@ module ActiveRecord def test_data_sources data_sources = @connection.data_sources - assert data_sources.include?("accounts") - assert data_sources.include?("authors") - assert data_sources.include?("tasks") - assert data_sources.include?("topics") + assert_includes data_sources, "accounts" + assert_includes data_sources, "authors" + assert_includes data_sources, "tasks" + assert_includes data_sources, "topics" end def test_data_source_exists? assert @connection.data_source_exists?("accounts") assert @connection.data_source_exists?(:accounts) assert_not @connection.data_source_exists?("nonexistingtable") + assert_not @connection.data_source_exists?("'") assert_not @connection.data_source_exists?(nil) end def test_indexes idx_name = "accounts_idx" - if @connection.respond_to?(:indexes) - indexes = @connection.indexes("accounts") - assert indexes.empty? - - @connection.add_index :accounts, :firm_id, :name => idx_name - indexes = @connection.indexes("accounts") - assert_equal "accounts", indexes.first.table - assert_equal idx_name, indexes.first.name - assert !indexes.first.unique - assert_equal ["firm_id"], indexes.first.columns - else - warn "#{@connection.class} does not respond to #indexes" - end + indexes = @connection.indexes("accounts") + assert indexes.empty? + + @connection.add_index :accounts, :firm_id, name: idx_name + indexes = @connection.indexes("accounts") + assert_equal "accounts", indexes.first.table + assert_equal idx_name, indexes.first.name + assert !indexes.first.unique + assert_equal ["firm_id"], indexes.first.columns + ensure + @connection.remove_index(:accounts, name: idx_name) rescue nil + end + def test_remove_index_when_name_and_wrong_column_name_specified + index_name = "accounts_idx" + + @connection.add_index :accounts, :firm_id, name: index_name + assert_raises ArgumentError do + @connection.remove_index :accounts, name: index_name, column: :wrong_column_name + end ensure - @connection.remove_index(:accounts, :name => idx_name) rescue nil + @connection.remove_index(:accounts, name: index_name) end def test_current_database if @connection.respond_to?(:current_database) - assert_equal ARTest.connection_config['arunit']['database'], @connection.current_database + assert_equal ARTest.connection_config["arunit"]["database"], @connection.current_database end end if current_adapter?(:Mysql2Adapter) def test_charset assert_not_nil @connection.charset - assert_not_equal 'character_set_database', @connection.charset - assert_equal @connection.show_variable('character_set_database'), @connection.charset + assert_not_equal "character_set_database", @connection.charset + assert_equal @connection.show_variable("character_set_database"), @connection.charset end def test_collation assert_not_nil @connection.collation - assert_not_equal 'collation_database', @connection.collation - assert_equal @connection.show_variable('collation_database'), @connection.collation + assert_not_equal "collation_database", @connection.collation + assert_equal @connection.show_variable("collation_database"), @connection.collation end def test_show_nonexistent_variable_returns_nil - assert_nil @connection.show_variable('foo_bar_baz') + assert_nil @connection.show_variable("foo_bar_baz") end def test_not_specifying_database_name_for_cross_database_selects begin assert_nothing_raised do - ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['arunit'].except(:database)) + ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations["arunit"].except(:database)) config = ARTest.connection_config ActiveRecord::Base.connection.execute( @@ -126,9 +143,9 @@ module ActiveRecord alias_method :table_alias_length, :test_table_alias_length end - assert_equal 'posts', @connection.table_alias_for('posts') - assert_equal 'posts_comm', @connection.table_alias_for('posts_comments') - assert_equal 'dbo_posts', @connection.table_alias_for('dbo.posts') + assert_equal "posts", @connection.table_alias_for("posts") + assert_equal "posts_comm", @connection.table_alias_for("posts_comments") + assert_equal "dbo_posts", @connection.table_alias_for("dbo.posts") class << @connection remove_method :table_alias_length @@ -138,20 +155,20 @@ module ActiveRecord # test resetting sequences in odd tables in PostgreSQL if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!) - require 'models/movie' - require 'models/subscriber' + require "models/movie" + require "models/subscriber" def test_reset_empty_table_with_custom_pk Movie.delete_all - Movie.connection.reset_pk_sequence! 'movies' - assert_equal 1, Movie.create(:name => 'fight club').id + Movie.connection.reset_pk_sequence! "movies" + assert_equal 1, Movie.create(name: "fight club").id end def test_reset_table_with_non_integer_pk Subscriber.delete_all - Subscriber.connection.reset_pk_sequence! 'subscribers' - sub = Subscriber.new(:name => 'robert drake') - sub.id = 'bob drake' + Subscriber.connection.reset_pk_sequence! "subscribers" + sub = Subscriber.new(name: "robert drake") + sub.id = "bob drake" assert_nothing_raised { sub.save! } end end @@ -182,7 +199,7 @@ module ActiveRecord 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' + self.table_name = "fk_test_has_fk" end error = assert_raises(ActiveRecord::InvalidForeignKey) do @@ -193,6 +210,14 @@ module ActiveRecord assert_not_nil error.cause end + + def test_value_limit_violations_are_translated_to_specific_exception + error = assert_raises(ActiveRecord::ValueTooLong) do + Event.create(title: "abcdefgh") + end + + assert_not_nil error.cause + end end def test_disable_referential_integrity @@ -218,21 +243,21 @@ module ActiveRecord end def test_select_methods_passing_a_association_relation - author = Author.create!(name: 'john') - Post.create!(author: author, title: 'foo', body: 'bar') - query = author.posts.where(title: 'foo').select(:title) - assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bound_attributes)) - assert_equal({"title" => "foo"}, @connection.select_one(query)) + author = Author.create!(name: "john") + Post.create!(author: author, title: "foo", body: "bar") + query = author.posts.where(title: "foo").select(:title) + assert_equal({ "title" => "foo" }, @connection.select_one(query.arel, nil, query.bound_attributes)) + assert_equal({ "title" => "foo" }, @connection.select_one(query)) assert @connection.select_all(query).is_a?(ActiveRecord::Result) assert_equal "foo", @connection.select_value(query) assert_equal ["foo"], @connection.select_values(query) end def test_select_methods_passing_a_relation - Post.create!(title: 'foo', body: 'bar') - query = Post.where(title: 'foo').select(:title) - assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bound_attributes)) - assert_equal({"title" => "foo"}, @connection.select_one(query)) + Post.create!(title: "foo", body: "bar") + query = Post.where(title: "foo").select(:title) + assert_equal({ "title" => "foo" }, @connection.select_one(query.arel, nil, query.bound_attributes)) + assert_equal({ "title" => "foo" }, @connection.select_one(query)) assert @connection.select_all(query).is_a?(ActiveRecord::Result) assert_equal "foo", @connection.select_value(query) assert_equal ["foo"], @connection.select_values(query) @@ -246,7 +271,7 @@ module ActiveRecord def test_log_invalid_encoding error = assert_raise ActiveRecord::StatementInvalid do @connection.send :log, "SELECT 'Ñ‹' FROM DUAL" do - raise 'Ñ‹'.force_encoding(Encoding::ASCII_8BIT) + raise "Ñ‹".force_encoding(Encoding::ASCII_8BIT) end end diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index 99f97c7914..a70eb5a094 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'support/connection_helper' +require "support/connection_helper" class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase include ConnectionHelper @@ -21,56 +21,56 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase def (ActiveRecord::Base.connection).index_name_exists?(*); false; end expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) " - assert_equal expected, add_index(:people, :last_name, :length => nil) + assert_equal expected, add_index(:people, :last_name, length: nil) expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10)) " - assert_equal expected, add_index(:people, :last_name, :length => 10) + assert_equal expected, add_index(:people, :last_name, length: 10) 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`) " - assert_equal expected, add_index(:people, :last_name, :type => type) + assert_equal expected, add_index(:people, :last_name, type: type) end %w(btree hash).each do |using| expected = "CREATE INDEX `index_people_on_last_name` USING #{using} ON `people` (`last_name`) " - assert_equal expected, add_index(:people, :last_name, :using => using) + assert_equal expected, add_index(:people, :last_name, using: using) end expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) " - assert_equal expected, add_index(:people, :last_name, :length => 10, :using => :btree) + assert_equal expected, add_index(:people, :last_name, length: 10, using: :btree) expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) ALGORITHM = COPY" - assert_equal expected, add_index(:people, :last_name, :length => 10, using: :btree, algorithm: :copy) + assert_equal expected, add_index(:people, :last_name, length: 10, using: :btree, algorithm: :copy) assert_raise ArgumentError do add_index(:people, :last_name, algorithm: :coyp) end expected = "CREATE INDEX `index_people_on_last_name_and_first_name` USING btree ON `people` (`last_name`(15), `first_name`(15)) " - assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree) + assert_equal expected, add_index(:people, [:last_name, :first_name], length: 15, using: :btree) end def test_index_in_create def (ActiveRecord::Base.connection).data_source_exists?(*); false; end %w(SPATIAL FULLTEXT UNIQUE).each do |type| - expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`) ) ENGINE=InnoDB" + expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`)) ENGINE=InnoDB" actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t| t.index :last_name, type: type end assert_equal expected, actual end - expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10)) ) ENGINE=InnoDB" + expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10))) ENGINE=InnoDB" actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t| t.index :last_name, length: 10, using: :btree end @@ -102,13 +102,13 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase def test_create_mysql_database_with_encoding assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt) - assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'}) - assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci}) + assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, charset: "latin1") + assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, charset: :big5, collation: :big5_chinese_ci) end def test_recreate_mysql_database_with_encoding - create_database(:luca, {:charset => 'latin1'}) - assert_equal "CREATE DATABASE `luca` DEFAULT CHARACTER SET `latin1`", recreate_database(:luca, {:charset => 'latin1'}) + create_database(:luca, charset: "latin1") + assert_equal "CREATE DATABASE `luca` DEFAULT CHARACTER SET `latin1`", recreate_database(:luca, charset: "latin1") end def test_add_column @@ -116,11 +116,11 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase end def test_add_column_with_limit - assert_equal "ALTER TABLE `people` ADD `key` varchar(32)", add_column(:people, :key, :string, :limit => 32) + assert_equal "ALTER TABLE `people` ADD `key` varchar(32)", add_column(:people, :key, :string, limit: 32) end def test_drop_table_with_specific_database - assert_equal "DROP TABLE `otherdb`.`people`", drop_table('otherdb.people') + assert_equal "DROP TABLE `otherdb`.`people`", drop_table("otherdb.people") end def test_add_timestamps @@ -128,8 +128,8 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase begin ActiveRecord::Base.connection.create_table :delete_me ActiveRecord::Base.connection.add_timestamps :delete_me, null: true - assert column_present?('delete_me', 'updated_at', 'datetime') - assert column_present?('delete_me', 'created_at', 'datetime') + assert column_present?("delete_me", "updated_at", "datetime") + assert column_present?("delete_me", "created_at", "datetime") ensure ActiveRecord::Base.connection.drop_table :delete_me rescue nil end @@ -142,9 +142,9 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase ActiveRecord::Base.connection.create_table :delete_me do |t| t.timestamps null: true end - ActiveRecord::Base.connection.remove_timestamps :delete_me, { null: true } - assert !column_present?('delete_me', 'updated_at', 'datetime') - assert !column_present?('delete_me', 'created_at', 'datetime') + ActiveRecord::Base.connection.remove_timestamps :delete_me, null: true + assert !column_present?("delete_me", "updated_at", "datetime") + assert !column_present?("delete_me", "created_at", "datetime") ensure ActiveRecord::Base.connection.drop_table :delete_me rescue nil end @@ -155,7 +155,7 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase ActiveRecord::Base.connection.stubs(:data_source_exists?).with(:temp).returns(false) ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false) - expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`) ) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query" + expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`)) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query" actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t| t.index :zip end @@ -185,6 +185,6 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase def column_present?(table_name, column_name, type) results = ActiveRecord::Base.connection.select_all("SHOW FIELDS FROM #{table_name} LIKE '#{column_name}'") - results.first && results.first['Type'] == type + results.first && results.first["Type"] == type end end diff --git a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb index abdf3dbf5b..8f7c803a21 100644 --- a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb +++ b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'models/topic' +require "models/topic" module ActiveRecord module ConnectionAdapters @@ -20,7 +20,7 @@ module ActiveRecord def test_create_question_marks str = "foo?bar" - x = Topic.create!(:title => str, :content => str) + x = Topic.create!(title: str, content: str) x.reload assert_equal str, x.title assert_equal str, x.content @@ -39,7 +39,7 @@ module ActiveRecord def test_create_null_bytes str = "foo\0bar" - x = Topic.create!(:title => str, :content => str) + x = Topic.create!(title: str, content: str) x.reload assert_equal str, x.title assert_equal str, x.content diff --git a/activerecord/test/cases/adapters/mysql2/boolean_test.rb b/activerecord/test/cases/adapters/mysql2/boolean_test.rb index 8575df9e43..2fa39282fb 100644 --- a/activerecord/test/cases/adapters/mysql2/boolean_test.rb +++ b/activerecord/test/cases/adapters/mysql2/boolean_test.rb @@ -43,11 +43,16 @@ class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase boolean = BooleanType.create!(archived: true, published: true) attributes = boolean.reload.attributes_before_type_cast - assert_equal 1, attributes["archived"] assert_equal "1", attributes["published"] + boolean = BooleanType.create!(archived: false, published: false) + attributes = boolean.reload.attributes_before_type_cast + assert_equal 0, attributes["archived"] + assert_equal "0", attributes["published"] + assert_equal 1, @connection.type_cast(true) + assert_equal 0, @connection.type_cast(false) end test "test type casting without emulated booleans" do @@ -55,11 +60,16 @@ class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase boolean = BooleanType.create!(archived: true, published: true) attributes = boolean.reload.attributes_before_type_cast - assert_equal 1, attributes["archived"] assert_equal "1", attributes["published"] + boolean = BooleanType.create!(archived: false, published: false) + attributes = boolean.reload.attributes_before_type_cast + assert_equal 0, attributes["archived"] + assert_equal "0", attributes["published"] + assert_equal 1, @connection.type_cast(true) + assert_equal 0, @connection.type_cast(false) end test "with booleans stored as 1 and 0" do @@ -76,11 +86,11 @@ class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase end def boolean_column - BooleanType.columns.find { |c| c.name == 'archived' } + BooleanType.columns.find { |c| c.name == "archived" } end def string_column - BooleanType.columns.find { |c| c.name == 'published' } + BooleanType.columns.find { |c| c.name == "published" } end def emulate_booleans(value) diff --git a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb index 963116f08a..50ba9ab831 100644 --- a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb +++ b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb @@ -7,48 +7,57 @@ class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase repair_validations(CollationTest) def test_columns_include_collation_different_from_table - assert_equal 'utf8_bin', CollationTest.columns_hash['string_cs_column'].collation - assert_equal 'utf8_general_ci', CollationTest.columns_hash['string_ci_column'].collation + assert_equal "utf8_bin", CollationTest.columns_hash["string_cs_column"].collation + assert_equal "utf8_general_ci", CollationTest.columns_hash["string_ci_column"].collation end def test_case_sensitive - assert !CollationTest.columns_hash['string_ci_column'].case_sensitive? - assert CollationTest.columns_hash['string_cs_column'].case_sensitive? + assert !CollationTest.columns_hash["string_ci_column"].case_sensitive? + assert CollationTest.columns_hash["string_cs_column"].case_sensitive? end def test_case_insensitive_comparison_for_ci_column - CollationTest.validates_uniqueness_of(:string_ci_column, :case_sensitive => false) - CollationTest.create!(:string_ci_column => 'A') - invalid = CollationTest.new(:string_ci_column => 'a') + CollationTest.validates_uniqueness_of(:string_ci_column, case_sensitive: false) + CollationTest.create!(string_ci_column: "A") + invalid = CollationTest.new(string_ci_column: "a") queries = assert_sql { invalid.save } ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) } assert_no_match(/lower/i, ci_uniqueness_query) end def test_case_insensitive_comparison_for_cs_column - CollationTest.validates_uniqueness_of(:string_cs_column, :case_sensitive => false) - CollationTest.create!(:string_cs_column => 'A') - invalid = CollationTest.new(:string_cs_column => 'a') + CollationTest.validates_uniqueness_of(:string_cs_column, case_sensitive: false) + CollationTest.create!(string_cs_column: "A") + invalid = CollationTest.new(string_cs_column: "a") queries = assert_sql { invalid.save } - cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/)} + cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) } assert_match(/lower/i, cs_uniqueness_query) end def test_case_sensitive_comparison_for_ci_column - CollationTest.validates_uniqueness_of(:string_ci_column, :case_sensitive => true) - CollationTest.create!(:string_ci_column => 'A') - invalid = CollationTest.new(:string_ci_column => 'A') + CollationTest.validates_uniqueness_of(:string_ci_column, case_sensitive: true) + CollationTest.create!(string_ci_column: "A") + invalid = CollationTest.new(string_ci_column: "A") queries = assert_sql { invalid.save } ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) } assert_match(/binary/i, ci_uniqueness_query) end def test_case_sensitive_comparison_for_cs_column - CollationTest.validates_uniqueness_of(:string_cs_column, :case_sensitive => true) - CollationTest.create!(:string_cs_column => 'A') - invalid = CollationTest.new(:string_cs_column => 'A') + CollationTest.validates_uniqueness_of(:string_cs_column, case_sensitive: true) + CollationTest.create!(string_cs_column: "A") + invalid = CollationTest.new(string_cs_column: "A") queries = assert_sql { invalid.save } cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) } assert_no_match(/binary/i, cs_uniqueness_query) end + + def test_case_sensitive_comparison_for_binary_column + CollationTest.validates_uniqueness_of(:binary_column, case_sensitive: true) + CollationTest.create!(binary_column: "A") + invalid = CollationTest.new(binary_column: "A") + queries = assert_sql { invalid.save } + bin_uniqueness_query = queries.detect { |q| q.match(/binary_column/) } + assert_no_match(/\bBINARY\b/, bin_uniqueness_query) + end end diff --git a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb index 668c07dacb..8826ad7fd1 100644 --- a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb +++ b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'support/schema_dumping_helper' +require "support/schema_dumping_helper" class Mysql2CharsetCollationTest < ActiveRecord::Mysql2TestCase include SchemaDumpingHelper @@ -8,8 +8,8 @@ class Mysql2CharsetCollationTest < ActiveRecord::Mysql2TestCase setup do @connection = ActiveRecord::Base.connection @connection.create_table :charset_collations, force: true do |t| - t.string :string_ascii_bin, charset: 'ascii', collation: 'ascii_bin' - t.text :text_ucs2_unicode_ci, charset: 'ucs2', collation: 'ucs2_unicode_ci' + t.string :string_ascii_bin, charset: "ascii", collation: "ascii_bin" + t.text :text_ucs2_unicode_ci, charset: "ucs2", collation: "ucs2_unicode_ci" end end @@ -18,37 +18,37 @@ class Mysql2CharsetCollationTest < ActiveRecord::Mysql2TestCase end test "string column with charset and collation" do - column = @connection.columns(:charset_collations).find { |c| c.name == 'string_ascii_bin' } + column = @connection.columns(:charset_collations).find { |c| c.name == "string_ascii_bin" } assert_equal :string, column.type - assert_equal 'ascii_bin', column.collation + assert_equal "ascii_bin", column.collation end test "text column with charset and collation" do - column = @connection.columns(:charset_collations).find { |c| c.name == 'text_ucs2_unicode_ci' } + column = @connection.columns(:charset_collations).find { |c| c.name == "text_ucs2_unicode_ci" } assert_equal :text, column.type - assert_equal 'ucs2_unicode_ci', column.collation + assert_equal "ucs2_unicode_ci", column.collation end test "add column with charset and collation" do - @connection.add_column :charset_collations, :title, :string, charset: 'utf8', collation: 'utf8_bin' + @connection.add_column :charset_collations, :title, :string, charset: "utf8", collation: "utf8_bin" - column = @connection.columns(:charset_collations).find { |c| c.name == 'title' } + column = @connection.columns(:charset_collations).find { |c| c.name == "title" } assert_equal :string, column.type - assert_equal 'utf8_bin', column.collation + assert_equal "utf8_bin", column.collation end test "change column with charset and collation" do - @connection.add_column :charset_collations, :description, :string, charset: 'utf8', collation: 'utf8_unicode_ci' - @connection.change_column :charset_collations, :description, :text, charset: 'utf8', collation: 'utf8_general_ci' + @connection.add_column :charset_collations, :description, :string, charset: "utf8", collation: "utf8_unicode_ci" + @connection.change_column :charset_collations, :description, :text, charset: "utf8", collation: "utf8_general_ci" - column = @connection.columns(:charset_collations).find { |c| c.name == 'description' } + column = @connection.columns(:charset_collations).find { |c| c.name == "description" } assert_equal :text, column.type - assert_equal 'utf8_general_ci', column.collation + assert_equal "utf8_general_ci", column.collation end test "schema dump includes collation" do output = dump_table_schema("charset_collations") assert_match %r{t.string\s+"string_ascii_bin",\s+collation: "ascii_bin"$}, output - assert_match %r{t.text\s+"text_ucs2_unicode_ci",\s+limit: 65535,\s+collation: "ucs2_unicode_ci"$}, output + assert_match %r{t.text\s+"text_ucs2_unicode_ci",\s+collation: "ucs2_unicode_ci"$}, output end end diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index 575138eb2a..ff012efc5f 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'support/connection_helper' +require "support/connection_helper" class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase include ConnectionHelper @@ -9,7 +9,7 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase def setup super @subscriber = SQLSubscriber.new - @subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber) + @subscription = ActiveSupport::Notifications.subscribe("sql.active_record", @subscriber) @connection = ActiveRecord::Base.connection end @@ -20,9 +20,9 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase def test_bad_connection assert_raise ActiveRecord::NoDatabaseError do - configuration = ActiveRecord::Base.configurations['arunit'].merge(database: 'inexistent_activerecord_unittest') + configuration = ActiveRecord::Base.configurations["arunit"].merge(database: "inexistent_activerecord_unittest") connection = ActiveRecord::Base.mysql2_connection(configuration) - connection.drop_table 'ex', if_exists: true + connection.drop_table "ex", if_exists: true end end @@ -39,7 +39,7 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase def test_no_automatic_reconnection_after_timeout assert @connection.active? - @connection.update('set @@wait_timeout=1') + @connection.update("set @@wait_timeout=1") sleep 2 assert !@connection.active? @@ -49,7 +49,7 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase def test_successful_reconnection_after_timeout_with_manual_reconnect assert @connection.active? - @connection.update('set @@wait_timeout=1') + @connection.update("set @@wait_timeout=1") sleep 2 @connection.reconnect! assert @connection.active? @@ -57,72 +57,93 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase def test_successful_reconnection_after_timeout_with_verify assert @connection.active? - @connection.update('set @@wait_timeout=1') + @connection.update("set @@wait_timeout=1") sleep 2 @connection.verify! assert @connection.active? end + def test_execute_after_disconnect + @connection.disconnect! + + assert_raise(ActiveRecord::StatementInvalid) do + @connection.execute("SELECT 1") + end + end + + def test_quote_after_disconnect + @connection.disconnect! + + assert_raise(Mysql2::Error) do + @connection.quote("string") + end + end + + def test_active_after_disconnect + @connection.disconnect! + assert_equal false, @connection.active? + end + def test_mysql_connection_collation_is_configured - assert_equal 'utf8_unicode_ci', @connection.show_variable('collation_connection') - assert_equal 'utf8_general_ci', ARUnit2Model.connection.show_variable('collation_connection') + assert_equal "utf8_unicode_ci", @connection.show_variable("collation_connection") + assert_equal "utf8_general_ci", ARUnit2Model.connection.show_variable("collation_connection") end def test_mysql_default_in_strict_mode - result = @connection.exec_query "SELECT @@SESSION.sql_mode" - assert_equal [["STRICT_ALL_TABLES"]], result.rows + result = @connection.select_value("SELECT @@SESSION.sql_mode") + assert_match %r(STRICT_ALL_TABLES), result end def test_mysql_strict_mode_disabled run_without_connection do |orig_connection| - ActiveRecord::Base.establish_connection(orig_connection.merge({:strict => false})) - result = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.sql_mode" - assert_equal [['']], result.rows + ActiveRecord::Base.establish_connection(orig_connection.merge(strict: false)) + result = ActiveRecord::Base.connection.select_value("SELECT @@SESSION.sql_mode") + assert_no_match %r(STRICT_ALL_TABLES), result end end - def test_passing_arbitary_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] + def test_mysql_strict_mode_specified_default + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.merge(strict: :default)) + global_sql_mode = ActiveRecord::Base.connection.select_value("SELECT @@GLOBAL.sql_mode") + session_sql_mode = ActiveRecord::Base.connection.select_value("SELECT @@SESSION.sql_mode") + assert_equal global_sql_mode, session_sql_mode end end - - def test_passing_flags_by_array_to_adapter - run_without_connection do |orig_connection| - ActiveRecord::Base.establish_connection(orig_connection.merge({flags: ['COMPRESS'] })) - assert_equal ["COMPRESS", "FOUND_ROWS"], ActiveRecord::Base.connection.raw_connection.query_options[:flags] + + def test_mysql_sql_mode_variable_overrides_strict_mode + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { "sql_mode" => "ansi" })) + result = ActiveRecord::Base.connection.select_value("SELECT @@SESSION.sql_mode") + assert_no_match %r(STRICT_ALL_TABLES), result end end - - def test_mysql_strict_mode_specified_default + + def test_passing_arbitary_flags_to_adapter run_without_connection do |orig_connection| - ActiveRecord::Base.establish_connection(orig_connection.merge({strict: :default})) - global_sql_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.sql_mode" - session_sql_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.sql_mode" - assert_equal global_sql_mode.rows, session_sql_mode.rows + 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] end end - def test_mysql_set_session_variable + def test_passing_flags_by_array_to_adapter run_without_connection do |orig_connection| - ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => 3}})) - session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT" - assert_equal 3, session_mode.rows.first.first.to_i + ActiveRecord::Base.establish_connection(orig_connection.merge(flags: ["COMPRESS"])) + assert_equal ["COMPRESS", "FOUND_ROWS"], ActiveRecord::Base.connection.raw_connection.query_options[:flags] end end - def test_mysql_sql_mode_variable_overrides_strict_mode + def test_mysql_set_session_variable run_without_connection do |orig_connection| - ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { 'sql_mode' => 'ansi' })) - result = ActiveRecord::Base.connection.exec_query 'SELECT @@SESSION.sql_mode' - assert_not_equal [['STRICT_ALL_TABLES']], result.rows + ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { default_week_format: 3 })) + session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT" + assert_equal 3, session_mode.rows.first.first.to_i end end def test_mysql_set_session_variable_to_default run_without_connection do |orig_connection| - ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => :default}})) + ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { default_week_format: :default })) global_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.DEFAULT_WEEK_FORMAT" session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT" assert_equal global_mode.rows, session_mode.rows @@ -130,21 +151,21 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase end def test_logs_name_show_variable - @connection.show_variable 'foo' + @connection.show_variable "foo" assert_equal "SCHEMA", @subscriber.logged[0][1] end def test_logs_name_rename_column_sql @connection.execute "CREATE TABLE `bar_baz` (`foo` varchar(255))" @subscriber.logged.clear - @connection.send(:rename_column_sql, 'bar_baz', 'foo', 'foo2') + @connection.send(:rename_column_sql, "bar_baz", "foo", "foo2") assert_equal "SCHEMA", @subscriber.logged[0][1] ensure @connection.execute "DROP TABLE `bar_baz`" end def test_get_and_release_advisory_lock - lock_name = "test_lock_name" + lock_name = "test lock'n'name" got_lock = @connection.get_advisory_lock(lock_name) assert got_lock, "get_advisory_lock should have returned true but it didn't" @@ -155,19 +176,19 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase released_lock = @connection.release_advisory_lock(lock_name) assert released_lock, "expected release_advisory_lock to return true but it didn't" - assert test_lock_free(lock_name), 'expected the test lock to be available after releasing' + assert test_lock_free(lock_name), "expected the test lock to be available after releasing" end def test_release_non_existent_advisory_lock - lock_name = "fake_lock_name" + lock_name = "fake lock'n'name" released_non_existent_lock = @connection.release_advisory_lock(lock_name) assert_equal released_non_existent_lock, false, - 'expected release_advisory_lock to return false when there was no lock to release' + "expected release_advisory_lock to return false when there was no lock to release" end protected - def test_lock_free(lock_name) - @connection.select_value("SELECT IS_FREE_LOCK('#{lock_name}');") == 1 - end + def test_lock_free(lock_name) + @connection.select_value("SELECT IS_FREE_LOCK(#{@connection.quote(lock_name)})") == 1 + end end diff --git a/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb new file mode 100644 index 0000000000..135789a57d --- /dev/null +++ b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb @@ -0,0 +1,45 @@ +require "cases/helper" + +class Mysql2DatetimePrecisionQuotingTest < ActiveRecord::Mysql2TestCase + setup do + @connection = ActiveRecord::Base.connection + end + + test "microsecond precision for MySQL gte 5.6.4" do + stub_version "5.6.4" + assert_microsecond_precision + end + + test "no microsecond precision for MySQL lt 5.6.4" do + stub_version "5.6.3" + assert_no_microsecond_precision + end + + test "microsecond precision for MariaDB gte 5.3.0" do + stub_version "5.5.5-10.1.8-MariaDB-log" + assert_microsecond_precision + end + + test "no microsecond precision for MariaDB lt 5.3.0" do + stub_version "5.2.9-MariaDB" + assert_no_microsecond_precision + end + + private + def assert_microsecond_precision + assert_match_quoted_microsecond_datetime(/\.000001\z/) + end + + def assert_no_microsecond_precision + assert_match_quoted_microsecond_datetime(/\d\z/) + end + + def assert_match_quoted_microsecond_datetime(match) + assert_match match, @connection.quoted_date(Time.now.change(usec: 1)) + end + + 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) + end +end diff --git a/activerecord/test/cases/adapters/mysql2/enum_test.rb b/activerecord/test/cases/adapters/mysql2/enum_test.rb index 35dbc76d1b..7ad3e3ca2d 100644 --- a/activerecord/test/cases/adapters/mysql2/enum_test.rb +++ b/activerecord/test/cases/adapters/mysql2/enum_test.rb @@ -5,22 +5,17 @@ class Mysql2EnumTest < ActiveRecord::Mysql2TestCase end def test_enum_limit - column = EnumTest.columns_hash['enum_column'] + column = EnumTest.columns_hash["enum_column"] assert_equal 8, column.limit end - def test_should_not_be_blob_or_text_column - column = EnumTest.columns_hash['enum_column'] - assert_not column.blob_or_text_column? - end - def test_should_not_be_unsigned - column = EnumTest.columns_hash['enum_column'] + column = EnumTest.columns_hash["enum_column"] assert_not column.unsigned? end def test_should_not_be_bigint - column = EnumTest.columns_hash['enum_column'] + column = EnumTest.columns_hash["enum_column"] assert_not column.bigint? end end diff --git a/activerecord/test/cases/adapters/mysql2/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb index 4fc7414b18..7916921e5a 100644 --- a/activerecord/test/cases/adapters/mysql2/explain_test.rb +++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb @@ -1,27 +1,21 @@ require "cases/helper" -require 'models/developer' -require 'models/computer' +require "models/developer" +require "models/computer" -module ActiveRecord - module ConnectionAdapters - class Mysql2Adapter - class ExplainTest < ActiveRecord::Mysql2TestCase - fixtures :developers +class Mysql2ExplainTest < ActiveRecord::Mysql2TestCase + fixtures :developers - def test_explain_for_one_query - explain = Developer.where(:id => 1).explain - assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain - assert_match %r(developers |.* const), explain - end + def test_explain_for_one_query + explain = Developer.where(id: 1).explain + assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain + assert_match %r(developers |.* const), explain + end - def test_explain_with_eager_loading - explain = Developer.where(:id => 1).includes(:audit_logs).explain - assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain - assert_match %r(developers |.* const), explain - assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` = 1), explain - assert_match %r(audit_logs |.* ALL), explain - end - end - end + def test_explain_with_eager_loading + explain = Developer.where(id: 1).includes(:audit_logs).explain + assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain + assert_match %r(developers |.* const), explain + assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` = 1), explain + assert_match %r(audit_logs |.* ALL), explain end end diff --git a/activerecord/test/cases/adapters/mysql2/json_test.rb b/activerecord/test/cases/adapters/mysql2/json_test.rb index c8c933af5e..630cdb36a4 100644 --- a/activerecord/test/cases/adapters/mysql2/json_test.rb +++ b/activerecord/test/cases/adapters/mysql2/json_test.rb @@ -1,172 +1,195 @@ -require 'cases/helper' -require 'support/schema_dumping_helper' +require "cases/helper" +require "support/schema_dumping_helper" if ActiveRecord::Base.connection.supports_json? -class Mysql2JSONTest < ActiveRecord::Mysql2TestCase - include SchemaDumpingHelper - self.use_transactional_tests = false + class Mysql2JSONTest < ActiveRecord::Mysql2TestCase + include SchemaDumpingHelper + self.use_transactional_tests = false - class JsonDataType < ActiveRecord::Base - self.table_name = 'json_data_type' + class JsonDataType < ActiveRecord::Base + self.table_name = "json_data_type" - store_accessor :settings, :resolution - end + store_accessor :settings, :resolution + end - def setup - @connection = ActiveRecord::Base.connection - begin - @connection.create_table('json_data_type') do |t| - t.json 'payload' - t.json 'settings' + def setup + @connection = ActiveRecord::Base.connection + begin + @connection.create_table("json_data_type") do |t| + t.json "payload" + t.json "settings" + end end end - end - def teardown - @connection.drop_table :json_data_type, if_exists: true - JsonDataType.reset_column_information - end + def teardown + @connection.drop_table :json_data_type, if_exists: true + JsonDataType.reset_column_information + end - def test_column - column = JsonDataType.columns_hash["payload"] - assert_equal :json, column.type - assert_equal 'json', column.sql_type + def test_column + column = JsonDataType.columns_hash["payload"] + assert_equal :json, column.type + assert_equal "json", column.sql_type - type = JsonDataType.type_for_attribute("payload") - assert_not type.binary? - end + type = JsonDataType.type_for_attribute("payload") + assert_not type.binary? + end - def test_change_table_supports_json - @connection.change_table('json_data_type') do |t| - t.json 'users' + def test_change_table_supports_json + @connection.change_table("json_data_type") do |t| + t.json "users" + end + JsonDataType.reset_column_information + column = JsonDataType.columns_hash["users"] + assert_equal :json, column.type end - JsonDataType.reset_column_information - column = JsonDataType.columns_hash['users'] - assert_equal :json, column.type - end - def test_schema_dumping - output = dump_table_schema("json_data_type") - assert_match(/t\.json\s+"settings"/, output) - end + def test_schema_dumping + output = dump_table_schema("json_data_type") + assert_match(/t\.json\s+"settings"/, output) + end - def test_cast_value_on_write - x = JsonDataType.new payload: {"string" => "foo", :symbol => :bar} - assert_equal({"string" => "foo", :symbol => :bar}, x.payload_before_type_cast) - assert_equal({"string" => "foo", "symbol" => "bar"}, x.payload) - x.save - assert_equal({"string" => "foo", "symbol" => "bar"}, x.reload.payload) - end + def test_cast_value_on_write + x = JsonDataType.new payload: { "string" => "foo", :symbol => :bar } + assert_equal({ "string" => "foo", :symbol => :bar }, x.payload_before_type_cast) + assert_equal({ "string" => "foo", "symbol" => "bar" }, x.payload) + x.save + assert_equal({ "string" => "foo", "symbol" => "bar" }, x.reload.payload) + end - def test_type_cast_json - type = JsonDataType.type_for_attribute("payload") + def test_type_cast_json + type = JsonDataType.type_for_attribute("payload") - data = "{\"a_key\":\"a_value\"}" - hash = type.deserialize(data) - assert_equal({'a_key' => 'a_value'}, hash) - assert_equal({'a_key' => 'a_value'}, type.deserialize(data)) + data = "{\"a_key\":\"a_value\"}" + hash = type.deserialize(data) + assert_equal({ "a_key" => "a_value" }, hash) + assert_equal({ "a_key" => "a_value" }, type.deserialize(data)) - assert_equal({}, type.deserialize("{}")) - assert_equal({'key'=>nil}, type.deserialize('{"key": null}')) - assert_equal({'c'=>'}','"a"'=>'b "a b'}, type.deserialize(%q({"c":"}", "\"a\"":"b \"a b"}))) - end + assert_equal({}, type.deserialize("{}")) + assert_equal({ "key"=>nil }, type.deserialize('{"key": null}')) + assert_equal({ "c"=>"}",'"a"'=>'b "a b' }, type.deserialize(%q({"c":"}", "\"a\"":"b \"a b"}))) + end - def test_rewrite - @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')" - x = JsonDataType.first - x.payload = { '"a\'' => 'b' } - assert x.save! - end + def test_rewrite + @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')" + x = JsonDataType.first + x.payload = { '"a\'' => "b" } + assert x.save! + end - def test_select - @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')" - x = JsonDataType.first - assert_equal({'k' => 'v'}, x.payload) - end + def test_select + @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')" + x = JsonDataType.first + assert_equal({ "k" => "v" }, x.payload) + end - def test_select_multikey - @connection.execute %q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')| - x = JsonDataType.first - assert_equal({'k1' => 'v1', 'k2' => 'v2', 'k3' => [1,2,3]}, x.payload) - end + def test_select_multikey + @connection.execute %q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')| + x = JsonDataType.first + assert_equal({ "k1" => "v1", "k2" => "v2", "k3" => [1,2,3] }, x.payload) + end - def test_null_json - @connection.execute %q|insert into json_data_type (payload) VALUES(null)| - x = JsonDataType.first - assert_equal(nil, x.payload) - end + def test_null_json + @connection.execute "insert into json_data_type (payload) VALUES(null)" + x = JsonDataType.first + assert_equal(nil, x.payload) + end - def test_select_array_json_value - @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')| - x = JsonDataType.first - assert_equal(['v0', {'k1' => 'v1'}], x.payload) - end + def test_select_array_json_value + @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')| + x = JsonDataType.first + assert_equal(["v0", { "k1" => "v1" }], x.payload) + end - def test_rewrite_array_json_value - @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')| - x = JsonDataType.first - x.payload = ['v1', {'k2' => 'v2'}, 'v3'] - assert x.save! - end + def test_select_nil_json_after_create + json = JsonDataType.create(payload: nil) + x = JsonDataType.where(payload:nil).first + assert_equal(json, x) + end - def test_with_store_accessors - x = JsonDataType.new(resolution: "320×480") - assert_equal "320×480", x.resolution + def test_select_nil_json_after_update + json = JsonDataType.create(payload: "foo") + x = JsonDataType.where(payload:nil).first + assert_equal(nil, x) - x.save! - x = JsonDataType.first - assert_equal "320×480", x.resolution + json.update_attributes payload: nil + x = JsonDataType.where(payload:nil).first + assert_equal(json.reload, x) + end - x.resolution = "640×1136" - x.save! + def test_rewrite_array_json_value + @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')| + x = JsonDataType.first + x.payload = ["v1", { "k2" => "v2" }, "v3"] + assert x.save! + end - x = JsonDataType.first - assert_equal "640×1136", x.resolution - end + def test_with_store_accessors + x = JsonDataType.new(resolution: "320×480") + assert_equal "320×480", x.resolution - def test_duplication_with_store_accessors - x = JsonDataType.new(resolution: "320×480") - assert_equal "320×480", x.resolution + x.save! + x = JsonDataType.first + assert_equal "320×480", x.resolution - y = x.dup - assert_equal "320×480", y.resolution - end + x.resolution = "640×1136" + x.save! - def test_yaml_round_trip_with_store_accessors - x = JsonDataType.new(resolution: "320×480") - assert_equal "320×480", x.resolution + x = JsonDataType.first + assert_equal "640×1136", x.resolution + end - y = YAML.load(YAML.dump(x)) - assert_equal "320×480", y.resolution - end + def test_duplication_with_store_accessors + x = JsonDataType.new(resolution: "320×480") + assert_equal "320×480", x.resolution - def test_changes_in_place - json = JsonDataType.new - assert_not json.changed? + y = x.dup + assert_equal "320×480", y.resolution + end - json.payload = { 'one' => 'two' } - assert json.changed? - assert json.payload_changed? + def test_yaml_round_trip_with_store_accessors + x = JsonDataType.new(resolution: "320×480") + assert_equal "320×480", x.resolution - json.save! - assert_not json.changed? + y = YAML.load(YAML.dump(x)) + assert_equal "320×480", y.resolution + end - json.payload['three'] = 'four' - assert json.payload_changed? + def test_changes_in_place + json = JsonDataType.new + assert_not json.changed? - json.save! - json.reload + json.payload = { "one" => "two" } + assert json.changed? + assert json.payload_changed? - assert_equal({ 'one' => 'two', 'three' => 'four' }, json.payload) - assert_not json.changed? - end + json.save! + assert_not json.changed? - def test_assigning_invalid_json - json = JsonDataType.new + json.payload["three"] = "four" + assert json.payload_changed? - json.payload = 'foo' + json.save! + json.reload - assert_nil json.payload + assert_equal({ "one" => "two", "three" => "four" }, json.payload) + assert_not json.changed? + end + + def test_assigning_string_literal + json = JsonDataType.create(payload: "foo") + assert_equal "foo", json.payload + end + + def test_assigning_number + json = JsonDataType.create(payload: 1.234) + assert_equal 1.234, json.payload + end + + def test_assigning_boolean + json = JsonDataType.create(payload: true) + assert_equal true, json.payload + end end end -end diff --git a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb index 4efd728754..69336eb906 100644 --- a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb @@ -1,10 +1,33 @@ require "cases/helper" +require "support/ddl_helper" class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase + include DdlHelper + def setup @conn = ActiveRecord::Base.connection end + def test_exec_query_nothing_raises_with_no_result_queries + assert_nothing_raised do + with_example_table do + @conn.exec_query("INSERT INTO ex (number) VALUES (1)") + @conn.exec_query("DELETE FROM ex WHERE number = 1") + end + end + end + + def test_valid_column + with_example_table do + column = @conn.columns("ex").find { |col| col.name == "id" } + assert @conn.valid_type?(column.type) + end + end + + def test_invalid_column + assert_not @conn.valid_type?(:foobar) + end + def test_columns_for_distinct_zero_orders assert_equal "posts.id", @conn.columns_for_distinct("posts.id", []) @@ -22,8 +45,8 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase def test_columns_for_distinct_with_case assert_equal( - 'posts.id, CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END AS alias_0', - @conn.columns_for_distinct('posts.id', + "posts.id, CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END AS alias_0", + @conn.columns_for_distinct("posts.id", ["CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END"]) ) end @@ -41,4 +64,10 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase assert_equal "posts.id, posts.created_at AS alias_0", @conn.columns_for_distinct("posts.id", [order]) end + + private + + def with_example_table(definition = "id int auto_increment primary key, number int, data varchar(255)", &block) + super(@conn, "ex", definition, &block) + end end diff --git a/activerecord/test/cases/adapters/mysql2/quoting_test.rb b/activerecord/test/cases/adapters/mysql2/quoting_test.rb deleted file mode 100644 index 2de7e1b526..0000000000 --- a/activerecord/test/cases/adapters/mysql2/quoting_test.rb +++ /dev/null @@ -1,21 +0,0 @@ -require "cases/helper" - -class Mysql2QuotingTest < ActiveRecord::Mysql2TestCase - setup do - @connection = ActiveRecord::Base.connection - end - - test 'quoted date precision for gte 5.6.4' do - @connection.stubs(:full_version).returns('5.6.4') - @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version) - t = Time.now.change(usec: 1) - assert_match(/\.000001\z/, @connection.quoted_date(t)) - end - - test 'quoted date precision for lt 5.6.4' do - @connection.stubs(:full_version).returns('5.6.3') - @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version) - t = Time.now.change(usec: 1) - assert_no_match(/\.000001\z/, @connection.quoted_date(t)) - end -end diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb index ffb4e2c5cf..776549eb7a 100644 --- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb @@ -4,24 +4,24 @@ require "cases/helper" # reserved word names (ie: group, order, values, etc...) class Mysql2ReservedWordTest < ActiveRecord::Mysql2TestCase class Group < ActiveRecord::Base - Group.table_name = 'group' + Group.table_name = "group" belongs_to :select has_one :values end class Select < ActiveRecord::Base - Select.table_name = 'select' + Select.table_name = "select" has_many :groups end class Values < ActiveRecord::Base - Values.table_name = 'values' + Values.table_name = "values" end class Distinct < ActiveRecord::Base - Distinct.table_name = 'distinct' + Distinct.table_name = "distinct" has_and_belongs_to_many :selects - has_many :values, :through => :groups + has_many :values, through: :groups end def setup @@ -30,15 +30,15 @@ class Mysql2ReservedWordTest < ActiveRecord::Mysql2TestCase # we call execute directly here (and do similar below) because ActiveRecord::Base#create_table() # will fail with these table names if these test cases fail - create_tables_directly 'group'=>'id int auto_increment primary key, `order` varchar(255), select_id int', - 'select'=>'id int auto_increment primary key', - 'values'=>'id int auto_increment primary key, group_id int', - 'distinct'=>'id int auto_increment primary key', - 'distinct_select'=>'distinct_id int, select_id int' + create_tables_directly "group"=>"id int auto_increment primary key, `order` varchar(255), select_id int", + "select"=>"id int auto_increment primary key", + "values"=>"id int auto_increment primary key, group_id int", + "distinct"=>"id int auto_increment primary key", + "distinct_select"=>"distinct_id int, select_id int" end teardown do - drop_tables_directly ['group', 'select', 'values', 'distinct', 'distinct_select', 'order'] + drop_tables_directly ["group", "select", "values", "distinct", "distinct_select", "order"] end # create tables with reserved-word names and columns @@ -57,9 +57,9 @@ class Mysql2ReservedWordTest < ActiveRecord::Mysql2TestCase # alter column with a reserved-word name in a table with a reserved-word name def test_change_columns - assert_nothing_raised { @connection.change_column_default(:group, :order, 'whatever') } + assert_nothing_raised { @connection.change_column_default(:group, :order, "whatever") } #the quoting here will reveal any double quoting issues in change_column's interaction with the column method in the adapter - assert_nothing_raised { @connection.change_column('group', 'order', :Int, :default => 0) } + assert_nothing_raised { @connection.change_column("group", "order", :Int, default: 0) } assert_nothing_raised { @connection.rename_column(:group, :order, :values) } end @@ -78,11 +78,11 @@ class Mysql2ReservedWordTest < ActiveRecord::Mysql2TestCase create_test_fixtures :select, :distinct, :group, :values, :distinct_select x = nil assert_nothing_raised { x = Group.new } - x.order = 'x' + x.order = "x" assert_nothing_raised { x.save } - x.order = 'y' + x.order = "y" assert_nothing_raised { x.save } - assert_nothing_raised { Group.find_by_order('y') } + assert_nothing_raised { Group.find_by_order("y") } assert_nothing_raised { Group.find(1) } end @@ -124,29 +124,28 @@ class Mysql2ReservedWordTest < ActiveRecord::Mysql2TestCase end def test_associations_work_with_reserved_words - assert_nothing_raised { Select.all.merge!(:includes => [:groups]).to_a } + assert_nothing_raised { Select.all.merge!(includes: [:groups]).to_a } end #the following functions were added to DRY test cases private - # custom fixture loader, uses FixtureSet#create_fixtures and appends base_path to the current file's path - def create_test_fixtures(*fixture_names) - ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names) - end - - # custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name - def drop_tables_directly(table_names, connection = @connection) - table_names.each do |name| - connection.drop_table name, if_exists: true + # custom fixture loader, uses FixtureSet#create_fixtures and appends base_path to the current file's path + def create_test_fixtures(*fixture_names) + ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names) end - 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) - tables.each do |table_name, column_properties| - connection.execute("CREATE TABLE `#{table_name}` ( #{column_properties} )") + # custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name + def drop_tables_directly(table_names, connection = @connection) + table_names.each do |name| + connection.drop_table name, if_exists: true + end end - 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) + tables.each do |table_name, column_properties| + connection.execute("CREATE TABLE `#{table_name}` ( #{column_properties} )") + end + end end diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb index 7c89fda582..fa54aac6b3 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb @@ -18,7 +18,7 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase connection.initialize_schema_migrations_table - assert connection.column_exists?(table_name, :version, :string, collation: 'utf8_general_ci') + assert connection.column_exists?(table_name, :version, :string, collation: "utf8_general_ci") end end @@ -29,31 +29,31 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase connection.initialize_internal_metadata_table - assert connection.column_exists?(table_name, :key, :string, collation: 'utf8_general_ci') + assert connection.column_exists?(table_name, :key, :string, collation: "utf8_general_ci") end end private - def with_encoding_utf8mb4 - database_name = connection.current_database - database_info = connection.select_one("SELECT * FROM information_schema.schemata WHERE schema_name = '#{database_name}'") + def with_encoding_utf8mb4 + database_name = connection.current_database + database_info = connection.select_one("SELECT * FROM information_schema.schemata WHERE schema_name = '#{database_name}'") - original_charset = database_info["DEFAULT_CHARACTER_SET_NAME"] - original_collation = database_info["DEFAULT_COLLATION_NAME"] + original_charset = database_info["DEFAULT_CHARACTER_SET_NAME"] + original_collation = database_info["DEFAULT_COLLATION_NAME"] - execute("ALTER DATABASE #{database_name} DEFAULT CHARACTER SET utf8mb4") + execute("ALTER DATABASE #{database_name} DEFAULT CHARACTER SET utf8mb4") - yield - ensure - execute("ALTER DATABASE #{database_name} DEFAULT CHARACTER SET #{original_charset} COLLATE #{original_collation}") - end + yield + ensure + execute("ALTER DATABASE #{database_name} DEFAULT CHARACTER SET #{original_charset} COLLATE #{original_collation}") + end - def connection - @connection ||= ActiveRecord::Base.connection - end + def connection + @connection ||= ActiveRecord::Base.connection + end - def execute(sql) - connection.execute(sql) - end + def execute(sql) + connection.execute(sql) + end end diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb index 43957791b1..aea930cfe6 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb @@ -1,6 +1,6 @@ require "cases/helper" -require 'models/post' -require 'models/comment' +require "models/post" +require "models/comment" module ActiveRecord module ConnectionAdapters @@ -16,7 +16,7 @@ module ActiveRecord @omgpost = Class.new(ActiveRecord::Base) do self.inheritance_column = :disabled self.table_name = "#{db}.#{table}" - def self.name; 'Post'; end + def self.name; "Post"; end end end @@ -31,13 +31,13 @@ module ActiveRecord t.float :float_25, limit: 25 end - column_no_limit = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_no_limit' } - column_short = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_short' } - column_long = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_long' } + column_no_limit = @connection.columns(:mysql_doubles).find { |c| c.name == "float_no_limit" } + column_short = @connection.columns(:mysql_doubles).find { |c| c.name == "float_short" } + column_long = @connection.columns(:mysql_doubles).find { |c| c.name == "float_long" } - column_23 = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_23' } - column_24 = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_24' } - column_25 = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_25' } + column_23 = @connection.columns(:mysql_doubles).find { |c| c.name == "float_23" } + column_24 = @connection.columns(:mysql_doubles).find { |c| c.name == "float_24" } + column_25 = @connection.columns(:mysql_doubles).find { |c| c.name == "float_25" } # Mysql floats are precision 0..24, Mysql doubles are precision 25..53 assert_equal 24, column_no_limit.limit @@ -56,7 +56,7 @@ module ActiveRecord end def test_primary_key - assert_equal 'id', @omgpost.primary_key + assert_equal "id", @omgpost.primary_key end def test_data_source_exists? @@ -69,18 +69,18 @@ module ActiveRecord end def test_dump_indexes - index_a_name = 'index_key_tests_on_snack' - index_b_name = 'index_key_tests_on_pizza' - index_c_name = 'index_key_tests_on_awesome' + index_a_name = "index_key_tests_on_snack" + index_b_name = "index_key_tests_on_pizza" + index_c_name = "index_key_tests_on_awesome" - table = 'key_tests' + table = "key_tests" indexes = @connection.indexes(table).sort_by(&:name) assert_equal 3,indexes.size - index_a = indexes.select{|i| i.name == index_a_name}[0] - index_b = indexes.select{|i| i.name == index_b_name}[0] - index_c = indexes.select{|i| i.name == index_c_name}[0] + index_a = indexes.select { |i| i.name == index_a_name }[0] + index_b = indexes.select { |i| i.name == index_b_name }[0] + index_c = indexes.select { |i| i.name == index_c_name }[0] assert_equal :btree, index_a.using assert_nil index_a.type assert_equal :btree, index_b.using @@ -103,3 +103,24 @@ module ActiveRecord end end end + +class Mysql2AnsiQuotesTest < ActiveRecord::Mysql2TestCase + def setup + @connection = ActiveRecord::Base.connection + @connection.execute("SET SESSION sql_mode='ANSI_QUOTES'") + end + + def teardown + @connection.reconnect! + end + + def test_primary_key_method_with_ansi_quotes + assert_equal "id", @connection.primary_key("topics") + end + + def test_foreign_keys_method_with_ansi_quotes + fks = @connection.foreign_keys("lessons_students") + assert_equal([["lessons_students", "students", :cascade]], + fks.map { |fk| [fk.from_table, fk.to_table, fk.on_delete] }) + end +end diff --git a/activerecord/test/cases/adapters/mysql2/sp_test.rb b/activerecord/test/cases/adapters/mysql2/sp_test.rb index 4197ba45f1..4182532535 100644 --- a/activerecord/test/cases/adapters/mysql2/sp_test.rb +++ b/activerecord/test/cases/adapters/mysql2/sp_test.rb @@ -1,13 +1,13 @@ require "cases/helper" -require 'models/topic' -require 'models/reply' +require "models/topic" +require "models/reply" class Mysql2StoredProcedureTest < ActiveRecord::Mysql2TestCase fixtures :topics def setup @connection = ActiveRecord::Base.connection - unless ActiveRecord::Base.connection.version >= '5.6.0' + unless ActiveRecord::Base.connection.version >= "5.6.0" skip("no stored procedure support") end end @@ -17,19 +17,19 @@ class Mysql2StoredProcedureTest < ActiveRecord::Mysql2TestCase # In MySQL 5.6, CLIENT_MULTI_RESULTS is enabled by default. # http://dev.mysql.com/doc/refman/5.6/en/call.html def test_multi_results - rows = @connection.select_rows('CALL ten();') + rows = @connection.select_rows("CALL ten();") assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}" assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select_rows'" end def test_multi_results_from_select_one - row = @connection.select_one('CALL topics(1);') - assert_equal 'David', row['author_name'] + row = @connection.select_one("CALL topics(1);") + assert_equal "David", row["author_name"] assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select_one'" end def test_multi_results_from_find_by_sql - topics = Topic.find_by_sql 'CALL topics(3);' + topics = Topic.find_by_sql "CALL topics(3);" assert_equal 3, topics.size assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select'" end diff --git a/activerecord/test/cases/adapters/mysql2/sql_types_test.rb b/activerecord/test/cases/adapters/mysql2/sql_types_test.rb index 4926bc2267..bee42d48f1 100644 --- a/activerecord/test/cases/adapters/mysql2/sql_types_test.rb +++ b/activerecord/test/cases/adapters/mysql2/sql_types_test.rb @@ -2,10 +2,10 @@ require "cases/helper" class Mysql2SqlTypesTest < ActiveRecord::Mysql2TestCase def test_binary_types - assert_equal 'varbinary(64)', type_to_sql(:binary, 64) - assert_equal 'varbinary(4095)', type_to_sql(:binary, 4095) - assert_equal 'blob', type_to_sql(:binary, 4096) - assert_equal 'blob', type_to_sql(:binary) + assert_equal "varbinary(64)", type_to_sql(:binary, 64) + assert_equal "varbinary(4095)", type_to_sql(:binary, 4095) + assert_equal "blob", type_to_sql(:binary, 4096) + assert_equal "blob", type_to_sql(:binary) end def type_to_sql(*args) diff --git a/activerecord/test/cases/adapters/mysql2/table_options_test.rb b/activerecord/test/cases/adapters/mysql2/table_options_test.rb index af121ee7d9..61a8ce9bc0 100644 --- a/activerecord/test/cases/adapters/mysql2/table_options_test.rb +++ b/activerecord/test/cases/adapters/mysql2/table_options_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'support/schema_dumping_helper' +require "support/schema_dumping_helper" class Mysql2TableOptionsTest < ActiveRecord::Mysql2TestCase include SchemaDumpingHelper diff --git a/activerecord/test/cases/adapters/mysql2/transaction_test.rb b/activerecord/test/cases/adapters/mysql2/transaction_test.rb new file mode 100644 index 0000000000..edd5353ee3 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql2/transaction_test.rb @@ -0,0 +1,55 @@ +require "cases/helper" +require "support/connection_helper" + +module ActiveRecord + class Mysql2TransactionTest < ActiveRecord::Mysql2TestCase + self.use_transactional_tests = false + + class Sample < ActiveRecord::Base + self.table_name = "samples" + end + + setup do + @connection = ActiveRecord::Base.connection + @connection.clear_cache! + + @connection.transaction do + @connection.drop_table "samples", if_exists: true + @connection.create_table("samples") do |t| + t.integer "value" + end + end + + Sample.reset_column_information + end + + teardown do + @connection.drop_table "samples", if_exists: true + end + + test "raises Deadlocked when a deadlock is encountered" do + assert_raises(ActiveRecord::Deadlocked) do + s1 = Sample.create value: 1 + s2 = Sample.create value: 2 + + thread = Thread.new do + Sample.transaction do + s1.lock! + sleep 1 + s2.update_attributes value: 1 + end + end + + sleep 0.5 + + Sample.transaction do + s2.lock! + sleep 1 + s1.update_attributes value: 2 + end + + thread.join + end + end + 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 c95a64cc16..452f8d5ae8 100644 --- a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb +++ b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb @@ -28,10 +28,10 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase end test "minus value is out of range" do - assert_raise(RangeError) do + assert_raise(ActiveModel::RangeError) do UnsignedType.create(unsigned_integer: -10) end - assert_raise(RangeError) do + assert_raise(ActiveModel::RangeError) do UnsignedType.create(unsigned_bigint: -10) end assert_raise(ActiveRecord::StatementInvalid) do @@ -50,7 +50,7 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase t.unsigned_decimal :unsigned_decimal_t, precision: 10, scale: 2 end - @connection.columns("unsigned_types").select { |c| /^unsigned_/ === c.name }.each do |column| + @connection.columns("unsigned_types").select { |c| /^unsigned_/.match?(c.name) }.each do |column| assert column.unsigned? end end @@ -58,7 +58,7 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase test "schema dump includes unsigned option" do schema = dump_table_schema "unsigned_types" assert_match %r{t.integer\s+"unsigned_integer",\s+unsigned: true$}, schema - assert_match %r{t.integer\s+"unsigned_bigint",\s+limit: 8,\s+unsigned: true$}, schema + assert_match %r{t.bigint\s+"unsigned_bigint",\s+unsigned: true$}, schema assert_match %r{t.float\s+"unsigned_float",\s+limit: 24,\s+unsigned: true$}, schema assert_match %r{t.decimal\s+"unsigned_decimal",\s+precision: 10,\s+scale: 2,\s+unsigned: true$}, schema end diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb index ed44bf7362..d3c65f3d94 100644 --- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb @@ -1,4 +1,4 @@ -require 'cases/helper' +require "cases/helper" class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase def setup @@ -15,12 +15,12 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase def test_create_database_with_encoding assert_equal %(CREATE DATABASE "matt" ENCODING = 'utf8'), create_database(:matt) - assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'latin1'), create_database(:aimonetti, :encoding => :latin1) - assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'latin1'), create_database(:aimonetti, 'encoding' => :latin1) + assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'latin1'), create_database(:aimonetti, encoding: :latin1) + assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'latin1'), create_database(:aimonetti, "encoding" => :latin1) end def test_create_database_with_collation_and_ctype - assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'UTF8' LC_COLLATE = 'ja_JP.UTF8' LC_CTYPE = 'ja_JP.UTF8'), create_database(:aimonetti, :encoding => :"UTF8", :collation => :"ja_JP.UTF8", :ctype => :"ja_JP.UTF8") + assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'UTF8' LC_COLLATE = 'ja_JP.UTF8' LC_CTYPE = 'ja_JP.UTF8'), create_database(:aimonetti, encoding: :"UTF8", collation: :"ja_JP.UTF8", ctype: :"ja_JP.UTF8") end def test_add_index @@ -28,7 +28,13 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:define_method, :index_name_exists?) { |*| false } expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" ("last_name") WHERE state = 'active') - assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'") + assert_equal expected, add_index(:people, :last_name, unique: true, where: "state = 'active'") + + expected = %(CREATE UNIQUE INDEX "index_people_on_lower_last_name" ON "people" (lower(last_name))) + assert_equal expected, add_index(:people, "lower(last_name)", unique: true) + + expected = %(CREATE UNIQUE INDEX "index_people_on_last_name_varchar_pattern_ops" ON "people" (last_name varchar_pattern_ops)) + assert_equal expected, add_index(:people, "last_name varchar_pattern_ops", unique: true) expected = %(CREATE INDEX CONCURRENTLY "index_people_on_last_name" ON "people" ("last_name")) assert_equal expected, add_index(:people, :last_name, algorithm: :concurrently) @@ -39,16 +45,17 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase expected = %(CREATE INDEX CONCURRENTLY "index_people_on_last_name" ON "people" USING #{type} ("last_name")) assert_equal expected, add_index(:people, :last_name, using: type, algorithm: :concurrently) + + expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" USING #{type} ("last_name") WHERE state = 'active') + assert_equal expected, add_index(:people, :last_name, using: type, unique: true, where: "state = 'active'") + + expected = %(CREATE UNIQUE INDEX "index_people_on_lower_last_name" ON "people" USING #{type} (lower(last_name))) + assert_equal expected, add_index(:people, "lower(last_name)", using: type, unique: true) end assert_raise ArgumentError do add_index(:people, :last_name, algorithm: :copy) end - expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name")) - assert_equal expected, add_index(:people, :last_name, :unique => true, :using => :gist) - - expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name") WHERE state = 'active') - assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'", :using => :gist) ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send :remove_method, :index_name_exists? end @@ -56,7 +63,7 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase def test_remove_index # remove_index calls index_name_for_remove which can't work since execute is stubbed ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:define_method, :index_name_for_remove) do |*| - 'index_people_on_last_name' + "index_people_on_last_name" end expected = %(DROP INDEX CONCURRENTLY "index_people_on_last_name") @@ -69,6 +76,17 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send :remove_method, :index_name_for_remove end + def test_remove_index_when_name_is_specified + expected = %(DROP INDEX CONCURRENTLY "index_people_on_last_name") + assert_equal expected, remove_index(:people, name: "index_people_on_last_name", algorithm: :concurrently) + end + + def test_remove_index_with_wrong_option + assert_raises ArgumentError do + remove_index(:people, coulmn: :last_name) + end + end + private def method_missing(method_symbol, *arguments) ActiveRecord::Base.connection.send(method_symbol, *arguments) diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb index 380a90d765..97960b6c51 100644 --- a/activerecord/test/cases/adapters/postgresql/array_test.rb +++ b/activerecord/test/cases/adapters/postgresql/array_test.rb @@ -1,35 +1,35 @@ require "cases/helper" -require 'support/schema_dumping_helper' +require "support/schema_dumping_helper" class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper include InTimeZone class PgArray < ActiveRecord::Base - self.table_name = 'pg_arrays' + self.table_name = "pg_arrays" end def setup @connection = ActiveRecord::Base.connection - enable_extension!('hstore', @connection) + enable_extension!("hstore", @connection) @connection.transaction do - @connection.create_table('pg_arrays') do |t| - t.string 'tags', array: true - t.integer 'ratings', array: true + @connection.create_table("pg_arrays") do |t| + t.string "tags", array: true + t.integer "ratings", array: true t.datetime :datetimes, array: true t.hstore :hstores, array: true end end PgArray.reset_column_information - @column = PgArray.columns_hash['tags'] + @column = PgArray.columns_hash["tags"] @type = PgArray.type_for_attribute("tags") end teardown do - @connection.drop_table 'pg_arrays', if_exists: true - disable_extension!('hstore', @connection) + @connection.drop_table "pg_arrays", if_exists: true + disable_extension!("hstore", @connection) end def test_column @@ -38,26 +38,26 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase assert @column.array? assert_not @type.binary? - ratings_column = PgArray.columns_hash['ratings'] + ratings_column = PgArray.columns_hash["ratings"] assert_equal :integer, ratings_column.type assert ratings_column.array? end def test_default - @connection.add_column 'pg_arrays', 'score', :integer, array: true, default: [4, 4, 2] + @connection.add_column "pg_arrays", "score", :integer, array: true, default: [4, 4, 2] PgArray.reset_column_information - assert_equal([4, 4, 2], PgArray.column_defaults['score']) + assert_equal([4, 4, 2], PgArray.column_defaults["score"]) assert_equal([4, 4, 2], PgArray.new.score) ensure PgArray.reset_column_information end def test_default_strings - @connection.add_column 'pg_arrays', 'names', :string, array: true, default: ["foo", "bar"] + @connection.add_column "pg_arrays", "names", :string, array: true, default: ["foo", "bar"] PgArray.reset_column_information - assert_equal(["foo", "bar"], PgArray.column_defaults['names']) + assert_equal(["foo", "bar"], PgArray.column_defaults["names"]) assert_equal(["foo", "bar"], PgArray.new.names) ensure PgArray.reset_column_information @@ -68,10 +68,10 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase @connection.change_column :pg_arrays, :snippets, :text, array: true, default: [] PgArray.reset_column_information - column = PgArray.columns_hash['snippets'] + column = PgArray.columns_hash["snippets"] assert_equal :text, column.type - assert_equal [], PgArray.column_defaults['snippets'] + assert_equal [], PgArray.column_defaults["snippets"] assert column.array? end @@ -88,17 +88,17 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase @connection.change_column_default :pg_arrays, :tags, [] PgArray.reset_column_information - assert_equal [], PgArray.column_defaults['tags'] + assert_equal [], PgArray.column_defaults["tags"] end def test_type_cast_array - assert_equal(['1', '2', '3'], @type.deserialize('{1,2,3}')) - assert_equal([], @type.deserialize('{}')) - assert_equal([nil], @type.deserialize('{NULL}')) + assert_equal(["1", "2", "3"], @type.deserialize("{1,2,3}")) + assert_equal([], @type.deserialize("{}")) + assert_equal([nil], @type.deserialize("{NULL}")) end def test_type_cast_integers - x = PgArray.new(ratings: ['1', '2']) + x = PgArray.new(ratings: ["1", "2"]) assert_equal([1, 2], x.ratings) @@ -117,15 +117,15 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase def test_select_with_strings @connection.execute "insert into pg_arrays (tags) VALUES ('{1,2,3}')" x = PgArray.first - assert_equal(['1','2','3'], x.tags) + assert_equal(["1","2","3"], x.tags) end def test_rewrite_with_strings @connection.execute "insert into pg_arrays (tags) VALUES ('{1,2,3}')" x = PgArray.first - x.tags = ['1','2','3','4'] + x.tags = ["1","2","3","4"] x.save! - assert_equal ['1','2','3','4'], x.reload.tags + assert_equal ["1","2","3","4"], x.reload.tags end def test_select_with_integers @@ -137,25 +137,25 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase def test_rewrite_with_integers @connection.execute "insert into pg_arrays (ratings) VALUES ('{1,2,3}')" x = PgArray.first - x.ratings = [2, '3', 4] + x.ratings = [2, "3", 4] x.save! assert_equal [2, 3, 4], x.reload.ratings end def test_multi_dimensional_with_strings - assert_cycle(:tags, [[['1'], ['2']], [['2'], ['3']]]) + assert_cycle(:tags, [[["1"], ["2"]], [["2"], ["3"]]]) end def test_with_empty_strings - assert_cycle(:tags, [ '1', '2', '', '4', '', '5' ]) + assert_cycle(:tags, [ "1", "2", "", "4", "", "5" ]) end def test_with_multi_dimensional_empty_strings - assert_cycle(:tags, [[['1', '2'], ['', '4'], ['', '5']]]) + assert_cycle(:tags, [[["1", "2"], ["", "4"], ["", "5"]]]) end def test_with_arbitrary_whitespace - assert_cycle(:tags, [[['1', '2'], [' ', '4'], [' ', '5']]]) + assert_cycle(:tags, [[["1", "2"], [" ", "4"], [" ", "5"]]]) end def test_multi_dimensional_with_integers @@ -163,34 +163,39 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase end def test_strings_with_quotes - assert_cycle(:tags, ['this has','some "s that need to be escaped"']) + assert_cycle(:tags, ["this has",'some "s that need to be escaped"']) end def test_strings_with_commas - assert_cycle(:tags, ['this,has','many,values']) + assert_cycle(:tags, ["this,has","many,values"]) end def test_strings_with_array_delimiters - assert_cycle(:tags, ['{','}']) + assert_cycle(:tags, ["{","}"]) end def test_strings_with_null_strings - assert_cycle(:tags, ['NULL','NULL']) + assert_cycle(:tags, ["NULL","NULL"]) end def test_contains_nils - assert_cycle(:tags, ['1',nil,nil]) + assert_cycle(:tags, ["1",nil,nil]) end def test_insert_fixture tag_values = ["val1", "val2", "val3_with_'_multiple_quote_'_chars"] - @connection.insert_fixture({"tags" => tag_values}, "pg_arrays" ) + @connection.insert_fixture({ "tags" => tag_values }, "pg_arrays" ) assert_equal(PgArray.last.tags, tag_values) end def test_attribute_for_inspect_for_array_field + record = PgArray.new { |a| a.ratings = (1..10).to_a } + assert_equal("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]", record.attribute_for_inspect(:ratings)) + end + + def test_attribute_for_inspect_for_array_field_for_large_array record = PgArray.new { |a| a.ratings = (1..11).to_a } - assert_equal("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]", record.attribute_for_inspect(:ratings)) + assert_equal("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]", record.attribute_for_inspect(:ratings)) end def test_escaping @@ -206,14 +211,14 @@ 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) + assert_equal x.tags_before_type_cast, PgArray.type_for_attribute("tags").serialize(tags) end def test_quoting_non_standard_delimiters strings = ["hello,", "world;"] oid = ActiveRecord::ConnectionAdapters::PostgreSQL::OID - comma_delim = oid::Array.new(ActiveRecord::Type::String.new, ',') - semicolon_delim = oid::Array.new(ActiveRecord::Type::String.new, ';') + comma_delim = oid::Array.new(ActiveRecord::Type::String.new, ",") + semicolon_delim = oid::Array.new(ActiveRecord::Type::String.new, ";") assert_equal %({"hello,",world;}), comma_delim.serialize(strings) assert_equal %({hello,;"world;"}), semicolon_delim.serialize(strings) @@ -231,13 +236,13 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase end def test_mutate_value_in_array - x = PgArray.create!(hstores: [{ a: 'a' }, { b: 'b' }]) + x = PgArray.create!(hstores: [{ a: "a" }, { b: "b" }]) - x.hstores.first['a'] = 'c' + x.hstores.first["a"] = "c" x.save! x.reload - assert_equal [{ 'a' => 'c' }, { 'b' => 'b' }], x.hstores + assert_equal [{ "a" => "c" }, { "b" => "b" }], x.hstores assert_not x.changed? end @@ -285,6 +290,12 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase assert_equal record.tags, record.reload.tags end + def test_where_by_attribute_with_array + tags = ["black", "blue"] + record = PgArray.create!(tags: tags) + assert_equal record, PgArray.where(tags: tags).take + end + def test_uniqueness_validation klass = Class.new(PgArray) do validates_uniqueness_of :tags @@ -300,18 +311,24 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase assert_equal ["has already been taken"], e2.errors[:tags], "Should have uniqueness message for tags" end - private - def assert_cycle field, array - # test creation - x = PgArray.create!(field => array) - x.reload - assert_equal(array, x.public_send(field)) - - # test updating - x = PgArray.create!(field => []) - x.public_send("#{field}=", array) - x.save! - x.reload - assert_equal(array, x.public_send(field)) + def test_encoding_arrays_of_utf8_strings + string_with_utf8 = "nový" + assert_equal [string_with_utf8], @type.deserialize(@type.serialize([string_with_utf8])) + assert_equal [[string_with_utf8]], @type.deserialize(@type.serialize([[string_with_utf8]])) end + + private + def assert_cycle(field, array) + # test creation + x = PgArray.create!(field => array) + x.reload + assert_equal(array, x.public_send(field)) + + # test updating + x = PgArray.create!(field => []) + x.public_send("#{field}=", array) + x.save! + x.reload + assert_equal(array, x.public_send(field)) + end end diff --git a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb index 6f72fa6e0f..7712e809a2 100644 --- a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb @@ -1,6 +1,6 @@ require "cases/helper" -require 'support/connection_helper' -require 'support/schema_dumping_helper' +require "support/connection_helper" +require "support/schema_dumping_helper" class PostgresqlBitStringTest < ActiveRecord::PostgreSQLTestCase include ConnectionHelper @@ -10,7 +10,7 @@ class PostgresqlBitStringTest < ActiveRecord::PostgreSQLTestCase def setup @connection = ActiveRecord::Base.connection - @connection.create_table('postgresql_bit_strings', :force => true) do |t| + @connection.create_table("postgresql_bit_strings", force: true) do |t| t.bit :a_bit, default: "00000011", limit: 8 t.bit_varying :a_bit_varying, default: "0011", limit: 4 t.bit :another_bit @@ -20,7 +20,7 @@ class PostgresqlBitStringTest < ActiveRecord::PostgreSQLTestCase def teardown return unless @connection - @connection.drop_table 'postgresql_bit_strings', if_exists: true + @connection.drop_table "postgresql_bit_strings", if_exists: true end def test_bit_string_column @@ -44,10 +44,10 @@ class PostgresqlBitStringTest < ActiveRecord::PostgreSQLTestCase end def test_default - assert_equal "00000011", PostgresqlBitString.column_defaults['a_bit'] + assert_equal "00000011", PostgresqlBitString.column_defaults["a_bit"] assert_equal "00000011", PostgresqlBitString.new.a_bit - assert_equal "0011", PostgresqlBitString.column_defaults['a_bit_varying'] + assert_equal "0011", PostgresqlBitString.column_defaults["a_bit_varying"] assert_equal "0011", PostgresqlBitString.new.a_bit_varying end @@ -57,16 +57,19 @@ class PostgresqlBitStringTest < ActiveRecord::PostgreSQLTestCase assert_match %r{t\.bit_varying\s+"a_bit_varying",\s+limit: 4,\s+default: "0011"$}, output end - def test_assigning_invalid_hex_string_raises_exception - assert_raises(ActiveRecord::StatementInvalid) { PostgresqlBitString.create! a_bit: "FF" } - assert_raises(ActiveRecord::StatementInvalid) { PostgresqlBitString.create! a_bit_varying: "FF" } + if ActiveRecord::Base.connection.prepared_statements + def test_assigning_invalid_hex_string_raises_exception + assert_raises(ActiveRecord::StatementInvalid) { PostgresqlBitString.create! a_bit: "FF" } + assert_raises(ActiveRecord::StatementInvalid) { PostgresqlBitString.create! a_bit_varying: "F" } + end end def test_roundtrip - PostgresqlBitString.create! a_bit: "00001010", a_bit_varying: "0101" - record = PostgresqlBitString.first + record = PostgresqlBitString.create!(a_bit: "00001010", a_bit_varying: "0101") assert_equal "00001010", record.a_bit assert_equal "0101", record.a_bit_varying + assert_nil record.another_bit + assert_nil record.another_bit_varying record.a_bit = "11111111" record.a_bit_varying = "0xF" diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb index b6bb1929e6..dc0df8715a 100644 --- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb @@ -1,26 +1,29 @@ require "cases/helper" +require "support/schema_dumping_helper" class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase + include SchemaDumpingHelper + class ByteaDataType < ActiveRecord::Base - self.table_name = 'bytea_data_type' + self.table_name = "bytea_data_type" end def setup @connection = ActiveRecord::Base.connection begin @connection.transaction do - @connection.create_table('bytea_data_type') do |t| - t.binary 'payload' - t.binary 'serialized' + @connection.create_table("bytea_data_type") do |t| + t.binary "payload" + t.binary "serialized" end end end - @column = ByteaDataType.columns_hash['payload'] + @column = ByteaDataType.columns_hash["payload"] @type = ByteaDataType.type_for_attribute("payload") end teardown do - @connection.drop_table 'bytea_data_type', if_exists: true + @connection.drop_table "bytea_data_type", if_exists: true end def test_column @@ -29,7 +32,7 @@ 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, 100_000) assert_raise ActiveRecord::ActiveRecordError do @connection.type_to_sql :binary, 4294967295 end @@ -39,8 +42,8 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase assert @column data = "\u001F\x8B" - assert_equal('UTF-8', data.encoding.name) - assert_equal('ASCII-8BIT', @type.deserialize(data).encoding.name) + assert_equal("UTF-8", data.encoding.name) + assert_equal("ASCII-8BIT", @type.deserialize(data).encoding.name) end def test_type_cast_binary_value @@ -85,14 +88,14 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase def test_via_to_sql_with_complicating_connection Thread.new do other_conn = ActiveRecord::Base.connection - other_conn.execute('SET standard_conforming_strings = off') + other_conn.execute("SET standard_conforming_strings = off") end.join test_via_to_sql end def test_write_binary - data = File.read(File.join(File.dirname(__FILE__), '..', '..', '..', 'assets', 'example.log')) + data = File.read(File.join(File.dirname(__FILE__), "..", "..", "..", "assets", "example.log")) assert(data.size > 1) record = ByteaDataType.create(payload: data) assert_not record.new_record? @@ -122,4 +125,10 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase obj.reload assert_equal "hello world", obj.serialized end + + def test_schema_dumping + output = dump_table_schema("bytea_data_type") + assert_match %r{t\.binary\s+"payload"$}, output + assert_match %r{t\.binary\s+"serialized"$}, output + end end diff --git a/activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb b/activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb new file mode 100644 index 0000000000..03b44feab6 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb @@ -0,0 +1,26 @@ +require "cases/helper" + +class PostgresqlCaseInsensitiveTest < ActiveRecord::PostgreSQLTestCase + class Default < ActiveRecord::Base; end + + def test_case_insensitiveness + connection = ActiveRecord::Base.connection + table = Default.arel_table + + column = Default.columns_hash["char1"] + comparison = connection.case_insensitive_comparison table, :char1, column, nil + assert_match(/lower/i, comparison.to_sql) + + column = Default.columns_hash["char2"] + comparison = connection.case_insensitive_comparison table, :char2, column, nil + assert_match(/lower/i, comparison.to_sql) + + column = Default.columns_hash["char3"] + comparison = connection.case_insensitive_comparison table, :char3, column, nil + assert_match(/lower/i, comparison.to_sql) + + column = Default.columns_hash["multiline_default"] + comparison = connection.case_insensitive_comparison table, :multiline_default, column, nil + assert_match(/lower/i, comparison.to_sql) + end +end diff --git a/activerecord/test/cases/adapters/postgresql/change_schema_test.rb b/activerecord/test/cases/adapters/postgresql/change_schema_test.rb index bc12df668d..ea642069d2 100644 --- a/activerecord/test/cases/adapters/postgresql/change_schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/change_schema_test.rb @@ -1,4 +1,4 @@ -require 'cases/helper' +require "cases/helper" module ActiveRecord class Migration @@ -19,17 +19,17 @@ module ActiveRecord def test_change_string_to_date connection.change_column :strings, :somedate, :timestamp, using: 'CAST("somedate" AS timestamp)' - assert_equal :datetime, connection.columns(:strings).find { |c| c.name == 'somedate' }.type + assert_equal :datetime, connection.columns(:strings).find { |c| c.name == "somedate" }.type end def test_change_type_with_symbol connection.change_column :strings, :somedate, :timestamp, cast_as: :timestamp - assert_equal :datetime, connection.columns(:strings).find { |c| c.name == 'somedate' }.type + assert_equal :datetime, connection.columns(:strings).find { |c| c.name == "somedate" }.type end def test_change_type_with_array connection.change_column :strings, :somedate, :timestamp, array: true, cast_as: :timestamp - column = connection.columns(:strings).find { |c| c.name == 'somedate' } + column = connection.columns(:strings).find { |c| c.name == "somedate" } assert_equal :datetime, column.type assert column.array? end diff --git a/activerecord/test/cases/adapters/postgresql/citext_test.rb b/activerecord/test/cases/adapters/postgresql/citext_test.rb index bd62041e79..ca95e4b626 100644 --- a/activerecord/test/cases/adapters/postgresql/citext_test.rb +++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb @@ -1,49 +1,49 @@ -require 'cases/helper' -require 'support/schema_dumping_helper' +require "cases/helper" +require "support/schema_dumping_helper" if ActiveRecord::Base.connection.supports_extensions? class PostgresqlCitextTest < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper class Citext < ActiveRecord::Base - self.table_name = 'citexts' + self.table_name = "citexts" end def setup @connection = ActiveRecord::Base.connection - enable_extension!('citext', @connection) + enable_extension!("citext", @connection) - @connection.create_table('citexts') do |t| - t.citext 'cival' + @connection.create_table("citexts") do |t| + t.citext "cival" end end teardown do - @connection.drop_table 'citexts', if_exists: true - disable_extension!('citext', @connection) + @connection.drop_table "citexts", if_exists: true + disable_extension!("citext", @connection) end def test_citext_enabled - assert @connection.extension_enabled?('citext') + assert @connection.extension_enabled?("citext") end def test_column - column = Citext.columns_hash['cival'] + column = Citext.columns_hash["cival"] assert_equal :citext, column.type - assert_equal 'citext', column.sql_type + assert_equal "citext", column.sql_type assert_not column.array? - type = Citext.type_for_attribute('cival') + type = Citext.type_for_attribute("cival") assert_not type.binary? end def test_change_table_supports_json @connection.transaction do - @connection.change_table('citexts') do |t| - t.citext 'username' + @connection.change_table("citexts") do |t| + t.citext "username" end Citext.reset_column_information - column = Citext.columns_hash['username'] + column = Citext.columns_hash["username"] assert_equal :citext, column.type raise ActiveRecord::Rollback # reset the schema change @@ -53,7 +53,7 @@ if ActiveRecord::Base.connection.supports_extensions? end def test_write - x = Citext.new(cival: 'Some CI Text') + x = Citext.new(cival: "Some CI Text") x.save! citext = Citext.first assert_equal "Some CI Text", citext.cival @@ -66,8 +66,8 @@ if ActiveRecord::Base.connection.supports_extensions? def test_select_case_insensitive @connection.execute "insert into citexts (cival) values('Cased Text')" - x = Citext.where(cival: 'cased text').first - assert_equal 'Cased Text', x.cival + x = Citext.where(cival: "cased text").first + assert_equal "Cased Text", x.cival end def test_schema_dump_with_shorthand diff --git a/activerecord/test/cases/adapters/postgresql/collation_test.rb b/activerecord/test/cases/adapters/postgresql/collation_test.rb index 8470329c35..b39e298a5d 100644 --- a/activerecord/test/cases/adapters/postgresql/collation_test.rb +++ b/activerecord/test/cases/adapters/postgresql/collation_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'support/schema_dumping_helper' +require "support/schema_dumping_helper" class PostgresqlCollationTest < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper @@ -7,8 +7,8 @@ class PostgresqlCollationTest < ActiveRecord::PostgreSQLTestCase def setup @connection = ActiveRecord::Base.connection @connection.create_table :postgresql_collations, force: true do |t| - t.string :string_c, collation: 'C' - t.text :text_posix, collation: 'POSIX' + t.string :string_c, collation: "C" + t.text :text_posix, collation: "POSIX" end end @@ -17,32 +17,32 @@ class PostgresqlCollationTest < ActiveRecord::PostgreSQLTestCase end test "string column with collation" do - column = @connection.columns(:postgresql_collations).find { |c| c.name == 'string_c' } + column = @connection.columns(:postgresql_collations).find { |c| c.name == "string_c" } assert_equal :string, column.type - assert_equal 'C', column.collation + assert_equal "C", column.collation end test "text column with collation" do - column = @connection.columns(:postgresql_collations).find { |c| c.name == 'text_posix' } + column = @connection.columns(:postgresql_collations).find { |c| c.name == "text_posix" } assert_equal :text, column.type - assert_equal 'POSIX', column.collation + assert_equal "POSIX", column.collation end test "add column with collation" do - @connection.add_column :postgresql_collations, :title, :string, collation: 'C' + @connection.add_column :postgresql_collations, :title, :string, collation: "C" - column = @connection.columns(:postgresql_collations).find { |c| c.name == 'title' } + column = @connection.columns(:postgresql_collations).find { |c| c.name == "title" } assert_equal :string, column.type - assert_equal 'C', column.collation + assert_equal "C", column.collation end test "change column with collation" do @connection.add_column :postgresql_collations, :description, :string - @connection.change_column :postgresql_collations, :description, :text, collation: 'POSIX' + @connection.change_column :postgresql_collations, :description, :text, collation: "POSIX" - column = @connection.columns(:postgresql_collations).find { |c| c.name == 'description' } + column = @connection.columns(:postgresql_collations).find { |c| c.name == "description" } assert_equal :text, column.type - assert_equal 'POSIX', column.collation + assert_equal "POSIX", column.collation end test "schema dump includes collation" do diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb index 1de87e5f01..1da2a9e2ac 100644 --- a/activerecord/test/cases/adapters/postgresql/composite_test.rb +++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'support/connection_helper' +require "support/connection_helper" module PostgresqlCompositeBehavior include ConnectionHelper @@ -20,7 +20,7 @@ module PostgresqlCompositeBehavior street VARCHAR(90) ); SQL - @connection.create_table('postgresql_composites') do |t| + @connection.create_table("postgresql_composites") do |t| t.column :address, :full_address end end @@ -29,8 +29,8 @@ module PostgresqlCompositeBehavior def teardown super - @connection.drop_table 'postgresql_composites', if_exists: true - @connection.execute 'DROP TYPE IF EXISTS full_address' + @connection.drop_table "postgresql_composites", if_exists: true + @connection.execute "DROP TYPE IF EXISTS full_address" reset_connection PostgresqlComposite.reset_column_information end @@ -69,12 +69,12 @@ class PostgresqlCompositeTest < ActiveRecord::PostgreSQLTestCase end private - def ensure_warning_is_issued - warning = capture(:stderr) do - PostgresqlComposite.columns_hash + def ensure_warning_is_issued + warning = capture(:stderr) do + PostgresqlComposite.columns_hash + end + assert_match(/unknown OID \d+: failed to recognize type of 'address'\. It will be treated as String\./, warning) end - assert_match(/unknown OID \d+: failed to recognize type of 'address'\. It will be treated as String\./, warning) - end end class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::PostgreSQLTestCase @@ -126,7 +126,7 @@ class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::PostgreSQLTestCase composite.address = FullAddress.new("Paris", "Rue Basse") composite.save! - assert_equal 'Paris', composite.reload.address.city - assert_equal 'Rue Basse', composite.reload.address.street + assert_equal "Paris", composite.reload.address.city + assert_equal "Rue Basse", composite.reload.address.street end end diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index d559de3e28..48c82cb7b9 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'support/connection_helper' +require "support/connection_helper" module ActiveRecord class PostgresqlConnectionTest < ActiveRecord::PostgreSQLTestCase @@ -13,7 +13,7 @@ module ActiveRecord def setup super @subscriber = SQLSubscriber.new - @subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber) + @subscription = ActiveSupport::Notifications.subscribe("sql.active_record", @subscriber) @connection = ActiveRecord::Base.connection end @@ -23,10 +23,10 @@ module ActiveRecord end def test_truncate - count = ActiveRecord::Base.connection.execute("select count(*) from comments").first['count'].to_i + count = ActiveRecord::Base.connection.execute("select count(*) from comments").first["count"].to_i assert_operator count, :>, 0 ActiveRecord::Base.connection.truncate("comments") - count = ActiveRecord::Base.connection.execute("select count(*) from comments").first['count'].to_i + count = ActiveRecord::Base.connection.execute("select count(*) from comments").first["count"].to_i assert_equal 0, count end @@ -54,85 +54,87 @@ module ActiveRecord NonExistentTable.establish_connection(params) # Verify the connection param has been applied. - expect = NonExistentTable.connection.query('show geqo').first.first - assert_equal 'off', expect + expect = NonExistentTable.connection.query("show geqo").first.first + assert_equal "off", expect end def test_reset - @connection.query('ROLLBACK') - @connection.query('SET geqo TO off') + @connection.query("ROLLBACK") + @connection.query("SET geqo TO off") # Verify the setting has been applied. - expect = @connection.query('show geqo').first.first - assert_equal 'off', expect + expect = @connection.query("show geqo").first.first + assert_equal "off", expect @connection.reset! # Verify the setting has been cleared. - expect = @connection.query('show geqo').first.first - assert_equal 'on', expect + expect = @connection.query("show geqo").first.first + assert_equal "on", expect end def test_reset_with_transaction - @connection.query('ROLLBACK') - @connection.query('SET geqo TO off') + @connection.query("ROLLBACK") + @connection.query("SET geqo TO off") # Verify the setting has been applied. - expect = @connection.query('show geqo').first.first - assert_equal 'off', expect + expect = @connection.query("show geqo").first.first + assert_equal "off", expect - @connection.query('BEGIN') + @connection.query("BEGIN") @connection.reset! # Verify the setting has been cleared. - expect = @connection.query('show geqo').first.first - assert_equal 'on', expect + expect = @connection.query("show geqo").first.first + assert_equal "on", expect end def test_tables_logs_name - ActiveSupport::Deprecation.silence { @connection.tables('hello') } - assert_equal 'SCHEMA', @subscriber.logged[0][1] + ActiveSupport::Deprecation.silence { @connection.tables("hello") } + assert_equal "SCHEMA", @subscriber.logged[0][1] end def test_indexes_logs_name - @connection.indexes('items', 'hello') - assert_equal 'SCHEMA', @subscriber.logged[0][1] + @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') } - assert_equal 'SCHEMA', @subscriber.logged[0][1] + ActiveSupport::Deprecation.silence { @connection.table_exists?("items") } + assert_equal "SCHEMA", @subscriber.logged[0][1] end def test_table_alias_length_logs_name @connection.instance_variable_set("@table_alias_length", nil) @connection.table_alias_length - assert_equal 'SCHEMA', @subscriber.logged[0][1] + assert_equal "SCHEMA", @subscriber.logged[0][1] end def test_current_database_logs_name @connection.current_database - assert_equal 'SCHEMA', @subscriber.logged[0][1] + assert_equal "SCHEMA", @subscriber.logged[0][1] end def test_encoding_logs_name @connection.encoding - assert_equal 'SCHEMA', @subscriber.logged[0][1] + assert_equal "SCHEMA", @subscriber.logged[0][1] end def test_schema_names_logs_name @connection.schema_names - assert_equal 'SCHEMA', @subscriber.logged[0][1] + assert_equal "SCHEMA", @subscriber.logged[0][1] end - def test_statement_key_is_logged - bind = Relation::QueryAttribute.new(nil, 1, Type::Value.new) - @connection.exec_query('SELECT $1::integer', 'SQL', [bind], prepare: true) - name = @subscriber.payloads.last[:statement_name] - assert name - res = @connection.exec_query("EXPLAIN (FORMAT JSON) EXECUTE #{name}(1)") - plan = res.column_types['QUERY PLAN'].deserialize res.rows.first.first - assert_operator plan.length, :>, 0 + if ActiveRecord::Base.connection.prepared_statements + def test_statement_key_is_logged + bind = Relation::QueryAttribute.new(nil, 1, Type::Value.new) + @connection.exec_query("SELECT $1::integer", "SQL", [bind], prepare: true) + name = @subscriber.payloads.last[:statement_name] + assert name + res = @connection.exec_query("EXPLAIN (FORMAT JSON) EXECUTE #{name}(1)") + plan = res.column_types["QUERY PLAN"].deserialize res.rows.first.first + assert_operator plan.length, :>, 0 + end end # Must have PostgreSQL >= 9.2, or with_manual_interventions set to @@ -144,7 +146,7 @@ module ActiveRecord # To restart PostgreSQL 9.1 on OS X, installed via MacPorts, ... # sudo su postgres -c "pg_ctl restart -D /opt/local/var/db/postgresql91/defaultdb/ -m fast" def test_reconnection_after_actual_disconnection_with_verify - original_connection_pid = @connection.query('select pg_backend_pid()') + original_connection_pid = @connection.query("select pg_backend_pid()") # Sanity check. assert @connection.active? @@ -153,8 +155,8 @@ module ActiveRecord secondary_connection = ActiveRecord::Base.connection_pool.checkout 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 ' + + elsif ARTest.config["with_manual_interventions"] + puts "Kill the connection now (e.g. by restarting the PostgreSQL " + 'server with the "-m fast" option) and then press enter.' $stdin.gets else @@ -170,7 +172,7 @@ module ActiveRecord # If we get no exception here, then either we re-connected successfully, or # we never actually got disconnected. - new_connection_pid = @connection.query('select pg_backend_pid()') + 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 " + @@ -182,7 +184,7 @@ module ActiveRecord def test_set_session_variable_true run_without_connection do |orig_connection| - ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => true}})) + ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { debug_print_plan: true })) set_true = ActiveRecord::Base.connection.exec_query "SHOW DEBUG_PRINT_PLAN" assert_equal set_true.rows, [["on"]] end @@ -190,7 +192,7 @@ module ActiveRecord def test_set_session_variable_false run_without_connection do |orig_connection| - ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => false}})) + ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { debug_print_plan: false })) set_false = ActiveRecord::Base.connection.exec_query "SHOW DEBUG_PRINT_PLAN" assert_equal set_false.rows, [["off"]] end @@ -199,14 +201,14 @@ module ActiveRecord def test_set_session_variable_nil run_without_connection do |orig_connection| # This should be a no-op that does not raise an error - ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => nil}})) + ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { debug_print_plan: nil })) end end def test_set_session_variable_default run_without_connection do |orig_connection| # This should execute a query that does not raise an error - ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => :default}})) + ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { debug_print_plan: :default })) end end @@ -222,14 +224,14 @@ module ActiveRecord got_lock = @connection.get_advisory_lock(lock_id) assert got_lock, "get_advisory_lock should have returned true but it didn't" - advisory_lock = @connection.query(list_advisory_locks).find {|l| l[1] == lock_id} + advisory_lock = @connection.query(list_advisory_locks).find { |l| l[1] == lock_id } assert advisory_lock, "expected to find an advisory lock with lock_id #{lock_id} but there wasn't one" released_lock = @connection.release_advisory_lock(lock_id) assert released_lock, "expected release_advisory_lock to return true but it didn't" - advisory_locks = @connection.query(list_advisory_locks).select {|l| l[1] == lock_id} + advisory_locks = @connection.query(list_advisory_locks).select { |l| l[1] == lock_id } assert_empty advisory_locks, "expected to have released advisory lock with lock_id #{lock_id} but it was still held" end @@ -239,17 +241,17 @@ module ActiveRecord with_warning_suppression do released_non_existent_lock = @connection.release_advisory_lock(fake_lock_id) assert_equal released_non_existent_lock, false, - 'expected release_advisory_lock to return false when there was no lock to release' + "expected release_advisory_lock to return false when there was no lock to release" end end protected - def with_warning_suppression - log_level = @connection.client_min_messages - @connection.client_min_messages = 'error' - yield - @connection.client_min_messages = log_level - end + def with_warning_suppression + log_level = @connection.client_min_messages + @connection.client_min_messages = "error" + yield + @connection.client_min_messages = log_level + end end end diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index 232c25cb3b..0ac8b7339b 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -1,6 +1,5 @@ require "cases/helper" -require 'support/ddl_helper' - +require "support/ddl_helper" class PostgresqlTime < ActiveRecord::Base end @@ -38,8 +37,8 @@ class PostgresqlDataTypeTest < ActiveRecord::PostgreSQLTestCase end def test_time_values - assert_equal '-1 years -2 days', @first_time.time_interval - assert_equal '-21 days', @first_time.scaled_time_interval + assert_equal "-1 years -2 days", @first_time.time_interval + assert_equal "-21 days", @first_time.scaled_time_interval end def test_oid_values @@ -47,10 +46,10 @@ class PostgresqlDataTypeTest < ActiveRecord::PostgreSQLTestCase end def test_update_time - @first_time.time_interval = '2 years 3 minutes' + @first_time.time_interval = "2 years 3 minutes" assert @first_time.save assert @first_time.reload - assert_equal '2 years 00:03:00', @first_time.time_interval + assert_equal "2 years 00:03:00", @first_time.time_interval end def test_update_oid @@ -62,7 +61,7 @@ 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, 100_000) assert_raise ActiveRecord::ActiveRecordError do @connection.type_to_sql :text, 4294967295 end @@ -77,15 +76,15 @@ class PostgresqlInternalDataTypeTest < ActiveRecord::PostgreSQLTestCase end def test_name_column_type - with_example_table @connection, 'ex', 'data name' do - column = @connection.columns('ex').find { |col| col.name == 'data' } + with_example_table @connection, "ex", "data name" do + column = @connection.columns("ex").find { |col| col.name == "data" } assert_equal :string, column.type end end def test_char_column_type - with_example_table @connection, 'ex', 'data "char"' do - column = @connection.columns('ex').find { |col| col.name == 'data' } + with_example_table @connection, "ex", 'data "char"' do + column = @connection.columns("ex").find { |col| col.name == "data" } assert_equal :string, column.type end end diff --git a/activerecord/test/cases/adapters/postgresql/domain_test.rb b/activerecord/test/cases/adapters/postgresql/domain_test.rb index 6102ddacd1..f1eb8adb15 100644 --- a/activerecord/test/cases/adapters/postgresql/domain_test.rb +++ b/activerecord/test/cases/adapters/postgresql/domain_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'support/connection_helper' +require "support/connection_helper" class PostgresqlDomainTest < ActiveRecord::PostgreSQLTestCase include ConnectionHelper @@ -12,15 +12,15 @@ class PostgresqlDomainTest < ActiveRecord::PostgreSQLTestCase @connection = ActiveRecord::Base.connection @connection.transaction do @connection.execute "CREATE DOMAIN custom_money as numeric(8,2)" - @connection.create_table('postgresql_domains') do |t| + @connection.create_table("postgresql_domains") do |t| t.column :price, :custom_money end end end teardown do - @connection.drop_table 'postgresql_domains', if_exists: true - @connection.execute 'DROP DOMAIN IF EXISTS custom_money' + @connection.drop_table "postgresql_domains", if_exists: true + @connection.execute "DROP DOMAIN IF EXISTS custom_money" reset_connection end diff --git a/activerecord/test/cases/adapters/postgresql/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb index 6816a6514b..5e5a3158ba 100644 --- a/activerecord/test/cases/adapters/postgresql/enum_test.rb +++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'support/connection_helper' +require "support/connection_helper" class PostgresqlEnumTest < ActiveRecord::PostgreSQLTestCase include ConnectionHelper @@ -14,15 +14,15 @@ class PostgresqlEnumTest < ActiveRecord::PostgreSQLTestCase @connection.execute <<-SQL CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy'); SQL - @connection.create_table('postgresql_enums') do |t| + @connection.create_table("postgresql_enums") do |t| t.column :current_mood, :mood end end end teardown do - @connection.drop_table 'postgresql_enums', if_exists: true - @connection.execute 'DROP TYPE IF EXISTS mood' + @connection.drop_table "postgresql_enums", if_exists: true + @connection.execute "DROP TYPE IF EXISTS mood" reset_connection end @@ -37,10 +37,10 @@ class PostgresqlEnumTest < ActiveRecord::PostgreSQLTestCase end def test_enum_defaults - @connection.add_column 'postgresql_enums', 'good_mood', :mood, default: 'happy' + @connection.add_column "postgresql_enums", "good_mood", :mood, default: "happy" PostgresqlEnum.reset_column_information - assert_equal "happy", PostgresqlEnum.column_defaults['good_mood'] + assert_equal "happy", PostgresqlEnum.column_defaults["good_mood"] assert_equal "happy", PostgresqlEnum.new.good_mood ensure PostgresqlEnum.reset_column_information diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb index 4d0fd640aa..7493bce4fb 100644 --- a/activerecord/test/cases/adapters/postgresql/explain_test.rb +++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb @@ -1,20 +1,20 @@ require "cases/helper" -require 'models/developer' -require 'models/computer' +require "models/developer" +require "models/computer" class PostgreSQLExplainTest < ActiveRecord::PostgreSQLTestCase fixtures :developers def test_explain_for_one_query - explain = Developer.where(:id => 1).explain - assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = $1), explain + explain = Developer.where(id: 1).explain + assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\$1 \[\["id", 1\]\]|1)), explain assert_match %(QUERY PLAN), explain end def test_explain_with_eager_loading - explain = Developer.where(:id => 1).includes(:audit_logs).explain + explain = Developer.where(id: 1).includes(:audit_logs).explain assert_match %(QUERY PLAN), explain - assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = $1), explain + assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\$1 \[\["id", 1\]\]|1)), explain assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain end end diff --git a/activerecord/test/cases/adapters/postgresql/full_text_test.rb b/activerecord/test/cases/adapters/postgresql/full_text_test.rb index bde7513339..5ddfe32007 100644 --- a/activerecord/test/cases/adapters/postgresql/full_text_test.rb +++ b/activerecord/test/cases/adapters/postgresql/full_text_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'support/schema_dumping_helper' +require "support/schema_dumping_helper" class PostgresqlFullTextTest < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper @@ -7,13 +7,13 @@ class PostgresqlFullTextTest < ActiveRecord::PostgreSQLTestCase setup do @connection = ActiveRecord::Base.connection - @connection.create_table('tsvectors') do |t| - t.tsvector 'text_vector' + @connection.create_table("tsvectors") do |t| + t.tsvector "text_vector" end end teardown do - @connection.drop_table 'tsvectors', if_exists: true + @connection.drop_table "tsvectors", if_exists: true end def test_tsvector_column diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb index 9e250c2b7c..a65d4d1ad9 100644 --- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb +++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb @@ -1,6 +1,6 @@ require "cases/helper" -require 'support/connection_helper' -require 'support/schema_dumping_helper' +require "support/connection_helper" +require "support/schema_dumping_helper" class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase include ConnectionHelper @@ -18,7 +18,7 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase def setup @connection = ActiveRecord::Base.connection - @connection.create_table('postgresql_points') do |t| + @connection.create_table("postgresql_points") do |t| t.point :x t.point :y, default: [12.2, 13.3] t.point :z, default: "(14.4,15.5)" @@ -27,22 +27,10 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase t.point :legacy_y, default: [12.2, 13.3] t.point :legacy_z, default: "(14.4,15.5)" end - @connection.create_table('deprecated_points') do |t| - t.point :x - end end teardown do - @connection.drop_table 'postgresql_points', if_exists: true - @connection.drop_table 'deprecated_points', if_exists: true - end - - class DeprecatedPoint < ActiveRecord::Base; end - - def test_deprecated_legacy_type - assert_deprecated do - DeprecatedPoint.new - end + @connection.drop_table "postgresql_points", if_exists: true end def test_column @@ -56,10 +44,10 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase end def test_default - assert_equal ActiveRecord::Point.new(12.2, 13.3), PostgresqlPoint.column_defaults['y'] + assert_equal ActiveRecord::Point.new(12.2, 13.3), PostgresqlPoint.column_defaults["y"] assert_equal ActiveRecord::Point.new(12.2, 13.3), PostgresqlPoint.new.y - assert_equal ActiveRecord::Point.new(14.4, 15.5), PostgresqlPoint.column_defaults['z'] + assert_equal ActiveRecord::Point.new(14.4, 15.5), PostgresqlPoint.column_defaults["z"] assert_equal ActiveRecord::Point.new(14.4, 15.5), PostgresqlPoint.new.z end @@ -104,6 +92,13 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase assert_equal ActiveRecord::Point.new(1, 2), p.x end + def test_empty_string_assignment + assert_nothing_raised { PostgresqlPoint.new(x: "") } + + p = PostgresqlPoint.new(x: "") + assert_equal nil, p.x + end + def test_array_of_points_round_trip expected_value = [ ActiveRecord::Point.new(1, 2), @@ -129,10 +124,10 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase end def test_legacy_default - assert_equal [12.2, 13.3], PostgresqlPoint.column_defaults['legacy_y'] + assert_equal [12.2, 13.3], PostgresqlPoint.column_defaults["legacy_y"] assert_equal [12.2, 13.3], PostgresqlPoint.new.legacy_y - assert_equal [14.4, 15.5], PostgresqlPoint.column_defaults['legacy_z'] + assert_equal [14.4, 15.5], PostgresqlPoint.column_defaults["legacy_z"] assert_equal [14.4, 15.5], PostgresqlPoint.new.legacy_z end @@ -183,51 +178,51 @@ class PostgresqlGeometricTest < ActiveRecord::PostgreSQLTestCase end teardown do - @connection.drop_table 'postgresql_geometrics', if_exists: true + @connection.drop_table "postgresql_geometrics", if_exists: true end def test_geometric_types g = PostgresqlGeometric.new( - :a_line_segment => '(2.0, 3), (5.5, 7.0)', - :a_box => '2.0, 3, 5.5, 7.0', - :a_path => '[(2.0, 3), (5.5, 7.0), (8.5, 11.0)]', - :a_polygon => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))', - :a_circle => '<(5.3, 10.4), 2>' + a_line_segment: "(2.0, 3), (5.5, 7.0)", + a_box: "2.0, 3, 5.5, 7.0", + a_path: "[(2.0, 3), (5.5, 7.0), (8.5, 11.0)]", + a_polygon: "((2.0, 3), (5.5, 7.0), (8.5, 11.0))", + a_circle: "<(5.3, 10.4), 2>" ) g.save! h = PostgresqlGeometric.find(g.id) - assert_equal '[(2,3),(5.5,7)]', h.a_line_segment - assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner - assert_equal '[(2,3),(5.5,7),(8.5,11)]', h.a_path - assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon - assert_equal '<(5.3,10.4),2>', h.a_circle + assert_equal "[(2,3),(5.5,7)]", h.a_line_segment + assert_equal "(5.5,7),(2,3)", h.a_box # reordered to store upper right corner then bottom left corner + assert_equal "[(2,3),(5.5,7),(8.5,11)]", h.a_path + assert_equal "((2,3),(5.5,7),(8.5,11))", h.a_polygon + assert_equal "<(5.3,10.4),2>", h.a_circle end def test_alternative_format g = PostgresqlGeometric.new( - :a_line_segment => '((2.0, 3), (5.5, 7.0))', - :a_box => '(2.0, 3), (5.5, 7.0)', - :a_path => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))', - :a_polygon => '2.0, 3, 5.5, 7.0, 8.5, 11.0', - :a_circle => '((5.3, 10.4), 2)' + a_line_segment: "((2.0, 3), (5.5, 7.0))", + a_box: "(2.0, 3), (5.5, 7.0)", + a_path: "((2.0, 3), (5.5, 7.0), (8.5, 11.0))", + a_polygon: "2.0, 3, 5.5, 7.0, 8.5, 11.0", + a_circle: "((5.3, 10.4), 2)" ) g.save! h = PostgresqlGeometric.find(g.id) - assert_equal '[(2,3),(5.5,7)]', h.a_line_segment - assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner - assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path - assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon - assert_equal '<(5.3,10.4),2>', h.a_circle + assert_equal "[(2,3),(5.5,7)]", h.a_line_segment + assert_equal "(5.5,7),(2,3)", h.a_box # reordered to store upper right corner then bottom left corner + assert_equal "((2,3),(5.5,7),(8.5,11))", h.a_path + assert_equal "((2,3),(5.5,7),(8.5,11))", h.a_polygon + assert_equal "<(5.3,10.4),2>", h.a_circle end def test_geometric_function - PostgresqlGeometric.create! a_path: '[(2.0, 3), (5.5, 7.0), (8.5, 11.0)]' # [ ] is an open path - PostgresqlGeometric.create! a_path: '((2.0, 3), (5.5, 7.0), (8.5, 11.0))' # ( ) is a closed path + PostgresqlGeometric.create! a_path: "[(2.0, 3), (5.5, 7.0), (8.5, 11.0)]" # [ ] is an open path + PostgresqlGeometric.create! a_path: "((2.0, 3), (5.5, 7.0), (8.5, 11.0))" # ( ) is a closed path objs = PostgresqlGeometric.find_by_sql "SELECT isopen(a_path) FROM postgresql_geometrics ORDER BY id ASC" assert_equal [true, false], objs.map(&:isopen) @@ -263,28 +258,28 @@ class PostgreSQLGeometricLineTest < ActiveRecord::PostgreSQLTestCase teardown do if defined?(@connection) - @connection.drop_table 'postgresql_lines', if_exists: true + @connection.drop_table "postgresql_lines", if_exists: true end end def test_geometric_line_type g = PostgresqlLine.new( - a_line: '{2.0, 3, 5.5}' + a_line: "{2.0, 3, 5.5}" ) g.save! h = PostgresqlLine.find(g.id) - assert_equal '{2,3,5.5}', h.a_line + assert_equal "{2,3,5.5}", h.a_line end def test_alternative_format_line_type g = PostgresqlLine.new( - a_line: '(2.0, 3), (4.0, 6.0)' + a_line: "(2.0, 3), (4.0, 6.0)" ) g.save! h = PostgresqlLine.find(g.id) - assert_equal '{1.5,-1,0}', h.a_line + assert_equal "{1.5,-1,0}", h.a_line end def test_schema_dumping_for_line_type @@ -367,12 +362,12 @@ class PostgreSQLGeometricTypesTest < ActiveRecord::PostgreSQLTestCase private - def assert_column_exists(column_name) - assert connection.column_exists?(table_name, column_name) - end + def assert_column_exists(column_name) + assert connection.column_exists?(table_name, column_name) + end - def assert_type_correct(column_name, type) - column = connection.columns(table_name).find { |c| c.name == column_name.to_s } - assert_equal type, column.type - end + def assert_type_correct(column_name, type) + column = connection.columns(table_name).find { |c| c.name == column_name.to_s } + assert_equal type, column.type + end end diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index 27cc65a643..9236a67b11 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -1,11 +1,11 @@ require "cases/helper" -require 'support/schema_dumping_helper' +require "support/schema_dumping_helper" if ActiveRecord::Base.connection.supports_extensions? class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper class Hstore < ActiveRecord::Base - self.table_name = 'hstores' + self.table_name = "hstores" store_accessor :settings, :language, :timezone end @@ -13,40 +13,40 @@ if ActiveRecord::Base.connection.supports_extensions? def setup @connection = ActiveRecord::Base.connection - unless @connection.extension_enabled?('hstore') - @connection.enable_extension 'hstore' + unless @connection.extension_enabled?("hstore") + @connection.enable_extension "hstore" @connection.commit_db_transaction end @connection.reconnect! @connection.transaction do - @connection.create_table('hstores') do |t| - t.hstore 'tags', :default => '' - t.hstore 'payload', array: true - t.hstore 'settings' + @connection.create_table("hstores") do |t| + t.hstore "tags", default: "" + t.hstore "payload", array: true + t.hstore "settings" end end Hstore.reset_column_information - @column = Hstore.columns_hash['tags'] + @column = Hstore.columns_hash["tags"] @type = Hstore.type_for_attribute("tags") end teardown do - @connection.drop_table 'hstores', if_exists: true + @connection.drop_table "hstores", if_exists: true end def test_hstore_included_in_extensions assert @connection.respond_to?(:extensions), "connection should have a list of extensions" - assert @connection.extensions.include?('hstore'), "extension list should include hstore" + assert_includes @connection.extensions, "hstore", "extension list should include hstore" end def test_disable_enable_hstore - assert @connection.extension_enabled?('hstore') - @connection.disable_extension 'hstore' - assert_not @connection.extension_enabled?('hstore') - @connection.enable_extension 'hstore' - assert @connection.extension_enabled?('hstore') + assert @connection.extension_enabled?("hstore") + @connection.disable_extension "hstore" + assert_not @connection.extension_enabled?("hstore") + @connection.enable_extension "hstore" + assert @connection.extension_enabled?("hstore") ensure # Restore column(s) dropped by `drop extension hstore cascade;` load_schema @@ -61,22 +61,22 @@ if ActiveRecord::Base.connection.supports_extensions? end def test_default - @connection.add_column 'hstores', 'permissions', :hstore, default: '"users"=>"read", "articles"=>"write"' + @connection.add_column "hstores", "permissions", :hstore, default: '"users"=>"read", "articles"=>"write"' Hstore.reset_column_information - assert_equal({"users"=>"read", "articles"=>"write"}, Hstore.column_defaults['permissions']) - assert_equal({"users"=>"read", "articles"=>"write"}, Hstore.new.permissions) + assert_equal({ "users"=>"read", "articles"=>"write" }, Hstore.column_defaults["permissions"]) + assert_equal({ "users"=>"read", "articles"=>"write" }, Hstore.new.permissions) ensure Hstore.reset_column_information end def test_change_table_supports_hstore @connection.transaction do - @connection.change_table('hstores') do |t| - t.hstore 'users', default: '' + @connection.change_table("hstores") do |t| + t.hstore "users", default: "" end Hstore.reset_column_information - column = Hstore.columns_hash['users'] + column = Hstore.columns_hash["users"] assert_equal :hstore, column.type raise ActiveRecord::Rollback # reset the schema change @@ -103,18 +103,18 @@ if ActiveRecord::Base.connection.supports_extensions? end def test_cast_value_on_write - x = Hstore.new tags: {"bool" => true, "number" => 5} - assert_equal({"bool" => true, "number" => 5}, x.tags_before_type_cast) - assert_equal({"bool" => "true", "number" => "5"}, x.tags) + x = Hstore.new tags: { "bool" => true, "number" => 5 } + assert_equal({ "bool" => true, "number" => 5 }, x.tags_before_type_cast) + assert_equal({ "bool" => "true", "number" => "5" }, x.tags) x.save - assert_equal({"bool" => "true", "number" => "5"}, x.reload.tags) + assert_equal({ "bool" => "true", "number" => "5" }, x.reload.tags) end def test_type_cast_hstore - assert_equal({'1' => '2'}, @type.deserialize("\"1\"=>\"2\"")) + assert_equal({ "1" => "2" }, @type.deserialize("\"1\"=>\"2\"")) assert_equal({}, @type.deserialize("")) - assert_equal({'key'=>nil}, @type.deserialize('key => NULL')) - assert_equal({'c'=>'}','"a"'=>'b "a b'}, @type.deserialize(%q(c=>"}", "\"a\""=>"b \"a b"))) + assert_equal({ "key"=>nil }, @type.deserialize("key => NULL")) + assert_equal({ "c"=>"}",'"a"'=>'b "a b' }, @type.deserialize(%q(c=>"}", "\"a\""=>"b \"a b"))) end def test_with_store_accessors @@ -156,132 +156,132 @@ if ActiveRecord::Base.connection.supports_extensions? end def test_changes_in_place - hstore = Hstore.create!(settings: { 'one' => 'two' }) - hstore.settings['three'] = 'four' + hstore = Hstore.create!(settings: { "one" => "two" }) + hstore.settings["three"] = "four" hstore.save! hstore.reload - assert_equal 'four', hstore.settings['three'] + assert_equal "four", hstore.settings["three"] assert_not hstore.changed? end def test_gen1 - assert_equal(%q(" "=>""), @type.serialize({' '=>''})) + assert_equal('" "=>""', @type.serialize(" "=>"")) end def test_gen2 - assert_equal(%q(","=>""), @type.serialize({','=>''})) + assert_equal('","=>""', @type.serialize(","=>"")) end def test_gen3 - assert_equal(%q("="=>""), @type.serialize({'='=>''})) + assert_equal('"="=>""', @type.serialize("="=>"")) end def test_gen4 - assert_equal(%q(">"=>""), @type.serialize({'>'=>''})) + assert_equal('">"=>""', @type.serialize(">"=>"")) end def test_parse1 - assert_equal({'a'=>nil,'b'=>nil,'c'=>'NuLl','null'=>'c'}, @type.deserialize('a=>null,b=>NuLl,c=>"NuLl",null=>c')) + assert_equal({ "a"=>nil,"b"=>nil,"c"=>"NuLl","null"=>"c" }, @type.deserialize('a=>null,b=>NuLl,c=>"NuLl",null=>c')) end def test_parse2 - assert_equal({" " => " "}, @type.deserialize("\\ =>\\ ")) + assert_equal({ " " => " " }, @type.deserialize("\\ =>\\ ")) end def test_parse3 - assert_equal({"=" => ">"}, @type.deserialize("==>>")) + assert_equal({ "=" => ">" }, @type.deserialize("==>>")) end def test_parse4 - assert_equal({"=a"=>"q=w"}, @type.deserialize('\=a=>q=w')) + assert_equal({ "=a"=>"q=w" }, @type.deserialize('\=a=>q=w')) end def test_parse5 - assert_equal({"=a"=>"q=w"}, @type.deserialize('"=a"=>q\=w')) + assert_equal({ "=a"=>"q=w" }, @type.deserialize('"=a"=>q\=w')) end def test_parse6 - assert_equal({"\"a"=>"q>w"}, @type.deserialize('"\"a"=>q>w')) + assert_equal({ "\"a"=>"q>w" }, @type.deserialize('"\"a"=>q>w')) end def test_parse7 - assert_equal({"\"a"=>"q\"w"}, @type.deserialize('\"a=>q"w')) + assert_equal({ "\"a"=>"q\"w" }, @type.deserialize('\"a=>q"w')) end def test_rewrite @connection.execute "insert into hstores (tags) VALUES ('1=>2')" x = Hstore.first - x.tags = { '"a\'' => 'b' } + x.tags = { '"a\'' => "b" } assert x.save! end def test_select @connection.execute "insert into hstores (tags) VALUES ('1=>2')" x = Hstore.first - assert_equal({'1' => '2'}, x.tags) + assert_equal({ "1" => "2" }, x.tags) end def test_array_cycle - assert_array_cycle([{"AA" => "BB", "CC" => "DD"}, {"AA" => nil}]) + assert_array_cycle([{ "AA" => "BB", "CC" => "DD" }, { "AA" => nil }]) end def test_array_strings_with_quotes - assert_array_cycle([{'this has' => 'some "s that need to be escaped"'}]) + assert_array_cycle([{ "this has" => 'some "s that need to be escaped"' }]) end def test_array_strings_with_commas - assert_array_cycle([{'this,has' => 'many,values'}]) + assert_array_cycle([{ "this,has" => "many,values" }]) end def test_array_strings_with_array_delimiters - assert_array_cycle(['{' => '}']) + assert_array_cycle(["{" => "}"]) end def test_array_strings_with_null_strings - assert_array_cycle([{'NULL' => 'NULL'}]) + assert_array_cycle([{ "NULL" => "NULL" }]) end def test_contains_nils - assert_array_cycle([{'NULL' => nil}]) + assert_array_cycle([{ "NULL" => nil }]) end def test_select_multikey @connection.execute "insert into hstores (tags) VALUES ('1=>2,2=>3')" x = Hstore.first - assert_equal({'1' => '2', '2' => '3'}, x.tags) + assert_equal({ "1" => "2", "2" => "3" }, x.tags) end def test_create - assert_cycle('a' => 'b', '1' => '2') + assert_cycle("a" => "b", "1" => "2") end def test_nil - assert_cycle('a' => nil) + assert_cycle("a" => nil) end def test_quotes - assert_cycle('a' => 'b"ar', '1"foo' => '2') + assert_cycle("a" => 'b"ar', '1"foo' => "2") end def test_whitespace - assert_cycle('a b' => 'b ar', '1"foo' => '2') + assert_cycle("a b" => "b ar", '1"foo' => "2") end def test_backslash - assert_cycle('a\\b' => 'b\\ar', '1"foo' => '2') + assert_cycle('a\\b' => 'b\\ar', '1"foo' => "2") end def test_comma - assert_cycle('a, b' => 'bar', '1"foo' => '2') + assert_cycle("a, b" => "bar", '1"foo' => "2") end def test_arrow - assert_cycle('a=>b' => 'bar', '1"foo' => '2') + assert_cycle("a=>b" => "bar", '1"foo' => "2") end def test_quoting_special_characters - assert_cycle('ca' => 'cà ', 'ac' => 'à c') + assert_cycle("ca" => "cà ", "ac" => "à c") end def test_multiline @@ -300,20 +300,20 @@ if ActiveRecord::Base.connection.supports_extensions? end def test_hstore_with_serialized_attributes - HstoreWithSerialize.create! tags: TagCollection.new({"one" => "two"}) + HstoreWithSerialize.create! tags: TagCollection.new("one" => "two") record = HstoreWithSerialize.first assert_instance_of TagCollection, record.tags - assert_equal({"one" => "two"}, record.tags.to_hash) + assert_equal({ "one" => "two" }, record.tags.to_hash) record.tags = TagCollection.new("three" => "four") record.save! - assert_equal({"three" => "four"}, HstoreWithSerialize.first.tags.to_hash) + assert_equal({ "three" => "four" }, HstoreWithSerialize.first.tags.to_hash) end def test_clone_hstore_with_serialized_attributes - HstoreWithSerialize.create! tags: TagCollection.new({"one" => "two"}) + HstoreWithSerialize.create! tags: TagCollection.new("one" => "two") record = HstoreWithSerialize.first dupe = record.dup - assert_equal({"one" => "two"}, dupe.tags.to_hash) + assert_equal({ "one" => "two" }, dupe.tags.to_hash) end def test_schema_dump_with_shorthand @@ -322,32 +322,32 @@ if ActiveRecord::Base.connection.supports_extensions? end private - def assert_array_cycle(array) - # test creation - x = Hstore.create!(payload: array) - x.reload - assert_equal(array, x.payload) - - # test updating - x = Hstore.create!(payload: []) - x.payload = array - x.save! - x.reload - assert_equal(array, x.payload) - end - - def assert_cycle(hash) - # test creation - x = Hstore.create!(:tags => hash) - x.reload - assert_equal(hash, x.tags) + def assert_array_cycle(array) + # test creation + x = Hstore.create!(payload: array) + x.reload + assert_equal(array, x.payload) + + # test updating + x = Hstore.create!(payload: []) + x.payload = array + x.save! + x.reload + assert_equal(array, x.payload) + end - # test updating - x = Hstore.create!(:tags => {}) - x.tags = hash - x.save! - x.reload - assert_equal(hash, x.tags) - end + def assert_cycle(hash) + # test creation + x = Hstore.create!(tags: hash) + x.reload + assert_equal(hash, x.tags) + + # test updating + x = Hstore.create!(tags: {}) + x.tags = hash + x.save! + x.reload + assert_equal(hash, x.tags) + end end end diff --git a/activerecord/test/cases/adapters/postgresql/infinity_test.rb b/activerecord/test/cases/adapters/postgresql/infinity_test.rb index bfda933fa4..19b00258b6 100644 --- a/activerecord/test/cases/adapters/postgresql/infinity_test.rb +++ b/activerecord/test/cases/adapters/postgresql/infinity_test.rb @@ -15,7 +15,7 @@ class PostgresqlInfinityTest < ActiveRecord::PostgreSQLTestCase end teardown do - @connection.drop_table 'postgresql_infinities', if_exists: true + @connection.drop_table "postgresql_infinities", if_exists: true end test "type casting infinity on a float column" do @@ -25,11 +25,11 @@ class PostgresqlInfinityTest < ActiveRecord::PostgreSQLTestCase end test "type casting string on a float column" do - record = PostgresqlInfinity.new(float: 'Infinity') + record = PostgresqlInfinity.new(float: "Infinity") assert_equal Float::INFINITY, record.float - record = PostgresqlInfinity.new(float: '-Infinity') + record = PostgresqlInfinity.new(float: "-Infinity") assert_equal(-Float::INFINITY, record.float) - record = PostgresqlInfinity.new(float: 'NaN') + record = PostgresqlInfinity.new(float: "NaN") assert_send [record.float, :nan?] end diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index b3b121b4fb..273b2c1c7b 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -1,11 +1,11 @@ require "cases/helper" -require 'support/schema_dumping_helper' +require "support/schema_dumping_helper" module PostgresqlJSONSharedTestCases include SchemaDumpingHelper class JsonDataType < ActiveRecord::Base - self.table_name = 'json_data_type' + self.table_name = "json_data_type" store_accessor :settings, :resolution end @@ -13,9 +13,9 @@ module PostgresqlJSONSharedTestCases def setup @connection = ActiveRecord::Base.connection begin - @connection.create_table('json_data_type') do |t| - t.public_send column_type, 'payload', default: {} # t.json 'payload', default: {} - t.public_send column_type, 'settings' # t.json 'settings' + @connection.create_table("json_data_type") do |t| + t.public_send column_type, "payload", default: {} # t.json 'payload', default: {} + t.public_send column_type, "settings" # t.json 'settings' end rescue ActiveRecord::StatementInvalid skip "do not test on PostgreSQL without #{column_type} type." @@ -38,22 +38,22 @@ module PostgresqlJSONSharedTestCases end def test_default - @connection.add_column 'json_data_type', 'permissions', column_type, default: '{"users": "read", "posts": ["read", "write"]}' + @connection.add_column "json_data_type", "permissions", column_type, default: { "users": "read", "posts": ["read", "write"] } JsonDataType.reset_column_information - assert_equal({"users"=>"read", "posts"=>["read", "write"]}, JsonDataType.column_defaults['permissions']) - assert_equal({"users"=>"read", "posts"=>["read", "write"]}, JsonDataType.new.permissions) + assert_equal({ "users"=>"read", "posts"=>["read", "write"] }, JsonDataType.column_defaults["permissions"]) + assert_equal({ "users"=>"read", "posts"=>["read", "write"] }, JsonDataType.new.permissions) ensure JsonDataType.reset_column_information end def test_change_table_supports_json @connection.transaction do - @connection.change_table('json_data_type') do |t| - t.public_send column_type, 'users', default: '{}' # t.json 'users', default: '{}' + @connection.change_table("json_data_type") do |t| + t.public_send column_type, "users", default: "{}" # t.json 'users', default: '{}' end JsonDataType.reset_column_information - column = JsonDataType.columns_hash['users'] + column = JsonDataType.columns_hash["users"] assert_equal column_type, column.type raise ActiveRecord::Rollback # reset the schema change @@ -68,11 +68,11 @@ module PostgresqlJSONSharedTestCases end def test_cast_value_on_write - x = JsonDataType.new payload: {"string" => "foo", :symbol => :bar} - assert_equal({"string" => "foo", :symbol => :bar}, x.payload_before_type_cast) - assert_equal({"string" => "foo", "symbol" => "bar"}, x.payload) + x = JsonDataType.new payload: { "string" => "foo", :symbol => :bar } + assert_equal({ "string" => "foo", :symbol => :bar }, x.payload_before_type_cast) + assert_equal({ "string" => "foo", "symbol" => "bar" }, x.payload) x.save - assert_equal({"string" => "foo", "symbol" => "bar"}, x.reload.payload) + assert_equal({ "string" => "foo", "symbol" => "bar" }, x.reload.payload) end def test_type_cast_json @@ -80,49 +80,65 @@ module PostgresqlJSONSharedTestCases data = "{\"a_key\":\"a_value\"}" hash = type.deserialize(data) - assert_equal({'a_key' => 'a_value'}, hash) - assert_equal({'a_key' => 'a_value'}, type.deserialize(data)) + assert_equal({ "a_key" => "a_value" }, hash) + assert_equal({ "a_key" => "a_value" }, type.deserialize(data)) assert_equal({}, type.deserialize("{}")) - assert_equal({'key'=>nil}, type.deserialize('{"key": null}')) - assert_equal({'c'=>'}','"a"'=>'b "a b'}, type.deserialize(%q({"c":"}", "\"a\"":"b \"a b"}))) + assert_equal({ "key"=>nil }, type.deserialize('{"key": null}')) + assert_equal({ "c"=>"}",'"a"'=>'b "a b' }, type.deserialize(%q({"c":"}", "\"a\"":"b \"a b"}))) end def test_rewrite @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')" x = JsonDataType.first - x.payload = { '"a\'' => 'b' } + x.payload = { '"a\'' => "b" } assert x.save! end def test_select @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')" x = JsonDataType.first - assert_equal({'k' => 'v'}, x.payload) + assert_equal({ "k" => "v" }, x.payload) end def test_select_multikey @connection.execute %q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')| x = JsonDataType.first - assert_equal({'k1' => 'v1', 'k2' => 'v2', 'k3' => [1,2,3]}, x.payload) + assert_equal({ "k1" => "v1", "k2" => "v2", "k3" => [1,2,3] }, x.payload) end def test_null_json - @connection.execute %q|insert into json_data_type (payload) VALUES(null)| + @connection.execute "insert into json_data_type (payload) VALUES(null)" x = JsonDataType.first assert_equal(nil, x.payload) end + def test_select_nil_json_after_create + json = JsonDataType.create(payload: nil) + x = JsonDataType.where(payload:nil).first + assert_equal(json, x) + end + + def test_select_nil_json_after_update + json = JsonDataType.create(payload: "foo") + x = JsonDataType.where(payload:nil).first + assert_equal(nil, x) + + json.update_attributes payload: nil + x = JsonDataType.where(payload:nil).first + assert_equal(json.reload, x) + end + def test_select_array_json_value @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')| x = JsonDataType.first - assert_equal(['v0', {'k1' => 'v1'}], x.payload) + assert_equal(["v0", { "k1" => "v1" }], x.payload) end def test_rewrite_array_json_value @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')| x = JsonDataType.first - x.payload = ['v1', {'k2' => 'v2'}, 'v3'] + x.payload = ["v1", { "k2" => "v2" }, "v3"] assert x.save! end @@ -161,29 +177,36 @@ module PostgresqlJSONSharedTestCases json = JsonDataType.new assert_not json.changed? - json.payload = { 'one' => 'two' } + json.payload = { "one" => "two" } assert json.changed? assert json.payload_changed? json.save! assert_not json.changed? - json.payload['three'] = 'four' + json.payload["three"] = "four" assert json.payload_changed? json.save! json.reload - assert_equal({ 'one' => 'two', 'three' => 'four' }, json.payload) + assert_equal({ "one" => "two", "three" => "four" }, json.payload) assert_not json.changed? end - def test_assigning_invalid_json - json = JsonDataType.new + def test_assigning_string_literal + json = JsonDataType.create(payload: "foo") + assert_equal "foo", json.payload + end - json.payload = 'foo' + def test_assigning_number + json = JsonDataType.create(payload: 1.234) + assert_equal 1.234, json.payload + end - assert_nil json.payload + def test_assigning_boolean + json = JsonDataType.create(payload: true) + assert_equal true, json.payload end end diff --git a/activerecord/test/cases/adapters/postgresql/ltree_test.rb b/activerecord/test/cases/adapters/postgresql/ltree_test.rb index 56516c82b4..2b5ac1cac6 100644 --- a/activerecord/test/cases/adapters/postgresql/ltree_test.rb +++ b/activerecord/test/cases/adapters/postgresql/ltree_test.rb @@ -1,20 +1,20 @@ require "cases/helper" -require 'support/schema_dumping_helper' +require "support/schema_dumping_helper" class PostgresqlLtreeTest < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper class Ltree < ActiveRecord::Base - self.table_name = 'ltrees' + self.table_name = "ltrees" end def setup @connection = ActiveRecord::Base.connection - enable_extension!('ltree', @connection) + enable_extension!("ltree", @connection) @connection.transaction do - @connection.create_table('ltrees') do |t| - t.ltree 'path' + @connection.create_table("ltrees") do |t| + t.ltree "path" end end rescue ActiveRecord::StatementInvalid @@ -22,28 +22,28 @@ class PostgresqlLtreeTest < ActiveRecord::PostgreSQLTestCase end teardown do - @connection.drop_table 'ltrees', if_exists: true + @connection.drop_table "ltrees", if_exists: true end def test_column - column = Ltree.columns_hash['path'] + column = Ltree.columns_hash["path"] assert_equal :ltree, column.type assert_equal "ltree", column.sql_type assert_not column.array? - type = Ltree.type_for_attribute('path') + type = Ltree.type_for_attribute("path") assert_not type.binary? end def test_write - ltree = Ltree.new(path: '1.2.3.4') + ltree = Ltree.new(path: "1.2.3.4") assert ltree.save! end def test_select @connection.execute "insert into ltrees (path) VALUES ('1.2.3')" ltree = Ltree.first - assert_equal '1.2.3', ltree.path + assert_equal "1.2.3", ltree.path end def test_schema_dump_with_shorthand diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb index c031178479..1b5d8362af 100644 --- a/activerecord/test/cases/adapters/postgresql/money_test.rb +++ b/activerecord/test/cases/adapters/postgresql/money_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'support/schema_dumping_helper' +require "support/schema_dumping_helper" class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper @@ -9,14 +9,14 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase setup do @connection = ActiveRecord::Base.connection @connection.execute("set lc_monetary = 'C'") - @connection.create_table('postgresql_moneys', force: true) do |t| + @connection.create_table("postgresql_moneys", force: true) do |t| t.money "wealth" t.money "depth", default: "150.55" end end teardown do - @connection.drop_table 'postgresql_moneys', if_exists: true + @connection.drop_table "postgresql_moneys", if_exists: true end def test_column @@ -31,7 +31,7 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase end def test_default - assert_equal BigDecimal.new("150.55"), PostgresqlMoney.column_defaults['depth'] + assert_equal BigDecimal.new("150.55"), PostgresqlMoney.column_defaults["depth"] assert_equal BigDecimal.new("150.55"), PostgresqlMoney.new.depth end @@ -46,7 +46,7 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase end def test_money_type_cast - type = PostgresqlMoney.type_for_attribute('wealth') + type = PostgresqlMoney.type_for_attribute("wealth") assert_equal(12345678.12, type.cast("$12,345,678.12")) assert_equal(12345678.12, type.cast("$12.345.678,12")) assert_equal(-1.15, type.cast("-$1.15")) @@ -63,7 +63,7 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase money = PostgresqlMoney.create(wealth: "987.65") assert_equal 987.65, money.wealth - new_value = BigDecimal.new('123.45') + new_value = BigDecimal.new("123.45") money.wealth = new_value money.save! money.reload @@ -80,7 +80,7 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase def test_update_all_with_money_big_decimal money = PostgresqlMoney.create! - PostgresqlMoney.update_all(wealth: '123.45'.to_d) + PostgresqlMoney.update_all(wealth: "123.45".to_d) money.reload assert_equal 123.45, money.wealth diff --git a/activerecord/test/cases/adapters/postgresql/network_test.rb b/activerecord/test/cases/adapters/postgresql/network_test.rb index fe6ee4e2d9..a33b0ef8a7 100644 --- a/activerecord/test/cases/adapters/postgresql/network_test.rb +++ b/activerecord/test/cases/adapters/postgresql/network_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'support/schema_dumping_helper' +require "support/schema_dumping_helper" class PostgresqlNetworkTest < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper @@ -7,15 +7,15 @@ class PostgresqlNetworkTest < ActiveRecord::PostgreSQLTestCase setup do @connection = ActiveRecord::Base.connection - @connection.create_table('postgresql_network_addresses', force: true) do |t| - t.inet 'inet_address', default: "192.168.1.1" - t.cidr 'cidr_address', default: "192.168.1.0/24" - t.macaddr 'mac_address', default: "ff:ff:ff:ff:ff:ff" + @connection.create_table("postgresql_network_addresses", force: true) do |t| + t.inet "inet_address", default: "192.168.1.1" + t.cidr "cidr_address", default: "192.168.1.0/24" + t.macaddr "mac_address", default: "ff:ff:ff:ff:ff:ff" end end teardown do - @connection.drop_table 'postgresql_network_addresses', if_exists: true + @connection.drop_table "postgresql_network_addresses", if_exists: true end def test_cidr_column @@ -49,33 +49,33 @@ class PostgresqlNetworkTest < ActiveRecord::PostgreSQLTestCase end def test_network_types - PostgresqlNetworkAddress.create(cidr_address: '192.168.0.0/24', - inet_address: '172.16.1.254/32', - mac_address: '01:23:45:67:89:0a') + PostgresqlNetworkAddress.create(cidr_address: "192.168.0.0/24", + inet_address: "172.16.1.254/32", + mac_address: "01:23:45:67:89:0a") address = PostgresqlNetworkAddress.first - assert_equal IPAddr.new('192.168.0.0/24'), address.cidr_address - assert_equal IPAddr.new('172.16.1.254'), address.inet_address - assert_equal '01:23:45:67:89:0a', address.mac_address + assert_equal IPAddr.new("192.168.0.0/24"), address.cidr_address + assert_equal IPAddr.new("172.16.1.254"), address.inet_address + assert_equal "01:23:45:67:89:0a", address.mac_address - address.cidr_address = '10.1.2.3/32' - address.inet_address = '10.0.0.0/8' - address.mac_address = 'bc:de:f0:12:34:56' + address.cidr_address = "10.1.2.3/32" + address.inet_address = "10.0.0.0/8" + address.mac_address = "bc:de:f0:12:34:56" address.save! assert address.reload - assert_equal IPAddr.new('10.1.2.3/32'), address.cidr_address - assert_equal IPAddr.new('10.0.0.0/8'), address.inet_address - assert_equal 'bc:de:f0:12:34:56', address.mac_address + assert_equal IPAddr.new("10.1.2.3/32"), address.cidr_address + assert_equal IPAddr.new("10.0.0.0/8"), address.inet_address + assert_equal "bc:de:f0:12:34:56", address.mac_address end def test_invalid_network_address - invalid_address = PostgresqlNetworkAddress.new(cidr_address: 'invalid addr', - inet_address: 'invalid addr') + invalid_address = PostgresqlNetworkAddress.new(cidr_address: "invalid addr", + inet_address: "invalid addr") assert_nil invalid_address.cidr_address assert_nil invalid_address.inet_address - assert_equal 'invalid addr', invalid_address.cidr_address_before_type_cast - assert_equal 'invalid addr', invalid_address.inet_address_before_type_cast + assert_equal "invalid addr", invalid_address.cidr_address_before_type_cast + assert_equal "invalid addr", invalid_address.inet_address_before_type_cast assert invalid_address.save invalid_address.reload diff --git a/activerecord/test/cases/adapters/postgresql/numbers_test.rb b/activerecord/test/cases/adapters/postgresql/numbers_test.rb index ba7e7dc9a3..834354dcc9 100644 --- a/activerecord/test/cases/adapters/postgresql/numbers_test.rb +++ b/activerecord/test/cases/adapters/postgresql/numbers_test.rb @@ -5,14 +5,14 @@ class PostgresqlNumberTest < ActiveRecord::PostgreSQLTestCase setup do @connection = ActiveRecord::Base.connection - @connection.create_table('postgresql_numbers', force: true) do |t| - t.column 'single', 'REAL' - t.column 'double', 'DOUBLE PRECISION' + @connection.create_table("postgresql_numbers", force: true) do |t| + t.column "single", "REAL" + t.column "double", "DOUBLE PRECISION" end end teardown do - @connection.drop_table 'postgresql_numbers', if_exists: true + @connection.drop_table "postgresql_numbers", if_exists: true end def test_data_type diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 31e87722d9..e6af93a53e 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -1,10 +1,11 @@ require "cases/helper" -require 'support/ddl_helper' -require 'support/connection_helper' +require "support/ddl_helper" +require "support/connection_helper" module ActiveRecord module ConnectionAdapters class PostgreSQLAdapterTest < ActiveRecord::PostgreSQLTestCase + self.use_transactional_tests = false include DdlHelper include ConnectionHelper @@ -14,15 +15,15 @@ module ActiveRecord def test_bad_connection assert_raise ActiveRecord::NoDatabaseError do - configuration = ActiveRecord::Base.configurations['arunit'].merge(database: 'should_not_exist-cinco-dog-db') + configuration = ActiveRecord::Base.configurations["arunit"].merge(database: "should_not_exist-cinco-dog-db") connection = ActiveRecord::Base.postgresql_connection(configuration) - connection.exec_query('SELECT 1') + connection.exec_query("SELECT 1") end end def test_valid_column with_example_table do - column = @connection.columns('ex').find { |col| col.name == 'id' } + column = @connection.columns("ex").find { |col| col.name == "id" } assert @connection.valid_type?(column.type) end end @@ -33,188 +34,138 @@ module ActiveRecord def test_primary_key with_example_table do - assert_equal 'id', @connection.primary_key('ex') + assert_equal "id", @connection.primary_key("ex") end end def test_primary_key_works_tables_containing_capital_letters - assert_equal 'id', @connection.primary_key('CamelCase') + assert_equal "id", @connection.primary_key("CamelCase") end def test_non_standard_primary_key - with_example_table 'data character varying(255) primary key' do - assert_equal 'data', @connection.primary_key('ex') + with_example_table "data character varying(255) primary key" do + assert_equal "data", @connection.primary_key("ex") end end def test_primary_key_returns_nil_for_no_pk - with_example_table 'id integer' do - assert_nil @connection.primary_key('ex') + with_example_table "id integer" do + assert_nil @connection.primary_key("ex") end end def test_primary_key_raises_error_if_table_not_found assert_raises(ActiveRecord::StatementInvalid) do - @connection.primary_key('unobtainium') + @connection.primary_key("unobtainium") end end - def test_insert_sql_with_proprietary_returning_clause - with_example_table do - id = @connection.insert_sql("insert into ex (number) values(5150)", nil, "number") - assert_equal 5150, id - end - end - - def test_insert_sql_with_quoted_schema_and_table_name - with_example_table do - id = @connection.insert_sql('insert into "public"."ex" (number) values(5150)') - expect = @connection.query('select max(id) from ex').first.first - assert_equal expect, id - end - end - - def test_insert_sql_with_no_space_after_table_name - with_example_table do - id = @connection.insert_sql("insert into ex(number) values(5150)") - expect = @connection.query('select max(id) from ex').first.first - assert_equal expect, id - end - end - - def test_multiline_insert_sql - with_example_table do - id = @connection.insert_sql(<<-SQL) - insert into ex( - number) - values( - 5152 - ) - SQL - expect = @connection.query('select max(id) from ex').first.first - assert_equal expect, id - end - end - - def test_insert_sql_with_returning_disabled - connection = connection_without_insert_returning - id = connection.insert_sql("insert into postgresql_partitioned_table_parent (number) VALUES (1)") - expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first - assert_equal expect.to_i, id - end - def test_exec_insert_with_returning_disabled connection = connection_without_insert_returning - result = connection.exec_insert("insert into postgresql_partitioned_table_parent (number) VALUES (1)", nil, [], 'id', 'postgresql_partitioned_table_parent_id_seq') - expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first + result = connection.exec_insert("insert into postgresql_partitioned_table_parent (number) VALUES (1)", nil, [], "id", "postgresql_partitioned_table_parent_id_seq") + expect = connection.query("select max(id) from postgresql_partitioned_table_parent").first.first assert_equal expect.to_i, result.rows.first.first end def test_exec_insert_with_returning_disabled_and_no_sequence_name_given connection = connection_without_insert_returning - result = connection.exec_insert("insert into postgresql_partitioned_table_parent (number) VALUES (1)", nil, [], 'id') - expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first + result = connection.exec_insert("insert into postgresql_partitioned_table_parent (number) VALUES (1)", nil, [], "id") + expect = connection.query("select max(id) from postgresql_partitioned_table_parent").first.first assert_equal expect.to_i, result.rows.first.first end def test_exec_insert_default_values_with_returning_disabled_and_no_sequence_name_given connection = connection_without_insert_returning - result = connection.exec_insert("insert into postgresql_partitioned_table_parent DEFAULT VALUES", nil, [], 'id') - expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first + result = connection.exec_insert("insert into postgresql_partitioned_table_parent DEFAULT VALUES", nil, [], "id") + expect = connection.query("select max(id) from postgresql_partitioned_table_parent").first.first assert_equal expect.to_i, result.rows.first.first end def test_exec_insert_default_values_quoted_schema_with_returning_disabled_and_no_sequence_name_given connection = connection_without_insert_returning - result = connection.exec_insert('insert into "public"."postgresql_partitioned_table_parent" DEFAULT VALUES', nil, [], 'id') - expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first + result = connection.exec_insert('insert into "public"."postgresql_partitioned_table_parent" DEFAULT VALUES', nil, [], "id") + expect = connection.query("select max(id) from postgresql_partitioned_table_parent").first.first assert_equal expect.to_i, result.rows.first.first end - def test_sql_for_insert_with_returning_disabled - connection = connection_without_insert_returning - sql, binds = connection.sql_for_insert('sql', nil, nil, nil, 'binds') - assert_equal ['sql', 'binds'], [sql, binds] - end - def test_serial_sequence - assert_equal 'public.accounts_id_seq', - @connection.serial_sequence('accounts', 'id') + assert_equal "public.accounts_id_seq", + @connection.serial_sequence("accounts", "id") assert_raises(ActiveRecord::StatementInvalid) do - @connection.serial_sequence('zomg', 'id') + @connection.serial_sequence("zomg", "id") end end def test_default_sequence_name - assert_equal 'public.accounts_id_seq', - @connection.default_sequence_name('accounts', 'id') + assert_equal "public.accounts_id_seq", + @connection.default_sequence_name("accounts", "id") - assert_equal 'public.accounts_id_seq', - @connection.default_sequence_name('accounts') + assert_equal "public.accounts_id_seq", + @connection.default_sequence_name("accounts") end def test_default_sequence_name_bad_table - assert_equal 'zomg_id_seq', - @connection.default_sequence_name('zomg', 'id') + assert_equal "zomg_id_seq", + @connection.default_sequence_name("zomg", "id") - assert_equal 'zomg_id_seq', - @connection.default_sequence_name('zomg') + assert_equal "zomg_id_seq", + @connection.default_sequence_name("zomg") end def test_pk_and_sequence_for with_example_table do - pk, seq = @connection.pk_and_sequence_for('ex') - assert_equal 'id', pk - assert_equal @connection.default_sequence_name('ex', 'id'), seq.to_s + pk, seq = @connection.pk_and_sequence_for("ex") + assert_equal "id", pk + assert_equal @connection.default_sequence_name("ex", "id"), seq.to_s end end def test_pk_and_sequence_for_with_non_standard_primary_key - with_example_table 'code serial primary key' do - pk, seq = @connection.pk_and_sequence_for('ex') - assert_equal 'code', pk - assert_equal @connection.default_sequence_name('ex', 'code'), seq.to_s + with_example_table "code serial primary key" do + pk, seq = @connection.pk_and_sequence_for("ex") + assert_equal "code", pk + assert_equal @connection.default_sequence_name("ex", "code"), seq.to_s end end def test_pk_and_sequence_for_returns_nil_if_no_seq - with_example_table 'id integer primary key' do - assert_nil @connection.pk_and_sequence_for('ex') + with_example_table "id integer primary key" do + assert_nil @connection.pk_and_sequence_for("ex") end end def test_pk_and_sequence_for_returns_nil_if_no_pk - with_example_table 'id integer' do - assert_nil @connection.pk_and_sequence_for('ex') + with_example_table "id integer" do + assert_nil @connection.pk_and_sequence_for("ex") end end def test_pk_and_sequence_for_returns_nil_if_table_not_found - assert_nil @connection.pk_and_sequence_for('unobtainium') + assert_nil @connection.pk_and_sequence_for("unobtainium") end def test_pk_and_sequence_for_with_collision_pg_class_oid - @connection.exec_query('create table ex(id serial primary key)') - @connection.exec_query('create table ex2(id serial primary key)') + @connection.exec_query("create table ex(id serial primary key)") + @connection.exec_query("create table ex2(id serial primary key)") correct_depend_record = [ "'pg_class'::regclass", "'ex_id_seq'::regclass", - '0', + "0", "'pg_class'::regclass", "'ex'::regclass", - '1', + "1", "'a'" ] collision_depend_record = [ "'pg_attrdef'::regclass", "'ex2_id_seq'::regclass", - '0', + "0", "'pg_class'::regclass", "'ex'::regclass", - '1', + "1", "'a'" ] @@ -228,39 +179,15 @@ module ActiveRecord "INSERT INTO pg_depend VALUES(#{correct_depend_record.join(',')})" ) - seq = @connection.pk_and_sequence_for('ex').last + seq = @connection.pk_and_sequence_for("ex").last assert_equal PostgreSQL::Name.new("public", "ex_id_seq"), seq @connection.exec_query( "DELETE FROM pg_depend WHERE objid = 'ex2_id_seq'::regclass AND refobjid = 'ex'::regclass AND deptype = 'a'" ) ensure - @connection.drop_table 'ex', if_exists: true - @connection.drop_table 'ex2', if_exists: true - end - - def test_exec_insert_number - with_example_table do - insert(@connection, 'number' => 10) - - result = @connection.exec_query('SELECT number FROM ex WHERE number = 10') - - assert_equal 1, result.rows.length - assert_equal 10, result.rows.last.last - end - end - - def test_exec_insert_string - with_example_table do - str = 'ã„ãŸã ãã¾ã™ï¼' - insert(@connection, 'number' => 10, 'data' => str) - - result = @connection.exec_query('SELECT number, data FROM ex WHERE number = 10') - - value = result.rows.last.last - - assert_equal str, value - end + @connection.drop_table "ex", if_exists: true + @connection.drop_table "ex2", if_exists: true end def test_table_alias_length @@ -271,59 +198,77 @@ module ActiveRecord def test_exec_no_binds with_example_table do - result = @connection.exec_query('SELECT id, data FROM ex') + result = @connection.exec_query("SELECT id, data FROM ex") assert_equal 0, result.rows.length assert_equal 2, result.columns.length assert_equal %w{ id data }, result.columns - string = @connection.quote('foo') + string = @connection.quote("foo") @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") - result = @connection.exec_query('SELECT id, data FROM ex') + result = @connection.exec_query("SELECT id, data FROM ex") assert_equal 1, result.rows.length assert_equal 2, result.columns.length - assert_equal [[1, 'foo']], result.rows + assert_equal [[1, "foo"]], result.rows end end - def test_exec_with_binds - with_example_table do - string = @connection.quote('foo') - @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") - result = @connection.exec_query( - 'SELECT id, data FROM ex WHERE id = $1', nil, [bind_param(1)]) + if ActiveRecord::Base.connection.prepared_statements + def test_exec_with_binds + with_example_table do + string = @connection.quote("foo") + @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") - assert_equal 1, result.rows.length - assert_equal 2, result.columns.length + bind = Relation::QueryAttribute.new("id", 1, Type::Value.new) + result = @connection.exec_query("SELECT id, data FROM ex WHERE id = $1", nil, [bind]) + + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length - assert_equal [[1, 'foo']], result.rows + assert_equal [[1, "foo"]], result.rows + end end - end - def test_exec_typecasts_bind_vals - with_example_table do - string = @connection.quote('foo') - @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") + def test_exec_typecasts_bind_vals + with_example_table do + string = @connection.quote("foo") + @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") - bind = ActiveRecord::Relation::QueryAttribute.new("id", "1-fuu", ActiveRecord::Type::Integer.new) - result = @connection.exec_query( - 'SELECT id, data FROM ex WHERE id = $1', nil, [bind]) + bind = Relation::QueryAttribute.new("id", "1-fuu", Type::Integer.new) + result = @connection.exec_query("SELECT id, data FROM ex WHERE id = $1", nil, [bind]) - assert_equal 1, result.rows.length - assert_equal 2, result.columns.length + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length - assert_equal [[1, 'foo']], result.rows + assert_equal [[1, "foo"]], result.rows + end end end def test_partial_index with_example_table do - @connection.add_index 'ex', %w{ id number }, :name => 'partial', :where => "number > 100" - index = @connection.indexes('ex').find { |idx| idx.name == 'partial' } + @connection.add_index "ex", %w{ id number }, name: "partial", where: "number > 100" + index = @connection.indexes("ex").find { |idx| idx.name == "partial" } assert_equal "(number > 100)", index.where end end + def test_expression_index + with_example_table do + @connection.add_index "ex", "mod(id, 10), abs(number)", name: "expression" + index = @connection.indexes("ex").find { |idx| idx.name == "expression" } + assert_equal "mod(id, 10), abs(number)", index.columns + end + end + + def test_index_with_opclass + with_example_table do + @connection.add_index "ex", "data varchar_pattern_ops", name: "with_opclass" + index = @connection.indexes("ex").find { |idx| idx.name == "with_opclass" } + assert_equal "data varchar_pattern_ops", index.columns + end + end + def test_columns_for_distinct_zero_orders assert_equal "posts.id", @connection.columns_for_distinct("posts.id", []) @@ -341,8 +286,8 @@ module ActiveRecord def test_columns_for_distinct_with_case assert_equal( - 'posts.id, CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END AS alias_0', - @connection.columns_for_distinct('posts.id', + "posts.id, CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END AS alias_0", + @connection.columns_for_distinct("posts.id", ["CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END"]) ) end @@ -423,7 +368,7 @@ module ActiveRecord def test_unparsed_defaults_are_at_least_set_when_saving with_example_table "id SERIAL PRIMARY KEY, number INTEGER NOT NULL DEFAULT (4 + 4) * 2 / 4" do number_klass = Class.new(ActiveRecord::Base) do - self.table_name = 'ex' + self.table_name = "ex" end column = number_klass.columns_hash["number"] assert_nil column.default @@ -438,31 +383,14 @@ module ActiveRecord end private - def insert(ctx, data) - binds = data.map { |name, value| - bind_param(value, name) - } - columns = binds.map(&:name) - - bind_subs = columns.length.times.map { |x| "$#{x + 1}" } - - sql = "INSERT INTO ex (#{columns.join(", ")}) - VALUES (#{bind_subs.join(', ')})" - ctx.exec_insert(sql, 'SQL', binds) - end - - def with_example_table(definition = 'id serial primary key, number integer, data character varying(255)', &block) - super(@connection, 'ex', definition, &block) - end - - def connection_without_insert_returning - ActiveRecord::Base.postgresql_connection(ActiveRecord::Base.configurations['arunit'].merge(:insert_returning => false)) - end + def with_example_table(definition = "id serial primary key, number integer, data character varying(255)", &block) + super(@connection, "ex", definition, &block) + end - def bind_param(value, name = nil) - ActiveRecord::Relation::QueryAttribute.new(name, value, ActiveRecord::Type::Value.new) - end + def connection_without_insert_returning + ActiveRecord::Base.postgresql_connection(ActiveRecord::Base.configurations["arunit"].merge(insert_returning: false)) + end end end end diff --git a/activerecord/test/cases/adapters/postgresql/prepared_statements_test.rb b/activerecord/test/cases/adapters/postgresql/prepared_statements_test.rb new file mode 100644 index 0000000000..b898929f8a --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/prepared_statements_test.rb @@ -0,0 +1,21 @@ +require "cases/helper" +require "models/developer" + +class PreparedStatementsTest < ActiveRecord::PostgreSQLTestCase + fixtures :developers + + def setup + @default_prepared_statements = Developer.connection_config[:prepared_statements] + Developer.connection_config[:prepared_statements] = false + end + + def teardown + Developer.connection_config[:prepared_statements] = @default_prepared_statements + end + + def nothing_raised_with_falsy_prepared_statements + assert_nothing_raised do + Developer.where(id: 1) + end + end +end diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb index 5e6f4dbbb8..865a3a5098 100644 --- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb +++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'ipaddr' +require "ipaddr" module ActiveRecord module ConnectionAdapters @@ -10,11 +10,11 @@ module ActiveRecord end def test_type_cast_true - assert_equal 't', @conn.type_cast(true) + assert_equal "t", @conn.type_cast(true) end def test_type_cast_false - assert_equal 'f', @conn.type_cast(false) + assert_equal "f", @conn.type_cast(false) end def test_quote_float_nan diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb index 0edfa4ed9d..f411884dfd 100644 --- a/activerecord/test/cases/adapters/postgresql/range_test.rb +++ b/activerecord/test/cases/adapters/postgresql/range_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'support/connection_helper' +require "support/connection_helper" if ActiveRecord::Base.connection.respond_to?(:supports_ranges?) && ActiveRecord::Base.connection.supports_ranges? class PostgresqlRange < ActiveRecord::Base @@ -23,7 +23,7 @@ if ActiveRecord::Base.connection.respond_to?(:supports_ranges?) && ActiveRecord: ); _SQL - @connection.create_table('postgresql_ranges') do |t| + @connection.create_table("postgresql_ranges") do |t| t.daterange :date_range t.numrange :num_range t.tsrange :ts_range @@ -32,7 +32,7 @@ _SQL t.int8range :int8_range end - @connection.add_column 'postgresql_ranges', 'float_range', 'floatrange' + @connection.add_column "postgresql_ranges", "float_range", "floatrange" end PostgresqlRange.reset_column_information rescue ActiveRecord::StatementInvalid @@ -93,8 +93,8 @@ _SQL end teardown do - @connection.drop_table 'postgresql_ranges', if_exists: true - @connection.execute 'DROP TYPE IF EXISTS floatrange' + @connection.drop_table "postgresql_ranges", if_exists: true + @connection.execute "DROP TYPE IF EXISTS floatrange" reset_connection end @@ -132,10 +132,10 @@ _SQL end def test_numrange_values - assert_equal BigDecimal.new('0.1')..BigDecimal.new('0.2'), @first_range.num_range - assert_equal BigDecimal.new('0.1')...BigDecimal.new('0.2'), @second_range.num_range - assert_equal BigDecimal.new('0.1')...BigDecimal.new('Infinity'), @third_range.num_range - assert_equal BigDecimal.new('-Infinity')...BigDecimal.new('Infinity'), @fourth_range.num_range + assert_equal BigDecimal.new("0.1")..BigDecimal.new("0.2"), @first_range.num_range + assert_equal BigDecimal.new("0.1")...BigDecimal.new("0.2"), @second_range.num_range + assert_equal BigDecimal.new("0.1")...BigDecimal.new("Infinity"), @third_range.num_range + assert_equal BigDecimal.new("-Infinity")...BigDecimal.new("Infinity"), @fourth_range.num_range assert_nil @empty_range.num_range end @@ -148,8 +148,8 @@ _SQL end def test_tstzrange_values - assert_equal Time.parse('2010-01-01 09:30:00 UTC')..Time.parse('2011-01-01 17:30:00 UTC'), @first_range.tstz_range - assert_equal Time.parse('2010-01-01 09:30:00 UTC')...Time.parse('2011-01-01 17:30:00 UTC'), @second_range.tstz_range + assert_equal Time.parse("2010-01-01 09:30:00 UTC")..Time.parse("2011-01-01 17:30:00 UTC"), @first_range.tstz_range + assert_equal Time.parse("2010-01-01 09:30:00 UTC")...Time.parse("2011-01-01 17:30:00 UTC"), @second_range.tstz_range assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.tstz_range) assert_nil @empty_range.tstz_range end @@ -183,17 +183,17 @@ _SQL end def test_create_tstzrange - tstzrange = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2011-02-02 14:30:00 CDT') + tstzrange = Time.parse("2010-01-01 14:30:00 +0100")...Time.parse("2011-02-02 14:30:00 CDT") round_trip(@new_range, :tstz_range, tstzrange) assert_equal @new_range.tstz_range, tstzrange - assert_equal @new_range.tstz_range, Time.parse('2010-01-01 13:30:00 UTC')...Time.parse('2011-02-02 19:30:00 UTC') + assert_equal @new_range.tstz_range, Time.parse("2010-01-01 13:30:00 UTC")...Time.parse("2011-02-02 19:30:00 UTC") end def test_update_tstzrange assert_equal_round_trip(@first_range, :tstz_range, - Time.parse('2010-01-01 14:30:00 CDT')...Time.parse('2011-02-02 14:30:00 CET')) + Time.parse("2010-01-01 14:30:00 CDT")...Time.parse("2011-02-02 14:30:00 CET")) assert_nil_round_trip(@first_range, :tstz_range, - Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2010-01-01 13:30:00 +0000')) + Time.parse("2010-01-01 14:30:00 +0100")...Time.parse("2010-01-01 13:30:00 +0000")) end def test_create_tsrange @@ -232,14 +232,14 @@ _SQL def test_create_numrange assert_equal_round_trip(@new_range, :num_range, - BigDecimal.new('0.5')...BigDecimal.new('1')) + BigDecimal.new("0.5")...BigDecimal.new("1")) end def test_update_numrange assert_equal_round_trip(@first_range, :num_range, - BigDecimal.new('0.5')...BigDecimal.new('1')) + BigDecimal.new("0.5")...BigDecimal.new("1")) assert_nil_round_trip(@first_range, :num_range, - BigDecimal.new('0.5')...BigDecimal.new('0.5')) + BigDecimal.new("0.5")...BigDecimal.new("0.5")) end def test_create_daterange @@ -282,6 +282,12 @@ _SQL assert_raises(ArgumentError) { PostgresqlRange.create!(tstz_range: "(''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']") } end + def test_where_by_attribute_with_range + range = 1..100 + record = PostgresqlRange.create!(int4_range: range) + assert_equal record, PostgresqlRange.where(int4_range: range).take + end + def test_update_all_with_ranges PostgresqlRange.create! diff --git a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb index c895ab9db5..0ff04bfa27 100644 --- a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb +++ b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb @@ -1,5 +1,5 @@ -require 'cases/helper' -require 'support/connection_helper' +require "cases/helper" +require "support/connection_helper" class PostgreSQLReferentialIntegrityTest < ActiveRecord::PostgreSQLTestCase self.use_transactional_tests = false @@ -14,7 +14,7 @@ class PostgreSQLReferentialIntegrityTest < ActiveRecord::PostgreSQLTestCase def execute(sql) if IS_REFERENTIAL_INTEGRITY_SQL.call(sql) super "BROKEN;" rescue nil # put transaction in broken state - raise ActiveRecord::StatementInvalid, 'PG::InsufficientPrivilege' + raise ActiveRecord::StatementInvalid, "PG::InsufficientPrivilege" else super end @@ -24,7 +24,7 @@ class PostgreSQLReferentialIntegrityTest < ActiveRecord::PostgreSQLTestCase module ProgrammerMistake def execute(sql) if IS_REFERENTIAL_INTEGRITY_SQL.call(sql) - raise ArgumentError, 'something is not right.' + raise ArgumentError, "something is not right." else super end @@ -48,10 +48,10 @@ class PostgreSQLReferentialIntegrityTest < ActiveRecord::PostgreSQLTestCase warning = capture(:stderr) do e = assert_raises(ActiveRecord::InvalidForeignKey) do @connection.disable_referential_integrity do - raise ActiveRecord::InvalidForeignKey, 'Should be re-raised' + raise ActiveRecord::InvalidForeignKey, "Should be re-raised" end end - assert_equal 'Should be re-raised', e.message + assert_equal "Should be re-raised", e.message end assert_match (/WARNING: Rails was not able to disable referential integrity/), warning assert_match (/cause: PG::InsufficientPrivilege/), warning @@ -63,10 +63,10 @@ class PostgreSQLReferentialIntegrityTest < ActiveRecord::PostgreSQLTestCase warning = capture(:stderr) do e = assert_raises(ActiveRecord::StatementInvalid) do @connection.disable_referential_integrity do - raise ActiveRecord::StatementInvalid, 'Should be re-raised' + raise ActiveRecord::StatementInvalid, "Should be re-raised" end end - assert_equal 'Should be re-raised', e.message + assert_equal "Should be re-raised", e.message end assert warning.blank?, "expected no warnings but got:\n#{warning}" end @@ -105,7 +105,7 @@ class PostgreSQLReferentialIntegrityTest < ActiveRecord::PostgreSQLTestCase private - def assert_transaction_is_not_broken - assert_equal 1, @connection.select_value("SELECT 1") - end + def assert_transaction_is_not_broken + assert_equal 1, @connection.select_value("SELECT 1") + end end diff --git a/activerecord/test/cases/adapters/postgresql/rename_table_test.rb b/activerecord/test/cases/adapters/postgresql/rename_table_test.rb index bd64bae308..e9e7f717ac 100644 --- a/activerecord/test/cases/adapters/postgresql/rename_table_test.rb +++ b/activerecord/test/cases/adapters/postgresql/rename_table_test.rb @@ -24,11 +24,11 @@ class PostgresqlRenameTableTest < ActiveRecord::PostgreSQLTestCase private - def num_indices_named(name) - @connection.execute(<<-SQL).values.length - SELECT 1 FROM "pg_index" - JOIN "pg_class" ON "pg_index"."indexrelid" = "pg_class"."oid" - WHERE "pg_class"."relname" = '#{name}' - SQL - end + def num_indices_named(name) + @connection.execute(<<-SQL).values.length + SELECT 1 FROM "pg_index" + JOIN "pg_class" ON "pg_index"."indexrelid" = "pg_class"."oid" + WHERE "pg_class"."relname" = '#{name}' + SQL + end end diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb index a0afd922b2..7193f23880 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb @@ -6,12 +6,12 @@ end class SchemaAuthorizationTest < ActiveRecord::PostgreSQLTestCase self.use_transactional_tests = false - TABLE_NAME = 'schema_things' + TABLE_NAME = "schema_things" COLUMNS = [ - 'id serial primary key', - 'name character varying(50)' + "id serial primary key", + "name character varying(50)" ] - USERS = ['rails_pg_schema_user1', 'rails_pg_schema_user2'] + USERS = ["rails_pg_schema_user1", "rails_pg_schema_user2"] def setup @connection = ActiveRecord::Base.connection @@ -45,7 +45,7 @@ class SchemaAuthorizationTest < ActiveRecord::PostgreSQLTestCase def test_session_auth= assert_raise(ActiveRecord::StatementInvalid) do - @connection.session_auth = 'DEFAULT' + @connection.session_auth = "DEFAULT" @connection.execute "SELECT * FROM #{TABLE_NAME}" end end @@ -55,20 +55,22 @@ class SchemaAuthorizationTest < ActiveRecord::PostgreSQLTestCase set_session_auth USERS.each do |u| set_session_auth u - assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [bind_param(1)]).first['name'] + assert_equal u, @connection.select_value("SELECT name FROM #{TABLE_NAME} WHERE id = 1") set_session_auth end end end - def test_auth_with_bind - assert_nothing_raised do - set_session_auth - USERS.each do |u| - @connection.clear_cache! - set_session_auth u - assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [bind_param(1)]).first['name'] + if ActiveRecord::Base.connection.prepared_statements + def test_auth_with_bind + assert_nothing_raised do set_session_auth + USERS.each do |u| + @connection.clear_cache! + set_session_auth u + assert_equal u, @connection.select_value("SELECT name FROM #{TABLE_NAME} WHERE id = $1", "SQL", [bind_param(1)]) + set_session_auth + end end end end @@ -88,9 +90,9 @@ class SchemaAuthorizationTest < ActiveRecord::PostgreSQLTestCase assert_nothing_raised do USERS.each do |u| set_session_auth u - st = SchemaThing.new :name => 'TEST1' + st = SchemaThing.new name: "TEST1" st.save! - st = SchemaThing.new :id => 5, :name => 'TEST2' + st = SchemaThing.new id: 5, name: "TEST2" st.save! set_session_auth end @@ -98,17 +100,17 @@ class SchemaAuthorizationTest < ActiveRecord::PostgreSQLTestCase end def test_tables_in_current_schemas - assert !@connection.tables.include?(TABLE_NAME) + assert_not_includes @connection.tables, TABLE_NAME USERS.each do |u| set_session_auth u - assert @connection.tables.include?(TABLE_NAME) + assert_includes @connection.tables, TABLE_NAME set_session_auth end end private - def set_session_auth auth = nil - @connection.session_auth = auth || 'default' + def set_session_auth(auth = nil) + @connection.session_auth = auth || "default" end def bind_param(value) diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index f50fe88b9b..51a2306c59 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -1,6 +1,6 @@ require "cases/helper" -require 'models/default' -require 'support/schema_dumping_helper' +require "models/default" +require "support/schema_dumping_helper" module PGSchemaHelper def with_schema_search_path(schema_search_path) @@ -17,32 +17,32 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase include PGSchemaHelper self.use_transactional_tests = false - SCHEMA_NAME = 'test_schema' - SCHEMA2_NAME = 'test_schema2' - TABLE_NAME = 'things' - CAPITALIZED_TABLE_NAME = 'Things' - INDEX_A_NAME = 'a_index_things_on_name' - INDEX_B_NAME = 'b_index_things_on_different_columns_in_each_schema' - INDEX_C_NAME = 'c_index_full_text_search' - INDEX_D_NAME = 'd_index_things_on_description_desc' - INDEX_E_NAME = 'e_index_things_on_name_vector' - INDEX_A_COLUMN = 'name' - INDEX_B_COLUMN_S1 = 'email' - INDEX_B_COLUMN_S2 = 'moment' - INDEX_C_COLUMN = %q{(to_tsvector('english', coalesce(things.name, '')))} - INDEX_D_COLUMN = 'description' - INDEX_E_COLUMN = 'name_vector' + SCHEMA_NAME = "test_schema" + SCHEMA2_NAME = "test_schema2" + TABLE_NAME = "things" + CAPITALIZED_TABLE_NAME = "Things" + INDEX_A_NAME = "a_index_things_on_name" + INDEX_B_NAME = "b_index_things_on_different_columns_in_each_schema" + INDEX_C_NAME = "c_index_full_text_search" + INDEX_D_NAME = "d_index_things_on_description_desc" + INDEX_E_NAME = "e_index_things_on_name_vector" + INDEX_A_COLUMN = "name" + INDEX_B_COLUMN_S1 = "email" + INDEX_B_COLUMN_S2 = "moment" + INDEX_C_COLUMN = "(to_tsvector('english', coalesce(things.name, '')))" + INDEX_D_COLUMN = "description" + INDEX_E_COLUMN = "name_vector" COLUMNS = [ - 'id integer', - 'name character varying(50)', - 'email character varying(50)', - 'description character varying(100)', - 'name_vector tsvector', - 'moment timestamp without time zone default now()' + "id integer", + "name character varying(50)", + "email character varying(50)", + "description character varying(100)", + "name_vector tsvector", + "moment timestamp without time zone default now()" ] - PK_TABLE_NAME = 'table_with_pk' - UNMATCHED_SEQUENCE_NAME = 'unmatched_primary_key_default_value_seq' - UNMATCHED_PK_TABLE_NAME = 'table_with_unmatched_sequence_for_pk' + PK_TABLE_NAME = "table_with_pk" + UNMATCHED_SEQUENCE_NAME = "unmatched_primary_key_default_value_seq" + UNMATCHED_PK_TABLE_NAME = "table_with_unmatched_sequence_for_pk" class Thing1 < ActiveRecord::Base self.table_name = "test_schema.things" @@ -61,7 +61,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase end class Thing5 < ActiveRecord::Base - self.table_name = 'things' + self.table_name = "things" end class Song < ActiveRecord::Base @@ -130,7 +130,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase ensure @connection.drop_schema "test_schema3" end - assert !@connection.schema_names.include?("test_schema3") + assert_not_includes @connection.schema_names, "test_schema3" end def test_drop_schema_if_exists @@ -168,20 +168,22 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase def test_raise_wrapped_exception_on_bad_prepare assert_raises(ActiveRecord::StatementInvalid) do - @connection.exec_query "select * from developers where id = ?", 'sql', [bind_param(1)] + @connection.exec_query "select * from developers where id = ?", "sql", [bind_param(1)] end end - def test_schema_change_with_prepared_stmt - altered = false - @connection.exec_query "select * from developers where id = $1", 'sql', [bind_param(1)] - @connection.exec_query "alter table developers add column zomg int", 'sql', [] - altered = true - @connection.exec_query "select * from developers where id = $1", 'sql', [bind_param(1)] - ensure - # We are not using DROP COLUMN IF EXISTS because that syntax is only - # supported by pg 9.X - @connection.exec_query("alter table developers drop column zomg", 'sql', []) if altered + if ActiveRecord::Base.connection.prepared_statements + def test_schema_change_with_prepared_stmt + altered = false + @connection.exec_query "select * from developers where id = $1", "sql", [bind_param(1)] + @connection.exec_query "alter table developers add column zomg int", "sql", [] + altered = true + @connection.exec_query "select * from developers where id = $1", "sql", [bind_param(1)] + ensure + # We are not using DROP COLUMN IF EXISTS because that syntax is only + # supported by pg 9.X + @connection.exec_query("alter table developers drop column zomg", "sql", []) if altered + end end def test_data_source_exists? @@ -198,7 +200,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase end def test_data_source_exists_when_not_on_schema_search_path - with_schema_search_path('PUBLIC') do + with_schema_search_path("PUBLIC") do assert(!@connection.data_source_exists?(TABLE_NAME), "data_source exists but should not be found") end end @@ -244,9 +246,9 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase end def test_proper_encoding_of_table_name - assert_equal '"table_name"', @connection.quote_table_name('table_name') + assert_equal '"table_name"', @connection.quote_table_name("table_name") assert_equal '"table.name"', @connection.quote_table_name('"table.name"') - assert_equal '"schema_name"."table_name"', @connection.quote_table_name('schema_name.table_name') + assert_equal '"schema_name"."table_name"', @connection.quote_table_name("schema_name.table_name") assert_equal '"schema_name"."table.name"', @connection.quote_table_name('schema_name."table.name"') assert_equal '"schema.name"."table_name"', @connection.quote_table_name('"schema.name".table_name') assert_equal '"schema.name"."table.name"', @connection.quote_table_name('"schema.name"."table.name"') @@ -258,25 +260,25 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase assert_equal 0, Thing3.count assert_equal 0, Thing4.count - Thing1.create(:id => 1, :name => "thing1", :email => "thing1@localhost", :moment => Time.now) + Thing1.create(id: 1, name: "thing1", email: "thing1@localhost", moment: Time.now) assert_equal 1, Thing1.count assert_equal 0, Thing2.count assert_equal 0, Thing3.count assert_equal 0, Thing4.count - Thing2.create(:id => 1, :name => "thing1", :email => "thing1@localhost", :moment => Time.now) + Thing2.create(id: 1, name: "thing1", email: "thing1@localhost", moment: Time.now) assert_equal 1, Thing1.count assert_equal 1, Thing2.count assert_equal 0, Thing3.count assert_equal 0, Thing4.count - Thing3.create(:id => 1, :name => "thing1", :email => "thing1@localhost", :moment => Time.now) + Thing3.create(id: 1, name: "thing1", email: "thing1@localhost", moment: Time.now) assert_equal 1, Thing1.count assert_equal 1, Thing2.count assert_equal 1, Thing3.count assert_equal 0, Thing4.count - Thing4.create(:id => 1, :name => "thing1", :email => "thing1@localhost", :moment => Time.now) + Thing4.create(id: 1, name: "thing1", email: "thing1@localhost", moment: Time.now) assert_equal 1, Thing1.count assert_equal 1, Thing2.count assert_equal 1, Thing3.count @@ -285,7 +287,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase def test_raise_on_unquoted_schema_name assert_raises(ActiveRecord::StatementInvalid) do - with_schema_search_path '$user,public' + with_schema_search_path "$user,public" end end @@ -305,7 +307,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase 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_not @connection.index_name_exists?(TABLE_NAME, "missing_index", true) end end @@ -323,14 +325,14 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase def test_dump_indexes_for_table_with_scheme_specified_in_name indexes = @connection.indexes("#{SCHEMA_NAME}.#{TABLE_NAME}") - assert_equal 4, indexes.size + assert_equal 5, indexes.size end def test_with_uppercase_index_name @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)" with_schema_search_path SCHEMA_NAME do - assert_nothing_raised { @connection.remove_index "things", name: "things_Index"} + assert_nothing_raised { @connection.remove_index "things", name: "things_Index" } end end @@ -354,13 +356,13 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase %(#{SCHEMA_NAME}."#{PK_TABLE_NAME}"), %(#{SCHEMA_NAME}.#{PK_TABLE_NAME}) ].each do |given| - assert_equal 'id', @connection.primary_key(given), "primary key should be found when table referenced as #{given}" + assert_equal "id", @connection.primary_key(given), "primary key should be found when table referenced as #{given}" end end def test_primary_key_assuming_schema_search_path with_schema_search_path(SCHEMA_NAME) do - assert_equal 'id', @connection.primary_key(PK_TABLE_NAME), "primary key should be found" + assert_equal "id", @connection.primary_key(PK_TABLE_NAME), "primary key should be found" end end @@ -379,7 +381,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase %("#{SCHEMA_NAME}"."#{UNMATCHED_PK_TABLE_NAME}") ].each do |given| pk, seq = @connection.pk_and_sequence_for(given) - assert_equal 'id', pk, "primary key should be found when table referenced as #{given}" + assert_equal "id", pk, "primary key should be found when table referenced as #{given}" assert_equal pg_name.new(SCHEMA_NAME, "#{PK_TABLE_NAME}_id_seq"), seq, "sequence name should be found when table referenced as #{given}" if given == %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}") assert_equal pg_name.new(SCHEMA_NAME, UNMATCHED_SEQUENCE_NAME), seq, "sequence name should be found when table referenced as #{given}" if given == %("#{SCHEMA_NAME}"."#{UNMATCHED_PK_TABLE_NAME}") end @@ -387,10 +389,10 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase def test_current_schema { - %('$user',public) => 'public', + %('$user',public) => "public", SCHEMA_NAME => SCHEMA_NAME, %(#{SCHEMA2_NAME},#{SCHEMA_NAME},public) => SCHEMA2_NAME, - %(public,#{SCHEMA2_NAME},#{SCHEMA_NAME}) => 'public' + %(public,#{SCHEMA2_NAME},#{SCHEMA_NAME}) => "public" }.each do |given,expect| with_schema_search_path(given) { assert_equal expect, @connection.current_schema } end @@ -399,7 +401,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase def test_prepared_statements_with_multiple_schemas [SCHEMA_NAME, SCHEMA2_NAME].each do |schema_name| with_schema_search_path schema_name do - Thing5.create(:id => 1, :name => "thing inside #{SCHEMA_NAME}", :email => "thing1@localhost", :moment => Time.now) + Thing5.create(id: 1, name: "thing inside #{SCHEMA_NAME}", email: "thing1@localhost", moment: Time.now) end end @@ -412,10 +414,10 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase def test_schema_exists? { - 'public' => true, + "public" => true, SCHEMA_NAME => true, SCHEMA2_NAME => true, - 'darkside' => false + "darkside" => false }.each do |given,expect| assert_equal expect, @connection.schema_exists?(given) end @@ -440,25 +442,29 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase private def columns(table_name) @connection.send(:column_definitions, table_name).map do |name, type, default| - "#{name} #{type}" + (default ? " default #{default}" : '') + "#{name} #{type}" + (default ? " default #{default}" : "") end end def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name, third_index_column_name, fourth_index_column_name) with_schema_search_path(this_schema_name) do indexes = @connection.indexes(TABLE_NAME).sort_by(&:name) - assert_equal 4,indexes.size - - do_dump_index_assertions_for_one_index(indexes[0], INDEX_A_NAME, first_index_column_name) - do_dump_index_assertions_for_one_index(indexes[1], INDEX_B_NAME, second_index_column_name) - do_dump_index_assertions_for_one_index(indexes[2], INDEX_D_NAME, third_index_column_name) - do_dump_index_assertions_for_one_index(indexes[3], INDEX_E_NAME, fourth_index_column_name) - - indexes.select{|i| i.name != INDEX_E_NAME}.each do |index| - assert_equal :btree, index.using - end - assert_equal :gin, indexes.select{|i| i.name == INDEX_E_NAME}[0].using - assert_equal :desc, indexes.select{|i| i.name == INDEX_D_NAME}[0].orders[INDEX_D_COLUMN] + assert_equal 5, indexes.size + + index_a, index_b, index_c, index_d, index_e = indexes + + do_dump_index_assertions_for_one_index(index_a, INDEX_A_NAME, first_index_column_name) + do_dump_index_assertions_for_one_index(index_b, INDEX_B_NAME, second_index_column_name) + do_dump_index_assertions_for_one_index(index_d, INDEX_D_NAME, third_index_column_name) + do_dump_index_assertions_for_one_index(index_e, INDEX_E_NAME, fourth_index_column_name) + + assert_equal :btree, index_a.using + assert_equal :btree, index_b.using + assert_equal :gin, index_c.using + assert_equal :btree, index_d.using + assert_equal :gin, index_e.using + + assert_equal :desc, index_d.orders[INDEX_D_COLUMN] end end diff --git a/activerecord/test/cases/adapters/postgresql/serial_test.rb b/activerecord/test/cases/adapters/postgresql/serial_test.rb index 7d30db247b..d711b3b729 100644 --- a/activerecord/test/cases/adapters/postgresql/serial_test.rb +++ b/activerecord/test/cases/adapters/postgresql/serial_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'support/schema_dumping_helper' +require "support/schema_dumping_helper" class PostgresqlSerialTest < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper @@ -10,6 +10,7 @@ class PostgresqlSerialTest < ActiveRecord::PostgreSQLTestCase @connection = ActiveRecord::Base.connection @connection.create_table "postgresql_serials", force: true do |t| t.serial :seq + t.integer :serials_id, default: -> { "nextval('postgresql_serials_id_seq')" } end end @@ -24,9 +25,21 @@ class PostgresqlSerialTest < ActiveRecord::PostgreSQLTestCase assert column.serial? end + def test_not_serial_column + column = PostgresqlSerial.columns_hash["serials_id"] + assert_equal :integer, column.type + assert_equal "integer", column.sql_type + assert_not column.serial? + end + def test_schema_dump_with_shorthand output = dump_table_schema "postgresql_serials" - assert_match %r{t\.serial\s+"seq"}, output + assert_match %r{t\.serial\s+"seq",\s+null: false$}, output + end + + def test_schema_dump_with_not_serial + output = dump_table_schema "postgresql_serials" + assert_match %r{t\.integer\s+"serials_id",\s+default: -> \{ "nextval\('postgresql_serials_id_seq'::regclass\)" \}$}, output end end @@ -39,6 +52,7 @@ class PostgresqlBigSerialTest < ActiveRecord::PostgreSQLTestCase @connection = ActiveRecord::Base.connection @connection.create_table "postgresql_big_serials", force: true do |t| t.bigserial :seq + t.bigint :serials_id, default: -> { "nextval('postgresql_big_serials_id_seq')" } end end @@ -53,8 +67,20 @@ class PostgresqlBigSerialTest < ActiveRecord::PostgreSQLTestCase assert column.serial? end + def test_not_bigserial_column + column = PostgresqlBigSerial.columns_hash["serials_id"] + assert_equal :integer, column.type + assert_equal "bigint", column.sql_type + assert_not column.serial? + end + def test_schema_dump_with_shorthand output = dump_table_schema "postgresql_big_serials" - assert_match %r{t\.bigserial\s+"seq"}, output + assert_match %r{t\.bigserial\s+"seq",\s+null: false$}, output + end + + def test_schema_dump_with_not_bigserial + output = dump_table_schema "postgresql_big_serials" + assert_match %r{t\.bigint\s+"serials_id",\s+default: -> \{ "nextval\('postgresql_big_serials_id_seq'::regclass\)" \}$}, output end end diff --git a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb index 5aab246c99..eb9978a898 100644 --- a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb +++ b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb @@ -1,4 +1,4 @@ -require 'cases/helper' +require "cases/helper" module ActiveRecord module ConnectionAdapters @@ -17,22 +17,22 @@ module ActiveRecord if Process.respond_to?(:fork) def test_cache_is_per_pid cache = StatementPool.new nil, 10 - cache['foo'] = 'bar' - assert_equal 'bar', cache['foo'] + cache["foo"] = "bar" + assert_equal "bar", cache["foo"] pid = fork { - lookup = cache['foo']; + lookup = cache["foo"]; exit!(!lookup) } Process.waitpid pid - assert $?.success?, 'process should exit successfully' + assert $?.success?, "process should exit successfully" end end def test_dealloc_does_not_raise_on_inactive_connection cache = StatementPool.new InactivePGconn.new, 10 - cache['foo'] = 'bar' + cache["foo"] = "bar" assert_nothing_raised { cache.clear } end end diff --git a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb index 4c4866b46b..e7c1d97d16 100644 --- a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb +++ b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb @@ -1,6 +1,6 @@ -require 'cases/helper' -require 'models/developer' -require 'models/topic' +require "cases/helper" +require "models/developer" +require "models/topic" class PostgresqlTimestampTest < ActiveRecord::PostgreSQLTestCase class PostgresqlTimestampWithZone < ActiveRecord::Base; end @@ -32,7 +32,7 @@ class PostgresqlTimestampTest < ActiveRecord::PostgreSQLTestCase with_timezone_config default: :local, aware_attributes: false do @connection.reconnect! # make sure to use a non-UTC time zone - @connection.execute("SET time zone 'America/Jamaica'", 'SCHEMA') + @connection.execute("SET time zone 'America/Jamaica'", "SCHEMA") timestamp = PostgresqlTimestampWithZone.find(1) assert_equal Time.utc(2010,1,1, 11,0,0), timestamp.time @@ -54,37 +54,37 @@ class PostgresqlTimestampFixtureTest < ActiveRecord::PostgreSQLTestCase def test_load_infinity_and_beyond d = Developer.find_by_sql("select 'infinity'::timestamp as updated_at") - assert d.first.updated_at.infinite?, 'timestamp should be infinite' + assert d.first.updated_at.infinite?, "timestamp should be infinite" d = Developer.find_by_sql("select '-infinity'::timestamp as updated_at") time = d.first.updated_at - assert time.infinite?, 'timestamp should be infinite' + assert time.infinite?, "timestamp should be infinite" assert_operator time, :<, 0 end def test_save_infinity_and_beyond - d = Developer.create!(:name => 'aaron', :updated_at => 1.0 / 0.0) + d = Developer.create!(name: "aaron", updated_at: 1.0 / 0.0) assert_equal(1.0 / 0.0, d.updated_at) - d = Developer.create!(:name => 'aaron', :updated_at => -1.0 / 0.0) + d = Developer.create!(name: "aaron", updated_at: -1.0 / 0.0) assert_equal(-1.0 / 0.0, d.updated_at) end def test_bc_timestamp date = Date.new(0) - 1.week - Developer.create!(:name => "aaron", :updated_at => date) + Developer.create!(name: "aaron", updated_at: date) assert_equal date, Developer.find_by_name("aaron").updated_at end def test_bc_timestamp_leap_year date = Time.utc(-4, 2, 29) - Developer.create!(:name => "taihou", :updated_at => date) + Developer.create!(name: "taihou", updated_at: date) assert_equal date, Developer.find_by_name("taihou").updated_at end def test_bc_timestamp_year_zero date = Time.utc(0, 4, 7) - Developer.create!(:name => "yahagi", :updated_at => date) + Developer.create!(name: "yahagi", updated_at: date) assert_equal date, Developer.find_by_name("yahagi").updated_at end end diff --git a/activerecord/test/cases/adapters/postgresql/transaction_test.rb b/activerecord/test/cases/adapters/postgresql/transaction_test.rb new file mode 100644 index 0000000000..c450524de8 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/transaction_test.rb @@ -0,0 +1,98 @@ +require "cases/helper" +require "support/connection_helper" +require "concurrent/atomic/cyclic_barrier" + +module ActiveRecord + class PostgresqlTransactionTest < ActiveRecord::PostgreSQLTestCase + self.use_transactional_tests = false + + class Sample < ActiveRecord::Base + self.table_name = "samples" + end + + setup do + @connection = ActiveRecord::Base.connection + + @connection.transaction do + @connection.drop_table "samples", if_exists: true + @connection.create_table("samples") do |t| + t.integer "value" + end + end + + Sample.reset_column_information + end + + teardown do + @connection.drop_table "samples", if_exists: true + end + + test "raises SerializationFailure when a serialization failure occurs" do + assert_raises(ActiveRecord::SerializationFailure) do + before = Concurrent::CyclicBarrier.new(2) + after = Concurrent::CyclicBarrier.new(2) + + thread = Thread.new do + with_warning_suppression do + Sample.transaction isolation: :serializable do + before.wait + Sample.create value: Sample.sum(:value) + after.wait + end + end + end + + begin + with_warning_suppression do + Sample.transaction isolation: :serializable do + before.wait + Sample.create value: Sample.sum(:value) + after.wait + end + end + ensure + thread.join + end + end + end + + test "raises Deadlocked when a deadlock is encountered" do + with_warning_suppression do + assert_raises(ActiveRecord::Deadlocked) do + barrier = Concurrent::CyclicBarrier.new(2) + + s1 = Sample.create value: 1 + s2 = Sample.create value: 2 + + thread = Thread.new do + Sample.transaction do + s1.lock! + barrier.wait + s2.update_attributes value: 1 + end + end + + begin + Sample.transaction do + s2.lock! + barrier.wait + s1.update_attributes value: 2 + end + ensure + thread.join + end + end + end + end + + protected + + def with_warning_suppression + log_level = ActiveRecord::Base.connection.client_min_messages + ActiveRecord::Base.connection.client_min_messages = "error" + yield + ensure + ActiveRecord::Base.connection.client_min_messages = log_level + end + end +end diff --git a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb index 77a99ca778..bd45a9daa0 100644 --- a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb +++ b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb @@ -1,4 +1,4 @@ -require 'cases/helper' +require "cases/helper" class PostgresqlTypeLookupTest < ActiveRecord::PostgreSQLTestCase setup do @@ -9,8 +9,8 @@ class PostgresqlTypeLookupTest < ActiveRecord::PostgreSQLTestCase box_array = @connection.type_map.lookup(1020) int_array = @connection.type_map.lookup(1007) - assert_equal ';', box_array.delimiter - assert_equal ',', int_array.delimiter + assert_equal ";", box_array.delimiter + assert_equal ",", int_array.delimiter end test "array types correctly respect registration of subtypes" do @@ -18,7 +18,7 @@ class PostgresqlTypeLookupTest < ActiveRecord::PostgreSQLTestCase bigint_array = @connection.type_map.lookup(1016, -1, "bigint[]") big_array = [123456789123456789] - assert_raises(RangeError) { int_array.serialize(big_array) } + assert_raises(ActiveModel::RangeError) { int_array.serialize(big_array) } assert_equal "{123456789123456789}", bigint_array.serialize(big_array) end @@ -27,7 +27,7 @@ class PostgresqlTypeLookupTest < ActiveRecord::PostgreSQLTestCase bigint_range = @connection.type_map.lookup(3926, -1, "int8range") big_range = 0..123456789123456789 - assert_raises(RangeError) { int_range.serialize(big_range) } + assert_raises(ActiveModel::RangeError) { int_range.serialize(big_range) } assert_equal "[0,123456789123456789]", bigint_range.serialize(big_range) end end diff --git a/activerecord/test/cases/adapters/postgresql/utils_test.rb b/activerecord/test/cases/adapters/postgresql/utils_test.rb index 095c1826e5..01c597beae 100644 --- a/activerecord/test/cases/adapters/postgresql/utils_test.rb +++ b/activerecord/test/cases/adapters/postgresql/utils_test.rb @@ -1,5 +1,5 @@ -require 'cases/helper' -require 'active_record/connection_adapters/postgresql/utils' +require "cases/helper" +require "active_record/connection_adapters/postgresql/utils" class PostgreSQLUtilsTest < ActiveRecord::PostgreSQLTestCase Name = ActiveRecord::ConnectionAdapters::PostgreSQL::Name @@ -7,14 +7,14 @@ class PostgreSQLUtilsTest < ActiveRecord::PostgreSQLTestCase def test_extract_schema_qualified_name { - %(table_name) => [nil,'table_name'], - %("table.name") => [nil,'table.name'], + %(table_name) => [nil,"table_name"], + %("table.name") => [nil,"table.name"], %(schema.table_name) => %w{schema table_name}, %("schema".table_name) => %w{schema table_name}, %(schema."table_name") => %w{schema table_name}, %("schema"."table_name") => %w{schema table_name}, - %("even spaces".table) => ['even spaces','table'], - %(schema."table.name") => ['schema', 'table.name'] + %("even spaces".table) => ["even spaces","table"], + %(schema."table.name") => ["schema", "table.name"] }.each do |given, expect| assert_equal Name.new(*expect), extract_schema_qualified_name(given) end @@ -54,7 +54,7 @@ class PostgreSQLNameTest < ActiveRecord::PostgreSQLTestCase end test "can be used as hash key" do - hash = {Name.new("schema", "article_seq") => "success"} + 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")] diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index 7628075ad2..9a59691737 100644 --- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb +++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'support/schema_dumping_helper' +require "support/schema_dumping_helper" module PostgresqlUUIDHelper def connection @@ -20,10 +20,10 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase end setup do - enable_extension!('uuid-ossp', connection) + enable_extension!("uuid-ossp", connection) connection.create_table "uuid_data_type" do |t| - t.uuid 'guid' + t.uuid "guid" end end @@ -34,13 +34,13 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase def test_change_column_default @connection.add_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v1()" UUIDType.reset_column_information - column = UUIDType.columns_hash['thingy'] + column = UUIDType.columns_hash["thingy"] assert_equal "uuid_generate_v1()", column.default_function @connection.change_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v4()" UUIDType.reset_column_information - column = UUIDType.columns_hash['thingy'] + column = UUIDType.columns_hash["thingy"] assert_equal "uuid_generate_v4()", column.default_function ensure UUIDType.reset_column_information @@ -57,46 +57,46 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase end def test_treat_blank_uuid_as_nil - UUIDType.create! guid: '' + UUIDType.create! guid: "" assert_equal(nil, UUIDType.last.guid) end def test_treat_invalid_uuid_as_nil - uuid = UUIDType.create! guid: 'foobar' + uuid = UUIDType.create! guid: "foobar" assert_equal(nil, uuid.guid) end def test_invalid_uuid_dont_modify_before_type_cast - uuid = UUIDType.new guid: 'foobar' - assert_equal 'foobar', uuid.guid_before_type_cast + uuid = UUIDType.new guid: "foobar" + assert_equal "foobar", uuid.guid_before_type_cast end def test_acceptable_uuid_regex # Valid uuids - ['A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11', - '{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}', - 'a0eebc999c0b4ef8bb6d6bb9bd380a11', - 'a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11', - '{a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}', + ["A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11", + "{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}", + "a0eebc999c0b4ef8bb6d6bb9bd380a11", + "a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11", + "{a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}", # The following is not a valid RFC 4122 UUID, but PG doesn't seem to care, # so we shouldn't block it either. (Pay attention to "fb6d" – the "f" here # is invalid – it must be one of 8, 9, A, B, a, b according to the spec.) - '{a0eebc99-9c0b-4ef8-fb6d-6bb9bd380a11}', + "{a0eebc99-9c0b-4ef8-fb6d-6bb9bd380a11}", ].each do |valid_uuid| uuid = UUIDType.new guid: valid_uuid assert_not_nil uuid.guid end # Invalid uuids - [['A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11'], + [["A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11"], Hash.new, 0, 0.0, true, - 'Z0000C99-9C0B-4EF8-BB6D-6BB9BD380A11', - 'a0eebc999r0b4ef8ab6d6bb9bd380a11', - 'a0ee-bc99------4ef8-bb6d-6bb9-bd38-0a11', - '{a0eebc99-bb6d6bb9-bd380a11}'].each do |invalid_uuid| + "Z0000C99-9C0B-4EF8-BB6D-6BB9BD380A11", + "a0eebc999r0b4ef8ab6d6bb9bd380a11", + "a0ee-bc99------4ef8-bb6d-6bb9-bd38-0a11", + "{a0eebc99-bb6d6bb9-bd380a11}"].each do |invalid_uuid| uuid = UUIDType.new guid: invalid_uuid assert_nil uuid.guid end @@ -142,13 +142,13 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper class UUID < ActiveRecord::Base - self.table_name = 'pg_uuids' + self.table_name = "pg_uuids" end setup do - connection.create_table('pg_uuids', id: :uuid, default: 'uuid_generate_v1()') do |t| - t.string 'name' - t.uuid 'other_uuid', default: 'uuid_generate_v4()' + connection.create_table("pg_uuids", id: :uuid, default: "uuid_generate_v1()") do |t| + t.string "name" + t.uuid "other_uuid", default: "uuid_generate_v4()" end # Create custom PostgreSQL function to generate UUIDs @@ -160,21 +160,21 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase SQL # Create such a table with custom function as default value generator - connection.create_table('pg_uuids_2', id: :uuid, default: 'my_uuid_generator()') do |t| - t.string 'name' - t.uuid 'other_uuid_2', default: 'my_uuid_generator()' + connection.create_table("pg_uuids_2", id: :uuid, default: "my_uuid_generator()") do |t| + t.string "name" + t.uuid "other_uuid_2", default: "my_uuid_generator()" end end teardown do drop_table "pg_uuids" - drop_table 'pg_uuids_2' - connection.execute 'DROP FUNCTION IF EXISTS my_uuid_generator();' + drop_table "pg_uuids_2" + connection.execute "DROP FUNCTION IF EXISTS my_uuid_generator();" end if ActiveRecord::Base.connection.supports_extensions? def test_id_is_uuid - assert_equal :uuid, UUID.columns_hash['id'].type + assert_equal :uuid, UUID.columns_hash["id"].type assert UUID.primary_key end @@ -190,21 +190,21 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase end def test_pk_and_sequence_for_uuid_primary_key - pk, seq = connection.pk_and_sequence_for('pg_uuids') - assert_equal 'id', pk + pk, seq = connection.pk_and_sequence_for("pg_uuids") + assert_equal "id", pk assert_equal nil, seq end def test_schema_dumper_for_uuid_primary_key schema = dump_table_schema "pg_uuids" assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: -> { "uuid_generate_v1\(\)" }/, schema) - assert_match(/t\.uuid "other_uuid", default: -> { "uuid_generate_v4\(\)" }/, schema) + assert_match(/t\.uuid "other_uuid", default: -> { "uuid_generate_v4\(\)" }/, schema) end def test_schema_dumper_for_uuid_primary_key_with_custom_default schema = dump_table_schema "pg_uuids_2" assert_match(/\bcreate_table "pg_uuids_2", id: :uuid, default: -> { "my_uuid_generator\(\)" }/, schema) - assert_match(/t\.uuid "other_uuid_2", default: -> { "my_uuid_generator\(\)" }/, schema) + assert_match(/t\.uuid "other_uuid_2", default: -> { "my_uuid_generator\(\)" }/, schema) end end end @@ -214,9 +214,9 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper setup do - connection.create_table('pg_uuids', id: false) do |t| + connection.create_table("pg_uuids", id: false) do |t| t.primary_key :id, :uuid, default: nil - t.string 'name' + t.string "name" end end @@ -244,30 +244,30 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::PostgreSQLTestCase include PostgresqlUUIDHelper class UuidPost < ActiveRecord::Base - self.table_name = 'pg_uuid_posts' + self.table_name = "pg_uuid_posts" has_many :uuid_comments, inverse_of: :uuid_post end class UuidComment < ActiveRecord::Base - self.table_name = 'pg_uuid_comments' + self.table_name = "pg_uuid_comments" belongs_to :uuid_post end setup do connection.transaction do - connection.create_table('pg_uuid_posts', id: :uuid) do |t| - t.string 'title' + connection.create_table("pg_uuid_posts", id: :uuid) do |t| + t.string "title" end - connection.create_table('pg_uuid_comments', id: :uuid) do |t| + connection.create_table("pg_uuid_comments", id: :uuid) do |t| t.references :uuid_post, type: :uuid - t.string 'content' + t.string "content" end end end teardown do - drop_table "pg_uuid_comments" - drop_table "pg_uuid_posts" + drop_table "pg_uuid_comments" + drop_table "pg_uuid_posts" end if ActiveRecord::Base.connection.supports_extensions? @@ -290,5 +290,4 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::PostgreSQLTestCase assert_nil UuidPost.find_by(id: 789) end end - end diff --git a/activerecord/test/cases/adapters/postgresql/xml_test.rb b/activerecord/test/cases/adapters/postgresql/xml_test.rb index add32699fa..826b384fb3 100644 --- a/activerecord/test/cases/adapters/postgresql/xml_test.rb +++ b/activerecord/test/cases/adapters/postgresql/xml_test.rb @@ -1,28 +1,28 @@ -require 'cases/helper' -require 'support/schema_dumping_helper' +require "cases/helper" +require "support/schema_dumping_helper" class PostgresqlXMLTest < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper class XmlDataType < ActiveRecord::Base - self.table_name = 'xml_data_type' + self.table_name = "xml_data_type" end def setup @connection = ActiveRecord::Base.connection begin @connection.transaction do - @connection.create_table('xml_data_type') do |t| - t.xml 'payload' + @connection.create_table("xml_data_type") do |t| + t.xml "payload" end end rescue ActiveRecord::StatementInvalid skip "do not test on PG without xml" end - @column = XmlDataType.columns_hash['payload'] + @column = XmlDataType.columns_hash["payload"] end teardown do - @connection.drop_table 'xml_data_type', if_exists: true + @connection.drop_table "xml_data_type", if_exists: true end def test_column @@ -30,7 +30,7 @@ class PostgresqlXMLTest < ActiveRecord::PostgreSQLTestCase end def test_null_xml - @connection.execute %q|insert into xml_data_type (payload) VALUES(null)| + @connection.execute "insert into xml_data_type (payload) VALUES(null)" assert_nil XmlDataType.first.payload end diff --git a/activerecord/test/cases/adapters/sqlite3/collation_test.rb b/activerecord/test/cases/adapters/sqlite3/collation_test.rb index 58a9469ce5..28e8f12c18 100644 --- a/activerecord/test/cases/adapters/sqlite3/collation_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/collation_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'support/schema_dumping_helper' +require "support/schema_dumping_helper" class SQLite3CollationTest < ActiveRecord::SQLite3TestCase include SchemaDumpingHelper @@ -7,8 +7,8 @@ class SQLite3CollationTest < ActiveRecord::SQLite3TestCase def setup @connection = ActiveRecord::Base.connection @connection.create_table :collation_table_sqlite3, force: true do |t| - t.string :string_nocase, collation: 'NOCASE' - t.text :text_rtrim, collation: 'RTRIM' + t.string :string_nocase, collation: "NOCASE" + t.text :text_rtrim, collation: "RTRIM" end end @@ -17,32 +17,32 @@ class SQLite3CollationTest < ActiveRecord::SQLite3TestCase end test "string column with collation" do - column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == 'string_nocase' } + column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == "string_nocase" } assert_equal :string, column.type - assert_equal 'NOCASE', column.collation + assert_equal "NOCASE", column.collation end test "text column with collation" do - column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == 'text_rtrim' } + column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == "text_rtrim" } assert_equal :text, column.type - assert_equal 'RTRIM', column.collation + assert_equal "RTRIM", column.collation end test "add column with collation" do - @connection.add_column :collation_table_sqlite3, :title, :string, collation: 'RTRIM' + @connection.add_column :collation_table_sqlite3, :title, :string, collation: "RTRIM" - column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == 'title' } + column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == "title" } assert_equal :string, column.type - assert_equal 'RTRIM', column.collation + assert_equal "RTRIM", column.collation end test "change column with collation" do @connection.add_column :collation_table_sqlite3, :description, :string - @connection.change_column :collation_table_sqlite3, :description, :text, collation: 'RTRIM' + @connection.change_column :collation_table_sqlite3, :description, :text, collation: "RTRIM" - column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == 'description' } + column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == "description" } assert_equal :text, column.type - assert_equal 'RTRIM', column.collation + assert_equal "RTRIM", column.collation end test "schema dump includes collation" do diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb index 34e3b2e023..8342b05870 100644 --- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb @@ -10,8 +10,8 @@ class CopyTableTest < ActiveRecord::SQLite3TestCase end end - def test_copy_table(from = 'customers', to = 'customers2', options = {}) - assert_nothing_raised {copy_table(from, to, options)} + def test_copy_table(from = "customers", to = "customers2", options = {}) + assert_nothing_raised { copy_table(from, to, options) } assert_equal row_count(from), row_count(to) if block_given? @@ -24,39 +24,39 @@ class CopyTableTest < ActiveRecord::SQLite3TestCase end def test_copy_table_renaming_column - test_copy_table('customers', 'customers2', - :rename => {'name' => 'person_name'}) do |from, to, options| - expected = column_values(from, 'name') - assert_equal expected, column_values(to, 'person_name') + test_copy_table("customers", "customers2", + rename: { "name" => "person_name" }) do |from, to, options| + expected = column_values(from, "name") + assert_equal expected, column_values(to, "person_name") assert expected.any?, "No values in table: #{expected.inspect}" end end def test_copy_table_allows_to_pass_options_to_create_table - @connection.create_table('blocker_table') - test_copy_table('customers', 'blocker_table', force: true) + @connection.create_table("blocker_table") + test_copy_table("customers", "blocker_table", force: true) end def test_copy_table_with_index - 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') + 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") end end end def test_copy_table_without_primary_key - test_copy_table('developers_projects', 'programmers_projects') do - assert_nil @connection.primary_key('programmers_projects') + test_copy_table("developers_projects", "programmers_projects") do + assert_nil @connection.primary_key("programmers_projects") end end def test_copy_table_with_id_col_that_is_not_primary_key - test_copy_table('goofy_string_id', 'goofy_string_id2') do - original_id = @connection.columns('goofy_string_id').detect{|col| col.name == 'id' } - copied_id = @connection.columns('goofy_string_id2').detect{|col| col.name == 'id' } + test_copy_table("goofy_string_id", "goofy_string_id2") do + original_id = @connection.columns("goofy_string_id").detect { |col| col.name == "id" } + 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 @@ -64,28 +64,28 @@ class CopyTableTest < ActiveRecord::SQLite3TestCase end def test_copy_table_with_unconventional_primary_key - test_copy_table('owners', 'owners_unconventional') do - original_pk = @connection.primary_key('owners') - copied_pk = @connection.primary_key('owners_unconventional') + test_copy_table("owners", "owners_unconventional") do + original_pk = @connection.primary_key("owners") + copied_pk = @connection.primary_key("owners_unconventional") assert_equal original_pk, copied_pk end end def test_copy_table_with_binary_column - test_copy_table 'binaries', 'binaries2' + test_copy_table "binaries", "binaries2" end protected def copy_table(from, to, options = {}) - @connection.copy_table(from, to, {:temporary => true}.merge(options)) + @connection.copy_table(from, to, { temporary: true }.merge(options)) end def column_names(table) - @connection.table_structure(table).map {|column| column['name']} + @connection.table_structure(table).map { |column| column["name"] } end def column_values(table, column) - @connection.select_all("SELECT #{column} FROM #{table} ORDER BY id").map {|row| row[column]} + @connection.select_all("SELECT #{column} FROM #{table} ORDER BY id").map { |row| row[column] } end def table_indexes_without_name(table) @@ -93,6 +93,6 @@ protected end def row_count(table) - @connection.select_one("SELECT COUNT(*) AS count FROM #{table}")['count'] + @connection.select_one("SELECT COUNT(*) AS count FROM #{table}")["count"] end end diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb index 2aec322582..128acb79cf 100644 --- a/activerecord/test/cases/adapters/sqlite3/explain_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb @@ -1,27 +1,21 @@ require "cases/helper" -require 'models/developer' -require 'models/computer' +require "models/developer" +require "models/computer" -module ActiveRecord - module ConnectionAdapters - class SQLite3Adapter - class ExplainTest < ActiveRecord::SQLite3TestCase - fixtures :developers +class SQLite3ExplainTest < ActiveRecord::SQLite3TestCase + fixtures :developers - def test_explain_for_one_query - explain = Developer.where(:id => 1).explain - assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = ?), explain - assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain) - end + def test_explain_for_one_query + explain = Developer.where(id: 1).explain + assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\? \[\["id", 1\]\]|1)), explain + assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain) + end - def test_explain_with_eager_loading - explain = Developer.where(:id => 1).includes(:audit_logs).explain - assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = ?), explain - assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain) - assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain - assert_match(/(SCAN )?TABLE audit_logs/, explain) - end - end - end + def test_explain_with_eager_loading + explain = Developer.where(id: 1).includes(:audit_logs).explain + assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\? \[\["id", 1\]\]|1)), explain + assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain) + assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain + assert_match(/(SCAN )?TABLE audit_logs/, explain) end end diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index 87a892db37..80a37e83ff 100644 --- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb @@ -1,95 +1,94 @@ require "cases/helper" -require 'bigdecimal' -require 'yaml' -require 'securerandom' - -module ActiveRecord - module ConnectionAdapters - class SQLite3Adapter - class QuotingTest < ActiveRecord::SQLite3TestCase - def setup - @conn = Base.sqlite3_connection :database => ':memory:', - :adapter => 'sqlite3', - :timeout => 100 - end - - def test_type_cast_binary_encoding_without_logger - @conn.extend(Module.new { def logger; end }) - binary = SecureRandom.hex - expected = binary.dup.encode!(Encoding::UTF_8) - assert_equal expected, @conn.type_cast(binary) - end - - def test_type_cast_symbol - assert_equal 'foo', @conn.type_cast(:foo) - end - - def test_type_cast_date - date = Date.today - expected = @conn.quoted_date(date) - assert_equal expected, @conn.type_cast(date) - end - - def test_type_cast_time - time = Time.now - expected = @conn.quoted_date(time) - assert_equal expected, @conn.type_cast(time) - end - - def test_type_cast_numeric - assert_equal 10, @conn.type_cast(10) - assert_equal 2.2, @conn.type_cast(2.2) - end - - def test_type_cast_nil - assert_equal nil, @conn.type_cast(nil) - end - - def test_type_cast_true - assert_equal 't', @conn.type_cast(true) - end - - def test_type_cast_false - assert_equal 'f', @conn.type_cast(false) - end - - def test_type_cast_bigdecimal - bd = BigDecimal.new '10.0' - assert_equal bd.to_f, @conn.type_cast(bd) - end - - def test_type_cast_unknown_should_raise_error - obj = Class.new.new - assert_raise(TypeError) { @conn.type_cast(obj) } - end - - def test_type_cast_object_which_responds_to_quoted_id - quoted_id_obj = Class.new { - def quoted_id - "'zomg'" - end - - def id - 10 - end - }.new - assert_equal 10, @conn.type_cast(quoted_id_obj) - - quoted_id_obj = Class.new { - def quoted_id - "'zomg'" - end - }.new - assert_raise(TypeError) { @conn.type_cast(quoted_id_obj) } - end - - def test_quoting_binary_strings - value = "hello".encode('ascii-8bit') - type = Type::String.new - - assert_equal "'hello'", @conn.quote(type.serialize(value)) - end +require "bigdecimal" +require "yaml" +require "securerandom" + +class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase + def setup + @conn = ActiveRecord::Base.connection + end + + def test_type_cast_binary_encoding_without_logger + @conn.extend(Module.new { def logger; end }) + binary = SecureRandom.hex + expected = binary.dup.encode!(Encoding::UTF_8) + assert_equal expected, @conn.type_cast(binary) + end + + def test_type_cast_symbol + assert_equal "foo", @conn.type_cast(:foo) + end + + def test_type_cast_date + date = Date.today + expected = @conn.quoted_date(date) + assert_equal expected, @conn.type_cast(date) + end + + def test_type_cast_time + time = Time.now + expected = @conn.quoted_date(time) + assert_equal expected, @conn.type_cast(time) + end + + def test_type_cast_numeric + assert_equal 10, @conn.type_cast(10) + assert_equal 2.2, @conn.type_cast(2.2) + end + + def test_type_cast_nil + assert_equal nil, @conn.type_cast(nil) + end + + def test_type_cast_true + assert_equal "t", @conn.type_cast(true) + end + + def test_type_cast_false + assert_equal "f", @conn.type_cast(false) + end + + def test_type_cast_bigdecimal + bd = BigDecimal.new "10.0" + assert_equal bd.to_f, @conn.type_cast(bd) + end + + def test_type_cast_unknown_should_raise_error + obj = Class.new.new + assert_raise(TypeError) { @conn.type_cast(obj) } + end + + def test_type_cast_object_which_responds_to_quoted_id + quoted_id_obj = Class.new { + def quoted_id + "'zomg'" + end + + def id + 10 end - end + }.new + assert_equal 10, @conn.type_cast(quoted_id_obj) + + quoted_id_obj = Class.new { + def quoted_id + "'zomg'" + end + }.new + assert_raise(TypeError) { @conn.type_cast(quoted_id_obj) } + end + + def test_quoting_binary_strings + value = "hello".encode("ascii-8bit") + type = ActiveRecord::Type::String.new + + assert_equal "'hello'", @conn.quote(type.serialize(value)) + end + + def test_quoted_time_returns_date_qualified_time + value = ::Time.utc(2000, 1, 1, 12, 30, 0, 999999) + type = ActiveRecord::Type::Time.new + + assert_equal "'2000-01-01 12:30:00.999999'", @conn.quote(type.serialize(value)) end end diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 02c3358ba6..66f9349111 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -1,7 +1,7 @@ require "cases/helper" -require 'models/owner' -require 'tempfile' -require 'support/ddl_helper' +require "models/owner" +require "tempfile" +require "support/ddl_helper" module ActiveRecord module ConnectionAdapters @@ -14,22 +14,22 @@ module ActiveRecord end def setup - @conn = Base.sqlite3_connection database: ':memory:', - adapter: 'sqlite3', + @conn = Base.sqlite3_connection database: ":memory:", + adapter: "sqlite3", timeout: 100 end def test_bad_connection assert_raise ActiveRecord::NoDatabaseError do connection = ActiveRecord::Base.sqlite3_connection(adapter: "sqlite3", database: "/tmp/should/_not/_exist/-cinco-dog.db") - connection.drop_table 'ex', if_exists: true + connection.drop_table "ex", if_exists: true end end unless in_memory_db? def test_connect_with_url original_connection = ActiveRecord::Base.remove_connection - tf = Tempfile.open 'whatever' + tf = Tempfile.open "whatever" url = "sqlite3:#{tf.path}" ActiveRecord::Base.establish_connection(url) assert ActiveRecord::Base.connection @@ -51,7 +51,7 @@ module ActiveRecord def test_valid_column with_example_table do - column = @conn.columns('ex').find { |col| col.name == 'id' } + column = @conn.columns("ex").find { |col| col.name == "id" } assert @conn.valid_type?(column.type) end end @@ -66,9 +66,9 @@ module ActiveRecord end def test_column_types - owner = Owner.create!(name: "hello".encode('ascii-8bit')) + owner = Owner.create!(name: "hello".encode("ascii-8bit")) owner.reload - select = Owner.columns.map { |c| "typeof(#{c.name})" }.join ', ' + select = Owner.columns.map { |c| "typeof(#{c.name})" }.join ", " result = Owner.connection.exec_query <<-esql SELECT #{select} FROM #{Owner.table_name} @@ -83,10 +83,10 @@ module ActiveRecord def test_exec_insert with_example_table do vals = [Relation::QueryAttribute.new("number", 10, Type::Value.new)] - @conn.exec_insert('insert into ex (number) VALUES (?)', 'SQL', vals) + @conn.exec_insert("insert into ex (number) VALUES (?)", "SQL", vals) result = @conn.exec_query( - 'select number from ex where number = ?', 'SQL', vals) + "select number from ex where number = ?", "SQL", vals) assert_equal 1, result.rows.length assert_equal 10, result.rows.first.first @@ -94,8 +94,8 @@ module ActiveRecord end def test_primary_key_returns_nil_for_no_pk - with_example_table 'id int, data string' do - assert_nil @conn.primary_key('ex') + with_example_table "id int, data string" do + assert_nil @conn.primary_key("ex") end end @@ -107,69 +107,69 @@ module ActiveRecord def test_bad_timeout assert_raises(TypeError) do - Base.sqlite3_connection database: ':memory:', - adapter: 'sqlite3', - timeout: 'usa' + Base.sqlite3_connection database: ":memory:", + adapter: "sqlite3", + timeout: "usa" end end # connection is OK with a nil timeout def test_nil_timeout - conn = Base.sqlite3_connection database: ':memory:', - adapter: 'sqlite3', + conn = Base.sqlite3_connection database: ":memory:", + adapter: "sqlite3", timeout: nil - assert conn, 'made a connection' + assert conn, "made a connection" end def test_connect - assert @conn, 'should have connection' + assert @conn, "should have connection" end # sqlite3 defaults to UTF-8 encoding def test_encoding - assert_equal 'UTF-8', @conn.encoding + assert_equal "UTF-8", @conn.encoding end def test_exec_no_binds - with_example_table 'id int, data string' do - result = @conn.exec_query('SELECT id, data FROM ex') + with_example_table "id int, data string" do + result = @conn.exec_query("SELECT id, data FROM ex") assert_equal 0, result.rows.length assert_equal 2, result.columns.length assert_equal %w{ id data }, result.columns @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') - result = @conn.exec_query('SELECT id, data FROM ex') + result = @conn.exec_query("SELECT id, data FROM ex") assert_equal 1, result.rows.length assert_equal 2, result.columns.length - assert_equal [[1, 'foo']], result.rows + assert_equal [[1, "foo"]], result.rows end end def test_exec_query_with_binds - with_example_table 'id int, data string' do + with_example_table "id int, data string" do @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') result = @conn.exec_query( - 'SELECT id, data FROM ex WHERE id = ?', nil, [Relation::QueryAttribute.new(nil, 1, Type::Value.new)]) + "SELECT id, data FROM ex WHERE id = ?", nil, [Relation::QueryAttribute.new(nil, 1, Type::Value.new)]) assert_equal 1, result.rows.length assert_equal 2, result.columns.length - assert_equal [[1, 'foo']], result.rows + assert_equal [[1, "foo"]], result.rows end end def test_exec_query_typecasts_bind_vals - with_example_table 'id int, data string' do + with_example_table "id int, data string" do @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') result = @conn.exec_query( - 'SELECT id, data FROM ex WHERE id = ?', nil, [Relation::QueryAttribute.new("id", "1-fuu", Type::Integer.new)]) + "SELECT id, data FROM ex WHERE id = ?", nil, [Relation::QueryAttribute.new("id", "1-fuu", Type::Integer.new)]) assert_equal 1, result.rows.length assert_equal 2, result.columns.length - assert_equal [[1, 'foo']], result.rows + assert_equal [[1, "foo"]], result.rows end end @@ -182,15 +182,15 @@ module ActiveRecord ) eosql str = "\x80".force_encoding("ASCII-8BIT") - binary = DualEncoding.new name: 'ã„ãŸã ãã¾ã™ï¼', data: str + binary = DualEncoding.new name: "ã„ãŸã ãã¾ã™ï¼", data: str binary.save! assert_equal str, binary.data ensure - DualEncoding.connection.drop_table 'dual_encodings', if_exists: true + DualEncoding.connection.drop_table "dual_encodings", if_exists: true end def test_type_cast_should_not_mutate_encoding - name = 'hello'.force_encoding(Encoding::ASCII_8BIT) + name = "hello".force_encoding(Encoding::ASCII_8BIT) Owner.create(name: name) assert_equal Encoding::ASCII_8BIT, name.encoding ensure @@ -204,8 +204,8 @@ module ActiveRecord assert_equal 1, records.length record = records.first - assert_equal 10, record['number'] - assert_equal 1, record['id'] + assert_equal 10, record["number"] + assert_equal 1, record["id"] end end @@ -213,24 +213,12 @@ module ActiveRecord assert_equal "''", @conn.quote_string("'") end - def test_insert_sql - with_example_table do - 2.times do |i| - rv = @conn.insert_sql "INSERT INTO ex (number) VALUES (#{i})" - assert_equal(i + 1, rv) - end - - records = @conn.execute "SELECT * FROM ex" - assert_equal 2, records.length - end - end - - def test_insert_sql_logged + def test_insert_logged with_example_table do sql = "INSERT INTO ex (number) VALUES (10)" name = "foo" assert_logged [[sql, name, []]] do - @conn.insert_sql sql, name + @conn.insert(sql, name) end end end @@ -238,8 +226,8 @@ module ActiveRecord def test_insert_id_value_returned with_example_table do sql = "INSERT INTO ex (number) VALUES (10)" - idval = 'vuvuzela' - id = @conn.insert_sql sql, nil, nil, idval + idval = "vuvuzela" + id = @conn.insert(sql, nil, nil, idval) assert_equal idval, id end end @@ -249,7 +237,7 @@ module ActiveRecord 2.times do |i| @conn.create "INSERT INTO ex (number) VALUES (#{i})" end - rows = @conn.select_rows 'select number, id from ex' + rows = @conn.select_rows "select number, id from ex" assert_equal [[0, 1], [1, 2]], rows end end @@ -266,7 +254,7 @@ module ActiveRecord def test_transaction with_example_table do - count_sql = 'select count(*) from ex' + count_sql = "select count(*) from ex" @conn.begin_db_transaction @conn.create "INSERT INTO ex (number) VALUES (10)" @@ -280,7 +268,7 @@ module ActiveRecord def test_tables with_example_table do ActiveSupport::Deprecation.silence { assert_equal %w{ ex }, @conn.tables } - with_example_table 'id integer PRIMARY KEY AUTOINCREMENT, number integer', 'people' do + with_example_table "id integer PRIMARY KEY AUTOINCREMENT, number integer", "people" do ActiveSupport::Deprecation.silence { assert_equal %w{ ex people }.sort, @conn.tables.sort } end end @@ -291,17 +279,17 @@ module ActiveRecord SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence' SQL - assert_logged [[sql.squish, 'SCHEMA', []]] do + assert_logged [[sql.squish, "SCHEMA", []]] do ActiveSupport::Deprecation.silence do - @conn.tables('hello') + @conn.tables("hello") end end end def test_indexes_logs_name with_example_table do - assert_logged [["PRAGMA index_list(\"ex\")", 'SCHEMA', []]] do - @conn.indexes('ex', 'hello') + assert_logged [["PRAGMA index_list(\"ex\")", "SCHEMA", []]] do + @conn.indexes("ex", "hello") end end end @@ -312,9 +300,9 @@ module ActiveRecord SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence' AND name = 'ex' SQL - assert_logged [[sql.squish, 'SCHEMA', []]] do + assert_logged [[sql.squish, "SCHEMA", []]] do ActiveSupport::Deprecation.silence do - assert @conn.table_exists?('ex') + assert @conn.table_exists?("ex") end end end @@ -322,7 +310,7 @@ module ActiveRecord def test_columns with_example_table do - columns = @conn.columns('ex').sort_by(&:name) + columns = @conn.columns("ex").sort_by(&:name) assert_equal 2, columns.length assert_equal %w{ id number }.sort, columns.map(&:name) assert_equal [nil, nil], columns.map(&:default) @@ -331,17 +319,17 @@ module ActiveRecord end def test_columns_with_default - with_example_table 'id integer PRIMARY KEY AUTOINCREMENT, number integer default 10' do - column = @conn.columns('ex').find { |x| - x.name == 'number' + with_example_table "id integer PRIMARY KEY AUTOINCREMENT, number integer default 10" do + column = @conn.columns("ex").find { |x| + x.name == "number" } - assert_equal '10', column.default + assert_equal "10", column.default end end def test_columns_with_not_null - with_example_table 'id integer PRIMARY KEY AUTOINCREMENT, number integer not null' do - column = @conn.columns('ex').find { |x| x.name == 'number' } + with_example_table "id integer PRIMARY KEY AUTOINCREMENT, number integer not null" do + column = @conn.columns("ex").find { |x| x.name == "number" } assert_not column.null, "column should not be null" end end @@ -349,59 +337,59 @@ module ActiveRecord def test_indexes_logs with_example_table do assert_logged [["PRAGMA index_list(\"ex\")", "SCHEMA", []]] do - @conn.indexes('ex') + @conn.indexes("ex") end end end def test_no_indexes - assert_equal [], @conn.indexes('items') + assert_equal [], @conn.indexes("items") end def test_index with_example_table do - @conn.add_index 'ex', 'id', unique: true, name: 'fun' - index = @conn.indexes('ex').find { |idx| idx.name == 'fun' } + @conn.add_index "ex", "id", unique: true, name: "fun" + index = @conn.indexes("ex").find { |idx| idx.name == "fun" } - assert_equal 'ex', index.table - assert index.unique, 'index is unique' - assert_equal ['id'], index.columns + assert_equal "ex", index.table + assert index.unique, "index is unique" + assert_equal ["id"], index.columns end end def test_non_unique_index with_example_table do - @conn.add_index 'ex', 'id', name: 'fun' - index = @conn.indexes('ex').find { |idx| idx.name == 'fun' } - assert_not index.unique, 'index is not unique' + @conn.add_index "ex", "id", name: "fun" + index = @conn.indexes("ex").find { |idx| idx.name == "fun" } + assert_not index.unique, "index is not unique" end end def test_compound_index with_example_table do - @conn.add_index 'ex', %w{ id number }, name: 'fun' - index = @conn.indexes('ex').find { |idx| idx.name == 'fun' } + @conn.add_index "ex", %w{ id number }, name: "fun" + index = @conn.indexes("ex").find { |idx| idx.name == "fun" } assert_equal %w{ id number }.sort, index.columns.sort end end def test_primary_key with_example_table do - assert_equal 'id', @conn.primary_key('ex') - with_example_table 'internet integer PRIMARY KEY AUTOINCREMENT, number integer not null', 'foos' do - assert_equal 'internet', @conn.primary_key('foos') + assert_equal "id", @conn.primary_key("ex") + with_example_table "internet integer PRIMARY KEY AUTOINCREMENT, number integer not null", "foos" do + assert_equal "internet", @conn.primary_key("foos") end end end def test_no_primary_key - with_example_table 'number integer not null' do - assert_nil @conn.primary_key('ex') + with_example_table "number integer not null" do + assert_nil @conn.primary_key("ex") end end def test_supports_extensions - assert_not @conn.supports_extensions?, 'does not support extensions' + assert_not @conn.supports_extensions?, "does not support extensions" end def test_respond_to_enable_extension @@ -414,15 +402,15 @@ module ActiveRecord def test_statement_closed db = ::SQLite3::Database.new(ActiveRecord::Base. - configurations['arunit']['database']) + configurations["arunit"]["database"]) statement = ::SQLite3::Statement.new(db, - 'CREATE TABLE statement_test (number integer not null)') - statement.stub(:step, ->{ raise ::SQLite3::BusyException.new('busy') }) do + "CREATE TABLE statement_test (number integer not null)") + statement.stub(:step, -> { raise ::SQLite3::BusyException.new("busy") }) do assert_called(statement, :columns, returns: []) do assert_called(statement, :close) do ::SQLite3::Statement.stub(:new, statement) do assert_raises ActiveRecord::StatementInvalid do - @conn.exec_query 'select * from statement_test' + @conn.exec_query "select * from statement_test" end end end @@ -432,22 +420,22 @@ module ActiveRecord private - def assert_logged logs - subscriber = SQLSubscriber.new - subscription = ActiveSupport::Notifications.subscribe('sql.active_record', subscriber) - yield - assert_equal logs, subscriber.logged - ensure - ActiveSupport::Notifications.unsubscribe(subscription) - end + def assert_logged(logs) + subscriber = SQLSubscriber.new + subscription = ActiveSupport::Notifications.subscribe("sql.active_record", subscriber) + yield + assert_equal logs, subscriber.logged + ensure + ActiveSupport::Notifications.unsubscribe(subscription) + end - def with_example_table(definition = nil, table_name = 'ex', &block) - definition ||= <<-SQL - id integer PRIMARY KEY AUTOINCREMENT, - number integer - SQL - super(@conn, table_name, definition, &block) - end + def with_example_table(definition = nil, table_name = "ex", &block) + definition ||= <<-SQL + id integer PRIMARY KEY AUTOINCREMENT, + number integer + SQL + super(@conn, table_name, definition, &block) + end end end end diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb index 9b675b804b..b1b4463bf1 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'models/owner' +require "models/owner" module ActiveRecord module ConnectionAdapters @@ -8,12 +8,12 @@ module ActiveRecord Dir.mktmpdir do |dir| begin dir = Pathname.new(dir) - @conn = Base.sqlite3_connection :database => dir.join("db/foo.sqlite3"), - :adapter => 'sqlite3', - :timeout => 100 + @conn = Base.sqlite3_connection database: dir.join("db/foo.sqlite3"), + adapter: "sqlite3", + timeout: 100 - assert Dir.exist? dir.join('db') - assert File.exist? dir.join('db/foo.sqlite3') + assert Dir.exist? dir.join("db") + assert File.exist? dir.join("db/foo.sqlite3") ensure @conn.disconnect! if @conn end diff --git a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb index 559b951109..aebcce3691 100644 --- a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb @@ -1,24 +1,20 @@ -require 'cases/helper' +require "cases/helper" -module ActiveRecord::ConnectionAdapters - class SQLite3Adapter - class StatementPoolTest < ActiveRecord::SQLite3TestCase - if Process.respond_to?(:fork) - def test_cache_is_per_pid +class SQLite3StatementPoolTest < ActiveRecord::SQLite3TestCase + if Process.respond_to?(:fork) + def test_cache_is_per_pid - cache = StatementPool.new(10) - cache['foo'] = 'bar' - assert_equal 'bar', cache['foo'] + cache = ActiveRecord::ConnectionAdapters::SQLite3Adapter::StatementPool.new(10) + cache["foo"] = "bar" + assert_equal "bar", cache["foo"] - pid = fork { - lookup = cache['foo']; - exit!(!lookup) - } + pid = fork { + lookup = cache["foo"]; + exit!(!lookup) + } - Process.waitpid pid - assert $?.success?, 'process should exit successfully' - end - end + Process.waitpid pid + assert $?.success?, "process should exit successfully" end end end diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb index 5536702f58..f8136fde72 100644 --- a/activerecord/test/cases/aggregations_test.rb +++ b/activerecord/test/cases/aggregations_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'models/customer' +require "models/customer" class AggregationsTest < ActiveRecord::TestCase fixtures :customers @@ -51,17 +51,17 @@ class AggregationsTest < ActiveRecord::TestCase Customer.update_all("gps_location = '24x113'") customers(:david).reload - assert_equal '24x113', customers(:david)['gps_location'] + assert_equal "24x113", customers(:david)["gps_location"] - assert_equal GpsLocation.new('24x113'), customers(:david).gps_location + assert_equal GpsLocation.new("24x113"), customers(:david).gps_location end def test_gps_equality - assert_equal GpsLocation.new('39x110'), GpsLocation.new('39x110') + assert_equal GpsLocation.new("39x110"), GpsLocation.new("39x110") end def test_gps_inequality - assert_not_equal GpsLocation.new('39x110'), GpsLocation.new('39x111') + assert_not_equal GpsLocation.new("39x110"), GpsLocation.new("39x111") end def test_allow_nil_gps_is_nil @@ -102,7 +102,7 @@ class AggregationsTest < ActiveRecord::TestCase end def test_nil_assignment_results_in_nil - customers(:david).gps_location = GpsLocation.new('39x111') + customers(:david).gps_location = GpsLocation.new("39x111") assert_not_nil customers(:david).gps_location customers(:david).gps_location = nil assert_nil customers(:david).gps_location @@ -129,26 +129,36 @@ class AggregationsTest < ActiveRecord::TestCase end def test_custom_constructor - assert_equal 'Barney GUMBLE', customers(:barney).fullname.to_s + assert_equal "Barney GUMBLE", customers(:barney).fullname.to_s assert_kind_of Fullname, customers(:barney).fullname end def test_custom_converter - customers(:barney).fullname = 'Barnoit Gumbleau' - assert_equal 'Barnoit GUMBLEAU', customers(:barney).fullname.to_s + customers(:barney).fullname = "Barnoit Gumbleau" + assert_equal "Barnoit GUMBLEAU", customers(:barney).fullname.to_s assert_kind_of Fullname, customers(:barney).fullname end + + def test_assigning_hash_to_custom_converter + customers(:barney).fullname = { first: "Barney", last: "Stinson" } + assert_equal "Barney STINSON", customers(:barney).name + end + + def test_assigning_hash_without_custom_converter + customers(:barney).fullname_no_converter = { first: "Barney", last: "Stinson" } + assert_equal({ first: "Barney", last: "Stinson" }.to_s, customers(:barney).name) + end end class OverridingAggregationsTest < ActiveRecord::TestCase class DifferentName; end class Person < ActiveRecord::Base - composed_of :composed_of, :mapping => %w(person_first_name first_name) + composed_of :composed_of, mapping: %w(person_first_name first_name) end class DifferentPerson < Person - composed_of :composed_of, :class_name => 'DifferentName', :mapping => %w(different_person_first_name first_name) + composed_of :composed_of, class_name: "DifferentName", mapping: %w(different_person_first_name first_name) end def test_composed_of_aggregation_redefinition_reflections_should_differ_and_not_inherited diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb index 9c99689c1e..e3eccad71f 100644 --- a/activerecord/test/cases/ar_schema_test.rb +++ b/activerecord/test/cases/ar_schema_test.rb @@ -17,6 +17,7 @@ if ActiveRecord::Base.connection.supports_migrations? @connection.drop_table :nep_fruits rescue nil @connection.drop_table :nep_schema_migrations rescue nil @connection.drop_table :has_timestamps rescue nil + @connection.drop_table :multiple_indexes rescue nil ActiveRecord::SchemaMigration.delete_all rescue nil ActiveRecord::Migration.verbose = @original_verbose end @@ -36,7 +37,7 @@ if ActiveRecord::Base.connection.supports_migrations? end def test_schema_define - ActiveRecord::Schema.define(:version => 7) do + ActiveRecord::Schema.define(version: 7) do create_table :fruits do |t| t.column :color, :string t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle @@ -55,7 +56,7 @@ if ActiveRecord::Base.connection.supports_migrations? old_table_name_prefix = ActiveRecord::Base.table_name_prefix ActiveRecord::Base.table_name_prefix = "nep_" ActiveRecord::SchemaMigration.table_name = "nep_#{table_name}" - ActiveRecord::Schema.define(:version => 7) do + ActiveRecord::Schema.define(version: 7) do create_table :fruits do |t| t.column :color, :string t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle @@ -71,7 +72,7 @@ if ActiveRecord::Base.connection.supports_migrations? def test_schema_raises_an_error_for_invalid_column_type assert_raise NoMethodError do - ActiveRecord::Schema.define(:version => 8) do + ActiveRecord::Schema.define(version: 8) do create_table :vegetables do |t| t.unknown :color end @@ -80,7 +81,7 @@ if ActiveRecord::Base.connection.supports_migrations? end def test_schema_subclass - Class.new(ActiveRecord::Schema).define(:version => 9) do + Class.new(ActiveRecord::Schema).define(version: 9) do create_table :fruits end assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" } @@ -93,6 +94,21 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal "20131219224947", ActiveRecord::SchemaMigration.normalize_migration_number("20131219224947") end + def test_schema_load_with_multiple_indexes_for_column_of_different_names + ActiveRecord::Schema.define do + create_table :multiple_indexes do |t| + t.string "foo" + t.index ["foo"], name: "multiple_indexes_foo_1" + t.index ["foo"], name: "multiple_indexes_foo_2" + end + end + + indexes = @connection.indexes("multiple_indexes") + + assert_equal 2, indexes.length + assert_equal ["multiple_indexes_foo_1", "multiple_indexes_foo_2"], indexes.collect(&:name).sort + end + def test_timestamps_without_null_set_null_to_false_on_create_table ActiveRecord::Schema.define do create_table :has_timestamps do |t| @@ -100,8 +116,8 @@ if ActiveRecord::Base.connection.supports_migrations? end end - assert !@connection.columns(:has_timestamps).find { |c| c.name == 'created_at' }.null - assert !@connection.columns(:has_timestamps).find { |c| c.name == 'updated_at' }.null + assert !@connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null + assert !@connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null end def test_timestamps_without_null_set_null_to_false_on_change_table @@ -113,8 +129,8 @@ if ActiveRecord::Base.connection.supports_migrations? end end - assert !@connection.columns(:has_timestamps).find { |c| c.name == 'created_at' }.null - assert !@connection.columns(:has_timestamps).find { |c| c.name == 'updated_at' }.null + assert !@connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null + assert !@connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null end def test_timestamps_without_null_set_null_to_false_on_add_timestamps @@ -123,8 +139,8 @@ if ActiveRecord::Base.connection.supports_migrations? add_timestamps :has_timestamps, default: Time.now end - assert !@connection.columns(:has_timestamps).find { |c| c.name == 'created_at' }.null - assert !@connection.columns(:has_timestamps).find { |c| c.name == 'updated_at' }.null + assert !@connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null + assert !@connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null end end end diff --git a/activerecord/test/cases/associations/association_scope_test.rb b/activerecord/test/cases/associations/association_scope_test.rb index 472e270f8c..c322333f6d 100644 --- a/activerecord/test/cases/associations/association_scope_test.rb +++ b/activerecord/test/cases/associations/association_scope_test.rb @@ -1,11 +1,11 @@ -require 'cases/helper' -require 'models/post' -require 'models/author' +require "cases/helper" +require "models/post" +require "models/author" module ActiveRecord module Associations class AssociationScopeTest < ActiveRecord::TestCase - test 'does not duplicate conditions' do + test "does not duplicate conditions" do scope = AssociationScope.scope(Author.new.association(:welcome_posts), Author.connection) binds = scope.where_clause.binds.map(&:value) diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 4f99c57c3c..2418346d1b 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -1,28 +1,28 @@ -require 'cases/helper' -require 'models/developer' -require 'models/project' -require 'models/company' -require 'models/topic' -require 'models/reply' -require 'models/computer' -require 'models/post' -require 'models/author' -require 'models/tag' -require 'models/tagging' -require 'models/comment' -require 'models/sponsor' -require 'models/member' -require 'models/essay' -require 'models/toy' -require 'models/invoice' -require 'models/line_item' -require 'models/column' -require 'models/record' -require 'models/admin' -require 'models/admin/user' -require 'models/ship' -require 'models/treasure' -require 'models/parrot' +require "cases/helper" +require "models/developer" +require "models/project" +require "models/company" +require "models/topic" +require "models/reply" +require "models/computer" +require "models/post" +require "models/author" +require "models/tag" +require "models/tagging" +require "models/comment" +require "models/sponsor" +require "models/member" +require "models/essay" +require "models/toy" +require "models/invoice" +require "models/line_item" +require "models/column" +require "models/record" +require "models/admin" +require "models/admin/user" +require "models/ship" +require "models/treasure" +require "models/parrot" class BelongsToAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :developers, :projects, :topics, @@ -43,11 +43,11 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase ActiveRecord::SQLCounter.clear_log Client.find(3).firm ensure - assert ActiveRecord::SQLCounter.log_all.all? { |sql| /order by/i !~ sql }, 'ORDER BY was used in the query' + assert ActiveRecord::SQLCounter.log_all.all? { |sql| /order by/i !~ sql }, "ORDER BY was used in the query" end def test_belongs_to_with_primary_key - client = Client.create(:name => "Primary key client", :firm_name => companies(:first_firm).name) + client = Client.create(name: "Primary key client", firm_name: companies(:first_firm).name) assert_equal companies(:first_firm).name, client.firm_with_primary_key.name end @@ -94,7 +94,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase account = model.new assert_not account.valid? - assert_equal [{error: :blank}], account.errors.details[:company] + assert_equal [{ error: :blank }], account.errors.details[:company] ensure ActiveRecord::Base.belongs_to_required_by_default = original_value end @@ -111,7 +111,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase account = model.new assert_not account.valid? - assert_equal [{error: :blank}], account.errors.details[:company] + assert_equal [{ error: :blank }], account.errors.details[:company] ensure ActiveRecord::Base.belongs_to_required_by_default = original_value end @@ -120,21 +120,21 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase counter = 0 comments = Class.new(ActiveRecord::Base) { - self.table_name = 'comments' - self.inheritance_column = 'not_there' + self.table_name = "comments" + self.inheritance_column = "not_there" posts = Class.new(ActiveRecord::Base) { - self.table_name = 'posts' - self.inheritance_column = 'not_there' + self.table_name = "posts" + self.inheritance_column = "not_there" default_scope -> { counter += 1 - where("id = :inc", :inc => counter) + where("id = :inc", inc: counter) } - has_many :comments, :anonymous_class => comments + has_many :comments, anonymous_class: comments } - belongs_to :post, :anonymous_class => posts, :inverse_of => false + belongs_to :post, anonymous_class: posts, inverse_of: false } assert_equal 0, counter @@ -166,9 +166,9 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase Admin.const_set "Region", Class.new(ActiveRecord::Base) e = assert_raise(ActiveRecord::AssociationTypeMismatch) { - Admin::RegionalUser.new(region: 'wrong value') + Admin::RegionalUser.new(region: "wrong value") } - assert_match(/^Region\([^)]+\) expected, got String\([^)]+\)$/, e.message) + assert_match(/^Region\([^)]+\) expected, got "wrong value" which is an instance of String\([^)]+\)$/, e.message) ensure Admin.send :remove_const, "Region" if Admin.const_defined?("Region") Admin.send :remove_const, "RegionalUser" if Admin.const_defined?("RegionalUser") @@ -203,14 +203,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_eager_loading_with_primary_key Firm.create("name" => "Apple") Client.create("name" => "Citibank", :firm_name => "Apple") - citibank_result = Client.all.merge!(:where => {:name => "Citibank"}, :includes => :firm_with_primary_key).first + citibank_result = Client.all.merge!(where: { name: "Citibank" }, includes: :firm_with_primary_key).first assert citibank_result.association(:firm_with_primary_key).loaded? end def test_eager_loading_with_primary_key_as_symbol Firm.create("name" => "Apple") Client.create("name" => "Citibank", :firm_name => "Apple") - citibank_result = Client.all.merge!(:where => {:name => "Citibank"}, :includes => :firm_with_primary_key_symbols).first + citibank_result = Client.all.merge!(where: { name: "Citibank" }, includes: :firm_with_primary_key_symbols).first assert citibank_result.association(:firm_with_primary_key_symbols).loaded? end @@ -224,7 +224,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end def test_creating_the_belonging_object_with_primary_key - client = Client.create(:name => "Primary key client") + client = Client.create(name: "Primary key client") apple = client.create_firm_with_primary_key("name" => "Apple") assert_equal apple, client.firm_with_primary_key client.save @@ -247,36 +247,36 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_building_the_belonging_object_with_explicit_sti_base_class account = Account.new - company = account.build_firm(:type => "Company") + company = account.build_firm(type: "Company") assert_kind_of Company, company, "Expected #{company.class} to be a Company" end def test_building_the_belonging_object_with_sti_subclass account = Account.new - company = account.build_firm(:type => "Firm") + company = account.build_firm(type: "Firm") assert_kind_of Firm, company, "Expected #{company.class} to be a Firm" end def test_building_the_belonging_object_with_an_invalid_type account = Account.new - assert_raise(ActiveRecord::SubclassNotFound) { account.build_firm(:type => "InvalidType") } + assert_raise(ActiveRecord::SubclassNotFound) { account.build_firm(type: "InvalidType") } end def test_building_the_belonging_object_with_an_unrelated_type account = Account.new - assert_raise(ActiveRecord::SubclassNotFound) { account.build_firm(:type => "Account") } + assert_raise(ActiveRecord::SubclassNotFound) { account.build_firm(type: "Account") } end def test_building_the_belonging_object_with_primary_key - client = Client.create(:name => "Primary key client") + client = Client.create(name: "Primary key client") apple = client.build_firm_with_primary_key("name" => "Apple") client.save assert_equal apple.name, client.firm_name end def test_create! - client = Client.create!(:name => "Jimmy") - account = client.create_account!(:credit_limit => 10) + client = Client.create!(name: "Jimmy") + account = client.create_account!(credit_limit: 10) assert_equal account, client.account assert account.persisted? client.save @@ -285,7 +285,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end def test_failing_create! - client = Client.create!(:name => "Jimmy") + client = Client.create!(name: "Jimmy") assert_raise(ActiveRecord::RecordInvalid) { client.create_account! } assert_not_nil client.account assert client.account.new_record? @@ -301,7 +301,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end def test_natural_assignment_to_nil_with_primary_key - client = Client.create(:name => "Primary key client", :firm_name => companies(:first_firm).name) + client = Client.create(name: "Primary key client", firm_name: companies(:first_firm).name) client.firm_with_primary_key = nil client.save client.association(:firm_with_primary_key).reload @@ -325,19 +325,19 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase sponsor.association(:sponsorable).reload assert_nil sponsor.sponsorable - sponsor.sponsorable_type = '' # the column doesn't have to be declared NOT NULL + sponsor.sponsorable_type = "" # the column doesn't have to be declared NOT NULL assert_nil sponsor.association(:sponsorable).send(:klass) sponsor.association(:sponsorable).reload assert_nil sponsor.sponsorable - sponsor.sponsorable = Member.new :name => "Bert" + sponsor.sponsorable = Member.new name: "Bert" assert_equal Member, sponsor.association(:sponsorable).send(:klass) assert_equal "members", sponsor.association(:sponsorable).aliased_table_name end def test_with_polymorphic_and_condition sponsor = Sponsor.create - member = Member.create :name => "Bert" + member = Member.create name: "Bert" sponsor.sponsorable = member assert_equal member, sponsor.sponsorable @@ -346,16 +346,16 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_with_select assert_equal 1, Company.find(2).firm_with_select.attributes.size - assert_equal 1, Company.all.merge!(:includes => :firm_with_select ).find(2).firm_with_select.attributes.size + assert_equal 1, Company.all.merge!(includes: :firm_with_select ).find(2).firm_with_select.attributes.size end def test_belongs_to_without_counter_cache_option # Ship has a conventionally named `treasures_count` column, but the counter_cache # option is not given on the association. - ship = Ship.create(name: 'Countless') + ship = Ship.create(name: "Countless") assert_no_difference lambda { ship.reload.treasures_count }, "treasures_count should not be changed unless counter_cache is given on the relation" do - treasure = Treasure.new(name: 'Gold', ship: ship) + treasure = Treasure.new(name: "Gold", ship: ship) treasure.save end @@ -461,8 +461,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end def test_belongs_to_counter_after_save - topic = Topic.create!(:title => "monday night") - topic.replies.create!(:title => "re: monday night", :content => "football") + topic = Topic.create!(title: "monday night") + topic.replies.create!(title: "re: monday night", content: "football") assert_equal 1, Topic.find(topic.id)[:replies_count] topic.save! @@ -564,8 +564,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end def test_belongs_to_counter_when_update_columns - topic = Topic.create!(:title => "37s") - topic.replies.create!(:title => "re: 37s", :content => "rails") + topic = Topic.create!(title: "37s") + topic.replies.create!(title: "re: 37s", content: "rails") assert_equal 1, Topic.find(topic.id)[:replies_count] topic.update_columns(content: "rails is wonderful") @@ -601,7 +601,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_new_record_with_foreign_key_but_no_object client = Client.new("firm_id" => 1) # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first - assert_equal Firm.all.merge!(:order => "id").first, client.firm_with_basic_id + assert_equal Firm.all.merge!(order: "id").first, client.firm_with_basic_id end def test_setting_foreign_key_after_nil_target_loaded @@ -626,16 +626,22 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_queries(0) { tagging.super_tag } end + def test_dont_find_target_when_saving_foreign_key_after_stale_association_loaded + client = Client.create!(name: "Test client", firm_with_basic_id: Firm.find(1)) + client.firm_id = Firm.create!(name: "Test firm").id + assert_queries(1) { client.save! } + end + def test_field_name_same_as_foreign_key computer = Computer.find(1) assert_not_nil computer.developer, ":foreign key == attribute didn't lock up" # ' end def test_counter_cache - topic = Topic.create :title => "Zoom-zoom-zoom" + topic = Topic.create title: "Zoom-zoom-zoom" assert_equal 0, topic[:replies_count] - reply = Reply.create(:title => "re: zoom", :content => "speedy quick!") + reply = Reply.create(title: "re: zoom", content: "speedy quick!") reply.topic = topic assert_equal 1, topic.reload[:replies_count] @@ -646,10 +652,10 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end def test_counter_cache_double_destroy - topic = Topic.create :title => "Zoom-zoom-zoom" + topic = Topic.create title: "Zoom-zoom-zoom" 5.times do - topic.replies.create(:title => "re: zoom", :content => "speedy quick!") + topic.replies.create(title: "re: zoom", content: "speedy quick!") end assert_equal 5, topic.reload[:replies_count] @@ -666,10 +672,10 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end def test_concurrent_counter_cache_double_destroy - topic = Topic.create :title => "Zoom-zoom-zoom" + topic = Topic.create title: "Zoom-zoom-zoom" 5.times do - topic.replies.create(:title => "re: zoom", :content => "speedy quick!") + topic.replies.create(title: "re: zoom", content: "speedy quick!") end assert_equal 5, topic.reload[:replies_count] @@ -687,10 +693,10 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end def test_custom_counter_cache - reply = Reply.create(:title => "re: zoom", :content => "speedy quick!") + reply = Reply.create(title: "re: zoom", content: "speedy quick!") assert_equal 0, reply[:replies_count] - silly = SillyReply.create(:title => "gaga", :content => "boo-boo") + silly = SillyReply.create(title: "gaga", content: "boo-boo") silly.reply = reply assert_equal 1, reply.reload[:replies_count] @@ -700,10 +706,21 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal 17, reply.replies.size end + def test_replace_counter_cache + topic = Topic.create(title: "Zoom-zoom-zoom") + reply = Reply.create(title: "re: zoom", content: "speedy quick!") + + reply.topic = topic + reply.save + topic.reload + + assert_equal 1, topic.replies_count + end + def test_association_assignment_sticks post = Post.first - author1, author2 = Author.all.merge!(:limit => 2).to_a + author1, author2 = Author.all.merge!(limit: 2).to_a assert_not_nil author1 assert_not_nil author2 @@ -726,7 +743,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert companies(:first_client).readonly_firm.readonly? end - def test_test_polymorphic_assignment_foreign_key_type_string + def test_polymorphic_assignment_foreign_key_type_string comment = Comment.first comment.author = Author.first comment.resource = Member.first @@ -756,7 +773,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_polymorphic_assignment_with_primary_key_foreign_type_field_updating # should update when assigning a saved record essay = Essay.new - writer = Author.create(:name => "David") + writer = Author.create(name: "David") essay.writer = writer assert_equal "Author", essay.writer_type @@ -781,7 +798,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_assignment_updates_foreign_id_field_for_new_and_saved_records client = Client.new - saved_firm = Firm.create :name => "Saved" + saved_firm = Firm.create name: "Saved" new_firm = Firm.new client.firm = saved_firm @@ -793,7 +810,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_polymorphic_assignment_with_primary_key_updates_foreign_id_field_for_new_and_saved_records essay = Essay.new - saved_writer = Author.create(:name => "David") + saved_writer = Author.create(name: "David") new_writer = Author.new essay.writer = saved_writer @@ -809,7 +826,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_nil essay.writer_type essay.writer_id = 1 - essay.writer_type = 'Author' + essay.writer_type = "Author" essay.writer = nil assert_nil essay.writer_id @@ -831,14 +848,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_nothing_raised do Account.find(@account.id).save! - Account.all.merge!(:includes => :firm).find(@account.id).save! + Account.all.merge!(includes: :firm).find(@account.id).save! end @account.firm.delete assert_nothing_raised do Account.find(@account.id).save! - Account.all.merge!(:includes => :firm).find(@account.id).save! + Account.all.merge!(includes: :firm).find(@account.id).save! end end @@ -859,18 +876,18 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_belongs_to_invalid_dependent_option_raises_exception error = assert_raise ArgumentError do - Class.new(Author).belongs_to :special_author_address, :dependent => :nullify + Class.new(Author).belongs_to :special_author_address, dependent: :nullify end - assert_equal error.message, 'The :dependent option must be one of [:destroy, :delete], but is :nullify' + assert_equal error.message, "The :dependent option must be one of [:destroy, :delete], but is :nullify" end def test_attributes_are_being_set_when_initialized_from_belongs_to_association_with_where_clause - new_firm = accounts(:signals37).build_firm(:name => 'Apple') + new_firm = accounts(:signals37).build_firm(name: "Apple") assert_equal new_firm.name, "Apple" end def test_attributes_are_set_without_error_when_initialized_from_belongs_to_association_with_array_in_where_clause - new_account = Account.where(:credit_limit => [ 50, 60 ]).new + new_account = Account.where(credit_limit: [ 50, 60 ]).new assert_nil new_account.credit_limit end @@ -919,7 +936,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert !proxy.stale_target? assert_equal members(:groucho), sponsor.sponsorable - sponsor.sponsorable_type = 'Firm' + sponsor.sponsorable_type = "Firm" assert proxy.stale_target? assert_equal companies(:first_firm), sponsor.sponsorable @@ -944,7 +961,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase comment = comments(:greetings) assert_difference lambda { post.reload.tags_count }, -1 do - assert_difference 'comment.reload.tags_count', +1 do + assert_difference "comment.reload.tags_count", +1 do tagging.taggable = comment end end @@ -991,42 +1008,42 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end def test_build_with_block - client = Client.create(:name => 'Client Company') + client = Client.create(name: "Client Company") - firm = client.build_firm{ |f| f.name = 'Agency Company' } - assert_equal 'Agency Company', firm.name + firm = client.build_firm { |f| f.name = "Agency Company" } + assert_equal "Agency Company", firm.name end def test_create_with_block - client = Client.create(:name => 'Client Company') + client = Client.create(name: "Client Company") - firm = client.create_firm{ |f| f.name = 'Agency Company' } - assert_equal 'Agency Company', firm.name + firm = client.create_firm { |f| f.name = "Agency Company" } + assert_equal "Agency Company", firm.name end def test_create_bang_with_block - client = Client.create(:name => 'Client Company') + client = Client.create(name: "Client Company") - firm = client.create_firm!{ |f| f.name = 'Agency Company' } - assert_equal 'Agency Company', firm.name + firm = client.create_firm! { |f| f.name = "Agency Company" } + assert_equal "Agency Company", firm.name end def test_should_set_foreign_key_on_create_association - client = Client.create! :name => "fuu" + client = Client.create! name: "fuu" - firm = client.create_firm :name => "baa" + firm = client.create_firm name: "baa" assert_equal firm.id, client.client_of end def test_should_set_foreign_key_on_create_association! - client = Client.create! :name => "fuu" + client = Client.create! name: "fuu" - firm = client.create_firm! :name => "baa" + firm = client.create_firm! name: "baa" assert_equal firm.id, client.client_of end def test_self_referential_belongs_to_with_counter_cache_assigning_nil - comment = Comment.create! :post => posts(:thinking), :body => "fuu" + comment = Comment.create! post: posts(:thinking), body: "fuu" comment.parent = nil comment.save! @@ -1047,7 +1064,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_polymorphic_with_custom_primary_key toy = Toy.create! - sponsor = Sponsor.create!(:sponsorable => toy) + sponsor = Sponsor.create!(sponsorable: toy) assert_equal toy, sponsor.reload.sponsorable end @@ -1066,7 +1083,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_reflect_the_most_recent_change author1, author2 = Author.limit(2) - post = Post.new(:title => "foo", :body=> "bar") + post = Post.new(title: "foo", body: "bar") post.author = author1 post.author_id = author2.id @@ -1075,8 +1092,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal post.author_id, author2.id end - test 'dangerous association name raises ArgumentError' do - [:errors, 'errors', :save, 'save'].each do |name| + test "dangerous association name raises ArgumentError" do + [:errors, "errors", :save, "save"].each do |name| assert_raises(ArgumentError, "Association #{name} should not be allowed") do Class.new(ActiveRecord::Base) do belongs_to name @@ -1085,7 +1102,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end end - test 'belongs_to works with model called Record' do + test "belongs_to works with model called Record" do record = Record.create! Column.create! record: record assert_equal 1, Column.count diff --git a/activerecord/test/cases/associations/bidirectional_destroy_dependencies_test.rb b/activerecord/test/cases/associations/bidirectional_destroy_dependencies_test.rb index 2b867965ba..8a0e041864 100644 --- a/activerecord/test/cases/associations/bidirectional_destroy_dependencies_test.rb +++ b/activerecord/test/cases/associations/bidirectional_destroy_dependencies_test.rb @@ -1,5 +1,5 @@ -require 'cases/helper' -require 'models/content' +require "cases/helper" +require "models/content" class BidirectionalDestroyDependenciesTest < ActiveRecord::TestCase fixtures :content, :content_positions diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb index a4298a25a6..2f62d0367e 100644 --- a/activerecord/test/cases/associations/callbacks_test.rb +++ b/activerecord/test/cases/associations/callbacks_test.rb @@ -1,10 +1,10 @@ require "cases/helper" -require 'models/post' -require 'models/author' -require 'models/project' -require 'models/developer' -require 'models/computer' -require 'models/company' +require "models/post" +require "models/author" +require "models/project" +require "models/developer" +require "models/computer" +require "models/company" class AssociationCallbacksTest < ActiveRecord::TestCase fixtures :posts, :authors, :projects, :developers @@ -61,20 +61,20 @@ class AssociationCallbacksTest < ActiveRecord::TestCase end def test_has_many_callbacks_with_create - morten = Author.create :name => "Morten" - post = morten.posts_with_proc_callbacks.create! :title => "Hello", :body => "How are you doing?" + morten = Author.create name: "Morten" + post = morten.posts_with_proc_callbacks.create! title: "Hello", body: "How are you doing?" assert_equal ["before_adding<new>", "after_adding#{post.id}"], morten.post_log end def test_has_many_callbacks_with_create! - morten = Author.create! :name => "Morten" - post = morten.posts_with_proc_callbacks.create :title => "Hello", :body => "How are you doing?" + morten = Author.create! name: "Morten" + post = morten.posts_with_proc_callbacks.create title: "Hello", body: "How are you doing?" assert_equal ["before_adding<new>", "after_adding#{post.id}"], morten.post_log end def test_has_many_callbacks_for_save_on_parent - jack = Author.new :name => "Jack" - jack.posts_with_callbacks.build :title => "Call me back!", :body => "Before you wake up and after you sleep" + jack = Author.new name: "Jack" + jack.posts_with_callbacks.build title: "Call me back!", body: "Before you wake up and after you sleep" callback_log = ["before_adding<new>", "after_adding#{jack.posts_with_callbacks.first.id}"] assert_equal callback_log, jack.post_log @@ -84,8 +84,8 @@ class AssociationCallbacksTest < ActiveRecord::TestCase end def test_has_many_callbacks_for_destroy_on_parent - firm = Firm.create! :name => "Firm" - client = firm.clients.create! :name => "Client" + firm = Firm.create! name: "Firm" + client = firm.clients.create! name: "Client" firm.destroy assert_equal ["before_remove#{client.id}", "after_remove#{client.id}"], firm.log @@ -108,14 +108,14 @@ class AssociationCallbacksTest < ActiveRecord::TestCase klass = Class.new(Project) do def self.name; Project.name; end has_and_belongs_to_many :developers_with_callbacks, - :class_name => "Developer", - :before_add => lambda { |o,r| + class_name: "Developer", + before_add: lambda { |o,r| dev = r new_dev = r.new_record? } end rec = klass.create! - alice = Developer.new(:name => 'alice') + alice = Developer.new(name: "alice") rec.developers_with_callbacks << alice assert_equal alice, dev assert_not_nil new_dev @@ -126,18 +126,17 @@ class AssociationCallbacksTest < ActiveRecord::TestCase def test_has_and_belongs_to_many_after_add_called_after_save ar = projects(:active_record) assert ar.developers_log.empty? - alice = Developer.new(:name => 'alice') + alice = Developer.new(name: "alice") ar.developers_with_callbacks << alice assert_equal"after_adding#{alice.id}", ar.developers_log.last - bob = ar.developers_with_callbacks.create(:name => 'bob') + bob = ar.developers_with_callbacks.create(name: "bob") assert_equal "after_adding#{bob.id}", ar.developers_log.last - ar.developers_with_callbacks.build(:name => 'charlie') + ar.developers_with_callbacks.build(name: "charlie") assert_equal "after_adding<new>", ar.developers_log.last end - def test_has_and_belongs_to_many_remove_callback david = developers(:david) jamis = developers(:jamis) @@ -160,14 +159,14 @@ class AssociationCallbacksTest < ActiveRecord::TestCase activerecord.reload assert activerecord.developers_with_callbacks.size == 2 end - activerecord.developers_with_callbacks.flat_map {|d| ["before_removing#{d.id}","after_removing#{d.id}"]}.sort + activerecord.developers_with_callbacks.flat_map { |d| ["before_removing#{d.id}","after_removing#{d.id}"] }.sort assert activerecord.developers_with_callbacks.clear assert_predicate activerecord.developers_log, :empty? end def test_has_many_and_belongs_to_many_callbacks_for_save_on_parent - project = Project.new :name => "Callbacks" - project.developers_with_callbacks.build :name => "Jack", :salary => 95000 + project = Project.new name: "Callbacks" + project.developers_with_callbacks.build name: "Jack", salary: 95000 callback_log = ["before_adding<new>", "after_adding<new>"] assert_equal callback_log, project.developers_log @@ -177,14 +176,14 @@ class AssociationCallbacksTest < ActiveRecord::TestCase end def test_dont_add_if_before_callback_raises_exception - assert !@david.unchangeable_posts.include?(@authorless) + assert_not_includes @david.unchangeable_posts, @authorless begin @david.unchangeable_posts << @authorless rescue Exception end assert @david.post_log.empty? - assert !@david.unchangeable_posts.include?(@authorless) + assert_not_includes @david.unchangeable_posts, @authorless @david.reload - assert !@david.unchangeable_posts.include?(@authorless) + assert_not_includes @david.unchangeable_posts, @authorless end end diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index 51d8e0523e..e87431bf32 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -1,56 +1,56 @@ require "cases/helper" -require 'models/post' -require 'models/comment' -require 'models/author' -require 'models/categorization' -require 'models/category' -require 'models/company' -require 'models/topic' -require 'models/reply' -require 'models/person' -require 'models/vertex' -require 'models/edge' +require "models/post" +require "models/comment" +require "models/author" +require "models/categorization" +require "models/category" +require "models/company" +require "models/topic" +require "models/reply" +require "models/person" +require "models/vertex" +require "models/edge" class CascadedEagerLoadingTest < ActiveRecord::TestCase fixtures :authors, :mixins, :companies, :posts, :topics, :accounts, :comments, :categorizations, :people, :categories, :edges, :vertices def test_eager_association_loading_with_cascaded_two_levels - authors = Author.all.merge!(:includes=>{:posts=>:comments}, :order=>"authors.id").to_a + authors = Author.all.merge!(includes: { posts: :comments }, order: "authors.id").to_a assert_equal 3, authors.size assert_equal 5, authors[0].posts.size assert_equal 3, authors[1].posts.size - assert_equal 10, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i} + assert_equal 10, authors[0].posts.collect { |post| post.comments.size }.inject(0) { |sum,i| sum+i } end def test_eager_association_loading_with_cascaded_two_levels_and_one_level - authors = Author.all.merge!(:includes=>[{:posts=>:comments}, :categorizations], :order=>"authors.id").to_a + authors = Author.all.merge!(includes: [{ posts: :comments }, :categorizations], order: "authors.id").to_a assert_equal 3, authors.size assert_equal 5, authors[0].posts.size assert_equal 3, authors[1].posts.size - assert_equal 10, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i} + assert_equal 10, authors[0].posts.collect { |post| post.comments.size }.inject(0) { |sum,i| sum+i } assert_equal 1, authors[0].categorizations.size assert_equal 2, authors[1].categorizations.size end def test_eager_association_loading_with_hmt_does_not_table_name_collide_when_joining_associations assert_nothing_raised do - Author.joins(:posts).eager_load(:comments).where(:posts => {:tags_count => 1}).to_a + Author.joins(:posts).eager_load(:comments).where(posts: { tags_count: 1 }).to_a end - authors = Author.joins(:posts).eager_load(:comments).where(:posts => {:tags_count => 1}).to_a + authors = Author.joins(:posts).eager_load(:comments).where(posts: { tags_count: 1 }).to_a assert_equal 1, assert_no_queries { authors.size } assert_equal 10, assert_no_queries { authors[0].comments.size } end def test_eager_association_loading_grafts_stashed_associations_to_correct_parent assert_nothing_raised do - Person.eager_load(:primary_contact => :primary_contact).where('primary_contacts_people_2.first_name = ?', 'Susan').order('people.id').to_a + Person.eager_load(primary_contact: :primary_contact).where("primary_contacts_people_2.first_name = ?", "Susan").order("people.id").to_a end - assert_equal people(:michael), Person.eager_load(:primary_contact => :primary_contact).where('primary_contacts_people_2.first_name = ?', 'Susan').order('people.id').first + assert_equal people(:michael), Person.eager_load(primary_contact: :primary_contact).where("primary_contacts_people_2.first_name = ?", "Susan").order("people.id").first end def test_cascaded_eager_association_loading_with_join_for_count - categories = Category.joins(:categorizations).includes([{:posts=>:comments}, :authors]) + categories = Category.joins(:categorizations).includes([{ posts: :comments }, :authors]) assert_equal 4, categories.count assert_equal 4, categories.to_a.count @@ -59,7 +59,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_cascaded_eager_association_loading_with_duplicated_includes - categories = Category.includes(:categorizations).includes(:categorizations => :author).where("categorizations.id is not null").references(:categorizations) + categories = Category.includes(:categorizations).includes(categorizations: :author).where("categorizations.id is not null").references(:categorizations) assert_nothing_raised do assert_equal 3, categories.count assert_equal 3, categories.to_a.size @@ -67,7 +67,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_cascaded_eager_association_loading_with_twice_includes_edge_cases - categories = Category.includes(:categorizations => :author).includes(:categorizations => :post).where("posts.id is not null").references(:posts) + categories = Category.includes(categorizations: :author).includes(categorizations: :post).where("posts.id is not null").references(:posts) assert_nothing_raised do assert_equal 3, categories.count assert_equal 3, categories.to_a.size @@ -82,29 +82,29 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_cascaded_two_levels_with_two_has_many_associations - authors = Author.all.merge!(:includes=>{:posts=>[:comments, :categorizations]}, :order=>"authors.id").to_a + authors = Author.all.merge!(includes: { posts: [:comments, :categorizations] }, order: "authors.id").to_a assert_equal 3, authors.size assert_equal 5, authors[0].posts.size assert_equal 3, authors[1].posts.size - assert_equal 10, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i} + assert_equal 10, authors[0].posts.collect { |post| post.comments.size }.inject(0) { |sum,i| sum+i } end def test_eager_association_loading_with_cascaded_two_levels_and_self_table_reference - authors = Author.all.merge!(:includes=>{:posts=>[:comments, :author]}, :order=>"authors.id").to_a + authors = Author.all.merge!(includes: { posts: [:comments, :author] }, order: "authors.id").to_a assert_equal 3, authors.size assert_equal 5, authors[0].posts.size assert_equal authors(:david).name, authors[0].name - assert_equal [authors(:david).name], authors[0].posts.collect{|post| post.author.name}.uniq + assert_equal [authors(:david).name], authors[0].posts.collect { |post| post.author.name }.uniq end def test_eager_association_loading_with_cascaded_two_levels_with_condition - authors = Author.all.merge!(:includes=>{:posts=>:comments}, :where=>"authors.id=1", :order=>"authors.id").to_a + authors = Author.all.merge!(includes: { posts: :comments }, where: "authors.id=1", order: "authors.id").to_a assert_equal 1, authors.size assert_equal 5, authors[0].posts.size end def test_eager_association_loading_with_cascaded_three_levels_by_ping_pong - firms = Firm.all.merge!(:includes=>{:account=>{:firm=>:account}}, :order=>"companies.id").to_a + firms = Firm.all.merge!(includes: { account: { firm: :account } }, order: "companies.id").to_a assert_equal 2, firms.size assert_equal firms.first.account, firms.first.account.firm.account assert_equal companies(:first_firm).account, assert_no_queries { firms.first.account.firm.account } @@ -112,7 +112,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_has_many_sti - topics = Topic.all.merge!(:includes => :replies, :order => 'topics.id').to_a + topics = Topic.all.merge!(includes: :replies, order: "topics.id").to_a first, second, = topics(:first).replies.size, topics(:second).replies.size assert_no_queries do assert_equal first, topics[0].replies.size @@ -121,11 +121,11 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_has_many_sti_and_subclasses - silly = SillyReply.new(:title => "gaga", :content => "boo-boo", :parent_id => 1) + silly = SillyReply.new(title: "gaga", content: "boo-boo", parent_id: 1) silly.parent_id = 1 assert silly.save - topics = Topic.all.merge!(:includes => :replies, :order => ['topics.id', 'replies_topics.id']).to_a + topics = Topic.all.merge!(includes: :replies, order: ["topics.id", "replies_topics.id"]).to_a assert_no_queries do assert_equal 2, topics[0].replies.size assert_equal 0, topics[1].replies.size @@ -133,14 +133,14 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_belongs_to_sti - replies = Reply.all.merge!(:includes => :topic, :order => 'topics.id').to_a - assert replies.include?(topics(:second)) - assert !replies.include?(topics(:first)) + replies = Reply.all.merge!(includes: :topic, order: "topics.id").to_a + assert_includes replies, topics(:second) + assert_not_includes replies, topics(:first) assert_equal topics(:first), assert_no_queries { replies.first.topic } end def test_eager_association_loading_with_multiple_stis_and_order - author = Author.all.merge!(:includes => { :posts => [ :special_comments , :very_special_comment ] }, :order => ['authors.name', 'comments.body', 'very_special_comments_posts.body'], :where => 'posts.id = 4').first + author = Author.all.merge!(includes: { posts: [ :special_comments , :very_special_comment ] }, order: ["authors.name", "comments.body", "very_special_comments_posts.body"], where: "posts.id = 4").first assert_equal authors(:david), author assert_no_queries do author.posts.first.special_comments @@ -149,7 +149,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_of_stis_with_multiple_references - authors = Author.all.merge!(:includes => { :posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } } }, :order => 'comments.body, very_special_comments_posts.body', :where => 'posts.id = 4').to_a + authors = Author.all.merge!(includes: { posts: { special_comments: { post: [ :special_comments, :very_special_comment ] } } }, order: "comments.body, very_special_comments_posts.body", where: "posts.id = 4").to_a assert_equal [authors(:david)], authors assert_no_queries do authors.first.posts.first.special_comments.first.post.special_comments @@ -158,7 +158,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_where_first_level_returns_nil - authors = Author.all.merge!(:includes => {:post_about_thinking => :comments}, :order => 'authors.id DESC').to_a + authors = Author.all.merge!(includes: { post_about_thinking: :comments }, order: "authors.id DESC").to_a assert_equal [authors(:bob), authors(:mary), authors(:david)], authors assert_no_queries do authors[2].post_about_thinking.comments.first @@ -166,12 +166,12 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_recursive_cascading_four_levels_has_many_through - source = Vertex.all.merge!(:includes=>{:sinks=>{:sinks=>{:sinks=>:sinks}}}, :order => 'vertices.id').first + source = Vertex.all.merge!(includes: { sinks: { sinks: { sinks: :sinks } } }, order: "vertices.id").first assert_equal vertices(:vertex_4), assert_no_queries { source.sinks.first.sinks.first.sinks.first } end def test_eager_association_loading_with_recursive_cascading_four_levels_has_and_belongs_to_many - sink = Vertex.all.merge!(:includes=>{:sources=>{:sources=>{:sources=>:sources}}}, :order => 'vertices.id DESC').first + sink = Vertex.all.merge!(includes: { sources: { sources: { sources: :sources } } }, order: "vertices.id DESC").first assert_equal vertices(:vertex_1), assert_no_queries { sink.sources.first.sources.first.sources.first.sources.first } end diff --git a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb index 75a6295350..aa82b9dd2a 100644 --- a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb +++ b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb @@ -1,34 +1,33 @@ -require 'cases/helper' -require 'models/post' -require 'models/tagging' +require "cases/helper" +require "models/post" +require "models/tagging" module Namespaced class Post < ActiveRecord::Base - self.table_name = 'posts' - has_one :tagging, :as => :taggable, :class_name => 'Tagging' + self.table_name = "posts" + has_one :tagging, as: :taggable, class_name: "Tagging" end end class EagerLoadIncludeFullStiClassNamesTest < ActiveRecord::TestCase - def setup generate_test_objects end def generate_test_objects - post = Namespaced::Post.create( :title => 'Great stuff', :body => 'This is not', :author_id => 1 ) - Tagging.create( :taggable => post ) + post = Namespaced::Post.create( title: "Great stuff", body: "This is not", author_id: 1 ) + Tagging.create( taggable: post ) end def test_class_names old = ActiveRecord::Base.store_full_sti_class ActiveRecord::Base.store_full_sti_class = false - post = Namespaced::Post.includes(:tagging).find_by_title('Great stuff') + post = Namespaced::Post.includes(:tagging).find_by_title("Great stuff") assert_nil post.tagging ActiveRecord::Base.store_full_sti_class = true - post = Namespaced::Post.includes(:tagging).find_by_title('Great stuff') + post = Namespaced::Post.includes(:tagging).find_by_title("Great stuff") assert_instance_of Tagging, post.tagging ensure ActiveRecord::Base.store_full_sti_class = old 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 f571198079..a7a8c6a783 100644 --- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb +++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb @@ -1,11 +1,11 @@ -require 'cases/helper' -require 'models/post' -require 'models/tag' -require 'models/author' -require 'models/comment' -require 'models/category' -require 'models/categorization' -require 'models/tagging' +require "cases/helper" +require "models/post" +require "models/tag" +require "models/author" +require "models/comment" +require "models/category" +require "models/categorization" +require "models/tagging" module Remembered extend ActiveSupport::Concern @@ -23,30 +23,30 @@ module Remembered end class ShapeExpression < ActiveRecord::Base - belongs_to :shape, :polymorphic => true - belongs_to :paint, :polymorphic => true + belongs_to :shape, polymorphic: true + belongs_to :paint, polymorphic: true end class Circle < ActiveRecord::Base - has_many :shape_expressions, :as => :shape + has_many :shape_expressions, as: :shape include Remembered end class Square < ActiveRecord::Base - has_many :shape_expressions, :as => :shape + has_many :shape_expressions, as: :shape include Remembered end class Triangle < ActiveRecord::Base - has_many :shape_expressions, :as => :shape + has_many :shape_expressions, as: :shape include Remembered end class PaintColor < ActiveRecord::Base - has_many :shape_expressions, :as => :paint - belongs_to :non_poly, :foreign_key => "non_poly_one_id", :class_name => "NonPolyOne" + has_many :shape_expressions, as: :paint + belongs_to :non_poly, foreign_key: "non_poly_one_id", class_name: "NonPolyOne" include Remembered end class PaintTexture < ActiveRecord::Base - has_many :shape_expressions, :as => :paint - belongs_to :non_poly, :foreign_key => "non_poly_two_id", :class_name => "NonPolyTwo" + has_many :shape_expressions, as: :paint + belongs_to :non_poly, foreign_key: "non_poly_two_id", class_name: "NonPolyTwo" include Remembered end class NonPolyOne < ActiveRecord::Base @@ -58,8 +58,6 @@ class NonPolyTwo < ActiveRecord::Base include Remembered end - - class EagerLoadPolyAssocsTest < ActiveRecord::TestCase NUM_SIMPLE_OBJS = 50 NUM_SHAPE_EXPRESSIONS = 100 @@ -78,19 +76,19 @@ class EagerLoadPolyAssocsTest < ActiveRecord::TestCase [Circle, Square, Triangle, NonPolyOne, NonPolyTwo].map(&:create!) end 1.upto(NUM_SIMPLE_OBJS) do - PaintColor.create!(:non_poly_one_id => NonPolyOne.sample.id) - PaintTexture.create!(:non_poly_two_id => NonPolyTwo.sample.id) + PaintColor.create!(non_poly_one_id: NonPolyOne.sample.id) + PaintTexture.create!(non_poly_two_id: NonPolyTwo.sample.id) end 1.upto(NUM_SHAPE_EXPRESSIONS) do shape_type = [Circle, Square, Triangle].sample paint_type = [PaintColor, PaintTexture].sample - ShapeExpression.create!(:shape_type => shape_type.to_s, :shape_id => shape_type.sample.id, - :paint_type => paint_type.to_s, :paint_id => paint_type.sample.id) + ShapeExpression.create!(shape_type: shape_type.to_s, shape_id: shape_type.sample.id, + paint_type: paint_type.to_s, paint_id: paint_type.sample.id) end end def test_include_query - res = ShapeExpression.all.merge!(:includes => [ :shape, { :paint => :non_poly } ]).to_a + res = ShapeExpression.all.merge!(includes: [ :shape, { paint: :non_poly } ]).to_a assert_equal NUM_SHAPE_EXPRESSIONS, res.size assert_queries(0) do res.each do |se| @@ -103,10 +101,10 @@ end class EagerLoadNestedIncludeWithMissingDataTest < ActiveRecord::TestCase def setup - @davey_mcdave = Author.create(:name => 'Davey McDave') - @first_post = @davey_mcdave.posts.create(:title => 'Davey Speaks', :body => 'Expressive wordage') - @first_comment = @first_post.comments.create(:body => 'Inflamatory doublespeak') - @first_categorization = @davey_mcdave.categorizations.create(:category => Category.first, :post => @first_post) + @davey_mcdave = Author.create(name: "Davey McDave") + @first_post = @davey_mcdave.posts.create(title: "Davey Speaks", body: "Expressive wordage") + @first_comment = @first_post.comments.create(body: "Inflamatory doublespeak") + @first_categorization = @davey_mcdave.categorizations.create(category: Category.first, post: @first_post) end teardown do @@ -119,8 +117,8 @@ class EagerLoadNestedIncludeWithMissingDataTest < ActiveRecord::TestCase def test_missing_data_in_a_nested_include_should_not_cause_errors_when_constructing_objects assert_nothing_raised do # @davey_mcdave doesn't have any author_favorites - includes = {:posts => :comments, :categorizations => :category, :author_favorites => :favorite_author } - Author.all.merge!(:includes => includes, :where => {:authors => {:name => @davey_mcdave.name}}, :order => 'categories.name').to_a + includes = { posts: :comments, categorizations: :category, author_favorites: :favorite_author } + Author.all.merge!(includes: includes, where: { authors: { name: @davey_mcdave.name } }, order: "categories.name").to_a end end end diff --git a/activerecord/test/cases/associations/eager_singularization_test.rb b/activerecord/test/cases/associations/eager_singularization_test.rb index a61a070331..5d1c1c4b9b 100644 --- a/activerecord/test/cases/associations/eager_singularization_test.rb +++ b/activerecord/test/cases/associations/eager_singularization_test.rb @@ -1,148 +1,147 @@ require "cases/helper" - if ActiveRecord::Base.connection.supports_migrations? -class EagerSingularizationTest < ActiveRecord::TestCase - class Virus < ActiveRecord::Base - belongs_to :octopus - end - - class Octopus < ActiveRecord::Base - has_one :virus - end - - class Pass < ActiveRecord::Base - belongs_to :bus - end - - class Bus < ActiveRecord::Base - has_many :passes - end - - class Mess < ActiveRecord::Base - has_and_belongs_to_many :crises - end - - class Crisis < ActiveRecord::Base - has_and_belongs_to_many :messes - has_many :analyses, :dependent => :destroy - has_many :successes, :through => :analyses - has_many :dresses, :dependent => :destroy - has_many :compresses, :through => :dresses - end - - class Analysis < ActiveRecord::Base - belongs_to :crisis - belongs_to :success - end - - class Success < ActiveRecord::Base - has_many :analyses, :dependent => :destroy - has_many :crises, :through => :analyses - end + class EagerSingularizationTest < ActiveRecord::TestCase + class Virus < ActiveRecord::Base + belongs_to :octopus + end + + class Octopus < ActiveRecord::Base + has_one :virus + end + + class Pass < ActiveRecord::Base + belongs_to :bus + end - class Dress < ActiveRecord::Base - belongs_to :crisis - has_many :compresses - end + class Bus < ActiveRecord::Base + has_many :passes + end - class Compress < ActiveRecord::Base - belongs_to :dress - end - - def setup - connection.create_table :viri do |t| - t.column :octopus_id, :integer - t.column :species, :string - end - connection.create_table :octopi do |t| - t.column :species, :string + class Mess < ActiveRecord::Base + has_and_belongs_to_many :crises end - connection.create_table :passes do |t| - t.column :bus_id, :integer - t.column :rides, :integer - end - connection.create_table :buses do |t| - t.column :name, :string - end - connection.create_table :crises_messes, :id => false do |t| - t.column :crisis_id, :integer - t.column :mess_id, :integer - end - connection.create_table :messes do |t| - t.column :name, :string - end - connection.create_table :crises do |t| - t.column :name, :string - end - connection.create_table :successes do |t| - t.column :name, :string - end - connection.create_table :analyses do |t| - t.column :crisis_id, :integer - t.column :success_id, :integer - end - connection.create_table :dresses do |t| - t.column :crisis_id, :integer + + class Crisis < ActiveRecord::Base + has_and_belongs_to_many :messes + has_many :analyses, dependent: :destroy + has_many :successes, through: :analyses + has_many :dresses, dependent: :destroy + has_many :compresses, through: :dresses end - connection.create_table :compresses do |t| - t.column :dress_id, :integer + + class Analysis < ActiveRecord::Base + belongs_to :crisis + belongs_to :success end - end - teardown do - connection.drop_table :viri - connection.drop_table :octopi - connection.drop_table :passes - connection.drop_table :buses - connection.drop_table :crises_messes - connection.drop_table :messes - connection.drop_table :crises - connection.drop_table :successes - connection.drop_table :analyses - connection.drop_table :dresses - connection.drop_table :compresses - end + class Success < ActiveRecord::Base + has_many :analyses, dependent: :destroy + has_many :crises, through: :analyses + end - def connection - ActiveRecord::Base.connection - end + class Dress < ActiveRecord::Base + belongs_to :crisis + has_many :compresses + end - def test_eager_no_extra_singularization_belongs_to - assert_nothing_raised do - Virus.all.merge!(:includes => :octopus).to_a + class Compress < ActiveRecord::Base + belongs_to :dress end - end - def test_eager_no_extra_singularization_has_one - assert_nothing_raised do - Octopus.all.merge!(:includes => :virus).to_a + def setup + connection.create_table :viri do |t| + t.column :octopus_id, :integer + t.column :species, :string + end + connection.create_table :octopi do |t| + t.column :species, :string + end + connection.create_table :passes do |t| + t.column :bus_id, :integer + t.column :rides, :integer + end + connection.create_table :buses do |t| + t.column :name, :string + end + connection.create_table :crises_messes, id: false do |t| + t.column :crisis_id, :integer + t.column :mess_id, :integer + end + connection.create_table :messes do |t| + t.column :name, :string + end + connection.create_table :crises do |t| + t.column :name, :string + end + connection.create_table :successes do |t| + t.column :name, :string + end + connection.create_table :analyses do |t| + t.column :crisis_id, :integer + t.column :success_id, :integer + end + connection.create_table :dresses do |t| + t.column :crisis_id, :integer + end + connection.create_table :compresses do |t| + t.column :dress_id, :integer + end end - end - def test_eager_no_extra_singularization_has_many - assert_nothing_raised do - Bus.all.merge!(:includes => :passes).to_a + teardown do + connection.drop_table :viri + connection.drop_table :octopi + connection.drop_table :passes + connection.drop_table :buses + connection.drop_table :crises_messes + connection.drop_table :messes + connection.drop_table :crises + connection.drop_table :successes + connection.drop_table :analyses + connection.drop_table :dresses + connection.drop_table :compresses end - end - def test_eager_no_extra_singularization_has_and_belongs_to_many - assert_nothing_raised do - Crisis.all.merge!(:includes => :messes).to_a - Mess.all.merge!(:includes => :crises).to_a + def connection + ActiveRecord::Base.connection end - end - def test_eager_no_extra_singularization_has_many_through_belongs_to - assert_nothing_raised do - Crisis.all.merge!(:includes => :successes).to_a + def test_eager_no_extra_singularization_belongs_to + assert_nothing_raised do + Virus.all.merge!(includes: :octopus).to_a + end end - end - def test_eager_no_extra_singularization_has_many_through_has_many - assert_nothing_raised do - Crisis.all.merge!(:includes => :compresses).to_a + def test_eager_no_extra_singularization_has_one + assert_nothing_raised do + Octopus.all.merge!(includes: :virus).to_a + end + end + + def test_eager_no_extra_singularization_has_many + assert_nothing_raised do + Bus.all.merge!(includes: :passes).to_a + end + end + + def test_eager_no_extra_singularization_has_and_belongs_to_many + assert_nothing_raised do + Crisis.all.merge!(includes: :messes).to_a + Mess.all.merge!(includes: :crises).to_a + end + end + + def test_eager_no_extra_singularization_has_many_through_belongs_to + assert_nothing_raised do + Crisis.all.merge!(includes: :successes).to_a + end + end + + def test_eager_no_extra_singularization_has_many_through_has_many + assert_nothing_raised do + Crisis.all.merge!(includes: :compresses).to_a + end end end end -end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 874d53c51f..d1c4c1cef8 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -1,31 +1,31 @@ require "cases/helper" -require 'models/post' -require 'models/tagging' -require 'models/tag' -require 'models/comment' -require 'models/author' -require 'models/essay' -require 'models/category' -require 'models/company' -require 'models/person' -require 'models/reader' -require 'models/owner' -require 'models/pet' -require 'models/reference' -require 'models/job' -require 'models/subscriber' -require 'models/subscription' -require 'models/book' -require 'models/developer' -require 'models/computer' -require 'models/project' -require 'models/member' -require 'models/membership' -require 'models/club' -require 'models/categorization' -require 'models/sponsor' -require 'models/mentor' -require 'models/contract' +require "models/post" +require "models/tagging" +require "models/tag" +require "models/comment" +require "models/author" +require "models/essay" +require "models/category" +require "models/company" +require "models/person" +require "models/reader" +require "models/owner" +require "models/pet" +require "models/reference" +require "models/job" +require "models/subscriber" +require "models/subscription" +require "models/book" +require "models/developer" +require "models/computer" +require "models/project" +require "models/member" +require "models/membership" +require "models/club" +require "models/categorization" +require "models/sponsor" +require "models/mentor" +require "models/contract" class EagerAssociationTest < ActiveRecord::TestCase fixtures :posts, :comments, :authors, :essays, :author_addresses, :categories, :categories_posts, @@ -34,42 +34,42 @@ class EagerAssociationTest < ActiveRecord::TestCase :developers, :projects, :developers_projects, :members, :memberships, :clubs, :sponsors def test_eager_with_has_one_through_join_model_with_conditions_on_the_through - member = Member.all.merge!(:includes => :favourite_club).find(members(:some_other_guy).id) + member = Member.all.merge!(includes: :favourite_club).find(members(:some_other_guy).id) assert_nil member.favourite_club end def test_loading_with_one_association - posts = Post.all.merge!(:includes => :comments).to_a + posts = Post.all.merge!(includes: :comments).to_a post = posts.find { |p| p.id == 1 } assert_equal 2, post.comments.size - assert post.comments.include?(comments(:greetings)) + assert_includes post.comments, comments(:greetings) - post = Post.all.merge!(:includes => :comments, :where => "posts.title = 'Welcome to the weblog'").first + post = Post.all.merge!(includes: :comments, where: "posts.title = 'Welcome to the weblog'").first assert_equal 2, post.comments.size - assert post.comments.include?(comments(:greetings)) + assert_includes post.comments, comments(:greetings) - posts = Post.all.merge!(:includes => :last_comment).to_a + posts = Post.all.merge!(includes: :last_comment).to_a post = posts.find { |p| p.id == 1 } assert_equal Post.find(1).last_comment, post.last_comment end def test_loading_with_one_association_with_non_preload - posts = Post.all.merge!(:includes => :last_comment, :order => 'comments.id DESC').to_a + posts = Post.all.merge!(includes: :last_comment, order: "comments.id DESC").to_a post = posts.find { |p| p.id == 1 } assert_equal Post.find(1).last_comment, post.last_comment end def test_loading_conditions_with_or posts = authors(:david).posts.references(:comments).merge( - :includes => :comments, - :where => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE} = 'SpecialComment'" + includes: :comments, + where: "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE} = 'SpecialComment'" ).to_a assert_nil posts.detect { |p| p.author_id != authors(:david).id }, "expected to find only david's posts" end def test_with_ordering - list = Post.all.merge!(:includes => :comments, :order => "posts.id DESC").to_a + list = Post.all.merge!(includes: :comments, order: "posts.id DESC").to_a [:other_by_mary, :other_by_bob, :misc_by_mary, :misc_by_bob, :eager_other, :sti_habtm, :sti_post_and_comments, :sti_comments, :authorless, :thinking, :welcome ].each_with_index do |post, index| @@ -90,49 +90,53 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_no_queries { authors.map(&:post) } end + def test_calculate_with_string_in_from_and_eager_loading + assert_equal 10, Post.from("authors, posts").eager_load(:comments).where("posts.author_id = authors.id").count + end + def test_with_two_tables_in_from_without_getting_double_quoted posts = Post.select("posts.*").from("authors, posts").eager_load(:comments).where("posts.author_id = authors.id").order("posts.id").to_a assert_equal 2, posts.first.comments.size end def test_loading_with_multiple_associations - posts = Post.all.merge!(:includes => [ :comments, :author, :categories ], :order => "posts.id").to_a + posts = Post.all.merge!(includes: [ :comments, :author, :categories ], order: "posts.id").to_a assert_equal 2, posts.first.comments.size assert_equal 2, posts.first.categories.size - assert posts.first.comments.include?(comments(:greetings)) + assert_includes posts.first.comments, comments(:greetings) end def test_duplicate_middle_objects - comments = Comment.all.merge!(:where => 'post_id = 1', :includes => [:post => :author]).to_a + comments = Comment.all.merge!(where: "post_id = 1", includes: [post: :author]).to_a assert_no_queries do - comments.each {|comment| comment.post.author.name} + comments.each { |comment| comment.post.author.name } end end def test_preloading_has_many_in_multiple_queries_with_more_ids_than_database_can_handle assert_called(Comment.connection, :in_clause_length, returns: 5) do - posts = Post.all.merge!(:includes=>:comments).to_a + posts = Post.all.merge!(includes: :comments).to_a assert_equal 11, posts.size end end def test_preloading_has_many_in_one_queries_when_database_has_no_limit_on_ids_it_can_handle assert_called(Comment.connection, :in_clause_length, returns: nil) do - posts = Post.all.merge!(:includes=>:comments).to_a + posts = Post.all.merge!(includes: :comments).to_a assert_equal 11, posts.size end end def test_preloading_habtm_in_multiple_queries_with_more_ids_than_database_can_handle assert_called(Comment.connection, :in_clause_length, times: 2, returns: 5) do - posts = Post.all.merge!(:includes=>:categories).to_a + posts = Post.all.merge!(includes: :categories).to_a assert_equal 11, posts.size end end def test_preloading_habtm_in_one_queries_when_database_has_no_limit_on_ids_it_can_handle assert_called(Comment.connection, :in_clause_length, times: 2, returns: nil) do - posts = Post.all.merge!(:includes=>:categories).to_a + posts = Post.all.merge!(includes: :categories).to_a assert_equal 11, posts.size end end @@ -141,7 +145,7 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_called(Comment.connection, :in_clause_length, returns: nil) do post = posts(:welcome) assert_queries(2) do - Post.includes(:comments).where(:id => post.id).to_a + Post.includes(:comments).where(id: post.id).to_a end end end @@ -150,7 +154,7 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_called(Comment.connection, :in_clause_length, returns: 1) do post1, post2 = posts(:welcome), posts(:thinking) assert_queries(3) do - Post.includes(:comments).where(:id => [post1.id, post2.id]).to_a + Post.includes(:comments).where(id: [post1.id, post2.id]).to_a end end end @@ -159,50 +163,50 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_called(Comment.connection, :in_clause_length, returns: 3) do post = posts(:welcome) assert_queries(2) do - Post.includes(:comments).where(:id => post.id).to_a + Post.includes(:comments).where(id: post.id).to_a end end end def test_including_duplicate_objects_from_belongs_to - popular_post = Post.create!(:title => 'foo', :body => "I like cars!") - comment = popular_post.comments.create!(:body => "lol") - popular_post.readers.create!(:person => people(:michael)) - popular_post.readers.create!(:person => people(:david)) + popular_post = Post.create!(title: "foo", body: "I like cars!") + comment = popular_post.comments.create!(body: "lol") + popular_post.readers.create!(person: people(:michael)) + popular_post.readers.create!(person: people(:david)) - readers = Reader.all.merge!(:where => ["post_id = ?", popular_post.id], - :includes => {:post => :comments}).to_a + readers = Reader.all.merge!(where: ["post_id = ?", popular_post.id], + includes: { post: :comments }).to_a readers.each do |reader| assert_equal [comment], reader.post.comments end end def test_including_duplicate_objects_from_has_many - car_post = Post.create!(:title => 'foo', :body => "I like cars!") + car_post = Post.create!(title: "foo", body: "I like cars!") car_post.categories << categories(:general) car_post.categories << categories(:technology) - comment = car_post.comments.create!(:body => "hmm") - categories = Category.all.merge!(:where => { 'posts.id' => car_post.id }, - :includes => {:posts => :comments}).to_a + comment = car_post.comments.create!(body: "hmm") + categories = Category.all.merge!(where: { "posts.id" => car_post.id }, + includes: { posts: :comments }).to_a categories.each do |category| assert_equal [comment], category.posts[0].comments end end def test_associations_loaded_for_all_records - post = Post.create!(:title => 'foo', :body => "I like cars!") - SpecialComment.create!(:body => 'Come on!', :post => post) - first_category = Category.create! :name => 'First!', :posts => [post] - second_category = Category.create! :name => 'Second!', :posts => [post] + post = Post.create!(title: "foo", body: "I like cars!") + SpecialComment.create!(body: "Come on!", post: post) + first_category = Category.create! name: "First!", posts: [post] + second_category = Category.create! name: "Second!", posts: [post] - categories = Category.where(:id => [first_category.id, second_category.id]).includes(:posts => :special_comments) + categories = Category.where(id: [first_category.id, second_category.id]).includes(posts: :special_comments) assert_equal categories.map { |category| category.posts.first.special_comments.loaded? }, [true, true] end def test_finding_with_includes_on_has_many_association_with_same_include_includes_only_once author_id = authors(:david).id - author = assert_queries(3) { Author.all.merge!(:includes => {:posts_with_comments => :comments}).find(author_id) } # find the author, then find the posts, then find the comments + author = assert_queries(3) { Author.all.merge!(includes: { posts_with_comments: :comments }).find(author_id) } # find the author, then find the posts, then find the comments author.posts_with_comments.each do |post_with_comments| assert_equal post_with_comments.comments.length, post_with_comments.comments.count assert_nil post_with_comments.comments.to_a.uniq! @@ -213,7 +217,7 @@ class EagerAssociationTest < ActiveRecord::TestCase author = authors(:david) post = author.post_about_thinking_with_last_comment last_comment = post.last_comment - author = assert_queries(3) { Author.all.merge!(:includes => {:post_about_thinking_with_last_comment => :last_comment}).find(author.id)} # find the author, then find the posts, then find the comments + author = assert_queries(3) { Author.all.merge!(includes: { post_about_thinking_with_last_comment: :last_comment }).find(author.id) } # find the author, then find the posts, then find the comments assert_no_queries do assert_equal post, author.post_about_thinking_with_last_comment assert_equal last_comment, author.post_about_thinking_with_last_comment.last_comment @@ -224,7 +228,7 @@ class EagerAssociationTest < ActiveRecord::TestCase post = posts(:welcome) author = post.author author_address = author.author_address - post = assert_queries(3) { Post.all.merge!(:includes => {:author_with_address => :author_address}).find(post.id) } # find the post, then find the author, then find the address + post = assert_queries(3) { Post.all.merge!(includes: { author_with_address: :author_address }).find(post.id) } # find the post, then find the author, then find the address assert_no_queries do assert_equal author, post.author_with_address assert_equal author_address, post.author_with_address.author_address @@ -234,7 +238,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_finding_with_includes_on_null_belongs_to_association_with_same_include_includes_only_once post = posts(:welcome) post.update!(author: nil) - post = assert_queries(1) { Post.all.merge!(includes: {author_with_address: :author_address}).find(post.id) } + 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 @@ -244,7 +248,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_finding_with_includes_on_null_belongs_to_polymorphic_association sponsor = sponsors(:moustache_club_sponsor_for_groucho) sponsor.update!(sponsorable: nil) - sponsor = assert_queries(1) { Sponsor.all.merge!(:includes => :sponsorable).find(sponsor.id) } + sponsor = assert_queries(1) { Sponsor.all.merge!(includes: :sponsorable).find(sponsor.id) } assert_no_queries do assert_equal nil, sponsor.sponsorable end @@ -252,9 +256,9 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_finding_with_includes_on_empty_polymorphic_type_column sponsor = sponsors(:moustache_club_sponsor_for_groucho) - sponsor.update!(sponsorable_type: '', sponsorable_id: nil) # sponsorable_type column might be declared NOT NULL + sponsor.update!(sponsorable_type: "", sponsorable_id: nil) # sponsorable_type column might be declared NOT NULL sponsor = assert_queries(1) do - assert_nothing_raised { Sponsor.all.merge!(:includes => :sponsorable).find(sponsor.id) } + assert_nothing_raised { Sponsor.all.merge!(includes: :sponsorable).find(sponsor.id) } end assert_no_queries do assert_equal nil, sponsor.sponsorable @@ -262,25 +266,25 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_loading_from_an_association - posts = authors(:david).posts.merge(:includes => :comments, :order => "posts.id").to_a + posts = authors(:david).posts.merge(includes: :comments, order: "posts.id").to_a assert_equal 2, posts.first.comments.size end def test_loading_from_an_association_that_has_a_hash_of_conditions assert_nothing_raised do - Author.all.merge!(:includes => :hello_posts_with_hash_conditions).to_a + Author.all.merge!(includes: :hello_posts_with_hash_conditions).to_a end - assert !Author.all.merge!(:includes => :hello_posts_with_hash_conditions).find(authors(:david).id).hello_posts.empty? + assert !Author.all.merge!(includes: :hello_posts_with_hash_conditions).find(authors(:david).id).hello_posts.empty? end def test_loading_with_no_associations - assert_nil Post.all.merge!(:includes => :author).find(posts(:authorless).id).author + assert_nil Post.all.merge!(includes: :author).find(posts(:authorless).id).author end # Regression test for 21c75e5 def test_nested_loading_does_not_raise_exception_when_association_does_not_exist assert_nothing_raised do - Post.all.merge!(:includes => {:author => :author_addresss}).find(posts(:authorless).id) + Post.all.merge!(includes: { author: :author_addresss }).find(posts(:authorless).id) end end @@ -288,107 +292,107 @@ class EagerAssociationTest < ActiveRecord::TestCase post_id = Comment.where(author_id: nil).where.not(post_id: nil).first.post_id assert_nothing_raised do - Post.preload(:comments => [{:author => :essays}]).find(post_id) + Post.preload(comments: [{ author: :essays }]).find(post_id) end end def test_nested_loading_through_has_one_association - aa = AuthorAddress.all.merge!(:includes => {:author => :posts}).find(author_addresses(:david_address).id) + aa = AuthorAddress.all.merge!(includes: { author: :posts }).find(author_addresses(:david_address).id) assert_equal aa.author.posts.count, aa.author.posts.length end def test_nested_loading_through_has_one_association_with_order - aa = AuthorAddress.all.merge!(:includes => {:author => :posts}, :order => 'author_addresses.id').find(author_addresses(:david_address).id) + aa = AuthorAddress.all.merge!(includes: { author: :posts }, order: "author_addresses.id").find(author_addresses(:david_address).id) assert_equal aa.author.posts.count, aa.author.posts.length end def test_nested_loading_through_has_one_association_with_order_on_association - aa = AuthorAddress.all.merge!(:includes => {:author => :posts}, :order => 'authors.id').find(author_addresses(:david_address).id) + aa = AuthorAddress.all.merge!(includes: { author: :posts }, order: "authors.id").find(author_addresses(:david_address).id) assert_equal aa.author.posts.count, aa.author.posts.length end def test_nested_loading_through_has_one_association_with_order_on_nested_association - aa = AuthorAddress.all.merge!(:includes => {:author => :posts}, :order => 'posts.id').find(author_addresses(:david_address).id) + aa = AuthorAddress.all.merge!(includes: { author: :posts }, order: "posts.id").find(author_addresses(:david_address).id) assert_equal aa.author.posts.count, aa.author.posts.length end def test_nested_loading_through_has_one_association_with_conditions aa = AuthorAddress.references(:author_addresses).merge( - :includes => {:author => :posts}, - :where => "author_addresses.id > 0" + includes: { author: :posts }, + where: "author_addresses.id > 0" ).find author_addresses(:david_address).id assert_equal aa.author.posts.count, aa.author.posts.length end def test_nested_loading_through_has_one_association_with_conditions_on_association aa = AuthorAddress.references(:authors).merge( - :includes => {:author => :posts}, - :where => "authors.id > 0" + includes: { author: :posts }, + where: "authors.id > 0" ).find author_addresses(:david_address).id assert_equal aa.author.posts.count, aa.author.posts.length end def test_nested_loading_through_has_one_association_with_conditions_on_nested_association aa = AuthorAddress.references(:posts).merge( - :includes => {:author => :posts}, - :where => "posts.id > 0" + includes: { author: :posts }, + where: "posts.id > 0" ).find author_addresses(:david_address).id assert_equal aa.author.posts.count, aa.author.posts.length end def test_eager_association_loading_with_belongs_to_and_foreign_keys - pets = Pet.all.merge!(:includes => :owner).to_a + pets = Pet.all.merge!(includes: :owner).to_a assert_equal 4, pets.length end def test_eager_association_loading_with_belongs_to - comments = Comment.all.merge!(:includes => :post).to_a + comments = Comment.all.merge!(includes: :post).to_a assert_equal 11, comments.length titles = comments.map { |c| c.post.title } - assert titles.include?(posts(:welcome).title) - assert titles.include?(posts(:sti_post_and_comments).title) + assert_includes titles, posts(:welcome).title + assert_includes titles, posts(:sti_post_and_comments).title end def test_eager_association_loading_with_belongs_to_and_limit - comments = Comment.all.merge!(:includes => :post, :limit => 5, :order => 'comments.id').to_a + comments = Comment.all.merge!(includes: :post, limit: 5, order: "comments.id").to_a assert_equal 5, comments.length assert_equal [1,2,3,5,6], comments.collect(&:id) end def test_eager_association_loading_with_belongs_to_and_limit_and_conditions - comments = Comment.all.merge!(:includes => :post, :where => 'post_id = 4', :limit => 3, :order => 'comments.id').to_a + comments = Comment.all.merge!(includes: :post, where: "post_id = 4", limit: 3, order: "comments.id").to_a assert_equal 3, comments.length assert_equal [5,6,7], comments.collect(&:id) end def test_eager_association_loading_with_belongs_to_and_limit_and_offset - comments = Comment.all.merge!(:includes => :post, :limit => 3, :offset => 2, :order => 'comments.id').to_a + comments = Comment.all.merge!(includes: :post, limit: 3, offset: 2, order: "comments.id").to_a assert_equal 3, comments.length assert_equal [3,5,6], comments.collect(&:id) end def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions - comments = Comment.all.merge!(:includes => :post, :where => 'post_id = 4', :limit => 3, :offset => 1, :order => 'comments.id').to_a + comments = Comment.all.merge!(includes: :post, where: "post_id = 4", limit: 3, offset: 1, order: "comments.id").to_a assert_equal 3, comments.length assert_equal [6,7,8], comments.collect(&:id) end def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions_array - comments = Comment.all.merge!(:includes => :post, :where => ['post_id = ?',4], :limit => 3, :offset => 1, :order => 'comments.id').to_a + comments = Comment.all.merge!(includes: :post, where: ["post_id = ?",4], limit: 3, offset: 1, order: "comments.id").to_a assert_equal 3, comments.length assert_equal [6,7,8], comments.collect(&:id) end def test_eager_association_loading_with_belongs_to_and_conditions_string_with_unquoted_table_name assert_nothing_raised do - Comment.includes(:post).references(:posts).where('posts.id = ?', 4) + Comment.includes(:post).references(:posts).where("posts.id = ?", 4) end end def test_eager_association_loading_with_belongs_to_and_conditions_hash comments = [] assert_nothing_raised do - comments = Comment.all.merge!(:includes => :post, :where => {:posts => {:id => 4}}, :limit => 3, :order => 'comments.id').to_a + comments = Comment.all.merge!(includes: :post, where: { posts: { id: 4 } }, limit: 3, order: "comments.id").to_a end assert_equal 3, comments.length assert_equal [5,6,7], comments.collect(&:id) @@ -398,7 +402,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_association_loading_with_belongs_to_and_conditions_string_with_quoted_table_name - quoted_posts_id= Comment.connection.quote_table_name('posts') + '.' + Comment.connection.quote_column_name('id') + quoted_posts_id= Comment.connection.quote_table_name("posts") + "." + Comment.connection.quote_column_name("id") assert_nothing_raised do Comment.includes(:post).references(:posts).where("#{quoted_posts_id} = ?", 4) end @@ -406,61 +410,61 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_association_loading_with_belongs_to_and_order_string_with_unquoted_table_name assert_nothing_raised do - Comment.all.merge!(:includes => :post, :order => 'posts.id').to_a + Comment.all.merge!(includes: :post, order: "posts.id").to_a end end def test_eager_association_loading_with_belongs_to_and_order_string_with_quoted_table_name - quoted_posts_id= Comment.connection.quote_table_name('posts') + '.' + Comment.connection.quote_column_name('id') + quoted_posts_id= Comment.connection.quote_table_name("posts") + "." + Comment.connection.quote_column_name("id") assert_nothing_raised do Comment.includes(:post).references(:posts).order(quoted_posts_id) end end def test_eager_association_loading_with_belongs_to_and_limit_and_multiple_associations - posts = Post.all.merge!(:includes => [:author, :very_special_comment], :limit => 1, :order => 'posts.id').to_a + posts = Post.all.merge!(includes: [:author, :very_special_comment], limit: 1, order: "posts.id").to_a assert_equal 1, posts.length assert_equal [1], posts.collect(&:id) end def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_multiple_associations - posts = Post.all.merge!(:includes => [:author, :very_special_comment], :limit => 1, :offset => 1, :order => 'posts.id').to_a + posts = Post.all.merge!(includes: [:author, :very_special_comment], limit: 1, offset: 1, order: "posts.id").to_a assert_equal 1, posts.length assert_equal [2], posts.collect(&:id) end def test_eager_association_loading_with_belongs_to_inferred_foreign_key_from_association_name - author_favorite = AuthorFavorite.all.merge!(:includes => :favorite_author).first + author_favorite = AuthorFavorite.all.merge!(includes: :favorite_author).first assert_equal authors(:mary), assert_no_queries { author_favorite.favorite_author } end def test_eager_load_belongs_to_quotes_table_and_column_names job = Job.includes(:ideal_reference).find jobs(:unicyclist).id references(:michael_unicyclist) - assert_no_queries{ assert_equal references(:michael_unicyclist), job.ideal_reference} + assert_no_queries { assert_equal references(:michael_unicyclist), job.ideal_reference } end def test_eager_load_has_one_quotes_table_and_column_names - michael = Person.all.merge!(:includes => :favourite_reference).find(people(:michael).id) + michael = Person.all.merge!(includes: :favourite_reference).find(people(:michael).id) references(:michael_unicyclist) - assert_no_queries{ assert_equal references(:michael_unicyclist), michael.favourite_reference} + assert_no_queries { assert_equal references(:michael_unicyclist), michael.favourite_reference } end def test_eager_load_has_many_quotes_table_and_column_names - michael = Person.all.merge!(:includes => :references).find(people(:michael).id) + michael = Person.all.merge!(includes: :references).find(people(:michael).id) references(:michael_magician,:michael_unicyclist) - assert_no_queries{ assert_equal references(:michael_magician,:michael_unicyclist), michael.references.sort_by(&:id) } + assert_no_queries { assert_equal references(:michael_magician,:michael_unicyclist), michael.references.sort_by(&:id) } end def test_eager_load_has_many_through_quotes_table_and_column_names - michael = Person.all.merge!(:includes => :jobs).find(people(:michael).id) + michael = Person.all.merge!(includes: :jobs).find(people(:michael).id) jobs(:magician, :unicyclist) - assert_no_queries{ assert_equal jobs(:unicyclist, :magician), michael.jobs.sort_by(&:id) } + assert_no_queries { assert_equal jobs(:unicyclist, :magician), michael.jobs.sort_by(&:id) } end def test_eager_load_has_many_with_string_keys subscriptions = subscriptions(:webster_awdr, :webster_rfr) - subscriber =Subscriber.all.merge!(:includes => :subscriptions).find(subscribers(:second).id) + subscriber =Subscriber.all.merge!(includes: :subscriptions).find(subscribers(:second).id) assert_equal subscriptions, subscriber.subscriptions.sort_by(&:id) end @@ -471,32 +475,32 @@ class EagerAssociationTest < ActiveRecord::TestCase b = Book.create! - Subscription.create!(:subscriber_id => "PL", :book_id => b.id) + Subscription.create!(subscriber_id: "PL", book_id: b.id) s.reload s.book_ids = s.book_ids end def test_eager_load_has_many_through_with_string_keys books = books(:awdr, :rfr) - subscriber = Subscriber.all.merge!(:includes => :books).find(subscribers(:second).id) + subscriber = Subscriber.all.merge!(includes: :books).find(subscribers(:second).id) assert_equal books, subscriber.books.sort_by(&:id) end def test_eager_load_belongs_to_with_string_keys subscriber = subscribers(:second) - subscription = Subscription.all.merge!(:includes => :subscriber).find(subscriptions(:webster_awdr).id) + subscription = Subscription.all.merge!(includes: :subscriber).find(subscriptions(:webster_awdr).id) assert_equal subscriber, subscription.subscriber end def test_eager_association_loading_with_explicit_join - posts = Post.all.merge!(:includes => :comments, :joins => "INNER JOIN authors ON posts.author_id = authors.id AND authors.name = 'Mary'", :limit => 1, :order => 'author_id').to_a + posts = Post.all.merge!(includes: :comments, joins: "INNER JOIN authors ON posts.author_id = authors.id AND authors.name = 'Mary'", limit: 1, order: "author_id").to_a assert_equal 1, posts.length end def test_eager_with_has_many_through - posts_with_comments = people(:michael).posts.merge(:includes => :comments, :order => 'posts.id').to_a - posts_with_author = people(:michael).posts.merge(:includes => :author, :order => 'posts.id').to_a - posts_with_comments_and_author = people(:michael).posts.merge(:includes => [ :comments, :author ], :order => 'posts.id').to_a + posts_with_comments = people(:michael).posts.merge(includes: :comments, order: "posts.id").to_a + posts_with_author = people(:michael).posts.merge(includes: :author, order: "posts.id").to_a + posts_with_comments_and_author = people(:michael).posts.merge(includes: [ :comments, :author ], order: "posts.id").to_a assert_equal 2, posts_with_comments.inject(0) { |sum, post| sum + post.comments.size } assert_equal authors(:david), assert_no_queries { posts_with_author.first.author } assert_equal authors(:david), assert_no_queries { posts_with_comments_and_author.first.author } @@ -504,35 +508,35 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_with_has_many_through_a_belongs_to_association author = authors(:mary) - Post.create!(:author => author, :title => "TITLE", :body => "BODY") - author.author_favorites.create(:favorite_author_id => 1) - author.author_favorites.create(:favorite_author_id => 2) - posts_with_author_favorites = author.posts.merge(:includes => :author_favorites).to_a + Post.create!(author: author, title: "TITLE", body: "BODY") + author.author_favorites.create(favorite_author_id: 1) + author.author_favorites.create(favorite_author_id: 2) + posts_with_author_favorites = author.posts.merge(includes: :author_favorites).to_a assert_no_queries { posts_with_author_favorites.first.author_favorites.first.author_id } end def test_eager_with_has_many_through_an_sti_join_model - author = Author.all.merge!(:includes => :special_post_comments, :order => 'authors.id').first + author = Author.all.merge!(includes: :special_post_comments, order: "authors.id").first assert_equal [comments(:does_it_hurt)], assert_no_queries { author.special_post_comments } end def test_eager_with_has_many_through_an_sti_join_model_with_conditions_on_both - author = Author.all.merge!(:includes => :special_nonexistent_post_comments, :order => 'authors.id').first + author = Author.all.merge!(includes: :special_nonexistent_post_comments, order: "authors.id").first assert_equal [], author.special_nonexistent_post_comments end def test_eager_with_has_many_through_join_model_with_conditions - assert_equal Author.all.merge!(:includes => :hello_post_comments, - :order => 'authors.id').first.hello_post_comments.sort_by(&:id), - Author.all.merge!(:order => 'authors.id').first.hello_post_comments.sort_by(&:id) + assert_equal Author.all.merge!(includes: :hello_post_comments, + order: "authors.id").first.hello_post_comments.sort_by(&:id), + Author.all.merge!(order: "authors.id").first.hello_post_comments.sort_by(&:id) end def test_eager_with_has_many_through_join_model_with_conditions_on_top_level - assert_equal comments(:more_greetings), Author.all.merge!(:includes => :comments_with_order_and_conditions).find(authors(:david).id).comments_with_order_and_conditions.first + assert_equal comments(:more_greetings), Author.all.merge!(includes: :comments_with_order_and_conditions).find(authors(:david).id).comments_with_order_and_conditions.first end def test_eager_with_has_many_through_join_model_with_include - author_comments = Author.all.merge!(:includes => :comments_with_include).find(authors(:david).id).comments_with_include.to_a + author_comments = Author.all.merge!(includes: :comments_with_include).find(authors(:david).id).comments_with_include.to_a assert_no_queries do author_comments.first.post.title end @@ -540,7 +544,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_with_has_many_through_with_conditions_join_model_with_include post_tags = Post.find(posts(:welcome).id).misc_tags - eager_post_tags = Post.all.merge!(:includes => :misc_tags).find(1).misc_tags + eager_post_tags = Post.all.merge!(includes: :misc_tags).find(1).misc_tags assert_equal post_tags, eager_post_tags end @@ -551,67 +555,67 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_has_many_and_limit - posts = Post.all.merge!(:order => 'posts.id asc', :includes => [ :author, :comments ], :limit => 2).to_a + posts = Post.all.merge!(order: "posts.id asc", includes: [ :author, :comments ], limit: 2).to_a assert_equal 2, posts.size assert_equal 3, posts.inject(0) { |sum, post| sum + post.comments.size } end def test_eager_with_has_many_and_limit_and_conditions - posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => "posts.body = 'hello'", :order => "posts.id").to_a + posts = Post.all.merge!(includes: [ :author, :comments ], limit: 2, where: "posts.body = 'hello'", order: "posts.id").to_a assert_equal 2, posts.size assert_equal [4,5], posts.collect(&:id) end def test_eager_with_has_many_and_limit_and_conditions_array - posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => [ "posts.body = ?", 'hello' ], :order => "posts.id").to_a + posts = Post.all.merge!(includes: [ :author, :comments ], limit: 2, where: [ "posts.body = ?", "hello" ], order: "posts.id").to_a assert_equal 2, posts.size assert_equal [4,5], posts.collect(&:id) end def test_eager_with_has_many_and_limit_and_conditions_array_on_the_eagers - posts = Post.includes(:author, :comments).limit(2).references(:author).where("authors.name = ?", 'David') + posts = Post.includes(:author, :comments).limit(2).references(:author).where("authors.name = ?", "David") assert_equal 2, posts.size - count = Post.includes(:author, :comments).limit(2).references(:author).where("authors.name = ?", 'David').count + count = Post.includes(:author, :comments).limit(2).references(:author).where("authors.name = ?", "David").count assert_equal posts.size, count end def test_eager_with_has_many_and_limit_and_high_offset - posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :offset => 10, :where => { 'authors.name' => 'David' }).to_a + posts = Post.all.merge!(includes: [ :author, :comments ], limit: 2, offset: 10, where: { "authors.name" => "David" }).to_a assert_equal 0, posts.size end def test_eager_with_has_many_and_limit_and_high_offset_and_multiple_array_conditions assert_queries(1) do posts = Post.references(:authors, :comments). - merge(:includes => [ :author, :comments ], :limit => 2, :offset => 10, - :where => [ "authors.name = ? and comments.body = ?", 'David', 'go crazy' ]).to_a + merge(includes: [ :author, :comments ], limit: 2, offset: 10, + where: [ "authors.name = ? and comments.body = ?", "David", "go crazy" ]).to_a assert_equal 0, posts.size end end def test_eager_with_has_many_and_limit_and_high_offset_and_multiple_hash_conditions assert_queries(1) do - posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :offset => 10, - :where => { 'authors.name' => 'David', 'comments.body' => 'go crazy' }).to_a + posts = Post.all.merge!(includes: [ :author, :comments ], limit: 2, offset: 10, + where: { "authors.name" => "David", "comments.body" => "go crazy" }).to_a assert_equal 0, posts.size end end def test_count_eager_with_has_many_and_limit_and_high_offset - posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :offset => 10, :where => { 'authors.name' => 'David' }).count(:all) + posts = Post.all.merge!(includes: [ :author, :comments ], limit: 2, offset: 10, where: { "authors.name" => "David" }).count(:all) assert_equal 0, posts end def test_eager_with_has_many_and_limit_with_no_results - posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => "posts.title = 'magic forest'").to_a + posts = Post.all.merge!(includes: [ :author, :comments ], limit: 2, where: "posts.title = 'magic forest'").to_a assert_equal 0, posts.size end def test_eager_count_performed_on_a_has_many_association_with_multi_table_conditional author = authors(:david) author_posts_without_comments = author.posts.select { |post| post.comments.blank? } - assert_equal author_posts_without_comments.size, author.posts.includes(:comments).where('comments.id is null').references(:comments).count + assert_equal author_posts_without_comments.size, author.posts.includes(:comments).where("comments.id is null").references(:comments).count end def test_eager_count_performed_on_a_has_many_through_association_with_multi_table_conditional @@ -621,13 +625,13 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_has_and_belongs_to_many_and_limit - posts = Post.all.merge!(:includes => :categories, :order => "posts.id", :limit => 3).to_a + posts = Post.all.merge!(includes: :categories, order: "posts.id", limit: 3).to_a assert_equal 3, posts.size assert_equal 2, posts[0].categories.size assert_equal 1, posts[1].categories.size assert_equal 0, posts[2].categories.size - assert posts[0].categories.include?(categories(:technology)) - assert posts[1].categories.include?(categories(:general)) + assert_includes posts[0].categories, categories(:technology) + assert_includes posts[1].categories, categories(:general) end # Since the preloader for habtm gets raw row hashes from the database and then @@ -687,32 +691,32 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_association_loading_with_habtm - posts = Post.all.merge!(:includes => :categories, :order => "posts.id").to_a + posts = Post.all.merge!(includes: :categories, order: "posts.id").to_a assert_equal 2, posts[0].categories.size assert_equal 1, posts[1].categories.size assert_equal 0, posts[2].categories.size - assert posts[0].categories.include?(categories(:technology)) - assert posts[1].categories.include?(categories(:general)) + assert_includes posts[0].categories, categories(:technology) + assert_includes posts[1].categories, categories(:general) end def test_eager_with_inheritance - SpecialPost.all.merge!(:includes => [ :comments ]).to_a + SpecialPost.all.merge!(includes: [ :comments ]).to_a end def test_eager_has_one_with_association_inheritance - post = Post.all.merge!(:includes => [ :very_special_comment ]).find(4) + post = Post.all.merge!(includes: [ :very_special_comment ]).find(4) assert_equal "VerySpecialComment", post.very_special_comment.class.to_s end def test_eager_has_many_with_association_inheritance - post = Post.all.merge!(:includes => [ :special_comments ]).find(4) + post = Post.all.merge!(includes: [ :special_comments ]).find(4) post.special_comments.each do |special_comment| assert special_comment.is_a?(SpecialComment) end end def test_eager_habtm_with_association_inheritance - post = Post.all.merge!(:includes => [ :special_categories ]).find(6) + post = Post.all.merge!(includes: [ :special_categories ]).find(6) assert_equal 1, post.special_categories.size post.special_categories.each do |special_category| assert_equal "SpecialCategory", special_category.class.to_s @@ -721,8 +725,8 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_with_has_one_dependent_does_not_destroy_dependent assert_not_nil companies(:first_firm).account - f = Firm.all.merge!(:includes => :account, - :where => ["companies.name = ?", "37signals"]).first + f = Firm.all.merge!(includes: :account, + where: ["companies.name = ?", "37signals"]).first assert_not_nil f.account assert_equal companies(:first_firm, :reload).account, f.account end @@ -736,21 +740,53 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_with_invalid_association_reference assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { - Post.all.merge!(:includes=> :monkeys ).find(6) + Post.all.merge!(includes: :monkeys ).find(6) } assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { - Post.all.merge!(:includes=>[ :monkeys ]).find(6) + Post.all.merge!(includes: [ :monkeys ]).find(6) } assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { - Post.all.merge!(:includes=>[ 'monkeys' ]).find(6) + Post.all.merge!(includes: [ "monkeys" ]).find(6) } assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys, :elephants") { - Post.all.merge!(:includes=>[ :monkeys, :elephants ]).find(6) + Post.all.merge!(includes: [ :monkeys, :elephants ]).find(6) } end + def test_eager_has_many_through_with_order + tag = OrderedTag.create(name: "Foo") + post1 = Post.create!(title: "Beaches", body: "I like beaches!") + post2 = Post.create!(title: "Pools", body: "I like pools!") + + Tagging.create!(taggable_type: "Post", taggable_id: post1.id, tag: tag) + Tagging.create!(taggable_type: "Post", taggable_id: post2.id, tag: tag) + + tag_with_includes = OrderedTag.includes(:tagged_posts).find(tag.id) + assert_equal(tag_with_includes.taggings.map(&:taggable).map(&:title), tag_with_includes.tagged_posts.map(&:title)) + end + + def test_eager_has_many_through_multiple_with_order + tag1 = OrderedTag.create!(name: "Bar") + tag2 = OrderedTag.create!(name: "Foo") + + post1 = Post.create!(title: "Beaches", body: "I like beaches!") + post2 = Post.create!(title: "Pools", body: "I like pools!") + + Tagging.create!(taggable: post1, tag: tag1) + Tagging.create!(taggable: post2, tag: tag1) + Tagging.create!(taggable: post2, tag: tag2) + Tagging.create!(taggable: post1, tag: tag2) + + tags_with_includes = OrderedTag.where(id: [tag1, tag2].map(&:id)).includes(:tagged_posts).order(:id).to_a + tag1_with_includes = tags_with_includes.first + tag2_with_includes = tags_with_includes.last + + assert_equal([post2, post1].map(&:title), tag1_with_includes.tagged_posts.map(&:title)) + assert_equal([post1, post2].map(&:title), tag2_with_includes.tagged_posts.map(&:title)) + end + def test_eager_with_default_scope - developer = EagerDeveloperWithDefaultScope.where(:name => 'David').first + developer = EagerDeveloperWithDefaultScope.where(name: "David").first projects = Project.order(:id).to_a assert_no_queries do assert_equal(projects, developer.projects) @@ -758,7 +794,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_default_scope_as_class_method - developer = EagerDeveloperWithClassMethodDefaultScope.where(:name => 'David').first + developer = EagerDeveloperWithClassMethodDefaultScope.where(name: "David").first projects = Project.order(:id).to_a assert_no_queries do assert_equal(projects, developer.projects) @@ -775,7 +811,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_default_scope_as_class_method_using_find_by_method - developer = EagerDeveloperWithClassMethodDefaultScope.find_by(name: 'David') + developer = EagerDeveloperWithClassMethodDefaultScope.find_by(name: "David") projects = Project.order(:id).to_a assert_no_queries do assert_equal(projects, developer.projects) @@ -783,7 +819,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_default_scope_as_lambda - developer = EagerDeveloperWithLambdaDefaultScope.where(:name => 'David').first + developer = EagerDeveloperWithLambdaDefaultScope.where(name: "David").first projects = Project.order(:id).to_a assert_no_queries do assert_equal(projects, developer.projects) @@ -792,8 +828,8 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_with_default_scope_as_block # warm up the habtm cache - EagerDeveloperWithBlockDefaultScope.where(:name => 'David').first.projects - developer = EagerDeveloperWithBlockDefaultScope.where(:name => 'David').first + EagerDeveloperWithBlockDefaultScope.where(name: "David").first.projects + developer = EagerDeveloperWithBlockDefaultScope.where(name: "David").first projects = Project.order(:id).to_a assert_no_queries do assert_equal(projects, developer.projects) @@ -801,7 +837,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_default_scope_as_callable - developer = EagerDeveloperWithCallableDefaultScope.where(:name => 'David').first + developer = EagerDeveloperWithCallableDefaultScope.where(name: "David").first projects = Project.order(:id).to_a assert_no_queries do assert_equal(projects, developer.projects) @@ -809,22 +845,22 @@ class EagerAssociationTest < ActiveRecord::TestCase end def find_all_ordered(className, include=nil) - className.all.merge!(:order=>"#{className.table_name}.#{className.primary_key}", :includes=>include).to_a + className.all.merge!(order: "#{className.table_name}.#{className.primary_key}", includes: include).to_a end def test_limited_eager_with_order assert_equal( posts(:thinking, :sti_comments), Post.all.merge!( - :includes => [:author, :comments], :where => { 'authors.name' => 'David' }, - :order => 'UPPER(posts.title)', :limit => 2, :offset => 1 + includes: [:author, :comments], where: { "authors.name" => "David" }, + order: "UPPER(posts.title)", limit: 2, offset: 1 ).to_a ) assert_equal( posts(:sti_post_and_comments, :sti_comments), Post.all.merge!( - :includes => [:author, :comments], :where => { 'authors.name' => 'David' }, - :order => 'UPPER(posts.title) DESC', :limit => 2, :offset => 1 + includes: [:author, :comments], where: { "authors.name" => "David" }, + order: "UPPER(posts.title) DESC", limit: 2, offset: 1 ).to_a ) end @@ -833,15 +869,15 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_equal( posts(:thinking, :sti_comments), Post.all.merge!( - :includes => [:author, :comments], :where => { 'authors.name' => 'David' }, - :order => ['UPPER(posts.title)', 'posts.id'], :limit => 2, :offset => 1 + includes: [:author, :comments], where: { "authors.name" => "David" }, + order: ["UPPER(posts.title)", "posts.id"], limit: 2, offset: 1 ).to_a ) assert_equal( posts(:sti_post_and_comments, :sti_comments), Post.all.merge!( - :includes => [:author, :comments], :where => { 'authors.name' => 'David' }, - :order => ['UPPER(posts.title) DESC', 'posts.id'], :limit => 2, :offset => 1 + includes: [:author, :comments], where: { "authors.name" => "David" }, + order: ["UPPER(posts.title) DESC", "posts.id"], limit: 2, offset: 1 ).to_a ) end @@ -850,25 +886,25 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_equal( people(:david, :susan), Person.references(:number1_fans_people).merge( - :includes => [:readers, :primary_contact, :number1_fan], - :where => "number1_fans_people.first_name like 'M%'", - :order => 'people.id', :limit => 2, :offset => 0 + includes: [:readers, :primary_contact, :number1_fan], + where: "number1_fans_people.first_name like 'M%'", + order: "people.id", limit: 2, offset: 0 ).to_a ) end def test_polymorphic_type_condition - post = Post.all.merge!(:includes => :taggings).find(posts(:thinking).id) - assert post.taggings.include?(taggings(:thinking_general)) - post = SpecialPost.all.merge!(:includes => :taggings).find(posts(:thinking).id) - assert post.taggings.include?(taggings(:thinking_general)) + post = Post.all.merge!(includes: :taggings).find(posts(:thinking).id) + assert_includes post.taggings, taggings(:thinking_general) + post = SpecialPost.all.merge!(includes: :taggings).find(posts(:thinking).id) + assert_includes post.taggings, taggings(:thinking_general) end def test_eager_with_multiple_associations_with_same_table_has_many_and_habtm # Eager includes of has many and habtm associations aren't necessarily sorted in the same way def assert_equal_after_sort(item1, item2, item3 = nil) - assert_equal(item1.sort{|a,b| a.id <=> b.id}, item2.sort{|a,b| a.id <=> b.id}) - assert_equal(item3.sort{|a,b| a.id <=> b.id}, item2.sort{|a,b| a.id <=> b.id}) if item3 + assert_equal(item1.sort { |a,b| a.id <=> b.id }, item2.sort { |a,b| a.id <=> b.id }) + assert_equal(item3.sort { |a,b| a.id <=> b.id }, item2.sort { |a,b| a.id <=> b.id }) if item3 end # Test regular association, association with conditions, association with # STI, and association with conditions assured not to be true @@ -911,24 +947,24 @@ class EagerAssociationTest < ActiveRecord::TestCase end end def test_eager_with_valid_association_as_string_not_symbol - assert_nothing_raised { Post.all.merge!(:includes => 'comments').to_a } + assert_nothing_raised { Post.all.merge!(includes: "comments").to_a } end def test_eager_with_floating_point_numbers assert_queries(2) do # Before changes, the floating point numbers will be interpreted as table names and will cause this to run in one query - Comment.all.merge!(:where => "123.456 = 123.456", :includes => :post).to_a + Comment.all.merge!(where: "123.456 = 123.456", includes: :post).to_a end end def test_preconfigured_includes_with_belongs_to author = posts(:welcome).author_with_posts - assert_no_queries {assert_equal 5, author.posts.size} + assert_no_queries { assert_equal 5, author.posts.size } end def test_preconfigured_includes_with_has_one comment = posts(:sti_comments).very_special_comment_with_post - assert_no_queries {assert_equal posts(:sti_comments), comment.post} + assert_no_queries { assert_equal posts(:sti_comments), comment.post } end def test_eager_association_with_scope_with_joins @@ -970,13 +1006,13 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_association_loading_notification - notifications = messages_for('instantiation.active_record') do - Developer.all.merge!(:includes => 'projects', :where => { 'developers_projects.access_level' => 1 }, :limit => 5).to_a.size + notifications = messages_for("instantiation.active_record") do + Developer.all.merge!(includes: "projects", where: { "developers_projects.access_level" => 1 }, limit: 5).to_a.size end message = notifications.first payload = message.last - count = Developer.all.merge!(:includes => 'projects', :where => { 'developers_projects.access_level' => 1 }, :limit => 5).to_a.size + count = Developer.all.merge!(includes: "projects", where: { "developers_projects.access_level" => 1 }, limit: 5).to_a.size # eagerloaded row count should be greater than just developer count assert_operator payload[:record_count], :>, count @@ -984,7 +1020,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_base_messages - notifications = messages_for('instantiation.active_record') do + notifications = messages_for("instantiation.active_record") do Developer.all.to_a end message = notifications.first @@ -1012,55 +1048,55 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_conditions_on_join_table_with_include_and_limit - assert_equal 3, Developer.all.merge!(:includes => 'projects', :where => { 'developers_projects.access_level' => 1 }, :limit => 5).to_a.size + assert_equal 3, Developer.all.merge!(includes: "projects", where: { "developers_projects.access_level" => 1 }, limit: 5).to_a.size end def test_dont_create_temporary_active_record_instances Developer.instance_count = 0 - developers = Developer.all.merge!(:includes => 'projects', :where => { 'developers_projects.access_level' => 1 }, :limit => 5).to_a + developers = Developer.all.merge!(includes: "projects", where: { "developers_projects.access_level" => 1 }, limit: 5).to_a assert_equal developers.count, Developer.instance_count end def test_order_on_join_table_with_include_and_limit - assert_equal 5, Developer.all.merge!(:includes => 'projects', :order => 'developers_projects.joined_on DESC', :limit => 5).to_a.size + assert_equal 5, Developer.all.merge!(includes: "projects", order: "developers_projects.joined_on DESC", limit: 5).to_a.size end def test_eager_loading_with_order_on_joined_table_preloads posts = assert_queries(2) do - Post.all.merge!(:joins => :comments, :includes => :author, :order => 'comments.id DESC').to_a + Post.all.merge!(joins: :comments, includes: :author, order: "comments.id DESC").to_a end assert_equal posts(:eager_other), posts[1] - assert_equal authors(:mary), assert_no_queries { posts[1].author} + assert_equal authors(:mary), assert_no_queries { posts[1].author } end def test_eager_loading_with_conditions_on_joined_table_preloads posts = assert_queries(2) do - Post.all.merge!(:select => 'distinct posts.*', :includes => :author, :joins => [:comments], :where => "comments.body like 'Thank you%'", :order => 'posts.id').to_a + Post.all.merge!(select: "distinct posts.*", includes: :author, joins: [:comments], where: "comments.body like 'Thank you%'", order: "posts.id").to_a end assert_equal [posts(:welcome)], posts - assert_equal authors(:david), assert_no_queries { posts[0].author} + assert_equal authors(:david), assert_no_queries { posts[0].author } posts = assert_queries(2) do - Post.all.merge!(:select => 'distinct posts.*', :includes => :author, :joins => [:comments], :where => "comments.body like 'Thank you%'", :order => 'posts.id').to_a + Post.all.merge!(select: "distinct posts.*", includes: :author, joins: [:comments], where: "comments.body like 'Thank you%'", order: "posts.id").to_a end assert_equal [posts(:welcome)], posts - assert_equal authors(:david), assert_no_queries { posts[0].author} + assert_equal authors(:david), assert_no_queries { posts[0].author } posts = assert_queries(2) do - Post.all.merge!(:includes => :author, :joins => {:taggings => :tag}, :where => "tags.name = 'General'", :order => 'posts.id').to_a + Post.all.merge!(includes: :author, joins: { taggings: :tag }, where: "tags.name = 'General'", order: "posts.id").to_a end assert_equal posts(:welcome, :thinking), posts posts = assert_queries(2) do - Post.all.merge!(:includes => :author, :joins => {:taggings => {:tag => :taggings}}, :where => "taggings_tags.super_tag_id=2", :order => 'posts.id').to_a + Post.all.merge!(includes: :author, joins: { taggings: { tag: :taggings } }, where: "taggings_tags.super_tag_id=2", order: "posts.id").to_a end assert_equal posts(:welcome, :thinking), posts end def test_preload_has_many_with_association_condition_and_default_scope - post = Post.create!(:title => 'Beaches', :body => "I like beaches!") - Reader.create! :person => people(:david), :post => post - LazyReader.create! :person => people(:susan), :post => post + post = Post.create!(title: "Beaches", body: "I like beaches!") + Reader.create! person: people(:david), post: post + LazyReader.create! person: people(:susan), post: post assert_equal 1, post.lazy_readers.to_a.size assert_equal 2, post.lazy_readers_skimmers_or_not.to_a.size @@ -1071,39 +1107,39 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_loading_with_conditions_on_string_joined_table_preloads posts = assert_queries(2) do - Post.all.merge!(:select => 'distinct posts.*', :includes => :author, :joins => "INNER JOIN comments on comments.post_id = posts.id", :where => "comments.body like 'Thank you%'", :order => 'posts.id').to_a + Post.all.merge!(select: "distinct posts.*", includes: :author, joins: "INNER JOIN comments on comments.post_id = posts.id", where: "comments.body like 'Thank you%'", order: "posts.id").to_a end assert_equal [posts(:welcome)], posts - assert_equal authors(:david), assert_no_queries { posts[0].author} + assert_equal authors(:david), assert_no_queries { posts[0].author } posts = assert_queries(2) do - Post.all.merge!(:select => 'distinct posts.*', :includes => :author, :joins => ["INNER JOIN comments on comments.post_id = posts.id"], :where => "comments.body like 'Thank you%'", :order => 'posts.id').to_a + Post.all.merge!(select: "distinct posts.*", includes: :author, joins: ["INNER JOIN comments on comments.post_id = posts.id"], where: "comments.body like 'Thank you%'", order: "posts.id").to_a end assert_equal [posts(:welcome)], posts - assert_equal authors(:david), assert_no_queries { posts[0].author} + assert_equal authors(:david), assert_no_queries { posts[0].author } end def test_eager_loading_with_select_on_joined_table_preloads posts = assert_queries(2) do - Post.all.merge!(:select => 'posts.*, authors.name as author_name', :includes => :comments, :joins => :author, :order => 'posts.id').to_a + Post.all.merge!(select: "posts.*, authors.name as author_name", includes: :comments, joins: :author, order: "posts.id").to_a end - assert_equal 'David', posts[0].author_name - assert_equal posts(:welcome).comments, assert_no_queries { posts[0].comments} + assert_equal "David", posts[0].author_name + assert_equal posts(:welcome).comments, assert_no_queries { posts[0].comments } end def test_eager_loading_with_conditions_on_join_model_preloads authors = assert_queries(2) do - Author.all.merge!(:includes => :author_address, :joins => :comments, :where => "posts.title like 'Welcome%'").to_a + Author.all.merge!(includes: :author_address, joins: :comments, where: "posts.title like 'Welcome%'").to_a end assert_equal authors(:david), authors[0] assert_equal author_addresses(:david_address), authors[0].author_address end def test_preload_belongs_to_uses_exclusive_scope - people = Person.males.merge(:includes => :primary_contact).to_a + people = Person.males.merge(includes: :primary_contact).to_a assert_not_equal people.length, 0 people.each do |person| - assert_no_queries {assert_not_nil person.primary_contact} + assert_no_queries { assert_not_nil person.primary_contact } assert_equal Person.find(person.id).primary_contact, person.primary_contact end end @@ -1127,9 +1163,9 @@ class EagerAssociationTest < ActiveRecord::TestCase expected = Firm.find(1).clients_using_primary_key.sort_by(&:name) # Oracle adapter truncates alias to 30 characters if current_adapter?(:OracleAdapter) - firm = Firm.all.merge!(:includes => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies'[0,30]+'.name').find(1) + firm = Firm.all.merge!(includes: :clients_using_primary_key, order: "clients_using_primary_keys_companies"[0,30]+".name").find(1) else - firm = Firm.all.merge!(:includes => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies.name').find(1) + firm = Firm.all.merge!(includes: :clients_using_primary_key, order: "clients_using_primary_keys_companies.name").find(1) end assert_no_queries do assert_equal expected, firm.clients_using_primary_key @@ -1138,7 +1174,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_preload_has_one_using_primary_key expected = accounts(:signals37) - firm = Firm.all.merge!(:includes => :account_using_primary_key, :order => 'companies.id').first + firm = Firm.all.merge!(includes: :account_using_primary_key, order: "companies.id").first assert_no_queries do assert_equal expected, firm.account_using_primary_key end @@ -1146,35 +1182,35 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_include_has_one_using_primary_key expected = accounts(:signals37) - firm = Firm.all.merge!(:includes => :account_using_primary_key, :order => 'accounts.id').to_a.detect {|f| f.id == 1} + firm = Firm.all.merge!(includes: :account_using_primary_key, order: "accounts.id").to_a.detect { |f| f.id == 1 } assert_no_queries do assert_equal expected, firm.account_using_primary_key end end def test_preloading_empty_belongs_to - c = Client.create!(:name => 'Foo', :client_of => Company.maximum(:id) + 1) + c = Client.create!(name: "Foo", client_of: Company.maximum(:id) + 1) client = assert_queries(2) { Client.preload(:firm).find(c.id) } assert_no_queries { assert_nil client.firm } end def test_preloading_empty_belongs_to_polymorphic - t = Tagging.create!(:taggable_type => 'Post', :taggable_id => Post.maximum(:id) + 1, :tag => tags(:general)) + t = Tagging.create!(taggable_type: "Post", taggable_id: Post.maximum(:id) + 1, tag: tags(:general)) tagging = assert_queries(2) { Tagging.preload(:taggable).find(t.id) } assert_no_queries { assert_nil tagging.taggable } end def test_preloading_through_empty_belongs_to - c = Client.create!(:name => 'Foo', :client_of => Company.maximum(:id) + 1) + c = Client.create!(name: "Foo", client_of: Company.maximum(:id) + 1) client = assert_queries(2) { Client.preload(:accounts).find(c.id) } assert_no_queries { assert client.accounts.empty? } end def test_preloading_has_many_through_with_distinct - mary = Author.includes(:unique_categorized_posts).where(:id => authors(:mary).id).first + mary = Author.includes(:unique_categorized_posts).where(id: authors(:mary).id).first assert_equal 1, mary.unique_categorized_posts.length assert_equal 1, mary.unique_categorized_post_ids.length end @@ -1202,13 +1238,13 @@ class EagerAssociationTest < ActiveRecord::TestCase groucho = members(:groucho) sponsor = assert_queries(2) { - Sponsor.includes(:thing).where(:id => sponsor.id).first + Sponsor.includes(:thing).where(id: sponsor.id).first } assert_no_queries { assert_equal groucho, sponsor.thing } end def test_joins_with_includes_should_preload_via_joins - post = assert_queries(1) { Post.includes(:comments).joins(:comments).order('posts.id desc').to_a.first } + post = assert_queries(1) { Post.includes(:comments).joins(:comments).order("posts.id desc").to_a.first } assert_queries(0) do assert_not_equal 0, post.comments.to_a.count @@ -1216,17 +1252,17 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_join_eager_with_empty_order_should_generate_valid_sql - assert_nothing_raised(ActiveRecord::StatementInvalid) do - Post.includes(:comments).order("").where(:comments => {:body => "Thank you for the welcome"}).first + assert_nothing_raised do + Post.includes(:comments).order("").where(comments: { body: "Thank you for the welcome" }).first end end def test_deep_including_through_habtm # warm up habtm cache - posts = Post.all.merge!(:includes => {:categories => :categorizations}, :order => "posts.id").to_a + posts = Post.all.merge!(includes: { categories: :categorizations }, order: "posts.id").to_a posts[0].categories[0].categorizations.length - posts = Post.all.merge!(:includes => {:categories => :categorizations}, :order => "posts.id").to_a + posts = Post.all.merge!(includes: { categories: :categorizations }, order: "posts.id").to_a assert_no_queries { assert_equal 2, posts[0].categories[0].categorizations.length } assert_no_queries { assert_equal 1, posts[0].categories[1].categorizations.length } assert_no_queries { assert_equal 2, posts[1].categories[0].categorizations.length } @@ -1243,19 +1279,19 @@ class EagerAssociationTest < ActiveRecord::TestCase end test "scoping with a circular preload" do - assert_equal Comment.find(1), Comment.preload(:post => :comments).scoping { Comment.find(1) } + assert_equal Comment.find(1), Comment.preload(post: :comments).scoping { Comment.find(1) } end test "circular preload does not modify unscoped" do expected = FirstPost.unscoped.find(2) - FirstPost.preload(:comments => :first_post).find(1) + FirstPost.preload(comments: :first_post).find(1) assert_equal expected, FirstPost.unscoped.find(2) end test "preload ignores the scoping" do assert_equal( Comment.find(1).post, - Post.where('1 = 0').scoping { Comment.preload(:post).find(1).post } + Post.where("1 = 0").scoping { Comment.preload(:post).find(1).post } ) end @@ -1280,10 +1316,10 @@ class EagerAssociationTest < ActiveRecord::TestCase end test "works in combination with order(:symbol) and reorder(:symbol)" do - author = Author.includes(:posts).references(:posts).order(:name).find_by('posts.title IS NOT NULL') + author = Author.includes(:posts).references(:posts).order(:name).find_by("posts.title IS NOT NULL") assert_equal authors(:bob), author - author = Author.includes(:posts).references(:posts).reorder(:name).find_by('posts.title IS NOT NULL') + author = Author.includes(:posts).references(:posts).reorder(:name).find_by("posts.title IS NOT NULL") assert_equal authors(:bob), author end @@ -1309,6 +1345,7 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_nothing_raised do authors(:david).essays.includes(:writer).any? + authors(:david).essays.includes(:writer).exists? end end @@ -1323,7 +1360,7 @@ class EagerAssociationTest < ActiveRecord::TestCase test "including associations with where.not adds implicit references" do author = assert_queries(2) { - Author.includes(:posts).where.not(posts: { title: 'Welcome to the weblog'} ).last + Author.includes(:posts).where.not(posts: { title: "Welcome to the weblog" } ).last } assert_no_queries { @@ -1357,9 +1394,21 @@ class EagerAssociationTest < ActiveRecord::TestCase exception = assert_raises(ArgumentError) do Author.preload(10).to_a end - assert_equal('10 was not recognized for preload', exception.message) + assert_equal("10 was not recognized for preload", exception.message) + end + + test "associations with extensions are not instance dependent" do + assert_nothing_raised do + Author.includes(:posts_with_extension).to_a + end end + test "including associations with extensions and an instance dependent scope is not supported" do + e = assert_raises(ArgumentError) do + Author.includes(:posts_with_extension_and_instance).to_a + end + assert_match(/Preloading instance dependent scopes is not supported/, e.message) + end test "preloading readonly association" do # has-one @@ -1375,6 +1424,24 @@ class EagerAssociationTest < ActiveRecord::TestCase assert david.readonly_comments.first.readonly? end + test "eager-loading non-readonly association" do + # has_one + firm = Firm.where(id: "1").eager_load(:account).first! + assert_not firm.account.readonly? + + # has_and_belongs_to_many + project = Project.where(id: "2").eager_load(:developers).first! + assert_not project.developers.first.readonly? + + # has_many :through + david = Author.where(id: "1").eager_load(:comments).first! + assert_not david.comments.first.readonly? + + # belongs_to + post = Post.where(id: "1").eager_load(:author).first! + assert_not post.author.readonly? + end + test "eager-loading readonly association" do # has-one firm = Firm.where(id: "1").eager_load(:readonly_account).first! @@ -1389,17 +1456,17 @@ class EagerAssociationTest < ActiveRecord::TestCase assert david.readonly_comments.first.readonly? # belongs_to - post = Post.where(id: "1").eager_load(:author).first! - assert post.author.readonly? + post = Post.where(id: "1").eager_load(:readonly_author).first! + assert post.readonly_author.readonly? end test "preloading a polymorphic association with references to the associated table" do - post = Post.includes(:tags).references(:tags).where('tags.name = ?', 'General').first + post = Post.includes(:tags).references(:tags).where("tags.name = ?", "General").first assert_equal posts(:welcome), post end test "eager-loading a polymorphic association with references to the associated table" do - post = Post.eager_load(:tags).where('tags.name = ?', 'General').first + post = Post.eager_load(:tags).where("tags.name = ?", "General").first assert_equal posts(:welcome), post end diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb index b161cde335..cc86e1a16d 100644 --- a/activerecord/test/cases/associations/extension_test.rb +++ b/activerecord/test/cases/associations/extension_test.rb @@ -1,10 +1,10 @@ require "cases/helper" -require 'models/post' -require 'models/comment' -require 'models/project' -require 'models/developer' -require 'models/computer' -require 'models/company_in_module' +require "models/post" +require "models/comment" +require "models/project" +require "models/developer" +require "models/computer" +require "models/company_in_module" class AssociationsExtensionsTest < ActiveRecord::TestCase fixtures :projects, :developers, :developers_projects, :comments, :posts @@ -63,19 +63,19 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase extend!(Developer) extend!(MyApplication::Business::Developer) - assert Object.const_get 'DeveloperAssociationNameAssociationExtension' - assert MyApplication::Business.const_get 'DeveloperAssociationNameAssociationExtension' + assert Object.const_get "DeveloperAssociationNameAssociationExtension" + assert MyApplication::Business.const_get "DeveloperAssociationNameAssociationExtension" end def test_proxy_association_after_scoped post = posts(:welcome) assert_equal post.association(:comments), post.comments.the_association - assert_equal post.association(:comments), post.comments.where('1=1').the_association + assert_equal post.association(:comments), post.comments.where("1=1").the_association end private def extend!(model) - ActiveRecord::Associations::Builder::HasMany.define_extensions(model, :association_name) { } + ActiveRecord::Associations::Builder::HasMany.define_extensions(model, :association_name) {} end end diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index cab9dff6ca..6bded77eb2 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 @@ -1,94 +1,99 @@ require "cases/helper" -require 'models/developer' -require 'models/computer' -require 'models/project' -require 'models/company' -require 'models/course' -require 'models/customer' -require 'models/order' -require 'models/categorization' -require 'models/category' -require 'models/post' -require 'models/author' -require 'models/tag' -require 'models/tagging' -require 'models/parrot' -require 'models/person' -require 'models/pirate' -require 'models/professor' -require 'models/treasure' -require 'models/price_estimate' -require 'models/club' -require 'models/member' -require 'models/membership' -require 'models/sponsor' -require 'models/country' -require 'models/treaty' -require 'models/vertex' -require 'models/publisher' -require 'models/publisher/article' -require 'models/publisher/magazine' -require 'active_support/core_ext/string/conversions' +require "models/developer" +require "models/computer" +require "models/project" +require "models/company" +require "models/course" +require "models/customer" +require "models/order" +require "models/categorization" +require "models/category" +require "models/post" +require "models/author" +require "models/tag" +require "models/tagging" +require "models/parrot" +require "models/person" +require "models/pirate" +require "models/professor" +require "models/treasure" +require "models/price_estimate" +require "models/club" +require "models/user" +require "models/member" +require "models/membership" +require "models/sponsor" +require "models/country" +require "models/treaty" +require "models/vertex" +require "models/publisher" +require "models/publisher/article" +require "models/publisher/magazine" +require "active_support/core_ext/string/conversions" class ProjectWithAfterCreateHook < ActiveRecord::Base - self.table_name = 'projects' + self.table_name = "projects" has_and_belongs_to_many :developers, - :class_name => "DeveloperForProjectWithAfterCreateHook", - :join_table => "developers_projects", - :foreign_key => "project_id", - :association_foreign_key => "developer_id" + class_name: "DeveloperForProjectWithAfterCreateHook", + join_table: "developers_projects", + foreign_key: "project_id", + association_foreign_key: "developer_id" after_create :add_david def add_david - david = DeveloperForProjectWithAfterCreateHook.find_by_name('David') + david = DeveloperForProjectWithAfterCreateHook.find_by_name("David") david.projects << self end end class DeveloperForProjectWithAfterCreateHook < ActiveRecord::Base - self.table_name = 'developers' + self.table_name = "developers" has_and_belongs_to_many :projects, - :class_name => "ProjectWithAfterCreateHook", - :join_table => "developers_projects", - :association_foreign_key => "project_id", - :foreign_key => "developer_id" + class_name: "ProjectWithAfterCreateHook", + join_table: "developers_projects", + association_foreign_key: "project_id", + foreign_key: "developer_id" end class ProjectWithSymbolsForKeys < ActiveRecord::Base - self.table_name = 'projects' + self.table_name = "projects" has_and_belongs_to_many :developers, - :class_name => "DeveloperWithSymbolsForKeys", - :join_table => :developers_projects, - :foreign_key => :project_id, - :association_foreign_key => "developer_id" + class_name: "DeveloperWithSymbolsForKeys", + join_table: :developers_projects, + foreign_key: :project_id, + association_foreign_key: "developer_id" end class DeveloperWithSymbolsForKeys < ActiveRecord::Base - self.table_name = 'developers' + self.table_name = "developers" has_and_belongs_to_many :projects, - :class_name => "ProjectWithSymbolsForKeys", - :join_table => :developers_projects, - :association_foreign_key => :project_id, - :foreign_key => "developer_id" + class_name: "ProjectWithSymbolsForKeys", + join_table: :developers_projects, + association_foreign_key: :project_id, + foreign_key: "developer_id" end class SubDeveloper < Developer - self.table_name = 'developers' + self.table_name = "developers" has_and_belongs_to_many :special_projects, - :join_table => 'developers_projects', - :foreign_key => "project_id", - :association_foreign_key => "developer_id" + join_table: "developers_projects", + foreign_key: "project_id", + association_foreign_key: "developer_id" end 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 +end + class DeveloperWithExtendOption < Developer module NamedExtension def category - 'sns' + "sns" end end @@ -96,8 +101,8 @@ class DeveloperWithExtendOption < Developer end class ProjectUnscopingDavidDefaultScope < ActiveRecord::Base - self.table_name = 'projects' - has_and_belongs_to_many :developers, -> { unscope(where: 'name') }, + self.table_name = "projects" + has_and_belongs_to_many :developers, -> { unscope(where: "name") }, class_name: "LazyBlockDeveloperCalledDavid", join_table: "developers_projects", foreign_key: "project_id", @@ -109,14 +114,14 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase :parrots, :pirates, :parrots_pirates, :treasures, :price_estimates, :tags, :taggings, :computers def setup_data_for_habtm_case - ActiveRecord::Base.connection.execute('delete from countries_treaties') + ActiveRecord::Base.connection.execute("delete from countries_treaties") - country = Country.new(:name => 'India') - country.country_id = 'c1' + country = Country.new(name: "India") + country.country_id = "c1" country.save! - treaty = Treaty.new(:name => 'peace') - treaty.treaty_id = 't1' + treaty = Treaty.new(name: "peace") + treaty.treaty_id = "t1" country.treaties << treaty end @@ -130,22 +135,35 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase setup_data_for_habtm_case con = ActiveRecord::Base.connection - sql = 'select * from countries_treaties' + sql = "select * from countries_treaties" record = con.select_rows(sql).last - assert_equal 'c1', record[0] - assert_equal 't1', record[1] + assert_equal "c1", record[0] + assert_equal "t1", record[1] end def test_proper_usage_of_primary_keys_and_join_table setup_data_for_habtm_case - assert_equal 'country_id', Country.primary_key - assert_equal 'treaty_id', Treaty.primary_key + assert_equal "country_id", Country.primary_key + assert_equal "treaty_id", Treaty.primary_key country = Country.first assert_equal 1, country.treaties.count end + def test_join_table_composite_primary_key_should_not_warn + country = Country.new(name: "India") + country.country_id = "c1" + country.save! + + treaty = Treaty.new(name: "peace") + treaty.treaty_id = "t1" + warning = capture(:stderr) do + country.treaties << treaty + end + assert_no_match(/WARNING: Active Record does not support composite primary key\./, warning) + end + def test_has_and_belongs_to_many david = Developer.find(1) @@ -155,7 +173,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase active_record = Project.find(1) assert !active_record.developers.empty? assert_equal 3, active_record.developers.size - assert active_record.developers.include?(david) + assert_includes active_record.developers, david end def test_adding_single @@ -244,7 +262,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_habtm_saving_multiple_relationships new_project = Project.new("name" => "Grimetime") amount_of_developers = 4 - developers = (0...amount_of_developers).collect {|i| Developer.create(:name => "JME #{i}") }.reverse + developers = (0...amount_of_developers).collect { |i| Developer.create(name: "JME #{i}") }.reverse new_project.developer_ids = [developers[0].id, developers[1].id] new_project.developers_with_callback_ids = [developers[2].id, developers[3].id] @@ -269,11 +287,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_habtm_collection_size_from_params - devel = Developer.new({ + devel = Developer.new( projects_attributes: { - '0' => {} - } - }) + "0" => {} + }) assert_equal 1, devel.projects.size end @@ -309,9 +326,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_build_by_new_record - devel = Developer.new(:name => "Marcel", :salary => 75000) - devel.projects.build(:name => "Make bed") - proj2 = devel.projects.build(:name => "Lie in it") + devel = Developer.new(name: "Marcel", salary: 75000) + devel.projects.build(name: "Make bed") + proj2 = devel.projects.build(name: "Lie in it") assert_equal devel.projects.last, proj2 assert !proj2.persisted? devel.save @@ -334,9 +351,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_create_by_new_record - devel = Developer.new(:name => "Marcel", :salary => 75000) - devel.projects.build(:name => "Make bed") - proj2 = devel.projects.build(:name => "Lie in it") + devel = Developer.new(name: "Marcel", salary: 75000) + devel.projects.build(name: "Make bed") + proj2 = devel.projects.build(name: "Lie in it") assert_equal devel.projects.last, proj2 assert !proj2.persisted? devel.save @@ -348,16 +365,16 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_creation_respects_hash_condition # in Oracle '' is saved as null therefore need to save ' ' in not null column - post = categories(:general).post_with_conditions.build(:body => ' ') + post = categories(:general).post_with_conditions.build(body: " ") assert post.save - assert_equal 'Yet Another Testing Title', post.title + assert_equal "Yet Another Testing Title", post.title # in Oracle '' is saved as null therefore need to save ' ' in not null column - another_post = categories(:general).post_with_conditions.create(:body => ' ') + another_post = categories(:general).post_with_conditions.create(body: " ") assert another_post.persisted? - assert_equal 'Yet Another Testing Title', another_post.title + assert_equal "Yet Another Testing Title", another_post.title end def test_distinct_after_the_fact @@ -531,7 +548,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_no_queries(ignore_none: false) do assert project.developers.loaded? - assert project.developers.include?(developer) + assert_includes project.developers, developer end end @@ -542,14 +559,14 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase project.reload assert ! project.developers.loaded? assert_queries(1) do - assert project.developers.include?(developer) + assert_includes project.developers, developer end assert ! project.developers.loaded? end def test_include_returns_false_for_non_matching_record_to_verify_scoping project = projects(:active_record) - developer = Developer.create :name => "Bryan", :salary => 50_000 + developer = Developer.create name: "Bryan", salary: 50_000 assert ! project.developers.loaded? assert ! project.developers.include?(developer) @@ -563,32 +580,32 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_dynamic_find_should_respect_association_order # Developers are ordered 'name DESC, id DESC' - high_id_jamis = projects(:active_record).developers.create(:name => 'Jamis') + high_id_jamis = projects(:active_record).developers.create(name: "Jamis") - assert_equal high_id_jamis, projects(:active_record).developers.merge(:where => "name = 'Jamis'").first - assert_equal high_id_jamis, projects(:active_record).developers.find_by_name('Jamis') + assert_equal high_id_jamis, projects(:active_record).developers.merge(where: "name = 'Jamis'").first + assert_equal high_id_jamis, projects(:active_record).developers.find_by_name("Jamis") end def test_find_should_append_to_association_order - ordered_developers = projects(:active_record).developers.order('projects.id') - assert_equal ['developers.name desc, developers.id desc', 'projects.id'], ordered_developers.order_values + ordered_developers = projects(:active_record).developers.order("projects.id") + assert_equal ["developers.name desc, developers.id desc", "projects.id"], ordered_developers.order_values end def test_dynamic_find_all_should_respect_readonly_access - projects(:active_record).readonly_developers.each { |d| assert_raise(ActiveRecord::ReadOnlyRecord) { d.save! } if d.valid?} + projects(:active_record).readonly_developers.each { |d| assert_raise(ActiveRecord::ReadOnlyRecord) { d.save! } if d.valid? } projects(:active_record).readonly_developers.each(&:readonly?) end def test_new_with_values_in_collection - jamis = DeveloperForProjectWithAfterCreateHook.find_by_name('Jamis') - david = DeveloperForProjectWithAfterCreateHook.find_by_name('David') - project = ProjectWithAfterCreateHook.new(:name => "Cooking with Bertie") + jamis = DeveloperForProjectWithAfterCreateHook.find_by_name("Jamis") + david = DeveloperForProjectWithAfterCreateHook.find_by_name("David") + project = ProjectWithAfterCreateHook.new(name: "Cooking with Bertie") project.developers << jamis project.save! project.reload - assert project.developers.include?(jamis) - assert project.developers.include?(david) + assert_includes project.developers, jamis + assert_includes project.developers, david end def test_find_in_association_with_options @@ -599,8 +616,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_association_with_extend_option - eponine = DeveloperWithExtendOption.create(name: 'Eponine') - assert_equal 'sns', eponine.projects.category + eponine = DeveloperWithExtendOption.create(name: "Eponine") + assert_equal "sns", eponine.projects.category end def test_replace_with_less @@ -615,7 +632,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase david.projects = [projects(:action_controller), Project.new("name" => "ActionWebSearch")] david.save assert_equal 2, david.projects.length - assert !david.projects.include?(projects(:active_record)) + assert_not_includes david.projects, projects(:active_record) end def test_replace_on_new_object @@ -633,9 +650,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase developer.special_projects << special_project developer.reload - assert developer.projects.include?(special_project) - assert developer.special_projects.include?(special_project) - assert !developer.special_projects.include?(other_project) + assert_includes developer.projects, special_project + assert_includes developer.special_projects, special_project + assert_not_includes developer.special_projects, other_project end def test_symbol_join_table @@ -676,7 +693,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_habtm_respects_select_query_method - assert_equal ['id'], developers(:david).projects.select(:id).first.attributes.keys + assert_equal ["id"], developers(:david).projects.select(:id).first.attributes.keys end def test_join_table_alias @@ -687,8 +704,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal( 3, Developer.references(:developers_projects_join).merge( - :includes => {:projects => :developers}, - :where => 'projects_developers_projects_join.joined_on IS NOT NULL' + includes: { projects: :developers }, + where: "projects_developers_projects_join.joined_on IS NOT NULL" ).to_a.size ) end @@ -707,15 +724,15 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal( 3, Developer.references(:developers_projects_join).merge( - :includes => {:projects => :developers}, :where => 'projects_developers_projects_join.joined_on IS NOT NULL', - :group => group.join(",") + includes: { projects: :developers }, where: "projects_developers_projects_join.joined_on IS NOT NULL", + group: group.join(",") ).to_a.size ) end def test_find_grouped - all_posts_from_category1 = Post.all.merge!(:where => "category_id = 1", :joins => :categories).to_a - grouped_posts_of_category1 = Post.all.merge!(:where => "category_id = 1", :group => "author_id", :select => 'count(posts.id) as posts_count', :joins => :categories).to_a + all_posts_from_category1 = Post.all.merge!(where: "category_id = 1", joins: :categories).to_a + grouped_posts_of_category1 = Post.all.merge!(where: "category_id = 1", group: "author_id", select: "count(posts.id) as posts_count", joins: :categories).to_a assert_equal 5, all_posts_from_category1.size assert_equal 2, grouped_posts_of_category1.size end @@ -762,7 +779,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_assign_ids_ignoring_blanks developer = Developer.new("name" => "Joe") - developer.project_ids = [projects(:active_record).id, nil, projects(:action_controller).id, ''] + developer.project_ids = [projects(:active_record).id, nil, projects(:action_controller).id, ""] developer.save developer.reload assert_equal 2, developer.projects.length @@ -782,8 +799,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_symbols_as_keys - developer = DeveloperWithSymbolsForKeys.new(:name => 'David') - project = ProjectWithSymbolsForKeys.new(:name => 'Rails Testing') + developer = DeveloperWithSymbolsForKeys.new(name: "David") + project = ProjectWithSymbolsForKeys.new(name: "Rails Testing") project.developers << developer project.save! @@ -796,7 +813,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_dynamic_find_should_respect_association_include # SQL error in sort clause if :include is not included # due to Unknown column 'authors.id' - assert Category.find(1).posts_with_authors_sorted_by_author_id.find_by_title('Welcome to the weblog') + assert Category.find(1).posts_with_authors_sorted_by_author_id.find_by_title("Welcome to the weblog") end def test_count @@ -828,12 +845,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_attributes_are_being_set_when_initialized_from_habtm_association_with_where_clause - new_developer = projects(:action_controller).developers.where(:name => "Marcelo").build + new_developer = projects(:action_controller).developers.where(name: "Marcelo").build assert_equal new_developer.name, "Marcelo" end def test_attributes_are_being_set_when_initialized_from_habtm_association_with_multiple_where_clauses - new_developer = projects(:action_controller).developers.where(:name => "Marcelo").where(:salary => 90_000).build + new_developer = projects(:action_controller).developers.where(name: "Marcelo").where(salary: 90_000).build assert_equal new_developer.name, "Marcelo" assert_equal new_developer.salary, 90_000 end @@ -841,7 +858,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_include_method_in_has_and_belongs_to_many_association_should_return_true_for_instance_added_with_build project = Project.new developer = project.developers.build - assert project.developers.include?(developer) + assert_includes project.developers, developer end def test_destruction_does_not_error_without_primary_key @@ -858,7 +875,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase projects = Developer.new.projects assert_no_queries(ignore_none: false) do assert_equal [], projects - assert_equal [], projects.where(title: 'omg') + assert_equal [], projects.where(title: "omg") assert_equal [], projects.pluck(:title) assert_equal 0, projects.count end @@ -872,7 +889,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase treasure.valid? assert_equal 1, treasure.rich_people.size - assert_nil rich_person.first_name, 'should not run associated person validation on create when validate: false' + assert_nil rich_person.first_name, "should not run associated person validation on create when validate: false" end def test_association_with_validate_false_does_not_run_associated_validation_callbacks_on_update @@ -885,11 +902,11 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase treasure.valid? assert_equal 1, treasure.rich_people.size - assert_equal person_first_name, rich_person.first_name, 'should not run associated person validation on update when validate: false' + assert_equal person_first_name, rich_person.first_name, "should not run associated person validation on update when validate: false" end def test_custom_join_table - assert_equal 'edges', Vertex.reflect_on_association(:sources).join_table + assert_equal "edges", Vertex.reflect_on_association(:sources).join_table end def test_has_and_belongs_to_many_in_a_namespaced_model_pointing_to_a_namespaced_model @@ -913,11 +930,11 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_redefine_habtm child = SubDeveloper.new("name" => "Aredridel") child.special_projects << SpecialProject.new("name" => "Special Project") - assert child.save, 'child object should be saved' + assert child.save, "child object should be saved" end def test_habtm_with_reflection_using_class_name_and_fixtures - assert_not_nil Developer._reflections['shared_computers'] + assert_not_nil Developer._reflections["shared_computers"] # Checking the fixture for named association is important here, because it's the only way # we've been able to reproduce this bug assert_not_nil File.read(File.expand_path("../../../fixtures/developers.yml", __FILE__)).index("shared_computers") @@ -925,8 +942,16 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_with_symbol_class_name - assert_nothing_raised NoMethodError do - DeveloperWithSymbolClassName.new + assert_nothing_raised do + developer = DeveloperWithSymbolClassName.new + developer.projects + end + end + + def test_with_constant_class_name + assert_nothing_raised do + developer = DeveloperWithConstantClassName.new + developer.projects end end @@ -983,6 +1008,11 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end end + def test_association_name_is_the_same_as_join_table_name + user = User.create! + assert_nothing_raised { user.jobs_pool.clear } + end + def test_has_and_belongs_to_many_while_partial_writes_false begin original_partial_writes = ActiveRecord::Base.partial_writes diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index e975f4fbdd..c1e04714fb 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -1,43 +1,43 @@ require "cases/helper" -require 'models/developer' -require 'models/computer' -require 'models/project' -require 'models/company' -require 'models/contract' -require 'models/topic' -require 'models/reply' -require 'models/category' -require 'models/image' -require 'models/post' -require 'models/author' -require 'models/essay' -require 'models/comment' -require 'models/person' -require 'models/reader' -require 'models/tagging' -require 'models/tag' -require 'models/invoice' -require 'models/line_item' -require 'models/car' -require 'models/bulb' -require 'models/engine' -require 'models/categorization' -require 'models/minivan' -require 'models/speedometer' -require 'models/reference' -require 'models/job' -require 'models/college' -require 'models/student' -require 'models/pirate' -require 'models/ship' -require 'models/ship_part' -require 'models/treasure' -require 'models/parrot' -require 'models/tyre' -require 'models/subscriber' -require 'models/subscription' -require 'models/zine' -require 'models/interest' +require "models/developer" +require "models/computer" +require "models/project" +require "models/company" +require "models/contract" +require "models/topic" +require "models/reply" +require "models/category" +require "models/image" +require "models/post" +require "models/author" +require "models/essay" +require "models/comment" +require "models/person" +require "models/reader" +require "models/tagging" +require "models/tag" +require "models/invoice" +require "models/line_item" +require "models/car" +require "models/bulb" +require "models/engine" +require "models/categorization" +require "models/minivan" +require "models/speedometer" +require "models/reference" +require "models/job" +require "models/college" +require "models/student" +require "models/pirate" +require "models/ship" +require "models/ship_part" +require "models/treasure" +require "models/parrot" +require "models/tyre" +require "models/subscriber" +require "models/subscription" +require "models/zine" +require "models/interest" class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase fixtures :authors, :posts, :comments @@ -46,7 +46,7 @@ class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCa author = authors(:david) # this can fail on adapters which require ORDER BY expressions to be included in the SELECT expression # if the reorder clauses are not correctly handled - assert author.posts_with_comments_sorted_by_comment_id.where('comments.id > 0').reorder('posts.comments_count DESC', 'posts.tags_count DESC').last + assert author.posts_with_comments_sorted_by_comment_id.where("comments.id > 0").reorder("posts.comments_count DESC", "posts.tags_count DESC").last end end @@ -54,18 +54,18 @@ class HasManyAssociationsTestPrimaryKeys < ActiveRecord::TestCase fixtures :authors, :essays, :subscribers, :subscriptions, :people def test_custom_primary_key_on_new_record_should_fetch_with_query - subscriber = Subscriber.new(nick: 'webster132') + subscriber = Subscriber.new(nick: "webster132") assert !subscriber.subscriptions.loaded? assert_queries 1 do assert_equal 2, subscriber.subscriptions.size end - assert_equal subscriber.subscriptions, Subscription.where(subscriber_id: 'webster132') + assert_equal subscriber.subscriptions, Subscription.where(subscriber_id: "webster132") end def test_association_primary_key_on_new_record_should_fetch_with_query - author = Author.new(:name => "David") + author = Author.new(name: "David") assert !author.essays.loaded? assert_queries 1 do @@ -116,14 +116,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_anonymous_has_many developer = Class.new(ActiveRecord::Base) { - self.table_name = 'developers' + self.table_name = "developers" dev = self developer_project = Class.new(ActiveRecord::Base) { - self.table_name = 'developers_projects' - belongs_to :developer, :anonymous_class => dev + self.table_name = "developers_projects" + belongs_to :developer, anonymous_class: dev } - has_many :developer_projects, :anonymous_class => developer_project, :foreign_key => 'developer_id' + has_many :developer_projects, anonymous_class: developer_project, foreign_key: "developer_id" } dev = developer.first named = Developer.find(dev.id) @@ -135,20 +135,20 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_default_scope_on_relations_is_not_cached counter = 0 posts = Class.new(ActiveRecord::Base) { - self.table_name = 'posts' - self.inheritance_column = 'not_there' + self.table_name = "posts" + self.inheritance_column = "not_there" post = self comments = Class.new(ActiveRecord::Base) { - self.table_name = 'comments' - self.inheritance_column = 'not_there' - belongs_to :post, :anonymous_class => post + self.table_name = "comments" + self.inheritance_column = "not_there" + belongs_to :post, anonymous_class: post default_scope -> { counter += 1 - where("id = :inc", :inc => counter) + where("id = :inc", inc: counter) } } - has_many :comments, :anonymous_class => comments, :foreign_key => 'post_id' + has_many :comments, anonymous_class: comments, foreign_key: "post_id" } assert_equal 0, counter post = posts.first @@ -159,15 +159,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_has_many_build_with_options - college = College.create(name: 'UFMT') - Student.create(active: true, college_id: college.id, name: 'Sarah') + college = College.create(name: "UFMT") + Student.create(active: true, college_id: college.id, name: "Sarah") assert_equal college.students, Student.where(active: true, college_id: college.id) end def test_add_record_to_collection_should_change_its_updated_at - ship = Ship.create(name: 'dauntless') - part = ShipPart.create(name: 'cockpit') + ship = Ship.create(name: "dauntless") + part = ShipPart.create(name: "cockpit") updated_at = part.updated_at travel(1.second) do @@ -181,8 +181,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_clear_collection_should_not_change_updated_at # GH#17161: .clear calls delete_all (and returns the association), # which is intended to not touch associated objects's updated_at field - ship = Ship.create(name: 'dauntless') - part = ShipPart.create(name: 'cockpit', ship_id: ship.id) + ship = Ship.create(name: "dauntless") + part = ShipPart.create(name: "cockpit", ship_id: ship.id) ship.parts.clear part.reload @@ -192,27 +192,27 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_create_from_association_should_respect_default_scope - car = Car.create(:name => 'honda') - assert_equal 'honda', car.name + car = Car.create(name: "honda") + assert_equal "honda", car.name bulb = Bulb.create - assert_equal 'defaulty', bulb.name + assert_equal "defaulty", bulb.name bulb = car.bulbs.build - assert_equal 'defaulty', bulb.name + assert_equal "defaulty", bulb.name bulb = car.bulbs.create - assert_equal 'defaulty', bulb.name + assert_equal "defaulty", bulb.name end def test_build_and_create_from_association_should_respect_passed_attributes_over_default_scope - car = Car.create(name: 'honda') + car = Car.create(name: "honda") - bulb = car.bulbs.build(name: 'exotic') - assert_equal 'exotic', bulb.name + bulb = car.bulbs.build(name: "exotic") + assert_equal "exotic", bulb.name - bulb = car.bulbs.create(name: 'exotic') - assert_equal 'exotic', bulb.name + bulb = car.bulbs.create(name: "exotic") + assert_equal "exotic", bulb.name bulb = car.awesome_bulbs.build(frickinawesome: false) assert_equal false, bulb.frickinawesome @@ -225,36 +225,37 @@ class HasManyAssociationsTest < ActiveRecord::TestCase author = Author.new post = author.thinking_posts.build - assert_equal 'So I was thinking', post.title + assert_equal "So I was thinking", post.title end def test_create_from_association_with_nil_values_should_work - car = Car.create(:name => 'honda') + car = Car.create(name: "honda") bulb = car.bulbs.new(nil) - assert_equal 'defaulty', bulb.name + assert_equal "defaulty", bulb.name bulb = car.bulbs.build(nil) - assert_equal 'defaulty', bulb.name + assert_equal "defaulty", bulb.name bulb = car.bulbs.create(nil) - assert_equal 'defaulty', bulb.name + assert_equal "defaulty", bulb.name end def test_do_not_call_callbacks_for_delete_all - car = Car.create(:name => 'honda') + car = Car.create(name: "honda") car.funky_bulbs.create! + assert_equal 1, car.funky_bulbs.count assert_nothing_raised { car.reload.funky_bulbs.delete_all } - assert_equal 0, Bulb.count, "bulbs should have been deleted using :delete_all strategy" + assert_equal 0, car.funky_bulbs.count, "bulbs should have been deleted using :delete_all strategy" end def test_delete_all_on_association_is_the_same_as_not_loaded author = authors :david - author.thinking_posts.create!(:body => "test") + author.thinking_posts.create!(body: "test") author.reload expected_sql = capture_sql { author.thinking_posts.delete_all } - author.thinking_posts.create!(:body => "test") + author.thinking_posts.create!(body: "test") author.reload author.thinking_posts.inspect loaded_sql = capture_sql { author.thinking_posts.delete_all } @@ -263,11 +264,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_delete_all_on_association_with_nil_dependency_is_the_same_as_not_loaded author = authors :david - author.posts.create!(:title => "test", :body => "body") + author.posts.create!(title: "test", body: "body") author.reload expected_sql = capture_sql { author.posts.delete_all } - author.posts.create!(:title => "test", :body => "body") + author.posts.create!(title: "test", body: "body") author.reload author.posts.to_a loaded_sql = capture_sql { author.posts.delete_all } @@ -282,29 +283,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_building_the_associated_object_with_explicit_sti_base_class firm = DependentFirm.new - company = firm.companies.build(:type => "Company") + company = firm.companies.build(type: "Company") assert_kind_of Company, company, "Expected #{company.class} to be a Company" end def test_building_the_associated_object_with_sti_subclass firm = DependentFirm.new - company = firm.companies.build(:type => "Client") + company = firm.companies.build(type: "Client") assert_kind_of Client, company, "Expected #{company.class} to be a Client" end def test_building_the_associated_object_with_an_invalid_type firm = DependentFirm.new - assert_raise(ActiveRecord::SubclassNotFound) { firm.companies.build(:type => "Invalid") } + assert_raise(ActiveRecord::SubclassNotFound) { firm.companies.build(type: "Invalid") } end def test_building_the_associated_object_with_an_unrelated_type firm = DependentFirm.new - assert_raise(ActiveRecord::SubclassNotFound) { firm.companies.build(:type => "Account") } + assert_raise(ActiveRecord::SubclassNotFound) { firm.companies.build(type: "Account") } end test "building the association with an array" do speedometer = Speedometer.new(speedometer_id: "a") - data = [{name: "first"}, {name: "second"}] + data = [{ name: "first" }, { name: "second" }] speedometer.minivans.build(data) assert_equal 2, speedometer.minivans.size @@ -313,24 +314,24 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_association_keys_bypass_attribute_protection - car = Car.create(:name => 'honda') + car = Car.create(name: "honda") bulb = car.bulbs.new assert_equal car.id, bulb.car_id - bulb = car.bulbs.new :car_id => car.id + 1 + bulb = car.bulbs.new car_id: car.id + 1 assert_equal car.id, bulb.car_id bulb = car.bulbs.build assert_equal car.id, bulb.car_id - bulb = car.bulbs.build :car_id => car.id + 1 + bulb = car.bulbs.build car_id: car.id + 1 assert_equal car.id, bulb.car_id bulb = car.bulbs.create assert_equal car.id, bulb.car_id - bulb = car.bulbs.create :car_id => car.id + 1 + bulb = car.bulbs.create car_id: car.id + 1 assert_equal car.id, bulb.car_id end @@ -340,19 +341,19 @@ class HasManyAssociationsTest < ActiveRecord::TestCase line_item = invoice.line_items.new assert_equal invoice.id, line_item.invoice_id - line_item = invoice.line_items.new :invoice_id => invoice.id + 1 + line_item = invoice.line_items.new invoice_id: invoice.id + 1 assert_equal invoice.id, line_item.invoice_id line_item = invoice.line_items.build assert_equal invoice.id, line_item.invoice_id - line_item = invoice.line_items.build :invoice_id => invoice.id + 1 + line_item = invoice.line_items.build invoice_id: invoice.id + 1 assert_equal invoice.id, line_item.invoice_id line_item = invoice.line_items.create assert_equal invoice.id, line_item.invoice_id - line_item = invoice.line_items.create :invoice_id => invoice.id + 1 + line_item = invoice.line_items.create invoice_id: invoice.id + 1 assert_equal invoice.id, line_item.invoice_id end @@ -373,64 +374,95 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_no_sql_should_be_fired_if_association_already_loaded - Car.create(:name => 'honda') + Car.create(name: "honda") bulbs = Car.first.bulbs bulbs.to_a # to load all instances of bulbs assert_no_queries do bulbs.first() - bulbs.first({}) end assert_no_queries do bulbs.second() - bulbs.second({}) end assert_no_queries do bulbs.third() - bulbs.third({}) end assert_no_queries do bulbs.fourth() - bulbs.fourth({}) end assert_no_queries do bulbs.fifth() - bulbs.fifth({}) end assert_no_queries do bulbs.forty_two() - bulbs.forty_two({}) end assert_no_queries do bulbs.third_to_last() - bulbs.third_to_last({}) end assert_no_queries do bulbs.second_to_last() - bulbs.second_to_last({}) end assert_no_queries do bulbs.last() - bulbs.last({}) + end + end + + def test_finder_method_with_dirty_target + company = companies(:first_firm) + new_clients = [] + assert_no_queries(ignore_none: false) do + new_clients << company.clients_of_firm.build(name: "Another Client") + new_clients << company.clients_of_firm.build(name: "Another Client II") + new_clients << company.clients_of_firm.build(name: "Another Client III") + end + + assert_not company.clients_of_firm.loaded? + assert_queries(1) do + assert_same new_clients[0], company.clients_of_firm.third + assert_same new_clients[1], company.clients_of_firm.fourth + assert_same new_clients[2], company.clients_of_firm.fifth + assert_same new_clients[0], company.clients_of_firm.third_to_last + assert_same new_clients[1], company.clients_of_firm.second_to_last + assert_same new_clients[2], company.clients_of_firm.last + end + end + + def test_finder_bang_method_with_dirty_target + company = companies(:first_firm) + new_clients = [] + assert_no_queries(ignore_none: false) do + new_clients << company.clients_of_firm.build(name: "Another Client") + new_clients << company.clients_of_firm.build(name: "Another Client II") + new_clients << company.clients_of_firm.build(name: "Another Client III") + end + + assert_not company.clients_of_firm.loaded? + assert_queries(1) do + assert_same new_clients[0], company.clients_of_firm.third! + assert_same new_clients[1], company.clients_of_firm.fourth! + assert_same new_clients[2], company.clients_of_firm.fifth! + assert_same new_clients[0], company.clients_of_firm.third_to_last! + assert_same new_clients[1], company.clients_of_firm.second_to_last! + assert_same new_clients[2], company.clients_of_firm.last! end end def test_create_resets_cached_counters - person = Person.create!(:first_name => 'tenderlove') + person = Person.create!(first_name: "tenderlove") post = Post.first assert_equal [], person.readers assert_nil person.readers.find_by_post_id(post.id) - person.readers.create(:post_id => post.id) + person.readers.create(post_id: post.id) assert_equal 1, person.readers.count assert_equal 1, person.readers.length @@ -438,25 +470,41 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal person, person.readers.first.person end - def force_signal37_to_load_all_clients_of_firm - companies(:first_firm).clients_of_firm.each {|f| } + def test_update_all_respects_association_scope + person = Person.new + person.first_name = "Naruto" + person.references << Reference.new + person.id = 10 + person.references + person.save! + assert_equal 1, person.references.update_all(favourite: true) + end + + def test_exists_respects_association_scope + person = Person.new + person.first_name = "Sasuke" + person.references << Reference.new + person.id = 10 + person.references + person.save! + assert_predicate person.references, :exists? end # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first def test_counting_with_counter_sql - assert_equal 3, Firm.all.merge!(:order => "id").first.clients.count + assert_equal 3, Firm.all.merge!(order: "id").first.clients.count end def test_counting - assert_equal 3, Firm.all.merge!(:order => "id").first.plain_clients.count + assert_equal 3, Firm.all.merge!(order: "id").first.plain_clients.count end def test_counting_with_single_hash - assert_equal 1, Firm.all.merge!(:order => "id").first.plain_clients.where(:name => "Microsoft").count + assert_equal 1, Firm.all.merge!(order: "id").first.plain_clients.where(name: "Microsoft").count end def test_counting_with_column_name_and_hash - assert_equal 3, Firm.all.merge!(:order => "id").first.plain_clients.count(:name) + assert_equal 3, Firm.all.merge!(order: "id").first.plain_clients.count(:name) end def test_counting_with_association_limit @@ -466,11 +514,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_finding - assert_equal 3, Firm.all.merge!(:order => "id").first.clients.length + assert_equal 3, Firm.all.merge!(order: "id").first.clients.length end def test_finding_array_compatibility - assert_equal 3, Firm.order(:id).find{|f| f.id > 0}.clients.length + assert_equal 3, Firm.order(:id).find { |f| f.id > 0 }.clients.length end def test_find_many_with_merged_options @@ -480,13 +528,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_should_append_to_association_order - ordered_clients = companies(:first_firm).clients_sorted_desc.order('companies.id') - assert_equal ['id DESC', 'companies.id'], ordered_clients.order_values + ordered_clients = companies(:first_firm).clients_sorted_desc.order("companies.id") + assert_equal ["id DESC", "companies.id"], ordered_clients.order_values end def test_dynamic_find_should_respect_association_order assert_equal companies(:another_first_firm_client), companies(:first_firm).clients_sorted_desc.where("type = 'Client'").first - assert_equal companies(:another_first_firm_client), companies(:first_firm).clients_sorted_desc.find_by_type('Client') + assert_equal companies(:another_first_firm_client), companies(:first_firm).clients_sorted_desc.find_by_type("Client") end def test_taking @@ -508,14 +556,18 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_taking_with_a_number # taking from unloaded Relation bob = Author.find(authors(:bob).id) + new_post = bob.posts.build + assert_not bob.posts.loaded? assert_equal [posts(:misc_by_bob)], bob.posts.take(1) - bob = Author.find(authors(:bob).id) assert_equal [posts(:misc_by_bob), posts(:other_by_bob)], bob.posts.take(2) + assert_equal [posts(:misc_by_bob), posts(:other_by_bob), new_post], bob.posts.take(3) # taking from loaded Relation - bob.posts.to_a - assert_equal [posts(:misc_by_bob)], authors(:bob).posts.take(1) - assert_equal [posts(:misc_by_bob), posts(:other_by_bob)], authors(:bob).posts.take(2) + bob.posts.load + assert bob.posts.loaded? + assert_equal [posts(:misc_by_bob)], bob.posts.take(1) + assert_equal [posts(:misc_by_bob), posts(:other_by_bob)], bob.posts.take(2) + assert_equal [posts(:misc_by_bob), posts(:other_by_bob), new_post], bob.posts.take(3) end def test_taking_with_inverse_of @@ -534,45 +586,45 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_finding_default_orders - assert_equal "Summit", Firm.all.merge!(:order => "id").first.clients.first.name + assert_equal "Summit", Firm.all.merge!(order: "id").first.clients.first.name end def test_finding_with_different_class_name_and_order - assert_equal "Apex", Firm.all.merge!(:order => "id").first.clients_sorted_desc.first.name + assert_equal "Apex", Firm.all.merge!(order: "id").first.clients_sorted_desc.first.name end def test_finding_with_foreign_key - assert_equal "Microsoft", Firm.all.merge!(:order => "id").first.clients_of_firm.first.name + assert_equal "Microsoft", Firm.all.merge!(order: "id").first.clients_of_firm.first.name end def test_finding_with_condition - assert_equal "Microsoft", Firm.all.merge!(:order => "id").first.clients_like_ms.first.name + assert_equal "Microsoft", Firm.all.merge!(order: "id").first.clients_like_ms.first.name end def test_finding_with_condition_hash - assert_equal "Microsoft", Firm.all.merge!(:order => "id").first.clients_like_ms_with_hash_conditions.first.name + assert_equal "Microsoft", Firm.all.merge!(order: "id").first.clients_like_ms_with_hash_conditions.first.name end def test_finding_using_primary_key - assert_equal "Summit", Firm.all.merge!(:order => "id").first.clients_using_primary_key.first.name + assert_equal "Summit", Firm.all.merge!(order: "id").first.clients_using_primary_key.first.name end def test_update_all_on_association_accessed_before_save - firm = Firm.new(name: 'Firm') + firm = Firm.new(name: "Firm") clients_proxy_id = firm.clients.object_id firm.clients << Client.first firm.save! - assert_equal firm.clients.count, firm.clients.update_all(description: 'Great!') + assert_equal firm.clients.count, firm.clients.update_all(description: "Great!") assert_not_equal clients_proxy_id, firm.clients.object_id end def test_update_all_on_association_accessed_before_save_with_explicit_foreign_key # We can use the same cached proxy object because the id is available for the scope - firm = Firm.new(name: 'Firm', id: 100) + firm = Firm.new(name: "Firm", id: 100) clients_proxy_id = firm.clients.object_id firm.clients << Client.first firm.save! - assert_equal firm.clients.count, firm.clients.update_all(description: 'Great!') + assert_equal firm.clients.count, firm.clients.update_all(description: "Great!") assert_equal clients_proxy_id, firm.clients.object_id end @@ -582,7 +634,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_ids - firm = Firm.all.merge!(:order => "id").first + firm = Firm.all.merge!(order: "id").first assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find } @@ -601,9 +653,23 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find(2, 99) } end + def test_find_one_message_on_primary_key + firm = Firm.all.merge!(order: "id").first + + e = assert_raises(ActiveRecord::RecordNotFound) do + firm.clients.find(0) + end + assert_equal 0, e.id + assert_equal "id", e.primary_key + assert_equal "Client", e.model + assert_match (/\ACouldn't find Client with 'id'=0/), e.message + end + def test_find_ids_and_inverse_of force_signal37_to_load_all_clients_of_firm + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + firm = companies(:first_firm) client = firm.clients_of_firm.find(3) assert_kind_of Client, client @@ -614,7 +680,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_all - firm = Firm.all.merge!(:order => "id").first + firm = Firm.all.merge!(order: "id").first assert_equal 3, firm.clients.where("#{QUOTED_TYPE} = 'Client'").to_a.length assert_equal 1, firm.clients.where("name = 'Summit'").to_a.length end @@ -625,7 +691,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert ! firm.clients.loaded? assert_queries(4) do - firm.clients.find_each(:batch_size => 1) {|c| assert_equal firm.id, c.firm_id } + firm.clients.find_each(batch_size: 1) { |c| assert_equal firm.id, c.firm_id } end assert ! firm.clients.loaded? @@ -635,7 +701,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm = companies(:first_firm) assert_queries(2) do - firm.clients.where(name: 'Microsoft').find_each(batch_size: 1) do |c| + firm.clients.where(name: "Microsoft").find_each(batch_size: 1) do |c| assert_equal firm.id, c.firm_id assert_equal "Microsoft", c.name end @@ -650,8 +716,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert ! firm.clients.loaded? assert_queries(2) do - firm.clients.find_in_batches(:batch_size => 2) do |clients| - clients.each {|c| assert_equal firm.id, c.firm_id } + firm.clients.find_in_batches(batch_size: 2) do |clients| + clients.each { |c| assert_equal firm.id, c.firm_id } end end @@ -660,29 +726,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_find_all_sanitized # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first - firm = Firm.all.merge!(:order => "id").first + firm = Firm.all.merge!(order: "id").first summit = firm.clients.where("name = 'Summit'").to_a assert_equal summit, firm.clients.where("name = ?", "Summit").to_a - assert_equal summit, firm.clients.where("name = :name", { :name => "Summit" }).to_a + assert_equal summit, firm.clients.where("name = :name", name: "Summit").to_a end def test_find_first - firm = Firm.all.merge!(:order => "id").first + firm = Firm.all.merge!(order: "id").first client2 = Client.find(2) assert_equal firm.clients.first, firm.clients.order("id").first assert_equal client2, firm.clients.where("#{QUOTED_TYPE} = 'Client'").order("id").first end def test_find_first_sanitized - firm = Firm.all.merge!(:order => "id").first + firm = Firm.all.merge!(order: "id").first client2 = Client.find(2) - assert_equal client2, firm.clients.merge!(:where => ["#{QUOTED_TYPE} = ?", 'Client'], :order => "id").first - assert_equal client2, firm.clients.merge!(:where => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }], :order => "id").first + assert_equal client2, firm.clients.merge!(where: ["#{QUOTED_TYPE} = ?", "Client"], order: "id").first + assert_equal client2, firm.clients.merge!(where: ["#{QUOTED_TYPE} = :type", { type: "Client" }], order: "id").first end def test_find_all_with_include_and_conditions assert_nothing_raised do - Developer.all.merge!(:joins => :audit_logs, :where => {'audit_logs.message' => nil, :name => 'Smith'}).to_a + Developer.all.merge!(joins: :audit_logs, where: { "audit_logs.message" => nil, :name => "Smith" }).to_a end end @@ -692,8 +758,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_grouped - all_clients_of_firm1 = Client.all.merge!(:where => "firm_id = 1").to_a - grouped_clients_of_firm1 = Client.all.merge!(:where => "firm_id = 1", :group => "firm_id", :select => 'firm_id, count(id) as clients_count').to_a + all_clients_of_firm1 = Client.all.merge!(where: "firm_id = 1").to_a + grouped_clients_of_firm1 = Client.all.merge!(where: "firm_id = 1", group: "firm_id", select: "firm_id, count(id) as clients_count").to_a assert_equal 3, all_clients_of_firm1.size assert_equal 1, grouped_clients_of_firm1.size end @@ -715,19 +781,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_select_query_method - assert_equal ['id', 'body'], posts(:welcome).comments.select(:id, :body).first.attributes.keys + assert_equal ["id", "body"], posts(:welcome).comments.select(:id, :body).first.attributes.keys end def test_select_with_block 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 def test_adding force_signal37_to_load_all_clients_of_firm + + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + natural = Client.new("name" => "Natural Company") companies(:first_firm).clients_of_firm << natural assert_equal 3, companies(:first_firm).clients_of_firm.size # checking via the collection @@ -738,7 +814,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_adding_using_create first_firm = companies(:first_firm) assert_equal 3, first_firm.plain_clients.size - first_firm.plain_clients.create(:name => "Natural Company") + first_firm.plain_clients.create(name: "Natural Company") assert_equal 4, first_firm.plain_clients.length assert_equal 4, first_firm.plain_clients.size end @@ -746,7 +822,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_create_with_bang_on_has_many_when_parent_is_new_raises error = assert_raise(ActiveRecord::RecordNotSaved) do firm = Firm.new - firm.plain_clients.create! :name=>"Whoever" + firm.plain_clients.create! name: "Whoever" end assert_equal "You cannot call create unless the parent is saved", error.message @@ -755,7 +831,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_regular_create_on_has_many_when_parent_is_new_raises error = assert_raise(ActiveRecord::RecordNotSaved) do firm = Firm.new - firm.plain_clients.create :name=>"Whoever" + firm.plain_clients.create name: "Whoever" end assert_equal "You cannot call create unless the parent is saved", error.message @@ -763,7 +839,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_create_with_bang_on_has_many_raises_when_record_not_saved assert_raise(ActiveRecord::RecordInvalid) do - firm = Firm.all.merge!(:order => "id").first + firm = Firm.all.merge!(order: "id").first firm.plain_clients.create! end end @@ -784,21 +860,24 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_adding_a_collection force_signal37_to_load_all_clients_of_firm + + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + companies(:first_firm).clients_of_firm.concat([Client.new("name" => "Natural Company"), Client.new("name" => "Apple")]) assert_equal 4, companies(:first_firm).clients_of_firm.size assert_equal 4, companies(:first_firm).clients_of_firm.reload.size end def test_transactions_when_adding_to_persisted - good = Client.new(:name => "Good") - bad = Client.new(:name => "Bad", :raise_on_save => true) + good = Client.new(name: "Good") + bad = Client.new(name: "Bad", raise_on_save: true) begin companies(:first_firm).clients_of_firm.concat(good, bad) rescue Client::RaisedOnSave end - assert !companies(:first_firm).clients_of_firm.reload.include?(good) + assert_not_includes companies(:first_firm).clients_of_firm.reload, good end def test_transactions_when_adding_to_new_record @@ -863,7 +942,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_build_many company = companies(:first_firm) - new_clients = assert_no_queries(ignore_none: false) { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) } + new_clients = assert_no_queries(ignore_none: false) { company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) } assert_equal 2, new_clients.size end @@ -880,7 +959,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 1, first_topic.replies.length assert_no_queries do - first_topic.replies.build(:title => "Not saved", :content => "Superstars") + first_topic.replies.build(title: "Not saved", content: "Superstars") assert_equal 2, first_topic.replies.size end @@ -889,7 +968,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_build_via_block company = companies(:first_firm) - new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build {|client| client.name = "Another Client" } } + new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build { |client| client.name = "Another Client" } } assert !company.clients_of_firm.loaded? assert_equal "Another Client", new_client.name @@ -900,7 +979,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_build_many_via_block company = companies(:first_firm) new_clients = assert_no_queries(ignore_none: false) do - company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) do |client| + company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) do |client| client.name = "changed" end end @@ -919,7 +998,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase first_firm.clients_of_firm.reset assert_queries(1) do - first_firm.clients_of_firm.create(:name => "Superstars") + first_firm.clients_of_firm.create(name: "Superstars") end assert_equal 3, first_firm.clients_of_firm.size @@ -927,6 +1006,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_create force_signal37_to_load_all_clients_of_firm + + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + new_client = companies(:first_firm).clients_of_firm.create("name" => "Another Client") assert new_client.persisted? assert_equal new_client, companies(:first_firm).clients_of_firm.last @@ -934,7 +1016,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_create_many - companies(:first_firm).clients_of_firm.create([{"name" => "Another Client"}, {"name" => "Another Client II"}]) + companies(:first_firm).clients_of_firm.create([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) assert_equal 4, companies(:first_firm).clients_of_firm.reload.size end @@ -946,6 +1028,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_deleting force_signal37_to_load_all_clients_of_firm + + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + companies(:first_firm).clients_of_firm.delete(companies(:first_firm).clients_of_firm.first) assert_equal 1, companies(:first_firm).clients_of_firm.size assert_equal 1, companies(:first_firm).clients_of_firm.reload.size @@ -962,7 +1047,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_has_many_without_counter_cache_option # Ship has a conventionally named `treasures_count` column, but the counter_cache # option is not given on the association. - ship = Ship.create(name: 'Countless', treasures_count: 10) + ship = Ship.create(name: "Countless", treasures_count: 10) assert_not Ship.reflect_on_association(:treasures).has_cached_counter? @@ -970,7 +1055,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal ship.treasures.size, 0 assert_no_difference lambda { ship.reload.treasures_count }, "treasures_count should not be changed" do - ship.treasures.create(name: 'Gold') + ship.treasures.create(name: "Gold") end assert_no_difference lambda { ship.reload.treasures_count }, "treasures_count should not be changed" do @@ -1073,10 +1158,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal original_count, topic.replies_count first_reply = topic.replies.first - first_reply.update_attributes(:parent_id => nil) + first_reply.update_attributes(parent_id: nil) assert_equal original_count - 1, topic.reload.replies_count - first_reply.update_attributes(:parent_id => topic.id) + first_reply.update_attributes(parent_id: topic.id) assert_equal original_count, topic.reload.replies_count end @@ -1089,17 +1174,20 @@ class HasManyAssociationsTest < ActiveRecord::TestCase reply1 = topic1.replies.first reply2 = topic2.replies.first - reply1.update_attributes(:parent_id => topic2.id) + reply1.update_attributes(parent_id: topic2.id) assert_equal original_count1 - 1, topic1.reload.replies_count assert_equal original_count2 + 1, topic2.reload.replies_count - reply2.update_attributes(:parent_id => topic1.id) + reply2.update_attributes(parent_id: topic1.id) assert_equal original_count1, topic1.reload.replies_count assert_equal original_count2, topic2.reload.replies_count end def test_deleting_a_collection force_signal37_to_load_all_clients_of_firm + + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + companies(:first_firm).clients_of_firm.create("name" => "Another Client") assert_equal 3, companies(:first_firm).clients_of_firm.size companies(:first_firm).clients_of_firm.delete([companies(:first_firm).clients_of_firm[0], companies(:first_firm).clients_of_firm[1], companies(:first_firm).clients_of_firm[2]]) @@ -1109,6 +1197,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_delete_all force_signal37_to_load_all_clients_of_firm + + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + companies(:first_firm).dependent_clients_of_firm.create("name" => "Another Client") clients = companies(:first_firm).dependent_clients_of_firm.to_a assert_equal 3, clients.count @@ -1120,6 +1211,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_delete_all_with_not_yet_loaded_association_collection force_signal37_to_load_all_clients_of_firm + + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + companies(:first_firm).clients_of_firm.create("name" => "Another Client") assert_equal 3, companies(:first_firm).clients_of_firm.size companies(:first_firm).clients_of_firm.reset @@ -1129,8 +1223,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_transaction_when_deleting_persisted - good = Client.new(:name => "Good") - bad = Client.new(:name => "Bad", :raise_on_destroy => true) + good = Client.new(name: "Good") + bad = Client.new(name: "Bad", raise_on_destroy: true) companies(:first_firm).clients_of_firm = [good, bad] @@ -1171,7 +1265,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_clearing_updates_counter_cache topic = Topic.first - assert_difference 'topic.reload.replies_count', -1 do + assert_difference "topic.reload.replies_count", -1 do topic.replies.clear end end @@ -1180,7 +1274,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase car = Car.first car.engines.create! - assert_difference 'car.reload.engines_count', -1 do + assert_difference "car.reload.engines_count", -1 do car.engines.clear end end @@ -1238,8 +1332,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_dependent_association_respects_optional_conditions_on_delete firm = companies(:odegy) - Client.create(:client_of => firm.id, :name => "BigShot Inc.") - Client.create(:client_of => firm.id, :name => "SmallTime Inc.") + Client.create(client_of: firm.id, name: "BigShot Inc.") + Client.create(client_of: firm.id, name: "SmallTime Inc.") # only one of two clients is included in the association due to the :conditions key assert_equal 2, Client.where(client_of: firm.id).size assert_equal 1, firm.dependent_conditional_clients_of_firm.size @@ -1250,8 +1344,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_dependent_association_respects_optional_sanitized_conditions_on_delete firm = companies(:odegy) - Client.create(:client_of => firm.id, :name => "BigShot Inc.") - Client.create(:client_of => firm.id, :name => "SmallTime Inc.") + Client.create(client_of: firm.id, name: "BigShot Inc.") + Client.create(client_of: firm.id, name: "SmallTime Inc.") # only one of two clients is included in the association due to the :conditions key assert_equal 2, Client.where(client_of: firm.id).size assert_equal 1, firm.dependent_sanitized_conditional_clients_of_firm.size @@ -1262,8 +1356,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_dependent_association_respects_optional_hash_conditions_on_delete firm = companies(:odegy) - Client.create(:client_of => firm.id, :name => "BigShot Inc.") - Client.create(:client_of => firm.id, :name => "SmallTime Inc.") + Client.create(client_of: firm.id, name: "BigShot Inc.") + Client.create(client_of: firm.id, name: "SmallTime Inc.") # only one of two clients is included in the association due to the :conditions key assert_equal 2, Client.where(client_of: firm.id).size assert_equal 1, firm.dependent_sanitized_conditional_clients_of_firm.size @@ -1289,12 +1383,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase ms_client = companies(:first_firm).clients_like_ms_with_hash_conditions.build assert ms_client.save - assert_equal 'Microsoft', ms_client.name + assert_equal "Microsoft", ms_client.name another_ms_client = companies(:first_firm).clients_like_ms_with_hash_conditions.create assert another_ms_client.persisted? - assert_equal 'Microsoft', another_ms_client.name + assert_equal "Microsoft", another_ms_client.name end def test_clearing_without_initial_access @@ -1308,17 +1402,20 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_deleting_a_item_which_is_not_in_the_collection force_signal37_to_load_all_clients_of_firm - summit = Client.find_by_name('Summit') + + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + + summit = Client.find_by_name("Summit") companies(:first_firm).clients_of_firm.delete(summit) assert_equal 2, companies(:first_firm).clients_of_firm.size assert_equal 2, companies(:first_firm).clients_of_firm.reload.size assert_equal 2, summit.client_of end - def test_deleting_by_fixnum_id + def test_deleting_by_integer_id david = Developer.find(1) - assert_difference 'david.projects.count', -1 do + assert_difference "david.projects.count", -1 do assert_equal 1, david.projects.delete(1).size end @@ -1328,8 +1425,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_deleting_by_string_id david = Developer.find(1) - assert_difference 'david.projects.count', -1 do - assert_equal 1, david.projects.delete('1').size + assert_difference "david.projects.count", -1 do + assert_equal 1, david.projects.delete("1").size end assert_equal 1, david.projects.size @@ -1344,6 +1441,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_destroying force_signal37_to_load_all_clients_of_firm + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + assert_difference "Client.count", -1 do companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first) end @@ -1352,9 +1451,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 1, companies(:first_firm).clients_of_firm.reload.size end - def test_destroying_by_fixnum_id + def test_destroying_by_integer_id force_signal37_to_load_all_clients_of_firm + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + assert_difference "Client.count", -1 do companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first.id) end @@ -1366,6 +1467,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_destroying_by_string_id force_signal37_to_load_all_clients_of_firm + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + assert_difference "Client.count", -1 do companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first.id.to_s) end @@ -1376,6 +1479,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_destroying_a_collection force_signal37_to_load_all_clients_of_firm + + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + companies(:first_firm).clients_of_firm.create("name" => "Another Client") assert_equal 3, companies(:first_firm).clients_of_firm.size @@ -1389,6 +1495,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_destroy_all force_signal37_to_load_all_clients_of_firm + + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + clients = companies(:first_firm).clients_of_firm.to_a assert !clients.empty?, "37signals has clients after load" destroyed = companies(:first_firm).clients_of_firm.destroy_all @@ -1402,17 +1511,17 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm = companies(:first_firm) assert_equal 3, firm.clients.size firm.destroy - assert Client.all.merge!(:where => "firm_id=#{firm.id}").to_a.empty? + assert Client.all.merge!(where: "firm_id=#{firm.id}").to_a.empty? end def test_dependence_for_associations_with_hash_condition david = authors(:david) - assert_difference('Post.count', -1) { assert david.destroy } + assert_difference("Post.count", -1) { assert david.destroy } end def test_destroy_dependent_when_deleted_from_association # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first - firm = Firm.all.merge!(:order => "id").first + firm = Firm.all.merge!(order: "id").first assert_equal 3, firm.clients.size client = firm.clients.first @@ -1439,7 +1548,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm.destroy rescue "do nothing" - assert_equal 3, Client.all.merge!(:where => "firm_id=#{firm.id}").to_a.size + assert_equal 3, Client.all.merge!(where: "firm_id=#{firm.id}").to_a.size end def test_dependence_on_account @@ -1463,21 +1572,21 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_restrict_with_exception - firm = RestrictedWithExceptionFirm.create!(:name => 'restrict') - firm.companies.create(:name => 'child') + firm = RestrictedWithExceptionFirm.create!(name: "restrict") + firm.companies.create(name: "child") assert !firm.companies.empty? assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy } - assert RestrictedWithExceptionFirm.exists?(:name => 'restrict') - assert firm.companies.exists?(:name => 'child') + assert RestrictedWithExceptionFirm.exists?(name: "restrict") + 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' } } } } + 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') + firm = RestrictedWithErrorFirm.create!(name: "restrict") + firm.companies.create(name: "child") assert !firm.companies.empty? @@ -1485,16 +1594,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase 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') + 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') + firm = RestrictedWithErrorFirm.create!(name: "restrict") + firm.companies.create(name: "child") assert !firm.companies.empty? @@ -1503,15 +1612,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert !firm.errors.empty? assert_equal "Cannot delete record because dependent companies exist", firm.errors[:base].first - assert RestrictedWithErrorFirm.exists?(:name => 'restrict') - assert firm.companies.exists?(:name => 'child') + assert RestrictedWithErrorFirm.exists?(name: "restrict") + assert firm.companies.exists?(name: "child") end def test_restrict_with_error_with_locale I18n.backend = I18n::Backend::Simple.new - I18n.backend.store_translations 'en', activerecord: {attributes: {restricted_with_error_firm: {companies: 'client companies'}}} - firm = RestrictedWithErrorFirm.create!(name: 'restrict') - firm.companies.create(name: 'child') + I18n.backend.store_translations "en", activerecord: { attributes: { restricted_with_error_firm: { companies: "client companies" } } } + firm = RestrictedWithErrorFirm.create!(name: "restrict") + firm.companies.create(name: "child") assert !firm.companies.empty? @@ -1520,8 +1629,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert !firm.errors.empty? assert_equal "Cannot delete record because dependent client companies exist", firm.errors[:base].first - assert RestrictedWithErrorFirm.exists?(name: 'restrict') - assert firm.companies.exists?(name: 'child') + assert RestrictedWithErrorFirm.exists?(name: "restrict") + assert firm.companies.exists?(name: "child") ensure I18n.backend.reload! end @@ -1531,10 +1640,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_included_in_collection_for_new_records - client = Client.create(:name => 'Persisted') + client = Client.create(name: "Persisted") assert_nil client.client_of assert_equal false, Firm.new.clients_of_firm.include?(client), - 'includes a client that does not belong to any firm' + "includes a client that does not belong to any firm" end def test_adding_array_and_collection @@ -1542,7 +1651,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_replace_with_less - firm = Firm.all.merge!(:order => "id").first + firm = Firm.all.merge!(order: "id").first firm.clients = [companies(:first_client)] assert firm.save, "Could not save firm" firm.reload @@ -1556,7 +1665,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_replace_with_new - firm = Firm.all.merge!(:order => "id").first + firm = Firm.all.merge!(order: "id").first firm.clients = [companies(:second_client), Client.new("name" => "New Client")] firm.save firm.reload @@ -1589,12 +1698,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm.clients = [] end - assert_equal [], firm.send('clients=', []) + assert_equal [], firm.send("clients=", []) end def test_transactions_when_replacing_on_persisted - good = Client.new(:name => "Good") - bad = Client.new(:name => "Bad", :raise_on_save => true) + good = Client.new(name: "Good") + bad = Client.new(name: "Bad", raise_on_save: true) companies(:first_firm).clients_of_firm = [good] @@ -1657,7 +1766,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase contract_a = Contract.create! contract_b = Contract.create! Contract.create! # another contract - company = Company.new(:name => "Some Company") + company = Company.new(name: "Some Company") company.contract_ids = [contract_a.id, contract_b.id] assert_equal [contract_a.id, contract_b.id], company.contract_ids @@ -1669,8 +1778,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_assign_ids_ignoring_blanks - firm = Firm.create!(:name => 'Apple') - firm.client_ids = [companies(:first_client).id, nil, companies(:second_client).id, ''] + firm = Firm.create!(name: "Apple") + firm.client_ids = [companies(:first_client).id, nil, companies(:second_client).id, ""] firm.save! assert_equal 2, firm.clients.reload.size @@ -1685,14 +1794,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase [ lambda { authors(:mary).comment_ids = [comments(:greetings).id, comments(:more_greetings).id] }, lambda { authors(:mary).comments = [comments(:greetings), comments(:more_greetings)] }, - lambda { authors(:mary).comments << Comment.create!(:body => "Yay", :post_id => 424242) }, + lambda { authors(:mary).comments << Comment.create!(body: "Yay", post_id: 424242) }, lambda { authors(:mary).comments.delete(authors(:mary).comments.first) }, - ].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) } + ].each { |block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) } end def test_dynamic_find_should_respect_association_order_for_through assert_equal Comment.find(10), authors(:david).comments_desc.where("comments.type = 'SpecialComment'").first - assert_equal Comment.find(10), authors(:david).comments_desc.find_by_type('SpecialComment') + assert_equal Comment.find(10), authors(:david).comments_desc.find_by_type("SpecialComment") end def test_has_many_through_respects_hash_conditions @@ -1726,7 +1835,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_include_returns_false_for_non_matching_record_to_verify_scoping firm = companies(:first_firm) - client = Client.create!(:name => 'Not Associated') + client = Client.create!(name: "Not Associated") assert ! firm.clients.loaded? assert_equal false, firm.clients.include?(client) @@ -1755,7 +1864,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_calling_first_or_last_on_existing_record_with_build_should_load_association firm = companies(:first_firm) - firm.clients.build(:name => 'Foo') + firm.clients.build(name: "Foo") assert !firm.clients.loaded? assert_queries 1 do @@ -1769,7 +1878,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_calling_first_nth_or_last_on_existing_record_with_create_should_not_load_association firm = companies(:first_firm) - firm.clients.create(:name => 'Foo') + firm.clients.create(name: "Foo") assert !firm.clients.loaded? assert_queries 3 do @@ -1793,7 +1902,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_calling_first_or_last_with_integer_on_association_should_not_load_association firm = companies(:first_firm) - firm.clients.create(:name => 'Foo') + firm.clients.create(name: "Foo") assert !firm.clients.loaded? assert_queries 2 do @@ -1923,13 +2032,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase old = ActiveRecord::Base.store_full_sti_class ActiveRecord::Base.store_full_sti_class = true - firm = Namespaced::Firm.create({ :name => 'Some Company' }) - firm.clients.create({ :name => 'Some Client' }) + firm = Namespaced::Firm.create(name: "Some Company") + firm.clients.create(name: "Some Client") stats = Namespaced::Firm.all.merge!( - :select => "#{Namespaced::Firm.table_name}.id, COUNT(#{Namespaced::Client.table_name}.id) AS num_clients", - :joins => :clients, - :group => "#{Namespaced::Firm.table_name}.id" + select: "#{Namespaced::Firm.table_name}.id, COUNT(#{Namespaced::Client.table_name}.id) AS num_clients", + joins: :clients, + group: "#{Namespaced::Firm.table_name}.id" ).find firm.id assert_equal 1, stats.num_clients.to_i ensure @@ -1955,8 +2064,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_creating_using_primary_key - firm = Firm.all.merge!(:order => "id").first - client = firm.clients_using_primary_key.create!(:name => 'test') + firm = Firm.all.merge!(order: "id").first + client = firm.clients_using_primary_key.create!(name: "test") assert_equal firm.name, client.firm_name end @@ -1979,12 +2088,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_attributes_are_being_set_when_initialized_from_has_many_association_with_where_clause - new_comment = posts(:welcome).comments.where(:body => "Some content").build + new_comment = posts(:welcome).comments.where(body: "Some content").build assert_equal new_comment.body, "Some content" end def test_attributes_are_being_set_when_initialized_from_has_many_association_with_multiple_where_clauses - new_comment = posts(:welcome).comments.where(:body => "Some content").where(:type => 'SpecialComment').build + new_comment = posts(:welcome).comments.where(body: "Some content").where(type: "SpecialComment").build assert_equal new_comment.body, "Some content" assert_equal new_comment.type, "SpecialComment" assert_equal new_comment.post_id, posts(:welcome).id @@ -1998,7 +2107,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_load_target_respects_protected_attributes topic = Topic.create! - reply = topic.replies.create(:title => "reply 1") + reply = topic.replies.create(title: "reply 1") reply.approved = false reply.save! @@ -2025,7 +2134,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_merging_with_custom_attribute_writer - bulb = Bulb.new(:color => "red") + bulb = Bulb.new(color: "red") assert_equal "RED!", bulb.color car = Car.create! @@ -2035,13 +2144,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_abstract_class_with_polymorphic_has_many - post = SubStiPost.create! :title => "fooo", :body => "baa" - tagging = Tagging.create! :taggable => post + post = SubStiPost.create! title: "fooo", body: "baa" + tagging = Tagging.create! taggable: post assert_equal [tagging], post.taggings end def test_with_polymorphic_has_many_with_custom_columns_name - post = Post.create! :title => 'foo', :body => 'bar' + post = Post.create! title: "foo", body: "bar" image = Image.create! post.images << image @@ -2051,10 +2160,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_build_with_polymorphic_has_many_does_not_allow_to_override_type_and_id welcome = posts(:welcome) - tagging = welcome.taggings.build(:taggable_id => 99, :taggable_type => 'ShouldNotChange') + tagging = welcome.taggings.build(taggable_id: 99, taggable_type: "ShouldNotChange") assert_equal welcome.id, tagging.taggable_id - assert_equal 'Post', tagging.taggable_type + assert_equal "Post", tagging.taggable_type end def test_dont_call_save_callbacks_twice_on_has_many @@ -2066,30 +2175,30 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_association_attributes_are_available_to_after_initialize - car = Car.create(:name => 'honda') + car = Car.create(name: "honda") bulb = car.bulbs.build - assert_equal car.id, bulb.attributes_after_initialize['car_id'] + assert_equal car.id, bulb.attributes_after_initialize["car_id"] end def test_attributes_are_set_when_initialized_from_has_many_null_relationship - car = Car.new name: 'honda' - bulb = car.bulbs.where(name: 'headlight').first_or_initialize - assert_equal 'headlight', bulb.name + car = Car.new name: "honda" + bulb = car.bulbs.where(name: "headlight").first_or_initialize + assert_equal "headlight", bulb.name end def test_attributes_are_set_when_initialized_from_polymorphic_has_many_null_relationship - post = Post.new title: 'title', body: 'bar' - tag = Tag.create!(name: 'foo') + post = Post.new title: "title", body: "bar" + tag = Tag.create!(name: "foo") tagging = post.taggings.where(tag: tag).first_or_initialize assert_equal tag.id, tagging.tag_id - assert_equal 'Post', tagging.taggable_type + assert_equal "Post", tagging.taggable_type end def test_replace - car = Car.create(:name => 'honda') + car = Car.create(name: "honda") bulb1 = car.bulbs.create bulb2 = Bulb.create @@ -2100,7 +2209,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_replace_returns_target - car = Car.create(:name => 'honda') + car = Car.create(name: "honda") bulb1 = car.bulbs.create bulb2 = car.bulbs.create bulb3 = Bulb.create @@ -2117,15 +2226,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end test "first_or_initialize adds the record to the association" do - firm = Firm.create! name: 'omg' + firm = Firm.create! name: "omg" client = firm.clients_of_firm.first_or_initialize assert_equal [client], firm.clients_of_firm end test "first_or_create adds the record to the association" do - firm = Firm.create! name: 'omg' + firm = Firm.create! name: "omg" firm.clients_of_firm.load_target - client = firm.clients_of_firm.first_or_create name: 'lol' + client = firm.clients_of_firm.first_or_create name: "lol" assert_equal [client], firm.clients_of_firm assert_equal [client], firm.reload.clients_of_firm end @@ -2147,7 +2256,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_no_queries(ignore_none: false) do assert_equal [], post.comments - assert_equal [], post.comments.where(body: 'omg') + assert_equal [], post.comments.where(body: "omg") assert_equal [], post.comments.pluck(:body) assert_equal 0, post.comments.sum(:id) assert_equal 0, post.comments.count @@ -2175,7 +2284,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase david = authors(:david) post = david.posts.first - post.type = 'PostWithSpecialCategorization' + post.type = "PostWithSpecialCategorization" post.save categorization = post.categorizations.first @@ -2188,8 +2297,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end test "does not duplicate associations when used with natural primary keys" do - speedometer = Speedometer.create!(id: '4') - speedometer.minivans.create!(minivan_id: 'a-van-red' ,name: 'a van', color: 'red') + speedometer = Speedometer.create!(id: "4") + speedometer.minivans.create!(minivan_id: "a-van-red" ,name: "a van", color: "red") assert_equal 1, speedometer.minivans.to_a.size, "Only one association should be present:\n#{speedometer.minivans.to_a}" assert_equal 1, speedometer.reload.minivans.to_a.size @@ -2205,7 +2314,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end test "can unscope and where the default scope of the associated model" do - Car.has_many :other_bulbs, -> { unscope(where: [:name]).where(name: 'other') }, class_name: "Bulb" + Car.has_many :other_bulbs, -> { unscope(where: [:name]).where(name: "other") }, class_name: "Bulb" car = Car.create! bulb1 = Bulb.create! name: "defaulty", car: car bulb2 = Bulb.create! name: "other", car: car @@ -2215,7 +2324,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end test "can rewhere the default scope of the associated model" do - Car.has_many :old_bulbs, -> { rewhere(name: 'old') }, class_name: "Bulb" + Car.has_many :old_bulbs, -> { rewhere(name: "old") }, class_name: "Bulb" car = Car.create! bulb1 = Bulb.create! name: "defaulty", car: car bulb2 = Bulb.create! name: "old", car: car @@ -2224,7 +2333,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal [bulb2], car.old_bulbs end - test 'unscopes the default scope of associated model when used with include' do + test "unscopes the default scope of associated model when used with include" do car = Car.create! bulb = Bulb.create! name: "other", car: car @@ -2244,7 +2353,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal "Failed to destroy the record", error.message end - test 'updates counter cache when default scope is given' do + test "updates counter cache when default scope is given" do topic = DefaultRejectedTopic.create approved: true assert_difference "topic.reload.replies_count", 1 do @@ -2252,8 +2361,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end end - test 'dangerous association name raises ArgumentError' do - [:errors, 'errors', :save, 'save'].each do |name| + test "dangerous association name raises ArgumentError" do + [:errors, "errors", :save, "save"].each do |name| assert_raises(ArgumentError, "Association #{name} should not be allowed") do Class.new(ActiveRecord::Base) do has_many name @@ -2262,7 +2371,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end end - test 'passes custom context validation to validate children' do + test "passes custom context validation to validate children" do pirate = FamousPirate.new pirate.famous_ships << ship = FamousShip.new @@ -2271,7 +2380,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal "can't be blank", ship.errors[:name].first end - test 'association with instance dependent scope' do + test "association with instance dependent scope" do bob = authors(:bob) Post.create!(title: "signed post by bob", body: "stuff", author: authors(:bob)) Post.create!(title: "anonymous post", body: "more stuff", author: authors(:bob)) @@ -2281,7 +2390,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal [], authors(:david).posts_with_signature.map(&:title) end - test 'associations autosaves when object is already persisted' do + test "associations autosaves when object is already persisted" do bulb = Bulb.create! tyre = Tyre.create! @@ -2294,7 +2403,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 1, car.tyres.count end - test 'associations replace in memory when records have the same id' do + test "associations replace in memory when records have the same id" do bulb = Bulb.create! car = Car.create!(bulbs: [bulb]) @@ -2305,7 +2414,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal "foo", car.bulbs.first.name end - test 'in memory replacement executes no queries' do + test "in memory replacement executes no queries" do bulb = Bulb.create! car = Car.create!(bulbs: [bulb]) @@ -2316,7 +2425,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end end - test 'in memory replacements do not execute callbacks' do + test "in memory replacements do not execute callbacks" do raise_after_add = false klass = Class.new(ActiveRecord::Base) do self.table_name = :cars @@ -2337,7 +2446,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end end - test 'in memory replacements sets inverse instance' do + test "in memory replacements sets inverse instance" do bulb = Bulb.create! car = Car.create!(bulbs: [bulb]) @@ -2347,7 +2456,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_same car, new_bulb.car end - test 'in memory replacement maintains order' do + test "in memory replacement maintains order" do first_bulb = Bulb.create! second_bulb = Bulb.create! car = Car.create!(bulbs: [first_bulb, second_bulb]) @@ -2358,10 +2467,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal [first_bulb, second_bulb], car.bulbs end - test 'double insertion of new object to association when same association used in the after create callback of a new object' do - car = Car.create! - car.bulbs << TrickyBulb.new - assert_equal 1, car.bulbs.size + 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 } + 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 @@ -2401,10 +2513,41 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_ids_reader_memoization - car = Car.create!(name: 'TofaÅŸ') + car = Car.create!(name: "TofaÅŸ") bulb = Bulb.create!(car: car) assert_equal [bulb.id], car.bulb_ids assert_no_queries { car.bulb_ids } end + + def test_loading_association_in_validate_callback_doesnt_affect_persistence + reset_callbacks(:validation, Bulb) do + Bulb.after_validation { |m| m.car.bulbs.load } + + car = Car.create!(name: "Car") + bulb = car.bulbs.create! + + assert_equal [bulb], car.bulbs + end + end + + private + + def force_signal37_to_load_all_clients_of_firm + companies(:first_firm).clients_of_firm.load_target + end + + def reset_callbacks(kind, klass) + old_callbacks = {} + old_callbacks[klass] = klass.send("_#{kind}_callbacks").dup + klass.subclasses.each do |subclass| + old_callbacks[subclass] = subclass.send("_#{kind}_callbacks").dup + end + yield + ensure + klass.send("_#{kind}_callbacks=", old_callbacks[klass]) + klass.subclasses.each do |subclass| + subclass.send("_#{kind}_callbacks=", old_callbacks[subclass]) + end + end end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index bb8c9fa19c..9f716d7820 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -1,31 +1,33 @@ require "cases/helper" -require 'models/post' -require 'models/person' -require 'models/reference' -require 'models/job' -require 'models/reader' -require 'models/comment' -require 'models/rating' -require 'models/tag' -require 'models/tagging' -require 'models/author' -require 'models/owner' -require 'models/pet' -require 'models/toy' -require 'models/contract' -require 'models/company' -require 'models/developer' -require 'models/computer' -require 'models/subscriber' -require 'models/book' -require 'models/subscription' -require 'models/essay' -require 'models/category' -require 'models/categorization' -require 'models/member' -require 'models/membership' -require 'models/club' -require 'models/organization' +require "models/post" +require "models/person" +require "models/reference" +require "models/job" +require "models/reader" +require "models/comment" +require "models/rating" +require "models/tag" +require "models/tagging" +require "models/author" +require "models/owner" +require "models/pet" +require "models/pet_treasure" +require "models/toy" +require "models/treasure" +require "models/contract" +require "models/company" +require "models/developer" +require "models/computer" +require "models/subscriber" +require "models/book" +require "models/subscription" +require "models/essay" +require "models/category" +require "models/categorization" +require "models/member" +require "models/membership" +require "models/club" +require "models/organization" class HasManyThroughAssociationsTest < ActiveRecord::TestCase fixtures :posts, :readers, :people, :comments, :authors, :categories, :taggings, :tags, @@ -35,8 +37,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase # Dummies to force column loads so query counts are clean. def setup - Person.create :first_name => 'gummy' - Reader.create :person_id => 0, :post_id => 0 + Person.create first_name: "gummy" + Reader.create person_id: 0, post_id: 0 end def test_preload_sti_rhs_class @@ -47,9 +49,9 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end def test_preload_sti_middle_relation - club = Club.create!(name: 'Aaron cool banana club') - member1 = Member.create!(name: 'Aaron') - member2 = Member.create!(name: 'Cat') + club = Club.create!(name: "Aaron cool banana club") + member1 = Member.create!(name: "Aaron") + member2 = Member.create!(name: "Cat") SuperMembership.create! club: club, member: member1 CurrentMembership.create! club: club, member: member2 @@ -63,12 +65,12 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } } end - def test_ordered_habtm + def test_ordered_has_many_through person_prime = Class.new(ActiveRecord::Base) do - def self.name; 'Person'; end + def self.name; "Person"; end has_many :readers - has_many :posts, -> { order('posts.id DESC') }, :through => :readers + has_many :posts, -> { order("posts.id DESC") }, through: :readers end posts = person_prime.includes(:posts).first.posts @@ -83,7 +85,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase subscription = make_model "Subscription" subscriber = make_model "Subscriber" - subscriber.primary_key = 'nick' + subscriber.primary_key = "nick" subscription.belongs_to :book, anonymous_class: book subscription.belongs_to :subscriber, anonymous_class: subscriber @@ -104,8 +106,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_no_pk_join_table_append lesson, _, student = make_no_pk_hm_t - sicp = lesson.new(:name => "SICP") - ben = student.new(:name => "Ben Bitdiddle") + sicp = lesson.new(name: "SICP") + ben = student.new(name: "Ben Bitdiddle") sicp.students << ben assert sicp.save! end @@ -113,17 +115,17 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_no_pk_join_table_delete lesson, lesson_student, student = make_no_pk_hm_t - sicp = lesson.new(:name => "SICP") - ben = student.new(:name => "Ben Bitdiddle") - louis = student.new(:name => "Louis Reasoner") + sicp = lesson.new(name: "SICP") + ben = student.new(name: "Ben Bitdiddle") + louis = student.new(name: "Louis Reasoner") sicp.students << ben sicp.students << louis assert sicp.save! sicp.students.reload assert_operator lesson_student.count, :>=, 2 - assert_no_difference('student.count') do - assert_difference('lesson_student.count', -2) do + assert_no_difference("student.count") do + assert_difference("lesson_student.count", -2) do sicp.students.destroy(*student.all.to_a) end end @@ -137,8 +139,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase after_destroy_called = true end - sicp = lesson.new(:name => "SICP") - ben = student.new(:name => "Ben Bitdiddle") + sicp = lesson.new(name: "SICP") + ben = student.new(name: "Ben Bitdiddle") sicp.students << ben assert sicp.save! @@ -148,16 +150,16 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end def make_no_pk_hm_t - lesson = make_model 'Lesson' - student = make_model 'Student' + lesson = make_model "Lesson" + student = make_model "Student" - lesson_student = make_model 'LessonStudent' - lesson_student.table_name = 'lessons_students' + lesson_student = make_model "LessonStudent" + lesson_student.table_name = "lessons_students" - lesson_student.belongs_to :lesson, :anonymous_class => lesson - lesson_student.belongs_to :student, :anonymous_class => student - lesson.has_many :lesson_students, :anonymous_class => lesson_student - lesson.has_many :students, :through => :lesson_students, :anonymous_class => student + lesson_student.belongs_to :lesson, anonymous_class: lesson + lesson_student.belongs_to :student, anonymous_class: student + lesson.has_many :lesson_students, anonymous_class: lesson_student + lesson.has_many :students, through: :lesson_students, anonymous_class: student [lesson, lesson_student, student] end @@ -173,7 +175,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase person = Person.new post = Post.new person.posts << post - assert person.posts.include?(post) + assert_includes person.posts, post end def test_associate_existing @@ -185,18 +187,18 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end assert_queries(1) do - assert post.people.include?(person) + assert_includes post.people, person end - assert post.reload.people.reload.include?(person) + assert_includes post.reload.people.reload, person end def test_delete_all_for_with_dependent_option_destroy person = people(:david) assert_equal 1, person.jobs_with_dependent_destroy.count - assert_no_difference 'Job.count' do - assert_difference 'Reference.count', -1 do + assert_no_difference "Job.count" do + assert_difference "Reference.count", -1 do person.reload.jobs_with_dependent_destroy.delete_all end end @@ -206,8 +208,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase person = people(:david) assert_equal 1, person.jobs_with_dependent_nullify.count - assert_no_difference 'Job.count' do - assert_no_difference 'Reference.count' do + assert_no_difference "Job.count" do + assert_no_difference "Reference.count" do person.reload.jobs_with_dependent_nullify.delete_all end end @@ -217,8 +219,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase person = people(:david) assert_equal 1, person.jobs_with_dependent_delete_all.count - assert_no_difference 'Job.count' do - assert_difference 'Reference.count', -1 do + assert_no_difference "Job.count" do + assert_difference "Reference.count", -1 do person.reload.jobs_with_dependent_delete_all.delete_all end end @@ -236,7 +238,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase post = posts(:thinking) person = people(:david) - assert_difference 'post.people.to_a.count', 2 do + assert_difference "post.people.to_a.count", 2 do post.people << person post.people << person end @@ -246,7 +248,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase post = posts(:thinking) person = people(:david) - assert_difference 'post.people.count', 2 do + assert_difference "post.people.count", 2 do post.people << person post.people << person end @@ -259,12 +261,12 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase post.people << person post.people << person - counts = ['post.people.count', 'post.people.to_a.count', 'post.readers.count', 'post.readers.to_a.count'] + counts = ["post.people.count", "post.people.to_a.count", "post.readers.count", "post.readers.to_a.count"] assert_difference counts, -2 do post.people.delete(person) end - assert !post.people.reload.include?(person) + assert_not_includes post.people.reload, person end def test_associating_new @@ -272,7 +274,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase new_person = nil # so block binding catches it assert_queries(0) do - new_person = Person.new :first_name => 'bob' + new_person = Person.new first_name: "bob" end # Associating new records always saves them @@ -282,59 +284,59 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end assert_queries(1) do - assert posts(:thinking).people.include?(new_person) + assert_includes posts(:thinking).people, new_person end - assert posts(:thinking).reload.people.reload.include?(new_person) + assert_includes posts(:thinking).reload.people.reload, new_person end def test_associate_new_by_building assert_queries(1) { posts(:thinking) } assert_queries(0) do - posts(:thinking).people.build(:first_name => "Bob") - posts(:thinking).people.new(:first_name => "Ted") + posts(:thinking).people.build(first_name: "Bob") + posts(:thinking).people.new(first_name: "Ted") end # Should only need to load the association once assert_queries(1) do - assert posts(:thinking).people.collect(&:first_name).include?("Bob") - assert posts(:thinking).people.collect(&:first_name).include?("Ted") + assert_includes posts(:thinking).people.collect(&:first_name), "Bob" + assert_includes posts(:thinking).people.collect(&:first_name), "Ted" end # 2 queries for each new record (1 to save the record itself, 1 for the join model) # * 2 new records = 4 # + 1 query to save the actual post = 5 assert_queries(5) do - posts(:thinking).body += '-changed' + posts(:thinking).body += "-changed" posts(:thinking).save end - assert posts(:thinking).reload.people.reload.collect(&:first_name).include?("Bob") - assert posts(:thinking).reload.people.reload.collect(&:first_name).include?("Ted") + assert_includes posts(:thinking).reload.people.reload.collect(&:first_name), "Bob" + assert_includes posts(:thinking).reload.people.reload.collect(&:first_name), "Ted" end def test_build_then_save_with_has_many_inverse post = posts(:thinking) - person = post.people.build(:first_name => "Bob") + person = post.people.build(first_name: "Bob") person.save post.reload - assert post.people.include?(person) + assert_includes post.people, person end def test_build_then_save_with_has_one_inverse post = posts(:thinking) - person = post.single_people.build(:first_name => "Bob") + person = post.single_people.build(first_name: "Bob") person.save post.reload - assert post.single_people.include?(person) + assert_includes post.single_people, person end def test_both_parent_ids_set_when_saving_new - post = Post.new(title: 'Hello', body: 'world') - person = Person.new(first_name: 'Sean') + post = Post.new(title: "Hello", body: "world") + person = Person.new(first_name: "Sean") post.people = [person] post.save @@ -346,7 +348,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end def test_delete_association - assert_queries(2){posts(:welcome);people(:michael); } + assert_queries(2) { posts(:welcome);people(:michael); } assert_queries(1) do posts(:welcome).people.delete(people(:michael)) @@ -392,10 +394,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase person = people(:michael) job = jobs(:magician) - reference = Reference.where(:job_id => job.id, :person_id => person.id).first + reference = Reference.where(job_id: job.id, person_id: person.id).first - assert_no_difference ['Job.count', 'Reference.count'] do - assert_difference 'person.jobs.count', -1 do + assert_no_difference ["Job.count", "Reference.count"] do + assert_difference "person.jobs.count", -1 do person.jobs_with_dependent_nullify.delete(job) end end @@ -414,8 +416,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase # Make sure we're not deleting everything assert person.jobs.count >= 2 - assert_no_difference 'Job.count' do - assert_difference ['person.jobs.count', 'Reference.count'], -1 do + assert_no_difference "Job.count" do + assert_difference ["person.jobs.count", "Reference.count"], -1 do person.jobs_with_dependent_delete_all.delete(job) end end @@ -435,8 +437,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase # Make sure we're not deleting everything assert person.jobs.count >= 2 - assert_no_difference 'Job.count' do - assert_difference ['person.jobs.count', 'Reference.count'], -1 do + assert_no_difference "Job.count" do + assert_difference ["person.jobs.count", "Reference.count"], -1 do person.jobs_with_dependent_destroy.delete(job) end end @@ -453,8 +455,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase # Create a reference which is not linked to a job. This should not be destroyed. person.references.create! - assert_no_difference 'Job.count' do - assert_difference 'Reference.count', -person.jobs.count do + assert_no_difference "Job.count" do + assert_difference "Reference.count", -person.jobs.count do person.destroy end end @@ -466,8 +468,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase # Create a reference which is not linked to a job. This should not be destroyed. person.references.create! - assert_no_difference 'Job.count' do - assert_difference 'Reference.count', -person.jobs.count do + assert_no_difference "Job.count" do + assert_difference "Reference.count", -person.jobs.count do person.destroy end end @@ -478,7 +480,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase references = person.references.to_a - assert_no_difference ['Reference.count', 'Job.count'] do + assert_no_difference ["Reference.count", "Job.count"] do person.destroy end @@ -489,30 +491,30 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_update_counter_caches_on_delete post = posts(:welcome) - tag = post.tags.create!(:name => 'doomed') + tag = post.tags.create!(name: "doomed") - assert_difference ['post.reload.tags_count'], -1 do + assert_difference ["post.reload.tags_count"], -1 do posts(:welcome).tags.delete(tag) end end def test_update_counter_caches_on_delete_with_dependent_destroy post = posts(:welcome) - tag = post.tags.create!(:name => 'doomed') + tag = post.tags.create!(name: "doomed") post.update_columns(tags_with_destroy_count: post.tags.count) - assert_difference ['post.reload.tags_with_destroy_count'], -1 do + assert_difference ["post.reload.tags_with_destroy_count"], -1 do posts(:welcome).tags_with_destroy.delete(tag) end end def test_update_counter_caches_on_delete_with_dependent_nullify post = posts(:welcome) - tag = post.tags.create!(:name => 'doomed') + tag = post.tags.create!(name: "doomed") post.update_columns(tags_with_nullify_count: post.tags.count) - assert_no_difference 'post.reload.tags_count' do - assert_difference 'post.reload.tags_with_nullify_count', -1 do + assert_no_difference "post.reload.tags_count" do + assert_difference "post.reload.tags_with_nullify_count", -1 do posts(:welcome).tags_with_nullify.delete(tag) end end @@ -520,7 +522,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_update_counter_caches_on_replace_association post = posts(:welcome) - tag = post.tags.create!(:name => 'doomed') + tag = post.tags.create!(name: "doomed") tag.tagged_posts << posts(:thinking) tag.tagged_posts = [] @@ -531,15 +533,15 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_update_counter_caches_on_destroy post = posts(:welcome) - tag = post.tags.create!(name: 'doomed') + tag = post.tags.create!(name: "doomed") - assert_difference 'post.reload.tags_count', -1 do + assert_difference "post.reload.tags_count", -1 do tag.tagged_posts.destroy(post) end end def test_replace_association - assert_queries(4){posts(:welcome);people(:david);people(:michael); posts(:welcome).people.reload} + assert_queries(4) { posts(:welcome);people(:david);people(:michael); posts(:welcome).people.reload } # 1 query to delete the existing reader (michael) # 1 query to associate the new reader (david) @@ -547,35 +549,35 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase posts(:welcome).people = [people(:david)] end - assert_queries(0){ - assert posts(:welcome).people.include?(people(:david)) - assert !posts(:welcome).people.include?(people(:michael)) + assert_queries(0) { + assert_includes posts(:welcome).people, people(:david) + assert_not_includes posts(:welcome).people, people(:michael) } - assert posts(:welcome).reload.people.reload.include?(people(:david)) - assert !posts(:welcome).reload.people.reload.include?(people(:michael)) + assert_includes posts(:welcome).reload.people.reload, people(:david) + assert_not_includes posts(:welcome).reload.people.reload, people(:michael) end def test_replace_order_is_preserved posts(:welcome).people.clear posts(:welcome).people = [people(:david), people(:michael)] - assert_equal [people(:david).id, people(:michael).id], posts(:welcome).readers.order('id').map(&:person_id) + assert_equal [people(:david).id, people(:michael).id], posts(:welcome).readers.order("id").map(&:person_id) # Test the inverse order in case the first success was a coincidence posts(:welcome).people.clear posts(:welcome).people = [people(:michael), people(:david)] - assert_equal [people(:michael).id, people(:david).id], posts(:welcome).readers.order('id').map(&:person_id) + assert_equal [people(:michael).id, people(:david).id], posts(:welcome).readers.order("id").map(&:person_id) end def test_replace_by_id_order_is_preserved posts(:welcome).people.clear posts(:welcome).person_ids = [people(:david).id, people(:michael).id] - assert_equal [people(:david).id, people(:michael).id], posts(:welcome).readers.order('id').map(&:person_id) + assert_equal [people(:david).id, people(:michael).id], posts(:welcome).readers.order("id").map(&:person_id) # Test the inverse order in case the first success was a coincidence posts(:welcome).people.clear posts(:welcome).person_ids = [people(:michael).id, people(:david).id] - assert_equal [people(:michael).id, people(:david).id], posts(:welcome).readers.order('id').map(&:person_id) + assert_equal [people(:michael).id, people(:david).id], posts(:welcome).readers.order("id").map(&:person_id) end def test_associate_with_create @@ -584,15 +586,15 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase # 1 query for the new record, 1 for the join table record # No need to update the actual collection yet! assert_queries(2) do - posts(:thinking).people.create(:first_name=>"Jeb") + posts(:thinking).people.create(first_name: "Jeb") end # *Now* we actually need the collection so it's loaded assert_queries(1) do - assert posts(:thinking).people.collect(&:first_name).include?("Jeb") + assert_includes posts(:thinking).people.collect(&:first_name), "Jeb" end - assert posts(:thinking).reload.people.reload.collect(&:first_name).include?("Jeb") + assert_includes posts(:thinking).reload.people.reload.collect(&:first_name), "Jeb" end def test_through_record_is_built_when_created_with_where @@ -603,66 +605,66 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_associate_with_create_and_no_options peeps = posts(:thinking).people.count - posts(:thinking).people.create(:first_name => 'foo') + posts(:thinking).people.create(first_name: "foo") assert_equal peeps + 1, posts(:thinking).people.count end def test_associate_with_create_with_through_having_conditions impatient_people = posts(:thinking).impatient_people.count - posts(:thinking).impatient_people.create!(:first_name => 'foo') + posts(:thinking).impatient_people.create!(first_name: "foo") assert_equal impatient_people + 1, posts(:thinking).impatient_people.count end def test_associate_with_create_exclamation_and_no_options peeps = posts(:thinking).people.count - posts(:thinking).people.create!(:first_name => 'foo') + posts(:thinking).people.create!(first_name: "foo") assert_equal peeps + 1, posts(:thinking).people.count end def test_create_on_new_record p = Post.new - error = assert_raises(ActiveRecord::RecordNotSaved) { p.people.create(:first_name => "mew") } + error = assert_raises(ActiveRecord::RecordNotSaved) { p.people.create(first_name: "mew") } assert_equal "You cannot call create unless the parent is saved", error.message - error = assert_raises(ActiveRecord::RecordNotSaved) { p.people.create!(:first_name => "snow") } + error = assert_raises(ActiveRecord::RecordNotSaved) { p.people.create!(first_name: "snow") } assert_equal "You cannot call create unless the parent is saved", error.message end def test_associate_with_create_and_invalid_options firm = companies(:first_firm) - assert_no_difference('firm.developers.count') { assert_nothing_raised { firm.developers.create(:name => '0') } } + assert_no_difference("firm.developers.count") { assert_nothing_raised { firm.developers.create(name: "0") } } end def test_associate_with_create_and_valid_options firm = companies(:first_firm) - assert_difference('firm.developers.count', 1) { firm.developers.create(:name => 'developer') } + assert_difference("firm.developers.count", 1) { firm.developers.create(name: "developer") } end def test_associate_with_create_bang_and_invalid_options firm = companies(:first_firm) - assert_no_difference('firm.developers.count') { assert_raises(ActiveRecord::RecordInvalid) { firm.developers.create!(:name => '0') } } + assert_no_difference("firm.developers.count") { assert_raises(ActiveRecord::RecordInvalid) { firm.developers.create!(name: "0") } } end def test_associate_with_create_bang_and_valid_options firm = companies(:first_firm) - assert_difference('firm.developers.count', 1) { firm.developers.create!(:name => 'developer') } + assert_difference("firm.developers.count", 1) { firm.developers.create!(name: "developer") } end def test_push_with_invalid_record firm = companies(:first_firm) - assert_raises(ActiveRecord::RecordInvalid) { firm.developers << Developer.new(:name => '0') } + assert_raises(ActiveRecord::RecordInvalid) { firm.developers << Developer.new(name: "0") } end def test_push_with_invalid_join_record repair_validations(Contract) do - Contract.validate {|r| r.errors[:base] << 'Invalid Contract' } + Contract.validate { |r| r.errors[:base] << "Invalid Contract" } firm = companies(:first_firm) - lifo = Developer.new(:name => 'lifo') + lifo = Developer.new(name: "lifo") assert_raises(ActiveRecord::RecordInvalid) { firm.developers << lifo } - lifo = Developer.create!(:name => 'lifo') + lifo = Developer.create!(name: "lifo") assert_raises(ActiveRecord::RecordInvalid) { firm.developers << lifo } end end @@ -692,7 +694,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase [:added, :after, "Michael"] ], log.last(2) - post.people_with_callbacks.push(people(:david), Person.create!(:first_name => "Bob"), Person.new(:first_name => "Lary")) + post.people_with_callbacks.push(people(:david), Person.create!(first_name: "Bob"), Person.new(first_name: "Lary")) assert_equal [ [:added, :before, "David"], [:added, :after, "David"], @@ -702,19 +704,19 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase [:added, :after, "Lary"] ],log.last(6) - post.people_with_callbacks.build(:first_name => "Ted") + post.people_with_callbacks.build(first_name: "Ted") assert_equal [ [:added, :before, "Ted"], [:added, :after, "Ted"] ], log.last(2) - post.people_with_callbacks.create(:first_name => "Sam") + post.people_with_callbacks.create(first_name: "Sam") assert_equal [ [:added, :before, "Sam"], [:added, :after, "Sam"] ], log.last(2) - post.people_with_callbacks = [people(:michael),people(:david), Person.new(:first_name => "Julian"), Person.create!(:first_name => "Roger")] + post.people_with_callbacks = [people(:michael),people(:david), Person.new(first_name: "Julian"), Person.create!(first_name: "Roger")] assert_equal((%w(Ted Bob Sam Lary) * 2).sort, log[-12..-5].collect(&:last).sort) assert_equal [ [:added, :before, "Julian"], @@ -727,7 +729,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_dynamic_find_should_respect_association_include # SQL error in sort clause if :include is not included # due to Unknown column 'comments.id' - assert Person.find(1).posts_with_comments_sorted_by_comment_id.find_by_title('Welcome to the weblog') + assert Person.find(1).posts_with_comments_sorted_by_comment_id.find_by_title("Welcome to the weblog") end def test_count_with_include_should_alias_join_table @@ -743,7 +745,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end def test_get_ids_for_has_many_through_with_conditions_should_not_preload - Tagging.create!(:taggable_type => 'Post', :taggable_id => posts(:welcome).id, :tag => tags(:misc)) + Tagging.create!(taggable_type: "Post", taggable_id: posts(:welcome).id, tag: tags(:misc)) assert_not_called(ActiveRecord::Associations::Preloader, :new) do posts(:welcome).misc_tag_ids end @@ -774,16 +776,16 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end def test_has_many_association_through_a_belongs_to_association_where_the_association_doesnt_exist - post = Post.create!(:title => "TITLE", :body => "BODY") + post = Post.create!(title: "TITLE", body: "BODY") assert_equal [], post.author_favorites end def test_has_many_association_through_a_belongs_to_association author = authors(:mary) - post = Post.create!(:author => author, :title => "TITLE", :body => "BODY") - author.author_favorites.create(:favorite_author_id => 1) - author.author_favorites.create(:favorite_author_id => 2) - author.author_favorites.create(:favorite_author_id => 3) + post = Post.create!(author: author, title: "TITLE", body: "BODY") + author.author_favorites.create(favorite_author_id: 1) + author.author_favorites.create(favorite_author_id: 2) + author.author_favorites.create(favorite_author_id: 3) assert_equal post.author.author_favorites, post.author_favorites end @@ -807,37 +809,37 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_modifying_has_many_through_has_one_reflection_should_raise [ - lambda { authors(:david).very_special_comments = [VerySpecialComment.create!(:body => "Gorp!", :post_id => 1011), VerySpecialComment.create!(:body => "Eep!", :post_id => 1012)] }, - lambda { authors(:david).very_special_comments << VerySpecialComment.create!(:body => "Hoohah!", :post_id => 1013) }, + lambda { authors(:david).very_special_comments = [VerySpecialComment.create!(body: "Gorp!", post_id: 1011), VerySpecialComment.create!(body: "Eep!", post_id: 1012)] }, + lambda { authors(:david).very_special_comments << VerySpecialComment.create!(body: "Hoohah!", post_id: 1013) }, lambda { authors(:david).very_special_comments.delete(authors(:david).very_special_comments.first) }, - ].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) } + ].each { |block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) } end def test_has_many_association_through_a_has_many_association_to_self - sarah = Person.create!(:first_name => 'Sarah', :primary_contact_id => people(:susan).id, :gender => 'F', :number1_fan_id => 1) - john = Person.create!(:first_name => 'John', :primary_contact_id => sarah.id, :gender => 'M', :number1_fan_id => 1) + sarah = Person.create!(first_name: "Sarah", primary_contact_id: people(:susan).id, gender: "F", number1_fan_id: 1) + john = Person.create!(first_name: "John", primary_contact_id: sarah.id, gender: "M", number1_fan_id: 1) assert_equal sarah.agents, [john] assert_equal people(:susan).agents.flat_map(&:agents), people(:susan).agents_of_agents end def test_associate_existing_with_nonstandard_primary_key_on_belongs_to - Categorization.create(:author => authors(:mary), :named_category_name => categories(:general).name) + Categorization.create(author: authors(:mary), named_category_name: categories(:general).name) assert_equal categories(:general), authors(:mary).named_categories.first end def test_collection_build_with_nonstandard_primary_key_on_belongs_to author = authors(:mary) - category = author.named_categories.build(:name => "Primary") + category = author.named_categories.build(name: "Primary") author.save - assert Categorization.exists?(:author_id => author.id, :named_category_name => category.name) - assert author.named_categories.reload.include?(category) + assert Categorization.exists?(author_id: author.id, named_category_name: category.name) + assert_includes author.named_categories.reload, category end def test_collection_create_with_nonstandard_primary_key_on_belongs_to author = authors(:mary) - category = author.named_categories.create(:name => "Primary") - assert Categorization.exists?(:author_id => author.id, :named_category_name => category.name) - assert author.named_categories.reload.include?(category) + category = author.named_categories.create(name: "Primary") + assert Categorization.exists?(author_id: author.id, named_category_name: category.name) + assert_includes author.named_categories.reload, category end def test_collection_exists @@ -849,9 +851,9 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_collection_delete_with_nonstandard_primary_key_on_belongs_to author = authors(:mary) - category = author.named_categories.create(:name => "Primary") + category = author.named_categories.create(name: "Primary") author.named_categories.delete(category) - assert !Categorization.exists?(:author_id => author.id, :named_category_name => category.name) + assert !Categorization.exists?(author_id: author.id, named_category_name: category.name) assert author.named_categories.reload.empty? end @@ -884,20 +886,20 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set company = companies(:rails_core) ids = [Developer.first.id, -9999] - assert_raises(ActiveRecord::AssociationTypeMismatch) {company.developer_ids= ids} + assert_raises(ActiveRecord::AssociationTypeMismatch) { company.developer_ids= ids } end def test_build_a_model_from_hm_through_association_with_where_clause - assert_nothing_raised { books(:awdr).subscribers.where(:nick => "marklazz").build } + assert_nothing_raised { books(:awdr).subscribers.where(nick: "marklazz").build } end def test_attributes_are_being_set_when_initialized_from_hm_through_association_with_where_clause - new_subscriber = books(:awdr).subscribers.where(:nick => "marklazz").build + new_subscriber = books(:awdr).subscribers.where(nick: "marklazz").build assert_equal new_subscriber.nick, "marklazz" end def test_attributes_are_being_set_when_initialized_from_hm_through_association_with_multiple_where_clauses - new_subscriber = books(:awdr).subscribers.where(:nick => "marklazz").where(:name => 'Marcelo Giorgi').build + new_subscriber = books(:awdr).subscribers.where(nick: "marklazz").where(name: "Marcelo Giorgi").build assert_equal new_subscriber.nick, "marklazz" assert_equal new_subscriber.name, "Marcelo Giorgi" end @@ -906,14 +908,14 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase person = Person.new reference = person.references.build job = reference.build_job - assert person.jobs.include?(job) + assert_includes person.jobs, job end def test_include_method_in_association_through_should_return_true_for_instance_added_with_nested_builds author = Author.new post = author.posts.build comment = post.comments.build - assert author.comments.include?(comment) + assert_includes author.comments, comment end def test_through_association_readonly_should_be_false @@ -930,7 +932,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_polymorphic_with_primary_key_option assert_equal [categories(:general)], authors(:david).essay_categories - authors = Author.joins(:essay_categories).where('categories.id' => categories(:general).id) + authors = Author.joins(:essay_categories).where("categories.id" => categories(:general).id) assert_equal authors(:david), authors.first assert_equal [owners(:blackbeard)], authors(:david).essay_owners @@ -942,7 +944,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_with_primary_key_option assert_equal [categories(:general)], authors(:david).essay_categories_2 - authors = Author.joins(:essay_categories_2).where('categories.id' => categories(:general).id) + authors = Author.joins(:essay_categories_2).where("categories.id" => categories(:general).id) assert_equal authors(:david), authors.first end @@ -954,30 +956,30 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end def test_has_many_through_with_default_scope_on_join_model - assert_equal posts(:welcome).comments.order('id').to_a, authors(:david).comments_on_first_posts + assert_equal posts(:welcome).comments.order("id").to_a, authors(:david).comments_on_first_posts end def test_create_has_many_through_with_default_scope_on_join_model - category = authors(:david).special_categories.create(:name => "Foo") - assert_equal 1, category.categorizations.where(:special => true).count + category = authors(:david).special_categories.create(name: "Foo") + assert_equal 1, category.categorizations.where(special: true).count end def test_joining_has_many_through_with_distinct - mary = Author.joins(:unique_categorized_posts).where(:id => authors(:mary).id).first + mary = Author.joins(:unique_categorized_posts).where(id: authors(:mary).id).first assert_equal 1, mary.unique_categorized_posts.length assert_equal 1, mary.unique_categorized_post_ids.length end def test_joining_has_many_through_belongs_to - posts = Post.joins(:author_categorizations).order('posts.id'). - where('categorizations.id' => categorizations(:mary_thinking_sti).id) + posts = Post.joins(:author_categorizations).order("posts.id"). + where("categorizations.id" => categorizations(:mary_thinking_sti).id) assert_equal [posts(:eager_other), posts(:misc_by_mary), posts(:other_by_mary)], posts end def test_select_chosen_fields_only author = authors(:david) - assert_equal ['body', 'id'].sort, author.comments.select('comments.body').first.attributes.keys.sort + assert_equal ["body", "id"].sort, author.comments.select("comments.body").first.attributes.keys.sort end def test_get_has_many_through_belongs_to_ids_with_conditions @@ -1020,7 +1022,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase post = posts(:welcome) address = author_addresses(:david_address) - assert post.author_addresses.include?(address) + assert_includes post.author_addresses, address post.author_addresses.delete(address) assert post[:author_count].nil? end @@ -1028,7 +1030,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_primary_key_option_on_source post = posts(:welcome) category = categories(:general) - Categorization.create!(:post_id => post.id, :named_category_name => category.name) + Categorization.create!(post_id: post.id, named_category_name: category.name) assert_equal [category], post.named_categories assert_equal [category.name], post.named_category_ids # checks when target loaded @@ -1037,29 +1039,29 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_create_should_not_raise_exception_when_join_record_has_errors repair_validations(Categorization) do - Categorization.validate { |r| r.errors[:base] << 'Invalid Categorization' } - Category.create(:name => 'Fishing', :authors => [Author.first]) + Categorization.validate { |r| r.errors[:base] << "Invalid Categorization" } + Category.create(name: "Fishing", authors: [Author.first]) end end def test_assign_array_to_new_record_builds_join_records - c = Category.new(:name => 'Fishing', :authors => [Author.first]) + c = Category.new(name: "Fishing", authors: [Author.first]) assert_equal 1, c.categorizations.size end def test_create_bang_should_raise_exception_when_join_record_has_errors repair_validations(Categorization) do - Categorization.validate { |r| r.errors[:base] << 'Invalid Categorization' } + Categorization.validate { |r| r.errors[:base] << "Invalid Categorization" } assert_raises(ActiveRecord::RecordInvalid) do - Category.create!(:name => 'Fishing', :authors => [Author.first]) + Category.create!(name: "Fishing", authors: [Author.first]) end end end def test_save_bang_should_raise_exception_when_join_record_has_errors repair_validations(Categorization) do - Categorization.validate { |r| r.errors[:base] << 'Invalid Categorization' } - c = Category.new(:name => 'Fishing', :authors => [Author.first]) + Categorization.validate { |r| r.errors[:base] << "Invalid Categorization" } + c = Category.new(name: "Fishing", authors: [Author.first]) assert_raises(ActiveRecord::RecordInvalid) do c.save! end @@ -1068,32 +1070,44 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_save_returns_falsy_when_join_record_has_errors repair_validations(Categorization) do - Categorization.validate { |r| r.errors[:base] << 'Invalid Categorization' } - c = Category.new(:name => 'Fishing', :authors => [Author.first]) + Categorization.validate { |r| r.errors[:base] << "Invalid Categorization" } + c = Category.new(name: "Fishing", authors: [Author.first]) assert_not c.save end end def test_preloading_empty_through_association_via_joins - person = Person.create!(:first_name => "Gaga") - person = Person.where(:id => person.id).where('readers.id = 1 or 1=1').references(:readers).includes(:posts).to_a.first + person = Person.create!(first_name: "Gaga") + person = Person.where(id: person.id).where("readers.id = 1 or 1=1").references(:readers).includes(:posts).to_a.first - assert person.posts.loaded?, 'person.posts should be loaded' + assert person.posts.loaded?, "person.posts should be loaded" assert_equal [], person.posts end + def test_preloading_empty_through_with_polymorphic_source_association + owner = Owner.create!(name: "Rainbow Unicat") + pet = Pet.create!(owner: owner) + person = Person.create!(first_name: "Gaga") + treasure = Treasure.create!(looter: person) + non_looted_treasure = Treasure.create!() + PetTreasure.create!(pet: pet, treasure: treasure, rainbow_color: "Ultra violet indigo") + PetTreasure.create!(pet: pet, treasure: non_looted_treasure, rainbow_color: "Ultra violet indigo") + + assert_equal [person], Owner.where(name: "Rainbow Unicat").includes(pets: :persons).first.persons.to_a + end + def test_explicitly_joining_join_table assert_equal owners(:blackbeard).toys, owners(:blackbeard).toys.with_pet end def test_has_many_through_with_polymorphic_source - post = tags(:general).tagged_posts.create! :title => "foo", :body => "bar" + post = tags(:general).tagged_posts.create! title: "foo", body: "bar" assert_equal [tags(:general)], post.reload.tags end def test_has_many_through_obeys_order_on_through_association owner = owners(:blackbeard) - assert owner.toys.to_sql.include?("pets.name desc") + assert_includes owner.toys.to_sql, "pets.name desc" assert_equal ["parrot", "bulbul"], owner.toys.map { |r| r.pet.name } end @@ -1102,7 +1116,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_no_queries(ignore_none: false) do assert_equal [], person.posts - assert_equal [], person.posts.where(body: 'omg') + assert_equal [], person.posts.where(body: "omg") assert_equal [], person.posts.pluck(:body) assert_equal 0, person.posts.sum(:tags_count) assert_equal 0, person.posts.count @@ -1134,9 +1148,9 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end def test_has_many_through_unscope_default_scope - post = Post.create!(:title => 'Beaches', :body => "I like beaches!") - Reader.create! :person => people(:david), :post => post - LazyReader.create! :person => people(:susan), :post => post + post = Post.create!(title: "Beaches", body: "I like beaches!") + Reader.create! person: people(:david), post: post + LazyReader.create! person: people(:susan), post: post assert_equal 2, post.people.to_a.size assert_equal 1, post.lazy_people.to_a.size @@ -1146,8 +1160,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end def test_has_many_through_add_with_sti_middle_relation - club = SuperClub.create!(name: 'Fight Club') - member = Member.create!(name: 'Tyler Durden') + club = SuperClub.create!(name: "Fight Club") + member = Member.create!(name: "Tyler Durden") club.members << member assert_equal 1, SuperMembership.where(member_id: member.id, club_id: club.id).count diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index c9d9e29f09..1a0e6d2f8e 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -1,15 +1,15 @@ require "cases/helper" -require 'models/developer' -require 'models/computer' -require 'models/project' -require 'models/company' -require 'models/ship' -require 'models/pirate' -require 'models/car' -require 'models/bulb' -require 'models/author' -require 'models/image' -require 'models/post' +require "models/developer" +require "models/computer" +require "models/project" +require "models/company" +require "models/ship" +require "models/pirate" +require "models/car" +require "models/bulb" +require "models/author" +require "models/image" +require "models/post" class HasOneAssociationsTest < ActiveRecord::TestCase self.use_transactional_tests = false unless supports_savepoints? @@ -28,7 +28,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase ActiveRecord::SQLCounter.clear_log companies(:first_firm).account ensure - assert ActiveRecord::SQLCounter.log_all.all? { |sql| /order by/i !~ sql }, 'ORDER BY was used in the query' + assert ActiveRecord::SQLCounter.log_all.all? { |sql| /order by/i !~ sql }, "ORDER BY was used in the query" end def test_has_one_cache_nils @@ -36,13 +36,13 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_queries(1) { assert_nil firm.account } assert_queries(0) { assert_nil firm.account } - firms = Firm.all.merge!(:includes => :account).to_a + firms = Firm.all.merge!(includes: :account).to_a assert_queries(0) { firms.each(&:account) } end def test_with_select assert_equal Firm.find(1).account_with_select.attributes.size, 2 - assert_equal Firm.all.merge!(:includes => :account_with_select).find(1).account_with_select.attributes.size, 2 + assert_equal Firm.all.merge!(includes: :account_with_select).find(1).account_with_select.attributes.size, 2 end def test_finding_using_primary_key @@ -102,7 +102,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase def test_nullification_on_association_change firm = companies(:rails_core) old_account_id = firm.account.id - firm.account = Account.new(:credit_limit => 5) + firm.account = Account.new(credit_limit: 5) # account is dependent with nullify, therefore its firm_id should be nil assert_nil Account.find(old_account_id).firm_id end @@ -125,12 +125,12 @@ class HasOneAssociationsTest < ActiveRecord::TestCase end def test_association_change_calls_delete - companies(:first_firm).deletable_account = Account.new(:credit_limit => 5) + companies(:first_firm).deletable_account = Account.new(credit_limit: 5) assert_equal [], Account.destroyed_account_ids[companies(:first_firm).id] end def test_association_change_calls_destroy - companies(:first_firm).account = Account.new(:credit_limit => 5) + companies(:first_firm).account = Account.new(credit_limit: 5) assert_equal [companies(:first_firm).id], Account.destroyed_account_ids[companies(:first_firm).id] end @@ -170,27 +170,27 @@ class HasOneAssociationsTest < ActiveRecord::TestCase end def test_dependence_with_nil_associate - firm = DependentFirm.new(:name => 'nullify') + firm = DependentFirm.new(name: "nullify") firm.save! assert_nothing_raised { firm.destroy } end def test_restrict_with_exception - firm = RestrictedWithExceptionFirm.create!(:name => 'restrict') - firm.create_account(:credit_limit => 10) + firm = RestrictedWithExceptionFirm.create!(name: "restrict") + firm.create_account(credit_limit: 10) assert_not_nil firm.account assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy } - assert RestrictedWithExceptionFirm.exists?(:name => 'restrict') + assert RestrictedWithExceptionFirm.exists?(name: "restrict") 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' } } } } + I18n.backend.store_translations :en, activerecord: { errors: { messages: { restrict_dependent_destroy: { one: "message for deprecated key" } } } } - firm = RestrictedWithErrorFirm.create!(name: 'restrict') + firm = RestrictedWithErrorFirm.create!(name: "restrict") firm.create_account(credit_limit: 10) assert_not_nil firm.account @@ -198,16 +198,16 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_deprecated { firm.destroy } assert !firm.errors.empty? - assert_equal 'message for deprecated key', firm.errors[:base].first - assert RestrictedWithErrorFirm.exists?(name: 'restrict') + 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) + firm = RestrictedWithErrorFirm.create!(name: "restrict") + firm.create_account(credit_limit: 10) assert_not_nil firm.account @@ -215,14 +215,14 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert !firm.errors.empty? assert_equal "Cannot delete record because a dependent account exists", firm.errors[:base].first - assert RestrictedWithErrorFirm.exists?(:name => 'restrict') + assert RestrictedWithErrorFirm.exists?(name: "restrict") assert firm.account.present? end def test_restrict_with_error_with_locale I18n.backend = I18n::Backend::Simple.new - I18n.backend.store_translations 'en', activerecord: {attributes: {restricted_with_error_firm: {account: 'firm account'}}} - firm = RestrictedWithErrorFirm.create!(name: 'restrict') + I18n.backend.store_translations "en", activerecord: { attributes: { restricted_with_error_firm: { account: "firm account" } } } + firm = RestrictedWithErrorFirm.create!(name: "restrict") firm.create_account(credit_limit: 10) assert_not_nil firm.account @@ -231,7 +231,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert !firm.errors.empty? assert_equal "Cannot delete record because a dependent firm account exists", firm.errors[:base].first - assert RestrictedWithErrorFirm.exists?(name: 'restrict') + assert RestrictedWithErrorFirm.exists?(name: "restrict") assert firm.account.present? ensure I18n.backend.reload! @@ -260,24 +260,24 @@ class HasOneAssociationsTest < ActiveRecord::TestCase def test_building_the_associated_object_with_explicit_sti_base_class firm = DependentFirm.new - company = firm.build_company(:type => "Company") + company = firm.build_company(type: "Company") assert_kind_of Company, company, "Expected #{company.class} to be a Company" end def test_building_the_associated_object_with_sti_subclass firm = DependentFirm.new - company = firm.build_company(:type => "Client") + company = firm.build_company(type: "Client") assert_kind_of Client, company, "Expected #{company.class} to be a Client" end def test_building_the_associated_object_with_an_invalid_type firm = DependentFirm.new - assert_raise(ActiveRecord::SubclassNotFound) { firm.build_company(:type => "Invalid") } + assert_raise(ActiveRecord::SubclassNotFound) { firm.build_company(type: "Invalid") } end def test_building_the_associated_object_with_an_unrelated_type firm = DependentFirm.new - assert_raise(ActiveRecord::SubclassNotFound) { firm.build_company(:type => "Account") } + assert_raise(ActiveRecord::SubclassNotFound) { firm.build_company(type: "Account") } end def test_build_and_create_should_not_happen_within_scope @@ -295,19 +295,19 @@ class HasOneAssociationsTest < ActiveRecord::TestCase end def test_create_association - firm = Firm.create(:name => "GlobalMegaCorp") - account = firm.create_account(:credit_limit => 1000) + firm = Firm.create(name: "GlobalMegaCorp") + account = firm.create_account(credit_limit: 1000) assert_equal account, firm.reload.account end def test_create_association_with_bang - firm = Firm.create(:name => "GlobalMegaCorp") - account = firm.create_account!(:credit_limit => 1000) + firm = Firm.create(name: "GlobalMegaCorp") + account = firm.create_account!(credit_limit: 1000) assert_equal account, firm.reload.account end def test_create_association_with_bang_failing - firm = Firm.create(:name => "GlobalMegaCorp") + firm = Firm.create(name: "GlobalMegaCorp") assert_raise ActiveRecord::RecordInvalid do firm.create_account! end @@ -319,7 +319,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase end def test_create_with_inexistent_foreign_key_failing - firm = Firm.create(name: 'GlobalMegaCorp') + firm = Firm.create(name: "GlobalMegaCorp") assert_raises(ActiveRecord::UnknownAttributeError) do firm.create_account_with_inexistent_foreign_key @@ -365,7 +365,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase def test_finding_with_interpolated_condition firm = Firm.first - superior = firm.clients.create(:name => 'SuperiorCo') + superior = firm.clients.create(name: "SuperiorCo") superior.rating = 10 superior.save assert_equal 10, firm.clients_with_interpolated_conditions.first.rating @@ -382,7 +382,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase end def test_save_still_works_after_accessing_nil_has_one - jp = Company.new :name => 'Jaded Pixel' + jp = Company.new name: "Jaded Pixel" jp.dummy_account.nil? assert_nothing_raised do @@ -411,14 +411,14 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_nothing_raised do Firm.find(@firm.id).save! - Firm.all.merge!(:includes => :account).find(@firm.id).save! + Firm.all.merge!(includes: :account).find(@firm.id).save! end @firm.account.destroy assert_nothing_raised do Firm.find(@firm.id).save! - Firm.all.merge!(:includes => :account).find(@firm.id).save! + Firm.all.merge!(includes: :account).find(@firm.id).save! end end @@ -435,7 +435,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase end def test_attributes_are_being_set_when_initialized_from_has_one_association_with_where_clause - new_account = companies(:first_firm).build_account(:firm_name => 'Account') + new_account = companies(:first_firm).build_account(firm_name: "Account") assert_equal new_account.firm_name, "Account" end @@ -505,63 +505,63 @@ class HasOneAssociationsTest < ActiveRecord::TestCase end def test_association_keys_bypass_attribute_protection - car = Car.create(:name => 'honda') + car = Car.create(name: "honda") bulb = car.build_bulb assert_equal car.id, bulb.car_id - bulb = car.build_bulb :car_id => car.id + 1 + bulb = car.build_bulb car_id: car.id + 1 assert_equal car.id, bulb.car_id bulb = car.create_bulb assert_equal car.id, bulb.car_id - bulb = car.create_bulb :car_id => car.id + 1 + bulb = car.create_bulb car_id: car.id + 1 assert_equal car.id, bulb.car_id end def test_association_protect_foreign_key - pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") + pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?") ship = pirate.build_ship assert_equal pirate.id, ship.pirate_id - ship = pirate.build_ship :pirate_id => pirate.id + 1 + ship = pirate.build_ship pirate_id: pirate.id + 1 assert_equal pirate.id, ship.pirate_id ship = pirate.create_ship assert_equal pirate.id, ship.pirate_id - ship = pirate.create_ship :pirate_id => pirate.id + 1 + ship = pirate.create_ship pirate_id: pirate.id + 1 assert_equal pirate.id, ship.pirate_id end def test_build_with_block - car = Car.create(:name => 'honda') + car = Car.create(name: "honda") - bulb = car.build_bulb{ |b| b.color = 'Red' } - assert_equal 'RED!', bulb.color + bulb = car.build_bulb { |b| b.color = "Red" } + assert_equal "RED!", bulb.color end def test_create_with_block - car = Car.create(:name => 'honda') + car = Car.create(name: "honda") - bulb = car.create_bulb{ |b| b.color = 'Red' } - assert_equal 'RED!', bulb.color + bulb = car.create_bulb { |b| b.color = "Red" } + assert_equal "RED!", bulb.color end def test_create_bang_with_block - car = Car.create(:name => 'honda') + car = Car.create(name: "honda") - bulb = car.create_bulb!{ |b| b.color = 'Red' } - assert_equal 'RED!', bulb.color + bulb = car.create_bulb! { |b| b.color = "Red" } + assert_equal "RED!", bulb.color end def test_association_attributes_are_available_to_after_initialize - car = Car.create(:name => 'honda') + car = Car.create(name: "honda") bulb = car.create_bulb - assert_equal car.id, bulb.attributes_after_initialize['car_id'] + assert_equal car.id, bulb.attributes_after_initialize["car_id"] end def test_has_one_transaction @@ -581,36 +581,36 @@ class HasOneAssociationsTest < ActiveRecord::TestCase def test_has_one_assignment_dont_trigger_save_on_change_of_same_object pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?") - ship = pirate.build_ship(name: 'old name') + ship = pirate.build_ship(name: "old name") ship.save! - ship.name = 'new name' + ship.name = "new name" assert ship.changed? assert_queries(1) do # One query for updating name, not triggering query for updating pirate_id pirate.ship = ship end - assert_equal 'new name', pirate.ship.reload.name + assert_equal "new name", pirate.ship.reload.name end def test_has_one_assignment_triggers_save_on_change_on_replacing_object pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?") - ship = pirate.build_ship(name: 'old name') + ship = pirate.build_ship(name: "old name") ship.save! - new_ship = Ship.create(name: 'new name') + new_ship = Ship.create(name: "new name") assert_queries(2) do # One query for updating name and second query for updating pirate_id pirate.ship = new_ship end - assert_equal 'new name', pirate.ship.reload.name + assert_equal "new name", pirate.ship.reload.name end def test_has_one_autosave_with_primary_key_manually_set - post = Post.create(id: 1234, title: "Some title", body: 'Some content') - author = Author.new(id: 33, name: 'Hank Moody') + post = Post.create(id: 1234, title: "Some title", body: "Some content") + author = Author.new(id: 33, name: "Hank Moody") author.post = post author.save @@ -621,7 +621,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase end def test_has_one_loading_for_new_record - post = Post.create!(author_id: 42, title: 'foo', body: 'bar') + post = Post.create!(author_id: 42, title: "foo", body: "bar") author = Author.new(id: 42) assert_equal post, author.post end @@ -635,7 +635,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase end def test_with_polymorphic_has_one_with_custom_columns_name - post = Post.create! :title => 'foo', :body => 'bar' + post = Post.create! title: "foo", body: "bar" image = Image.create! post.main_image = image @@ -644,8 +644,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_equal image, post.main_image end - test 'dangerous association name raises ArgumentError' do - [:errors, 'errors', :save, 'save'].each do |name| + test "dangerous association name raises ArgumentError" do + [:errors, "errors", :save, "save"].each do |name| assert_raises(ArgumentError, "Association #{name} should not be allowed") do Class.new(ActiveRecord::Base) do has_one name @@ -659,4 +659,22 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_deprecated { firm.account(true) } end + + class SpecialBook < ActiveRecord::Base + self.table_name = "books" + belongs_to :author, class_name: "SpecialAuthor" + end + + class SpecialAuthor < ActiveRecord::Base + self.table_name = "authors" + has_one :book, class_name: "SpecialBook", foreign_key: "author_id" + end + + def test_assocation_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 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 b2b46812b9..b2f47d2daf 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -1,25 +1,25 @@ require "cases/helper" -require 'models/club' -require 'models/member_type' -require 'models/member' -require 'models/membership' -require 'models/sponsor' -require 'models/organization' -require 'models/member_detail' -require 'models/minivan' -require 'models/dashboard' -require 'models/speedometer' -require 'models/category' -require 'models/author' -require 'models/essay' -require 'models/owner' -require 'models/post' -require 'models/comment' -require 'models/categorization' -require 'models/customer' -require 'models/carrier' -require 'models/shop_account' -require 'models/customer_carrier' +require "models/club" +require "models/member_type" +require "models/member" +require "models/membership" +require "models/sponsor" +require "models/organization" +require "models/member_detail" +require "models/minivan" +require "models/dashboard" +require "models/speedometer" +require "models/category" +require "models/author" +require "models/essay" +require "models/owner" +require "models/post" +require "models/comment" +require "models/categorization" +require "models/customer" +require "models/carrier" +require "models/shop_account" +require "models/customer_carrier" class HasOneThroughAssociationsTest < ActiveRecord::TestCase fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations, :minivans, @@ -34,14 +34,14 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase end def test_creating_association_creates_through_record - new_member = Member.create(:name => "Chris") - new_member.club = Club.create(:name => "LRUG") + new_member = Member.create(name: "Chris") + new_member.club = Club.create(name: "LRUG") assert_not_nil new_member.current_membership assert_not_nil new_member.club end def test_creating_association_builds_through_record_for_new - new_member = Member.new(:name => "Jane") + new_member = Member.new(name: "Jane") new_member.club = clubs(:moustache_club) assert new_member.current_membership assert_equal clubs(:moustache_club), new_member.current_membership.club @@ -51,8 +51,8 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase end def test_creating_association_sets_both_parent_ids_for_new - member = Member.new(name: 'Sean Griffin') - club = Club.new(name: 'Da Club') + member = Member.new(name: "Sean Griffin") + club = Club.new(name: "Da Club") member.club = club @@ -65,7 +65,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase end def test_replace_target_record - new_club = Club.create(:name => "Marx Bros") + new_club = Club.create(name: "Marx Bros") @member.club = new_club @member.reload assert_equal new_club, @member.club @@ -73,7 +73,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_replacing_target_record_deletes_old_association assert_no_difference "Membership.count" do - new_club = Club.create(:name => "Bananarama") + new_club = Club.create(name: "Bananarama") @member.club = new_club @member.reload end @@ -92,30 +92,30 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_eager_loading members = assert_queries(3) do #base table, through table, clubs table - Member.all.merge!(:includes => :club, :where => ["name = ?", "Groucho Marx"]).to_a + Member.all.merge!(includes: :club, where: ["name = ?", "Groucho Marx"]).to_a end assert_equal 1, members.size - assert_not_nil assert_no_queries {members[0].club} + assert_not_nil assert_no_queries { members[0].club } end def test_has_one_through_eager_loading_through_polymorphic members = assert_queries(3) do #base table, through table, clubs table - Member.all.merge!(:includes => :sponsor_club, :where => ["name = ?", "Groucho Marx"]).to_a + Member.all.merge!(includes: :sponsor_club, where: ["name = ?", "Groucho Marx"]).to_a end assert_equal 1, members.size - assert_not_nil assert_no_queries {members[0].sponsor_club} + assert_not_nil assert_no_queries { members[0].sponsor_club } end def test_has_one_through_with_conditions_eager_loading # conditions on the through table - assert_equal clubs(:moustache_club), Member.all.merge!(:includes => :favourite_club).find(@member.id).favourite_club + 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_equal 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 + 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_equal nil, Member.all.merge!(includes: :hairy_club).find(@member.id).reload.hairy_club end def test_has_one_through_polymorphic_with_source_type @@ -123,31 +123,31 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase end def test_eager_has_one_through_polymorphic_with_source_type - clubs = Club.all.merge!(:includes => :sponsored_member, :where => ["name = ?","Moustache and Eyebrow Fancier Club"]).to_a + clubs = Club.all.merge!(includes: :sponsored_member, where: ["name = ?","Moustache and Eyebrow Fancier Club"]).to_a # Only the eyebrow fanciers club has a sponsored_member - assert_not_nil assert_no_queries {clubs[0].sponsored_member} + assert_not_nil assert_no_queries { clubs[0].sponsored_member } end def test_has_one_through_nonpreload_eagerloading members = assert_queries(1) do - Member.all.merge!(:includes => :club, :where => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name').to_a #force fallback + Member.all.merge!(includes: :club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name").to_a #force fallback end assert_equal 1, members.size - assert_not_nil assert_no_queries {members[0].club} + assert_not_nil assert_no_queries { members[0].club } end def test_has_one_through_nonpreload_eager_loading_through_polymorphic members = assert_queries(1) do - Member.all.merge!(:includes => :sponsor_club, :where => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name').to_a #force fallback + Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name").to_a #force fallback end assert_equal 1, members.size - assert_not_nil assert_no_queries {members[0].sponsor_club} + assert_not_nil assert_no_queries { members[0].sponsor_club } end def test_has_one_through_nonpreload_eager_loading_through_polymorphic_with_more_than_one_through_record - Sponsor.new(:sponsor_club => clubs(:crazy_club), :sponsorable => members(:groucho)).save! + Sponsor.new(sponsor_club: clubs(:crazy_club), sponsorable: members(:groucho)).save! members = assert_queries(1) do - Member.all.merge!(:includes => :sponsor_club, :where => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name DESC').to_a #force fallback + Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name DESC").to_a #force fallback end assert_equal 1, members.size assert_not_nil assert_no_queries { members[0].sponsor_club } @@ -159,8 +159,8 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase end def test_assigning_association_correctly_assigns_target - new_member = Member.create(:name => "Chris") - new_member.club = new_club = Club.create(:name => "LRUG") + new_member = Member.create(name: "Chris") + new_member.club = new_club = Club.create(name: "LRUG") assert_equal new_club, new_member.association(:club).target end @@ -176,37 +176,37 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_assigning_to_has_one_through_preserves_decorated_join_record @organization = organizations(:nsa) - assert_difference 'MemberDetail.count', 1 do - @member_detail = MemberDetail.new(:extra_data => 'Extra') + assert_difference "MemberDetail.count", 1 do + @member_detail = MemberDetail.new(extra_data: "Extra") @member.member_detail = @member_detail @member.organization = @organization end assert_equal @organization, @member.organization - assert @organization.members.include?(@member) - assert_equal 'Extra', @member.member_detail.extra_data + assert_includes @organization.members, @member + assert_equal "Extra", @member.member_detail.extra_data end def test_reassigning_has_one_through @organization = organizations(:nsa) @new_organization = organizations(:discordians) - assert_difference 'MemberDetail.count', 1 do - @member_detail = MemberDetail.new(:extra_data => 'Extra') + assert_difference "MemberDetail.count", 1 do + @member_detail = MemberDetail.new(extra_data: "Extra") @member.member_detail = @member_detail @member.organization = @organization end assert_equal @organization, @member.organization - assert_equal 'Extra', @member.member_detail.extra_data - assert @organization.members.include?(@member) - assert !@new_organization.members.include?(@member) + assert_equal "Extra", @member.member_detail.extra_data + assert_includes @organization.members, @member + assert_not_includes @new_organization.members, @member - assert_no_difference 'MemberDetail.count' do + assert_no_difference "MemberDetail.count" do @member.organization = @new_organization end assert_equal @new_organization, @member.organization - assert_equal 'Extra', @member.member_detail.extra_data - assert !@organization.members.include?(@member) - assert @new_organization.members.include?(@member) + assert_equal "Extra", @member.member_detail.extra_data + assert_not_includes @organization.members, @member + assert_includes @new_organization.members, @member end def test_preloading_has_one_through_on_belongs_to @@ -217,7 +217,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase @member.member_detail = @member_detail @member.organization = @organization @member_details = assert_queries(3) do - MemberDetail.all.merge!(:includes => :member_type).to_a + MemberDetail.all.merge!(includes: :member_type).to_a end @new_detail = @member_details[0] assert @new_detail.send(:association, :member_type).loaded? @@ -230,19 +230,19 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase assert_nothing_raised do Club.find(@club.id).save! - Club.all.merge!(:includes => :sponsored_member).find(@club.id).save! + Club.all.merge!(includes: :sponsored_member).find(@club.id).save! end @club.sponsor.destroy assert_nothing_raised do Club.find(@club.id).save! - Club.all.merge!(:includes => :sponsored_member).find(@club.id).save! + Club.all.merge!(includes: :sponsored_member).find(@club.id).save! end end def test_through_belongs_to_after_destroy - @member_detail = MemberDetail.new(:extra_data => 'Extra') + @member_detail = MemberDetail.new(extra_data: "Extra") @member.member_detail = @member_detail @member.save! @@ -261,7 +261,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase end def test_value_is_properly_quoted - minivan = Minivan.find('m1') + minivan = Minivan.find("m1") assert_nothing_raised do minivan.dashboard end @@ -270,7 +270,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_polymorphic_with_primary_key_option assert_equal categories(:general), authors(:david).essay_category - authors = Author.joins(:essay_category).where('categories.id' => categories(:general).id) + authors = Author.joins(:essay_category).where("categories.id" => categories(:general).id) assert_equal authors(:david), authors.first assert_equal owners(:blackbeard), authors(:david).essay_owner @@ -282,12 +282,12 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_with_primary_key_option assert_equal categories(:general), authors(:david).essay_category_2 - authors = Author.joins(:essay_category_2).where('categories.id' => categories(:general).id) + authors = Author.joins(:essay_category_2).where("categories.id" => categories(:general).id) assert_equal authors(:david), authors.first end def test_has_one_through_with_default_scope_on_join_model - assert_equal posts(:welcome).comments.order('id').first, authors(:david).comment_on_first_post + assert_equal posts(:welcome).comments.order("id").first, authors(:david).comment_on_first_post end def test_has_one_through_many_raises_exception diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb index b3fe759ad9..7414869c8f 100644 --- a/activerecord/test/cases/associations/inner_join_association_test.rb +++ b/activerecord/test/cases/associations/inner_join_association_test.rb @@ -1,13 +1,13 @@ require "cases/helper" -require 'models/post' -require 'models/comment' -require 'models/author' -require 'models/essay' -require 'models/category' -require 'models/categorization' -require 'models/person' -require 'models/tagging' -require 'models/tag' +require "models/post" +require "models/comment" +require "models/author" +require "models/essay" +require "models/category" +require "models/categorization" +require "models/person" +require "models/tagging" +require "models/tag" class InnerJoinAssociationTest < ActiveRecord::TestCase fixtures :authors, :essays, :posts, :comments, :categories, :categories_posts, :categorizations, @@ -20,7 +20,7 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase def test_construct_finder_sql_does_not_table_name_collide_on_duplicate_associations assert_nothing_raised do - sql = Person.joins(:agents => {:agents => :agents}).joins(:agents => {:agents => {:primary_contact => :agents}}).to_sql + sql = Person.joins(agents: { agents: :agents }).joins(agents: { agents: { primary_contact: :agents } }).to_sql assert_match(/agents_people_4/i, sql) end end @@ -47,7 +47,7 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase end def test_join_conditions_allow_nil_associations - authors = Author.includes(:essays).where(:essays => {:id => nil}) + authors = Author.includes(:essays).where(essays: { id: nil }) assert_equal 2, authors.count end @@ -58,41 +58,41 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase end def test_find_with_implicit_inner_joins_honors_readonly_with_select - authors = Author.joins(:posts).select('authors.*').to_a + authors = Author.joins(:posts).select("authors.*").to_a assert !authors.empty?, "expected authors to be non-empty" - assert authors.all? {|a| !a.readonly? }, "expected no authors to be readonly" + assert authors.all? { |a| !a.readonly? }, "expected no authors to be readonly" end def test_find_with_implicit_inner_joins_honors_readonly_false authors = Author.joins(:posts).readonly(false).to_a assert !authors.empty?, "expected authors to be non-empty" - assert authors.all? {|a| !a.readonly? }, "expected no authors to be readonly" + assert authors.all? { |a| !a.readonly? }, "expected no authors to be readonly" end def test_find_with_implicit_inner_joins_does_not_set_associations - authors = Author.joins(:posts).select('authors.*').to_a + authors = Author.joins(:posts).select("authors.*").to_a assert !authors.empty?, "expected authors to be non-empty" assert authors.all? { |a| !a.instance_variable_defined?(:@posts) }, "expected no authors to have the @posts association loaded" end def test_count_honors_implicit_inner_joins - real_count = Author.all.to_a.sum{|a| a.posts.count } + real_count = Author.all.to_a.sum { |a| a.posts.count } assert_equal real_count, Author.joins(:posts).count, "plain inner join count should match the number of referenced posts records" end def test_calculate_honors_implicit_inner_joins - real_count = Author.all.to_a.sum{|a| a.posts.count } - assert_equal real_count, Author.joins(:posts).calculate(:count, 'authors.id'), "plain inner join count should match the number of referenced posts records" + real_count = Author.all.to_a.sum { |a| a.posts.count } + assert_equal real_count, Author.joins(:posts).calculate(:count, "authors.id"), "plain inner join count should match the number of referenced posts records" end def test_calculate_honors_implicit_inner_joins_and_distinct_and_conditions - real_count = Author.all.to_a.select {|a| a.posts.any? {|p| p.title =~ /^Welcome/} }.length - authors_with_welcoming_post_titles = Author.all.merge!(joins: :posts, where: "posts.title like 'Welcome%'").distinct.calculate(:count, 'authors.id') + real_count = Author.all.to_a.select { |a| a.posts.any? { |p| p.title.start_with?("Welcome") } }.length + authors_with_welcoming_post_titles = Author.all.merge!(joins: :posts, where: "posts.title like 'Welcome%'").distinct.calculate(:count, "authors.id") assert_equal real_count, authors_with_welcoming_post_titles, "inner join and conditions should have only returned authors posting titles starting with 'Welcome'" end def test_find_with_sti_join - scope = Post.joins(:special_comments).where(:id => posts(:sti_comments).id) + scope = Post.joins(:special_comments).where(id: posts(:sti_comments).id) # The join should match SpecialComment and its subclasses only assert scope.where("comments.type" => "Comment").empty? @@ -102,12 +102,12 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase def test_find_with_conditions_on_reflection assert !posts(:welcome).comments.empty? - assert Post.joins(:nonexistent_comments).where(:id => posts(:welcome).id).empty? # [sic!] + assert Post.joins(:nonexistent_comments).where(id: posts(:welcome).id).empty? # [sic!] end def test_find_with_conditions_on_through_reflection assert !posts(:welcome).tags.empty? - assert Post.joins(:misc_tags).where(:id => posts(:welcome).id).empty? + assert Post.joins(:misc_tags).where(id: posts(:welcome).id).empty? end test "the default scope of the target is applied when joining associations" do @@ -120,8 +120,8 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase test "the default scope of the target is correctly aliased when joining associations" do author = Author.create! name: "Jon" - author.categories.create! name: 'Not Special' - author.special_categories.create! name: 'Special' + author.categories.create! name: "Not Special" + author.special_categories.create! name: "Special" categories = author.categories.includes(:special_categorizations).references(:special_categorizations).to_a assert_equal 2, categories.size @@ -129,8 +129,8 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase test "the correct records are loaded when including an aliased association" do author = Author.create! name: "Jon" - author.categories.create! name: 'Not Special' - author.special_categories.create! name: 'Special' + author.categories.create! name: "Not Special" + author.special_categories.create! name: "Special" categories = author.categories.eager_load(:special_categorizations).order(:name).to_a assert_equal 0, categories.first.special_categorizations.size diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb index 57d1c8feda..6fe6ee6783 100644 --- a/activerecord/test/cases/associations/inverse_associations_test.rb +++ b/activerecord/test/cases/associations/inverse_associations_test.rb @@ -1,21 +1,21 @@ require "cases/helper" -require 'models/man' -require 'models/face' -require 'models/interest' -require 'models/zine' -require 'models/club' -require 'models/sponsor' -require 'models/rating' -require 'models/comment' -require 'models/car' -require 'models/bulb' -require 'models/mixed_case_monkey' -require 'models/admin' -require 'models/admin/account' -require 'models/admin/user' -require 'models/developer' -require 'models/company' -require 'models/project' +require "models/man" +require "models/face" +require "models/interest" +require "models/zine" +require "models/club" +require "models/sponsor" +require "models/rating" +require "models/comment" +require "models/car" +require "models/bulb" +require "models/mixed_case_monkey" +require "models/admin" +require "models/admin/account" +require "models/admin/user" +require "models/developer" +require "models/company" +require "models/project" class AutomaticInverseFindingTests < ActiveRecord::TestCase fixtures :ratings, :comments, :cars @@ -130,16 +130,16 @@ end class InverseAssociationTests < ActiveRecord::TestCase def test_should_allow_for_inverse_of_options_in_associations - assert_nothing_raised(ArgumentError, 'ActiveRecord should allow the inverse_of options on has_many') do - Class.new(ActiveRecord::Base).has_many(:wheels, :inverse_of => :car) + assert_nothing_raised do + Class.new(ActiveRecord::Base).has_many(:wheels, inverse_of: :car) end - assert_nothing_raised(ArgumentError, 'ActiveRecord should allow the inverse_of options on has_one') do - Class.new(ActiveRecord::Base).has_one(:engine, :inverse_of => :car) + assert_nothing_raised do + Class.new(ActiveRecord::Base).has_one(:engine, inverse_of: :car) end - assert_nothing_raised(ArgumentError, 'ActiveRecord should allow the inverse_of options on belongs_to') do - Class.new(ActiveRecord::Base).belongs_to(:car, :inverse_of => :driver) + assert_nothing_raised do + Class.new(ActiveRecord::Base).belongs_to(:car, inverse_of: :driver) end end @@ -203,9 +203,9 @@ class InverseAssociationTests < ActiveRecord::TestCase end def test_this_inverse_stuff - firm = Firm.create!(name: 'Adequate Holdings') - Project.create!(name: 'Project 1', firm: firm) - Developer.create!(name: 'Gorbypuff', firm: firm) + firm = Firm.create!(name: "Adequate Holdings") + Project.create!(name: "Project 1", firm: firm) + Developer.create!(name: "Gorbypuff", firm: firm) new_project = Project.last assert Project.reflect_on_association(:lead_developer).inverse_of.present?, "Expected inverse of to be present" @@ -220,73 +220,72 @@ class InverseHasOneTests < ActiveRecord::TestCase m = men(:gordon) f = m.face assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' + m.name = "Bongo" assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance" - f.man.name = 'Mungo' + f.man.name = "Mungo" assert_equal m.name, f.man.name, "Name of man should be the same after changes to child-owned instance" end - def test_parent_instance_should_be_shared_with_eager_loaded_child_on_find - m = Man.all.merge!(:where => {:name => 'Gordon'}, :includes => :face).first + m = Man.all.merge!(where: { name: "Gordon" }, includes: :face).first f = m.face assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' + m.name = "Bongo" assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance" - f.man.name = 'Mungo' + f.man.name = "Mungo" assert_equal m.name, f.man.name, "Name of man should be the same after changes to child-owned instance" - m = Man.all.merge!(:where => {:name => 'Gordon'}, :includes => :face, :order => 'faces.id').first + m = Man.all.merge!(where: { name: "Gordon" }, includes: :face, order: "faces.id").first f = m.face assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' + m.name = "Bongo" assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance" - f.man.name = 'Mungo' + f.man.name = "Mungo" assert_equal m.name, f.man.name, "Name of man should be the same after changes to child-owned instance" end def test_parent_instance_should_be_shared_with_newly_built_child m = Man.first - f = m.build_face(:description => 'haunted') + f = m.build_face(description: "haunted") assert_not_nil f.man assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' + m.name = "Bongo" assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance" - f.man.name = 'Mungo' + f.man.name = "Mungo" assert_equal m.name, f.man.name, "Name of man should be the same after changes to just-built-child-owned instance" end def test_parent_instance_should_be_shared_with_newly_created_child m = Man.first - f = m.create_face(:description => 'haunted') + f = m.create_face(description: "haunted") assert_not_nil f.man assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' + m.name = "Bongo" assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance" - f.man.name = 'Mungo' + f.man.name = "Mungo" assert_equal m.name, f.man.name, "Name of man should be the same after changes to newly-created-child-owned instance" end def test_parent_instance_should_be_shared_with_newly_created_child_via_bang_method m = Man.first - f = m.create_face!(:description => 'haunted') + f = m.create_face!(description: "haunted") assert_not_nil f.man assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' + m.name = "Bongo" assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance" - f.man.name = 'Mungo' + f.man.name = "Mungo" assert_equal m.name, f.man.name, "Name of man should be the same after changes to newly-created-child-owned instance" end def test_parent_instance_should_be_shared_with_replaced_via_accessor_child m = Man.first - f = Face.new(:description => 'haunted') + f = Face.new(description: "haunted") m.face = f assert_not_nil f.man assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' + m.name = "Bongo" assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance" - f.man.name = 'Mungo' + f.man.name = "Mungo" assert_equal m.name, f.man.name, "Name of man should be the same after changes to replaced-child-owned instance" end @@ -303,67 +302,67 @@ class InverseHasManyTests < ActiveRecord::TestCase is = m.interests is.each do |i| assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' + m.name = "Bongo" assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance" - i.man.name = 'Mungo' + i.man.name = "Mungo" assert_equal m.name, i.man.name, "Name of man should be the same after changes to child-owned instance" end end def test_parent_instance_should_be_shared_with_eager_loaded_children - m = Man.all.merge!(:where => {:name => 'Gordon'}, :includes => :interests).first + m = Man.all.merge!(where: { name: "Gordon" }, includes: :interests).first is = m.interests is.each do |i| assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' + m.name = "Bongo" assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance" - i.man.name = 'Mungo' + i.man.name = "Mungo" assert_equal m.name, i.man.name, "Name of man should be the same after changes to child-owned instance" end - m = Man.all.merge!(:where => {:name => 'Gordon'}, :includes => :interests, :order => 'interests.id').first + m = Man.all.merge!(where: { name: "Gordon" }, includes: :interests, order: "interests.id").first is = m.interests is.each do |i| assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' + m.name = "Bongo" assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance" - i.man.name = 'Mungo' + i.man.name = "Mungo" assert_equal m.name, i.man.name, "Name of man should be the same after changes to child-owned instance" end end def test_parent_instance_should_be_shared_with_newly_block_style_built_child m = Man.first - i = m.interests.build {|ii| ii.topic = 'Industrial Revolution Re-enactment'} + i = m.interests.build { |ii| ii.topic = "Industrial Revolution Re-enactment" } assert_not_nil i.topic, "Child attributes supplied to build via blocks should be populated" assert_not_nil i.man assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' + m.name = "Bongo" assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance" - i.man.name = 'Mungo' + i.man.name = "Mungo" assert_equal m.name, i.man.name, "Name of man should be the same after changes to just-built-child-owned instance" end def test_parent_instance_should_be_shared_with_newly_created_via_bang_method_child m = Man.first - i = m.interests.create!(:topic => 'Industrial Revolution Re-enactment') + i = m.interests.create!(topic: "Industrial Revolution Re-enactment") assert_not_nil i.man assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' + m.name = "Bongo" assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance" - i.man.name = 'Mungo' + i.man.name = "Mungo" assert_equal m.name, i.man.name, "Name of man should be the same after changes to newly-created-child-owned instance" end def test_parent_instance_should_be_shared_with_newly_block_style_created_child m = Man.first - i = m.interests.create {|ii| ii.topic = 'Industrial Revolution Re-enactment'} + i = m.interests.create { |ii| ii.topic = "Industrial Revolution Re-enactment" } assert_not_nil i.topic, "Child attributes supplied to create via blocks should be populated" assert_not_nil i.man assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' + m.name = "Bongo" assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance" - i.man.name = 'Mungo' + i.man.name = "Mungo" assert_equal m.name, i.man.name, "Name of man should be the same after changes to newly-created-child-owned instance" end @@ -385,25 +384,25 @@ class InverseHasManyTests < ActiveRecord::TestCase def test_parent_instance_should_be_shared_with_poked_in_child m = men(:gordon) - i = Interest.create(:topic => 'Industrial Revolution Re-enactment') + i = Interest.create(topic: "Industrial Revolution Re-enactment") m.interests << i assert_not_nil i.man assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' + m.name = "Bongo" assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance" - i.man.name = 'Mungo' + i.man.name = "Mungo" assert_equal m.name, i.man.name, "Name of man should be the same after changes to newly-created-child-owned instance" end def test_parent_instance_should_be_shared_with_replaced_via_accessor_children m = Man.first - i = Interest.new(:topic => 'Industrial Revolution Re-enactment') + i = Interest.new(topic: "Industrial Revolution Re-enactment") m.interests = [i] assert_not_nil i.man assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' + m.name = "Bongo" assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance" - i.man.name = 'Mungo' + i.man.name = "Mungo" assert_equal m.name, i.man.name, "Name of man should be the same after changes to replaced-child-owned instance" end @@ -485,7 +484,7 @@ class InverseHasManyTests < ActiveRecord::TestCase def test_child_instance_should_point_to_parent_without_saving man = Man.new - i = Interest.create(:topic => 'Industrial Revolution Re-enactment') + i = Interest.create(topic: "Industrial Revolution Re-enactment") man.interests << i assert_not_nil i.man @@ -495,6 +494,33 @@ class InverseHasManyTests < ActiveRecord::TestCase assert !man.persisted? end + + def test_inverse_instance_should_be_set_before_find_callbacks_are_run + reset_callbacks(Interest, :find) do + Interest.after_find { raise unless association(:man).loaded? && man.present? } + + assert Man.first.interests.reload.any? + assert Man.includes(:interests).first.interests.any? + assert Man.joins(:interests).includes(:interests).first.interests.any? + end + end + + def test_inverse_instance_should_be_set_before_initialize_callbacks_are_run + reset_callbacks(Interest, :initialize) do + Interest.after_initialize { raise unless association(:man).loaded? && man.present? } + + assert Man.first.interests.reload.any? + assert Man.includes(:interests).first.interests.any? + assert Man.joins(:interests).includes(:interests).first.interests.any? + end + end + + def reset_callbacks(target, type) + old_callbacks = target.send(:get_callbacks, type).deep_dup + yield + ensure + target.send(:set_callbacks, type, old_callbacks) if old_callbacks + end end class InverseBelongsToTests < ActiveRecord::TestCase @@ -504,49 +530,49 @@ class InverseBelongsToTests < ActiveRecord::TestCase f = faces(:trusting) m = f.man assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance" - f.description = 'gormless' + f.description = "gormless" assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance" - m.face.description = 'pleasing' + m.face.description = "pleasing" assert_equal f.description, m.face.description, "Description of face should be the same after changes to parent-owned instance" end def test_eager_loaded_child_instance_should_be_shared_with_parent_on_find - f = Face.all.merge!(:includes => :man, :where => {:description => 'trusting'}).first + f = Face.all.merge!(includes: :man, where: { description: "trusting" }).first m = f.man assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance" - f.description = 'gormless' + f.description = "gormless" assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance" - m.face.description = 'pleasing' + m.face.description = "pleasing" assert_equal f.description, m.face.description, "Description of face should be the same after changes to parent-owned instance" - f = Face.all.merge!(:includes => :man, :order => 'men.id', :where => {:description => 'trusting'}).first + f = Face.all.merge!(includes: :man, order: "men.id", where: { description: "trusting" }).first m = f.man assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance" - f.description = 'gormless' + f.description = "gormless" assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance" - m.face.description = 'pleasing' + m.face.description = "pleasing" assert_equal f.description, m.face.description, "Description of face should be the same after changes to parent-owned instance" end def test_child_instance_should_be_shared_with_newly_built_parent f = faces(:trusting) - m = f.build_man(:name => 'Charles') + m = f.build_man(name: "Charles") assert_not_nil m.face assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance" - f.description = 'gormless' + f.description = "gormless" assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance" - m.face.description = 'pleasing' + m.face.description = "pleasing" assert_equal f.description, m.face.description, "Description of face should be the same after changes to just-built-parent-owned instance" end def test_child_instance_should_be_shared_with_newly_created_parent f = faces(:trusting) - m = f.create_man(:name => 'Charles') + m = f.create_man(name: "Charles") assert_not_nil m.face assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance" - f.description = 'gormless' + f.description = "gormless" assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance" - m.face.description = 'pleasing' + m.face.description = "pleasing" assert_equal f.description, m.face.description, "Description of face should be the same after changes to newly-created-parent-owned instance" end @@ -554,24 +580,24 @@ class InverseBelongsToTests < ActiveRecord::TestCase i = interests(:trainspotting) m = i.man assert_not_nil m.interests - iz = m.interests.detect { |_iz| _iz.id == i.id} + iz = m.interests.detect { |_iz| _iz.id == i.id } assert_not_nil iz assert_equal i.topic, iz.topic, "Interest topics should be the same before changes to child" - i.topic = 'Eating cheese with a spoon' + i.topic = "Eating cheese with a spoon" assert_not_equal i.topic, iz.topic, "Interest topics should not be the same after changes to child" - iz.topic = 'Cow tipping' + iz.topic = "Cow tipping" assert_not_equal i.topic, iz.topic, "Interest topics should not be the same after changes to parent-owned instance" end def test_child_instance_should_be_shared_with_replaced_via_accessor_parent f = Face.first - m = Man.new(:name => 'Charles') + m = Man.new(name: "Charles") f.man = m assert_not_nil m.face assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance" - f.description = 'gormless' + f.description = "gormless" assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance" - m.face.description = 'pleasing' + m.face.description = "pleasing" assert_equal f.description, m.face.description, "Description of face should be the same after changes to replaced-parent-owned instance" end @@ -584,30 +610,30 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase fixtures :men, :faces, :interests def test_child_instance_should_be_shared_with_parent_on_find - f = Face.all.merge!(:where => {:description => 'confused'}).first + f = Face.all.merge!(where: { description: "confused" }).first m = f.polymorphic_man assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance" - f.description = 'gormless' + f.description = "gormless" assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to child instance" - m.polymorphic_face.description = 'pleasing' + m.polymorphic_face.description = "pleasing" assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to parent-owned instance" end def test_eager_loaded_child_instance_should_be_shared_with_parent_on_find - f = Face.all.merge!(:where => {:description => 'confused'}, :includes => :man).first + f = Face.all.merge!(where: { description: "confused" }, includes: :man).first m = f.polymorphic_man assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance" - f.description = 'gormless' + f.description = "gormless" assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to child instance" - m.polymorphic_face.description = 'pleasing' + m.polymorphic_face.description = "pleasing" assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to parent-owned instance" - f = Face.all.merge!(:where => {:description => 'confused'}, :includes => :man, :order => 'men.id').first + f = Face.all.merge!(where: { description: "confused" }, includes: :man, order: "men.id").first m = f.polymorphic_man assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance" - f.description = 'gormless' + f.description = "gormless" assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to child instance" - m.polymorphic_face.description = 'pleasing' + m.polymorphic_face.description = "pleasing" assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to parent-owned instance" end @@ -619,9 +645,9 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase face.polymorphic_man = new_man assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same before changes to parent instance" - face.description = 'Bongo' + face.description = "Bongo" assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to parent instance" - new_man.polymorphic_face.description = 'Mungo' + new_man.polymorphic_face.description = "Mungo" assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to replaced-parent-owned instance" end @@ -633,9 +659,9 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase face.polymorphic_man = new_man assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same before changes to parent instance" - face.description = 'Bongo' + face.description = "Bongo" assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to parent instance" - new_man.polymorphic_face.description = 'Mungo' + new_man.polymorphic_face.description = "Mungo" assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to replaced-parent-owned instance" end @@ -655,18 +681,18 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase i = interests(:llama_wrangling) m = i.polymorphic_man assert_not_nil m.polymorphic_interests - iz = m.polymorphic_interests.detect { |_iz| _iz.id == i.id} + iz = m.polymorphic_interests.detect { |_iz| _iz.id == i.id } assert_not_nil iz assert_equal i.topic, iz.topic, "Interest topics should be the same before changes to child" - i.topic = 'Eating cheese with a spoon' + i.topic = "Eating cheese with a spoon" assert_not_equal i.topic, iz.topic, "Interest topics should not be the same after changes to child" - iz.topic = 'Cow tipping' + iz.topic = "Cow tipping" assert_not_equal i.topic, iz.topic, "Interest topics should not be the same after changes to parent-owned instance" end def test_trying_to_access_inverses_that_dont_exist_shouldnt_raise_an_error # Ideally this would, if only for symmetry's sake with other association types - assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.horrible_polymorphic_man } + assert_nothing_raised { Face.first.horrible_polymorphic_man } end def test_trying_to_set_polymorphic_inverses_that_dont_exist_at_all_should_raise_an_error @@ -676,7 +702,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase def test_trying_to_set_polymorphic_inverses_that_dont_exist_on_the_instance_being_set_should_raise_an_error # passes because Man does have the correct inverse_of - assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.polymorphic_man = Man.first } + assert_nothing_raised { Face.first.polymorphic_man = Man.first } # fails because Interest does have the correct inverse_of assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.polymorphic_man = Interest.first } end @@ -688,7 +714,7 @@ class InverseMultipleHasManyInversesForSameModel < ActiveRecord::TestCase fixtures :men, :interests, :zines def test_that_we_can_load_associations_that_have_the_same_reciprocal_name_from_different_models - assert_nothing_raised(ActiveRecord::AssociationTypeMismatch) do + assert_nothing_raised do i = Interest.first i.zine i.man @@ -696,10 +722,10 @@ class InverseMultipleHasManyInversesForSameModel < ActiveRecord::TestCase end def test_that_we_can_create_associations_that_have_the_same_reciprocal_name_from_different_models - assert_nothing_raised(ActiveRecord::AssociationTypeMismatch) do + assert_nothing_raised do i = Interest.first - i.build_zine(:title => 'Get Some in Winter! 2008') - i.build_man(:name => 'Gordon') + i.build_zine(title: "Get Some in Winter! 2008") + i.build_man(name: "Gordon") i.save! end end diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index f6dddaf5b4..15a7ae941a 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -1,20 +1,20 @@ require "cases/helper" -require 'models/tag' -require 'models/tagging' -require 'models/post' -require 'models/rating' -require 'models/item' -require 'models/comment' -require 'models/author' -require 'models/category' -require 'models/categorization' -require 'models/vertex' -require 'models/edge' -require 'models/book' -require 'models/citation' -require 'models/aircraft' -require 'models/engine' -require 'models/car' +require "models/tag" +require "models/tagging" +require "models/post" +require "models/rating" +require "models/item" +require "models/comment" +require "models/author" +require "models/category" +require "models/categorization" +require "models/vertex" +require "models/edge" +require "models/book" +require "models/citation" +require "models/aircraft" +require "models/engine" +require "models/car" class AssociationsJoinModelTest < ActiveRecord::TestCase self.use_transactional_tests = false unless supports_savepoints? @@ -24,15 +24,15 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase :edges def test_has_many - assert authors(:david).categories.include?(categories(:general)) + assert_includes authors(:david).categories, categories(:general) end def test_has_many_inherited - assert authors(:mary).categories.include?(categories(:sti_test)) + assert_includes authors(:mary).categories, categories(:sti_test) end def test_inherited_has_many - assert categories(:sti_test).authors.include?(authors(:mary)) + assert_includes categories(:sti_test).authors, authors(:mary) end def test_has_many_distinct_through_join_model @@ -88,7 +88,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_polymorphic_has_many_going_through_join_model_with_custom_select_and_joins assert_equal tags(:general), tag = posts(:welcome).tags.add_joins_and_select.first - assert_nothing_raised(NoMethodError) { tag.author_id } + assert_nothing_raised { tag.author_id } end def test_polymorphic_has_many_going_through_join_model_with_custom_foreign_key @@ -97,10 +97,10 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_polymorphic_has_many_create_model_with_inheritance_and_custom_base_class - post = SubStiPost.create :title => 'SubStiPost', :body => 'SubStiPost body' + post = SubStiPost.create title: "SubStiPost", body: "SubStiPost body" assert_instance_of SubStiPost, post - tagging = tags(:misc).taggings.create(:taggable => post) + tagging = tags(:misc).taggings.create(taggable: post) assert_equal "SubStiPost", tagging.taggable_type end @@ -116,12 +116,12 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase post = posts(:thinking) assert_instance_of SpecialPost, post - tagging = tags(:misc).taggings.create(:taggable => post) + tagging = tags(:misc).taggings.create(taggable: post) assert_equal "Post", tagging.taggable_type end def test_polymorphic_has_one_create_model_with_inheritance - tagging = tags(:misc).create_tagging(:taggable => posts(:thinking)) + tagging = tags(:misc).create_tagging(taggable: posts(:thinking)) assert_equal "Post", tagging.taggable_type end @@ -142,7 +142,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_set_polymorphic_has_one_on_new_record tagging = tags(:misc).taggings.create - post = Post.new :title => "foo", :body => "bar" + post = Post.new title: "foo", body: "bar" post.tagging = tagging post.save! @@ -153,28 +153,28 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_create_polymorphic_has_many_with_scope old_count = posts(:welcome).taggings.count - tagging = posts(:welcome).taggings.create(:tag => tags(:misc)) + tagging = posts(:welcome).taggings.create(tag: tags(:misc)) assert_equal "Post", tagging.taggable_type assert_equal old_count+1, posts(:welcome).taggings.count end def test_create_bang_polymorphic_with_has_many_scope old_count = posts(:welcome).taggings.count - tagging = posts(:welcome).taggings.create!(:tag => tags(:misc)) + tagging = posts(:welcome).taggings.create!(tag: tags(:misc)) assert_equal "Post", tagging.taggable_type assert_equal old_count+1, posts(:welcome).taggings.count end def test_create_polymorphic_has_one_with_scope old_count = Tagging.count - tagging = posts(:welcome).create_tagging(:tag => tags(:misc)) + tagging = posts(:welcome).create_tagging(tag: tags(:misc)) assert_equal "Post", tagging.taggable_type assert_equal old_count+1, Tagging.count end def test_delete_polymorphic_has_many_with_delete_all assert_equal 1, posts(:welcome).taggings.count - posts(:welcome).taggings.first.update_columns taggable_type: 'PostWithHasManyDeleteAll' + posts(:welcome).taggings.first.update_columns taggable_type: "PostWithHasManyDeleteAll" post = find_post_with_dependency(1, :has_many, :taggings, :delete_all) old_count = Tagging.count @@ -185,7 +185,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_delete_polymorphic_has_many_with_destroy assert_equal 1, posts(:welcome).taggings.count - posts(:welcome).taggings.first.update_columns taggable_type: 'PostWithHasManyDestroy' + posts(:welcome).taggings.first.update_columns taggable_type: "PostWithHasManyDestroy" post = find_post_with_dependency(1, :has_many, :taggings, :destroy) old_count = Tagging.count @@ -196,7 +196,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_delete_polymorphic_has_many_with_nullify assert_equal 1, posts(:welcome).taggings.count - posts(:welcome).taggings.first.update_columns taggable_type: 'PostWithHasManyNullify' + posts(:welcome).taggings.first.update_columns taggable_type: "PostWithHasManyNullify" post = find_post_with_dependency(1, :has_many, :taggings, :nullify) old_count = Tagging.count @@ -207,7 +207,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_delete_polymorphic_has_one_with_destroy assert posts(:welcome).tagging - posts(:welcome).tagging.update_columns taggable_type: 'PostWithHasOneDestroy' + posts(:welcome).tagging.update_columns taggable_type: "PostWithHasOneDestroy" post = find_post_with_dependency(1, :has_one, :tagging, :destroy) old_count = Tagging.count @@ -219,7 +219,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_delete_polymorphic_has_one_with_nullify assert posts(:welcome).tagging - posts(:welcome).tagging.update_columns taggable_type: 'PostWithHasOneNullify' + posts(:welcome).tagging.update_columns taggable_type: "PostWithHasOneNullify" post = find_post_with_dependency(1, :has_one, :tagging, :nullify) old_count = Tagging.count @@ -235,15 +235,15 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_create_through_has_many_with_piggyback category = categories(:sti_test) - ernie = category.authors_with_select.create(:name => 'Ernie') + ernie = category.authors_with_select.create(name: "Ernie") assert_nothing_raised do - assert_equal ernie, category.authors_with_select.detect {|a| a.name == 'Ernie'} + assert_equal ernie, category.authors_with_select.detect { |a| a.name == "Ernie" } end end def test_include_has_many_through - posts = Post.all.merge!(:order => 'posts.id').to_a - posts_with_authors = Post.all.merge!(:includes => :authors, :order => 'posts.id').to_a + posts = Post.all.merge!(order: "posts.id").to_a + posts_with_authors = Post.all.merge!(includes: :authors, order: "posts.id").to_a assert_equal posts.length, posts_with_authors.length posts.length.times do |i| assert_equal posts[i].authors.length, assert_no_queries { posts_with_authors[i].authors.length } @@ -267,8 +267,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_include_polymorphic_has_many_through - posts = Post.all.merge!(:order => 'posts.id').to_a - posts_with_tags = Post.all.merge!(:includes => :tags, :order => 'posts.id').to_a + posts = Post.all.merge!(order: "posts.id").to_a + posts_with_tags = Post.all.merge!(includes: :tags, order: "posts.id").to_a assert_equal posts.length, posts_with_tags.length posts.length.times do |i| assert_equal posts[i].tags.length, assert_no_queries { posts_with_tags[i].tags.length } @@ -276,8 +276,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_include_polymorphic_has_many - posts = Post.all.merge!(:order => 'posts.id').to_a - posts_with_taggings = Post.all.merge!(:includes => :taggings, :order => 'posts.id').to_a + posts = Post.all.merge!(order: "posts.id").to_a + posts_with_taggings = Post.all.merge!(includes: :taggings, order: "posts.id").to_a assert_equal posts.length, posts_with_taggings.length posts.length.times do |i| assert_equal posts[i].taggings.length, assert_no_queries { posts_with_taggings[i].taggings.length } @@ -302,7 +302,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_array_methods_called_by_method_missing - assert authors(:david).categories.any? { |category| category.name == 'General' } + assert authors(:david).categories.any? { |category| category.name == "General" } assert_nothing_raised { authors(:david).categories.sort } end @@ -324,12 +324,12 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_through_with_custom_primary_key_on_has_many_source - assert_equal [authors(:david), authors(:bob)], posts(:thinking).authors_using_custom_pk.order('authors.id') + assert_equal [authors(:david), authors(:bob)], posts(:thinking).authors_using_custom_pk.order("authors.id") end def test_belongs_to_polymorphic_with_counter_cache assert_equal 1, posts(:welcome)[:tags_count] - tagging = posts(:welcome).taggings.create(:tag => tags(:general)) + tagging = posts(:welcome).taggings.create(tag: tags(:general)) assert_equal 2, posts(:welcome, :reload)[:tags_count] tagging.destroy assert_equal 1, posts(:welcome, :reload)[:tags_count] @@ -354,7 +354,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end assert_raise ActiveRecord::EagerLoadPolymorphicError do - tags(:general).taggings.includes(:taggable).where('bogus_table.column = 1').references(:bogus_table).to_a + tags(:general).taggings.includes(:taggable).where("bogus_table.column = 1").references(:bogus_table).to_a end end @@ -363,8 +363,15 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase assert_equal posts(:welcome, :thinking).sort_by(&:id), tags(:general).tagged_posts.sort_by(&:id) end + def test_has_many_polymorphic_associations_merges_through_scope + Tag.has_many :null_taggings, -> { none }, class_name: :Tagging + Tag.has_many :null_tagged_posts, through: :null_taggings, source: "taggable", source_type: "Post" + assert_equal [], tags(:general).null_tagged_posts + refute_equal [], tags(:general).tagged_posts + end + def test_eager_has_many_polymorphic_with_source_type - tag_with_include = Tag.all.merge!(:includes => :tagged_posts).find(tags(:general).id) + tag_with_include = Tag.all.merge!(includes: :tagged_posts).find(tags(:general).id) desired = posts(:welcome, :thinking) assert_no_queries do # added sort by ID as otherwise test using JRuby was failing as array elements were in different order @@ -374,19 +381,19 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_through_has_many_find_all - assert_equal comments(:greetings), authors(:david).comments.order('comments.id').to_a.first + assert_equal comments(:greetings), authors(:david).comments.order("comments.id").to_a.first end def test_has_many_through_has_many_find_all_with_custom_class - assert_equal comments(:greetings), authors(:david).funky_comments.order('comments.id').to_a.first + assert_equal comments(:greetings), authors(:david).funky_comments.order("comments.id").to_a.first end def test_has_many_through_has_many_find_first - assert_equal comments(:greetings), authors(:david).comments.order('comments.id').first + assert_equal comments(:greetings), authors(:david).comments.order("comments.id").first end def test_has_many_through_has_many_find_conditions - options = { :where => "comments.#{QUOTED_TYPE}='SpecialComment'", :order => 'comments.id' } + options = { where: "comments.#{QUOTED_TYPE}='SpecialComment'", order: "comments.id" } assert_equal comments(:does_it_hurt), authors(:david).comments.merge(options).first end @@ -411,7 +418,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_eager_load_has_many_through_has_many - author = Author.all.merge!(:where => ['name = ?', 'David'], :includes => :comments, :order => 'comments.id').first + author = Author.all.merge!(where: ["name = ?", "David"], includes: :comments, order: "comments.id").first SpecialComment.new; VerySpecialComment.new assert_no_queries do assert_equal [1,2,3,5,6,7,8,9,10,12], author.comments.collect(&:id) @@ -419,7 +426,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_eager_load_has_many_through_has_many_with_conditions - post = Post.all.merge!(:includes => :invalid_tags).first + post = Post.all.merge!(includes: :invalid_tags).first assert_no_queries do post.invalid_tags end @@ -427,8 +434,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_eager_belongs_to_and_has_one_not_singularized assert_nothing_raised do - Author.all.merge!(:includes => :author_address).first - AuthorAddress.all.merge!(:includes => :author).first + Author.all.merge!(includes: :author_address).first + AuthorAddress.all.merge!(includes: :author).first end end @@ -438,8 +445,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_add_to_self_referential_has_many_through - new_author = Author.create(:name => "Bob") - authors(:david).author_favorites.create :favorite_author => new_author + new_author = Author.create(name: "Bob") + authors(:david).author_favorites.create favorite_author: new_author assert_equal new_author, authors(:david).reload.favorite_authors.first end @@ -455,28 +462,27 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_associating_unsaved_records_with_has_many_through saved_post = posts(:thinking) - new_tag = Tag.new(:name => "new") + new_tag = Tag.new(name: "new") saved_post.tags << new_tag assert new_tag.persisted? #consistent with habtm! assert saved_post.persisted? - assert saved_post.tags.include?(new_tag) + assert_includes saved_post.tags, new_tag assert new_tag.persisted? - assert saved_post.reload.tags.reload.include?(new_tag) + assert_includes saved_post.reload.tags.reload, new_tag - - new_post = Post.new(:title => "Association replacement works!", :body => "You best believe it.") + new_post = Post.new(title: "Association replacement works!", body: "You best believe it.") saved_tag = tags(:general) new_post.tags << saved_tag assert !new_post.persisted? assert saved_tag.persisted? - assert new_post.tags.include?(saved_tag) + assert_includes new_post.tags, saved_tag new_post.save! assert new_post.persisted? - assert new_post.reload.tags.reload.include?(saved_tag) + assert_includes new_post.reload.tags.reload, saved_tag assert !posts(:thinking).tags.build.persisted? assert !posts(:thinking).tags.new.persisted? @@ -484,7 +490,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_create_associate_when_adding_to_has_many_through count = posts(:thinking).tags.count - push = Tag.create!(:name => 'pushme') + push = Tag.create!(name: "pushme") post_thinking = posts(:thinking) assert_nothing_raised { post_thinking.tags << push } assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag }, @@ -494,7 +500,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase assert_equal(count + 1, post_thinking.reload.tags.size) assert_equal(count + 1, post_thinking.tags.reload.size) - assert_kind_of Tag, post_thinking.tags.create!(:name => 'foo') + assert_kind_of Tag, post_thinking.tags.create!(name: "foo") assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag }, message = "Expected a Tag in tags collection, got #{wrong.class}.") assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging }, @@ -502,7 +508,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase assert_equal(count + 2, post_thinking.reload.tags.size) assert_equal(count + 2, post_thinking.tags.reload.size) - assert_nothing_raised { post_thinking.tags.concat(Tag.create!(:name => 'abc'), Tag.create!(:name => 'def')) } + assert_nothing_raised { post_thinking.tags.concat(Tag.create!(name: "abc"), Tag.create!(name: "def")) } assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag }, message = "Expected a Tag in tags collection, got #{wrong.class}.") assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging }, @@ -543,7 +549,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_delete_associate_when_deleting_from_has_many_through_with_nonstandard_id count = books(:awdr).references.count references_before = books(:awdr).references - book = Book.create!(:name => 'Getting Real') + book = Book.create!(name: "Getting Real") book_awdr = books(:awdr) book_awdr.references << book assert_equal(count + 1, book_awdr.references.reload.size) @@ -557,7 +563,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_delete_associate_when_deleting_from_has_many_through count = posts(:thinking).tags.count tags_before = posts(:thinking).tags.sort - tag = Tag.create!(:name => 'doomed') + tag = Tag.create!(name: "doomed") post_thinking = posts(:thinking) post_thinking.tags << tag assert_equal(count + 1, post_thinking.taggings.reload.size) @@ -574,9 +580,9 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_delete_associate_when_deleting_from_has_many_through_with_multiple_tags count = posts(:thinking).tags.count tags_before = posts(:thinking).tags.sort - doomed = Tag.create!(:name => 'doomed') - doomed2 = Tag.create!(:name => 'doomed2') - quaked = Tag.create!(:name => 'quaked') + doomed = Tag.create!(name: "doomed") + doomed2 = Tag.create!(name: "doomed2") + quaked = Tag.create!(name: "quaked") post_thinking = posts(:thinking) post_thinking.tags << doomed << doomed2 assert_equal(count + 2, post_thinking.reload.tags.reload.size) @@ -591,10 +597,10 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase assert_raise(ActiveRecord::AssociationTypeMismatch) { posts(:thinking).tags.delete(Object.new) } end - def test_deleting_by_fixnum_id_from_has_many_through + def test_deleting_by_integer_id_from_has_many_through post = posts(:thinking) - assert_difference 'post.tags.count', -1 do + assert_difference "post.tags.count", -1 do assert_equal 1, post.tags.delete(1).size end @@ -604,8 +610,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_deleting_by_string_id_from_has_many_through post = posts(:thinking) - assert_difference 'post.tags.count', -1 do - assert_equal 1, post.tags.delete('1').size + assert_difference "post.tags.count", -1 do + assert_equal 1, post.tags.delete("1").size end assert_equal 0, post.tags.size @@ -635,26 +641,26 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_polymorphic_has_many expected = taggings(:welcome_general) - p = Post.all.merge!(:includes => :taggings).find(posts(:welcome).id) - assert_no_queries {assert p.taggings.include?(expected)} - assert posts(:welcome).taggings.include?(taggings(:welcome_general)) + p = Post.all.merge!(includes: :taggings).find(posts(:welcome).id) + assert_no_queries { assert_includes p.taggings, expected } + assert_includes posts(:welcome).taggings, taggings(:welcome_general) end def test_polymorphic_has_one expected = posts(:welcome) - tagging = Tagging.all.merge!(:includes => :taggable).find(taggings(:welcome_general).id) - assert_no_queries { assert_equal expected, tagging.taggable} + tagging = Tagging.all.merge!(includes: :taggable).find(taggings(:welcome_general).id) + assert_no_queries { assert_equal expected, tagging.taggable } end def test_polymorphic_belongs_to - p = Post.all.merge!(:includes => {:taggings => :taggable}).find(posts(:welcome).id) - assert_no_queries {assert_equal posts(:welcome), p.taggings.first.taggable} + p = Post.all.merge!(includes: { taggings: :taggable }).find(posts(:welcome).id) + assert_no_queries { assert_equal posts(:welcome), p.taggings.first.taggable } end def test_preload_polymorphic_has_many_through - posts = Post.all.merge!(:order => 'posts.id').to_a - posts_with_tags = Post.all.merge!(:includes => :tags, :order => 'posts.id').to_a + posts = Post.all.merge!(order: "posts.id").to_a + posts_with_tags = Post.all.merge!(includes: :tags, order: "posts.id").to_a assert_equal posts.length, posts_with_tags.length posts.length.times do |i| assert_equal posts[i].tags.length, assert_no_queries { posts_with_tags[i].tags.length } @@ -662,26 +668,26 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_preload_polymorph_many_types - taggings = Tagging.all.merge!(:includes => :taggable, :where => ['taggable_type != ?', 'FakeModel']).to_a + taggings = Tagging.all.merge!(includes: :taggable, where: ["taggable_type != ?", "FakeModel"]).to_a assert_no_queries do taggings.first.taggable.id taggings[1].taggable.id end taggables = taggings.map(&:taggable) - assert taggables.include?(items(:dvd)) - assert taggables.include?(posts(:welcome)) + assert_includes taggables, items(:dvd) + assert_includes taggables, posts(:welcome) end def test_preload_nil_polymorphic_belongs_to assert_nothing_raised do - Tagging.all.merge!(:includes => :taggable, :where => ['taggable_type IS NULL']).to_a + Tagging.all.merge!(includes: :taggable, where: ["taggable_type IS NULL"]).to_a end end def test_preload_polymorphic_has_many - posts = Post.all.merge!(:order => 'posts.id').to_a - posts_with_taggings = Post.all.merge!(:includes => :taggings, :order => 'posts.id').to_a + posts = Post.all.merge!(order: "posts.id").to_a + posts_with_taggings = Post.all.merge!(includes: :taggings, order: "posts.id").to_a assert_equal posts.length, posts_with_taggings.length posts.length.times do |i| assert_equal posts[i].taggings.length, assert_no_queries { posts_with_taggings[i].taggings.length } @@ -689,7 +695,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_belongs_to_shared_parent - comments = Comment.all.merge!(:includes => :post, :where => 'post_id = 1').to_a + comments = Comment.all.merge!(includes: :post, where: "post_id = 1").to_a assert_no_queries do assert_equal comments.first.post, comments[1].post end @@ -703,7 +709,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase assert_no_queries do assert david.categories.loaded? - assert david.categories.include?(category) + assert_includes david.categories, category end end @@ -714,40 +720,57 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase david.reload assert ! david.categories.loaded? assert_queries(1) do - assert david.categories.include?(category) + assert_includes david.categories, category end assert ! david.categories.loaded? end def test_has_many_through_include_returns_false_for_non_matching_record_to_verify_scoping david = authors(:david) - category = Category.create!(:name => 'Not Associated') + category = Category.create!(name: "Not Associated") assert ! david.categories.loaded? assert ! david.categories.include?(category) end def test_has_many_through_goes_through_all_sti_classes - sub_sti_post = SubStiPost.create!(:title => 'test', :body => 'test', :author_id => 1) - new_comment = sub_sti_post.comments.create(:body => 'test') + sub_sti_post = SubStiPost.create!(title: "test", body: "test", author_id: 1) + new_comment = sub_sti_post.comments.create(body: "test") assert_equal [9, 10, new_comment.id], authors(:david).sti_post_comments.map(&:id).sort end def test_has_many_with_pluralize_table_names_false - aircraft = Aircraft.create!(:name => "Airbus 380") - engine = Engine.create!(:car_id => aircraft.id) + aircraft = Aircraft.create!(name: "Airbus 380") + engine = Engine.create!(car_id: aircraft.id) assert_equal aircraft.engines, [engine] end + def test_proper_error_message_for_eager_load_and_includes_association_errors + includes_error = assert_raises(ActiveRecord::ConfigurationError) { + Post.includes(:nonexistent_relation).where(nonexistent_relation: { name: "Rochester" }).find(1) + } + assert_equal("Can't join 'Post' to association named 'nonexistent_relation'; perhaps you misspelled it?", includes_error.message) + + eager_load_error = assert_raises(ActiveRecord::ConfigurationError) { + Post.eager_load(:nonexistent_relation).where(nonexistent_relation: { name: "Rochester" }).find(1) + } + assert_equal("Can't join 'Post' to association named 'nonexistent_relation'; perhaps you misspelled it?", eager_load_error.message) + + includes_and_eager_load_error = assert_raises(ActiveRecord::ConfigurationError) { + Post.eager_load(:nonexistent_relation).includes(:nonexistent_relation).where(nonexistent_relation: { name: "Rochester" }).find(1) + } + assert_equal("Can't join 'Post' to association named 'nonexistent_relation'; perhaps you misspelled it?", includes_and_eager_load_error.message) + end + private # create dynamic Post models to allow different dependency options def find_post_with_dependency(post_id, association, association_name, dependency) class_name = "PostWith#{association.to_s.classify}#{dependency.to_s.classify}" Post.find(post_id).update_columns type: class_name klass = Object.const_set(class_name, Class.new(ActiveRecord::Base)) - klass.table_name = 'posts' - klass.send(association, association_name, :as => :taggable, :dependent => dependency) + klass.table_name = "posts" + klass.send(association, association_name, as: :taggable, dependent: dependency) klass.find(post_id) end end diff --git a/activerecord/test/cases/associations/left_outer_join_association_test.rb b/activerecord/test/cases/associations/left_outer_join_association_test.rb index 4af791b758..42dbbad1c8 100644 --- a/activerecord/test/cases/associations/left_outer_join_association_test.rb +++ b/activerecord/test/cases/associations/left_outer_join_association_test.rb @@ -1,10 +1,10 @@ require "cases/helper" -require 'models/post' -require 'models/comment' -require 'models/author' -require 'models/essay' -require 'models/categorization' -require 'models/person' +require "models/post" +require "models/comment" +require "models/author" +require "models/essay" +require "models/categorization" +require "models/person" class LeftOuterJoinAssociationTest < ActiveRecord::TestCase fixtures :authors, :essays, :posts, :comments, :categorizations, :people @@ -17,45 +17,54 @@ class LeftOuterJoinAssociationTest < ActiveRecord::TestCase def test_construct_finder_sql_does_not_table_name_collide_on_duplicate_associations assert_nothing_raised do queries = capture_sql do - Person.left_outer_joins(:agents => {:agents => :agents}) - .left_outer_joins(:agents => {:agents => {:primary_contact => :agents}}).to_a + Person.left_outer_joins(agents: { agents: :agents }) + .left_outer_joins(agents: { agents: { primary_contact: :agents } }).to_a end - assert queries.any? { |sql| /agents_people_4/i =~ sql } + assert queries.any? { |sql| /agents_people_4/i.match?(sql) } end end - def test_construct_finder_sql_executes_a_left_outer_join - assert_not_equal Author.count, Author.joins(:posts).count - assert_equal Author.count, Author.left_outer_joins(:posts).count + def test_left_outer_joins_count_is_same_as_size_of_loaded_results + assert_equal 17, Post.left_outer_joins(:comments).to_a.size + assert_equal 17, Post.left_outer_joins(:comments).count end - def test_left_outer_join_by_left_joins - assert_not_equal Author.count, Author.joins(:posts).count - assert_equal Author.count, Author.left_joins(:posts).count + def test_left_joins_aliases_left_outer_joins + assert_equal Post.left_outer_joins(:comments).to_sql, Post.left_joins(:comments).to_sql + end + + def test_left_outer_joins_return_has_value_for_every_comment + all_post_ids = Post.pluck(:id) + assert_equal all_post_ids, all_post_ids & Post.left_outer_joins(:comments).pluck(:id) + end + + def test_left_outer_joins_actually_does_a_left_outer_join + queries = capture_sql { Author.left_outer_joins(:posts).to_a } + assert queries.any? { |sql| /LEFT OUTER JOIN/i.match?(sql) } end def test_construct_finder_sql_ignores_empty_left_outer_joins_hash - queries = capture_sql { Author.left_outer_joins({}) } - assert queries.none? { |sql| /LEFT OUTER JOIN/i =~ sql } + queries = capture_sql { Author.left_outer_joins({}).to_a } + assert queries.none? { |sql| /LEFT OUTER JOIN/i.match?(sql) } end def test_construct_finder_sql_ignores_empty_left_outer_joins_array - queries = capture_sql { Author.left_outer_joins([]) } - assert queries.none? { |sql| /LEFT OUTER JOIN/i =~ sql } + queries = capture_sql { Author.left_outer_joins([]).to_a } + assert queries.none? { |sql| /LEFT OUTER JOIN/i.match?(sql) } end def test_left_outer_joins_forbids_to_use_string_as_argument - assert_raise(ArgumentError){ Author.left_outer_joins('LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"').to_a } + assert_raise(ArgumentError) { Author.left_outer_joins('LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"').to_a } end def test_join_conditions_added_to_join_clause queries = capture_sql { Author.left_outer_joins(:essays).to_a } - assert queries.any? { |sql| /writer_type.*?=.*?(Author|\?|\$1)/i =~ sql } - assert queries.none? { |sql| /WHERE/i =~ sql } + assert queries.any? { |sql| /writer_type.*?=.*?(Author|\?|\$1|\:a1)/i.match?(sql) } + assert queries.none? { |sql| /WHERE/i.match?(sql) } end def test_find_with_sti_join - scope = Post.left_outer_joins(:special_comments).where(:id => posts(:sti_comments).id) + scope = Post.left_outer_joins(:special_comments).where(id: posts(:sti_comments).id) # The join should match SpecialComment and its subclasses only assert scope.where("comments.type" => "Comment").empty? diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb index b040485d99..dc26f6a383 100644 --- a/activerecord/test/cases/associations/nested_through_associations_test.rb +++ b/activerecord/test/cases/associations/nested_through_associations_test.rb @@ -1,27 +1,27 @@ require "cases/helper" -require 'models/author' -require 'models/post' -require 'models/person' -require 'models/reference' -require 'models/job' -require 'models/reader' -require 'models/comment' -require 'models/tag' -require 'models/tagging' -require 'models/subscriber' -require 'models/book' -require 'models/subscription' -require 'models/rating' -require 'models/member' -require 'models/member_detail' -require 'models/member_type' -require 'models/sponsor' -require 'models/club' -require 'models/organization' -require 'models/category' -require 'models/categorization' -require 'models/membership' -require 'models/essay' +require "models/author" +require "models/post" +require "models/person" +require "models/reference" +require "models/job" +require "models/reader" +require "models/comment" +require "models/tag" +require "models/tagging" +require "models/subscriber" +require "models/book" +require "models/subscription" +require "models/rating" +require "models/member" +require "models/member_detail" +require "models/member_type" +require "models/sponsor" +require "models/club" +require "models/organization" +require "models/category" +require "models/categorization" +require "models/membership" +require "models/essay" class NestedThroughAssociationsTest < ActiveRecord::TestCase fixtures :authors, :books, :posts, :subscriptions, :subscribers, :tags, :taggings, @@ -65,12 +65,12 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_has_many_with_has_many_through_source_reflection_preload_via_joins assert_includes_and_joins_equal( - Author.where('tags.id' => tags(:general).id), + Author.where("tags.id" => tags(:general).id), [authors(:david)], :tags ) # This ensures that the polymorphism of taggings is being observed correctly - authors = Author.joins(:tags).where('taggings.taggable_type' => 'FakeModel') + authors = Author.joins(:tags).where("taggings.taggable_type" => "FakeModel") assert authors.empty? end @@ -79,7 +79,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase # Through: has_many through def test_has_many_through_has_many_through_with_has_many_source_reflection luke, david = subscribers(:first), subscribers(:second) - assert_equal [luke, david, david], authors(:david).subscribers.order('subscribers.nick') + assert_equal [luke, david, david], authors(:david).subscribers.order("subscribers.nick") end def test_has_many_through_has_many_through_with_has_many_source_reflection_preload @@ -93,7 +93,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_has_many_through_with_has_many_source_reflection_preload_via_joins # All authors with subscribers where one of the subscribers' nick is 'alterself' assert_includes_and_joins_equal( - Author.where('subscribers.nick' => 'alterself'), + Author.where("subscribers.nick" => "alterself"), [authors(:david)], :subscribers ) end @@ -115,7 +115,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_has_one_with_has_one_through_source_reflection_preload_via_joins assert_includes_and_joins_equal( - Member.where('member_types.id' => member_types(:founding).id), + Member.where("member_types.id" => member_types(:founding).id), [members(:groucho)], :nested_member_types ) end @@ -137,7 +137,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_has_one_through_with_has_one_source_reflection_preload_via_joins assert_includes_and_joins_equal( - Member.where('sponsors.id' => sponsors(:moustache_club_sponsor_for_groucho).id), + Member.where("sponsors.id" => sponsors(:moustache_club_sponsor_for_groucho).id), [members(:groucho)], :nested_sponsors ) end @@ -149,7 +149,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase groucho_details, other_details = member_details(:groucho), member_details(:some_other_guy) assert_equal [groucho_details, other_details], - members(:groucho).organization_member_details.order('member_details.id') + members(:groucho).organization_member_details.order("member_details.id") end def test_has_many_through_has_one_with_has_many_through_source_reflection_preload @@ -164,12 +164,12 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_has_one_with_has_many_through_source_reflection_preload_via_joins assert_includes_and_joins_equal( - Member.where('member_details.id' => member_details(:groucho).id).order('member_details.id'), + Member.where("member_details.id" => member_details(:groucho).id).order("member_details.id"), [members(:groucho), members(:some_other_guy)], :organization_member_details ) members = Member.joins(:organization_member_details). - where('member_details.id' => 9) + where("member_details.id" => 9) assert members.empty? end @@ -180,7 +180,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase groucho_details, other_details = member_details(:groucho), member_details(:some_other_guy) assert_equal [groucho_details, other_details], - members(:groucho).organization_member_details_2.order('member_details.id') + members(:groucho).organization_member_details_2.order("member_details.id") end def test_has_many_through_has_one_through_with_has_many_source_reflection_preload @@ -196,12 +196,12 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_has_one_through_with_has_many_source_reflection_preload_via_joins assert_includes_and_joins_equal( - Member.where('member_details.id' => member_details(:groucho).id).order('member_details.id'), + Member.where("member_details.id" => member_details(:groucho).id).order("member_details.id"), [members(:groucho), members(:some_other_guy)], :organization_member_details_2 ) members = Member.joins(:organization_member_details_2). - where('member_details.id' => 9) + where("member_details.id" => 9) assert members.empty? end @@ -211,7 +211,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_has_many_with_has_and_belongs_to_many_source_reflection general, cooking = categories(:general), categories(:cooking) - assert_equal [general, cooking], authors(:bob).post_categories.order('categories.id') + assert_equal [general, cooking], authors(:bob).post_categories.order("categories.id") end def test_has_many_through_has_many_with_has_and_belongs_to_many_source_reflection_preload @@ -228,7 +228,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase Author.joins(:post_categories).first assert_includes_and_joins_equal( - Author.where('categories.id' => categories(:cooking).id), + Author.where("categories.id" => categories(:cooking).id), [authors(:bob)], :post_categories ) end @@ -239,7 +239,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection greetings, more = comments(:greetings), comments(:more_greetings) - assert_equal [greetings, more], categories(:technology).post_comments.order('comments.id') + assert_equal [greetings, more], categories(:technology).post_comments.order("comments.id") end def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload @@ -257,7 +257,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase Category.joins(:post_comments).first assert_includes_and_joins_equal( - Category.where('comments.id' => comments(:more_greetings).id).order('categories.id'), + Category.where("comments.id" => comments(:more_greetings).id).order("categories.id"), [categories(:general), categories(:technology)], :post_comments ) end @@ -268,7 +268,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection greetings, more = comments(:greetings), comments(:more_greetings) - assert_equal [greetings, more], authors(:bob).category_post_comments.order('comments.id') + assert_equal [greetings, more], authors(:bob).category_post_comments.order("comments.id") end def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection_preload @@ -285,7 +285,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase Author.joins(:category_post_comments).first assert_includes_and_joins_equal( - Author.where('comments.id' => comments(:does_it_hurt).id).order('authors.id'), + Author.where("comments.id" => comments(:does_it_hurt).id).order("authors.id"), [authors(:david), authors(:mary)], :category_post_comments ) end @@ -308,7 +308,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_has_many_through_with_belongs_to_source_reflection_preload_via_joins assert_includes_and_joins_equal( - Author.where('tags.id' => tags(:general).id), + Author.where("tags.id" => tags(:general).id), [authors(:david)], :tagging_tags ) end @@ -320,7 +320,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase welcome_general, thinking_general = taggings(:welcome_general), taggings(:thinking_general) assert_equal [welcome_general, thinking_general], - categorizations(:david_welcome_general).post_taggings.order('taggings.id') + categorizations(:david_welcome_general).post_taggings.order("taggings.id") end def test_has_many_through_belongs_to_with_has_many_through_source_reflection_preload @@ -334,7 +334,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_belongs_to_with_has_many_through_source_reflection_preload_via_joins assert_includes_and_joins_equal( - Categorization.where('taggings.id' => taggings(:welcome_general).id).order('taggings.id'), + Categorization.where("taggings.id" => taggings(:welcome_general).id).order("taggings.id"), [categorizations(:david_welcome_general)], :post_taggings ) end @@ -357,7 +357,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_has_one_with_has_one_through_source_reflection_preload_via_joins assert_includes_and_joins_equal( - Member.where('member_types.id' => member_types(:founding).id), + Member.where("member_types.id" => member_types(:founding).id), [members(:groucho)], :nested_member_type ) end @@ -391,7 +391,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_has_one_through_with_belongs_to_source_reflection_preload_via_joins assert_includes_and_joins_equal( - Member.where('categories.id' => categories(:technology).id), + Member.where("categories.id" => categories(:technology).id), [members(:blarpy_winkup)], :club_category ) end @@ -404,7 +404,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_distinct_has_many_through_a_has_many_through_association_on_through_reflection author = authors(:david) assert_equal [subscribers(:first), subscribers(:second)], - author.distinct_subscribers.order('subscribers.nick') + author.distinct_subscribers.order("subscribers.nick") end def test_nested_has_many_through_with_a_table_referenced_multiple_times @@ -413,26 +413,26 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase author.similar_posts.sort_by(&:id) # Mary and Bob both have posts in misc, but they are the only ones. - authors = Author.joins(:similar_posts).where('posts.id' => posts(:misc_by_bob).id) + authors = Author.joins(:similar_posts).where("posts.id" => posts(:misc_by_bob).id) assert_equal [authors(:mary), authors(:bob)], authors.distinct.sort_by(&:id) # Check the polymorphism of taggings is being observed correctly (in both joins) - authors = Author.joins(:similar_posts).where('taggings.taggable_type' => 'FakeModel') + authors = Author.joins(:similar_posts).where("taggings.taggable_type" => "FakeModel") assert authors.empty? - authors = Author.joins(:similar_posts).where('taggings_authors_join.taggable_type' => 'FakeModel') + authors = Author.joins(:similar_posts).where("taggings_authors_join.taggable_type" => "FakeModel") assert authors.empty? end def test_has_many_through_with_foreign_key_option_on_through_reflection - assert_equal [posts(:welcome), posts(:authorless)], people(:david).agents_posts.order('posts.id') + assert_equal [posts(:welcome), posts(:authorless)], people(:david).agents_posts.order("posts.id") assert_equal [authors(:david)], references(:david_unicyclist).agents_posts_authors - references = Reference.joins(:agents_posts_authors).where('authors.id' => authors(:david).id) + references = Reference.joins(:agents_posts_authors).where("authors.id" => authors(:david).id) assert_equal [references(:david_unicyclist)], references end def test_has_many_through_with_foreign_key_option_on_source_reflection - assert_equal [people(:michael), people(:susan)], jobs(:unicyclist).agents.order('people.id') + assert_equal [people(:michael), people(:susan)], jobs(:unicyclist).agents.order("people.id") jobs = Job.joins(:agents) assert_equal [jobs(:unicyclist), jobs(:unicyclist)], jobs @@ -443,7 +443,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase assert_equal [ratings(:special_comment_rating), ratings(:sub_special_comment_rating)], ratings # Ensure STI is respected in the join - scope = Post.joins(:special_comments_ratings).where(:id => posts(:sti_comments).id) + scope = Post.joins(:special_comments_ratings).where(id: posts(:sti_comments).id) assert scope.where("comments.type" => "Comment").empty? assert !scope.where("comments.type" => "SpecialComment").empty? assert !scope.where("comments.type" => "SubSpecialComment").empty? @@ -453,7 +453,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase taggings = posts(:sti_comments).special_comments_ratings_taggings assert_equal [taggings(:special_comment_rating)], taggings - scope = Post.joins(:special_comments_ratings_taggings).where(:id => posts(:sti_comments).id) + scope = Post.joins(:special_comments_ratings_taggings).where(id: posts(:sti_comments).id) assert scope.where("comments.type" => "Comment").empty? assert !scope.where("comments.type" => "SpecialComment").empty? end @@ -505,7 +505,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase end def test_nested_has_many_through_with_conditions_on_through_associations_preload - assert Author.where('tags.id' => 100).joins(:misc_post_first_blue_tags).empty? + assert Author.where("tags.id" => 100).joins(:misc_post_first_blue_tags).empty? authors = assert_queries(3) { Author.includes(:misc_post_first_blue_tags).to_a.sort_by(&:id) } blue = tags(:blue) @@ -518,7 +518,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_nested_has_many_through_with_conditions_on_through_associations_preload_via_joins # Pointless condition to force single-query loading assert_includes_and_joins_equal( - Author.where('tags.id = tags.id').references(:tags), + Author.where("tags.id = tags.id").references(:tags), [authors(:bob)], :misc_post_first_blue_tags ) end @@ -539,7 +539,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_nested_has_many_through_with_conditions_on_source_associations_preload_via_joins # Pointless condition to force single-query loading assert_includes_and_joins_equal( - Author.where('tags.id = tags.id').references(:tags), + Author.where("tags.id = tags.id").references(:tags), [authors(:bob)], :misc_post_first_blue_tags_2 ) end @@ -548,13 +548,13 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase assert_equal [categories(:general)], organizations(:nsa).author_essay_categories organizations = Organization.joins(:author_essay_categories). - where('categories.id' => categories(:general).id) + where("categories.id" => categories(:general).id) assert_equal [organizations(:nsa)], organizations assert_equal categories(:general), organizations(:nsa).author_owned_essay_category organizations = Organization.joins(:author_owned_essay_category). - where('categories.id' => categories(:general).id) + where("categories.id" => categories(:general).id) assert_equal [organizations(:nsa)], organizations end diff --git a/activerecord/test/cases/associations/required_test.rb b/activerecord/test/cases/associations/required_test.rb index 3e5494e897..f8b686721e 100644 --- a/activerecord/test/cases/associations/required_test.rb +++ b/activerecord/test/cases/associations/required_test.rb @@ -18,8 +18,8 @@ class RequiredAssociationsTest < ActiveRecord::TestCase end teardown do - @connection.drop_table 'parents', if_exists: true - @connection.drop_table 'children', if_exists: true + @connection.drop_table "parents", if_exists: true + @connection.drop_table "children", if_exists: true end test "belongs_to associations are not required by default" do @@ -92,11 +92,11 @@ class RequiredAssociationsTest < ActiveRecord::TestCase private - def subclass_of(klass, &block) - subclass = Class.new(klass, &block) - def subclass.name - superclass.name + def subclass_of(klass, &block) + subclass = Class.new(klass, &block) + def subclass.name + superclass.name + end + subclass end - subclass - end end diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index 01a058918a..c095b3a91c 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -1,80 +1,77 @@ require "cases/helper" -require 'models/computer' -require 'models/developer' -require 'models/project' -require 'models/company' -require 'models/categorization' -require 'models/category' -require 'models/post' -require 'models/author' -require 'models/comment' -require 'models/tag' -require 'models/tagging' -require 'models/person' -require 'models/reader' -require 'models/ship_part' -require 'models/ship' -require 'models/liquid' -require 'models/molecule' -require 'models/electron' -require 'models/man' -require 'models/interest' +require "models/computer" +require "models/developer" +require "models/project" +require "models/company" +require "models/categorization" +require "models/category" +require "models/post" +require "models/author" +require "models/comment" +require "models/tag" +require "models/tagging" +require "models/person" +require "models/reader" +require "models/ship_part" +require "models/ship" +require "models/liquid" +require "models/molecule" +require "models/electron" +require "models/man" +require "models/interest" class AssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :developers, :projects, :developers_projects, :computers, :people, :readers, :authors, :author_favorites def test_eager_loading_should_not_change_count_of_children - liquid = Liquid.create(:name => 'salty') - molecule = liquid.molecules.create(:name => 'molecule_1') - molecule.electrons.create(:name => 'electron_1') - molecule.electrons.create(:name => 'electron_2') + liquid = Liquid.create(name: "salty") + molecule = liquid.molecules.create(name: "molecule_1") + molecule.electrons.create(name: "electron_1") + molecule.electrons.create(name: "electron_2") - liquids = Liquid.includes(:molecules => :electrons).references(:molecules).where('molecules.id is not null') + liquids = Liquid.includes(molecules: :electrons).references(:molecules).where("molecules.id is not null") assert_equal 1, liquids[0].molecules.length end def test_subselect author = authors :david favs = author.author_favorites - fav2 = author.author_favorites.where(:author => Author.where(id: author.id)).to_a + fav2 = author.author_favorites.where(author: Author.where(id: author.id)).to_a assert_equal favs, fav2 end def test_loading_the_association_target_should_keep_child_records_marked_for_destruction - ship = Ship.create!(:name => "The good ship Dollypop") - part = ship.parts.create!(:name => "Mast") + ship = Ship.create!(name: "The good ship Dollypop") + part = ship.parts.create!(name: "Mast") part.mark_for_destruction - ship.parts.send(:load_target) assert ship.parts[0].marked_for_destruction? end def test_loading_the_association_target_should_load_most_recent_attributes_for_child_records_marked_for_destruction - ship = Ship.create!(:name => "The good ship Dollypop") - part = ship.parts.create!(:name => "Mast") + ship = Ship.create!(name: "The good ship Dollypop") + part = ship.parts.create!(name: "Mast") part.mark_for_destruction - ShipPart.find(part.id).update_columns(name: 'Deck') - ship.parts.send(:load_target) - assert_equal 'Deck', ship.parts[0].name + ShipPart.find(part.id).update_columns(name: "Deck") + assert_equal "Deck", ship.parts[0].name end - def test_include_with_order_works - assert_nothing_raised {Account.all.merge!(:order => 'id', :includes => :firm).first} - assert_nothing_raised {Account.all.merge!(:order => :id, :includes => :firm).first} + assert_nothing_raised { Account.all.merge!(order: "id", includes: :firm).first } + assert_nothing_raised { Account.all.merge!(order: :id, includes: :firm).first } end def test_bad_collection_keys - assert_raise(ArgumentError, 'ActiveRecord should have barked on bad collection keys') do - Class.new(ActiveRecord::Base).has_many(:wheels, :name => 'wheels') + assert_raise(ArgumentError, "ActiveRecord should have barked on bad collection keys") do + Class.new(ActiveRecord::Base).has_many(:wheels, name: "wheels") end end def test_should_construct_new_finder_sql_after_create - person = Person.new :first_name => 'clark' + person = Person.new first_name: "clark" assert_equal [], person.readers.to_a person.save! - reader = Reader.create! :person => person, :post => Post.new(:title => "foo", :body => "bar") + reader = Reader.create! person: person, post: Post.new(title: "foo", body: "bar") assert person.readers.find(reader.id) end @@ -122,9 +119,8 @@ class AssociationsTest < ActiveRecord::TestCase def test_association_with_references firm = companies(:first_firm) - assert_includes firm.association_with_references.references_values, 'foo' + assert_includes firm.association_with_references.references_values, "foo" end - end class AssociationProxyTest < ActiveRecord::TestCase @@ -133,9 +129,9 @@ class AssociationProxyTest < ActiveRecord::TestCase def test_push_does_not_load_target david = authors(:david) - david.posts << (post = Post.new(:title => "New on Edge", :body => "More cool stuff!")) + david.posts << (post = Post.new(title: "New on Edge", body: "More cool stuff!")) assert !david.posts.loaded? - assert david.posts.include?(post) + assert_includes david.posts, post end def test_push_has_many_through_does_not_load_target @@ -143,35 +139,35 @@ class AssociationProxyTest < ActiveRecord::TestCase david.categories << categories(:technology) assert !david.categories.loaded? - assert david.categories.include?(categories(:technology)) + assert_includes david.categories, categories(:technology) end def test_push_followed_by_save_does_not_load_target david = authors(:david) - david.posts << (post = Post.new(:title => "New on Edge", :body => "More cool stuff!")) + david.posts << (post = Post.new(title: "New on Edge", body: "More cool stuff!")) assert !david.posts.loaded? david.save assert !david.posts.loaded? - assert david.posts.include?(post) + assert_includes david.posts, post end def test_push_does_not_lose_additions_to_new_record - josh = Author.new(:name => "Josh") - josh.posts << Post.new(:title => "New on Edge", :body => "More cool stuff!") + josh = Author.new(name: "Josh") + josh.posts << Post.new(title: "New on Edge", body: "More cool stuff!") assert josh.posts.loaded? assert_equal 1, josh.posts.size end def test_append_behaves_like_push - josh = Author.new(:name => "Josh") - josh.posts.append Post.new(:title => "New on Edge", :body => "More cool stuff!") + josh = Author.new(name: "Josh") + josh.posts.append Post.new(title: "New on Edge", body: "More cool stuff!") assert josh.posts.loaded? assert_equal 1, josh.posts.size end def test_prepend_is_not_defined - josh = Author.new(:name => "Josh") + josh = Author.new(name: "Josh") assert_raises(NoMethodError) { josh.posts.prepend Post.new } end @@ -183,25 +179,33 @@ class AssociationProxyTest < ActiveRecord::TestCase assert !david.projects.loaded? end + def test_load_does_load_target + david = developers(:david) + + assert !david.projects.loaded? + david.projects.load + assert david.projects.loaded? + end + def test_inspect_does_not_reload_a_not_yet_loaded_target - andreas = Developer.new :name => 'Andreas', :log => 'new developer added' + andreas = Developer.new name: "Andreas", log: "new developer added" assert !andreas.audit_logs.loaded? assert_match(/message: "new developer added"/, andreas.audit_logs.inspect) end def test_save_on_parent_saves_children - developer = Developer.create :name => "Bryan", :salary => 50_000 + developer = Developer.create name: "Bryan", salary: 50_000 assert_equal 1, developer.reload.audit_logs.size end def test_create_via_association_with_block - post = authors(:david).posts.create(:title => "New on Edge") {|p| p.body = "More cool stuff!"} + post = authors(:david).posts.create(title: "New on Edge") { |p| p.body = "More cool stuff!" } assert_equal post.title, "New on Edge" assert_equal post.body, "More cool stuff!" end def test_create_with_bang_via_association_with_block - post = authors(:david).posts.create!(:title => "New on Edge") {|p| p.body = "More cool stuff!"} + post = authors(:david).posts.create!(title: "New on Edge") { |p| p.body = "More cool stuff!" } assert_equal post.title, "New on Edge" assert_equal post.body, "More cool stuff!" end @@ -219,7 +223,7 @@ class AssociationProxyTest < ActiveRecord::TestCase end def test_scoped_allows_conditions - assert developers(:david).projects.merge(where: 'foo').to_sql.include?('foo') + assert developers(:david).projects.merge(where: "foo").to_sql.include?("foo") end test "getting a scope from an association" do @@ -248,6 +252,15 @@ class AssociationProxyTest < ActiveRecord::TestCase test "first! works on loaded associations" do david = authors(:david) assert_equal david.posts.first, david.posts.reload.first! + assert david.posts.loaded? + assert_no_queries { david.posts.first! } + end + + def test_pluck_uses_loaded_target + david = authors(:david) + assert_equal david.posts.pluck(:title), david.posts.load.pluck(:title) + assert david.posts.loaded? + assert_no_queries { david.posts.pluck(:title) } end def test_reset_unloads_target @@ -264,18 +277,18 @@ class OverridingAssociationsTest < ActiveRecord::TestCase class DifferentPerson < ActiveRecord::Base; end class PeopleList < ActiveRecord::Base - has_and_belongs_to_many :has_and_belongs_to_many, :before_add => :enlist - has_many :has_many, :before_add => :enlist + has_and_belongs_to_many :has_and_belongs_to_many, before_add: :enlist + has_many :has_many, before_add: :enlist belongs_to :belongs_to has_one :has_one end class DifferentPeopleList < PeopleList # Different association with the same name, callbacks should be omitted here. - has_and_belongs_to_many :has_and_belongs_to_many, :class_name => 'DifferentPerson' - has_many :has_many, :class_name => 'DifferentPerson' - belongs_to :belongs_to, :class_name => 'DifferentPerson' - has_one :has_one, :class_name => 'DifferentPerson' + has_and_belongs_to_many :has_and_belongs_to_many, class_name: "DifferentPerson" + has_many :has_many, class_name: "DifferentPerson" + belongs_to :belongs_to, class_name: "DifferentPerson" + has_one :has_one, class_name: "DifferentPerson" end def test_habtm_association_redefinition_callbacks_should_differ_and_not_inherited diff --git a/activerecord/test/cases/attribute_decorators_test.rb b/activerecord/test/cases/attribute_decorators_test.rb index 2aeb2601c2..cfa6ed1da6 100644 --- a/activerecord/test/cases/attribute_decorators_test.rb +++ b/activerecord/test/cases/attribute_decorators_test.rb @@ -1,9 +1,9 @@ -require 'cases/helper' +require "cases/helper" module ActiveRecord class AttributeDecoratorsTest < ActiveRecord::TestCase class Model < ActiveRecord::Base - self.table_name = 'attribute_decorators_model' + self.table_name = "attribute_decorators_model" end class StringDecorator < SimpleDelegator @@ -28,19 +28,19 @@ module ActiveRecord teardown do return unless @connection - @connection.drop_table 'attribute_decorators_model', if_exists: true + @connection.drop_table "attribute_decorators_model", if_exists: true Model.attribute_type_decorations.clear Model.reset_column_information end test "attributes can be decorated" do - model = Model.new(a_string: 'Hello') - assert_equal 'Hello', model.a_string + model = Model.new(a_string: "Hello") + assert_equal "Hello", model.a_string Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) } - model = Model.new(a_string: 'Hello') - assert_equal 'Hello decorated!', model.a_string + model = Model.new(a_string: "Hello") + assert_equal "Hello decorated!", model.a_string end test "decoration does not eagerly load existing columns" do @@ -51,54 +51,54 @@ module ActiveRecord end test "undecorated columns are not touched" do - Model.attribute :another_string, :string, default: 'something or other' + Model.attribute :another_string, :string, default: "something or other" Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) } - assert_equal 'something or other', Model.new.another_string + assert_equal "something or other", Model.new.another_string end test "decorators can be chained" do Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) } Model.decorate_attribute_type(:a_string, :other) { |t| StringDecorator.new(t) } - model = Model.new(a_string: 'Hello!') + model = Model.new(a_string: "Hello!") - assert_equal 'Hello! decorated! decorated!', model.a_string + assert_equal "Hello! decorated! decorated!", model.a_string end test "decoration of the same type multiple times is idempotent" do Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) } Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) } - model = Model.new(a_string: 'Hello') - assert_equal 'Hello decorated!', model.a_string + model = Model.new(a_string: "Hello") + assert_equal "Hello decorated!", model.a_string end test "decorations occur in order of declaration" do Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) } Model.decorate_attribute_type(:a_string, :other) do |type| - StringDecorator.new(type, 'decorated again!') + StringDecorator.new(type, "decorated again!") end - model = Model.new(a_string: 'Hello!') + model = Model.new(a_string: "Hello!") - assert_equal 'Hello! decorated! decorated again!', model.a_string + assert_equal "Hello! decorated! decorated again!", model.a_string end test "decorating attributes does not modify parent classes" do - Model.attribute :another_string, :string, default: 'whatever' + Model.attribute :another_string, :string, default: "whatever" Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) } child_class = Class.new(Model) child_class.decorate_attribute_type(:another_string, :test) { |t| StringDecorator.new(t) } child_class.decorate_attribute_type(:a_string, :other) { |t| StringDecorator.new(t) } - model = Model.new(a_string: 'Hello!') - child = child_class.new(a_string: 'Hello!') + model = Model.new(a_string: "Hello!") + child = child_class.new(a_string: "Hello!") - assert_equal 'Hello! decorated!', model.a_string - assert_equal 'whatever', model.another_string - assert_equal 'Hello! decorated! decorated!', child.a_string - assert_equal 'whatever decorated!', child.another_string + assert_equal "Hello! decorated!", model.a_string + assert_equal "whatever", model.another_string + assert_equal "Hello! decorated! decorated!", child.a_string + assert_equal "whatever decorated!", child.another_string end class Multiplier < SimpleDelegator @@ -116,9 +116,9 @@ module ActiveRecord Multiplier.new(type) end - model = Model.new(a_string: 'whatever', an_int: 1) + model = Model.new(a_string: "whatever", an_int: 1) - assert_equal 'whatever', model.a_string + assert_equal "whatever", model.a_string assert_equal 2, model.an_int end end diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb index 74e556211b..a8592bd179 100644 --- a/activerecord/test/cases/attribute_methods/read_test.rb +++ b/activerecord/test/cases/attribute_methods/read_test.rb @@ -1,5 +1,4 @@ require "cases/helper" -require 'thread' module ActiveRecord module AttributeMethods @@ -40,13 +39,13 @@ module ActiveRecord instance = @klass.new @klass.attribute_names.each do |name| - assert !instance.methods.map(&:to_s).include?(name) + assert_not_includes instance.methods.map(&:to_s), name end @klass.define_attribute_methods @klass.attribute_names.each do |name| - assert instance.methods.map(&:to_s).include?(name), "#{name} is not defined" + assert_includes instance.methods.map(&:to_s), name, "#{name} is not defined" end end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 1db52af59b..4c77ecab7c 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -1,15 +1,15 @@ require "cases/helper" -require 'models/minimalistic' -require 'models/developer' -require 'models/auto_id' -require 'models/boolean' -require 'models/computer' -require 'models/topic' -require 'models/company' -require 'models/category' -require 'models/reply' -require 'models/contact' -require 'models/keyboard' +require "models/minimalistic" +require "models/developer" +require "models/auto_id" +require "models/boolean" +require "models/computer" +require "models/topic" +require "models/company" +require "models/category" +require "models/reply" +require "models/contact" +require "models/keyboard" class AttributeMethodsTest < ActiveRecord::TestCase include InTimeZone @@ -19,7 +19,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase def setup @old_matchers = ActiveRecord::Base.send(:attribute_method_matchers).dup @target = Class.new(ActiveRecord::Base) - @target.table_name = 'topics' + @target.table_name = "topics" end teardown do @@ -27,15 +27,34 @@ class AttributeMethodsTest < ActiveRecord::TestCase ActiveRecord::Base.send(:attribute_method_matchers).concat(@old_matchers) end - def test_attribute_for_inspect + test "attribute_for_inspect with a string" do t = topics(:first) t.title = "The First Topic Now Has A Title With\nNewlines And More Than 50 Characters" - assert_equal %("#{t.written_on.to_s(:db)}"), t.attribute_for_inspect(:written_on) assert_equal '"The First Topic Now Has A Title With\nNewlines And ..."', t.attribute_for_inspect(:title) end - def test_attribute_present + test "attribute_for_inspect with a date" do + t = topics(:first) + + assert_equal %("#{t.written_on.to_s(:db)}"), t.attribute_for_inspect(:written_on) + end + + test "attribute_for_inspect with an array" do + t = topics(:first) + t.content = [Object.new] + + assert_match %r(\[#<Object:0x[0-9a-f]+>\]), t.attribute_for_inspect(:content) + end + + test "attribute_for_inspect with a long array" do + t = topics(:first) + t.content = (1..11).to_a + + assert_equal "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]", t.attribute_for_inspect(:content) + end + + test "attribute_present" do t = Topic.new t.title = "hello there!" t.written_on = Time.now @@ -46,7 +65,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert !t.attribute_present?("author_name") end - def test_attribute_present_with_booleans + test "attribute_present with booleans" do b1 = Boolean.new b1.value = false assert b1.attribute_present?(:value) @@ -64,44 +83,44 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert Boolean.find(b4.id).attribute_present?(:value) end - def test_caching_nil_primary_key + test "caching a nil primary key" do klass = Class.new(Minimalistic) assert_called(klass, :reset_primary_key, returns: nil) do 2.times { klass.primary_key } end end - def test_attribute_keys_on_new_instance + 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_raise(NoMethodError) { t.title2 } end - def test_boolean_attributes + test "boolean attributes" do assert !Topic.find(1).approved? assert Topic.find(2).approved? end - def test_set_attributes + test "set attributes" do topic = Topic.find(1) - topic.attributes = { "title" => "Budget", "author_name" => "Jason" } + topic.attributes = { title: "Budget", author_name: "Jason" } topic.save assert_equal("Budget", topic.title) assert_equal("Jason", topic.author_name) assert_equal(topics(:first).author_email_address, Topic.find(1).author_email_address) end - def test_set_attributes_without_hash + test "set attributes without a hash" do topic = Topic.new - assert_raise(ArgumentError) { topic.attributes = '' } + assert_raise(ArgumentError) { topic.attributes = "" } end - def test_integers_as_nil - test = AutoId.create('value' => '') + test "integers as nil" do + test = AutoId.create(value: "") assert_nil AutoId.find(test.id).value end - def test_set_attributes_with_block + test "set attributes with a block" do topic = Topic.new do |t| t.title = "Budget" t.author_name = "Jason" @@ -111,7 +130,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal("Jason", topic.author_name) end - def test_respond_to? + test "respond_to?" do topic = Topic.find(1) assert_respond_to topic, "title" assert_respond_to topic, "title?" @@ -125,27 +144,27 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert !topic.respond_to?(:nothingness) end - def test_respond_to_with_custom_primary_key + test "respond_to? with a custom primary key" do keyboard = Keyboard.create assert_not_nil keyboard.key_number assert_equal keyboard.key_number, keyboard.id - assert keyboard.respond_to?('key_number') - assert keyboard.respond_to?('id') + assert keyboard.respond_to?("key_number") + assert keyboard.respond_to?("id") end - def test_id_before_type_cast_with_custom_primary_key + test "id_before_type_cast with a custom primary key" do 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_equal '10', keyboard.read_attribute_before_type_cast('key_number') - assert_equal '10', keyboard.read_attribute_before_type_cast(:key_number) + keyboard.key_number = "10" + assert_equal "10", keyboard.id_before_type_cast + assert_equal 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 - # Syck calls respond_to? before actually calling initialize - def test_respond_to_with_allocated_object + # Syck calls respond_to? before actually calling initialize. + test "respond_to? with an allocated object" do klass = Class.new(ActiveRecord::Base) do - self.table_name = 'topics' + self.table_name = "topics" end topic = klass.allocate @@ -155,31 +174,32 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_respond_to topic, :title end - # IRB inspects the return value of "MyModel.allocate". - def test_allocated_object_can_be_inspected + # IRB inspects the return value of MyModel.allocate. + test "allocated objects can be inspected" do topic = Topic.allocate assert_equal "#<Topic not initialized>", topic.inspect end - def test_array_content + test "array content" do + content = %w( one two three ) topic = Topic.new - topic.content = %w( one two three ) + topic.content = content topic.save - assert_equal(%w( one two three ), Topic.find(topic.id).content) + assert_equal content, Topic.find(topic.id).content end - def test_read_attributes_before_type_cast - category = Category.new({:name=>"Test category", :type => nil}) - category_attrs = {"name"=>"Test category", "id" => nil, "type" => nil, "categorizations_count" => nil} - assert_equal category_attrs , category.attributes_before_type_cast + test "read attributes_before_type_cast" do + category = Category.new(name: "Test category", type: nil) + category_attrs = { "name" => "Test category", "id" => nil, "type" => nil, "categorizations_count" => nil } + assert_equal category_attrs, category.attributes_before_type_cast end if current_adapter?(:Mysql2Adapter) - def test_read_attributes_before_type_cast_on_boolean - bool = Boolean.create!({ "value" => false }) - if RUBY_PLATFORM =~ /java/ - # JRuby will return the value before typecast as string + test "read attributes_before_type_cast on a boolean" do + bool = Boolean.create!("value" => false) + if RUBY_PLATFORM.include?("java") + # JRuby will return the value before typecast as string. assert_equal "0", bool.reload.attributes_before_type_cast["value"] else assert_equal 0, bool.reload.attributes_before_type_cast["value"] @@ -187,7 +207,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_read_attributes_before_type_cast_on_datetime + test "read attributes_before_type_cast on a datetime" do in_time_zone "Pacific Time (US & Canada)" do record = @target.new @@ -202,7 +222,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_read_attributes_after_type_cast_on_datetime + test "read attributes_after_type_cast on a date" do tz = "Pacific Time (US & Canada)" in_time_zone tz do @@ -223,7 +243,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_hash_content + test "hash content" do topic = Topic.new topic.content = { "one" => 1, "two" => 2 } topic.save @@ -237,7 +257,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal 3, Topic.find(topic.id).content["three"] end - def test_update_array_content + test "update array content" do topic = Topic.new topic.content = %w( one two three ) @@ -251,25 +271,25 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal(%w( one two three four five ), topic.content) end - def test_case_sensitive_attributes_hash - # DB2 is not case-sensitive + test "case-sensitive attributes hash" do + # DB2 is not case-sensitive. return true if current_adapter?(:DB2Adapter) - assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.first.attributes + assert_equal @loaded_fixtures["computers"]["workstation"].to_hash, Computer.first.attributes end - def test_attributes_without_primary_key + test "attributes without primary key" do klass = Class.new(ActiveRecord::Base) do - self.table_name = 'developers_projects' + self.table_name = "developers_projects" end assert_equal klass.column_names, klass.new.attributes.keys - assert_not klass.new.has_attribute?('id') + assert_not klass.new.has_attribute?("id") end - def test_hashes_not_mangled - new_topic = { :title => "New Topic" } - new_topic_values = { :title => "AnotherTopic" } + test "hashes are not mangled" do + new_topic = { title: "New Topic" } + new_topic_values = { title: "AnotherTopic" } topic = Topic.new(new_topic) assert_equal new_topic[:title], topic.title @@ -278,13 +298,13 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal new_topic_values[:title], topic.title end - def test_create_through_factory - topic = Topic.create("title" => "New Topic") + test "create through factory" do + topic = Topic.create(title: "New Topic") topicReloaded = Topic.find(topic.id) assert_equal(topic, topicReloaded) end - def test_write_attribute + test "write_attribute" do topic = Topic.new topic.send(:write_attribute, :title, "Still another topic") assert_equal "Still another topic", topic.title @@ -299,7 +319,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal "Still another topic: part 4", topic.title end - def test_read_attribute + test "read_attribute" do topic = Topic.new topic.title = "Don't change the topic" assert_equal "Don't change the topic", topic.read_attribute("title") @@ -309,15 +329,15 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal "Don't change the topic", topic[:title] end - def test_read_attribute_raises_missing_attribute_error_when_not_exists - computer = Computer.select('id').first + test "read_attribute raises ActiveModel::MissingAttributeError when the attribute does not exist" do + computer = Computer.select("id").first assert_raises(ActiveModel::MissingAttributeError) { computer[:developer] } assert_raises(ActiveModel::MissingAttributeError) { computer[:extendedWarranty] } - assert_raises(ActiveModel::MissingAttributeError) { computer[:no_column_exists] = 'Hello!' } - assert_nothing_raised { computer[:developer] = 'Hello!' } + assert_raises(ActiveModel::MissingAttributeError) { computer[:no_column_exists] = "Hello!" } + assert_nothing_raised { computer[:developer] = "Hello!" } end - def test_read_attribute_when_false + test "read_attribute when false" do topic = topics(:first) topic.approved = false assert !topic.approved?, "approved should be false" @@ -325,7 +345,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert !topic.approved?, "approved should be false" end - def test_read_attribute_when_true + test "read_attribute when true" do topic = topics(:first) topic.approved = true assert topic.approved?, "approved should be true" @@ -333,7 +353,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert topic.approved?, "approved should be true" end - def test_read_write_boolean_attribute + test "boolean attributes writing and reading" do topic = Topic.new topic.approved = "false" assert !topic.approved?, "approved should be false" @@ -348,7 +368,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert topic.approved?, "approved should be true" end - def test_overridden_write_attribute + test "overridden write_attribute" do topic = Topic.new def topic.write_attribute(attr_name, value) super(attr_name, value.downcase) @@ -367,7 +387,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal "yet another topic: part 4", topic.title end - def test_overridden_read_attribute + test "overridden read_attribute" do topic = Topic.new topic.title = "Stop changing the topic" def topic.read_attribute(attr_name) @@ -381,40 +401,40 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal "STOP CHANGING THE TOPIC", topic[:title] end - def test_read_overridden_attribute - topic = Topic.new(:title => 'a') - def topic.title() 'b' end - assert_equal 'a', topic[:title] + test "read overridden attribute" do + topic = Topic.new(title: "a") + def topic.title() "b" end + assert_equal "a", topic[:title] end - def test_query_attribute_string + test "string attribute predicate" do [nil, "", " "].each do |value| - assert_equal false, Topic.new(:author_name => value).author_name? + assert_equal false, Topic.new(author_name: value).author_name? end - assert_equal true, Topic.new(:author_name => "Name").author_name? + assert_equal true, Topic.new(author_name: "Name").author_name? end - def test_query_attribute_number + test "number attribute predicate" do [nil, 0, "0"].each do |value| - assert_equal false, Developer.new(:salary => value).salary? + assert_equal false, Developer.new(salary: value).salary? end - assert_equal true, Developer.new(:salary => 1).salary? - assert_equal true, Developer.new(:salary => "1").salary? + assert_equal true, Developer.new(salary: 1).salary? + assert_equal true, Developer.new(salary: "1").salary? end - def test_query_attribute_boolean + test "boolean attribute predicate" do [nil, "", false, "false", "f", 0].each do |value| - assert_equal false, Topic.new(:approved => value).approved? + assert_equal false, Topic.new(approved: value).approved? end [true, "true", "1", 1].each do |value| - assert_equal true, Topic.new(:approved => value).approved? + assert_equal true, Topic.new(approved: value).approved? end end - def test_query_attribute_with_custom_fields + test "custom field attribute predicate" do object = Company.find_by_sql(<<-SQL).first SELECT c1.*, c2.type as string_value, c2.rating as int_value FROM companies c1, companies c2 @@ -435,95 +455,95 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert !object.int_value? end - def test_non_attribute_access_and_assignment + test "non-attribute read and write" do topic = Topic.new assert !topic.respond_to?("mumbo") assert_raise(NoMethodError) { topic.mumbo } assert_raise(NoMethodError) { topic.mumbo = 5 } end - def test_undeclared_attribute_method_does_not_affect_respond_to_and_method_missing - topic = @target.new(:title => 'Budget') - assert topic.respond_to?('title') - assert_equal 'Budget', topic.title - assert !topic.respond_to?('title_hello_world') + test "undeclared attribute method does not affect respond_to? and method_missing" do + topic = @target.new(title: "Budget") + assert topic.respond_to?("title") + assert_equal "Budget", topic.title + assert !topic.respond_to?("title_hello_world") assert_raise(NoMethodError) { topic.title_hello_world } end - def test_declared_prefixed_attribute_method_affects_respond_to_and_method_missing - topic = @target.new(:title => 'Budget') + test "declared prefixed attribute method affects respond_to? and method_missing" do + topic = @target.new(title: "Budget") %w(default_ title_).each do |prefix| @target.class_eval "def #{prefix}attribute(*args) args end" @target.attribute_method_prefix prefix meth = "#{prefix}title" assert topic.respond_to?(meth) - assert_equal ['title'], topic.send(meth) - assert_equal ['title', 'a'], topic.send(meth, 'a') - assert_equal ['title', 1, 2, 3], topic.send(meth, 1, 2, 3) + assert_equal ["title"], topic.send(meth) + assert_equal ["title", "a"], topic.send(meth, "a") + assert_equal ["title", 1, 2, 3], topic.send(meth, 1, 2, 3) end end - def test_declared_suffixed_attribute_method_affects_respond_to_and_method_missing + test "declared suffixed attribute method affects respond_to? and method_missing" do %w(_default _title_default _it! _candidate= able?).each do |suffix| @target.class_eval "def attribute#{suffix}(*args) args end" @target.attribute_method_suffix suffix - topic = @target.new(:title => 'Budget') + topic = @target.new(title: "Budget") meth = "title#{suffix}" assert topic.respond_to?(meth) - assert_equal ['title'], topic.send(meth) - assert_equal ['title', 'a'], topic.send(meth, 'a') - assert_equal ['title', 1, 2, 3], topic.send(meth, 1, 2, 3) + assert_equal ["title"], topic.send(meth) + assert_equal ["title", "a"], topic.send(meth, "a") + assert_equal ["title", 1, 2, 3], topic.send(meth, 1, 2, 3) end end - def test_declared_affixed_attribute_method_affects_respond_to_and_method_missing - [['mark_', '_for_update'], ['reset_', '!'], ['default_', '_value?']].each do |prefix, suffix| + test "declared affixed attribute method affects respond_to? and method_missing" do + [["mark_", "_for_update"], ["reset_", "!"], ["default_", "_value?"]].each do |prefix, suffix| @target.class_eval "def #{prefix}attribute#{suffix}(*args) args end" - @target.attribute_method_affix({ :prefix => prefix, :suffix => suffix }) - topic = @target.new(:title => 'Budget') + @target.attribute_method_affix(prefix: prefix, suffix: suffix) + topic = @target.new(title: "Budget") meth = "#{prefix}title#{suffix}" assert topic.respond_to?(meth) - assert_equal ['title'], topic.send(meth) - assert_equal ['title', 'a'], topic.send(meth, 'a') - assert_equal ['title', 1, 2, 3], topic.send(meth, 1, 2, 3) + assert_equal ["title"], topic.send(meth) + assert_equal ["title", "a"], topic.send(meth, "a") + assert_equal ["title", 1, 2, 3], topic.send(meth, 1, 2, 3) end end - def test_should_unserialize_attributes_for_frozen_records - myobj = {:value1 => :value2} - topic = Topic.create("content" => myobj) + test "should unserialize attributes for frozen records" do + myobj = { value1: :value2 } + topic = Topic.create(content: myobj) topic.freeze assert_equal myobj, topic.content end - def test_typecast_attribute_from_select_to_false - Topic.create(:title => 'Budget') - # Oracle does not support boolean expressions in SELECT + test "typecast attribute from select to false" do + Topic.create(title: "Budget") + # Oracle does not support boolean expressions in SELECT. if current_adapter?(:OracleAdapter, :FbAdapter) - topic = Topic.all.merge!(:select => "topics.*, 0 as is_test").first + topic = Topic.all.merge!(select: "topics.*, 0 as is_test").first else - topic = Topic.all.merge!(:select => "topics.*, 1=2 as is_test").first + topic = Topic.all.merge!(select: "topics.*, 1=2 as is_test").first end assert !topic.is_test? end - def test_typecast_attribute_from_select_to_true - Topic.create(:title => 'Budget') - # Oracle does not support boolean expressions in SELECT + test "typecast attribute from select to true" do + Topic.create(title: "Budget") + # Oracle does not support boolean expressions in SELECT. if current_adapter?(:OracleAdapter, :FbAdapter) - topic = Topic.all.merge!(:select => "topics.*, 1 as is_test").first + topic = Topic.all.merge!(select: "topics.*, 1 as is_test").first else - topic = Topic.all.merge!(:select => "topics.*, 2=2 as is_test").first + topic = Topic.all.merge!(select: "topics.*, 2=2 as is_test").first end assert topic.is_test? end - def test_raises_dangerous_attribute_error_when_defining_activerecord_method_in_model + test "raises ActiveRecord::DangerousAttributeError when defining an AR method in a model" do %w(save create_or_update).each do |method| - klass = Class.new ActiveRecord::Base + klass = Class.new(ActiveRecord::Base) klass.class_eval "def #{method}() 'defined #{method}' end" assert_raise ActiveRecord::DangerousAttributeError do klass.instance_method_already_implemented?(method) @@ -531,7 +551,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_converted_values_are_returned_after_assignment + test "converted values are returned after assignment" do developer = Developer.new(name: 1337, salary: "50000") assert_equal "50000", developer.salary_before_type_cast @@ -546,7 +566,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal "1337", developer.name end - def test_write_nil_to_time_attributes + test "write nil to time attribute" do in_time_zone "Pacific Time (US & Canada)" do record = @target.new record.written_on = nil @@ -554,7 +574,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_write_time_to_date_attributes + test "write time to date attribute" do in_time_zone "Pacific Time (US & Canada)" do record = @target.new record.last_read = Time.utc(2010, 1, 1, 10) @@ -562,7 +582,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_time_attributes_are_retrieved_in_current_time_zone + test "time attributes are retrieved in the current time zone" do in_time_zone "Pacific Time (US & Canada)" do utc_time = Time.utc(2008, 1, 1) record = @target.new @@ -574,7 +594,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_setting_time_zone_aware_attribute_to_utc + test "setting a time zone-aware attribute to UTC" do in_time_zone "Pacific Time (US & Canada)" do utc_time = Time.utc(2008, 1, 1) record = @target.new @@ -585,7 +605,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_setting_time_zone_aware_attribute_in_other_time_zone + test "setting time zone-aware attribute in other time zone" do utc_time = Time.utc(2008, 1, 1) cst_time = utc_time.in_time_zone("Central Time (US & Canada)") in_time_zone "Pacific Time (US & Canada)" do @@ -597,18 +617,18 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_setting_time_zone_aware_read_attribute + test "setting time zone-aware read attribute" do utc_time = Time.utc(2008, 1, 1) cst_time = utc_time.in_time_zone("Central Time (US & Canada)") in_time_zone "Pacific Time (US & Canada)" do - record = @target.create(:written_on => cst_time).reload + record = @target.create(written_on: cst_time).reload assert_equal utc_time, record[:written_on] assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record[:written_on].time_zone assert_equal Time.utc(2007, 12, 31, 16), record[:written_on].time end end - def test_setting_time_zone_aware_attribute_with_string + test "setting time zone-aware attribute with a string" do utc_time = Time.utc(2008, 1, 1) (-11..13).each do |timezone_offset| time_string = utc_time.in_time_zone(timezone_offset).to_s @@ -622,27 +642,27 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_time_zone_aware_attribute_saved + test "time zone-aware attribute saved" do in_time_zone 1 do - record = @target.create(:written_on => '2012-02-20 10:00') + record = @target.create(written_on: "2012-02-20 10:00") - record.written_on = '2012-02-20 09:00' + record.written_on = "2012-02-20 09:00" record.save assert_equal Time.zone.local(2012, 02, 20, 9), record.reload.written_on end end - def test_setting_time_zone_aware_attribute_to_blank_string_returns_nil + test "setting a time zone-aware attribute to a blank string returns nil" do in_time_zone "Pacific Time (US & Canada)" do record = @target.new - record.written_on = ' ' + record.written_on = " " assert_nil record.written_on assert_nil record[:written_on] end end - def test_setting_time_zone_aware_attribute_interprets_time_zone_unaware_string_in_time_zone - time_string = 'Tue Jan 01 00:00:00 2008' + test "setting a time zone-aware attribute interprets time zone-unaware string in time zone" do + time_string = "Tue Jan 01 00:00:00 2008" (-11..13).each do |timezone_offset| in_time_zone timezone_offset do record = @target.new @@ -654,7 +674,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_setting_time_zone_aware_datetime_in_current_time_zone + test "setting a time zone-aware datetime in the current time zone" do utc_time = Time.utc(2008, 1, 1) in_time_zone "Pacific Time (US & Canada)" do record = @target.new @@ -665,7 +685,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_yaml_dumping_record_with_time_zone_aware_attribute + test "YAML dumping a record with time zone-aware attribute" do in_time_zone "Pacific Time (US & Canada)" do record = Topic.new(id: 1) record.written_on = "Jan 01 00:00:00 2014" @@ -673,7 +693,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_setting_time_zone_aware_time_in_current_time_zone + test "setting a time zone-aware time in the current time zone" do in_time_zone "Pacific Time (US & Canada)" do record = @target.new time_string = "10:00:00" @@ -683,12 +703,12 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal expected_time, record.bonus_time assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.bonus_time.time_zone - record.bonus_time = '' + record.bonus_time = "" assert_nil record.bonus_time end end - def test_setting_time_zone_aware_time_with_dst + test "setting a time zone-aware time with DST" do in_time_zone "Pacific Time (US & Canada)" do current_time = Time.zone.local(2014, 06, 15, 10) record = @target.new(bonus_time: current_time) @@ -702,7 +722,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_removing_time_zone_aware_types + test "removing time zone-aware types" do with_time_zone_aware_types(:datetime) do in_time_zone "Pacific Time (US & Canada)" do record = @target.new(bonus_time: "10:00:00") @@ -714,14 +734,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_time_zone_aware_attributes_dont_recurse_infinitely_on_invalid_values + 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 end end - def test_setting_time_zone_conversion_for_attributes_should_write_value_on_class_variable + test "setting a time_zone_conversion_for_attributes should write the value on a class variable" do Topic.skip_time_zone_conversion_for_attributes = [:field_a] Minimalistic.skip_time_zone_conversion_for_attributes = [:field_b] @@ -729,44 +749,44 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal [:field_b], Minimalistic.skip_time_zone_conversion_for_attributes end - def test_read_attributes_respect_access_control + test "attribute readers respect access control" do privatize("title") - topic = @target.new(:title => "The pros and cons of programming naked.") + topic = @target.new(title: "The pros and cons of programming naked.") assert !topic.respond_to?(:title) exception = assert_raise(NoMethodError) { topic.title } - assert exception.message.include?("private method") + assert_includes exception.message, "private method" assert_equal "I'm private", topic.send(:title) end - def test_write_attributes_respect_access_control + test "attribute writers respect access control" do privatize("title=(value)") topic = @target.new assert !topic.respond_to?(:title=) - exception = assert_raise(NoMethodError) { topic.title = "Pants"} - assert exception.message.include?("private method") + exception = assert_raise(NoMethodError) { topic.title = "Pants" } + assert_includes exception.message, "private method" topic.send(:title=, "Very large pants") end - def test_question_attributes_respect_access_control + test "attribute predicates respect access control" do privatize("title?") - topic = @target.new(:title => "Isaac Newton's pants") + topic = @target.new(title: "Isaac Newton's pants") assert !topic.respond_to?(:title?) exception = assert_raise(NoMethodError) { topic.title? } - assert exception.message.include?("private method") + assert_includes exception.message, "private method" assert topic.send(:title?) end - def test_bulk_update_respects_access_control + test "bulk updates respect access control" do privatize("title=(value)") - assert_raise(ActiveRecord::UnknownAttributeError) { @target.new(:title => "Rants about pants") } - assert_raise(ActiveRecord::UnknownAttributeError) { @target.new.attributes = { :title => "Ants in pants" } } + assert_raise(ActiveRecord::UnknownAttributeError) { @target.new(title: "Rants about pants") } + assert_raise(ActiveRecord::UnknownAttributeError) { @target.new.attributes = { title: "Ants in pants" } } end - def test_bulk_update_raise_unknown_attribute_error + test "bulk update raises ActiveRecord::UnknownAttributeError" do error = assert_raises(ActiveRecord::UnknownAttributeError) { Topic.new(hello: "world") } @@ -775,22 +795,22 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal "unknown attribute 'hello' for Topic.", error.message end - def test_methods_override_in_multi_level_subclass + test "method overrides in multi-level subclasses" do klass = Class.new(Developer) do def name "dev:#{read_attribute(:name)}" end end - 2.times { klass = Class.new klass } - dev = klass.new(name: 'arthurnn') + 2.times { klass = Class.new(klass) } + dev = klass.new(name: "arthurnn") dev.save! - assert_equal 'dev:arthurnn', dev.reload.name + assert_equal "dev:arthurnn", dev.reload.name end - def test_global_methods_are_overwritten + test "global methods are overwritten" do klass = Class.new(ActiveRecord::Base) do - self.table_name = 'computers' + self.table_name = "computers" end assert !klass.instance_method_already_implemented?(:system) @@ -798,11 +818,13 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_nil computer.system end - def test_global_methods_are_overwritten_when_subclassing - klass = Class.new(ActiveRecord::Base) { self.abstract_class = true } + test "global methods are overwritten when subclassing" do + klass = Class.new(ActiveRecord::Base) do + self.abstract_class = true + end subklass = Class.new(klass) do - self.table_name = 'computers' + self.table_name = "computers" end assert !klass.instance_method_already_implemented?(:system) @@ -811,7 +833,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_nil computer.system end - def test_instance_method_should_be_defined_on_the_base_class + test "instance methods should be defined on the base class" do subklass = Class.new(Topic) Topic.define_attribute_methods @@ -827,14 +849,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert subklass.method_defined?(:id), "subklass is missing id method" end - def test_read_attribute_with_nil_should_not_asplode - assert_equal nil, Topic.new.read_attribute(nil) + test "read_attribute with nil should not asplode" do + assert_nil Topic.new.read_attribute(nil) end # If B < A, and A defines an accessor for 'foo', we don't want to override # that by defining a 'foo' method in the generated methods module for B. # (That module will be inserted between the two, e.g. [B, <GeneratedAttributes>, A].) - def test_inherited_custom_accessors + test "inherited custom accessors" do klass = new_topic_like_ar_class do self.abstract_class = true def title; "omg"; end @@ -850,9 +872,9 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal "lol", topic.author_name end - def test_inherited_custom_accessors_with_reserved_names + test "inherited custom accessors with reserved names" do klass = Class.new(ActiveRecord::Base) do - self.table_name = 'computers' + self.table_name = "computers" self.abstract_class = true def system; "omg"; end def system=(val); self.developer = val; end @@ -868,18 +890,18 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal 99, computer.developer end - def test_on_the_fly_super_invokable_generated_attribute_methods_via_method_missing + test "on_the_fly_super_invokable_generated_attribute_methods_via_method_missing" do klass = new_topic_like_ar_class do def title - super + '!' + super + "!" end end real_topic = topics(:first) - assert_equal real_topic.title + '!', klass.find(real_topic.id).title + assert_equal real_topic.title + "!", klass.find(real_topic.id).title end - def test_on_the_fly_super_invokable_generated_predicate_attribute_methods_via_method_missing + test "on-the-fly super-invokable generated attribute predicates via method_missing" do klass = new_topic_like_ar_class do def title? !super @@ -890,7 +912,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal !real_topic.title?, klass.find(real_topic.id).title? end - def test_calling_super_when_parent_does_not_define_method_raises_error + test "calling super when the parent does not define method raises NoMethodError" do klass = new_topic_like_ar_class do def some_method_that_is_not_on_super super @@ -902,38 +924,38 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_attribute_method? + test "attribute_method?" do assert @target.attribute_method?(:title) assert @target.attribute_method?(:title=) assert_not @target.attribute_method?(:wibble) end - def test_attribute_method_returns_false_if_table_does_not_exist - @target.table_name = 'wibble' + test "attribute_method? returns false if the table does not exist" do + @target.table_name = "wibble" assert_not @target.attribute_method?(:title) end - def test_attribute_names_on_new_record + test "attribute_names on a new record" do model = @target.new assert_equal @target.column_names, model.attribute_names end - def test_attribute_names_on_queried_record + test "attribute_names on a queried record" do model = @target.last! assert_equal @target.column_names, model.attribute_names end - def test_attribute_names_with_custom_select - model = @target.select('id').last! + test "attribute_names with a custom select" do + model = @target.select("id").last! - assert_equal ['id'], model.attribute_names - # Sanity check, make sure other columns exist - assert_not_equal ['id'], @target.column_names + assert_equal ["id"], model.attribute_names + # Sanity check, make sure other columns exist. + assert_not_equal ["id"], @target.column_names end - def test_came_from_user + test "came_from_user?" do model = @target.first assert_not model.id_came_from_user? @@ -941,7 +963,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert model.id_came_from_user? end - def test_accessed_fields + test "accessed_fields" do model = @target.first assert_equal [], model.accessed_fields @@ -953,38 +975,38 @@ class AttributeMethodsTest < ActiveRecord::TestCase private - def new_topic_like_ar_class(&block) - klass = Class.new(ActiveRecord::Base) do - self.table_name = 'topics' - class_eval(&block) - end + def new_topic_like_ar_class(&block) + klass = Class.new(ActiveRecord::Base) do + self.table_name = "topics" + class_eval(&block) + end - assert_empty klass.generated_attribute_methods.instance_methods(false) - klass - end + assert_empty klass.generated_attribute_methods.instance_methods(false) + klass + end - def with_time_zone_aware_types(*types) - old_types = ActiveRecord::Base.time_zone_aware_types - ActiveRecord::Base.time_zone_aware_types = types - yield - ensure - ActiveRecord::Base.time_zone_aware_types = old_types - end + def with_time_zone_aware_types(*types) + old_types = ActiveRecord::Base.time_zone_aware_types + ActiveRecord::Base.time_zone_aware_types = types + yield + ensure + ActiveRecord::Base.time_zone_aware_types = old_types + end - def cached_columns - Topic.columns.map(&:name) - end + def cached_columns + Topic.columns.map(&:name) + end - def time_related_columns_on_topic - Topic.columns.select { |c| [:time, :date, :datetime, :timestamp].include?(c.type) } - end + def time_related_columns_on_topic + Topic.columns.select { |c| [:time, :date, :datetime, :timestamp].include?(c.type) } + end - def privatize(method_signature) - @target.class_eval(<<-private_method, __FILE__, __LINE__ + 1) - private - def #{method_signature} - "I'm private" - end - private_method - end + def privatize(method_signature) + @target.class_eval(<<-private_method, __FILE__, __LINE__ + 1) + private + def #{method_signature} + "I'm private" + end + private_method + end end diff --git a/activerecord/test/cases/attribute_set_test.rb b/activerecord/test/cases/attribute_set_test.rb index 7a24b85a36..059b5b2401 100644 --- a/activerecord/test/cases/attribute_set_test.rb +++ b/activerecord/test/cases/attribute_set_test.rb @@ -1,10 +1,10 @@ -require 'cases/helper' +require "cases/helper" module ActiveRecord class AttributeSetTest < ActiveRecord::TestCase test "building a new set from raw attributes" do builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new) - attributes = builder.build_from_database(foo: '1.1', bar: '2.2') + attributes = builder.build_from_database(foo: "1.1", bar: "2.2") assert_equal 1, attributes[:foo].value assert_equal 2.2, attributes[:bar].value @@ -14,7 +14,7 @@ module ActiveRecord test "building with custom types" do builder = AttributeSet::Builder.new(foo: Type::Float.new) - attributes = builder.build_from_database({ foo: '3.3', bar: '4.4' }, { bar: Type::Integer.new }) + attributes = builder.build_from_database({ foo: "3.3", bar: "4.4" }, bar: Type::Integer.new) assert_equal 3.3, attributes[:foo].value assert_equal 4, attributes[:bar].value @@ -22,16 +22,16 @@ module ActiveRecord test "[] returns a null object" do builder = AttributeSet::Builder.new(foo: Type::Float.new) - attributes = builder.build_from_database(foo: '3.3') + attributes = builder.build_from_database(foo: "3.3") - assert_equal '3.3', attributes[:foo].value_before_type_cast + assert_equal "3.3", attributes[:foo].value_before_type_cast assert_equal nil, attributes[:bar].value_before_type_cast assert_equal :bar, attributes[:bar].name end test "duping creates a new hash, but does not dup the attributes" do builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::String.new) - attributes = builder.build_from_database(foo: 1, bar: 'foo') + attributes = builder.build_from_database(foo: 1, bar: "foo") # Ensure the type cast value is cached attributes[:foo].value @@ -39,17 +39,17 @@ module ActiveRecord duped = attributes.dup duped.write_from_database(:foo, 2) - duped[:bar].value << 'bar' + duped[:bar].value << "bar" assert_equal 1, attributes[:foo].value assert_equal 2, duped[:foo].value - assert_equal 'foobar', attributes[:bar].value - assert_equal 'foobar', duped[:bar].value + assert_equal "foobar", attributes[:bar].value + assert_equal "foobar", duped[:bar].value end test "deep_duping creates a new hash and dups each attribute" do builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::String.new) - attributes = builder.build_from_database(foo: 1, bar: 'foo') + attributes = builder.build_from_database(foo: 1, bar: "foo") # Ensure the type cast value is cached attributes[:foo].value @@ -57,12 +57,12 @@ module ActiveRecord duped = attributes.deep_dup duped.write_from_database(:foo, 2) - duped[:bar].value << 'bar' + duped[:bar].value << "bar" assert_equal 1, attributes[:foo].value assert_equal 2, duped[:foo].value - assert_equal 'foo', attributes[:bar].value - assert_equal 'foobar', duped[:bar].value + assert_equal "foo", attributes[:bar].value + assert_equal "foobar", duped[:bar].value end test "freezing cloned set does not freeze original" do @@ -77,7 +77,7 @@ module ActiveRecord test "to_hash returns a hash of the type cast values" do builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new) - attributes = builder.build_from_database(foo: '1.1', bar: '2.2') + attributes = builder.build_from_database(foo: "1.1", bar: "2.2") assert_equal({ foo: 1, bar: 2.2 }, attributes.to_hash) assert_equal({ foo: 1, bar: 2.2 }, attributes.to_h) @@ -85,7 +85,7 @@ module ActiveRecord test "to_hash maintains order" do builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new) - attributes = builder.build_from_database(foo: '2.2', bar: '3.3') + attributes = builder.build_from_database(foo: "2.2", bar: "3.3") attributes[:bar] hash = attributes.to_h @@ -95,9 +95,9 @@ module ActiveRecord test "values_before_type_cast" do builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Integer.new) - attributes = builder.build_from_database(foo: '1.1', bar: '2.2') + attributes = builder.build_from_database(foo: "1.1", bar: "2.2") - assert_equal({ foo: '1.1', bar: '2.2' }, attributes.values_before_type_cast) + assert_equal({ foo: "1.1", bar: "2.2" }, attributes.values_before_type_cast) end test "known columns are built with uninitialized attributes" do @@ -129,7 +129,7 @@ module ActiveRecord test "fetch_value returns the value for the given initialized attribute" do builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new) - attributes = builder.build_from_database(foo: '1.1', bar: '2.2') + attributes = builder.build_from_database(foo: "1.1", bar: "2.2") assert_equal 1, attributes.fetch_value(:foo) assert_equal 2.2, attributes.fetch_value(:bar) @@ -150,8 +150,8 @@ module ActiveRecord test "fetch_value uses the given block for uninitialized attributes" do attributes = attributes_with_uninitialized_key - value = attributes.fetch_value(:bar) { |n| n.to_s + '!' } - assert_equal 'bar!', value + value = attributes.fetch_value(:bar) { |n| n.to_s + "!" } + assert_equal "bar!", value end test "fetch_value returns nil for uninitialized attributes if no block is given" do @@ -207,7 +207,7 @@ module ActiveRecord def attributes_with_uninitialized_key builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new) - builder.build_from_database(foo: '1.1') + builder.build_from_database(foo: "1.1") end test "freezing doesn't prevent the set from materializing" do diff --git a/activerecord/test/cases/attribute_test.rb b/activerecord/test/cases/attribute_test.rb index a24a4fc6a4..7cf6b498c9 100644 --- a/activerecord/test/cases/attribute_test.rb +++ b/activerecord/test/cases/attribute_test.rb @@ -1,4 +1,4 @@ -require 'cases/helper' +require "cases/helper" module ActiveRecord class AttributeTest < ActiveRecord::TestCase @@ -11,91 +11,91 @@ module ActiveRecord end test "from_database + read type casts from database" do - @type.expect(:deserialize, 'type cast from database', ['a value']) - attribute = Attribute.from_database(nil, 'a value', @type) + @type.expect(:deserialize, "type cast from database", ["a value"]) + attribute = Attribute.from_database(nil, "a value", @type) type_cast_value = attribute.value - assert_equal 'type cast from database', type_cast_value + assert_equal "type cast from database", type_cast_value end test "from_user + read type casts from user" do - @type.expect(:cast, 'type cast from user', ['a value']) - attribute = Attribute.from_user(nil, 'a value', @type) + @type.expect(:cast, "type cast from user", ["a value"]) + attribute = Attribute.from_user(nil, "a value", @type) type_cast_value = attribute.value - assert_equal 'type cast from user', type_cast_value + assert_equal "type cast from user", type_cast_value end test "reading memoizes the value" do - @type.expect(:deserialize, 'from the database', ['whatever']) - attribute = Attribute.from_database(nil, 'whatever', @type) + @type.expect(:deserialize, "from the database", ["whatever"]) + attribute = Attribute.from_database(nil, "whatever", @type) type_cast_value = attribute.value second_read = attribute.value - assert_equal 'from the database', type_cast_value + assert_equal "from the database", type_cast_value assert_same type_cast_value, second_read end test "reading memoizes falsy values" do - @type.expect(:deserialize, false, ['whatever']) - attribute = Attribute.from_database(nil, 'whatever', @type) + @type.expect(:deserialize, false, ["whatever"]) + attribute = Attribute.from_database(nil, "whatever", @type) attribute.value attribute.value end test "read_before_typecast returns the given value" do - attribute = Attribute.from_database(nil, 'raw value', @type) + attribute = Attribute.from_database(nil, "raw value", @type) raw_value = attribute.value_before_type_cast - assert_equal 'raw value', raw_value + assert_equal "raw value", raw_value end test "from_database + read_for_database type casts to and from database" do - @type.expect(:deserialize, 'read from database', ['whatever']) - @type.expect(:serialize, 'ready for database', ['read from database']) - attribute = Attribute.from_database(nil, 'whatever', @type) + @type.expect(:deserialize, "read from database", ["whatever"]) + @type.expect(:serialize, "ready for database", ["read from database"]) + attribute = Attribute.from_database(nil, "whatever", @type) serialize = attribute.value_for_database - assert_equal 'ready for database', serialize + assert_equal "ready for database", serialize end test "from_user + read_for_database type casts from the user to the database" do - @type.expect(:cast, 'read from user', ['whatever']) - @type.expect(:serialize, 'ready for database', ['read from user']) - attribute = Attribute.from_user(nil, 'whatever', @type) + @type.expect(:cast, "read from user", ["whatever"]) + @type.expect(:serialize, "ready for database", ["read from user"]) + attribute = Attribute.from_user(nil, "whatever", @type) serialize = attribute.value_for_database - assert_equal 'ready for database', serialize + assert_equal "ready for database", serialize end test "duping dups the value" do - @type.expect(:deserialize, 'type cast', ['a value']) - attribute = Attribute.from_database(nil, 'a value', @type) + @type.expect(:deserialize, "type cast", ["a value"]) + attribute = Attribute.from_database(nil, "a value", @type) value_from_orig = attribute.value value_from_clone = attribute.dup.value - value_from_orig << ' foo' + value_from_orig << " foo" - assert_equal 'type cast foo', value_from_orig - assert_equal 'type cast', value_from_clone + assert_equal "type cast foo", value_from_orig + assert_equal "type cast", value_from_clone end test "duping does not dup the value if it is not dupable" do - @type.expect(:deserialize, false, ['a value']) - attribute = Attribute.from_database(nil, 'a value', @type) + @type.expect(:deserialize, false, ["a value"]) + attribute = Attribute.from_database(nil, "a value", @type) assert_same attribute.value, attribute.dup.value end test "duping does not eagerly type cast if we have not yet type cast" do - attribute = Attribute.from_database(nil, 'a value', @type) + attribute = Attribute.from_database(nil, "a value", @type) attribute.dup end @@ -242,5 +242,12 @@ module ActiveRecord attribute.with_value_from_user(1) end end + + test "with_type preserves mutations" do + attribute = Attribute.from_database(:foo, "", Type::Value.new) + attribute.value << "1" + + assert_equal 1, attribute.with_type(Type::Integer.new).value + end end end diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb index 2991ca8b76..f4620ae2da 100644 --- a/activerecord/test/cases/attributes_test.rb +++ b/activerecord/test/cases/attributes_test.rb @@ -1,10 +1,10 @@ -require 'cases/helper' +require "cases/helper" class OverloadedType < ActiveRecord::Base attribute :overloaded_float, :integer attribute :overloaded_string_with_limit, :string, limit: 50 attribute :non_existent_decimal, :decimal - attribute :string_with_default, :string, default: 'the overloaded default' + attribute :string_with_default, :string, default: "the overloaded default" end class ChildOfOverloadedType < OverloadedType @@ -15,7 +15,7 @@ class GrandchildOfOverloadedType < ChildOfOverloadedType end class UnoverloadedType < ActiveRecord::Base - self.table_name = 'overloaded_types' + self.table_name = "overloaded_types" end module ActiveRecord @@ -38,20 +38,20 @@ module ActiveRecord data.reload assert_equal 2, data.overloaded_float - assert_kind_of Fixnum, OverloadedType.last.overloaded_float + assert_kind_of Integer, OverloadedType.last.overloaded_float assert_equal 2.0, UnoverloadedType.last.overloaded_float assert_kind_of Float, UnoverloadedType.last.overloaded_float end test "properties assigned in constructor" do - data = OverloadedType.new(overloaded_float: '3.3') + data = OverloadedType.new(overloaded_float: "3.3") assert_equal 3, data.overloaded_float end test "overloaded properties with limit" do - assert_equal 50, OverloadedType.type_for_attribute('overloaded_string_with_limit').limit - assert_equal 255, UnoverloadedType.type_for_attribute('overloaded_string_with_limit').limit + assert_equal 50, OverloadedType.type_for_attribute("overloaded_string_with_limit").limit + assert_equal 255, UnoverloadedType.type_for_attribute("overloaded_string_with_limit").limit end test "nonexistent attribute" do @@ -63,26 +63,35 @@ module ActiveRecord end end + test "model with nonexistent attribute with default value can be saved" do + klass = Class.new(OverloadedType) do + attribute :non_existent_string_with_default, :string, default: "nonexistent" + end + + model = klass.new + assert model.save + end + test "changing defaults" do data = OverloadedType.new unoverloaded_data = UnoverloadedType.new - assert_equal 'the overloaded default', data.string_with_default - assert_equal 'the original default', unoverloaded_data.string_with_default + assert_equal "the overloaded default", data.string_with_default + assert_equal "the original default", unoverloaded_data.string_with_default end test "defaults are not touched on the columns" do - assert_equal 'the original default', OverloadedType.columns_hash['string_with_default'].default + assert_equal "the original default", OverloadedType.columns_hash["string_with_default"].default end test "children inherit custom properties" do - data = ChildOfOverloadedType.new(overloaded_float: '4.4') + data = ChildOfOverloadedType.new(overloaded_float: "4.4") assert_equal 4, data.overloaded_float end test "children can override parents" do - data = GrandchildOfOverloadedType.new(overloaded_float: '4.4') + data = GrandchildOfOverloadedType.new(overloaded_float: "4.4") assert_equal 4.4, data.overloaded_float end @@ -97,13 +106,13 @@ module ActiveRecord assert_equal 6, klass.attribute_types.length assert_equal 6, klass.column_defaults.length - assert_not klass.attribute_types.include?('wibble') + assert_not klass.attribute_types.include?("wibble") klass.attribute :wibble, Type::Value.new assert_equal 7, klass.attribute_types.length assert_equal 7, klass.column_defaults.length - assert klass.attribute_types.include?('wibble') + assert_includes klass.attribute_types, "wibble" end test "the given default value is cast from user" do @@ -135,6 +144,17 @@ module ActiveRecord assert_equal 2, klass.new.counter end + test "procs are memoized before type casting" do + klass = Class.new(OverloadedType) do + @@counter = 0 + attribute :counter, :integer, default: -> { @@counter += 1 } + end + + model = klass.new + assert_equal 1, model.counter_before_type_cast + assert_equal 1, model.counter_before_type_cast + end + test "user provided defaults are persisted even if unchanged" do model = OverloadedType.create! @@ -185,5 +205,55 @@ module ActiveRecord assert_equal(:bar, child.new(foo: :bar).foo) end + + test "attributes not backed by database columns are not dirty when unchanged" do + refute OverloadedType.new.non_existent_decimal_changed? + end + + test "attributes not backed by database columns are always initialized" do + OverloadedType.create! + model = OverloadedType.first + + assert_nil model.non_existent_decimal + model.non_existent_decimal = "123" + assert_equal 123, model.non_existent_decimal + end + + test "attributes not backed by database columns return the default on models loaded from database" do + child = Class.new(OverloadedType) do + attribute :non_existent_decimal, :decimal, default: 123 + end + child.create! + model = child.first + + assert_equal 123, model.non_existent_decimal + end + + test "attributes not backed by database columns properly interact with mutation and dirty" do + child = Class.new(ActiveRecord::Base) do + self.table_name = "topics" + attribute :foo, :string, default: "lol" + end + child.create! + model = child.first + + assert_equal "lol", model.foo + + model.foo << "asdf" + assert_equal "lolasdf", model.foo + assert model.foo_changed? + + model.reload + assert_equal "lol", model.foo + + model.foo = "lol" + refute model.changed? + end + + test "attributes not backed by database columns appear in inspect" do + inspection = OverloadedType.new.inspect + + assert_includes inspection, "non_existent_decimal" + end end end diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 3608063b01..c24d7b8835 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -1,55 +1,55 @@ -require 'cases/helper' -require 'models/bird' -require 'models/comment' -require 'models/company' -require 'models/customer' -require 'models/developer' -require 'models/computer' -require 'models/invoice' -require 'models/line_item' -require 'models/order' -require 'models/parrot' -require 'models/person' -require 'models/pirate' -require 'models/post' -require 'models/reader' -require 'models/ship' -require 'models/ship_part' -require 'models/tag' -require 'models/tagging' -require 'models/treasure' -require 'models/eye' -require 'models/electron' -require 'models/molecule' -require 'models/member' -require 'models/member_detail' -require 'models/organization' -require 'models/guitar' -require 'models/tuning_peg' +require "cases/helper" +require "models/bird" +require "models/post" +require "models/comment" +require "models/company" +require "models/customer" +require "models/developer" +require "models/computer" +require "models/invoice" +require "models/line_item" +require "models/order" +require "models/parrot" +require "models/person" +require "models/pirate" +require "models/reader" +require "models/ship" +require "models/ship_part" +require "models/tag" +require "models/tagging" +require "models/treasure" +require "models/eye" +require "models/electron" +require "models/molecule" +require "models/member" +require "models/member_detail" +require "models/organization" +require "models/guitar" +require "models/tuning_peg" class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase def test_autosave_validation person = Class.new(ActiveRecord::Base) { - self.table_name = 'people' - validate :should_be_cool, :on => :create - def self.name; 'Person'; end + self.table_name = "people" + validate :should_be_cool, on: :create + def self.name; "Person"; end private def should_be_cool - unless self.first_name == 'cool' + unless self.first_name == "cool" errors.add :first_name, "not cool" end end } reference = Class.new(ActiveRecord::Base) { self.table_name = "references" - def self.name; 'Reference'; end + def self.name; "Reference"; end belongs_to :person, autosave: true, anonymous_class: person } - u = person.create!(first_name: 'cool') - u.update_attributes!(first_name: 'nah') # still valid because validation only applies on 'create' + u = person.create!(first_name: "cool") + u.update_attributes!(first_name: "nah") # still valid because validation only applies on 'create' assert reference.create!(person: u).persisted? end @@ -79,25 +79,25 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase private - def assert_no_difference_when_adding_callbacks_twice_for(model, association_name) - reflection = model.reflect_on_association(association_name) - assert_no_difference "callbacks_for_model(#{model.name}).length" do - model.send(:add_autosave_association_callbacks, reflection) + def assert_no_difference_when_adding_callbacks_twice_for(model, association_name) + reflection = model.reflect_on_association(association_name) + assert_no_difference "callbacks_for_model(#{model.name}).length" do + model.send(:add_autosave_association_callbacks, reflection) + end end - end - def callbacks_for_model(model) - model.instance_variables.grep(/_callbacks$/).flat_map do |ivar| - model.instance_variable_get(ivar) + def callbacks_for_model(model) + model.instance_variables.grep(/_callbacks$/).flat_map do |ivar| + model.instance_variable_get(ivar) + end end - end end class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase fixtures :companies, :accounts def test_should_save_parent_but_not_invalid_child - firm = Firm.new(:name => 'GlobalMegaCorp') + firm = Firm.new(name: "GlobalMegaCorp") assert firm.valid? firm.build_account_using_primary_key @@ -178,8 +178,8 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas end def test_not_resaved_when_unchanged - firm = Firm.all.merge!(:includes => :account).first - firm.name += '-changed' + firm = Firm.all.merge!(includes: :account).first + firm.name += "-changed" assert_queries(1) { firm.save! } firm = Firm.first @@ -196,21 +196,21 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas end def test_callbacks_firing_order_on_create - eye = Eye.create(:iris_attributes => {:color => 'honey'}) + eye = Eye.create(iris_attributes: { color: "honey" }) assert_equal [true, false], eye.after_create_callbacks_stack end def test_callbacks_firing_order_on_update - eye = Eye.create(iris_attributes: {color: 'honey'}) - eye.update(iris_attributes: {color: 'green'}) + eye = Eye.create(iris_attributes: { color: "honey" }) + eye.update(iris_attributes: { color: "green" }) assert_equal [true, false], eye.after_update_callbacks_stack end def test_callbacks_firing_order_on_save - eye = Eye.create(iris_attributes: {color: 'honey'}) + eye = Eye.create(iris_attributes: { color: "honey" }) assert_equal [false, false], eye.after_save_callbacks_stack - eye.update(iris_attributes: {color: 'blue'}) + eye.update(iris_attributes: { color: "blue" }) assert_equal [false, false, false, false], eye.after_save_callbacks_stack end end @@ -219,7 +219,7 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test fixtures :companies, :posts, :tags, :taggings def test_should_save_parent_but_not_invalid_child - client = Client.new(:name => 'Joe (the Plumber)') + client = Client.new(name: "Joe (the Plumber)") assert client.valid? client.build_firm @@ -231,7 +231,7 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test def test_save_fails_for_invalid_belongs_to # Oracle saves empty string as NULL therefore :message changed to one space - assert log = AuditLog.create(:developer_id => 0, :message => " ") + assert log = AuditLog.create(developer_id: 0, message: " ") log.developer = Developer.new assert !log.developer.valid? @@ -242,7 +242,7 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test def test_save_succeeds_for_invalid_belongs_to_with_validate_false # Oracle saves empty string as NULL therefore :message changed to one space - assert log = AuditLog.create(:developer_id => 0, :message=> " ") + assert log = AuditLog.create(developer_id: 0, message: " ") log.unvalidated_developer = Developer.new assert !log.unvalidated_developer.valid? @@ -362,22 +362,22 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test def test_store_association_with_a_polymorphic_relationship num_tagging = Tagging.count - tags(:misc).create_tagging(:taggable => posts(:thinking)) + tags(:misc).create_tagging(taggable: posts(:thinking)) assert_equal num_tagging + 1, Tagging.count end def test_build_and_then_save_parent_should_not_reload_target client = Client.first - apple = client.build_firm(:name => "Apple") + apple = client.build_firm(name: "Apple") client.save! assert_no_queries { assert_equal apple, client.firm } end def test_validation_does_not_validate_stale_association_target - valid_developer = Developer.create!(:name => "Dude", :salary => 50_000) + valid_developer = Developer.create!(name: "Dude", salary: 50_000) invalid_developer = Developer.new() - auditlog = AuditLog.new(:message => "foo") + auditlog = AuditLog.new(message: "foo") auditlog.developer = invalid_developer auditlog.developer_id = valid_developer.id @@ -388,7 +388,7 @@ end class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttributes < ActiveRecord::TestCase def test_invalid_adding_with_nested_attributes molecule = Molecule.new - valid_electron = Electron.new(name: 'electron') + valid_electron = Electron.new(name: "electron") invalid_electron = Electron.new molecule.electrons = [valid_electron, invalid_electron] @@ -396,7 +396,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib assert_not invalid_electron.valid? assert valid_electron.valid? - assert_not molecule.persisted?, 'Molecule should not be persisted when its electrons are invalid' + assert_not molecule.persisted?, "Molecule should not be persisted when its electrons are invalid" end def test_errors_should_be_indexed_when_passed_as_array @@ -419,7 +419,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib ActiveRecord::Base.index_nested_attribute_errors = true molecule = Molecule.new - valid_electron = Electron.new(name: 'electron') + valid_electron = Electron.new(name: "electron") invalid_electron = Electron.new molecule.electrons = [valid_electron, invalid_electron] @@ -435,7 +435,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib def test_errors_details_should_be_set molecule = Molecule.new - valid_electron = Electron.new(name: 'electron') + valid_electron = Electron.new(name: "electron") invalid_electron = Electron.new molecule.electrons = [valid_electron, invalid_electron] @@ -443,7 +443,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib assert_not invalid_electron.valid? assert valid_electron.valid? assert_not molecule.valid? - assert_equal [{error: :blank}], molecule.errors.details["electrons.name"] + assert_equal [{ error: :blank }], molecule.errors.details[:"electrons.name"] end def test_errors_details_should_be_indexed_when_passed_as_array @@ -457,8 +457,8 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib assert_not tuning_peg_invalid.valid? assert tuning_peg_valid.valid? assert_not guitar.valid? - assert_equal [{error: :not_a_number, value: nil}] , guitar.errors.details["tuning_pegs[1].pitch"] - assert_equal [], guitar.errors.details["tuning_pegs.pitch"] + assert_equal [{ error: :not_a_number, value: nil }], guitar.errors.details[:"tuning_pegs[1].pitch"] + assert_equal [], guitar.errors.details[:"tuning_pegs.pitch"] end def test_errors_details_should_be_indexed_when_global_flag_is_set @@ -466,7 +466,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib ActiveRecord::Base.index_nested_attribute_errors = true molecule = Molecule.new - valid_electron = Electron.new(name: 'electron') + valid_electron = Electron.new(name: "electron") invalid_electron = Electron.new molecule.electrons = [valid_electron, invalid_electron] @@ -474,15 +474,15 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib assert_not invalid_electron.valid? assert valid_electron.valid? assert_not molecule.valid? - assert_equal [{error: :blank}], molecule.errors.details["electrons[1].name"] - assert_equal [], molecule.errors.details["electrons.name"] + assert_equal [{ error: :blank }], molecule.errors.details[:"electrons[1].name"] + assert_equal [], molecule.errors.details[:"electrons.name"] ensure ActiveRecord::Base.index_nested_attribute_errors = old_attribute_config end def test_valid_adding_with_nested_attributes molecule = Molecule.new - valid_electron = Electron.new(name: 'electron') + valid_electron = Electron.new(name: "electron") molecule.electrons = [valid_electron] molecule.save @@ -585,16 +585,16 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa firm.save firm.reload assert_equal 2, firm.clients.length - assert firm.clients.include?(companies(:second_client)) + assert_includes firm.clients, companies(:second_client) end def test_assign_ids_for_through_a_belongs_to - post = Post.new(:title => "Assigning IDs works!", :body => "You heard it here first, folks!") + post = Post.new(title: "Assigning IDs works!", body: "You heard it here first, folks!") post.person_ids = [people(:david).id, people(:michael).id] post.save post.reload assert_equal 2, post.people.length - assert post.people.include?(people(:david)) + assert_includes post.people, people(:david) end def test_build_before_save @@ -602,7 +602,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build("name" => "Another Client") } assert !company.clients_of_firm.loaded? - company.name += '-changed' + company.name += "-changed" assert_queries(2) { assert company.save } assert new_client.persisted? assert_equal 3, company.clients_of_firm.reload.size @@ -610,19 +610,19 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa def test_build_many_before_save company = companies(:first_firm) - assert_no_queries(ignore_none: false) { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) } + assert_no_queries(ignore_none: false) { company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) } - company.name += '-changed' + company.name += "-changed" assert_queries(3) { assert company.save } assert_equal 4, company.clients_of_firm.reload.size end def test_build_via_block_before_save company = companies(:first_firm) - new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build {|client| client.name = "Another Client" } } + new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build { |client| client.name = "Another Client" } } assert !company.clients_of_firm.loaded? - company.name += '-changed' + company.name += "-changed" assert_queries(2) { assert company.save } assert new_client.persisted? assert_equal 3, company.clients_of_firm.reload.size @@ -631,12 +631,12 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa def test_build_many_via_block_before_save company = companies(:first_firm) assert_no_queries(ignore_none: false) do - company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) do |client| + company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) do |client| client.name = "changed" end end - company.name += '-changed' + company.name += "-changed" assert_queries(3) { assert company.save } assert_equal 4, company.clients_of_firm.reload.size end @@ -647,7 +647,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa assert firm.save firm.reload assert_equal 2, firm.clients.length - assert firm.clients.include?(Client.find_by_name("New Client")) + assert_includes firm.clients, Client.find_by_name("New Client") end end @@ -715,8 +715,8 @@ class TestDefaultAutosaveAssociationOnNewRecord < ActiveRecord::TestCase end def test_autosave_new_record_with_after_create_callback - post = PostWithAfterCreateCallback.new(title: 'Captain Murphy', body: 'is back') - post.comments.build(body: 'foo') + post = PostWithAfterCreateCallback.new(title: "Captain Murphy", body: "is back") + post.comments.build(body: "foo") post.save! assert_not_nil post.author_id @@ -727,8 +727,8 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase self.use_transactional_tests = false setup do - @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") - @ship = @pirate.create_ship(:name => 'Nights Dirty Lightning') + @pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?") + @ship = @pirate.create_ship(name: "Nights Dirty Lightning") end teardown do @@ -749,7 +749,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase end # has_one - def test_should_destroy_a_child_association_as_part_of_the_save_transaction_if_it_was_marked_for_destroyal + def test_should_destroy_a_child_association_as_part_of_the_save_transaction_if_it_was_marked_for_destruction assert !@pirate.ship.marked_for_destruction? @pirate.ship.mark_for_destruction @@ -764,12 +764,12 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase end def test_should_skip_validation_on_a_child_association_if_marked_for_destruction - @pirate.ship.name = '' + @pirate.ship.name = "" assert !@pirate.valid? @pirate.ship.mark_for_destruction @pirate.ship.expects(:valid?).never - assert_difference('Ship.count', -1) { @pirate.save! } + assert_difference("Ship.count", -1) { @pirate.save! } end def test_a_child_marked_for_destruction_should_not_be_destroyed_twice @@ -787,7 +787,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase def save(*args) super destroy - raise 'Oh noes!' + raise "Oh noes!" end end @@ -809,7 +809,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase end # belongs_to - def test_should_destroy_a_parent_association_as_part_of_the_save_transaction_if_it_was_marked_for_destroyal + def test_should_destroy_a_parent_association_as_part_of_the_save_transaction_if_it_was_marked_for_destruction assert !@ship.pirate.marked_for_destruction? @ship.pirate.mark_for_destruction @@ -824,12 +824,12 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase end def test_should_skip_validation_on_a_parent_association_if_marked_for_destruction - @ship.pirate.catchphrase = '' + @ship.pirate.catchphrase = "" assert !@ship.valid? @ship.pirate.mark_for_destruction @ship.pirate.expects(:valid?).never - assert_difference('Pirate.count', -1) { @ship.save! } + assert_difference("Pirate.count", -1) { @ship.save! } end def test_a_parent_marked_for_destruction_should_not_be_destroyed_twice @@ -847,7 +847,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase def save(*args) super destroy - raise 'Oh noes!' + raise "Oh noes!" end end @@ -858,16 +858,16 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase end def test_should_save_changed_child_objects_if_parent_is_saved - @pirate = @ship.create_pirate(:catchphrase => "Don' botharrr talkin' like one, savvy?") - @parrot = @pirate.parrots.create!(:name => 'Posideons Killer') + @pirate = @ship.create_pirate(catchphrase: "Don' botharrr talkin' like one, savvy?") + @parrot = @pirate.parrots.create!(name: "Posideons Killer") @parrot.name = "NewName" @ship.save - assert_equal 'NewName', @parrot.reload.name + assert_equal "NewName", @parrot.reload.name end def test_should_destroy_has_many_as_part_of_the_save_transaction_if_they_were_marked_for_destruction - 2.times { |i| @pirate.birds.create!(:name => "birds_#{i}") } + 2.times { |i| @pirate.birds.create!(name: "birds_#{i}") } assert !@pirate.birds.any?(&:marked_for_destruction?) @@ -891,9 +891,9 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase end def test_should_skip_validation_on_has_many_if_marked_for_destruction - 2.times { |i| @pirate.birds.create!(:name => "birds_#{i}") } + 2.times { |i| @pirate.birds.create!(name: "birds_#{i}") } - @pirate.birds.each { |bird| bird.name = '' } + @pirate.birds.each { |bird| bird.name = "" } assert !@pirate.valid? @pirate.birds.each do |bird| @@ -904,9 +904,9 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase end def test_should_skip_validation_on_has_many_if_destroyed - @pirate.birds.create!(:name => "birds_1") + @pirate.birds.create!(name: "birds_1") - @pirate.birds.each { |bird| bird.name = '' } + @pirate.birds.each { |bird| bird.name = "" } assert !@pirate.valid? @pirate.birds.each(&:destroy) @@ -914,7 +914,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase end def test_a_child_marked_for_destruction_should_not_be_destroyed_twice_while_saving_has_many - @pirate.birds.create!(:name => "birds_1") + @pirate.birds.create!(name: "birds_1") @pirate.birds.each(&:mark_for_destruction) assert @pirate.save @@ -924,14 +924,14 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase end def test_should_rollback_destructions_if_an_exception_occurred_while_saving_has_many - 2.times { |i| @pirate.birds.create!(:name => "birds_#{i}") } + 2.times { |i| @pirate.birds.create!(name: "birds_#{i}") } before = @pirate.birds.map { |c| c.mark_for_destruction ; c } # Stub the destroy method of the second child to raise an exception class << before.last def destroy(*args) super - raise 'Oh noes!' + raise "Oh noes!" end end @@ -940,9 +940,9 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase end def test_when_new_record_a_child_marked_for_destruction_should_not_affect_other_records_from_saving - @pirate = @ship.build_pirate(:catchphrase => "Arr' now I shall keep me eye on you matey!") # new record + @pirate = @ship.build_pirate(catchphrase: "Arr' now I shall keep me eye on you matey!") # new record - 3.times { |i| @pirate.birds.build(:name => "birds_#{i}") } + 3.times { |i| @pirate.birds.build(name: "birds_#{i}") } @pirate.birds[1].mark_for_destruction @pirate.save! @@ -968,8 +968,8 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase define_method("test_should_run_add_callback_#{callback_type}s_for_has_many") do association_name_with_callbacks = "birds_with_#{callback_type}_callbacks" - pirate = Pirate.new(:catchphrase => "Arr") - pirate.send(association_name_with_callbacks).build(:name => "Crowe the One-Eyed") + pirate = Pirate.new(catchphrase: "Arr") + pirate.send(association_name_with_callbacks).build(name: "Crowe the One-Eyed") expected = [ "before_adding_#{callback_type}_bird_<new>", @@ -982,7 +982,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase define_method("test_should_run_remove_callback_#{callback_type}s_for_has_many") do association_name_with_callbacks = "birds_with_#{callback_type}_callbacks" - @pirate.send(association_name_with_callbacks).create!(:name => "Crowe the One-Eyed") + @pirate.send(association_name_with_callbacks).create!(name: "Crowe the One-Eyed") @pirate.send(association_name_with_callbacks).each(&:mark_for_destruction) child_id = @pirate.send(association_name_with_callbacks).first.id @@ -999,7 +999,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase end def test_should_destroy_habtm_as_part_of_the_save_transaction_if_they_were_marked_for_destruction - 2.times { |i| @pirate.parrots.create!(:name => "parrots_#{i}") } + 2.times { |i| @pirate.parrots.create!(name: "parrots_#{i}") } assert !@pirate.parrots.any?(&:marked_for_destruction?) @pirate.parrots.each(&:mark_for_destruction) @@ -1015,9 +1015,9 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase end def test_should_skip_validation_on_habtm_if_marked_for_destruction - 2.times { |i| @pirate.parrots.create!(:name => "parrots_#{i}") } + 2.times { |i| @pirate.parrots.create!(name: "parrots_#{i}") } - @pirate.parrots.each { |parrot| parrot.name = '' } + @pirate.parrots.each { |parrot| parrot.name = "" } assert !@pirate.valid? @pirate.parrots.each do |parrot| @@ -1030,9 +1030,9 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase end def test_should_skip_validation_on_habtm_if_destroyed - @pirate.parrots.create!(:name => "parrots_1") + @pirate.parrots.create!(name: "parrots_1") - @pirate.parrots.each { |parrot| parrot.name = '' } + @pirate.parrots.each { |parrot| parrot.name = "" } assert !@pirate.valid? @pirate.parrots.each(&:destroy) @@ -1040,7 +1040,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase end def test_a_child_marked_for_destruction_should_not_be_destroyed_twice_while_saving_habtm - @pirate.parrots.create!(:name => "parrots_1") + @pirate.parrots.create!(name: "parrots_1") @pirate.parrots.each(&:mark_for_destruction) assert @pirate.save @@ -1053,13 +1053,13 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase end def test_should_rollback_destructions_if_an_exception_occurred_while_saving_habtm - 2.times { |i| @pirate.parrots.create!(:name => "parrots_#{i}") } + 2.times { |i| @pirate.parrots.create!(name: "parrots_#{i}") } before = @pirate.parrots.map { |c| c.mark_for_destruction ; c } class << @pirate.association(:parrots) def destroy(*args) super - raise 'Oh noes!' + raise "Oh noes!" end end @@ -1072,8 +1072,8 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase define_method("test_should_run_add_callback_#{callback_type}s_for_habtm") do association_name_with_callbacks = "parrots_with_#{callback_type}_callbacks" - pirate = Pirate.new(:catchphrase => "Arr") - pirate.send(association_name_with_callbacks).build(:name => "Crowe the One-Eyed") + pirate = Pirate.new(catchphrase: "Arr") + pirate.send(association_name_with_callbacks).build(name: "Crowe the One-Eyed") expected = [ "before_adding_#{callback_type}_parrot_<new>", @@ -1086,7 +1086,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase define_method("test_should_run_remove_callback_#{callback_type}s_for_habtm") do association_name_with_callbacks = "parrots_with_#{callback_type}_callbacks" - @pirate.send(association_name_with_callbacks).create!(:name => "Crowe the One-Eyed") + @pirate.send(association_name_with_callbacks).create!(name: "Crowe the One-Eyed") @pirate.send(association_name_with_callbacks).each(&:mark_for_destruction) child_id = @pirate.send(association_name_with_callbacks).first.id @@ -1108,21 +1108,21 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase def setup super - @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") - @ship = @pirate.create_ship(:name => 'Nights Dirty Lightning') + @pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?") + @ship = @pirate.create_ship(name: "Nights Dirty Lightning") end def test_should_still_work_without_an_associated_model @ship.destroy @pirate.reload.catchphrase = "Arr" @pirate.save - assert_equal 'Arr', @pirate.reload.catchphrase + assert_equal "Arr", @pirate.reload.catchphrase end def test_should_automatically_save_the_associated_model - @pirate.ship.name = 'The Vile Insanity' + @pirate.ship.name = "The Vile Insanity" @pirate.save - assert_equal 'The Vile Insanity', @pirate.reload.ship.name + assert_equal "The Vile Insanity", @pirate.reload.ship.name end def test_changed_for_autosave_should_handle_cycles @@ -1136,13 +1136,13 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase end def test_should_automatically_save_bang_the_associated_model - @pirate.ship.name = 'The Vile Insanity' + @pirate.ship.name = "The Vile Insanity" @pirate.save! - assert_equal 'The Vile Insanity', @pirate.reload.ship.name + assert_equal "The Vile Insanity", @pirate.reload.ship.name end def test_should_automatically_validate_the_associated_model - @pirate.ship.name = '' + @pirate.ship.name = "" assert @pirate.invalid? assert @pirate.errors[:"ship.name"].any? end @@ -1158,7 +1158,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase def test_should_not_ignore_different_error_messages_on_the_same_attribute old_validators = Ship._validators.deep_dup old_callbacks = Ship._validate_callbacks.deep_dup - Ship.validates_format_of :name, :with => /\w/ + Ship.validates_format_of :name, with: /\w/ @pirate.ship.name = "" @pirate.catchphrase = nil assert @pirate.invalid? @@ -1169,48 +1169,48 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase end def test_should_still_allow_to_bypass_validations_on_the_associated_model - @pirate.catchphrase = '' - @pirate.ship.name = '' - @pirate.save(:validate => false) + @pirate.catchphrase = "" + @pirate.ship.name = "" + @pirate.save(validate: false) # Oracle saves empty string as NULL if current_adapter?(:OracleAdapter) assert_equal [nil, nil], [@pirate.reload.catchphrase, @pirate.ship.name] else - assert_equal ['', ''], [@pirate.reload.catchphrase, @pirate.ship.name] + assert_equal ["", ""], [@pirate.reload.catchphrase, @pirate.ship.name] end end def test_should_allow_to_bypass_validations_on_associated_models_at_any_depth - 2.times { |i| @pirate.ship.parts.create!(:name => "part #{i}") } + 2.times { |i| @pirate.ship.parts.create!(name: "part #{i}") } - @pirate.catchphrase = '' - @pirate.ship.name = '' - @pirate.ship.parts.each { |part| part.name = '' } - @pirate.save(:validate => false) + @pirate.catchphrase = "" + @pirate.ship.name = "" + @pirate.ship.parts.each { |part| part.name = "" } + @pirate.save(validate: false) values = [@pirate.reload.catchphrase, @pirate.ship.name, *@pirate.ship.parts.map(&:name)] # Oracle saves empty string as NULL if current_adapter?(:OracleAdapter) assert_equal [nil, nil, nil, nil], values else - assert_equal ['', '', '', ''], values + assert_equal ["", "", "", ""], values end end def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that - @pirate.ship.name = '' + @pirate.ship.name = "" assert_raise(ActiveRecord::RecordInvalid) do @pirate.save! end end def test_should_not_save_and_return_false_if_a_callback_cancelled_saving - pirate = Pirate.new(:catchphrase => 'Arr') - ship = pirate.build_ship(:name => 'The Vile Insanity') + pirate = Pirate.new(catchphrase: "Arr") + ship = pirate.build_ship(name: "The Vile Insanity") ship.cancel_save_from_callback = true - assert_no_difference 'Pirate.count' do - assert_no_difference 'Ship.count' do + assert_no_difference "Pirate.count" do + assert_no_difference "Ship.count" do assert !pirate.save end end @@ -1219,14 +1219,14 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase def test_should_rollback_any_changes_if_an_exception_occurred_while_saving before = [@pirate.catchphrase, @pirate.ship.name] - @pirate.catchphrase = 'Arr' - @pirate.ship.name = 'The Vile Insanity' + @pirate.catchphrase = "Arr" + @pirate.ship.name = "The Vile Insanity" # Stub the save method of the @pirate.ship instance to raise an exception class << @pirate.ship def save(*args) super - raise 'Oh noes!' + raise "Oh noes!" end end @@ -1235,7 +1235,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase end def test_should_not_load_the_associated_model - assert_queries(1) { @pirate.catchphrase = 'Arr'; @pirate.save! } + assert_queries(1) { @pirate.catchphrase = "Arr"; @pirate.save! } end def test_mark_for_destruction_is_ignored_without_autosave_true @@ -1260,7 +1260,7 @@ class TestAutosaveAssociationOnAHasOneThroughAssociation < ActiveRecord::TestCas class << @member.organization def save(*args) super - raise 'Oh noes!' + raise "Oh noes!" end end assert_nothing_raised { @member.save } @@ -1272,31 +1272,31 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase def setup super - @ship = Ship.create(:name => 'Nights Dirty Lightning') - @pirate = @ship.create_pirate(:catchphrase => "Don' botharrr talkin' like one, savvy?") + @ship = Ship.create(name: "Nights Dirty Lightning") + @pirate = @ship.create_pirate(catchphrase: "Don' botharrr talkin' like one, savvy?") end def test_should_still_work_without_an_associated_model @pirate.destroy @ship.reload.name = "The Vile Insanity" @ship.save - assert_equal 'The Vile Insanity', @ship.reload.name + assert_equal "The Vile Insanity", @ship.reload.name end def test_should_automatically_save_the_associated_model - @ship.pirate.catchphrase = 'Arr' + @ship.pirate.catchphrase = "Arr" @ship.save - assert_equal 'Arr', @ship.reload.pirate.catchphrase + assert_equal "Arr", @ship.reload.pirate.catchphrase end def test_should_automatically_save_bang_the_associated_model - @ship.pirate.catchphrase = 'Arr' + @ship.pirate.catchphrase = "Arr" @ship.save! - assert_equal 'Arr', @ship.reload.pirate.catchphrase + assert_equal "Arr", @ship.reload.pirate.catchphrase end def test_should_automatically_validate_the_associated_model - @ship.pirate.catchphrase = '' + @ship.pirate.catchphrase = "" assert @ship.invalid? assert @ship.errors[:"pirate.catchphrase"].any? end @@ -1310,31 +1310,31 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase end def test_should_still_allow_to_bypass_validations_on_the_associated_model - @ship.pirate.catchphrase = '' - @ship.name = '' - @ship.save(:validate => false) + @ship.pirate.catchphrase = "" + @ship.name = "" + @ship.save(validate: false) # Oracle saves empty string as NULL if current_adapter?(:OracleAdapter) assert_equal [nil, nil], [@ship.reload.name, @ship.pirate.catchphrase] else - assert_equal ['', ''], [@ship.reload.name, @ship.pirate.catchphrase] + assert_equal ["", ""], [@ship.reload.name, @ship.pirate.catchphrase] end end def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that - @ship.pirate.catchphrase = '' + @ship.pirate.catchphrase = "" assert_raise(ActiveRecord::RecordInvalid) do @ship.save! end end def test_should_not_save_and_return_false_if_a_callback_cancelled_saving - ship = Ship.new(:name => 'The Vile Insanity') - pirate = ship.build_pirate(:catchphrase => 'Arr') + ship = Ship.new(name: "The Vile Insanity") + pirate = ship.build_pirate(catchphrase: "Arr") pirate.cancel_save_from_callback = true - assert_no_difference 'Ship.count' do - assert_no_difference 'Pirate.count' do + assert_no_difference "Ship.count" do + assert_no_difference "Pirate.count" do assert !ship.save end end @@ -1343,14 +1343,14 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase def test_should_rollback_any_changes_if_an_exception_occurred_while_saving before = [@ship.pirate.catchphrase, @ship.name] - @ship.pirate.catchphrase = 'Arr' - @ship.name = 'The Vile Insanity' + @ship.pirate.catchphrase = "Arr" + @ship.name = "The Vile Insanity" # Stub the save method of the @ship.pirate instance to raise an exception class << @ship.pirate def save(*args) super - raise 'Oh noes!' + raise "Oh noes!" end end @@ -1359,13 +1359,13 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase end def test_should_not_load_the_associated_model - assert_queries(1) { @ship.name = 'The Vile Insanity'; @ship.save! } + assert_queries(1) { @ship.name = "The Vile Insanity"; @ship.save! } end end module AutosaveAssociationOnACollectionAssociationTests def test_should_automatically_save_the_associated_models - new_names = ['Grace OMalley', 'Privateers Greed'] + new_names = ["Grace OMalley", "Privateers Greed"] @pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] } @pirate.save @@ -1373,7 +1373,7 @@ module AutosaveAssociationOnACollectionAssociationTests end def test_should_automatically_save_bang_the_associated_models - new_names = ['Grace OMalley', 'Privateers Greed'] + new_names = ["Grace OMalley", "Privateers Greed"] @pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] } @pirate.save! @@ -1391,7 +1391,7 @@ module AutosaveAssociationOnACollectionAssociationTests end def test_should_automatically_validate_the_associated_models - @pirate.send(@association_name).each { |child| child.name = '' } + @pirate.send(@association_name).each { |child| child.name = "" } assert !@pirate.valid? assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"] @@ -1399,7 +1399,7 @@ module AutosaveAssociationOnACollectionAssociationTests end def test_should_not_use_default_invalid_error_on_associated_models - @pirate.send(@association_name).build(:name => '') + @pirate.send(@association_name).build(name: "") assert !@pirate.valid? assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"] @@ -1407,11 +1407,11 @@ module AutosaveAssociationOnACollectionAssociationTests end def test_should_default_invalid_error_from_i18n - I18n.backend.store_translations(:en, activerecord: {errors: { models: + I18n.backend.store_translations(:en, activerecord: { errors: { models: { @associated_model_name.to_s.to_sym => { blank: "cannot be blank" } } - }}) + } }) - @pirate.send(@association_name).build(name: '') + @pirate.send(@association_name).build(name: "") assert !@pirate.valid? assert_equal ["cannot be blank"], @pirate.errors["#{@association_name}.name"] @@ -1422,7 +1422,7 @@ module AutosaveAssociationOnACollectionAssociationTests end def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid - @pirate.send(@association_name).each { |child| child.name = '' } + @pirate.send(@association_name).each { |child| child.name = "" } @pirate.catchphrase = nil assert !@pirate.valid? @@ -1431,10 +1431,10 @@ module AutosaveAssociationOnACollectionAssociationTests end def test_should_allow_to_bypass_validations_on_the_associated_models_on_update - @pirate.catchphrase = '' - @pirate.send(@association_name).each { |child| child.name = '' } + @pirate.catchphrase = "" + @pirate.send(@association_name).each { |child| child.name = "" } - assert @pirate.save(:validate => false) + assert @pirate.save(validate: false) # Oracle saves empty string as NULL if current_adapter?(:OracleAdapter) assert_equal [nil, nil, nil], [ @@ -1443,7 +1443,7 @@ module AutosaveAssociationOnACollectionAssociationTests @pirate.send(@association_name).last.name ] else - assert_equal ['', '', ''], [ + assert_equal ["", "", ""], [ @pirate.reload.catchphrase, @pirate.send(@association_name).first.name, @pirate.send(@association_name).last.name @@ -1461,24 +1461,24 @@ module AutosaveAssociationOnACollectionAssociationTests def test_should_allow_to_bypass_validations_on_the_associated_models_on_create assert_difference("#{ @association_name == :birds ? 'Bird' : 'Parrot' }.count", 2) do 2.times { @pirate.send(@association_name).build } - @pirate.save(:validate => false) + @pirate.save(validate: false) end end def test_should_not_save_and_return_false_if_a_callback_cancelled_saving_in_either_create_or_update - @pirate.catchphrase = 'Changed' - @child_1.name = 'Changed' + @pirate.catchphrase = "Changed" + @child_1.name = "Changed" @child_1.cancel_save_from_callback = true assert !@pirate.save assert_equal "Don' botharrr talkin' like one, savvy?", @pirate.reload.catchphrase assert_equal "Posideons Killer", @child_1.reload.name - new_pirate = Pirate.new(:catchphrase => 'Arr') - new_child = new_pirate.send(@association_name).build(:name => 'Grace OMalley') + new_pirate = Pirate.new(catchphrase: "Arr") + new_child = new_pirate.send(@association_name).build(name: "Grace OMalley") new_child.cancel_save_from_callback = true - assert_no_difference 'Pirate.count' do + assert_no_difference "Pirate.count" do assert_no_difference "#{new_child.class.name}.count" do assert !new_pirate.save end @@ -1487,16 +1487,16 @@ module AutosaveAssociationOnACollectionAssociationTests def test_should_rollback_any_changes_if_an_exception_occurred_while_saving before = [@pirate.catchphrase, *@pirate.send(@association_name).map(&:name)] - new_names = ['Grace OMalley', 'Privateers Greed'] + new_names = ["Grace OMalley", "Privateers Greed"] - @pirate.catchphrase = 'Arr' + @pirate.catchphrase = "Arr" @pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] } # Stub the save method of the first child instance to raise an exception class << @pirate.send(@association_name).first def save(*args) super - raise 'Oh noes!' + raise "Oh noes!" end end @@ -1505,20 +1505,20 @@ module AutosaveAssociationOnACollectionAssociationTests end def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that - @pirate.send(@association_name).each { |child| child.name = '' } + @pirate.send(@association_name).each { |child| child.name = "" } assert_raise(ActiveRecord::RecordInvalid) do @pirate.save! end end def test_should_not_load_the_associated_models_if_they_were_not_loaded_yet - assert_queries(1) { @pirate.catchphrase = 'Arr'; @pirate.save! } + assert_queries(1) { @pirate.catchphrase = "Arr"; @pirate.save! } @pirate.send(@association_name).load_target assert_queries(3) do - @pirate.catchphrase = 'Yarr' - new_names = ['Grace OMalley', 'Privateers Greed'] + @pirate.catchphrase = "Yarr" + new_names = ["Grace OMalley", "Privateers Greed"] @pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] } @pirate.save! end @@ -1533,9 +1533,9 @@ class TestAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase @association_name = :birds @associated_model_name = :bird - @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") - @child_1 = @pirate.birds.create(:name => 'Posideons Killer') - @child_2 = @pirate.birds.create(:name => 'Killer bandita Dionne') + @pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?") + @child_1 = @pirate.birds.create(name: "Posideons Killer") + @child_2 = @pirate.birds.create(name: "Killer bandita Dionne") end include AutosaveAssociationOnACollectionAssociationTests @@ -1551,8 +1551,8 @@ class TestAutosaveAssociationOnAHasAndBelongsToManyAssociation < ActiveRecord::T @habtm = true @pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?") - @child_1 = @pirate.parrots.create(name: 'Posideons Killer') - @child_2 = @pirate.parrots.create(name: 'Killer bandita Dionne') + @child_1 = @pirate.parrots.create(name: "Posideons Killer") + @child_2 = @pirate.parrots.create(name: "Killer bandita Dionne") end include AutosaveAssociationOnACollectionAssociationTests @@ -1568,8 +1568,8 @@ class TestAutosaveAssociationOnAHasAndBelongsToManyAssociationWithAcceptsNestedA @habtm = true @pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?") - @child_1 = @pirate.parrots.create(name: 'Posideons Killer') - @child_2 = @pirate.parrots.create(name: 'Killer bandita Dionne') + @child_1 = @pirate.parrots.create(name: "Posideons Killer") + @child_2 = @pirate.parrots.create(name: "Killer bandita Dionne") end include AutosaveAssociationOnACollectionAssociationTests @@ -1580,13 +1580,13 @@ class TestAutosaveAssociationValidationsOnAHasManyAssociation < ActiveRecord::Te def setup super - @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") - @pirate.birds.create(:name => 'cookoo') + @pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?") + @pirate.birds.create(name: "cookoo") end test "should automatically validate associations" do assert @pirate.valid? - @pirate.birds.each { |bird| bird.name = '' } + @pirate.birds.each { |bird| bird.name = "" } assert !@pirate.valid? end @@ -1597,20 +1597,20 @@ class TestAutosaveAssociationValidationsOnAHasOneAssociation < ActiveRecord::Tes def setup super - @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") - @pirate.create_ship(:name => 'titanic') + @pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?") + @pirate.create_ship(name: "titanic") super end test "should automatically validate associations with :validate => true" do assert @pirate.valid? - @pirate.ship.name = '' + @pirate.ship.name = "" assert !@pirate.valid? end test "should not automatically add validate associations without :validate => true" do assert @pirate.valid? - @pirate.non_validated_ship.name = '' + @pirate.non_validated_ship.name = "" assert @pirate.valid? end end @@ -1620,18 +1620,18 @@ class TestAutosaveAssociationValidationsOnABelongsToAssociation < ActiveRecord:: def setup super - @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") + @pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?") end test "should automatically validate associations with :validate => true" do assert @pirate.valid? - @pirate.parrot = Parrot.new(:name => '') + @pirate.parrot = Parrot.new(name: "") assert !@pirate.valid? end test "should not automatically validate associations without :validate => true" do assert @pirate.valid? - @pirate.non_validated_parrot = Parrot.new(:name => '') + @pirate.non_validated_parrot = Parrot.new(name: "") assert @pirate.valid? end end @@ -1641,20 +1641,20 @@ class TestAutosaveAssociationValidationsOnAHABTMAssociation < ActiveRecord::Test def setup super - @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") + @pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?") end test "should automatically validate associations with :validate => true" do assert @pirate.valid? - @pirate.parrots = [ Parrot.new(:name => 'popuga') ] - @pirate.parrots.each { |parrot| parrot.name = '' } + @pirate.parrots = [ Parrot.new(name: "popuga") ] + @pirate.parrots.each { |parrot| parrot.name = "" } assert !@pirate.valid? end test "should not automatically validate associations without :validate => true" do assert @pirate.valid? - @pirate.non_validated_parrots = [ Parrot.new(:name => 'popuga') ] - @pirate.non_validated_parrots.each { |parrot| parrot.name = '' } + @pirate.non_validated_parrots = [ Parrot.new(name: "popuga") ] + @pirate.non_validated_parrots.each { |parrot| parrot.name = "" } assert @pirate.valid? end end @@ -1695,6 +1695,6 @@ end class TestAutosaveAssociationWithTouch < ActiveRecord::TestCase def test_autosave_with_touch_should_not_raise_system_stack_error invoice = Invoice.create - assert_nothing_raised { invoice.line_items.create(:amount => 10) } + assert_nothing_raised { invoice.line_items.create(amount: 10) } end end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index eef2d29d02..fafa144c6f 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1,32 +1,30 @@ require "cases/helper" -require 'models/post' -require 'models/author' -require 'models/topic' -require 'models/reply' -require 'models/category' -require 'models/company' -require 'models/customer' -require 'models/developer' -require 'models/computer' -require 'models/project' -require 'models/default' -require 'models/auto_id' -require 'models/boolean' -require 'models/column_name' -require 'models/subscriber' -require 'models/keyboard' -require 'models/comment' -require 'models/minimalistic' -require 'models/warehouse_thing' -require 'models/parrot' -require 'models/person' -require 'models/edge' -require 'models/joke' -require 'models/bird' -require 'models/car' -require 'models/bulb' -require 'rexml/document' -require 'concurrent/atomic/count_down_latch' +require "models/post" +require "models/author" +require "models/topic" +require "models/reply" +require "models/category" +require "models/company" +require "models/customer" +require "models/developer" +require "models/computer" +require "models/project" +require "models/default" +require "models/auto_id" +require "models/boolean" +require "models/column_name" +require "models/subscriber" +require "models/comment" +require "models/minimalistic" +require "models/warehouse_thing" +require "models/parrot" +require "models/person" +require "models/edge" +require "models/joke" +require "models/bird" +require "models/car" +require "models/bulb" +require "concurrent/atomic/count_down_latch" class FirstAbstractClass < ActiveRecord::Base self.abstract_class = true @@ -75,17 +73,17 @@ class LintTest < ActiveRecord::TestCase end class BasicsTest < ActiveRecord::TestCase - fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts + fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, "warehouse-things", :authors, :categorizations, :categories, :posts def test_column_names_are_escaped conn = ActiveRecord::Base.connection classname = conn.class.name[/[^:]*$/] badchar = { - 'SQLite3Adapter' => '"', - 'Mysql2Adapter' => '`', - 'PostgreSQLAdapter' => '"', - 'OracleAdapter' => '"', - 'FbAdapter' => '"' + "SQLite3Adapter" => '"', + "Mysql2Adapter" => "`", + "PostgreSQLAdapter" => '"', + "OracleAdapter" => '"', + "FbAdapter" => '"' }.fetch(classname) { raise "need a bad char for #{classname}" } @@ -102,7 +100,7 @@ class BasicsTest < ActiveRecord::TestCase def test_columns_should_obey_set_primary_key pk = Subscriber.columns_hash[Subscriber.primary_key] - assert_equal 'nick', pk.name, 'nick should be primary key' + assert_equal "nick", pk.name, "nick should be primary key" end def test_primary_key_with_no_id @@ -117,6 +115,13 @@ class BasicsTest < ActiveRecord::TestCase end end + def test_many_mutations + car = Car.new name: "<3<3<3" + car.engines_count = 0 + 20_000.times { car.engines_count += 1 } + assert car.save + end + def test_limit_without_comma assert_equal 1, Topic.limit("1").to_a.length assert_equal 1, Topic.limit(1).to_a.length @@ -166,17 +171,17 @@ class BasicsTest < ActiveRecord::TestCase def test_previously_changed topic = Topic.first - topic.title = '<3<3<3' + topic.title = "<3<3<3" assert_equal({}, topic.previous_changes) topic.save! expected = ["The First Topic", "<3<3<3"] - assert_equal(expected, topic.previous_changes['title']) + assert_equal(expected, topic.previous_changes["title"]) end def test_previously_changed_dup topic = Topic.first - topic.title = '<3<3<3' + topic.title = "<3<3<3" topic.save! t2 = topic.dup @@ -213,7 +218,7 @@ class BasicsTest < ActiveRecord::TestCase with_env_tz eastern_time_zone do with_timezone_config default: :utc do time = Time.local(2000) - topic = Topic.create('written_on' => time) + topic = Topic.create("written_on" => time) saved_time = Topic.find(topic.id).reload.written_on assert_equal time, saved_time assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "EST"], time.to_a @@ -225,9 +230,9 @@ class BasicsTest < ActiveRecord::TestCase def test_preserving_time_objects_with_time_with_zone_conversion_to_default_timezone_utc with_env_tz eastern_time_zone do with_timezone_config default: :utc do - Time.use_zone 'Central Time (US & Canada)' do + Time.use_zone "Central Time (US & Canada)" do time = Time.zone.local(2000) - topic = Topic.create('written_on' => time) + topic = Topic.create("written_on" => time) saved_time = Topic.find(topic.id).reload.written_on assert_equal time, saved_time assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "CST"], time.to_a @@ -241,7 +246,7 @@ class BasicsTest < ActiveRecord::TestCase with_env_tz eastern_time_zone do with_timezone_config default: :local do time = Time.utc(2000) - topic = Topic.create('written_on' => time) + topic = Topic.create("written_on" => time) saved_time = Topic.find(topic.id).reload.written_on assert_equal time, saved_time assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "UTC"], time.to_a @@ -253,9 +258,9 @@ class BasicsTest < ActiveRecord::TestCase def test_preserving_time_objects_with_time_with_zone_conversion_to_default_timezone_local with_env_tz eastern_time_zone do with_timezone_config default: :local do - Time.use_zone 'Central Time (US & Canada)' do + Time.use_zone "Central Time (US & Canada)" do time = Time.zone.local(2000) - topic = Topic.create('written_on' => time) + topic = Topic.create("written_on" => time) saved_time = Topic.find(topic.id).reload.written_on assert_equal time, saved_time assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "CST"], time.to_a @@ -281,49 +286,48 @@ class BasicsTest < ActiveRecord::TestCase end def test_initialize_with_attributes - topic = Topic.new({ - "title" => "initialized from attributes", "written_on" => "2003-12-12 23:23" - }) + topic = Topic.new( + "title" => "initialized from attributes", "written_on" => "2003-12-12 23:23") assert_equal("initialized from attributes", topic.title) end def test_initialize_with_invalid_attribute - Topic.new({ "title" => "test", - "last_read(1i)" => "2005", "last_read(2i)" => "2", "last_read(3i)" => "31"}) + Topic.new("title" => "test", + "last_read(1i)" => "2005", "last_read(2i)" => "2", "last_read(3i)" => "31") rescue ActiveRecord::MultiparameterAssignmentErrors => ex assert_equal(1, ex.errors.size) assert_equal("last_read", ex.errors[0].attribute) end def test_create_after_initialize_without_block - cb = CustomBulb.create(:name => 'Dude') - assert_equal('Dude', cb.name) + cb = CustomBulb.create(name: "Dude") + assert_equal("Dude", cb.name) assert_equal(true, cb.frickinawesome) end def test_create_after_initialize_with_block - cb = CustomBulb.create {|c| c.name = 'Dude' } - assert_equal('Dude', cb.name) + cb = CustomBulb.create { |c| c.name = "Dude" } + assert_equal("Dude", cb.name) assert_equal(true, cb.frickinawesome) end def test_create_after_initialize_with_array_param - cbs = CustomBulb.create([{ name: 'Dude' }, { name: 'Bob' }]) - assert_equal 'Dude', cbs[0].name - assert_equal 'Bob', cbs[1].name + cbs = CustomBulb.create([{ name: "Dude" }, { name: "Bob" }]) + assert_equal "Dude", cbs[0].name + assert_equal "Bob", cbs[1].name assert cbs[0].frickinawesome assert !cbs[1].frickinawesome end def test_load - topics = Topic.all.merge!(:order => 'id').to_a + topics = Topic.all.merge!(order: "id").to_a assert_equal(5, topics.size) assert_equal(topics(:first).title, topics.first.title) end def test_load_with_condition - topics = Topic.all.merge!(:where => "author_name = 'Mary'").to_a + topics = Topic.all.merge!(where: "author_name = 'Mary'").to_a assert_equal(1, topics.size) assert_equal(topics(:second).title, topics.first.title) @@ -445,7 +449,7 @@ class BasicsTest < ActiveRecord::TestCase if current_adapter?(:Mysql2Adapter) def test_update_all_with_order_and_limit - assert_equal 1, Topic.limit(1).order('id DESC').update_all(:content => 'bulk updated!') + assert_equal 1, Topic.limit(1).order("id DESC").update_all(content: "bulk updated!") end end @@ -520,16 +524,16 @@ class BasicsTest < ActiveRecord::TestCase end def test_find_by_slug - assert_equal Topic.find('1-meowmeow'), Topic.find(1) + assert_equal Topic.find("1-meowmeow"), Topic.find(1) end def test_find_by_slug_with_array - assert_equal Topic.find([1, 2]), Topic.find(['1-meowmeow', '2-hello']) - assert_equal 'The Second Topic of the day', Topic.find(['2-hello', '1-meowmeow']).first.title + assert_equal Topic.find([1, 2]), Topic.find(["1-meowmeow", "2-hello"]) + assert_equal "The Second Topic of the day", Topic.find(["2-hello", "1-meowmeow"]).first.title end def test_find_by_slug_with_range - assert_equal Topic.where(id: '1-meowmeow'..'2-hello'), Topic.where(id: 1..2) + assert_equal Topic.where(id: "1-meowmeow".."2-hello"), Topic.where(id: 1..2) end def test_equality_of_new_records @@ -538,7 +542,7 @@ class BasicsTest < ActiveRecord::TestCase end def test_equality_of_destroyed_records - topic_1 = Topic.new(:title => 'test_1') + topic_1 = Topic.new(title: "test_1") topic_1.save topic_2 = Topic.find(topic_1.id) topic_1.destroy @@ -547,8 +551,8 @@ class BasicsTest < ActiveRecord::TestCase end def test_equality_with_blank_ids - one = Subscriber.new(:id => '') - two = Subscriber.new(:id => '') + one = Subscriber.new(id: "") + two = Subscriber.new(id: "") assert_equal one, two end @@ -557,8 +561,8 @@ class BasicsTest < ActiveRecord::TestCase car.bulbs.build car.save - assert car.bulbs == Bulb.where(car_id: car.id), 'CollectionProxy should be comparable with Relation' - assert Bulb.where(car_id: car.id) == car.bulbs, 'Relation should be comparable with CollectionProxy' + assert car.bulbs == Bulb.where(car_id: car.id), "CollectionProxy should be comparable with Relation" + assert Bulb.where(car_id: car.id) == car.bulbs, "Relation should be comparable with CollectionProxy" end def test_equality_of_relation_and_array @@ -566,7 +570,7 @@ class BasicsTest < ActiveRecord::TestCase car.bulbs.build car.save - assert Bulb.where(car_id: car.id) == car.bulbs.to_a, 'Relation should be comparable with Array' + assert Bulb.where(car_id: car.id) == car.bulbs.to_a, "Relation should be comparable with Array" end def test_equality_of_relation_and_association_relation @@ -574,8 +578,8 @@ class BasicsTest < ActiveRecord::TestCase car.bulbs.build car.save - assert_equal Bulb.where(car_id: car.id), car.bulbs.includes(:car), 'Relation should be comparable with AssociationRelation' - assert_equal car.bulbs.includes(:car), Bulb.where(car_id: car.id), 'AssociationRelation should be comparable with Relation' + assert_equal Bulb.where(car_id: car.id), car.bulbs.includes(:car), "Relation should be comparable with AssociationRelation" + assert_equal car.bulbs.includes(:car), Bulb.where(car_id: car.id), "AssociationRelation should be comparable with Relation" end def test_equality_of_collection_proxy_and_association_relation @@ -583,8 +587,8 @@ class BasicsTest < ActiveRecord::TestCase car.bulbs.build car.save - assert_equal car.bulbs, car.bulbs.includes(:car), 'CollectionProxy should be comparable with AssociationRelation' - assert_equal car.bulbs.includes(:car), car.bulbs, 'AssociationRelation should be comparable with CollectionProxy' + assert_equal car.bulbs, car.bulbs.includes(:car), "CollectionProxy should be comparable with AssociationRelation" + assert_equal car.bulbs.includes(:car), car.bulbs, "AssociationRelation should be comparable with CollectionProxy" end def test_hashing @@ -606,14 +610,14 @@ class BasicsTest < ActiveRecord::TestCase def test_create_without_prepared_statement topic = Topic.connection.unprepared_statement do - Topic.create(:title => 'foo') + Topic.create(title: "foo") end assert_equal topic, Topic.find(topic.id) end def test_destroy_without_prepared_statement - topic = Topic.create(title: 'foo') + topic = Topic.create(title: "foo") Topic.connection.unprepared_statement do Topic.find(topic.id).destroy end @@ -623,7 +627,7 @@ class BasicsTest < ActiveRecord::TestCase def test_comparison_with_different_objects topic = Topic.create - category = Category.create(:name => "comparison") + category = Category.create(name: "comparison") assert_nil topic <=> category end @@ -635,9 +639,9 @@ class BasicsTest < ActiveRecord::TestCase end def test_readonly_attributes - assert_equal Set.new([ 'title' , 'comments_count' ]), ReadonlyTitlePost.readonly_attributes + assert_equal Set.new([ "title" , "comments_count" ]), ReadonlyTitlePost.readonly_attributes - post = ReadonlyTitlePost.create(:title => "cannot change this", :body => "changeable") + post = ReadonlyTitlePost.create(title: "cannot change this", body: "changeable") post.reload assert_equal "cannot change this", post.title @@ -649,8 +653,8 @@ class BasicsTest < ActiveRecord::TestCase def test_unicode_column_name Weird.reset_column_information - weird = Weird.create(:ãªã¾ãˆ => 'ãŸã“焼ãä»®é¢') - assert_equal 'ãŸã“焼ãä»®é¢', weird.ãªã¾ãˆ + weird = Weird.create(ãªã¾ãˆ: "ãŸã“焼ãä»®é¢") + assert_equal "ãŸã“焼ãä»®é¢", weird.ãªã¾ãˆ end unless current_adapter?(:PostgreSQLAdapter) @@ -660,7 +664,7 @@ class BasicsTest < ActiveRecord::TestCase Weird.reset_column_information - assert_equal ["EUC-JP"], Weird.columns.map {|c| c.name.encoding.name }.uniq + assert_equal ["EUC-JP"], Weird.columns.map { |c| c.name.encoding.name }.uniq ensure silence_warnings { Encoding.default_internal = old_default_internal } Weird.reset_column_information @@ -668,21 +672,21 @@ class BasicsTest < ActiveRecord::TestCase end def test_non_valid_identifier_column_name - weird = Weird.create('a$b' => 'value') + weird = Weird.create("a$b" => "value") weird.reload - assert_equal 'value', weird.send('a$b') - assert_equal 'value', weird.read_attribute('a$b') + assert_equal "value", weird.send("a$b") + assert_equal "value", weird.read_attribute("a$b") - weird.update_columns('a$b' => 'value2') + weird.update_columns("a$b" => "value2") weird.reload - assert_equal 'value2', weird.send('a$b') - assert_equal 'value2', weird.read_attribute('a$b') + assert_equal "value2", weird.send("a$b") + assert_equal "value2", weird.read_attribute("a$b") end def test_group_weirds_by_from - Weird.create('a$b' => 'value', :from => 'aaron') + Weird.create("a$b" => "value", :from => "aaron") count = Weird.group(Weird.arel_table[:from]).count - assert_equal 1, count['aaron'] + assert_equal 1, count["aaron"] end def test_attributes_on_dummy_time @@ -712,11 +716,11 @@ class BasicsTest < ActiveRecord::TestCase end def test_boolean - b_nil = Boolean.create({ "value" => nil }) + b_nil = Boolean.create("value" => nil) nil_id = b_nil.id - b_false = Boolean.create({ "value" => false }) + b_false = Boolean.create("value" => false) false_id = b_false.id - b_true = Boolean.create({ "value" => true }) + b_true = Boolean.create("value" => true) true_id = b_true.id b_nil = Boolean.find(nil_id) @@ -728,7 +732,7 @@ class BasicsTest < ActiveRecord::TestCase end def test_boolean_without_questionmark - b_true = Boolean.create({ "value" => true }) + b_true = Boolean.create("value" => true) true_id = b_true.id subclass = Class.new(Boolean).find true_id @@ -738,11 +742,11 @@ class BasicsTest < ActiveRecord::TestCase end def test_boolean_cast_from_string - b_blank = Boolean.create({ "value" => "" }) + b_blank = Boolean.create("value" => "") blank_id = b_blank.id - b_false = Boolean.create({ "value" => "0" }) + b_false = Boolean.create("value" => "0") false_id = b_false.id - b_true = Boolean.create({ "value" => "1" }) + b_true = Boolean.create("value" => "1") true_id = b_true.id b_blank = Boolean.find(blank_id) @@ -791,8 +795,8 @@ class BasicsTest < ActiveRecord::TestCase DeveloperSalary = Struct.new(:amount) def test_dup_with_aggregate_of_same_name_as_attribute developer_with_aggregate = Class.new(ActiveRecord::Base) do - self.table_name = 'developers' - composed_of :salary, :class_name => 'BasicsTest::DeveloperSalary', :mapping => [%w(salary amount)] + self.table_name = "developers" + composed_of :salary, class_name: "BasicsTest::DeveloperSalary", mapping: [%w(salary amount)] end dev = developer_with_aggregate.find(1) @@ -839,7 +843,7 @@ class BasicsTest < ActiveRecord::TestCase end def test_clone_of_new_object_marks_attributes_as_dirty - developer = Developer.new :name => 'Bjorn', :salary => 100000 + developer = Developer.new name: "Bjorn", salary: 100000 assert developer.name_changed? assert developer.salary_changed? @@ -849,7 +853,7 @@ class BasicsTest < ActiveRecord::TestCase end def test_clone_of_new_object_marks_as_dirty_only_changed_attributes - developer = Developer.new :name => 'Bjorn' + developer = Developer.new name: "Bjorn" assert developer.name_changed? # obviously assert !developer.salary_changed? # attribute has non-nil default value, so treated as not changed @@ -859,7 +863,7 @@ class BasicsTest < ActiveRecord::TestCase end def test_dup_of_saved_object_marks_attributes_as_dirty - developer = Developer.create! :name => 'Bjorn', :salary => 100000 + developer = Developer.create! name: "Bjorn", salary: 100000 assert !developer.name_changed? assert !developer.salary_changed? @@ -869,7 +873,7 @@ class BasicsTest < ActiveRecord::TestCase end def test_dup_of_saved_object_marks_as_dirty_only_changed_attributes - developer = Developer.create! :name => 'Bjorn' + developer = Developer.create! name: "Bjorn" assert !developer.name_changed? # both attributes of saved object should be treated as not changed assert !developer.salary_changed? @@ -897,15 +901,15 @@ class BasicsTest < ActiveRecord::TestCase assert_equal Time.local(2004, 1,1,0,0,0,0), default.fixed_time # char types - assert_equal 'Y', default.char1 - assert_equal 'a varchar field', default.char2 - assert_equal 'a text field', default.char3 + assert_equal "Y", default.char1 + assert_equal "a varchar field", default.char2 + assert_equal "a text field", default.char3 end end end class NumericData < ActiveRecord::Base - self.table_name = 'numeric_data' + self.table_name = "numeric_data" attribute :my_house_population, :integer attribute :atoms_in_universe, :integer @@ -913,10 +917,10 @@ class BasicsTest < ActiveRecord::TestCase def test_big_decimal_conditions m = NumericData.new( - :bank_balance => 1586.43, - :big_bank_balance => BigDecimal("1000234000567.95"), - :world_population => 6000000000, - :my_house_population => 3 + bank_balance: 1586.43, + big_bank_balance: BigDecimal("1000234000567.95"), + world_population: 6000000000, + my_house_population: 3 ) assert m.save assert_equal 0, NumericData.where("bank_balance > ?", 2000.0).count @@ -924,10 +928,10 @@ class BasicsTest < ActiveRecord::TestCase def test_numeric_fields m = NumericData.new( - :bank_balance => 1586.43, - :big_bank_balance => BigDecimal("1000234000567.95"), - :world_population => 6000000000, - :my_house_population => 3 + bank_balance: 1586.43, + big_bank_balance: BigDecimal("1000234000567.95"), + world_population: 6000000000, + my_house_population: 3 ) assert m.save @@ -940,7 +944,7 @@ class BasicsTest < ActiveRecord::TestCase assert_kind_of Integer, m1.world_population assert_equal 6000000000, m1.world_population - assert_kind_of Fixnum, m1.my_house_population + assert_kind_of Integer, m1.my_house_population assert_equal 3, m1.my_house_population assert_kind_of BigDecimal, m1.bank_balance @@ -952,10 +956,10 @@ class BasicsTest < ActiveRecord::TestCase def test_numeric_fields_with_scale m = NumericData.new( - :bank_balance => 1586.43122334, - :big_bank_balance => BigDecimal("234000567.952344"), - :world_population => 6000000000, - :my_house_population => 3 + bank_balance: 1586.43122334, + big_bank_balance: BigDecimal("234000567.952344"), + world_population: 6000000000, + my_house_population: 3 ) assert m.save @@ -968,7 +972,7 @@ class BasicsTest < ActiveRecord::TestCase assert_kind_of Integer, m1.world_population assert_equal 6000000000, m1.world_population - assert_kind_of Fixnum, m1.my_house_population + assert_kind_of Integer, m1.my_house_population assert_equal 3, m1.my_house_population assert_kind_of BigDecimal, m1.bank_balance @@ -1001,16 +1005,16 @@ class BasicsTest < ActiveRecord::TestCase end def test_quoting_arrays - replies = Reply.all.merge!(:where => [ "id IN (?)", topics(:first).replies.collect(&:id) ]).to_a + replies = Reply.all.merge!(where: [ "id IN (?)", topics(:first).replies.collect(&:id) ]).to_a assert_equal topics(:first).replies.size, replies.size - replies = Reply.all.merge!(:where => [ "id IN (?)", [] ]).to_a + replies = Reply.all.merge!(where: [ "id IN (?)", [] ]).to_a assert_equal 0, replies.size end def test_quote author_name = "\\ \001 ' \n \\n \"" - topic = Topic.create('author_name' => author_name) + topic = Topic.create("author_name" => author_name) assert_equal author_name, Topic.find(topic.id).author_name end @@ -1034,12 +1038,6 @@ class BasicsTest < ActiveRecord::TestCase assert_equal t1.title, t2.title end - def test_reload_with_exclusive_scope - dev = DeveloperCalledDavid.first - dev.update!(name: "NotDavid" ) - assert_equal dev, dev.reload - end - def test_switching_between_table_name k = Class.new(Joke) @@ -1095,7 +1093,7 @@ class BasicsTest < ActiveRecord::TestCase def test_set_table_name_symbol_converted_to_string k = Class.new(Joke) k.table_name = :cold_jokes - assert_equal 'cold_jokes', k.table_name + assert_equal "cold_jokes", k.table_name end def test_quoted_table_name_after_set_table_name @@ -1157,17 +1155,17 @@ class BasicsTest < ActiveRecord::TestCase def test_no_limit_offset assert_nothing_raised do - Developer.all.merge!(:offset => 2).to_a + Developer.all.merge!(offset: 2).to_a end end def test_find_last last = Developer.last - assert_equal last, Developer.all.merge!(:order => 'id desc').first + assert_equal last, Developer.all.merge!(order: "id desc").first end def test_last - assert_equal Developer.all.merge!(:order => 'id desc').first, Developer.last + assert_equal Developer.all.merge!(order: "id desc").first, Developer.last end def test_all @@ -1177,37 +1175,37 @@ class BasicsTest < ActiveRecord::TestCase end def test_all_with_conditions - assert_equal Developer.all.merge!(:order => 'id desc').to_a, Developer.order('id desc').to_a + assert_equal Developer.all.merge!(order: "id desc").to_a, Developer.order("id desc").to_a end def test_find_ordered_last - last = Developer.all.merge!(:order => 'developers.salary ASC').last - assert_equal last, Developer.all.merge!(:order => 'developers.salary ASC').to_a.last + last = Developer.all.merge!(order: "developers.salary ASC").last + assert_equal last, Developer.all.merge!(order: "developers.salary ASC").to_a.last end def test_find_reverse_ordered_last - last = Developer.all.merge!(:order => 'developers.salary DESC').last - assert_equal last, Developer.all.merge!(:order => 'developers.salary DESC').to_a.last + last = Developer.all.merge!(order: "developers.salary DESC").last + assert_equal last, Developer.all.merge!(order: "developers.salary DESC").to_a.last end def test_find_multiple_ordered_last - last = Developer.all.merge!(:order => 'developers.name, developers.salary DESC').last - assert_equal last, Developer.all.merge!(:order => 'developers.name, developers.salary DESC').to_a.last + last = Developer.all.merge!(order: "developers.name, developers.salary DESC").last + assert_equal last, Developer.all.merge!(order: "developers.name, developers.salary DESC").to_a.last end def test_find_keeps_multiple_order_values - combined = Developer.all.merge!(:order => 'developers.name, developers.salary').to_a - assert_equal combined, Developer.all.merge!(:order => ['developers.name', 'developers.salary']).to_a + combined = Developer.all.merge!(order: "developers.name, developers.salary").to_a + assert_equal combined, Developer.all.merge!(order: ["developers.name", "developers.salary"]).to_a end def test_find_keeps_multiple_group_values - combined = Developer.all.merge!(:group => 'developers.name, developers.salary, developers.id, developers.created_at, developers.updated_at, developers.created_on, developers.updated_on').to_a - assert_equal combined, Developer.all.merge!(:group => ['developers.name', 'developers.salary', 'developers.id', 'developers.created_at', 'developers.updated_at', 'developers.created_on', 'developers.updated_on']).to_a + combined = Developer.all.merge!(group: "developers.name, developers.salary, developers.id, developers.created_at, developers.updated_at, developers.created_on, developers.updated_on").to_a + assert_equal combined, Developer.all.merge!(group: ["developers.name", "developers.salary", "developers.id", "developers.created_at", "developers.updated_at", "developers.created_on", "developers.updated_on"]).to_a end def test_find_symbol_ordered_last - last = Developer.all.merge!(:order => :salary).last - assert_equal last, Developer.all.merge!(:order => :salary).to_a.last + last = Developer.all.merge!(order: :salary).last + assert_equal last, Developer.all.merge!(order: :salary).to_a.last end def test_abstract_class_table_name @@ -1218,7 +1216,7 @@ class BasicsTest < ActiveRecord::TestCase old_class = LooseDescendant Object.send :remove_const, :LooseDescendant - descendant = old_class.create! :first_name => 'bob' + descendant = old_class.create! first_name: "bob" assert_not_nil LoosePerson.find(descendant.id), "Should have found instance of LooseDescendant when finding abstract LoosePerson: #{descendant.inspect}" ensure unless Object.const_defined?(:LooseDescendant) @@ -1227,7 +1225,7 @@ class BasicsTest < ActiveRecord::TestCase end def test_assert_queries - query = lambda { ActiveRecord::Base.connection.execute 'select count(*) from developers' } + query = lambda { ActiveRecord::Base.connection.execute "select count(*) from developers" } assert_queries(2) { 2.times { query.call } } assert_queries 1, &query assert_no_queries { assert true } @@ -1238,9 +1236,9 @@ class BasicsTest < ActiveRecord::TestCase log = StringIO.new ActiveRecord::Base.logger = ActiveSupport::Logger.new(log) ActiveRecord::Base.logger.level = Logger::WARN - ActiveRecord::Base.benchmark("Debug Topic Count", :level => :debug) { Topic.count } - ActiveRecord::Base.benchmark("Warn Topic Count", :level => :warn) { Topic.count } - ActiveRecord::Base.benchmark("Error Topic Count", :level => :error) { Topic.count } + ActiveRecord::Base.benchmark("Debug Topic Count", level: :debug) { Topic.count } + ActiveRecord::Base.benchmark("Warn Topic Count", level: :warn) { Topic.count } + ActiveRecord::Base.benchmark("Error Topic Count", level: :error) { Topic.count } assert_no_match(/Debug Topic Count/, log.string) assert_match(/Warn Topic Count/, log.string) assert_match(/Error Topic Count/, log.string) @@ -1253,7 +1251,7 @@ class BasicsTest < ActiveRecord::TestCase log = StringIO.new ActiveRecord::Base.logger = ActiveSupport::Logger.new(log) ActiveRecord::Base.logger.level = Logger::DEBUG - ActiveRecord::Base.benchmark("Logging", :level => :debug, :silence => false) { ActiveRecord::Base.logger.debug "Quiet" } + ActiveRecord::Base.benchmark("Logging", level: :debug, silence: false) { ActiveRecord::Base.logger.debug "Quiet" } assert_match(/Quiet/, log.string) ensure ActiveRecord::Base.logger = original_logger @@ -1261,9 +1259,9 @@ class BasicsTest < ActiveRecord::TestCase def test_clear_cache! # preheat cache - c1 = Post.connection.schema_cache.columns('posts') + c1 = Post.connection.schema_cache.columns("posts") ActiveRecord::Base.clear_cache! - c2 = Post.connection.schema_cache.columns('posts') + c2 = Post.connection.schema_cache.columns("posts") c1.each_with_index do |v, i| assert_not_same v, c2[i] end @@ -1280,7 +1278,7 @@ class BasicsTest < ActiveRecord::TestCase ActiveSupport::Dependencies.remove_unloadable_constants! assert_nil ActiveRecord::Scoping::ScopeRegistry.value_for(:current_scope, klass) ensure - Object.class_eval{ remove_const :UnloadablePost } if defined?(UnloadablePost) + Object.class_eval { remove_const :UnloadablePost } if defined?(UnloadablePost) end def test_marshal_round_trip @@ -1354,11 +1352,11 @@ class BasicsTest < ActiveRecord::TestCase end def test_has_attribute - assert Company.has_attribute?('id') - assert Company.has_attribute?('type') - assert Company.has_attribute?('name') - assert_not Company.has_attribute?('lastname') - assert_not Company.has_attribute?('age') + assert Company.has_attribute?("id") + assert Company.has_attribute?("type") + assert Company.has_attribute?("name") + assert_not Company.has_attribute?("lastname") + assert_not Company.has_attribute?("age") end def test_has_attribute_with_symbol @@ -1375,7 +1373,7 @@ class BasicsTest < ActiveRecord::TestCase end def test_touch_should_raise_error_on_a_new_object - company = Company.new(:rating => 1, :name => "37signals", :firm_name => "37signals") + company = Company.new(rating: 1, name: "37signals", firm_name: "37signals") assert_raises(ActiveRecord::ActiveRecordError) do company.touch :updated_at end @@ -1397,37 +1395,37 @@ class BasicsTest < ActiveRecord::TestCase def test_column_types_typecast topic = Topic.first - assert_not_equal 't.lo', topic.author_name + assert_not_equal "t.lo", topic.author_name attrs = topic.attributes.dup - attrs.delete 'id' + attrs.delete "id" typecast = Class.new(ActiveRecord::Type::Value) { - def cast value + def cast(value) "t.lo" end } - types = { 'author_name' => typecast.new } + types = { "author_name" => typecast.new } topic = Topic.instantiate(attrs, types) - assert_equal 't.lo', topic.author_name + assert_equal "t.lo", topic.author_name end def test_typecasting_aliases - assert_equal 10, Topic.select('10 as tenderlove').first.tenderlove + assert_equal 10, Topic.select("10 as tenderlove").first.tenderlove end def test_slice - company = Company.new(:rating => 1, :name => "37signals", :firm_name => "37signals") + company = Company.new(rating: 1, name: "37signals", firm_name: "37signals") hash = company.slice(:name, :rating, "arbitrary_method") assert_equal hash[:name], company.name - assert_equal hash['name'], company.name + assert_equal hash["name"], company.name assert_equal hash[:rating], company.rating - assert_equal hash['arbitrary_method'], company.arbitrary_method + assert_equal hash["arbitrary_method"], company.arbitrary_method assert_equal hash[:arbitrary_method], company.arbitrary_method assert_nil hash[:firm_name] - assert_nil hash['firm_name'] + assert_nil hash["firm_name"] end def test_default_values_are_deeply_dupped @@ -1438,7 +1436,7 @@ class BasicsTest < ActiveRecord::TestCase test "scoped can take a values hash" do klass = Class.new(ActiveRecord::Base) - assert_equal ['foo'], klass.all.merge!(select: 'foo').select_values + assert_equal ["foo"], klass.all.merge!(select: "foo").select_values end test "connection_handler can be overridden" do @@ -1504,6 +1502,10 @@ class BasicsTest < ActiveRecord::TestCase assert_not_equal Post.new.hash, Post.new.hash end + test "records of different classes have different hashes" do + assert_not_equal Post.new(id: 1).hash, Developer.new(id: 1).hash + end + test "resetting column information doesn't remove attribute methods" do topic = topics(:first) @@ -1516,8 +1518,8 @@ class BasicsTest < ActiveRecord::TestCase test "ignored columns are not present in columns_hash" do cache_columns = Developer.connection.schema_cache.columns_hash(Developer.table_name) - assert_includes cache_columns.keys, 'first_name' - refute_includes Developer.columns_hash.keys, 'first_name' + assert_includes cache_columns.keys, "first_name" + assert_not_includes Developer.columns_hash.keys, "first_name" end test "ignored columns have no attribute methods" do diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index 3602ee7ba2..f7e21faf0f 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -1,6 +1,6 @@ -require 'cases/helper' -require 'models/post' -require 'models/subscriber' +require "cases/helper" +require "models/post" +require "models/subscriber" class EachTest < ActiveRecord::TestCase fixtures :posts, :subscribers @@ -8,12 +8,12 @@ class EachTest < ActiveRecord::TestCase def setup @posts = Post.order("id asc") @total = Post.count - Post.count('id') # preheat arel's table cache + Post.count("id") # preheat arel's table cache end def test_each_should_execute_one_query_per_batch assert_queries(@total + 1) do - Post.find_each(:batch_size => 1) do |post| + Post.find_each(batch_size: 1) do |post| assert_kind_of Post, post end end @@ -21,14 +21,14 @@ class EachTest < ActiveRecord::TestCase def test_each_should_not_return_query_chain_and_execute_only_one_query assert_queries(1) do - result = Post.find_each(:batch_size => 100000){ } + result = Post.find_each(batch_size: 100000) {} assert_nil result end end def test_each_should_return_an_enumerator_if_no_block_is_present assert_queries(1) do - Post.find_each(:batch_size => 100000).with_index do |post, index| + Post.find_each(batch_size: 100000).with_index do |post, index| assert_kind_of Post, post assert_kind_of Integer, index end @@ -45,7 +45,7 @@ class EachTest < ActiveRecord::TestCase def test_each_enumerator_should_execute_one_query_per_batch assert_queries(@total + 1) do - Post.find_each(:batch_size => 1).with_index do |post, index| + Post.find_each(batch_size: 1).with_index do |post, index| assert_kind_of Post, post assert_kind_of Integer, index end @@ -62,16 +62,32 @@ class EachTest < ActiveRecord::TestCase def test_each_should_execute_if_id_is_in_select assert_queries(6) do - Post.select("id, title, type").find_each(:batch_size => 2) do |post| + Post.select("id, title, type").find_each(batch_size: 2) do |post| assert_kind_of Post, post end end end - def test_warn_if_limit_scope_is_set - assert_called(ActiveRecord::Base.logger, :warn) do - Post.limit(1).find_each { |post| post } + test "find_each should honor limit if passed a block" do + limit = @total - 1 + total = 0 + + Post.limit(limit).find_each do |post| + total += 1 end + + assert_equal limit, total + end + + test "find_each should honor limit if no block is passed" do + limit = @total - 1 + total = 0 + + Post.limit(limit).find_each.each do |post| + total += 1 + end + + assert_equal limit, total end def test_warn_if_order_scope_is_set @@ -84,7 +100,7 @@ class EachTest < ActiveRecord::TestCase previous_logger = ActiveRecord::Base.logger ActiveRecord::Base.logger = nil assert_nothing_raised do - Post.limit(1).find_each { |post| post } + Post.order("comments_count DESC").find_each { |post| post } end ensure ActiveRecord::Base.logger = previous_logger @@ -92,7 +108,7 @@ class EachTest < ActiveRecord::TestCase def test_find_in_batches_should_return_batches assert_queries(@total + 1) do - Post.find_in_batches(:batch_size => 1) do |batch| + Post.find_in_batches(batch_size: 1) do |batch| assert_kind_of Array, batch assert_kind_of Post, batch.first end @@ -108,7 +124,7 @@ class EachTest < ActiveRecord::TestCase end end - def test_find_in_batches_should_finish_the_end_option + def test_find_in_batches_should_end_at_the_finish_option assert_queries(6) do Post.find_in_batches(batch_size: 1, finish: 5) do |batch| assert_kind_of Array, batch @@ -119,18 +135,18 @@ class EachTest < ActiveRecord::TestCase def test_find_in_batches_shouldnt_execute_query_unless_needed assert_queries(2) do - Post.find_in_batches(:batch_size => @total) {|batch| assert_kind_of Array, batch } + Post.find_in_batches(batch_size: @total) { |batch| assert_kind_of Array, batch } end assert_queries(1) do - Post.find_in_batches(:batch_size => @total + 1) {|batch| assert_kind_of Array, batch } + Post.find_in_batches(batch_size: @total + 1) { |batch| assert_kind_of Array, batch } end end def test_find_in_batches_should_quote_batch_order c = Post.connection assert_sql(/ORDER BY #{c.quote_table_name('posts')}.#{c.quote_column_name('id')}/) do - Post.find_in_batches(:batch_size => 1) do |batch| + Post.find_in_batches(batch_size: 1) do |batch| assert_kind_of Array, batch assert_kind_of Post, batch.first end @@ -140,9 +156,9 @@ class EachTest < ActiveRecord::TestCase def test_find_in_batches_should_not_use_records_after_yielding_them_in_case_original_array_is_modified not_a_post = "not a post" def not_a_post.id; end - not_a_post.stub(:id, ->{ raise StandardError.new("not_a_post had #id called on it") }) do + not_a_post.stub(:id, -> { raise StandardError.new("not_a_post had #id called on it") }) do assert_nothing_raised do - Post.find_in_batches(:batch_size => 1) do |batch| + Post.find_in_batches(batch_size: 1) do |batch| assert_kind_of Array, batch assert_kind_of Post, batch.first @@ -164,6 +180,42 @@ class EachTest < ActiveRecord::TestCase assert_equal posts(:welcome).id, posts.first.id end + def test_find_in_batches_should_error_on_ignore_the_order + assert_raise(ArgumentError) do + PostWithDefaultScope.find_in_batches(error_on_ignore: true) {} + end + end + + def test_find_in_batches_should_not_error_if_config_overridden + # Set the config option which will be overridden + prev = ActiveRecord::Base.error_on_ignored_order + ActiveRecord::Base.error_on_ignored_order = true + assert_nothing_raised do + PostWithDefaultScope.find_in_batches(error_on_ignore: false) {} + end + ensure + # Set back to default + ActiveRecord::Base.error_on_ignored_order = prev + end + + def test_find_in_batches_should_error_on_config_specified_to_error + # Set the config option + prev = ActiveRecord::Base.error_on_ignored_order + ActiveRecord::Base.error_on_ignored_order = true + assert_raise(ArgumentError) do + PostWithDefaultScope.find_in_batches() {} + end + ensure + # Set back to default + ActiveRecord::Base.error_on_ignored_order = prev + end + + def test_find_in_batches_should_not_error_by_default + assert_nothing_raised do + PostWithDefaultScope.find_in_batches() {} + end + end + def test_find_in_batches_should_not_ignore_the_default_scope_if_it_is_other_then_order special_posts_ids = SpecialPostWithDefaultScope.all.map(&:id).sort posts = [] @@ -175,12 +227,12 @@ class EachTest < ActiveRecord::TestCase def test_find_in_batches_should_not_modify_passed_options assert_nothing_raised do - Post.find_in_batches({ batch_size: 42, start: 1 }.freeze){} + Post.find_in_batches({ batch_size: 42, start: 1 }.freeze) {} end end def test_find_in_batches_should_use_any_column_as_primary_key - nick_order_subscribers = Subscriber.order('nick asc') + nick_order_subscribers = Subscriber.order("nick asc") start_nick = nick_order_subscribers.second.nick subscribers = [] @@ -203,7 +255,7 @@ class EachTest < ActiveRecord::TestCase def test_find_in_batches_should_return_an_enumerator enum = nil assert_no_queries do - enum = Post.find_in_batches(:batch_size => 1) + enum = Post.find_in_batches(batch_size: 1) end assert_queries(4) do enum.first(4) do |batch| @@ -213,6 +265,28 @@ class EachTest < ActiveRecord::TestCase end end + test "find_in_batches should honor limit if passed a block" do + limit = @total - 1 + total = 0 + + Post.limit(limit).find_in_batches do |batch| + total += batch.size + end + + assert_equal limit, total + end + + test "find_in_batches should honor limit if no block is passed" do + limit = @total - 1 + total = 0 + + Post.limit(limit).find_in_batches.each do |batch| + total += batch.size + end + + assert_equal limit, total + end + def test_in_batches_should_not_execute_any_query assert_no_queries do assert_kind_of ActiveRecord::Batches::BatchEnumerator, Post.in_batches(of: 2) @@ -254,7 +328,7 @@ class EachTest < ActiveRecord::TestCase end def test_in_batches_each_record_should_be_ordered_by_id - ids = Post.order('id ASC').pluck(:id) + ids = Post.order("id ASC").pluck(:id) assert_queries(6) do Post.in_batches(of: 2).each_record.with_index do |post, i| assert_equal ids[i], post.id @@ -270,9 +344,9 @@ class EachTest < ActiveRecord::TestCase end def test_in_batches_delete_all_should_not_delete_records_in_other_batches - not_deleted_count = Post.where('id <= 2').count - Post.where('id > 2').in_batches(of: 2).delete_all - assert_equal 0, Post.where('id > 2').count + not_deleted_count = Post.where("id <= 2").count + Post.where("id > 2").in_batches(of: 2).delete_all + assert_equal 0, Post.where("id > 2").count assert_equal not_deleted_count, Post.count end @@ -309,15 +383,15 @@ class EachTest < ActiveRecord::TestCase end def test_in_batches_should_start_from_the_start_option - post = Post.order('id ASC').where('id >= ?', 2).first + post = Post.order("id ASC").where("id >= ?", 2).first assert_queries(2) do relation = Post.in_batches(of: 1, start: 2).first assert_equal post, relation.first end end - def test_in_batches_should_finish_the_end_option - post = Post.order('id DESC').where('id <= ?', 5).first + def test_in_batches_should_end_at_the_finish_option + post = Post.order("id DESC").where("id <= ?", 5).first assert_queries(7) do relation = Post.in_batches(of: 1, finish: 5, load: true).reverse_each.first assert_equal post, relation.last @@ -371,12 +445,12 @@ class EachTest < ActiveRecord::TestCase def test_in_batches_should_not_modify_passed_options assert_nothing_raised do - Post.in_batches({ of: 42, start: 1 }.freeze){} + Post.in_batches({ of: 42, start: 1 }.freeze) {} end end def test_in_batches_should_use_any_column_as_primary_key - nick_order_subscribers = Subscriber.order('nick asc') + nick_order_subscribers = Subscriber.order("nick asc") start_nick = nick_order_subscribers.second.nick subscribers = [] @@ -436,18 +510,108 @@ class EachTest < ActiveRecord::TestCase person.update_attributes(author_id: 1) Post.in_batches(of: 2) do |batch| - batch.where('author_id >= 1').update_all('author_id = author_id + 1') + batch.where("author_id >= 1").update_all("author_id = author_id + 1") end assert_equal 2, person.reload.author_id # incremented only once end if Enumerator.method_defined? :size def test_find_in_batches_should_return_a_sized_enumerator - assert_equal 11, Post.find_in_batches(:batch_size => 1).size - assert_equal 6, Post.find_in_batches(:batch_size => 2).size + assert_equal 11, Post.find_in_batches(batch_size: 1).size + assert_equal 6, Post.find_in_batches(batch_size: 2).size assert_equal 4, Post.find_in_batches(batch_size: 2, start: 4).size - assert_equal 4, Post.find_in_batches(:batch_size => 3).size - assert_equal 1, Post.find_in_batches(:batch_size => 10_000).size + assert_equal 4, Post.find_in_batches(batch_size: 3).size + assert_equal 1, Post.find_in_batches(batch_size: 10_000).size + end + end + + [true, false].each do |load| + test "in_batches should return limit records when limit is less than batch size and load is #{load}" do + limit = 3 + batch_size = 5 + total = 0 + + Post.limit(limit).in_batches(of: batch_size, load: load) do |batch| + total += batch.count + end + + assert_equal limit, total + end + + test "in_batches should return limit records when limit is greater than batch size and load is #{load}" do + limit = 5 + batch_size = 3 + total = 0 + + Post.limit(limit).in_batches(of: batch_size, load: load) do |batch| + total += batch.count + end + + assert_equal limit, total + end + + test "in_batches should return limit records when limit is a multiple of the batch size and load is #{load}" do + limit = 6 + batch_size = 3 + total = 0 + + Post.limit(limit).in_batches(of: batch_size, load: load) do |batch| + total += batch.count + end + + assert_equal limit, total + end + + test "in_batches should return no records if the limit is 0 and load is #{load}" do + limit = 0 + batch_size = 1 + total = 0 + + Post.limit(limit).in_batches(of: batch_size, load: load) do |batch| + total += batch.count + end + + assert_equal limit, total + end + + test "in_batches should return all if the limit is greater than the number of records when load is #{load}" do + limit = @total + 1 + batch_size = 1 + total = 0 + + Post.limit(limit).in_batches(of: batch_size, load: load) do |batch| + total += batch.count + end + + assert_equal @total, total + end + end + + test ".error_on_ignored_order_or_limit= is deprecated" do + begin + prev = ActiveRecord::Base.error_on_ignored_order + assert_deprecated "Please use error_on_ignored_order= instead." do + ActiveRecord::Base.error_on_ignored_order_or_limit = true + end + assert ActiveRecord::Base.error_on_ignored_order + ensure + ActiveRecord::Base.error_on_ignored_order = prev + end + end + + test ".error_on_ignored_order_or_limit is deprecated" do + expected = ActiveRecord::Base.error_on_ignored_order + actual = assert_deprecated "Please use error_on_ignored_order instead." do + ActiveRecord::Base.error_on_ignored_order_or_limit + end + assert_equal expected, actual + end + + test "#error_on_ignored_order_or_limit is deprecated" do + expected = ActiveRecord::Base.error_on_ignored_order + actual = assert_deprecated "Please use error_on_ignored_order instead." do + Post.new.error_on_ignored_order_or_limit end + assert_equal expected, actual end end diff --git a/activerecord/test/cases/binary_test.rb b/activerecord/test/cases/binary_test.rb index 9eb5352150..1fc30e24d2 100644 --- a/activerecord/test/cases/binary_test.rb +++ b/activerecord/test/cases/binary_test.rb @@ -4,23 +4,23 @@ require "cases/helper" # BLOB data with DB2, because the length of a statement # is limited to 32KB. unless current_adapter?(:DB2Adapter) - require 'models/binary' + require "models/binary" class BinaryTest < ActiveRecord::TestCase FIXTURES = %w(flowers.jpg example.log test.txt) def test_mixed_encoding str = "\x80" - str.force_encoding('ASCII-8BIT') + str.force_encoding("ASCII-8BIT") - binary = Binary.new :name => 'ã„ãŸã ãã¾ã™ï¼', :data => str + binary = Binary.new name: "ã„ãŸã ãã¾ã™ï¼", data: str binary.save! binary.reload assert_equal str, binary.data name = binary.name - assert_equal 'ã„ãŸã ãã¾ã™ï¼', name + assert_equal "ã„ãŸã ãã¾ã™ï¼", name end def test_load_save @@ -28,16 +28,16 @@ unless current_adapter?(:DB2Adapter) FIXTURES.each do |filename| data = File.read(ASSETS_ROOT + "/#{filename}") - data.force_encoding('ASCII-8BIT') + data.force_encoding("ASCII-8BIT") data.freeze - bin = Binary.new(:data => data) - assert_equal data, bin.data, 'Newly assigned data differs from original' + bin = Binary.new(data: data) + assert_equal data, bin.data, "Newly assigned data differs from original" bin.save! - assert_equal data, bin.data, 'Data differs from original after save' + assert_equal data, bin.data, "Data differs from original after save" - assert_equal data, bin.reload.data, 'Reloaded data differs from original' + assert_equal data, bin.reload.data, "Reloaded data differs from original" end end end diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb index cd9c76f1f0..98d202dd79 100644 --- a/activerecord/test/cases/bind_parameter_test.rb +++ b/activerecord/test/cases/bind_parameter_test.rb @@ -1,7 +1,7 @@ -require 'cases/helper' -require 'models/topic' -require 'models/author' -require 'models/post' +require "cases/helper" +require "models/topic" +require "models/author" +require "models/post" module ActiveRecord class BindParameterTest < ActiveRecord::TestCase @@ -24,17 +24,18 @@ module ActiveRecord @connection = ActiveRecord::Base.connection @subscriber = LogListener.new @pk = Topic.columns_hash[Topic.primary_key] - @subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber) + @subscription = ActiveSupport::Notifications.subscribe("sql.active_record", @subscriber) end teardown do ActiveSupport::Notifications.unsubscribe(@subscription) end - if ActiveRecord::Base.connection.supports_statement_cache? + if ActiveRecord::Base.connection.supports_statement_cache? && + ActiveRecord::Base.connection.prepared_statements def test_bind_from_join_in_subquery - subquery = Author.joins(:thinking_posts).where(name: 'David') - scope = Author.from(subquery, 'authors').where(id: 1) + subquery = Author.joins(:thinking_posts).where(name: "David") + scope = Author.from(subquery, "authors").where(id: 1) assert_equal 1, scope.count end @@ -43,7 +44,7 @@ module ActiveRecord binds = [Relation::QueryAttribute.new("id", 1, Type::Value.new)] sql = "select * from topics where id = #{sub.to_sql}" - @connection.exec_query(sql, 'SQL', binds) + @connection.exec_query(sql, "SQL", binds) message = @subscriber.calls.find { |args| args[4][:sql] == sql } assert_equal binds, message[4][:binds] @@ -52,17 +53,20 @@ module ActiveRecord def test_find_one_uses_binds Topic.find(1) message = @subscriber.calls.find { |args| args[4][:binds].any? { |attr| attr.value == 1 } } - assert message, 'expected a message with binds' + assert message, "expected a message with binds" end def test_logs_bind_vars_after_type_cast + binds = [Relation::QueryAttribute.new("id", "10", Type::Integer.new)] + type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) } payload = { - :name => 'SQL', - :sql => 'select * from topics where id = ?', - :binds => [Relation::QueryAttribute.new("id", "10", Type::Integer.new)] + name: "SQL", + sql: "select * from topics where id = ?", + binds: binds, + type_casted_binds: type_casted_binds } event = ActiveSupport::Notifications::Event.new( - 'foo', + "foo", Time.now, Time.now, 123, @@ -75,7 +79,7 @@ module ActiveRecord @debugs = [] end - def debug str + def debug(str) @debugs << str end }.new @@ -83,6 +87,12 @@ module ActiveRecord logger.sql event assert_match([[@pk.name, 10]].inspect, logger.debugs.first) end + + private + + def type_cast(value) + ActiveRecord::Base.connection.type_cast(value) + end end end end diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index c922a8d1c2..db2871d383 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -1,23 +1,24 @@ require "cases/helper" -require 'models/club' -require 'models/company' +require "models/book" +require "models/club" +require "models/company" require "models/contract" -require 'models/edge' -require 'models/organization' -require 'models/possession' -require 'models/topic' -require 'models/reply' -require 'models/minivan' -require 'models/speedometer' -require 'models/ship_part' -require 'models/treasure' -require 'models/developer' -require 'models/comment' -require 'models/rating' -require 'models/post' +require "models/edge" +require "models/organization" +require "models/possession" +require "models/topic" +require "models/reply" +require "models/minivan" +require "models/speedometer" +require "models/ship_part" +require "models/treasure" +require "models/developer" +require "models/comment" +require "models/rating" +require "models/post" class NumericData < ActiveRecord::Base - self.table_name = 'numeric_data' + self.table_name = "numeric_data" attribute :world_population, :integer attribute :my_house_population, :integer @@ -25,7 +26,7 @@ class NumericData < ActiveRecord::Base end class CalculationsTest < ActiveRecord::TestCase - fixtures :companies, :accounts, :topics, :speedometers, :minivans + fixtures :companies, :accounts, :topics, :speedometers, :minivans, :books def test_should_sum_field assert_equal 318, Account.sum(:credit_limit) @@ -56,7 +57,7 @@ class CalculationsTest < ActiveRecord::TestCase def test_should_return_integer_average_if_db_returns_such ShipPart.delete_all - ShipPart.create!(:id => 3, :name => 'foo') + ShipPart.create!(id: 3, name: "foo") value = ShipPart.average(:id) assert_equal 3, value end @@ -92,24 +93,24 @@ class CalculationsTest < ActiveRecord::TestCase def test_should_group_by_field c = Account.group(:firm_id).sum(:credit_limit) [1,6,2].each do |firm_id| - assert c.keys.include?(firm_id), "Group #{c.inspect} does not contain firm_id #{firm_id}" + assert_includes c.keys, firm_id, "Group #{c.inspect} does not contain firm_id #{firm_id}" end end def test_should_group_by_arel_attribute c = Account.group(Account.arel_table[:firm_id]).sum(:credit_limit) [1,6,2].each do |firm_id| - assert c.keys.include?(firm_id), "Group #{c.inspect} does not contain firm_id #{firm_id}" + assert_includes c.keys, firm_id, "Group #{c.inspect} does not contain firm_id #{firm_id}" end end def test_should_group_by_multiple_fields - c = Account.group('firm_id', :credit_limit).count(:all) - [ [nil, 50], [1, 50], [6, 50], [6, 55], [9, 53], [2, 60] ].each { |firm_and_limit| assert c.keys.include?(firm_and_limit) } + c = Account.group("firm_id", :credit_limit).count(:all) + [ [nil, 50], [1, 50], [6, 50], [6, 55], [9, 53], [2, 60] ].each { |firm_and_limit| assert_includes c.keys, firm_and_limit } end def test_should_group_by_multiple_fields_having_functions - c = Topic.group(:author_name, 'COALESCE(type, title)').count(:all) + c = Topic.group(:author_name, "COALESCE(type, title)").count(:all) assert_equal 1, c[["Carl", "The Third Topic of the day"]] assert_equal 1, c[["Mary", "Reply"]] assert_equal 1, c[["David", "The First Topic"]] @@ -124,7 +125,7 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_generate_valid_sql_with_joins_and_group - assert_nothing_raised ActiveRecord::StatementInvalid do + assert_nothing_raised do AuditLog.joins(:developer).group(:id).count end end @@ -165,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(3).where("firm_id IS NOT NULL") 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(3).where("firm_id IS NOT NULL") firm_id_attribute = Account.arel_table[:firm_id] assert_equal 3, accounts.count(firm_id_attribute) @@ -227,7 +228,7 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_group_by_summed_field_having_condition - c = Account.group(:firm_id).having('sum(credit_limit) > 50').sum(:credit_limit) + c = Account.group(:firm_id).having("sum(credit_limit) > 50").sum(:credit_limit) assert_nil c[1] assert_equal 105, c[6] assert_equal 60, c[2] @@ -248,52 +249,52 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_sum_field_with_conditions - assert_equal 105, Account.where('firm_id = 6').sum(:credit_limit) + assert_equal 105, Account.where("firm_id = 6").sum(:credit_limit) end def test_should_return_zero_if_sum_conditions_return_nothing - assert_equal 0, Account.where('1 = 2').sum(:credit_limit) - assert_equal 0, companies(:rails_core).companies.where('1 = 2').sum(:id) + assert_equal 0, Account.where("1 = 2").sum(:credit_limit) + assert_equal 0, companies(:rails_core).companies.where("1 = 2").sum(:id) end def test_sum_should_return_valid_values_for_decimals - NumericData.create(:bank_balance => 19.83) + NumericData.create(bank_balance: 19.83) assert_equal 19.83, NumericData.sum(:bank_balance) end def test_should_return_type_casted_values_with_group_and_expression - assert_equal 0.5, Account.group(:firm_name).sum('0.01 * credit_limit')['37signals'] + assert_equal 0.5, Account.group(:firm_name).sum("0.01 * credit_limit")["37signals"] end def test_should_group_by_summed_field_with_conditions - c = Account.where('firm_id > 1').group(:firm_id).sum(:credit_limit) + c = Account.where("firm_id > 1").group(:firm_id).sum(:credit_limit) assert_nil c[1] assert_equal 105, c[6] assert_equal 60, c[2] end def test_should_group_by_summed_field_with_conditions_and_having - c = Account.where('firm_id > 1').group(:firm_id). - having('sum(credit_limit) > 60').sum(:credit_limit) + c = Account.where("firm_id > 1").group(:firm_id). + having("sum(credit_limit) > 60").sum(:credit_limit) assert_nil c[1] assert_equal 105, c[6] assert_nil c[2] end def test_should_group_by_fields_with_table_alias - c = Account.group('accounts.firm_id').sum(:credit_limit) + c = Account.group("accounts.firm_id").sum(:credit_limit) assert_equal 50, c[1] assert_equal 105, c[6] assert_equal 60, c[2] end def test_should_calculate_with_invalid_field - assert_equal 6, Account.calculate(:count, '*') + assert_equal 6, Account.calculate(:count, "*") assert_equal 6, Account.calculate(:count, :all) end def test_should_calculate_grouped_with_invalid_field - c = Account.group('accounts.firm_id').count(:all) + c = Account.group("accounts.firm_id").count(:all) assert_equal 1, c[1] assert_equal 2, c[6] assert_equal 1, c[2] @@ -307,8 +308,8 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_group_by_association_with_non_numeric_foreign_key - Speedometer.create! id: 'ABC' - Minivan.create! id: 'OMG', speedometer_id: 'ABC' + Speedometer.create! id: "ABC" + Minivan.create! id: "OMG", speedometer_id: "ABC" c = Minivan.group(:speedometer).count(:all) first_key = c.keys.first @@ -317,7 +318,7 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_calculate_grouped_association_with_foreign_key_option - Account.belongs_to :another_firm, :class_name => 'Firm', :foreign_key => 'firm_id' + Account.belongs_to :another_firm, class_name: "Firm", foreign_key: "firm_id" c = Account.group(:another_firm).count(:all) assert_equal 1, c[companies(:first_firm)] assert_equal 2, c[companies(:rails_core)] @@ -327,17 +328,17 @@ class CalculationsTest < ActiveRecord::TestCase def test_should_calculate_grouped_by_function c = Company.group("UPPER(#{QUOTED_TYPE})").count(:all) assert_equal 2, c[nil] - assert_equal 1, c['DEPENDENTFIRM'] - assert_equal 5, c['CLIENT'] - assert_equal 2, c['FIRM'] + assert_equal 1, c["DEPENDENTFIRM"] + assert_equal 5, c["CLIENT"] + assert_equal 2, c["FIRM"] end def test_should_calculate_grouped_by_function_with_table_alias c = Company.group("UPPER(companies.#{QUOTED_TYPE})").count(:all) assert_equal 2, c[nil] - assert_equal 1, c['DEPENDENTFIRM'] - assert_equal 5, c['CLIENT'] - assert_equal 2, c['FIRM'] + assert_equal 1, c["DEPENDENTFIRM"] + assert_equal 5, c["CLIENT"] + assert_equal 2, c["FIRM"] end def test_should_not_overshadow_enumerable_sum @@ -353,19 +354,19 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_sum_scoped_field_with_conditions - assert_equal 8, companies(:rails_core).companies.where('id > 7').sum(:id) + assert_equal 8, companies(:rails_core).companies.where("id > 7").sum(:id) end def test_should_group_by_scoped_field c = companies(:rails_core).companies.group(:name).sum(:id) - assert_equal 7, c['Leetsoft'] - assert_equal 8, c['Jadedpixel'] + assert_equal 7, c["Leetsoft"] + assert_equal 8, c["Jadedpixel"] end def test_should_group_by_summed_field_through_association_and_having - c = companies(:rails_core).companies.group(:name).having('sum(id) > 7').sum(:id) - assert_nil c['Leetsoft'] - assert_equal 8, c['Jadedpixel'] + c = companies(:rails_core).companies.group(:name).having("sum(id) > 7").sum(:id) + assert_nil c["Leetsoft"] + assert_equal 8, c["Jadedpixel"] end def test_should_count_selected_field_with_include @@ -380,7 +381,7 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_perform_joined_include_when_referencing_included_tables - joined_count = Account.includes(:firm).where(:companies => {:name => '37signals'}).count + joined_count = Account.includes(:firm).where(companies: { name: "37signals" }).count assert_equal 1, joined_count end @@ -391,10 +392,10 @@ class CalculationsTest < ActiveRecord::TestCase def test_should_count_scoped_select_with_options Account.update_all("credit_limit = NULL") - Account.last.update_columns('credit_limit' => 49) - Account.first.update_columns('credit_limit' => 51) + Account.last.update_columns("credit_limit" => 49) + Account.first.update_columns("credit_limit" => 51) - assert_equal 1, Account.select("credit_limit").where('credit_limit >= 50').count + assert_equal 1, Account.select("credit_limit").where("credit_limit >= 50").count end def test_should_count_manual_select_with_include @@ -435,8 +436,8 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_count_field_in_joined_table - assert_equal 5, Account.joins(:firm).count('companies.id') - assert_equal 4, Account.joins(:firm).distinct.count('companies.id') + assert_equal 5, Account.joins(:firm).count("companies.id") + assert_equal 4, Account.joins(:firm).distinct.count("companies.id") end def test_count_arel_attribute_in_joined_table_with @@ -450,14 +451,14 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_count_field_in_joined_table_with_group_by - c = Account.group('accounts.firm_id').joins(:firm).count('companies.id') + c = Account.group("accounts.firm_id").joins(:firm).count("companies.id") - [1,6,2,9].each { |firm_id| assert c.keys.include?(firm_id) } + [1,6,2,9].each { |firm_id| assert_includes c.keys, firm_id } end def test_should_count_field_of_root_table_with_conflicting_group_by_column assert_equal({ 1 => 1 }, Firm.joins(:accounts).group(:firm_id).count) - assert_equal({ 1 => 1 }, Firm.joins(:accounts).group('accounts.firm_id').count) + assert_equal({ 1 => 1 }, Firm.joins(:accounts).group("accounts.firm_id").count) end def test_count_with_no_parameters_isnt_deprecated @@ -477,9 +478,13 @@ class CalculationsTest < ActiveRecord::TestCase end def test_count_with_where_and_order - assert_equal 1, Account.where(firm_name: '37signals').count - assert_equal 1, Account.where(firm_name: '37signals').order(:firm_name).count - assert_equal 1, Account.where(firm_name: '37signals').order(:firm_name).reverse_order.count + assert_equal 1, Account.where(firm_name: "37signals").count + assert_equal 1, Account.where(firm_name: "37signals").order(:firm_name).count + assert_equal 1, Account.where(firm_name: "37signals").order(:firm_name).reverse_order.count + end + + def test_count_with_block + assert_equal 4, Account.count { |account| account.credit_limit.modulo(10).zero? } end def test_should_sum_expression @@ -492,69 +497,69 @@ class CalculationsTest < ActiveRecord::TestCase end def test_sum_expression_returns_zero_when_no_records_to_sum - assert_equal 0, Account.where('1 = 2').sum("2 * credit_limit") + assert_equal 0, Account.where("1 = 2").sum("2 * credit_limit") end def test_count_with_from_option - assert_equal Company.count(:all), Company.from('companies').count(:all) + assert_equal Company.count(:all), Company.from("companies").count(:all) assert_equal Account.where("credit_limit = 50").count(:all), - Account.from('accounts').where("credit_limit = 50").count(:all) - assert_equal Company.where(:type => "Firm").count(:type), - Company.where(:type => "Firm").from('companies').count(:type) + Account.from("accounts").where("credit_limit = 50").count(:all) + assert_equal Company.where(type: "Firm").count(:type), + Company.where(type: "Firm").from("companies").count(:type) end def test_sum_with_from_option - assert_equal Account.sum(:credit_limit), Account.from('accounts').sum(:credit_limit) + assert_equal Account.sum(:credit_limit), Account.from("accounts").sum(:credit_limit) assert_equal Account.where("credit_limit > 50").sum(:credit_limit), - Account.where("credit_limit > 50").from('accounts').sum(:credit_limit) + Account.where("credit_limit > 50").from("accounts").sum(:credit_limit) end def test_average_with_from_option - assert_equal Account.average(:credit_limit), Account.from('accounts').average(:credit_limit) + assert_equal Account.average(:credit_limit), Account.from("accounts").average(:credit_limit) assert_equal Account.where("credit_limit > 50").average(:credit_limit), - Account.where("credit_limit > 50").from('accounts').average(:credit_limit) + Account.where("credit_limit > 50").from("accounts").average(:credit_limit) end def test_minimum_with_from_option - assert_equal Account.minimum(:credit_limit), Account.from('accounts').minimum(:credit_limit) + assert_equal Account.minimum(:credit_limit), Account.from("accounts").minimum(:credit_limit) assert_equal Account.where("credit_limit > 50").minimum(:credit_limit), - Account.where("credit_limit > 50").from('accounts').minimum(:credit_limit) + Account.where("credit_limit > 50").from("accounts").minimum(:credit_limit) end def test_maximum_with_from_option - assert_equal Account.maximum(:credit_limit), Account.from('accounts').maximum(:credit_limit) + assert_equal Account.maximum(:credit_limit), Account.from("accounts").maximum(:credit_limit) assert_equal Account.where("credit_limit > 50").maximum(:credit_limit), - Account.where("credit_limit > 50").from('accounts').maximum(:credit_limit) + Account.where("credit_limit > 50").from("accounts").maximum(:credit_limit) end def test_maximum_with_not_auto_table_name_prefix_if_column_included - Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)]) + Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)]) assert_equal 7, Company.includes(:contracts).maximum(:developer_id) end def test_minimum_with_not_auto_table_name_prefix_if_column_included - Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)]) + Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)]) assert_equal 7, Company.includes(:contracts).minimum(:developer_id) end def test_sum_with_not_auto_table_name_prefix_if_column_included - Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)]) + Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)]) assert_equal 7, Company.includes(:contracts).sum(:developer_id) end if current_adapter?(:Mysql2Adapter) def test_from_option_with_specified_index - assert_equal Edge.count(:all), Edge.from('edges USE INDEX(unique_edge_index)').count(:all) - assert_equal Edge.where('sink_id < 5').count(:all), - Edge.from('edges USE INDEX(unique_edge_index)').where('sink_id < 5').count(:all) + assert_equal Edge.count(:all), Edge.from("edges USE INDEX(unique_edge_index)").count(:all) + assert_equal Edge.where("sink_id < 5").count(:all), + Edge.from("edges USE INDEX(unique_edge_index)").where("sink_id < 5").count(:all) end end def test_from_option_with_table_different_than_class - assert_equal Account.count(:all), Company.from('accounts').count(:all) + assert_equal Account.count(:all), Company.from("accounts").count(:all) end def test_distinct_is_honored_when_used_with_count_operation_after_group @@ -577,7 +582,7 @@ class CalculationsTest < ActiveRecord::TestCase def test_pluck_type_cast topic = topics(:first) - relation = Topic.where(:id => topic.id) + relation = Topic.where(id: topic.id) assert_equal [ topic.approved ], relation.pluck(:approved) assert_equal [ topic.last_read ], relation.pluck(:last_read) assert_equal [ topic.written_on ], relation.pluck(:written_on) @@ -594,12 +599,12 @@ class CalculationsTest < ActiveRecord::TestCase end def test_pluck_on_aliased_attribute - assert_equal 'The First Topic', Topic.order(:id).pluck(:heading).first + assert_equal "The First Topic", Topic.order(:id).pluck(:heading).first end def test_pluck_with_serialization - t = Topic.create!(:content => { :foo => :bar }) - assert_equal [{:foo => :bar}], Topic.where(:id => t.id).pluck(:content) + t = Topic.create!(content: { foo: :bar }) + assert_equal [{ foo: :bar }], Topic.where(id: t.id).pluck(:content) end def test_pluck_with_qualified_column_name @@ -607,29 +612,29 @@ class CalculationsTest < ActiveRecord::TestCase end def test_pluck_auto_table_name_prefix - c = Company.create!(:name => "test", :contracts => [Contract.new]) + c = Company.create!(name: "test", contracts: [Contract.new]) assert_equal [c.id], Company.joins(:contracts).pluck(:id) end def test_pluck_if_table_included - c = Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)]) + c = Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)]) assert_equal [c.id], Company.includes(:contracts).where("contracts.id" => c.contracts.first).pluck(:id) end def test_pluck_not_auto_table_name_prefix_if_column_joined - Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)]) + Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)]) assert_equal [7], Company.joins(:contracts).pluck(:developer_id) end def test_pluck_with_selection_clause - assert_equal [50, 53, 55, 60], Account.pluck('DISTINCT credit_limit').sort - assert_equal [50, 53, 55, 60], Account.pluck('DISTINCT accounts.credit_limit').sort - assert_equal [50, 53, 55, 60], Account.pluck('DISTINCT(credit_limit)').sort + assert_equal [50, 53, 55, 60], Account.pluck("DISTINCT credit_limit").sort + assert_equal [50, 53, 55, 60], Account.pluck("DISTINCT accounts.credit_limit").sort + assert_equal [50, 53, 55, 60], Account.pluck("DISTINCT(credit_limit)").sort # MySQL returns "SUM(DISTINCT(credit_limit))" as the column name unless # an alias is provided. Without the alias, the column cannot be found # and properly typecast. - assert_equal [50 + 53 + 55 + 60], Account.pluck('SUM(DISTINCT(credit_limit)) as credit_limit') + assert_equal [50 + 53 + 55 + 60], Account.pluck("SUM(DISTINCT(credit_limit)) as credit_limit") end def test_plucks_with_ids @@ -638,11 +643,11 @@ class CalculationsTest < ActiveRecord::TestCase def test_pluck_with_includes_limit_and_empty_result assert_equal [], Topic.includes(:replies).limit(0).pluck(:id) - assert_equal [], Topic.includes(:replies).limit(1).where('0 = 1').pluck(:id) + assert_equal [], Topic.includes(:replies).limit(1).where("0 = 1").pluck(:id) end def test_pluck_not_auto_table_name_prefix_if_column_included - Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)]) + Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)]) ids = Company.includes(:contracts).pluck(:developer_id) assert_equal Company.count, ids.length assert_equal [7], ids.compact @@ -663,12 +668,12 @@ class CalculationsTest < ActiveRecord::TestCase def test_pluck_with_multiple_columns_and_selection_clause assert_equal [[1, 50], [2, 50], [3, 50], [4, 60], [5, 55], [6, 53]], - Account.pluck('id, credit_limit') + Account.pluck("id, credit_limit") end def test_pluck_with_multiple_columns_and_includes - Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)]) - companies_and_developers = Company.order('companies.id').includes(:contracts).pluck(:name, :developer_id) + Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)]) + companies_and_developers = Company.order("companies.id").includes(:contracts).pluck(:name, :developer_id) assert_equal Company.count, companies_and_developers.length assert_equal ["37signals", nil], companies_and_developers.first @@ -676,7 +681,7 @@ class CalculationsTest < ActiveRecord::TestCase end def test_pluck_with_reserved_words - Possession.create!(:where => "Over There") + Possession.create!(where: "Over There") assert_equal ["Over There"], Possession.pluck(:where) end @@ -690,7 +695,7 @@ class CalculationsTest < ActiveRecord::TestCase def test_pluck_columns_with_same_name expected = [["The First Topic", "The Second Topic of the day"], ["The Third Topic of the day", "The Fourth Topic of the day"]] actual = Topic.joins(:replies) - .pluck('topics.title', 'replies_topics.title') + .pluck("topics.title", "replies_topics.title") assert_equal expected, actual end @@ -711,21 +716,21 @@ class CalculationsTest < ActiveRecord::TestCase def test_pluck_loaded_relation companies = Company.order(:id).limit(3).load assert_no_queries do - assert_equal ['37signals', 'Summit', 'Microsoft'], companies.pluck(:name) + assert_equal ["37signals", "Summit", "Microsoft"], companies.pluck(:name) end end def test_pluck_loaded_relation_multiple_columns companies = Company.order(:id).limit(3).load assert_no_queries do - assert_equal [[1, '37signals'], [2, 'Summit'], [3, 'Microsoft']], companies.pluck(:id, :name) + assert_equal [[1, "37signals"], [2, "Summit"], [3, "Microsoft"]], companies.pluck(:id, :name) end end def test_pluck_loaded_relation_sql_fragment companies = Company.order(:name).limit(3).load assert_queries 1 do - assert_equal ['37signals', 'Apex', 'Ex Nihilo'], companies.pluck('DISTINCT name') + assert_equal ["37signals", "Apex", "Ex Nihilo"], companies.pluck("DISTINCT name") end end @@ -742,8 +747,8 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_reference_correct_aliases_while_joining_tables_of_has_many_through_association - assert_nothing_raised ActiveRecord::StatementInvalid do - developer = Developer.create!(name: 'developer') + assert_nothing_raised do + developer = Developer.create!(name: "developer") developer.ratings.includes(comment: :post).where(posts: { id: 1 }).count end end @@ -778,7 +783,7 @@ class CalculationsTest < ActiveRecord::TestCase end end - params = protected_params.new(credit_limit: '50') + params = protected_params.new(credit_limit: "50") assert_raises(ActiveModel::ForbiddenAttributesError) do Account.group(:id).having(params) @@ -789,4 +794,8 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal 50, result[1].credit_limit assert_equal 50, result[2].credit_limit end + + def test_group_by_attribute_with_custom_type + assert_equal({ "proposed" => 2, "published" => 2 }, Book.group(:status).count) + end end diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb index 4f70ae3a1d..4b517e9d70 100644 --- a/activerecord/test/cases/callbacks_test.rb +++ b/activerecord/test/cases/callbacks_test.rb @@ -1,9 +1,9 @@ require "cases/helper" -require 'models/developer' -require 'models/computer' +require "models/developer" +require "models/computer" class CallbackDeveloper < ActiveRecord::Base - self.table_name = 'developers' + self.table_name = "developers" class << self def callback_string(callback_method) @@ -16,7 +16,7 @@ class CallbackDeveloper < ActiveRecord::Base def define_callback_method(callback_method) define_method(callback_method) do - self.history << [callback_method, :method] + history << [callback_method, :method] end send(callback_method, :"#{callback_method}") end @@ -31,7 +31,7 @@ class CallbackDeveloper < ActiveRecord::Base end ActiveRecord::Callbacks::CALLBACKS.each do |callback_method| - next if callback_method.to_s =~ /^around_/ + 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)) @@ -55,19 +55,18 @@ class CallbackDeveloperWithHaltedValidation < CallbackDeveloper end class ParentDeveloper < ActiveRecord::Base - self.table_name = 'developers' + self.table_name = "developers" attr_accessor :after_save_called - before_validation {|record| record.after_save_called = true} + before_validation { |record| record.after_save_called = true } end class ChildDeveloper < ParentDeveloper - end class ImmutableDeveloper < ActiveRecord::Base - self.table_name = 'developers' + self.table_name = "developers" - validates_inclusion_of :salary, :in => 50000..200000 + validates_inclusion_of :salary, in: 50000..200000 before_save :cancel before_destroy :cancel @@ -79,7 +78,7 @@ class ImmutableDeveloper < ActiveRecord::Base end class DeveloperWithCanceledCallbacks < ActiveRecord::Base - self.table_name = 'developers' + self.table_name = "developers" validates_inclusion_of :salary, in: 50000..200000 @@ -93,19 +92,19 @@ class DeveloperWithCanceledCallbacks < ActiveRecord::Base end class OnCallbacksDeveloper < ActiveRecord::Base - self.table_name = 'developers' + self.table_name = "developers" before_validation { history << :before_validation } - before_validation(:on => :create){ history << :before_validation_on_create } - before_validation(:on => :update){ history << :before_validation_on_update } + before_validation(on: :create) { history << :before_validation_on_create } + before_validation(on: :update) { history << :before_validation_on_update } validate do history << :validate end after_validation { history << :after_validation } - after_validation(:on => :create){ history << :after_validation_on_create } - after_validation(:on => :update){ history << :after_validation_on_update } + after_validation(on: :create) { history << :after_validation_on_create } + after_validation(on: :update) { history << :after_validation_on_update } def history @history ||= [] @@ -113,17 +112,17 @@ class OnCallbacksDeveloper < ActiveRecord::Base end class ContextualCallbacksDeveloper < ActiveRecord::Base - self.table_name = 'developers' + self.table_name = "developers" before_validation { history << :before_validation } - before_validation :before_validation_on_create_and_update, :on => [ :create, :update ] + before_validation :before_validation_on_create_and_update, on: [ :create, :update ] validate do history << :validate end after_validation { history << :after_validation } - after_validation :after_validation_on_create_and_update, :on => [ :create, :update ] + 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 @@ -139,12 +138,12 @@ class ContextualCallbacksDeveloper < ActiveRecord::Base end class CallbackCancellationDeveloper < ActiveRecord::Base - self.table_name = 'developers' + 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_save { defined?(@cancel_before_save) ? !@cancel_before_save : false } before_create { !@cancel_before_create } before_update { !@cancel_before_update } before_destroy { !@cancel_before_destroy } @@ -156,7 +155,7 @@ class CallbackCancellationDeveloper < ActiveRecord::Base end class CallbackHaltedDeveloper < ActiveRecord::Base - self.table_name = 'developers' + 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 @@ -252,7 +251,7 @@ class CallbacksTest < ActiveRecord::TestCase end def test_create - david = CallbackDeveloper.create('name' => 'David', 'salary' => 1000000) + david = CallbackDeveloper.create("name" => "David", "salary" => 1000000) assert_equal [ [ :after_initialize, :method ], [ :after_initialize, :string ], @@ -298,7 +297,7 @@ class CallbacksTest < ActiveRecord::TestCase end def test_validate_on_create - david = OnCallbacksDeveloper.create('name' => 'David', 'salary' => 1000000) + david = OnCallbacksDeveloper.create("name" => "David", "salary" => 1000000) assert_equal [ :before_validation, :before_validation_on_create, @@ -309,7 +308,7 @@ class CallbacksTest < ActiveRecord::TestCase end def test_validate_on_contextual_create - david = ContextualCallbacksDeveloper.create('name' => 'David', 'salary' => 1000000) + david = ContextualCallbacksDeveloper.create("name" => "David", "salary" => 1000000) assert_equal [ :before_validation, :before_validation_on_create, @@ -632,5 +631,4 @@ class CallbacksTest < ActiveRecord::TestCase child.save assert child.after_save_called end - end diff --git a/activerecord/test/cases/clone_test.rb b/activerecord/test/cases/clone_test.rb index 5e43082c33..b89294c094 100644 --- a/activerecord/test/cases/clone_test.rb +++ b/activerecord/test/cases/clone_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'models/topic' +require "models/topic" module ActiveRecord class CloneTest < ActiveRecord::TestCase @@ -8,9 +8,9 @@ module ActiveRecord def test_persisted topic = Topic.first cloned = topic.clone - assert topic.persisted?, 'topic persisted' - assert cloned.persisted?, 'topic persisted' - assert !cloned.new_record?, 'topic is not new' + assert topic.persisted?, "topic persisted" + assert cloned.persisted?, "topic persisted" + assert !cloned.new_record?, "topic is not new" end def test_stays_frozen @@ -18,16 +18,16 @@ module ActiveRecord topic.freeze cloned = topic.clone - assert cloned.persisted?, 'topic persisted' - assert !cloned.new_record?, 'topic is not new' - assert cloned.frozen?, 'topic should be frozen' + assert cloned.persisted?, "topic persisted" + assert !cloned.new_record?, "topic is not new" + assert cloned.frozen?, "topic should be frozen" end def test_shallow topic = Topic.first cloned = topic.clone - topic.author_name = 'Aaron' - assert_equal 'Aaron', cloned.author_name + topic.author_name = "Aaron" + assert_equal "Aaron", cloned.author_name end def test_freezing_a_cloned_model_does_not_freeze_clone diff --git a/activerecord/test/cases/coders/json_test.rb b/activerecord/test/cases/coders/json_test.rb new file mode 100644 index 0000000000..d22d93d129 --- /dev/null +++ b/activerecord/test/cases/coders/json_test.rb @@ -0,0 +1,15 @@ +require "cases/helper" + +module ActiveRecord + module Coders + class JSONTest < ActiveRecord::TestCase + def test_returns_nil_if_empty_string_given + assert_nil JSON.load("") + end + + def test_returns_nil_if_nil_given + assert_nil JSON.load(nil) + end + end + end +end diff --git a/activerecord/test/cases/coders/yaml_column_test.rb b/activerecord/test/cases/coders/yaml_column_test.rb index b72c54f97b..b9c6224425 100644 --- a/activerecord/test/cases/coders/yaml_column_test.rb +++ b/activerecord/test/cases/coders/yaml_column_test.rb @@ -35,7 +35,7 @@ module ActiveRecord def test_returns_string_unless_starts_with_dash coder = YAMLColumn.new - assert_equal 'foo', coder.load("foo") + assert_equal "foo", coder.load("foo") end def test_load_handles_other_classes @@ -45,7 +45,7 @@ module ActiveRecord def test_load_doesnt_swallow_yaml_exceptions coder = YAMLColumn.new - bad_yaml = '--- {' + bad_yaml = "--- {" assert_raises(Psych::SyntaxError) do coder.load(bad_yaml) end diff --git a/activerecord/test/cases/column_alias_test.rb b/activerecord/test/cases/column_alias_test.rb index 40707d9cb2..9893ba9580 100644 --- a/activerecord/test/cases/column_alias_test.rb +++ b/activerecord/test/cases/column_alias_test.rb @@ -1,17 +1,17 @@ require "cases/helper" -require 'models/topic' +require "models/topic" class TestColumnAlias < ActiveRecord::TestCase fixtures :topics - QUERY = if 'Oracle' == ActiveRecord::Base.connection.adapter_name - 'SELECT id AS pk FROM topics WHERE ROWNUM < 2' - else - 'SELECT id AS pk FROM topics' - end + QUERY = if "Oracle" == ActiveRecord::Base.connection.adapter_name + "SELECT id AS pk FROM topics WHERE ROWNUM < 2" + else + "SELECT id AS pk FROM topics" + end def test_column_alias records = Topic.connection.select_all(QUERY) - assert_equal 'pk', records[0].keys[0] + assert_equal "pk", records[0].keys[0] end end diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb index 81162b7e98..a65bb89052 100644 --- a/activerecord/test/cases/column_definition_test.rb +++ b/activerecord/test/cases/column_definition_test.rb @@ -6,7 +6,7 @@ module ActiveRecord def setup @adapter = AbstractAdapter.new(nil) def @adapter.native_database_types - {:string => "varchar"} + { string: "varchar" } end @viz = @adapter.schema_creation end @@ -18,7 +18,7 @@ module ActiveRecord column_def = ColumnDefinition.new( column.name, "string", column.limit, column.precision, column.scale, column.default, column.null) - assert_equal "title varchar(20)", @viz.accept(column_def) + assert_equal "title varchar(20)", @viz.accept(column_def) end def test_should_include_default_clause_when_default_is_present @@ -26,7 +26,7 @@ module ActiveRecord column_def = ColumnDefinition.new( column.name, "string", column.limit, column.precision, column.scale, column.default, column.null) - assert_equal %Q{title varchar(20) DEFAULT 'Hello'}, @viz.accept(column_def) + assert_equal "title varchar(20) DEFAULT 'Hello'", @viz.accept(column_def) end def test_should_specify_not_null_if_null_option_is_false @@ -35,7 +35,7 @@ module ActiveRecord column_def = ColumnDefinition.new( column.name, "string", column.limit, column.precision, column.scale, column.default, column.null) - assert_equal %Q{title varchar(20) DEFAULT 'Hello' NOT NULL}, @viz.accept(column_def) + assert_equal "title varchar(20) DEFAULT 'Hello' NOT NULL", @viz.accept(column_def) end if current_adapter?(:Mysql2Adapter) @@ -60,21 +60,13 @@ module ActiveRecord end def test_should_not_set_default_for_blob_and_text_data_types - assert_raise ArgumentError do - MySQL::Column.new("title", "a", SqlTypeMetadata.new(sql_type: "blob")) - end - - text_type = MySQL::TypeMetadata.new( - SqlTypeMetadata.new(type: :text)) - assert_raise ArgumentError do - MySQL::Column.new("title", "Hello", text_type) - end + text_type = MySQL::TypeMetadata.new(SqlTypeMetadata.new(type: :text)) text_column = MySQL::Column.new("title", nil, text_type) - assert_equal nil, text_column.default + assert_nil text_column.default not_null_text_column = MySQL::Column.new("title", nil, text_type, false) - assert_equal "", not_null_text_column.default + assert_nil not_null_text_column.default end def test_has_default_should_return_false_for_blob_and_text_data_types diff --git a/activerecord/test/cases/comment_test.rb b/activerecord/test/cases/comment_test.rb new file mode 100644 index 0000000000..262ad319be --- /dev/null +++ b/activerecord/test/cases/comment_test.rb @@ -0,0 +1,139 @@ +require "cases/helper" +require "support/schema_dumping_helper" + +if ActiveRecord::Base.connection.supports_comments? + + class CommentTest < ActiveRecord::TestCase + include SchemaDumpingHelper + + class Commented < ActiveRecord::Base + self.table_name = "commenteds" + end + + class BlankComment < ActiveRecord::Base + end + + setup do + @connection = ActiveRecord::Base.connection + + @connection.create_table("commenteds", comment: "A table with comment", force: true) do |t| + t.string "name", comment: "Comment should help clarify the column purpose" + t.boolean "obvious", comment: "Question is: should you comment obviously named objects?" + t.string "content" + t.index "name", comment: %Q["Very important" index that powers all the performance.\nAnd it's fun!] + end + + @connection.create_table("blank_comments", comment: " ", force: true) do |t| + t.string :space_comment, comment: " " + t.string :empty_comment, comment: "" + t.string :nil_comment, comment: nil + t.string :absent_comment + t.index :space_comment, comment: " " + t.index :empty_comment, comment: "" + t.index :nil_comment, comment: nil + t.index :absent_comment + end + + Commented.reset_column_information + BlankComment.reset_column_information + end + + teardown do + @connection.drop_table "commenteds", if_exists: true + @connection.drop_table "blank_comments", if_exists: true + end + + def test_column_created_in_block + column = Commented.columns_hash["name"] + assert_equal :string, column.type + assert_equal "Comment should help clarify the column purpose", column.comment + end + + def test_blank_columns_created_in_block + %w[ space_comment empty_comment nil_comment absent_comment ].each do |field| + column = BlankComment.columns_hash[field] + assert_equal :string, column.type + assert_nil column.comment + end + end + + def test_blank_indexes_created_in_block + @connection.indexes("blank_comments").each do |index| + assert_nil index.comment + end + end + + def test_add_column_with_comment_later + @connection.add_column :commenteds, :rating, :integer, comment: "I am running out of imagination" + Commented.reset_column_information + column = Commented.columns_hash["rating"] + + assert_equal :integer, column.type + assert_equal "I am running out of imagination", column.comment + end + + def test_add_index_with_comment_later + @connection.add_index :commenteds, :obvious, name: "idx_obvious", comment: "We need to see obvious comments" + index = @connection.indexes("commenteds").find { |idef| idef.name == "idx_obvious" } + assert_equal "We need to see obvious comments", index.comment + end + + def test_add_comment_to_column + @connection.change_column :commenteds, :content, :string, comment: "Whoa, content describes itself!" + + Commented.reset_column_information + column = Commented.columns_hash["content"] + + assert_equal :string, column.type + assert_equal "Whoa, content describes itself!", column.comment + end + + def test_remove_comment_from_column + @connection.change_column :commenteds, :obvious, :string, comment: nil + + Commented.reset_column_information + column = Commented.columns_hash["obvious"] + + assert_equal :string, column.type + assert_nil column.comment + end + + def test_schema_dump_with_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, :obvious, :string, comment: nil + @connection.add_index :commenteds, :obvious, name: "idx_obvious", comment: "We need to see obvious comments" + + # And check that these changes are reflected in dump + output = dump_table_schema "commenteds" + assert_match %r[create_table "commenteds",.+\s+comment: "A table with comment"], output + assert_match %r[t\.string\s+"name",\s+comment: "Comment should help clarify the column purpose"], output + assert_match %r[t\.string\s+"obvious"\n], output + assert_match %r[t\.string\s+"content",\s+comment: "Whoa, content describes itself!"], output + assert_match %r[t\.integer\s+"rating",\s+comment: "I am running out of imagination"], output + assert_match %r[t\.index\s+.+\s+comment: "\\\"Very important\\\" index that powers all the performance.\\nAnd it's fun!"], output + assert_match %r[t\.index\s+.+\s+name: "idx_obvious",.+\s+comment: "We need to see obvious comments"], output + end + + def test_schema_dump_omits_blank_comments + output = dump_table_schema "blank_comments" + + assert_match %r[create_table "blank_comments"], output + assert_no_match %r[create_table "blank_comments",.+comment:], output + + assert_match %r[t\.string\s+"space_comment"\n], output + assert_no_match %r[t\.string\s+"space_comment", comment:\n], output + + assert_match %r[t\.string\s+"empty_comment"\n], output + assert_no_match %r[t\.string\s+"empty_comment", comment:\n], output + + assert_match %r[t\.string\s+"nil_comment"\n], output + assert_no_match %r[t\.string\s+"nil_comment", comment:\n], output + + assert_match %r[t\.string\s+"absent_comment"\n], output + assert_no_match %r[t\.string\s+"absent_comment", comment:\n], output + end + end + +end diff --git a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb index 580568c8ac..64189381cb 100644 --- a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb +++ b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb @@ -17,27 +17,27 @@ module ActiveRecord end def test_in_use? - assert_not @adapter.in_use?, 'adapter is not in use' - assert @adapter.lease, 'lease adapter' - assert @adapter.in_use?, 'adapter is in use' + assert_not @adapter.in_use?, "adapter is not in use" + assert @adapter.lease, "lease adapter" + assert @adapter.in_use?, "adapter is in use" end def test_lease_twice - assert @adapter.lease, 'should lease adapter' + assert @adapter.lease, "should lease adapter" assert_raises(ActiveRecordError) do @adapter.lease end end def test_expire_mutates_in_use - assert @adapter.lease, 'lease adapter' - assert @adapter.in_use?, 'adapter is in use' + assert @adapter.lease, "lease adapter" + assert @adapter.in_use?, "adapter is in use" @adapter.expire - assert_not @adapter.in_use?, 'adapter is in use' + assert_not @adapter.in_use?, "adapter is in use" end def test_close - pool = Pool.new(ConnectionSpecification.new({}, nil)) + pool = Pool.new(ConnectionSpecification.new("primary", {}, nil)) pool.insert_connection_for_test! @adapter @adapter.pool = pool diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb index 9b1865e8bb..d5d16e7568 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -4,43 +4,40 @@ module ActiveRecord module ConnectionAdapters class ConnectionHandlerTest < ActiveRecord::TestCase def setup - @klass = Class.new(Base) { def self.name; 'klass'; end } - @subklass = Class.new(@klass) { def self.name; 'subklass'; end } - @handler = ConnectionHandler.new - @pool = @handler.establish_connection(@klass, Base.connection_pool.spec) + @spec_name = "primary" + @pool = @handler.establish_connection(ActiveRecord::Base.configurations["arunit"]) + end + + def test_establish_connection_uses_spec_name + config = { "readonly" => { "adapter" => "sqlite3" } } + resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(config) + spec = resolver.spec(:readonly) + @handler.establish_connection(spec.to_hash) + + assert_not_nil @handler.retrieve_connection_pool("readonly") + ensure + @handler.remove_connection("readonly") end def test_retrieve_connection - assert @handler.retrieve_connection(@klass) + assert @handler.retrieve_connection(@spec_name) end def test_active_connections? assert !@handler.active_connections? - assert @handler.retrieve_connection(@klass) + assert @handler.retrieve_connection(@spec_name) assert @handler.active_connections? @handler.clear_active_connections! assert !@handler.active_connections? end - def test_retrieve_connection_pool_with_ar_base - assert_nil @handler.retrieve_connection_pool(ActiveRecord::Base) - end - def test_retrieve_connection_pool - assert_not_nil @handler.retrieve_connection_pool(@klass) + assert_not_nil @handler.retrieve_connection_pool(@spec_name) end - def test_retrieve_connection_pool_uses_superclass_when_no_subclass_connection - assert_not_nil @handler.retrieve_connection_pool(@subklass) - end - - def test_retrieve_connection_pool_uses_superclass_pool_after_subclass_establish_and_remove - sub_pool = @handler.establish_connection(@subklass, Base.connection_pool.spec) - assert_same sub_pool, @handler.retrieve_connection_pool(@subklass) - - @handler.remove_connection @subklass - assert_same @pool, @handler.retrieve_connection_pool(@subklass) + def test_retrieve_connection_pool_with_invalid_id + assert_nil @handler.retrieve_connection_pool("foo") end def test_connection_pools @@ -71,7 +68,7 @@ module ActiveRecord def test_retrieve_connection_pool_copies_schema_cache_from_ancestor_pool @pool.schema_cache = @pool.connection.schema_cache - @pool.schema_cache.add('posts') + @pool.schema_cache.add("posts") rd, wr = IO.pipe rd.binmode @@ -79,7 +76,7 @@ module ActiveRecord pid = fork { rd.close - pool = @handler.retrieve_connection_pool(@klass) + pool = @handler.retrieve_connection_pool(@spec_name) wr.write Marshal.dump pool.schema_cache.size wr.close exit! @@ -91,6 +88,36 @@ module ActiveRecord assert_equal @pool.schema_cache.size, Marshal.load(rd.read) rd.close end + + def test_a_class_using_custom_pool_and_switching_back_to_primary + klass2 = Class.new(Base) { def self.name; "klass2"; end } + + assert_equal klass2.connection.object_id, ActiveRecord::Base.connection.object_id + + pool = klass2.establish_connection(ActiveRecord::Base.connection_pool.spec.config) + assert_equal klass2.connection.object_id, pool.connection.object_id + refute_equal klass2.connection.object_id, ActiveRecord::Base.connection.object_id + + klass2.remove_connection + + assert_equal klass2.connection.object_id, ActiveRecord::Base.connection.object_id + end + + def test_connection_specification_name_should_fallback_to_parent + klassA = Class.new(Base) + klassB = Class.new(klassA) + + assert_equal klassB.connection_specification_name, klassA.connection_specification_name + klassA.connection_specification_name = "readonly" + assert_equal "readonly", klassB.connection_specification_name + end + + def test_remove_connection_should_not_remove_parent + klass2 = Class.new(Base) { def self.name; "klass2"; end } + klass2.remove_connection + refute_nil ActiveRecord::Base.connection.object_id + assert_equal klass2.connection.object_id, ActiveRecord::Base.connection.object_id + end end end end diff --git a/activerecord/test/cases/connection_adapters/connection_specification_test.rb b/activerecord/test/cases/connection_adapters/connection_specification_test.rb index ea2196cda2..10a3521c79 100644 --- a/activerecord/test/cases/connection_adapters/connection_specification_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_specification_test.rb @@ -4,7 +4,7 @@ module ActiveRecord module ConnectionAdapters class ConnectionSpecificationTest < ActiveRecord::TestCase def test_dup_deep_copy_config - spec = ConnectionSpecification.new({ :a => :b }, "bar") + spec = ConnectionSpecification.new("primary", { a: :b }, "bar") assert_not_equal(spec.config.object_id, spec.dup.config.object_id) end 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 9ee92a3cd2..4bb5c4f2e2 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 @@ -24,43 +24,43 @@ module ActiveRecord end def test_resolver_with_database_uri_and_current_env_symbol_key - ENV['DATABASE_URL'] = "postgres://localhost/foo" + ENV["DATABASE_URL"] = "postgres://localhost/foo" config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } } actual = resolve_spec(:default_env, config) - expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" } + expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost", "name"=>"default_env" } assert_equal expected, actual end def test_resolver_with_database_uri_and_current_env_symbol_key_and_rails_env - ENV['DATABASE_URL'] = "postgres://localhost/foo" - ENV['RAILS_ENV'] = "foo" + ENV["DATABASE_URL"] = "postgres://localhost/foo" + ENV["RAILS_ENV"] = "foo" config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } } actual = resolve_spec(:foo, config) - expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" } + expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost","name"=>"foo" } assert_equal expected, actual end def test_resolver_with_database_uri_and_current_env_symbol_key_and_rack_env - ENV['DATABASE_URL'] = "postgres://localhost/foo" - ENV['RACK_ENV'] = "foo" + ENV["DATABASE_URL"] = "postgres://localhost/foo" + ENV["RACK_ENV"] = "foo" config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } } actual = resolve_spec(:foo, config) - expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" } + expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost","name"=>"foo" } assert_equal expected, actual end def test_resolver_with_database_uri_and_known_key - ENV['DATABASE_URL'] = "postgres://localhost/foo" + ENV["DATABASE_URL"] = "postgres://localhost/foo" config = { "production" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } } actual = resolve_spec(:production, config) - expected = { "adapter"=>"not_postgres", "database"=>"not_foo", "host"=>"localhost" } + expected = { "adapter"=>"not_postgres", "database"=>"not_foo", "host"=>"localhost", "name"=>"production" } assert_equal expected, actual end def test_resolver_with_database_uri_and_unknown_symbol_key - ENV['DATABASE_URL'] = "postgres://localhost/foo" + ENV["DATABASE_URL"] = "postgres://localhost/foo" config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } } assert_raises AdapterNotSpecified do resolve_spec(:production, config) @@ -68,7 +68,7 @@ module ActiveRecord end def test_resolver_with_database_uri_and_supplied_url - ENV['DATABASE_URL'] = "not-postgres://not-localhost/not_foo" + ENV["DATABASE_URL"] = "not-postgres://not-localhost/not_foo" config = { "production" => { "adapter" => "also_not_postgres", "database" => "also_not_foo" } } actual = resolve_spec("postgres://localhost/foo", config) expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" } @@ -82,7 +82,7 @@ module ActiveRecord end def test_environment_does_not_exist_in_config_url_does_exist - ENV['DATABASE_URL'] = "postgres://localhost/foo" + ENV["DATABASE_URL"] = "postgres://localhost/foo" config = { "not_default_env" => { "adapter" => "not_postgres", "database" => "not_foo" } } actual = resolve_config(config) expect_prod = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" } @@ -90,10 +90,10 @@ module ActiveRecord end def test_url_with_hyphenated_scheme - ENV['DATABASE_URL'] = "ibm-db://localhost/foo" + ENV["DATABASE_URL"] = "ibm-db://localhost/foo" config = { "default_env" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } } actual = resolve_spec(:default_env, config) - expected = { "adapter"=>"ibm_db", "database"=>"foo", "host"=>"localhost" } + expected = { "adapter"=>"ibm_db", "database"=>"foo", "host"=>"localhost", "name"=>"default_env" } assert_equal expected, actual end @@ -134,7 +134,7 @@ module ActiveRecord end def test_blank_with_database_url - ENV['DATABASE_URL'] = "postgres://localhost/foo" + ENV["DATABASE_URL"] = "postgres://localhost/foo" config = {} actual = resolve_config(config) @@ -152,8 +152,8 @@ module ActiveRecord end def test_blank_with_database_url_with_rails_env - ENV['RAILS_ENV'] = "not_production" - ENV['DATABASE_URL'] = "postgres://localhost/foo" + ENV["RAILS_ENV"] = "not_production" + ENV["DATABASE_URL"] = "postgres://localhost/foo" config = {} actual = resolve_config(config) @@ -174,8 +174,8 @@ module ActiveRecord end def test_blank_with_database_url_with_rack_env - ENV['RACK_ENV'] = "not_production" - ENV['DATABASE_URL'] = "postgres://localhost/foo" + ENV["RACK_ENV"] = "not_production" + ENV["DATABASE_URL"] = "postgres://localhost/foo" config = {} actual = resolve_config(config) @@ -196,7 +196,7 @@ module ActiveRecord end def test_database_url_with_ipv6_host_and_port - ENV['DATABASE_URL'] = "postgres://[::1]:5454/foo" + ENV["DATABASE_URL"] = "postgres://[::1]:5454/foo" config = {} actual = resolve_config(config) @@ -208,7 +208,7 @@ module ActiveRecord end def test_url_sub_key_with_database_url - ENV['DATABASE_URL'] = "NOT-POSTGRES://localhost/NOT_FOO" + ENV["DATABASE_URL"] = "NOT-POSTGRES://localhost/NOT_FOO" config = { "default_env" => { "url" => "postgres://localhost/foo" } } actual = resolve_config(config) @@ -222,9 +222,9 @@ module ActiveRecord end def test_merge_no_conflicts_with_database_url - ENV['DATABASE_URL'] = "postgres://localhost/foo" + ENV["DATABASE_URL"] = "postgres://localhost/foo" - config = {"default_env" => { "pool" => "5" } } + config = { "default_env" => { "pool" => "5" } } actual = resolve_config(config) expected = { "default_env" => { "adapter" => "postgresql", @@ -237,9 +237,9 @@ module ActiveRecord end def test_merge_conflicts_with_database_url - ENV['DATABASE_URL'] = "postgres://localhost/foo" + ENV["DATABASE_URL"] = "postgres://localhost/foo" - config = {"default_env" => { "adapter" => "NOT-POSTGRES", "database" => "NOT-FOO", "pool" => "5" } } + config = { "default_env" => { "adapter" => "NOT-POSTGRES", "database" => "NOT-FOO", "pool" => "5" } } actual = resolve_config(config) expected = { "default_env" => { "adapter" => "postgresql", diff --git a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb index f2b1d9e4e7..3657b8340d 100644 --- a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb +++ b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb @@ -1,69 +1,69 @@ require "cases/helper" if current_adapter?(:Mysql2Adapter) -module ActiveRecord - module ConnectionAdapters - class MysqlTypeLookupTest < ActiveRecord::TestCase - setup do - @connection = ActiveRecord::Base.connection - end + module ActiveRecord + module ConnectionAdapters + class MysqlTypeLookupTest < ActiveRecord::TestCase + setup do + @connection = ActiveRecord::Base.connection + end - def test_boolean_types - emulate_booleans(true) do - assert_lookup_type :boolean, 'tinyint(1)' - assert_lookup_type :boolean, 'TINYINT(1)' + def test_boolean_types + emulate_booleans(true) do + assert_lookup_type :boolean, "tinyint(1)" + assert_lookup_type :boolean, "TINYINT(1)" + end end - end - def test_string_types - assert_lookup_type :string, "enum('one', 'two', 'three')" - assert_lookup_type :string, "ENUM('one', 'two', 'three')" - assert_lookup_type :string, "set('one', 'two', 'three')" - assert_lookup_type :string, "SET('one', 'two', 'three')" - end + def test_string_types + assert_lookup_type :string, "enum('one', 'two', 'three')" + assert_lookup_type :string, "ENUM('one', 'two', 'three')" + assert_lookup_type :string, "set('one', 'two', 'three')" + assert_lookup_type :string, "SET('one', 'two', 'three')" + end - def test_set_type_with_value_matching_other_type - assert_lookup_type :string, "SET('unicode', '8bit', 'none', 'time')" - end + def test_set_type_with_value_matching_other_type + assert_lookup_type :string, "SET('unicode', '8bit', 'none', 'time')" + end - def test_enum_type_with_value_matching_other_type - assert_lookup_type :string, "ENUM('unicode', '8bit', 'none')" - end + def test_enum_type_with_value_matching_other_type + assert_lookup_type :string, "ENUM('unicode', '8bit', 'none')" + end - def test_binary_types - assert_lookup_type :binary, 'bit' - assert_lookup_type :binary, 'BIT' - end + def test_binary_types + assert_lookup_type :binary, "bit" + assert_lookup_type :binary, "BIT" + end - def test_integer_types - emulate_booleans(false) do - assert_lookup_type :integer, 'tinyint(1)' - assert_lookup_type :integer, 'TINYINT(1)' - assert_lookup_type :integer, 'year' - assert_lookup_type :integer, 'YEAR' + def test_integer_types + emulate_booleans(false) do + assert_lookup_type :integer, "tinyint(1)" + assert_lookup_type :integer, "TINYINT(1)" + assert_lookup_type :integer, "year" + assert_lookup_type :integer, "YEAR" + end end - end - private + private - def assert_lookup_type(type, lookup) - cast_type = @connection.type_map.lookup(lookup) - assert_equal type, cast_type.type - end + def assert_lookup_type(type, lookup) + cast_type = @connection.type_map.lookup(lookup) + assert_equal type, cast_type.type + end - def emulate_booleans(value) - old_emulate_booleans = @connection.emulate_booleans - change_emulate_booleans(value) - yield - ensure - change_emulate_booleans(old_emulate_booleans) - end + def emulate_booleans(value) + old_emulate_booleans = @connection.emulate_booleans + change_emulate_booleans(value) + yield + ensure + change_emulate_booleans(old_emulate_booleans) + end - def change_emulate_booleans(value) - @connection.emulate_booleans = value - @connection.clear_cache! + def change_emulate_booleans(value) + @connection.emulate_booleans = value + @connection.clear_cache! + end end end end end -end diff --git a/activerecord/test/cases/connection_adapters/schema_cache_test.rb b/activerecord/test/cases/connection_adapters/schema_cache_test.rb index db832fe55d..d4459603af 100644 --- a/activerecord/test/cases/connection_adapters/schema_cache_test.rb +++ b/activerecord/test/cases/connection_adapters/schema_cache_test.rb @@ -9,28 +9,28 @@ module ActiveRecord end def test_primary_key - assert_equal 'id', @cache.primary_keys('posts') + assert_equal "id", @cache.primary_keys("posts") end def test_primary_key_for_non_existent_table - assert_nil @cache.primary_keys('omgponies') + assert_nil @cache.primary_keys("omgponies") end def test_caches_columns - columns = @cache.columns('posts') - assert_equal columns, @cache.columns('posts') + columns = @cache.columns("posts") + assert_equal columns, @cache.columns("posts") end def test_caches_columns_hash - columns_hash = @cache.columns_hash('posts') - assert_equal columns_hash, @cache.columns_hash('posts') + columns_hash = @cache.columns_hash("posts") + assert_equal columns_hash, @cache.columns_hash("posts") end def test_clearing - @cache.columns('posts') - @cache.columns_hash('posts') - @cache.data_sources('posts') - @cache.primary_keys('posts') + @cache.columns("posts") + @cache.columns_hash("posts") + @cache.data_sources("posts") + @cache.primary_keys("posts") @cache.clear! @@ -38,23 +38,23 @@ module ActiveRecord end def test_dump_and_load - @cache.columns('posts') - @cache.columns_hash('posts') - @cache.data_sources('posts') - @cache.primary_keys('posts') + @cache.columns("posts") + @cache.columns_hash("posts") + @cache.data_sources("posts") + @cache.primary_keys("posts") @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_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 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') } + assert_deprecated { assert @cache.table_exists?("posts") } + assert_deprecated { assert @cache.tables("posts") } + assert_deprecated { @cache.clear_table_cache!("posts") } 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 7566863653..e2e5445a4e 100644 --- a/activerecord/test/cases/connection_adapters/type_lookup_test.rb +++ b/activerecord/test/cases/connection_adapters/type_lookup_test.rb @@ -1,110 +1,110 @@ require "cases/helper" -unless current_adapter?(:PostgreSQLAdapter) # PostgreSQL does not use type strigns for lookup -module ActiveRecord - module ConnectionAdapters - class TypeLookupTest < ActiveRecord::TestCase - setup do - @connection = ActiveRecord::Base.connection - end +unless current_adapter?(:PostgreSQLAdapter) # PostgreSQL does not use type strings for lookup + module ActiveRecord + module ConnectionAdapters + class TypeLookupTest < ActiveRecord::TestCase + setup do + @connection = ActiveRecord::Base.connection + end - def test_boolean_types - assert_lookup_type :boolean, 'boolean' - assert_lookup_type :boolean, 'BOOLEAN' - end + def test_boolean_types + assert_lookup_type :boolean, "boolean" + assert_lookup_type :boolean, "BOOLEAN" + end - def test_string_types - assert_lookup_type :string, 'char' - assert_lookup_type :string, 'varchar' - assert_lookup_type :string, 'VARCHAR' - assert_lookup_type :string, 'varchar(255)' - assert_lookup_type :string, 'character varying' - end + def test_string_types + assert_lookup_type :string, "char" + assert_lookup_type :string, "varchar" + assert_lookup_type :string, "VARCHAR" + assert_lookup_type :string, "varchar(255)" + assert_lookup_type :string, "character varying" + end - def test_binary_types - assert_lookup_type :binary, 'binary' - assert_lookup_type :binary, 'BINARY' - assert_lookup_type :binary, 'blob' - assert_lookup_type :binary, 'BLOB' - end + def test_binary_types + assert_lookup_type :binary, "binary" + assert_lookup_type :binary, "BINARY" + assert_lookup_type :binary, "blob" + assert_lookup_type :binary, "BLOB" + end - def test_text_types - assert_lookup_type :text, 'text' - assert_lookup_type :text, 'TEXT' - assert_lookup_type :text, 'clob' - assert_lookup_type :text, 'CLOB' - end + def test_text_types + assert_lookup_type :text, "text" + assert_lookup_type :text, "TEXT" + assert_lookup_type :text, "clob" + assert_lookup_type :text, "CLOB" + end - def test_date_types - assert_lookup_type :date, 'date' - assert_lookup_type :date, 'DATE' - end + def test_date_types + assert_lookup_type :date, "date" + assert_lookup_type :date, "DATE" + end - def test_time_types - assert_lookup_type :time, 'time' - assert_lookup_type :time, 'TIME' - end + def test_time_types + assert_lookup_type :time, "time" + assert_lookup_type :time, "TIME" + end - def test_datetime_types - assert_lookup_type :datetime, 'datetime' - assert_lookup_type :datetime, 'DATETIME' - assert_lookup_type :datetime, 'timestamp' - assert_lookup_type :datetime, 'TIMESTAMP' - end + def test_datetime_types + assert_lookup_type :datetime, "datetime" + assert_lookup_type :datetime, "DATETIME" + assert_lookup_type :datetime, "timestamp" + assert_lookup_type :datetime, "TIMESTAMP" + end - def test_decimal_types - assert_lookup_type :decimal, 'decimal' - assert_lookup_type :decimal, 'decimal(2,8)' - assert_lookup_type :decimal, 'DECIMAL' - assert_lookup_type :decimal, 'numeric' - assert_lookup_type :decimal, 'numeric(2,8)' - assert_lookup_type :decimal, 'NUMERIC' - assert_lookup_type :decimal, 'number' - assert_lookup_type :decimal, 'number(2,8)' - assert_lookup_type :decimal, 'NUMBER' - end + def test_decimal_types + assert_lookup_type :decimal, "decimal" + assert_lookup_type :decimal, "decimal(2,8)" + assert_lookup_type :decimal, "DECIMAL" + assert_lookup_type :decimal, "numeric" + assert_lookup_type :decimal, "numeric(2,8)" + assert_lookup_type :decimal, "NUMERIC" + assert_lookup_type :decimal, "number" + assert_lookup_type :decimal, "number(2,8)" + assert_lookup_type :decimal, "NUMBER" + end - def test_float_types - assert_lookup_type :float, 'float' - assert_lookup_type :float, 'FLOAT' - assert_lookup_type :float, 'double' - assert_lookup_type :float, 'DOUBLE' - end + def test_float_types + assert_lookup_type :float, "float" + assert_lookup_type :float, "FLOAT" + assert_lookup_type :float, "double" + assert_lookup_type :float, "DOUBLE" + end - def test_integer_types - assert_lookup_type :integer, 'integer' - assert_lookup_type :integer, 'INTEGER' - assert_lookup_type :integer, 'tinyint' - assert_lookup_type :integer, 'smallint' - assert_lookup_type :integer, 'bigint' - end + def test_integer_types + assert_lookup_type :integer, "integer" + assert_lookup_type :integer, "INTEGER" + assert_lookup_type :integer, "tinyint" + assert_lookup_type :integer, "smallint" + assert_lookup_type :integer, "bigint" + end - def test_bigint_limit - cast_type = @connection.type_map.lookup("bigint") - if current_adapter?(:OracleAdapter) - assert_equal 19, cast_type.limit - else - assert_equal 8, cast_type.limit + def test_bigint_limit + cast_type = @connection.type_map.lookup("bigint") + if current_adapter?(:OracleAdapter) + assert_equal 19, cast_type.limit + else + assert_equal 8, cast_type.limit + end end - 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) + 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) + assert_equal :decimal, cast_type.type + assert_equal 2, cast_type.cast(2.1) + end end - end - private + private - def assert_lookup_type(type, lookup) - cast_type = @connection.type_map.lookup(lookup) - assert_equal type, cast_type.type + def assert_lookup_type(type, lookup) + cast_type = @connection.type_map.lookup(lookup) + assert_equal type, cast_type.type + end end end end end -end diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb index d43668e57c..d1e946d401 100644 --- a/activerecord/test/cases/connection_management_test.rb +++ b/activerecord/test/cases/connection_management_test.rb @@ -4,6 +4,8 @@ require "rack" module ActiveRecord module ConnectionAdapters class ConnectionManagementTest < ActiveRecord::TestCase + self.use_transactional_tests = false + class App attr_reader :calls def initialize @@ -12,14 +14,14 @@ module ActiveRecord def call(env) @calls << env - [200, {}, ['hi mom']] + [200, {}, ["hi mom"]] end end def setup @env = {} @app = App.new - @management = ConnectionManagement.new(@app) + @management = middleware(@app) # make sure we have an active connection assert ActiveRecord::Base.connection @@ -27,22 +29,17 @@ module ActiveRecord end def test_app_delegation - manager = ConnectionManagement.new(@app) + manager = middleware(@app) manager.call @env assert_equal [@env], @app.calls end - def test_connections_are_active_after_call - @management.call(@env) - assert ActiveRecord::Base.connection_handler.active_connections? - end - def test_body_responds_to_each _, _, body = @management.call(@env) bits = [] body.each { |bit| bits << bit } - assert_equal ['hi mom'], bits + assert_equal ["hi mom"], bits end def test_connections_are_cleared_after_body_close @@ -51,56 +48,65 @@ module ActiveRecord assert !ActiveRecord::Base.connection_handler.active_connections? end - def test_active_connections_are_not_cleared_on_body_close_during_test - @env['rack.test'] = true - _, _, body = @management.call(@env) - body.close - assert ActiveRecord::Base.connection_handler.active_connections? + def test_active_connections_are_not_cleared_on_body_close_during_transaction + ActiveRecord::Base.transaction do + _, _, body = @management.call(@env) + body.close + assert ActiveRecord::Base.connection_handler.active_connections? + end end def test_connections_closed_if_exception app = Class.new(App) { def call(env); raise NotImplementedError; end }.new - explosive = ConnectionManagement.new(app) + explosive = middleware(app) assert_raises(NotImplementedError) { explosive.call(@env) } assert !ActiveRecord::Base.connection_handler.active_connections? end - def test_connections_not_closed_if_exception_and_test - @env['rack.test'] = true - app = Class.new(App) { def call(env); raise; end }.new - explosive = ConnectionManagement.new(app) - assert_raises(RuntimeError) { explosive.call(@env) } - assert ActiveRecord::Base.connection_handler.active_connections? - end - - def test_connections_closed_if_exception_and_explicitly_not_test - @env['rack.test'] = false - app = Class.new(App) { def call(env); raise NotImplementedError; end }.new - explosive = ConnectionManagement.new(app) - assert_raises(NotImplementedError) { explosive.call(@env) } - assert !ActiveRecord::Base.connection_handler.active_connections? + def test_connections_not_closed_if_exception_inside_transaction + ActiveRecord::Base.transaction do + app = Class.new(App) { def call(env); raise RuntimeError; end }.new + explosive = middleware(app) + assert_raises(RuntimeError) { explosive.call(@env) } + assert ActiveRecord::Base.connection_handler.active_connections? + end end test "doesn't clear active connections when running in a test case" do - @env['rack.test'] = true - @management.call(@env) - assert ActiveRecord::Base.connection_handler.active_connections? + executor.wrap do + @management.call(@env) + assert ActiveRecord::Base.connection_handler.active_connections? + end end test "proxy is polite to its body and responds to it" do body = Class.new(String) { def to_path; "/path"; end }.new app = lambda { |_| [200, {}, body] } - response_body = ConnectionManagement.new(app).call(@env)[2] + response_body = middleware(app).call(@env)[2] assert response_body.respond_to?(:to_path) assert_equal "/path", response_body.to_path end test "doesn't mutate the original response" do - original_response = [200, {}, 'hi'] + original_response = [200, {}, "hi"] app = lambda { |_| original_response } - ConnectionManagement.new(app).call(@env)[2] - assert_equal 'hi', original_response.last + middleware(app).call(@env)[2] + assert_equal "hi", original_response.last end + + private + def executor + @executor ||= Class.new(ActiveSupport::Executor).tap do |exe| + ActiveRecord::QueryCache.install_executor_hooks(exe) + end + end + + def middleware(app) + lambda do |env| + a, b, c = executor.wrap { app.call(env) } + [a, b, Rack::BodyProxy.new(c) {}] + end + end end end end diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index efa3e0455e..d7ff9d6880 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'concurrent/atomic/count_down_latch' +require "concurrent/atomic/count_down_latch" module ActiveRecord module ConnectionAdapters @@ -151,7 +151,7 @@ module ActiveRecord assert_equal 1, active_connections(@pool).size ensure - @pool.connections.each(&:close) + @pool.connections.each { |conn| conn.close if conn.in_use? } end def test_remove_connection @@ -335,11 +335,22 @@ module ActiveRecord # is called with an anonymous class def test_anonymous_class_exception anonymous = Class.new(ActiveRecord::Base) - handler = ActiveRecord::Base.connection_handler - assert_raises(RuntimeError) { - handler.establish_connection anonymous, nil - } + assert_raises(RuntimeError) do + anonymous.establish_connection + end + 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 + assert_equal [:config, :connection_id, :spec_name], payloads[0].keys.sort + assert_equal "primary", payloads[0][:spec_name] + ensure + ActiveSupport::Notifications.unsubscribe(subscription) if subscription end def test_pool_sets_connection_schema_cache @@ -384,8 +395,8 @@ module ActiveRecord all_threads_in_new_connection.wait end rescue Timeout::Error - flunk 'pool unable to establish connections concurrently or implementation has ' << - 'changed, this test then needs to patch a different :new_connection method' + 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 all_go.count_down @@ -433,6 +444,9 @@ module ActiveRecord Thread.new { @pool.send(group_action_method) }.join # assert connection has been forcefully taken away from us assert_not @pool.active_connection? + + # make a new connection for with_connection to clean up + @pool.connection end end end @@ -505,7 +519,7 @@ module ActiveRecord pool.clear_reloadable_connections unless stuck_thread.join(2) - flunk 'clear_reloadable_connections must not let other connection waiting threads get stuck in queue' + flunk "clear_reloadable_connections must not let other connection waiting threads get stuck in queue" end assert_equal 0, pool.num_waiting_in_queue @@ -513,13 +527,13 @@ module ActiveRecord end private - def with_single_connection_pool - one_conn_spec = ActiveRecord::Base.connection_pool.spec.dup - one_conn_spec.config[:pool] = 1 # this is safe to do, because .dupped ConnectionSpecification also auto-dups its config - yield(pool = ConnectionPool.new(one_conn_spec)) - ensure - pool.disconnect! if pool - end + def with_single_connection_pool + one_conn_spec = ActiveRecord::Base.connection_pool.spec.dup + one_conn_spec.config[:pool] = 1 # this is safe to do, because .dupped ConnectionSpecification also auto-dups its config + yield(pool = ConnectionPool.new(one_conn_spec)) + ensure + pool.disconnect! if pool + end end end end diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb index 358b6ad537..0f62c73f8f 100644 --- a/activerecord/test/cases/connection_specification/resolver_test.rb +++ b/activerecord/test/cases/connection_specification/resolver_test.rb @@ -14,7 +14,7 @@ module ActiveRecord def test_url_invalid_adapter error = assert_raises(LoadError) do - spec 'ridiculous://foo?encoding=utf8' + spec "ridiculous://foo?encoding=utf8" end assert_match "Could not load 'active_record/connection_adapters/ridiculous_adapter'", error.message @@ -24,33 +24,36 @@ module ActiveRecord # checks that the adapter file can be required in. def test_url_from_environment - spec = resolve :production, 'production' => 'abstract://foo?encoding=utf8' + spec = resolve :production, "production" => "abstract://foo?encoding=utf8" assert_equal({ "adapter" => "abstract", "host" => "foo", - "encoding" => "utf8" }, spec) + "encoding" => "utf8", + "name" => "production" }, spec) end def test_url_sub_key - spec = resolve :production, 'production' => {"url" => 'abstract://foo?encoding=utf8'} + spec = resolve :production, "production" => { "url" => "abstract://foo?encoding=utf8" } assert_equal({ "adapter" => "abstract", "host" => "foo", - "encoding" => "utf8" }, spec) + "encoding" => "utf8", + "name" => "production" }, spec) end def test_url_sub_key_merges_correctly - hash = {"url" => 'abstract://foo?encoding=utf8&', "adapter" => "sqlite3", "host" => "bar", "pool" => "3"} - spec = resolve :production, 'production' => hash + hash = { "url" => "abstract://foo?encoding=utf8&", "adapter" => "sqlite3", "host" => "bar", "pool" => "3" } + spec = resolve :production, "production" => hash assert_equal({ "adapter" => "abstract", "host" => "foo", "encoding" => "utf8", - "pool" => "3" }, spec) + "pool" => "3", + "name" => "production" }, spec) end def test_url_host_no_db - spec = resolve 'abstract://foo?encoding=utf8' + spec = resolve "abstract://foo?encoding=utf8" assert_equal({ "adapter" => "abstract", "host" => "foo", @@ -58,13 +61,13 @@ module ActiveRecord end def test_url_missing_scheme - spec = resolve 'foo' + spec = resolve "foo" assert_equal({ "database" => "foo" }, spec) end def test_url_host_db - spec = resolve 'abstract://foo/bar?encoding=utf8' + spec = resolve "abstract://foo/bar?encoding=utf8" assert_equal({ "adapter" => "abstract", "database" => "bar", @@ -73,7 +76,7 @@ module ActiveRecord end def test_url_port - spec = resolve 'abstract://foo:123?encoding=utf8' + spec = resolve "abstract://foo:123?encoding=utf8" assert_equal({ "adapter" => "abstract", "port" => 123, @@ -82,40 +85,50 @@ module ActiveRecord end def test_encoded_password - password = 'am@z1ng_p@ssw0rd#!' + password = "am@z1ng_p@ssw0rd#!" encoded_password = URI.encode_www_form_component(password) spec = resolve "abstract://foo:#{encoded_password}@localhost/bar" assert_equal password, spec["password"] end def test_url_with_authority_for_sqlite3 - spec = resolve 'sqlite3:///foo_test' - assert_equal('/foo_test', spec["database"]) + spec = resolve "sqlite3:///foo_test" + assert_equal("/foo_test", spec["database"]) end def test_url_absolute_path_for_sqlite3 - spec = resolve 'sqlite3:/foo_test' - assert_equal('/foo_test', spec["database"]) + spec = resolve "sqlite3:/foo_test" + assert_equal("/foo_test", spec["database"]) end def test_url_relative_path_for_sqlite3 - spec = resolve 'sqlite3:foo_test' - assert_equal('foo_test', spec["database"]) + spec = resolve "sqlite3:foo_test" + assert_equal("foo_test", spec["database"]) end def test_url_memory_db_for_sqlite3 - spec = resolve 'sqlite3::memory:' - assert_equal(':memory:', spec["database"]) + spec = resolve "sqlite3::memory:" + assert_equal(":memory:", spec["database"]) end def test_url_sub_key_for_sqlite3 - spec = resolve :production, 'production' => {"url" => 'sqlite3:foo?encoding=utf8'} + spec = resolve :production, "production" => { "url" => "sqlite3:foo?encoding=utf8" } assert_equal({ "adapter" => "sqlite3", "database" => "foo", - "encoding" => "utf8" }, spec) + "encoding" => "utf8", + "name" => "production" }, spec) + end + + def test_spec_name_on_key_lookup + spec = spec(:readonly, "readonly" => { "adapter" => "sqlite3" }) + assert_equal "readonly", spec.name end + def test_spec_name_with_inline_config + spec = spec("adapter" => "sqlite3") + assert_equal "primary", spec.name, "should default to primary id" + end end end end diff --git a/activerecord/test/cases/core_test.rb b/activerecord/test/cases/core_test.rb index 3cb98832c5..3735572898 100644 --- a/activerecord/test/cases/core_test.rb +++ b/activerecord/test/cases/core_test.rb @@ -1,8 +1,8 @@ -require 'cases/helper' -require 'models/person' -require 'models/topic' -require 'pp' -require 'active_support/core_ext/string/strip' +require "cases/helper" +require "models/person" +require "models/topic" +require "pp" +require "active_support/core_ext/string/strip" class NonExistentTable < ActiveRecord::Base; end @@ -10,8 +10,8 @@ class CoreTest < ActiveRecord::TestCase fixtures :topics def test_inspect_class - assert_equal 'ActiveRecord::Base', ActiveRecord::Base.inspect - assert_equal 'LoosePerson(abstract)', LoosePerson.inspect + assert_equal "ActiveRecord::Base", ActiveRecord::Base.inspect + assert_equal "LoosePerson(abstract)", LoosePerson.inspect assert_match(/^Topic\(id: integer, title: string/, Topic.inspect) end @@ -25,8 +25,8 @@ class CoreTest < ActiveRecord::TestCase end def test_inspect_limited_select_instance - assert_equal %(#<Topic id: 1>), Topic.all.merge!(:select => 'id', :where => 'id = 1').first.inspect - assert_equal %(#<Topic id: 1, title: "The First Topic">), Topic.all.merge!(:select => 'id, title', :where => 'id = 1').first.inspect + assert_equal %(#<Topic id: 1>), Topic.all.merge!(select: "id", where: "id = 1").first.inspect + assert_equal %(#<Topic id: 1, title: "The First Topic">), Topic.all.merge!(select: "id, title", where: "id = 1").first.inspect end def test_inspect_class_without_table @@ -35,7 +35,7 @@ class CoreTest < ActiveRecord::TestCase def test_pretty_print_new topic = Topic.new - actual = '' + actual = "" PP.pp(topic, StringIO.new(actual)) expected = <<-PRETTY.strip_heredoc #<Topic:0xXXXXXX @@ -58,13 +58,13 @@ class CoreTest < ActiveRecord::TestCase created_at: nil, updated_at: nil> PRETTY - assert actual.start_with?(expected.split('XXXXXX').first) - assert actual.end_with?(expected.split('XXXXXX').last) + assert actual.start_with?(expected.split("XXXXXX").first) + assert actual.end_with?(expected.split("XXXXXX").last) end def test_pretty_print_persisted topic = topics(:first) - actual = '' + actual = "" PP.pp(topic, StringIO.new(actual)) expected = <<-PRETTY.strip_heredoc #<Topic:0x\\w+ @@ -92,11 +92,11 @@ class CoreTest < ActiveRecord::TestCase def test_pretty_print_uninitialized topic = Topic.allocate - actual = '' + actual = "" PP.pp(topic, StringIO.new(actual)) expected = "#<Topic:XXXXXX not initialized>\n" - assert actual.start_with?(expected.split('XXXXXX').first) - assert actual.end_with?(expected.split('XXXXXX').last) + assert actual.start_with?(expected.split("XXXXXX").first) + assert actual.end_with?(expected.split("XXXXXX").last) end def test_pretty_print_overridden_by_inspect @@ -105,7 +105,7 @@ class CoreTest < ActiveRecord::TestCase "inspecting topic" end end - actual = '' + actual = "" PP.pp(subtopic.new, StringIO.new(actual)) assert_equal "inspecting topic\n", actual end diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb index 922cb59280..84f2c3a465 100644 --- a/activerecord/test/cases/counter_cache_test.rb +++ b/activerecord/test/cases/counter_cache_test.rb @@ -1,30 +1,30 @@ -require 'cases/helper' -require 'models/topic' -require 'models/car' -require 'models/aircraft' -require 'models/wheel' -require 'models/engine' -require 'models/reply' -require 'models/category' -require 'models/categorization' -require 'models/dog' -require 'models/dog_lover' -require 'models/person' -require 'models/friendship' -require 'models/subscriber' -require 'models/subscription' -require 'models/book' +require "cases/helper" +require "models/topic" +require "models/car" +require "models/aircraft" +require "models/wheel" +require "models/engine" +require "models/reply" +require "models/category" +require "models/categorization" +require "models/dog" +require "models/dog_lover" +require "models/person" +require "models/friendship" +require "models/subscriber" +require "models/subscription" +require "models/book" class CounterCacheTest < ActiveRecord::TestCase fixtures :topics, :categories, :categorizations, :cars, :dogs, :dog_lovers, :people, :friendships, :subscribers, :subscriptions, :books class ::SpecialTopic < ::Topic - has_many :special_replies, :foreign_key => 'parent_id' - has_many :lightweight_special_replies, -> { select('topics.id, topics.title') }, :foreign_key => 'parent_id', :class_name => 'SpecialReply' + has_many :special_replies, foreign_key: "parent_id" + has_many :lightweight_special_replies, -> { select("topics.id, topics.title") }, foreign_key: "parent_id", class_name: "SpecialReply" end class ::SpecialReply < ::Reply - belongs_to :special_topic, :foreign_key => 'parent_id', :counter_cache => 'replies_count' + belongs_to :special_topic, foreign_key: "parent_id", counter_cache: "replies_count" end setup do @@ -32,13 +32,13 @@ class CounterCacheTest < ActiveRecord::TestCase end test "increment counter" do - assert_difference '@topic.reload.replies_count' do + assert_difference "@topic.reload.replies_count" do Topic.increment_counter(:replies_count, @topic.id) end end test "decrement counter" do - assert_difference '@topic.reload.replies_count', -1 do + assert_difference "@topic.reload.replies_count", -1 do Topic.decrement_counter(:replies_count, @topic.id) end end @@ -48,7 +48,7 @@ class CounterCacheTest < ActiveRecord::TestCase Topic.increment_counter(:replies_count, @topic.id) # check that it gets reset - assert_difference '@topic.reload.replies_count', -1 do + assert_difference "@topic.reload.replies_count", -1 do Topic.reset_counters(@topic.id, :replies) end end @@ -58,31 +58,31 @@ class CounterCacheTest < ActiveRecord::TestCase Topic.increment_counter(:replies_count, @topic.id) # check that it gets reset - assert_difference '@topic.reload.replies_count', -1 do + assert_difference "@topic.reload.replies_count", -1 do Topic.reset_counters(@topic.id, :replies_count) end end - test 'reset multiple counters' do + test "reset multiple counters" do Topic.update_counters @topic.id, replies_count: 1, unique_replies_count: 1 - assert_difference ['@topic.reload.replies_count', '@topic.reload.unique_replies_count'], -1 do + assert_difference ["@topic.reload.replies_count", "@topic.reload.unique_replies_count"], -1 do Topic.reset_counters(@topic.id, :replies, :unique_replies) end end test "reset counters with string argument" do - Topic.increment_counter('replies_count', @topic.id) + Topic.increment_counter("replies_count", @topic.id) - assert_difference '@topic.reload.replies_count', -1 do - Topic.reset_counters(@topic.id, 'replies') + assert_difference "@topic.reload.replies_count", -1 do + Topic.reset_counters(@topic.id, "replies") end end test "reset counters with modularized and camelized classnames" do - special = SpecialTopic.create!(:title => 'Special') + special = SpecialTopic.create!(title: "Special") SpecialTopic.increment_counter(:replies_count, special.id) - assert_difference 'special.reload.replies_count', -1 do + assert_difference "special.reload.replies_count", -1 do SpecialTopic.reset_counters(special.id, :special_replies) end end @@ -103,10 +103,10 @@ class CounterCacheTest < ActiveRecord::TestCase DogLover.increment_counter(:bred_dogs_count, david.id) DogLover.increment_counter(:trained_dogs_count, david.id) - assert_difference 'david.reload.bred_dogs_count', -1 do + assert_difference "david.reload.bred_dogs_count", -1 do DogLover.reset_counters(david.id, :bred_dogs) end - assert_difference 'david.reload.trained_dogs_count', -1 do + assert_difference "david.reload.trained_dogs_count", -1 do DogLover.reset_counters(david.id, :trained_dogs) end end @@ -116,26 +116,26 @@ class CounterCacheTest < ActiveRecord::TestCase assert_equal 2, category.categorizations.count assert_nil category.categorizations_count - Category.update_counters(category.id, :categorizations_count => category.categorizations.count) + Category.update_counters(category.id, categorizations_count: category.categorizations.count) assert_equal 2, category.reload.categorizations_count end test "update counter for decrement" do - assert_difference '@topic.reload.replies_count', -3 do - Topic.update_counters(@topic.id, :replies_count => -3) + assert_difference "@topic.reload.replies_count", -3 do + Topic.update_counters(@topic.id, replies_count: -3) end end test "update counters of multiple records" do t1, t2 = topics(:first, :second) - assert_difference ['t1.reload.replies_count', 't2.reload.replies_count'], 2 do - Topic.update_counters([t1.id, t2.id], :replies_count => 2) + assert_difference ["t1.reload.replies_count", "t2.reload.replies_count"], 2 do + Topic.update_counters([t1.id, t2.id], replies_count: 2) end end - test 'update multiple counters' do - assert_difference ['@topic.reload.replies_count', '@topic.reload.unique_replies_count'], 2 do + test "update multiple counters" do + assert_difference ["@topic.reload.replies_count", "@topic.reload.unique_replies_count"], 2 do Topic.update_counters @topic.id, replies_count: 2, unique_replies_count: 2 end end @@ -144,25 +144,25 @@ class CounterCacheTest < ActiveRecord::TestCase david, joanna = dog_lovers(:david, :joanna) joanna = joanna # squelch a warning - assert_difference 'joanna.reload.dogs_count', -1 do + assert_difference "joanna.reload.dogs_count", -1 do david.destroy end end test "reset the right counter if two have the same foreign key" do michael = people(:michael) - assert_nothing_raised(ActiveRecord::StatementInvalid) do + assert_nothing_raised do Person.reset_counters(michael.id, :friends_too) end end test "reset counter of has_many :through association" do - subscriber = subscribers('second') - Subscriber.reset_counters(subscriber.id, 'books') - Subscriber.increment_counter('books_count', subscriber.id) + subscriber = subscribers("second") + Subscriber.reset_counters(subscriber.id, "books") + Subscriber.increment_counter("books_count", subscriber.id) - assert_difference 'subscriber.reload.books_count', -1 do - Subscriber.reset_counters(subscriber.id, 'books') + assert_difference "subscriber.reload.books_count", -1 do + Subscriber.reset_counters(subscriber.id, "books") end end @@ -174,10 +174,10 @@ class CounterCacheTest < ActiveRecord::TestCase end test "reset counter works with select declared on association" do - special = SpecialTopic.create!(:title => 'Special') + special = SpecialTopic.create!(title: "Special") SpecialTopic.increment_counter(:replies_count, special.id) - assert_difference 'special.reload.replies_count', -1 do + assert_difference "special.reload.replies_count", -1 do SpecialTopic.reset_counters(special.id, :lightweight_special_replies) end end @@ -203,11 +203,11 @@ class CounterCacheTest < ActiveRecord::TestCase test "update counters in a polymorphic relationship" do aircraft = Aircraft.create! - assert_difference 'aircraft.reload.wheels_count' do + assert_difference "aircraft.reload.wheels_count" do aircraft.wheels << Wheel.create! end - assert_difference 'aircraft.reload.wheels_count', -1 do + assert_difference "aircraft.reload.wheels_count", -1 do aircraft.wheels.first.destroy end end diff --git a/activerecord/test/cases/custom_locking_test.rb b/activerecord/test/cases/custom_locking_test.rb index 26d015bf71..15c8b684e4 100644 --- a/activerecord/test/cases/custom_locking_test.rb +++ b/activerecord/test/cases/custom_locking_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'models/person' +require "models/person" module ActiveRecord class CustomLockingTest < ActiveRecord::TestCase @@ -7,9 +7,9 @@ module ActiveRecord def test_custom_lock if current_adapter?(:Mysql2Adapter) - assert_match 'SHARE MODE', Person.lock('LOCK IN SHARE MODE').to_sql + assert_match "SHARE MODE", Person.lock("LOCK IN SHARE MODE").to_sql assert_sql(/LOCK IN SHARE MODE/) do - Person.all.merge!(:lock => 'LOCK IN SHARE MODE').find(1) + Person.all.merge!(lock: "LOCK IN SHARE MODE").find(1) end end end diff --git a/activerecord/test/cases/database_statements_test.rb b/activerecord/test/cases/database_statements_test.rb index ba085991e0..bb16076fd2 100644 --- a/activerecord/test/cases/database_statements_test.rb +++ b/activerecord/test/cases/database_statements_test.rb @@ -5,6 +5,13 @@ class DatabaseStatementsTest < ActiveRecord::TestCase @connection = ActiveRecord::Base.connection end + unless current_adapter?(:OracleAdapter) + def test_exec_insert + result = @connection.exec_insert("INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)", nil, []) + assert_not_nil @connection.send(:last_inserted_id, result) + end + end + def test_insert_should_return_the_inserted_id assert_not_nil return_the_inserted_id(method: :insert) end @@ -13,16 +20,22 @@ 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:) - # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method - if current_adapter?(:OracleAdapter) - sequence_name = "accounts_seq" - id_value = @connection.next_sequence_value(sequence_name) - @connection.send(method, "INSERT INTO accounts (id, firm_id,credit_limit) VALUES (accounts_seq.nextval,42,5000)", nil, :id, id_value, sequence_name) - else - @connection.send(method, "INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)") + def return_the_inserted_id(method:) + # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method + if current_adapter?(:OracleAdapter) + sequence_name = "accounts_seq" + id_value = @connection.next_sequence_value(sequence_name) + @connection.send(method, "INSERT INTO accounts (id, firm_id,credit_limit) VALUES (accounts_seq.nextval,42,5000)", nil, :id, id_value, sequence_name) + else + @connection.send(method, "INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)") + end end - end end diff --git a/activerecord/test/cases/invalid_date_test.rb b/activerecord/test/cases/date_test.rb index 426a350379..2edc0415cd 100644 --- a/activerecord/test/cases/invalid_date_test.rb +++ b/activerecord/test/cases/date_test.rb @@ -1,7 +1,19 @@ -require 'cases/helper' -require 'models/topic' +require "cases/helper" +require "models/topic" + +class DateTest < ActiveRecord::TestCase + def test_date_with_time_value + time_value = Time.new(2016, 05, 11, 19, 0, 0) + topic = Topic.create(last_read: time_value) + assert_equal topic, Topic.find_by(last_read: time_value) + end + + def test_date_with_string_value + string_value = "2016-05-11 19:00:00" + topic = Topic.create(last_read: string_value) + assert_equal topic, Topic.find_by(last_read: string_value) + end -class InvalidDateTest < ActiveRecord::TestCase def test_assign_valid_dates valid_dates = [[2007, 11, 30], [1993, 2, 28], [2008, 2, 29]] @@ -19,7 +31,7 @@ class InvalidDateTest < ActiveRecord::TestCase invalid_dates.each do |date_src| assert_nothing_raised do - topic = Topic.new({"last_read(1i)" => date_src[0].to_s, "last_read(2i)" => date_src[1].to_s, "last_read(3i)" => date_src[2].to_s}) + topic = Topic.new("last_read(1i)" => date_src[0].to_s, "last_read(2i)" => date_src[1].to_s, "last_read(3i)" => date_src[2].to_s) # Oracle DATE columns are datetime columns and Oracle adapter returns Time value if current_adapter?(:OracleAdapter) assert_equal(topic.last_read.to_date, Time.local(*date_src).to_date, "The date should be modified according to the behavior of the Time object") diff --git a/activerecord/test/cases/date_time_precision_test.rb b/activerecord/test/cases/date_time_precision_test.rb index e996d142a2..a1c3c5af9c 100644 --- a/activerecord/test/cases/date_time_precision_test.rb +++ b/activerecord/test/cases/date_time_precision_test.rb @@ -1,88 +1,87 @@ -require 'cases/helper' -require 'support/schema_dumping_helper' +require "cases/helper" +require "support/schema_dumping_helper" -if ActiveRecord::Base.connection.supports_datetime_with_precision? -class DateTimePrecisionTest < ActiveRecord::TestCase - include SchemaDumpingHelper - self.use_transactional_tests = false +if subsecond_precision_supported? + class DateTimePrecisionTest < ActiveRecord::TestCase + include SchemaDumpingHelper + self.use_transactional_tests = false - class Foo < ActiveRecord::Base; end + class Foo < ActiveRecord::Base; end - setup do - @connection = ActiveRecord::Base.connection - Foo.reset_column_information - end - - teardown do - @connection.drop_table :foos, if_exists: true - end + setup do + @connection = ActiveRecord::Base.connection + Foo.reset_column_information + end - def test_datetime_data_type_with_precision - @connection.create_table(:foos, force: true) - @connection.add_column :foos, :created_at, :datetime, precision: 0 - @connection.add_column :foos, :updated_at, :datetime, precision: 5 - assert_equal 0, Foo.columns_hash['created_at'].precision - assert_equal 5, Foo.columns_hash['updated_at'].precision - end + teardown do + @connection.drop_table :foos, if_exists: true + end - def test_timestamps_helper_with_custom_precision - @connection.create_table(:foos, force: true) do |t| - t.timestamps precision: 4 + def test_datetime_data_type_with_precision + @connection.create_table(:foos, force: true) + @connection.add_column :foos, :created_at, :datetime, precision: 0 + @connection.add_column :foos, :updated_at, :datetime, precision: 5 + assert_equal 0, Foo.columns_hash["created_at"].precision + assert_equal 5, Foo.columns_hash["updated_at"].precision end - assert_equal 4, Foo.columns_hash['created_at'].precision - assert_equal 4, Foo.columns_hash['updated_at'].precision - end - def test_passing_precision_to_datetime_does_not_set_limit - @connection.create_table(:foos, force: true) do |t| - t.timestamps precision: 4 + def test_timestamps_helper_with_custom_precision + @connection.create_table(:foos, force: true) do |t| + t.timestamps precision: 4 + end + assert_equal 4, Foo.columns_hash["created_at"].precision + assert_equal 4, Foo.columns_hash["updated_at"].precision end - assert_nil Foo.columns_hash['created_at'].limit - assert_nil Foo.columns_hash['updated_at'].limit - end - def test_invalid_datetime_precision_raises_error - assert_raises ActiveRecord::ActiveRecordError do + def test_passing_precision_to_datetime_does_not_set_limit @connection.create_table(:foos, force: true) do |t| - t.timestamps precision: 7 + t.timestamps precision: 4 end + assert_nil Foo.columns_hash["created_at"].limit + assert_nil Foo.columns_hash["updated_at"].limit end - end - def test_formatting_datetime_according_to_precision - @connection.create_table(:foos, force: true) do |t| - t.datetime :created_at, precision: 0 - t.datetime :updated_at, precision: 4 + def test_invalid_datetime_precision_raises_error + assert_raises ActiveRecord::ActiveRecordError do + @connection.create_table(:foos, force: true) do |t| + t.timestamps precision: 7 + end + end end - date = ::Time.utc(2014, 8, 17, 12, 30, 0, 999999) - Foo.create!(created_at: date, updated_at: date) - assert foo = Foo.find_by(created_at: date) - assert_equal 1, Foo.where(updated_at: date).count - assert_equal date.to_s, foo.created_at.to_s - assert_equal date.to_s, foo.updated_at.to_s - assert_equal 000000, foo.created_at.usec - assert_equal 999900, foo.updated_at.usec - end - def test_schema_dump_includes_datetime_precision - @connection.create_table(:foos, force: true) do |t| - t.timestamps precision: 6 + def test_formatting_datetime_according_to_precision + @connection.create_table(:foos, force: true) do |t| + t.datetime :created_at, precision: 0 + t.datetime :updated_at, precision: 4 + end + date = ::Time.utc(2014, 8, 17, 12, 30, 0, 999999) + Foo.create!(created_at: date, updated_at: date) + assert foo = Foo.find_by(created_at: date) + assert_equal 1, Foo.where(updated_at: date).count + assert_equal date.to_s, foo.created_at.to_s + assert_equal date.to_s, foo.updated_at.to_s + assert_equal 000000, foo.created_at.usec + assert_equal 999900, foo.updated_at.usec end - output = dump_table_schema("foos") - assert_match %r{t\.datetime\s+"created_at",\s+precision: 6,\s+null: false$}, output - assert_match %r{t\.datetime\s+"updated_at",\s+precision: 6,\s+null: false$}, output - end - if current_adapter?(:PostgreSQLAdapter) - def test_datetime_precision_with_zero_should_be_dumped + def test_schema_dump_includes_datetime_precision @connection.create_table(:foos, force: true) do |t| - t.timestamps precision: 0 + t.timestamps precision: 6 end output = dump_table_schema("foos") - assert_match %r{t\.datetime\s+"created_at",\s+precision: 0,\s+null: false$}, output - assert_match %r{t\.datetime\s+"updated_at",\s+precision: 0,\s+null: false$}, output + assert_match %r{t\.datetime\s+"created_at",\s+precision: 6,\s+null: false$}, output + assert_match %r{t\.datetime\s+"updated_at",\s+precision: 6,\s+null: false$}, output end - end -end + if current_adapter?(:PostgreSQLAdapter) + def test_datetime_precision_with_zero_should_be_dumped + @connection.create_table(:foos, force: true) do |t| + t.timestamps precision: 0 + end + output = dump_table_schema("foos") + assert_match %r{t\.datetime\s+"created_at",\s+precision: 0,\s+null: false$}, output + assert_match %r{t\.datetime\s+"updated_at",\s+precision: 0,\s+null: false$}, output + end + end + end end diff --git a/activerecord/test/cases/date_time_test.rb b/activerecord/test/cases/date_time_test.rb index 4cbff564aa..3bc08f80ec 100644 --- a/activerecord/test/cases/date_time_test.rb +++ b/activerecord/test/cases/date_time_test.rb @@ -1,12 +1,12 @@ require "cases/helper" -require 'models/topic' -require 'models/task' +require "models/topic" +require "models/task" class DateTimeTest < ActiveRecord::TestCase include InTimeZone def test_saves_both_date_and_time - with_env_tz 'America/New_York' do + with_env_tz "America/New_York" do with_timezone_config default: :utc do time_values = [1807, 2, 10, 15, 30, 45] # create DateTime value with local time zone offset @@ -25,7 +25,7 @@ class DateTimeTest < ActiveRecord::TestCase def test_assign_empty_date_time task = Task.new - task.starting = '' + task.starting = "" task.ending = nil assert_nil task.starting assert_nil task.ending @@ -34,20 +34,20 @@ class DateTimeTest < ActiveRecord::TestCase def test_assign_bad_date_time_with_timezone in_time_zone "Pacific Time (US & Canada)" do task = Task.new - task.starting = '2014-07-01T24:59:59GMT' + task.starting = "2014-07-01T24:59:59GMT" assert_nil task.starting end end def test_assign_empty_date topic = Topic.new - topic.last_read = '' + topic.last_read = "" assert_nil topic.last_read end def test_assign_empty_time topic = Topic.new - topic.bonus_time = '' + topic.bonus_time = "" assert_nil topic.bonus_time end diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb index 067513e24c..fcaff38f82 100644 --- a/activerecord/test/cases/defaults_test.rb +++ b/activerecord/test/cases/defaults_test.rb @@ -1,7 +1,7 @@ require "cases/helper" -require 'support/schema_dumping_helper' -require 'models/default' -require 'models/entrant' +require "support/schema_dumping_helper" +require "models/default" +require "models/entrant" class DefaultTest < ActiveRecord::TestCase def test_nil_defaults_for_not_null_columns @@ -99,7 +99,7 @@ if current_adapter?(:Mysql2Adapter) class MysqlDefaultExpressionTest < ActiveRecord::TestCase include SchemaDumpingHelper - if ActiveRecord::Base.connection.version >= '5.6.0' + if ActiveRecord::Base.connection.version >= "5.6.0" test "schema dump includes default expression" do output = dump_table_schema("datetime_defaults") assert_match %r/t\.datetime\s+"modified_datetime",\s+default: -> { "CURRENT_TIMESTAMP" }/, output @@ -127,92 +127,66 @@ if current_adapter?(:Mysql2Adapter) ActiveRecord::Base.establish_connection connection end - # MySQL cannot have defaults on text/blob columns. It reports the - # default value as null. + # Strict mode controls how MySQL handles invalid or missing values + # in data-change statements such as INSERT or UPDATE. A value can be + # invalid for several reasons. For example, it might have the wrong + # data type for the column, or it might be out of range. A value is + # missing when a new row to be inserted does not contain a value for + # a non-NULL column that has no explicit DEFAULT clause in its definition. + # (For a NULL column, NULL is inserted if the value is missing.) # - # Despite this, in non-strict mode, MySQL will use an empty string - # as the default value of the field, if no other value is - # specified. + # If strict mode is not in effect, MySQL inserts adjusted values for + # invalid or missing values and produces warnings. In strict mode, + # you can produce this behavior by using INSERT IGNORE or UPDATE IGNORE. # - # Therefore, in non-strict mode, we want column.default to report - # an empty string as its default, to be consistent with that. - # - # In strict mode, column.default should be nil. - def test_mysql_text_not_null_defaults_non_strict + # https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sql-mode-strict + def test_mysql_not_null_defaults_non_strict using_strict(false) do - with_text_blob_not_null_table do |klass| + with_mysql_not_null_table do |klass| record = klass.new - assert_equal '', record.non_null_blob - assert_equal '', record.non_null_text - - assert_nil record.null_blob - assert_nil record.null_text + assert_nil record.non_null_integer + assert_nil record.non_null_string + assert_nil record.non_null_text + assert_nil record.non_null_blob record.save! record.reload - assert_equal '', record.non_null_text - assert_equal '', record.non_null_blob - - assert_nil record.null_text - assert_nil record.null_blob + assert_equal 0, record.non_null_integer + assert_equal "", record.non_null_string + assert_equal "", record.non_null_text + assert_equal "", record.non_null_blob end end end - def test_mysql_text_not_null_defaults_strict + def test_mysql_not_null_defaults_strict using_strict(true) do - with_text_blob_not_null_table do |klass| + with_mysql_not_null_table do |klass| record = klass.new - assert_nil record.non_null_blob + assert_nil record.non_null_integer + assert_nil record.non_null_string assert_nil record.non_null_text - assert_nil record.null_blob - assert_nil record.null_text + assert_nil record.non_null_blob assert_raises(ActiveRecord::StatementInvalid) { klass.create } end end end - def with_text_blob_not_null_table + def with_mysql_not_null_table klass = Class.new(ActiveRecord::Base) - klass.table_name = 'test_mysql_text_not_null_defaults' + klass.table_name = "test_mysql_not_null_defaults" klass.connection.create_table klass.table_name do |t| - t.column :non_null_text, :text, :null => false - t.column :non_null_blob, :blob, :null => false - t.column :null_text, :text, :null => true - t.column :null_blob, :blob, :null => true + t.integer :non_null_integer, null: false + t.string :non_null_string, null: false + t.text :non_null_text, null: false + t.blob :non_null_blob, null: false end yield klass ensure klass.connection.drop_table(klass.table_name) rescue nil end - - # MySQL uses an implicit default 0 rather than NULL unless in strict mode. - # We use an implicit NULL so schema.rb is compatible with other databases. - def test_mysql_integer_not_null_defaults - klass = Class.new(ActiveRecord::Base) - klass.table_name = 'test_integer_not_null_default_zero' - klass.connection.create_table klass.table_name do |t| - t.column :zero, :integer, :null => false, :default => 0 - t.column :omit, :integer, :null => false - end - - assert_equal '0', klass.columns_hash['zero'].default - assert !klass.columns_hash['zero'].null - assert_equal nil, klass.columns_hash['omit'].default - assert !klass.columns_hash['omit'].null - - assert_raise(ActiveRecord::StatementInvalid) { klass.create! } - - assert_nothing_raised do - instance = klass.create!(:omit => 1) - assert_equal 0, instance.zero - assert_equal 1, instance.omit - end - ensure - klass.connection.drop_table(klass.table_name) rescue nil - end end end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index cd1967c373..3bd8475bb0 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -1,29 +1,12 @@ -require 'cases/helper' -require 'models/topic' # For booleans -require 'models/pirate' # For timestamps -require 'models/parrot' -require 'models/person' # For optimistic locking -require 'models/aircraft' - -class Pirate # Just reopening it, not defining it - attr_accessor :detected_changes_in_after_update # Boolean for if changes are detected - attr_accessor :changes_detected_in_after_update # Actual changes - - after_update :check_changes - -private - # after_save/update and the model itself - # can end up checking dirty status and acting on the results - def check_changes - if self.changed? - self.detected_changes_in_after_update = true - self.changes_detected_in_after_update = self.changes - end - end -end +require "cases/helper" +require "models/topic" # For booleans +require "models/pirate" # For timestamps +require "models/parrot" +require "models/person" # For optimistic locking +require "models/aircraft" class NumericData < ActiveRecord::Base - self.table_name = 'numeric_data' + self.table_name = "numeric_data" end class DirtyTest < ActiveRecord::TestCase @@ -31,20 +14,20 @@ class DirtyTest < ActiveRecord::TestCase # Dummy to force column loads so query counts are clean. def setup - Person.create :first_name => 'foo' + Person.create first_name: "foo" end def test_attribute_changes # New record - no changes. pirate = Pirate.new - assert !pirate.catchphrase_changed? - assert_nil pirate.catchphrase_change + assert_equal false, pirate.catchphrase_changed? + assert_equal false, pirate.non_validated_parrot_id_changed? # Change catchphrase. - pirate.catchphrase = 'arrr' + pirate.catchphrase = "arrr" assert pirate.catchphrase_changed? assert_nil pirate.catchphrase_was - assert_equal [nil, 'arrr'], pirate.catchphrase_change + assert_equal [nil, "arrr"], pirate.catchphrase_change # Saved - no changes. pirate.save! @@ -52,15 +35,15 @@ class DirtyTest < ActiveRecord::TestCase assert_nil pirate.catchphrase_change # Same value - no changes. - pirate.catchphrase = 'arrr' + pirate.catchphrase = "arrr" assert !pirate.catchphrase_changed? assert_nil pirate.catchphrase_change end def test_time_attributes_changes_with_time_zone - in_time_zone 'Paris' do + in_time_zone "Paris" do target = Class.new(ActiveRecord::Base) - target.table_name = 'pirates' + target.table_name = "pirates" # New record - no changes. pirate = target.new @@ -68,7 +51,7 @@ class DirtyTest < ActiveRecord::TestCase assert_nil pirate.created_on_change # Saved - no changes. - pirate.catchphrase = 'arrrr, time zone!!' + pirate.catchphrase = "arrrr, time zone!!" pirate.save! assert !pirate.created_on_changed? assert_nil pirate.created_on_change @@ -85,9 +68,9 @@ class DirtyTest < ActiveRecord::TestCase end def test_setting_time_attributes_with_time_zone_field_to_itself_should_not_be_marked_as_a_change - in_time_zone 'Paris' do + in_time_zone "Paris" do target = Class.new(ActiveRecord::Base) - target.table_name = 'pirates' + target.table_name = "pirates" pirate = target.create! pirate.created_on = pirate.created_on @@ -96,9 +79,9 @@ class DirtyTest < ActiveRecord::TestCase end def test_time_attributes_changes_without_time_zone_by_skip - in_time_zone 'Paris' do + in_time_zone "Paris" do target = Class.new(ActiveRecord::Base) - target.table_name = 'pirates' + target.table_name = "pirates" target.skip_time_zone_conversion_for_attributes = [:created_on] @@ -108,7 +91,7 @@ class DirtyTest < ActiveRecord::TestCase assert_nil pirate.created_on_change # Saved - no changes. - pirate.catchphrase = 'arrrr, time zone!!' + pirate.catchphrase = "arrrr, time zone!!" pirate.save! assert !pirate.created_on_changed? assert_nil pirate.created_on_change @@ -127,7 +110,7 @@ class DirtyTest < ActiveRecord::TestCase def test_time_attributes_changes_without_time_zone with_timezone_config aware_attributes: false do target = Class.new(ActiveRecord::Base) - target.table_name = 'pirates' + target.table_name = "pirates" # New record - no changes. pirate = target.new @@ -135,7 +118,7 @@ class DirtyTest < ActiveRecord::TestCase assert_nil pirate.created_on_change # Saved - no changes. - pirate.catchphrase = 'arrrr, time zone!!' + pirate.catchphrase = "arrrr, time zone!!" pirate.save! assert !pirate.created_on_changed? assert_nil pirate.created_on_change @@ -151,7 +134,6 @@ class DirtyTest < ActiveRecord::TestCase end end - def test_aliased_attribute_changes # the actual attribute here is name, title is an # alias setup via alias_attribute @@ -159,15 +141,15 @@ class DirtyTest < ActiveRecord::TestCase assert !parrot.title_changed? assert_nil parrot.title_change - parrot.name = 'Sam' + parrot.name = "Sam" assert parrot.title_changed? assert_nil parrot.title_was assert_equal parrot.name_change, parrot.title_change end def test_restore_attribute! - pirate = Pirate.create!(:catchphrase => 'Yar!') - pirate.catchphrase = 'Ahoy!' + pirate = Pirate.create!(catchphrase: "Yar!") + pirate.catchphrase = "Ahoy!" pirate.restore_catchphrase! assert_equal "Yar!", pirate.catchphrase @@ -206,9 +188,9 @@ class DirtyTest < ActiveRecord::TestCase end def test_nullable_datetime_not_marked_as_changed_if_new_value_is_blank - in_time_zone 'Edinburgh' do + in_time_zone "Edinburgh" do target = Class.new(ActiveRecord::Base) - target.table_name = 'topics' + target.table_name = "topics" topic = target.create assert_nil topic.written_on @@ -224,19 +206,19 @@ class DirtyTest < ActiveRecord::TestCase def test_integer_zero_to_string_zero_not_marked_as_changed pirate = Pirate.new pirate.parrot_id = 0 - pirate.catchphrase = 'arrr' + pirate.catchphrase = "arrr" assert pirate.save! assert !pirate.changed? - pirate.parrot_id = '0' + pirate.parrot_id = "0" assert !pirate.changed? end def test_integer_zero_to_integer_zero_not_marked_as_changed pirate = Pirate.new pirate.parrot_id = 0 - pirate.catchphrase = 'arrr' + pirate.catchphrase = "arrr" assert pirate.save! assert !pirate.changed? @@ -246,18 +228,18 @@ class DirtyTest < ActiveRecord::TestCase end def test_float_zero_to_string_zero_not_marked_as_changed - data = NumericData.new :temperature => 0.0 + data = NumericData.new temperature: 0.0 data.save! assert_not data.changed? - data.temperature = '0' + data.temperature = "0" assert_empty data.changes - data.temperature = '0.0' + data.temperature = "0.0" assert_empty data.changes - data.temperature = '0.00' + data.temperature = "0.00" assert_empty data.changes end @@ -269,7 +251,7 @@ class DirtyTest < ActiveRecord::TestCase # check the change from 1 to '' pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties") - pirate.parrot_id = '' + pirate.parrot_id = "" assert pirate.parrot_id_changed? assert_equal([1, nil], pirate.parrot_id_change) pirate.save @@ -283,7 +265,7 @@ class DirtyTest < ActiveRecord::TestCase # check the change from 0 to '' pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties") - pirate.parrot_id = '' + pirate.parrot_id = "" assert pirate.parrot_id_changed? assert_equal([0, nil], pirate.parrot_id_change) end @@ -294,11 +276,11 @@ class DirtyTest < ActiveRecord::TestCase assert_equal [], pirate.changed assert_equal Hash.new, pirate.changes - pirate.catchphrase = 'arrr' + pirate.catchphrase = "arrr" assert pirate.changed? assert_nil pirate.catchphrase_was assert_equal %w(catchphrase), pirate.changed - assert_equal({'catchphrase' => [nil, 'arrr']}, pirate.changes) + assert_equal({ "catchphrase" => [nil, "arrr"] }, pirate.changes) pirate.save assert !pirate.changed? @@ -307,21 +289,21 @@ class DirtyTest < ActiveRecord::TestCase end def test_attribute_will_change! - pirate = Pirate.create!(:catchphrase => 'arr') + pirate = Pirate.create!(catchphrase: "arr") assert !pirate.catchphrase_changed? assert pirate.catchphrase_will_change! assert pirate.catchphrase_changed? - assert_equal ['arr', 'arr'], pirate.catchphrase_change + assert_equal ["arr", "arr"], pirate.catchphrase_change - pirate.catchphrase << ' matey!' + pirate.catchphrase << " matey!" assert pirate.catchphrase_changed? - assert_equal ['arr', 'arr matey!'], pirate.catchphrase_change + assert_equal ["arr", "arr matey!"], pirate.catchphrase_change end def test_association_assignment_changes_foreign_key - pirate = Pirate.create!(:catchphrase => 'jarl') - pirate.parrot = Parrot.create!(:name => 'Lorre') + pirate = Pirate.create!(catchphrase: "jarl") + pirate.parrot = Parrot.create!(name: "Lorre") assert pirate.changed? assert_equal %w(parrot_id), pirate.changed end @@ -332,7 +314,7 @@ class DirtyTest < ActiveRecord::TestCase assert !topic.approved_changed? # Coming from web form. - params = {:topic => {:approved => 1}} + params = { topic: { approved: 1 } } # In the controller. topic.attributes = params[:topic] assert topic.approved? @@ -340,37 +322,37 @@ class DirtyTest < ActiveRecord::TestCase end def test_partial_update - pirate = Pirate.new(:catchphrase => 'foo') + pirate = Pirate.new(catchphrase: "foo") old_updated_on = 1.hour.ago.beginning_of_day with_partial_writes Pirate, false do assert_queries(2) { 2.times { pirate.save! } } - Pirate.where(id: pirate.id).update_all(:updated_on => old_updated_on) + Pirate.where(id: pirate.id).update_all(updated_on: old_updated_on) end with_partial_writes Pirate, true do assert_queries(0) { 2.times { pirate.save! } } assert_equal old_updated_on, pirate.reload.updated_on - assert_queries(1) { pirate.catchphrase = 'bar'; pirate.save! } + assert_queries(1) { pirate.catchphrase = "bar"; pirate.save! } assert_not_equal old_updated_on, pirate.reload.updated_on end end def test_partial_update_with_optimistic_locking - person = Person.new(:first_name => 'foo') - old_lock_version = 1 + person = Person.new(first_name: "foo") + old_lock_version = person.lock_version with_partial_writes Person, false do assert_queries(2) { 2.times { person.save! } } - Person.where(id: person.id).update_all(:first_name => 'baz') + Person.where(id: person.id).update_all(first_name: "baz") end with_partial_writes Person, true do assert_queries(0) { 2.times { person.save! } } assert_equal old_lock_version, person.reload.lock_version - assert_queries(1) { person.first_name = 'bar'; person.save! } + assert_queries(1) { person.first_name = "bar"; person.save! } assert_not_equal old_lock_version, person.reload.lock_version end end @@ -388,7 +370,7 @@ class DirtyTest < ActiveRecord::TestCase end def test_reload_should_clear_changed_attributes - pirate = Pirate.create!(:catchphrase => "shiver me timbers") + pirate = Pirate.create!(catchphrase: "shiver me timbers") pirate.catchphrase = "*hic*" assert pirate.changed? pirate.reload @@ -396,7 +378,7 @@ class DirtyTest < ActiveRecord::TestCase end def test_dup_objects_should_not_copy_dirty_flag_from_creator - pirate = Pirate.create!(:catchphrase => "shiver me timbers") + pirate = Pirate.create!(catchphrase: "shiver me timbers") pirate_dup = pirate.dup pirate_dup.restore_catchphrase! pirate.catchphrase = "I love Rum" @@ -406,7 +388,7 @@ class DirtyTest < ActiveRecord::TestCase def test_reverted_changes_are_not_dirty phrase = "shiver me timbers" - pirate = Pirate.create!(:catchphrase => phrase) + pirate = Pirate.create!(catchphrase: phrase) pirate.catchphrase = "*hic*" assert pirate.changed? pirate.catchphrase = phrase @@ -415,7 +397,7 @@ class DirtyTest < ActiveRecord::TestCase def test_reverted_changes_are_not_dirty_after_multiple_changes phrase = "shiver me timbers" - pirate = Pirate.create!(:catchphrase => phrase) + pirate = Pirate.create!(catchphrase: phrase) 10.times do |i| pirate.catchphrase = "*hic*" * i assert pirate.changed? @@ -425,9 +407,8 @@ class DirtyTest < ActiveRecord::TestCase assert !pirate.changed? end - def test_reverted_changes_are_not_dirty_going_from_nil_to_value_and_back - pirate = Pirate.create!(:catchphrase => "Yar!") + pirate = Pirate.create!(catchphrase: "Yar!") pirate.parrot_id = 1 assert pirate.changed? @@ -442,7 +423,7 @@ class DirtyTest < ActiveRecord::TestCase def test_save_should_store_serialized_attributes_even_with_partial_writes with_partial_writes(Topic) do - topic = Topic.create!(:content => {:a => "a"}) + topic = Topic.create!(content: { a: "a" }) assert_not topic.changed? @@ -463,25 +444,25 @@ class DirtyTest < ActiveRecord::TestCase def test_save_always_should_update_timestamps_when_serialized_attributes_are_present with_partial_writes(Topic) do - topic = Topic.create!(:content => {:a => "a"}) + topic = Topic.create!(content: { a: "a" }) topic.save! updated_at = topic.updated_at travel(1.second) do - topic.content[:hello] = 'world' + topic.content[:hello] = "world" topic.save! end assert_not_equal updated_at, topic.updated_at - assert_equal 'world', topic.content[:hello] + assert_equal "world", topic.content[:hello] end end def test_save_should_not_save_serialized_attribute_with_partial_writes_if_not_present with_partial_writes(Topic) do - Topic.create!(:author_name => 'Bill', :content => {:a => "a"}) - topic = Topic.select('id, author_name').first - topic.update_columns author_name: 'John' + Topic.create!(author_name: "Bill", content: { a: "a" }) + topic = Topic.select("id, author_name").first + topic.update_columns author_name: "John" topic = Topic.first assert_not_nil topic.content end @@ -496,13 +477,13 @@ class DirtyTest < ActiveRecord::TestCase pirate.save! assert_equal 4, pirate.previous_changes.size - assert_equal [nil, "arrr"], pirate.previous_changes['catchphrase'] - assert_equal [nil, pirate.id], pirate.previous_changes['id'] - assert_nil pirate.previous_changes['updated_on'][0] - assert_not_nil pirate.previous_changes['updated_on'][1] - assert_nil pirate.previous_changes['created_on'][0] - assert_not_nil pirate.previous_changes['created_on'][1] - assert !pirate.previous_changes.key?('parrot_id') + assert_equal [nil, "arrr"], pirate.previous_changes["catchphrase"] + assert_equal [nil, pirate.id], pirate.previous_changes["id"] + assert_nil pirate.previous_changes["updated_on"][0] + assert_not_nil pirate.previous_changes["updated_on"][1] + assert_nil pirate.previous_changes["created_on"][0] + assert_not_nil pirate.previous_changes["created_on"][1] + assert !pirate.previous_changes.key?("parrot_id") # original values should be in previous_changes pirate = Pirate.new @@ -512,11 +493,11 @@ class DirtyTest < ActiveRecord::TestCase pirate.save assert_equal 4, pirate.previous_changes.size - assert_equal [nil, "arrr"], pirate.previous_changes['catchphrase'] - assert_equal [nil, pirate.id], pirate.previous_changes['id'] - assert pirate.previous_changes.include?('updated_on') - assert pirate.previous_changes.include?('created_on') - assert !pirate.previous_changes.key?('parrot_id') + assert_equal [nil, "arrr"], pirate.previous_changes["catchphrase"] + assert_equal [nil, pirate.id], pirate.previous_changes["id"] + assert_includes pirate.previous_changes, "updated_on" + assert_includes pirate.previous_changes, "created_on" + assert !pirate.previous_changes.key?("parrot_id") pirate.catchphrase = "Yar!!" pirate.reload @@ -530,11 +511,11 @@ class DirtyTest < ActiveRecord::TestCase pirate.save! assert_equal 2, pirate.previous_changes.size - assert_equal ["arrr", "Me Maties!"], pirate.previous_changes['catchphrase'] - assert_not_nil pirate.previous_changes['updated_on'][0] - assert_not_nil pirate.previous_changes['updated_on'][1] - assert !pirate.previous_changes.key?('parrot_id') - assert !pirate.previous_changes.key?('created_on') + assert_equal ["arrr", "Me Maties!"], pirate.previous_changes["catchphrase"] + assert_not_nil pirate.previous_changes["updated_on"][0] + assert_not_nil pirate.previous_changes["updated_on"][1] + assert !pirate.previous_changes.key?("parrot_id") + assert !pirate.previous_changes.key?("created_on") pirate = Pirate.find_by_catchphrase("Me Maties!") @@ -544,11 +525,11 @@ class DirtyTest < ActiveRecord::TestCase pirate.save assert_equal 2, pirate.previous_changes.size - assert_equal ["Me Maties!", "Thar She Blows!"], pirate.previous_changes['catchphrase'] - assert_not_nil pirate.previous_changes['updated_on'][0] - assert_not_nil pirate.previous_changes['updated_on'][1] - assert !pirate.previous_changes.key?('parrot_id') - assert !pirate.previous_changes.key?('created_on') + assert_equal ["Me Maties!", "Thar She Blows!"], pirate.previous_changes["catchphrase"] + assert_not_nil pirate.previous_changes["updated_on"][0] + assert_not_nil pirate.previous_changes["updated_on"][1] + assert !pirate.previous_changes.key?("parrot_id") + assert !pirate.previous_changes.key?("created_on") travel(1.second) @@ -556,11 +537,11 @@ class DirtyTest < ActiveRecord::TestCase pirate.update(catchphrase: "Ahoy!") assert_equal 2, pirate.previous_changes.size - assert_equal ["Thar She Blows!", "Ahoy!"], pirate.previous_changes['catchphrase'] - assert_not_nil pirate.previous_changes['updated_on'][0] - assert_not_nil pirate.previous_changes['updated_on'][1] - assert !pirate.previous_changes.key?('parrot_id') - assert !pirate.previous_changes.key?('created_on') + assert_equal ["Thar She Blows!", "Ahoy!"], pirate.previous_changes["catchphrase"] + assert_not_nil pirate.previous_changes["updated_on"][0] + assert_not_nil pirate.previous_changes["updated_on"][1] + assert !pirate.previous_changes.key?("parrot_id") + assert !pirate.previous_changes.key?("created_on") travel(1.second) @@ -568,11 +549,11 @@ class DirtyTest < ActiveRecord::TestCase pirate.update_attribute(:catchphrase, "Ninjas suck!") assert_equal 2, pirate.previous_changes.size - assert_equal ["Ahoy!", "Ninjas suck!"], pirate.previous_changes['catchphrase'] - assert_not_nil pirate.previous_changes['updated_on'][0] - assert_not_nil pirate.previous_changes['updated_on'][1] - assert !pirate.previous_changes.key?('parrot_id') - assert !pirate.previous_changes.key?('created_on') + assert_equal ["Ahoy!", "Ninjas suck!"], pirate.previous_changes["catchphrase"] + assert_not_nil pirate.previous_changes["updated_on"][0] + assert_not_nil pirate.previous_changes["updated_on"][1] + assert !pirate.previous_changes.key?("parrot_id") + assert !pirate.previous_changes.key?("created_on") ensure travel_back end @@ -593,24 +574,24 @@ class DirtyTest < ActiveRecord::TestCase def test_datetime_attribute_can_be_updated_with_fractional_seconds skip "Fractional seconds are not supported" unless subsecond_precision_supported? - in_time_zone 'Paris' do + in_time_zone "Paris" do target = Class.new(ActiveRecord::Base) - target.table_name = 'topics' + target.table_name = "topics" - written_on = Time.utc(2012, 12, 1, 12, 0, 0).in_time_zone('Paris') + written_on = Time.utc(2012, 12, 1, 12, 0, 0).in_time_zone("Paris") - topic = target.create(:written_on => written_on) + topic = target.create(written_on: written_on) topic.written_on += 0.3 - assert topic.written_on_changed?, 'Fractional second update not detected' + assert topic.written_on_changed?, "Fractional second update not detected" end end def test_datetime_attribute_doesnt_change_if_zone_is_modified_in_string - time_in_paris = Time.utc(2014, 1, 1, 12, 0, 0).in_time_zone('Paris') - pirate = Pirate.create!(:catchphrase => 'rrrr', :created_on => time_in_paris) + time_in_paris = Time.utc(2014, 1, 1, 12, 0, 0).in_time_zone("Paris") + pirate = Pirate.create!(catchphrase: "rrrr", created_on: time_in_paris) - pirate.created_on = pirate.created_on.in_time_zone('Tokyo').to_s + pirate.created_on = pirate.created_on.in_time_zone("Tokyo").to_s assert !pirate.created_on_changed? end @@ -618,13 +599,13 @@ class DirtyTest < ActiveRecord::TestCase with_partial_writes Person do jon = nil assert_sql(/first_name/i) do - jon = Person.create! first_name: 'Jon' + jon = Person.create! first_name: "Jon" end - assert ActiveRecord::SQLCounter.log_all.none? { |sql| sql =~ /followers_count/ } + assert ActiveRecord::SQLCounter.log_all.none? { |sql| sql.include?("followers_count") } jon.reload - assert_equal 'Jon', jon.first_name + assert_equal "Jon", jon.first_name assert_equal 0, jon.followers_count assert_not_nil jon.id end @@ -650,7 +631,7 @@ class DirtyTest < ActiveRecord::TestCase assert_equal("arrrr", pirate.catchphrase_was) assert pirate.catchphrase_changed?(from: "arrrr") assert_not pirate.catchphrase_changed?(from: "anything else") - assert pirate.changed_attributes.include?(:catchphrase) + assert_includes pirate.changed_attributes, :catchphrase pirate.save! pirate.reload @@ -689,7 +670,7 @@ class DirtyTest < ActiveRecord::TestCase end end klass = Class.new(ActiveRecord::Base) do - self.table_name = 'people' + self.table_name = "people" attribute :foo, test_type_class.new end @@ -699,7 +680,7 @@ class DirtyTest < ActiveRecord::TestCase test "attribute_will_change! doesn't try to save non-persistable attributes" do klass = Class.new(ActiveRecord::Base) do - self.table_name = 'people' + self.table_name = "people" attribute :non_persisted_attribute, :string end @@ -734,6 +715,17 @@ class DirtyTest < ActiveRecord::TestCase assert_equal "arr", pirate.catchphrase end + test "attributes assigned but not selected are dirty" do + person = Person.select(:id).first + refute person.changed? + + person.first_name = "Sean" + assert person.changed? + + person.first_name = nil + assert person.changed? + end + private def with_partial_writes(klass, on = true) old = klass.partial_writes? diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb index 638cffe0e6..3821e0c949 100644 --- a/activerecord/test/cases/dup_test.rb +++ b/activerecord/test/cases/dup_test.rb @@ -1,6 +1,6 @@ require "cases/helper" -require 'models/reply' -require 'models/topic' +require "models/reply" +require "models/topic" module ActiveRecord class DupTest < ActiveRecord::TestCase @@ -14,7 +14,7 @@ module ActiveRecord topic = Topic.first duped = topic.dup - assert !duped.readonly?, 'should not be readonly' + assert !duped.readonly?, "should not be readonly" end def test_is_readonly @@ -22,15 +22,15 @@ module ActiveRecord topic.readonly! duped = topic.dup - assert duped.readonly?, 'should be readonly' + assert duped.readonly?, "should be readonly" end def test_dup_not_persisted topic = Topic.first duped = topic.dup - assert !duped.persisted?, 'topic not persisted' - assert duped.new_record?, 'topic is new' + assert !duped.persisted?, "topic not persisted" + assert duped.new_record?, "topic is new" end def test_dup_not_destroyed @@ -49,9 +49,9 @@ module ActiveRecord def test_dup_with_modified_attributes topic = Topic.first - topic.author_name = 'Aaron' + topic.author_name = "Aaron" duped = topic.dup - assert_equal 'Aaron', duped.author_name + assert_equal "Aaron", duped.author_name end def test_dup_with_changes @@ -71,10 +71,10 @@ module ActiveRecord def test_dup_topics_are_independent topic = Topic.first - topic.author_name = 'Aaron' + topic.author_name = "Aaron" duped = topic.dup - duped.author_name = 'meow' + duped.author_name = "meow" assert_not_equal topic.changes, duped.changes end @@ -83,11 +83,11 @@ module ActiveRecord topic = Topic.first duped = topic.dup - duped.author_name = 'meow' - topic.author_name = 'Aaron' + duped.author_name = "meow" + topic.author_name = "Aaron" - assert_equal 'Aaron', topic.author_name - assert_equal 'meow', duped.author_name + assert_equal "Aaron", topic.author_name + assert_equal "meow", duped.author_name end def test_dup_timestamps_are_cleared @@ -127,7 +127,7 @@ module ActiveRecord assert duped.invalid? topic.title = nil - duped.title = 'Mathematics' + duped.title = "Mathematics" assert topic.invalid? assert duped.valid? end @@ -135,8 +135,8 @@ module ActiveRecord def test_dup_with_default_scope prev_default_scopes = Topic.default_scopes - Topic.default_scopes = [proc { Topic.where(:approved => true) }] - topic = Topic.new(:approved => false) + Topic.default_scopes = [proc { Topic.where(approved: true) }] + topic = Topic.new(approved: false) assert !topic.dup.approved?, "should not be overridden by default scopes" ensure Topic.default_scopes = prev_default_scopes @@ -144,7 +144,7 @@ module ActiveRecord def test_dup_without_primary_key klass = Class.new(ActiveRecord::Base) do - self.table_name = 'parrots_pirates' + self.table_name = "parrots_pirates" end record = klass.create! diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb index babacd1ee9..b7641fcf32 100644 --- a/activerecord/test/cases/enum_test.rb +++ b/activerecord/test/cases/enum_test.rb @@ -1,5 +1,5 @@ -require 'cases/helper' -require 'models/book' +require "cases/helper" +require "models/book" class EnumTest < ActiveRecord::TestCase fixtures :books @@ -18,6 +18,7 @@ class EnumTest < ActiveRecord::TestCase assert @book.author_visibility_visible? assert @book.illustrator_visibility_visible? assert @book.with_medium_font_size? + assert @book.medium_to_read? end test "query state with strings" do @@ -26,6 +27,7 @@ class EnumTest < ActiveRecord::TestCase assert_equal "english", @book.language assert_equal "visible", @book.author_visibility assert_equal "visible", @book.illustrator_visibility + assert_equal "medium", @book.difficulty end test "find via scope" do @@ -34,6 +36,7 @@ class EnumTest < ActiveRecord::TestCase assert_equal @book, Book.in_english.first assert_equal @book, Book.author_visibility_visible.first assert_equal @book, Book.illustrator_visibility_visible.first + assert_equal @book, Book.medium_to_read.first end test "find via where with values" do @@ -122,8 +125,8 @@ class EnumTest < ActiveRecord::TestCase old_language = @book.language @book.status = :proposed @book.language = :spanish - assert_equal [old_status, 'proposed'], @book.changes[:status] - assert_equal [old_language, 'spanish'], @book.changes[:language] + assert_equal [old_status, "proposed"], @book.changes[:status] + assert_equal [old_language, "spanish"], @book.changes[:language] end test "enum attribute was" do @@ -145,8 +148,8 @@ class EnumTest < ActiveRecord::TestCase test "enum attribute changed to" do @book.status = :proposed @book.language = :french - assert @book.attribute_changed?(:status, to: 'proposed') - assert @book.attribute_changed?(:language, to: 'french') + assert @book.attribute_changed?(:status, to: "proposed") + assert @book.attribute_changed?(:language, to: "french") end test "enum attribute changed from" do @@ -163,8 +166,8 @@ class EnumTest < ActiveRecord::TestCase old_language = @book.language @book.status = :proposed @book.language = :french - assert @book.attribute_changed?(:status, from: old_status, to: 'proposed') - assert @book.attribute_changed?(:language, from: old_language, to: 'french') + assert @book.attribute_changed?(:status, from: old_status, to: "proposed") + assert @book.attribute_changed?(:language, from: old_language, to: "french") end test "enum didn't change" do @@ -216,12 +219,12 @@ class EnumTest < ActiveRecord::TestCase end test "assign empty string value" do - @book.status = '' + @book.status = "" assert_nil @book.status end test "assign long empty string value" do - @book.status = ' ' + @book.status = " " assert_nil @book.status end @@ -318,7 +321,7 @@ class EnumTest < ActiveRecord::TestCase test "validate uniqueness" do klass = Class.new(ActiveRecord::Base) do - def self.name; 'Book'; end + def self.name; "Book"; end enum status: [:proposed, :written] validates_uniqueness_of :status end @@ -332,7 +335,7 @@ class EnumTest < ActiveRecord::TestCase test "validate inclusion of value in array" do klass = Class.new(ActiveRecord::Base) do - def self.name; 'Book'; end + def self.name; "Book"; end enum status: [:proposed, :written] validates_inclusion_of :status, in: ["written"] end @@ -356,11 +359,11 @@ class EnumTest < ActiveRecord::TestCase book1 = klass1.proposed.create! book1.status = :written - assert_equal ['proposed', 'written'], book1.status_change + assert_equal ["proposed", "written"], book1.status_change book2 = klass2.drafted.create! book2.status = :uploaded - assert_equal ['drafted', 'uploaded'], book2.status_change + assert_equal ["drafted", "uploaded"], book2.status_change end test "enums are inheritable" do @@ -372,11 +375,11 @@ class EnumTest < ActiveRecord::TestCase book1 = subklass1.proposed.create! book1.status = :written - assert_equal ['proposed', 'written'], book1.status_change + assert_equal ["proposed", "written"], book1.status_change book2 = subklass2.drafted.create! book2.status = :uploaded - assert_equal ['drafted', 'uploaded'], book2.status_change + assert_equal ["drafted", "uploaded"], book2.status_change end test "declare multiple enums at a time" do @@ -393,6 +396,22 @@ class EnumTest < ActiveRecord::TestCase assert book2.single? end + test "enum with alias_attribute" do + klass = Class.new(ActiveRecord::Base) do + self.table_name = "books" + alias_attribute :aliased_status, :status + enum aliased_status: [:proposed, :written, :published] + end + + book = klass.proposed.create! + assert book.proposed? + assert_equal "proposed", book.aliased_status + + book = klass.find(book.id) + assert book.proposed? + assert_equal "proposed", book.aliased_status + end + test "query state by predicate with prefix" do assert @book.author_visibility_visible? assert_not @book.author_visibility_invisible? @@ -406,6 +425,43 @@ class EnumTest < ActiveRecord::TestCase assert_not @book.in_french? end + test "query state by predicate with custom suffix" do + assert @book.medium_to_read? + assert_not @book.easy_to_read? + assert_not @book.hard_to_read? + end + + test "enum methods with custom suffix defined" do + assert @book.class.respond_to?(:easy_to_read) + assert @book.class.respond_to?(:medium_to_read) + assert @book.class.respond_to?(:hard_to_read) + + assert @book.respond_to?(:easy_to_read?) + assert @book.respond_to?(:medium_to_read?) + assert @book.respond_to?(:hard_to_read?) + + assert @book.respond_to?(:easy_to_read!) + assert @book.respond_to?(:medium_to_read!) + assert @book.respond_to?(:hard_to_read!) + end + + test "update enum attributes with custom suffix" do + @book.medium_to_read! + assert_not @book.easy_to_read? + assert @book.medium_to_read? + assert_not @book.hard_to_read? + + @book.easy_to_read! + assert @book.easy_to_read? + assert_not @book.medium_to_read? + assert_not @book.hard_to_read? + + @book.hard_to_read! + assert_not @book.easy_to_read? + assert_not @book.medium_to_read? + assert @book.hard_to_read? + end + test "uses default status when no status is provided in fixtures" do book = books(:tlg) assert book.proposed?, "expected fixture to default to proposed status" @@ -421,4 +477,8 @@ class EnumTest < ActiveRecord::TestCase book = Book.new assert book.hard? end + + test "data type of Enum type" do + assert_equal :integer, Book.type_for_attribute("status").type + end end diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb index 2dee8a26a5..ca87e04012 100644 --- a/activerecord/test/cases/explain_subscriber_test.rb +++ b/activerecord/test/cases/explain_subscriber_test.rb @@ -1,6 +1,6 @@ -require 'cases/helper' -require 'active_record/explain_subscriber' -require 'active_record/explain_registry' +require "cases/helper" +require "active_record/explain_subscriber" +require "active_record/explain_registry" if ActiveRecord::Base.connection.supports_explain? class ExplainSubscriberTest < ActiveRecord::TestCase @@ -25,31 +25,31 @@ if ActiveRecord::Base.connection.supports_explain? def test_collects_nothing_if_collect_is_false ActiveRecord::ExplainRegistry.collect = false - SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: 'select 1 from users', binds: [1, 2]) + SUBSCRIBER.finish(nil, nil, name: "SQL", sql: "select 1 from users", binds: [1, 2]) assert queries.empty? end def test_collects_pairs_of_queries_and_binds - sql = 'select 1 from users' + sql = "select 1 from users" binds = [1, 2] - SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: sql, binds: binds) + SUBSCRIBER.finish(nil, nil, name: "SQL", sql: sql, binds: binds) assert_equal 1, queries.size assert_equal sql, queries[0][0] assert_equal binds, queries[0][1] end def test_collects_nothing_if_the_statement_is_not_whitelisted - SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: 'SHOW max_identifier_length') + SUBSCRIBER.finish(nil, nil, name: "SQL", sql: "SHOW max_identifier_length") assert queries.empty? end def test_collects_nothing_if_the_statement_is_only_partially_matched - SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: 'select_db yo_mama') + SUBSCRIBER.finish(nil, nil, name: "SQL", sql: "select_db yo_mama") assert queries.empty? end def test_collects_cte_queries - SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: 'with s as (values(3)) select 1 from s') + SUBSCRIBER.finish(nil, nil, name: "SQL", sql: "with s as (values(3)) select 1 from s") assert_equal 1, queries.size end diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb index 64dfd86ce2..86fe90ae51 100644 --- a/activerecord/test/cases/explain_test.rb +++ b/activerecord/test/cases/explain_test.rb @@ -1,6 +1,6 @@ -require 'cases/helper' -require 'models/car' -require 'active_support/core_ext/string/strip' +require "cases/helper" +require "models/car" +require "active_support/core_ext/string/strip" if ActiveRecord::Base.connection.supports_explain? class ExplainTest < ActiveRecord::TestCase @@ -15,13 +15,13 @@ if ActiveRecord::Base.connection.supports_explain? end def test_relation_explain - message = Car.where(:name => 'honda').explain + message = Car.where(name: "honda").explain assert_match(/^EXPLAIN for:/, message) end def test_collecting_queries_for_explain queries = ActiveRecord::Base.collecting_queries_for_explain do - Car.where(:name => 'honda').to_a + Car.where(name: "honda").to_a end sql, binds = queries[0] @@ -30,7 +30,7 @@ if ActiveRecord::Base.connection.supports_explain? assert_equal 1, binds.length assert_equal "honda", binds.last.value else - assert_match 'honda', sql + assert_match "honda", sql end end @@ -40,17 +40,14 @@ if ActiveRecord::Base.connection.supports_explain? queries = sqls.zip(binds) stub_explain_for_query_plans do - expected = sqls.map {|sql| "EXPLAIN for: #{sql}\nquery plan #{sql}"}.join("\n") + expected = sqls.map { |sql| "EXPLAIN for: #{sql}\nquery plan #{sql}" }.join("\n") assert_equal expected, base.exec_explain(queries) end end def test_exec_explain_with_binds - object = Struct.new(:name) - cols = [object.new('wadus'), object.new('chaflan')] - sqls = %w(foo bar) - binds = [[[cols[0], 1]], [[cols[1], 2]]] + binds = [[bind_param("wadus", 1)], [bind_param("chaflan", 2)]] queries = sqls.zip(binds) stub_explain_for_query_plans(["query plan foo\n", "query plan bar\n"]) do @@ -68,20 +65,23 @@ if ActiveRecord::Base.connection.supports_explain? def test_unsupported_connection_adapter connection.stub(:supports_explain?, false) do assert_not_called(base.logger, :warn) do - Car.where(:name => 'honda').to_a + Car.where(name: "honda").to_a end end end private - def stub_explain_for_query_plans(query_plans = ['query plan foo', 'query plan bar']) + def stub_explain_for_query_plans(query_plans = ["query plan foo", "query plan bar"]) explain_called = 0 - connection.stub(:explain, proc{ explain_called += 1; query_plans[explain_called - 1] }) do + connection.stub(:explain, proc { explain_called += 1; query_plans[explain_called - 1] }) do yield end end + def bind_param(name, value) + ActiveRecord::Relation::QueryAttribute.new(name, value, ActiveRecord::Type::Value.new) + end end end diff --git a/activerecord/test/cases/finder_respond_to_test.rb b/activerecord/test/cases/finder_respond_to_test.rb index 6ab2657c44..3eaa993d45 100644 --- a/activerecord/test/cases/finder_respond_to_test.rb +++ b/activerecord/test/cases/finder_respond_to_test.rb @@ -1,8 +1,7 @@ require "cases/helper" -require 'models/topic' +require "models/topic" class FinderRespondToTest < ActiveRecord::TestCase - fixtures :topics def test_should_preserve_normal_respond_to_behaviour_on_base @@ -11,7 +10,7 @@ class FinderRespondToTest < ActiveRecord::TestCase end def test_should_preserve_normal_respond_to_behaviour_and_respond_to_newly_added_method - class << Topic; self; end.send(:define_method, :method_added_for_finder_respond_to_test) { } + class << Topic; self; end.send(:define_method, :method_added_for_finder_respond_to_test) {} assert_respond_to Topic, :method_added_for_finder_respond_to_test ensure class << Topic; self; end.send(:remove_method, :method_added_for_finder_respond_to_test) @@ -54,7 +53,7 @@ class FinderRespondToTest < ActiveRecord::TestCase private - def ensure_topic_method_is_not_cached(method_id) - class << Topic; self; end.send(:remove_method, method_id) if Topic.public_methods.include? method_id - end + def ensure_topic_method_is_not_cached(method_id) + class << Topic; self; end.send(:remove_method, method_id) if Topic.public_methods.include? method_id + end end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 5b41630cd8..51563b347c 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -1,35 +1,35 @@ require "cases/helper" -require 'models/post' -require 'models/author' -require 'models/categorization' -require 'models/comment' -require 'models/company' -require 'models/tagging' -require 'models/topic' -require 'models/reply' -require 'models/entrant' -require 'models/project' -require 'models/developer' -require 'models/computer' -require 'models/customer' -require 'models/toy' -require 'models/matey' -require 'models/dog' -require 'models/car' -require 'models/tyre' +require "models/post" +require "models/author" +require "models/categorization" +require "models/comment" +require "models/company" +require "models/tagging" +require "models/topic" +require "models/reply" +require "models/entrant" +require "models/project" +require "models/developer" +require "models/computer" +require "models/customer" +require "models/toy" +require "models/matey" +require "models/dog" +require "models/car" +require "models/tyre" class FinderTest < ActiveRecord::TestCase fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :author_addresses, :customers, :categories, :categorizations, :cars def test_find_by_id_with_hash - assert_raises(ActiveRecord::StatementInvalid) do - Post.find_by_id(:limit => 1) + assert_nothing_raised do + Post.find_by_id(limit: 1) end end def test_find_by_title_and_id_with_hash - assert_raises(ActiveRecord::StatementInvalid) do - Post.find_by_title_and_id('foo', :limit => 1) + assert_nothing_raised do + Post.find_by_title_and_id("foo", limit: 1) end end @@ -43,60 +43,60 @@ class FinderTest < ActiveRecord::TestCase end assert_equal "should happen", exception.message - assert_nothing_raised(RuntimeError) do + assert_nothing_raised do Topic.all.find(-> { raise "should not happen" }) { |e| e.title == topics(:first).title } end end def test_find_with_ids_returning_ordered records = Topic.find([4,2,5]) - assert_equal 'The Fourth Topic of the day', records[0].title - assert_equal 'The Second Topic of the day', records[1].title - assert_equal 'The Fifth Topic of the day', records[2].title + assert_equal "The Fourth Topic of the day", records[0].title + assert_equal "The Second Topic of the day", records[1].title + assert_equal "The Fifth Topic of the day", records[2].title records = Topic.find(4,2,5) - assert_equal 'The Fourth Topic of the day', records[0].title - assert_equal 'The Second Topic of the day', records[1].title - assert_equal 'The Fifth Topic of the day', records[2].title + assert_equal "The Fourth Topic of the day", records[0].title + assert_equal "The Second Topic of the day", records[1].title + assert_equal "The Fifth Topic of the day", records[2].title - records = Topic.find(['4','2','5']) - assert_equal 'The Fourth Topic of the day', records[0].title - assert_equal 'The Second Topic of the day', records[1].title - assert_equal 'The Fifth Topic of the day', records[2].title + records = Topic.find(["4","2","5"]) + assert_equal "The Fourth Topic of the day", records[0].title + assert_equal "The Second Topic of the day", records[1].title + assert_equal "The Fifth Topic of the day", records[2].title - records = Topic.find('4','2','5') - assert_equal 'The Fourth Topic of the day', records[0].title - assert_equal 'The Second Topic of the day', records[1].title - assert_equal 'The Fifth Topic of the day', records[2].title + records = Topic.find("4","2","5") + assert_equal "The Fourth Topic of the day", records[0].title + assert_equal "The Second Topic of the day", records[1].title + assert_equal "The Fifth Topic of the day", records[2].title end def test_find_with_ids_and_order_clause # The order clause takes precedence over the informed ids records = Topic.order(:author_name).find([5,3,1]) - assert_equal 'The Third Topic of the day', records[0].title - assert_equal 'The First Topic', records[1].title - assert_equal 'The Fifth Topic of the day', records[2].title + assert_equal "The Third Topic of the day", records[0].title + assert_equal "The First Topic", records[1].title + assert_equal "The Fifth Topic of the day", records[2].title records = Topic.order(:id).find([5,3,1]) - assert_equal 'The First Topic', records[0].title - assert_equal 'The Third Topic of the day', records[1].title - assert_equal 'The Fifth Topic of the day', records[2].title + assert_equal "The First Topic", records[0].title + assert_equal "The Third Topic of the day", records[1].title + assert_equal "The Fifth Topic of the day", records[2].title end def test_find_with_ids_with_limit_and_order_clause # The order clause takes precedence over the informed ids records = Topic.limit(2).order(:id).find([5,3,1]) assert_equal 2, records.size - assert_equal 'The First Topic', records[0].title - assert_equal 'The Third Topic of the day', records[1].title + assert_equal "The First Topic", records[0].title + assert_equal "The Third Topic of the day", records[1].title end def test_find_with_ids_and_limit records = Topic.limit(3).find([3,2,5,1,4]) assert_equal 3, records.size - assert_equal 'The Third Topic of the day', records[0].title - assert_equal 'The Second Topic of the day', records[1].title - assert_equal 'The Fifth Topic of the day', records[2].title + assert_equal "The Third Topic of the day", records[0].title + assert_equal "The Second Topic of the day", records[1].title + assert_equal "The Fifth Topic of the day", records[2].title end def test_find_with_ids_where_and_limit @@ -104,17 +104,17 @@ class FinderTest < ActiveRecord::TestCase # if it were among the first 3 it would raise an ActiveRecord::RecordNotFound records = Topic.where(approved: true).limit(3).find([3,2,5,1,4]) assert_equal 3, records.size - assert_equal 'The Third Topic of the day', records[0].title - assert_equal 'The Second Topic of the day', records[1].title - assert_equal 'The Fifth Topic of the day', records[2].title + assert_equal "The Third Topic of the day", records[0].title + assert_equal "The Second Topic of the day", records[1].title + assert_equal "The Fifth Topic of the day", records[2].title end def test_find_with_ids_and_offset records = Topic.offset(2).find([3,2,5,1,4]) assert_equal 3, records.size - assert_equal 'The Fifth Topic of the day', records[0].title - assert_equal 'The First Topic', records[1].title - assert_equal 'The Fourth Topic of the day', records[2].title + assert_equal "The Fifth Topic of the day", records[0].title + assert_equal "The First Topic", records[1].title + assert_equal "The Fourth Topic of the day", records[2].title end def test_find_passing_active_record_object_is_deprecated @@ -127,7 +127,7 @@ class FinderTest < ActiveRecord::TestCase gc_disabled = GC.disable Post.where("author_id" => nil) # warm up x = Symbol.all_symbols.count - Post.where("title" => {"xxxqqqq" => "bar"}) + Post.where("title" => { "xxxqqqq" => "bar" }) assert_equal x, Symbol.all_symbols.count ensure GC.enable if gc_disabled == false @@ -144,7 +144,7 @@ class FinderTest < ActiveRecord::TestCase assert_equal true, Topic.exists?("1") assert_equal true, Topic.exists?(title: "The First Topic") assert_equal true, Topic.exists?(heading: "The First Topic") - assert_equal true, Topic.exists?(:author_name => "Mary", :approved => true) + assert_equal true, Topic.exists?(author_name: "Mary", approved: true) assert_equal true, Topic.exists?(["parent_id = ?", 1]) assert_equal true, Topic.exists?(id: [1, 9999]) @@ -155,11 +155,11 @@ class FinderTest < ActiveRecord::TestCase end def test_exists_with_polymorphic_relation - post = Post.create!(title: 'Post', body: 'default', taggings: [Tagging.new(comment: 'tagging comment')]) - relation = Post.tagged_with_comment('tagging comment') + post = Post.create!(title: "Post", body: "default", taggings: [Tagging.new(comment: "tagging comment")]) + relation = Post.tagged_with_comment("tagging comment") - assert_equal true, relation.exists?(title: ['Post']) - assert_equal true, relation.exists?(['title LIKE ?', 'Post%']) + assert_equal true, relation.exists?(title: ["Post"]) + assert_equal true, relation.exists?(["title LIKE ?", "Post%"]) assert_equal true, relation.exists? assert_equal true, relation.exists?(post.id) assert_equal true, relation.exists?(post.id.to_s) @@ -173,11 +173,9 @@ class FinderTest < ActiveRecord::TestCase end end - def test_exists_fails_when_parameter_has_invalid_type - assert_raises(RangeError) do - assert_equal false, Topic.exists?(("9"*53).to_i) # number that's bigger than int - end + def test_exists_returns_false_when_parameter_has_invalid_type assert_equal false, Topic.exists?("foo") + assert_equal false, Topic.exists?(("9"*53).to_i) # number that's bigger than int end def test_exists_does_not_select_columns_without_alias @@ -211,7 +209,7 @@ class FinderTest < ActiveRecord::TestCase def test_exists_with_includes_limit_and_empty_result assert_equal false, Topic.includes(:replies).limit(0).exists? - assert_equal false, Topic.includes(:replies).limit(1).where('0 = 1').exists? + assert_equal false, Topic.includes(:replies).limit(1).where("0 = 1").exists? end def test_exists_with_distinct_association_includes_and_limit @@ -222,8 +220,8 @@ class FinderTest < ActiveRecord::TestCase def test_exists_with_distinct_association_includes_limit_and_order author = Author.first - assert_equal false, author.unique_categorized_posts.includes(:special_comments).order('comments.tags_count DESC').limit(0).exists? - assert_equal true, author.unique_categorized_posts.includes(:special_comments).order('comments.tags_count DESC').limit(1).exists? + assert_equal false, author.unique_categorized_posts.includes(:special_comments).order("comments.tags_count DESC").limit(0).exists? + assert_equal true, author.unique_categorized_posts.includes(:special_comments).order("comments.tags_count DESC").limit(1).exists? end def test_exists_with_empty_table_and_no_args_given @@ -233,17 +231,14 @@ class FinderTest < ActiveRecord::TestCase def test_exists_with_aggregate_having_three_mappings existing_address = customers(:david).address - assert_equal true, Customer.exists?(:address => existing_address) + assert_equal true, Customer.exists?(address: existing_address) end def test_exists_with_aggregate_having_three_mappings_with_one_difference existing_address = customers(:david).address - assert_equal false, Customer.exists?(:address => - Address.new(existing_address.street, existing_address.city, existing_address.country + "1")) - assert_equal false, Customer.exists?(:address => - Address.new(existing_address.street, existing_address.city + "1", existing_address.country)) - assert_equal false, Customer.exists?(:address => - Address.new(existing_address.street + "1", existing_address.city, existing_address.country)) + assert_equal false, Customer.exists?(address: Address.new(existing_address.street, existing_address.city, existing_address.country + "1")) + assert_equal false, Customer.exists?(address: Address.new(existing_address.street, existing_address.city + "1", existing_address.country)) + assert_equal false, Customer.exists?(address: Address.new(existing_address.street + "1", existing_address.city, existing_address.country)) end def test_exists_does_not_instantiate_records @@ -266,7 +261,7 @@ class FinderTest < ActiveRecord::TestCase assert_equal 2, Entrant.limit(2).find([1,3,2]).size entrants = Entrant.limit(3).offset(2).find([1,3,2]) assert_equal 1, entrants.size - assert_equal 'Ruby Guru', entrants.first.name + assert_equal "Ruby Guru", entrants.first.name # Also test an edge case: If you have 11 results, and you set a # limit of 3 and offset of 9, then you should find that there @@ -274,29 +269,29 @@ class FinderTest < ActiveRecord::TestCase devs = Developer.all last_devs = Developer.limit(3).offset(9).find devs.map(&:id) assert_equal 2, last_devs.size - assert_equal 'fixture_10', last_devs[0].name - assert_equal 'Jamis', last_devs[1].name + assert_equal "fixture_10", last_devs[0].name + assert_equal "Jamis", last_devs[1].name end def test_find_with_large_number - assert_raises(ActiveRecord::RecordNotFound) { Topic.find('9999999999999999999999999999999') } + assert_raises(ActiveRecord::RecordNotFound) { Topic.find("9999999999999999999999999999999") } end def test_find_by_with_large_number - assert_nil Topic.find_by(id: '9999999999999999999999999999999') + assert_nil Topic.find_by(id: "9999999999999999999999999999999") end def test_find_by_id_with_large_number - assert_nil Topic.find_by_id('9999999999999999999999999999999') + assert_nil Topic.find_by_id("9999999999999999999999999999999") end def test_find_on_relation_with_large_number - assert_nil Topic.where('1=1').find_by(id: 9999999999999999999999999999999) + assert_nil Topic.where("1=1").find_by(id: 9999999999999999999999999999999) end def test_find_by_bang_on_relation_with_large_number assert_raises(ActiveRecord::RecordNotFound) do - Topic.where('1=1').find_by!(id: 9999999999999999999999999999999) + Topic.where("1=1").find_by!(id: 9999999999999999999999999999999) end end @@ -313,7 +308,7 @@ class FinderTest < ActiveRecord::TestCase end def test_find_with_group_and_sanitized_having_method - developers = Developer.group(:salary).having("sum(salary) > ?", 10000).select('salary').to_a + developers = Developer.group(:salary).having("sum(salary) > ?", 10000).select("salary").to_a assert_equal 3, developers.size assert_equal 3, developers.map(&:salary).uniq.size assert developers.all? { |developer| developer.salary > 10000 } @@ -486,6 +481,66 @@ class FinderTest < ActiveRecord::TestCase end end + def test_second_to_last + assert_equal topics(:fourth).title, Topic.second_to_last.title + + # test with offset + 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 + + #test with limit + # assert_equal nil, Topic.limit(1).second # TODO: currently failing + assert_equal nil, Topic.limit(1).second_to_last + end + + def test_second_to_last_have_primary_key_order_by_default + expected = topics(:fourth) + expected.touch # PostgreSQL changes the default order if no order clause is used + assert_equal expected, Topic.second_to_last + end + + def test_model_class_responds_to_second_to_last_bang + assert Topic.second_to_last! + Topic.delete_all + assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do + Topic.second_to_last! + end + end + + def test_third_to_last + assert_equal topics(:third).title, Topic.third_to_last.title + + # 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 + + # 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 + end + + def test_third_to_last_have_primary_key_order_by_default + expected = topics(:third) + expected.touch # PostgreSQL changes the default order if no order clause is used + assert_equal expected, Topic.third_to_last + end + + def test_model_class_responds_to_third_to_last_bang + assert Topic.third_to_last! + Topic.delete_all + assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do + Topic.third_to_last! + end + end + def test_last_bang_present assert_nothing_raised do assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").last! @@ -507,9 +562,9 @@ class FinderTest < ActiveRecord::TestCase end def test_take_and_first_and_last_with_integer_should_use_sql_limit - assert_sql(/LIMIT|ROWNUM <=/) { Topic.take(3).entries } - assert_sql(/LIMIT|ROWNUM <=/) { Topic.first(2).entries } - assert_sql(/LIMIT|ROWNUM <=/) { Topic.last(5).entries } + assert_sql(/LIMIT|ROWNUM <=|FETCH FIRST/) { Topic.take(3).entries } + assert_sql(/LIMIT|ROWNUM <=|FETCH FIRST/) { Topic.first(2).entries } + assert_sql(/LIMIT|ROWNUM <=|FETCH FIRST/) { Topic.last(5).entries } end def test_last_with_integer_and_order_should_keep_the_order @@ -540,10 +595,10 @@ class FinderTest < ActiveRecord::TestCase assert_deprecated do Topic.order("coalesce(author_name, title)").last end - end + end def test_last_on_relation_with_limit_and_offset - post = posts('sti_comments') + post = posts("sti_comments") comments = post.comments.order(id: :asc) assert_equal comments.limit(2).to_a.last, comments.limit(2).last @@ -572,8 +627,8 @@ class FinderTest < ActiveRecord::TestCase def test_find_only_some_columns topic = Topic.select("author_name").find(1) - assert_raise(ActiveModel::MissingAttributeError) {topic.title} - assert_raise(ActiveModel::MissingAttributeError) {topic.title?} + assert_raise(ActiveModel::MissingAttributeError) { topic.title } + assert_raise(ActiveModel::MissingAttributeError) { topic.title? } assert_nil topic.read_attribute("title") assert_equal "David", topic.author_name assert !topic.attribute_present?("title") @@ -592,9 +647,14 @@ class FinderTest < ActiveRecord::TestCase assert_raise(ActiveRecord::RecordNotFound) { Topic.where(approved: true).find(1) } end - def test_find_on_hash_conditions_with_explicit_table_name - assert Topic.where('topics.approved' => false).find(1) - assert_raise(ActiveRecord::RecordNotFound) { Topic.where('topics.approved' => true).find(1) } + def test_find_on_hash_conditions_with_qualified_attribute_dot_notation_string + assert Topic.where("topics.approved" => false).find(1) + assert_raise(ActiveRecord::RecordNotFound) { Topic.where("topics.approved" => true).find(1) } + end + + def test_find_on_hash_conditions_with_qualified_attribute_dot_notation_symbol + assert Topic.where('topics.approved': false).find(1) + assert_raise(ActiveRecord::RecordNotFound) { Topic.where('topics.approved': true).find(1) } end def test_find_on_hash_conditions_with_hashed_table_name @@ -603,28 +663,28 @@ class FinderTest < ActiveRecord::TestCase end def test_find_on_combined_explicit_and_hashed_table_names - assert Topic.where('topics.approved' => false, topics: { author_name: "David" }).find(1) - assert_raise(ActiveRecord::RecordNotFound) { Topic.where('topics.approved' => true, topics: { author_name: "David" }).find(1) } - assert_raise(ActiveRecord::RecordNotFound) { Topic.where('topics.approved' => false, topics: { author_name: "Melanie" }).find(1) } + assert Topic.where("topics.approved" => false, topics: { author_name: "David" }).find(1) + assert_raise(ActiveRecord::RecordNotFound) { Topic.where("topics.approved" => true, topics: { author_name: "David" }).find(1) } + assert_raise(ActiveRecord::RecordNotFound) { Topic.where("topics.approved" => false, topics: { author_name: "Melanie" }).find(1) } end def test_find_with_hash_conditions_on_joined_table - firms = Firm.joins(:account).where(:accounts => { :credit_limit => 50 }) + firms = Firm.joins(:account).where(accounts: { credit_limit: 50 }) assert_equal 1, firms.size assert_equal companies(:first_firm), firms.first end def test_find_with_hash_conditions_on_joined_table_and_with_range - firms = DependentFirm.joins(:account).where(name: 'RailsCore', accounts: { credit_limit: 55..60 }) + firms = DependentFirm.joins(:account).where(name: "RailsCore", accounts: { credit_limit: 55..60 }) assert_equal 1, firms.size assert_equal companies(:rails_core), firms.first end def test_find_on_hash_conditions_with_explicit_table_name_and_aggregate david = customers(:david) - assert Customer.where('customers.name' => david.name, :address => david.address).find(david.id) + assert Customer.where("customers.name" => david.name, :address => david.address).find(david.id) assert_raise(ActiveRecord::RecordNotFound) { - Customer.where('customers.name' => david.name + "1", :address => david.address).find(david.id) + Customer.where("customers.name" => david.name + "1", :address => david.address).find(david.id) } end @@ -695,9 +755,9 @@ class FinderTest < ActiveRecord::TestCase end def test_hash_condition_find_with_array - p1, p2 = Post.limit(2).order('id asc').to_a - assert_equal [p1, p2], Post.where(id: [p1, p2]).order('id asc').to_a - assert_equal [p1, p2], Post.where(id: [p1, p2.id]).order('id asc').to_a + p1, p2 = Post.limit(2).order("id asc").to_a + assert_equal [p1, p2], Post.where(id: [p1, p2]).order("id asc").to_a + assert_equal [p1, p2], Post.where(id: [p1, p2.id]).order("id asc").to_a end def test_hash_condition_find_with_nil @@ -709,56 +769,56 @@ class FinderTest < ActiveRecord::TestCase def test_hash_condition_find_with_aggregate_having_one_mapping balance = customers(:david).balance assert_kind_of Money, balance - found_customer = Customer.where(:balance => balance).first + found_customer = Customer.where(balance: balance).first assert_equal customers(:david), found_customer end def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_aggregate gps_location = customers(:david).gps_location assert_kind_of GpsLocation, gps_location - found_customer = Customer.where(:gps_location => gps_location).first + found_customer = Customer.where(gps_location: gps_location).first assert_equal customers(:david), found_customer end def test_hash_condition_find_with_aggregate_having_one_mapping_and_key_value_being_attribute_value balance = customers(:david).balance assert_kind_of Money, balance - found_customer = Customer.where(:balance => balance.amount).first + found_customer = Customer.where(balance: balance.amount).first assert_equal customers(:david), found_customer end def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_attribute_value gps_location = customers(:david).gps_location assert_kind_of GpsLocation, gps_location - found_customer = Customer.where(:gps_location => gps_location.gps_location).first + found_customer = Customer.where(gps_location: gps_location.gps_location).first assert_equal customers(:david), found_customer end def test_hash_condition_find_with_aggregate_having_three_mappings address = customers(:david).address assert_kind_of Address, address - found_customer = Customer.where(:address => address).first + found_customer = Customer.where(address: address).first assert_equal customers(:david), found_customer end def test_hash_condition_find_with_one_condition_being_aggregate_and_another_not address = customers(:david).address assert_kind_of Address, address - found_customer = Customer.where(:address => address, :name => customers(:david).name).first + found_customer = Customer.where(address: address, name: customers(:david).name).first assert_equal customers(:david), found_customer end def test_condition_utc_time_interpolation_with_default_timezone_local - with_env_tz 'America/New_York' do + with_env_tz "America/New_York" do with_timezone_config default: :local do topic = Topic.first - assert_equal topic, Topic.where(['written_on = ?', topic.written_on.getutc]).first + assert_equal topic, Topic.where(["written_on = ?", topic.written_on.getutc]).first end end end def test_hash_condition_utc_time_interpolation_with_default_timezone_local - with_env_tz 'America/New_York' do + with_env_tz "America/New_York" do with_timezone_config default: :local do topic = Topic.first assert_equal topic, Topic.where(written_on: topic.written_on.getutc).first @@ -767,16 +827,16 @@ class FinderTest < ActiveRecord::TestCase end def test_condition_local_time_interpolation_with_default_timezone_utc - with_env_tz 'America/New_York' do + with_env_tz "America/New_York" do with_timezone_config default: :utc do topic = Topic.first - assert_equal topic, Topic.where(['written_on = ?', topic.written_on.getlocal]).first + assert_equal topic, Topic.where(["written_on = ?", topic.written_on.getlocal]).first end end end def test_hash_condition_local_time_interpolation_with_default_timezone_utc - with_env_tz 'America/New_York' do + with_env_tz "America/New_York" do with_timezone_config default: :utc do topic = Topic.first assert_equal topic, Topic.where(written_on: topic.written_on.getlocal).first @@ -793,7 +853,7 @@ class FinderTest < ActiveRecord::TestCase Company.where(["id=? AND name = ?", 2]).first } assert_raise(ActiveRecord::PreparedStatementInvalid) { - Company.where(["id=?", 2, 3, 4]).first + Company.where(["id=?", 2, 3, 4]).first } end @@ -804,7 +864,7 @@ class FinderTest < ActiveRecord::TestCase 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 + assert Company.where(["name = :name", { name: "37signals' go'es agains" }]).first end def test_named_bind_variables @@ -814,11 +874,6 @@ class FinderTest < ActiveRecord::TestCase assert_kind_of Time, Topic.where(["id = :id", { id: 1 }]).first.written_on end - def test_string_sanitation - assert_not_equal "'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1") - assert_equal "'something; select table'", ActiveRecord::Base.sanitize("something; select table") - end - def test_count_by_sql assert_equal(0, Entrant.count_by_sql("SELECT COUNT(*) FROM entrants WHERE id > 3")) assert_equal(1, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 2])) @@ -838,7 +893,7 @@ class FinderTest < ActiveRecord::TestCase end def test_find_by_on_attribute_that_is_a_reserved_word - dog_alias = 'Dog' + dog_alias = "Dog" dog = Dog.create(alias: dog_alias) assert_equal dog, Dog.find_by_alias(dog_alias) @@ -855,7 +910,7 @@ class FinderTest < ActiveRecord::TestCase end def test_find_by_one_attribute_with_conditions - assert_equal accounts(:rails_core_account), Account.where('firm_id = ?', 6).find_by_credit_limit(50) + assert_equal accounts(:rails_core_account), Account.where("firm_id = ?", 6).find_by_credit_limit(50) end def test_find_by_one_attribute_that_is_an_aggregate @@ -895,12 +950,12 @@ class FinderTest < ActiveRecord::TestCase def test_dynamic_finder_on_one_attribute_with_conditions_returns_same_results_after_caching # ensure this test can run independently of order class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.public_methods.include?(:find_by_credit_limit) - a = Account.where('firm_id = ?', 6).find_by_credit_limit(50) - assert_equal a, Account.where('firm_id = ?', 6).find_by_credit_limit(50) # find_by_credit_limit has been cached + a = Account.where("firm_id = ?", 6).find_by_credit_limit(50) + assert_equal a, Account.where("firm_id = ?", 6).find_by_credit_limit(50) # find_by_credit_limit has been cached end def test_find_by_one_attribute_with_several_options - assert_equal accounts(:unknown), Account.order('id DESC').where('id != ?', 3).find_by_credit_limit(50) + assert_equal accounts(:unknown), Account.order("id DESC").where("id != ?", 3).find_by_credit_limit(50) end def test_find_by_one_missing_attribute @@ -924,12 +979,12 @@ class FinderTest < ActiveRecord::TestCase end def test_find_last_with_offset - devs = Developer.order('id') + devs = Developer.order("id") 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 + assert_equal devs[-3], Developer.offset(2).order("id DESC").first end def test_find_by_nil_attribute @@ -949,18 +1004,18 @@ class FinderTest < ActiveRecord::TestCase def test_find_all_with_join developers_on_project_one = Developer. - joins('LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id'). - where('project_id=1').to_a + joins("LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id"). + where("project_id=1").to_a assert_equal 3, developers_on_project_one.length developer_names = developers_on_project_one.map(&:name) - assert developer_names.include?('David') - assert developer_names.include?('Jamis') + assert_includes developer_names, "David" + assert_includes developer_names, "Jamis" end def test_joins_dont_clobber_id first = Firm. - joins('INNER JOIN companies clients ON clients.firm_id = companies.id'). - where('companies.id = 1').first + joins("INNER JOIN companies clients ON clients.firm_id = companies.id"). + where("companies.id = 1").first assert_equal 1, first.id end @@ -979,7 +1034,7 @@ class FinderTest < ActiveRecord::TestCase end def test_find_ignores_previously_inserted_record - Post.create!(:title => 'test', :body => 'it out') + Post.create!(title: "test", body: "it out") assert_equal [], Post.where(id: nil) end @@ -988,13 +1043,13 @@ class FinderTest < ActiveRecord::TestCase end def test_find_by_empty_in_condition - assert_equal [], Post.where('id in (?)', []) + assert_equal [], Post.where("id in (?)", []) end def test_find_by_records - p1, p2 = Post.limit(2).order('id asc').to_a - assert_equal [p1, p2], Post.where(['id in (?)', [p1, p2]]).order('id asc') - assert_equal [p1, p2], Post.where(['id in (?)', [p1, p2.id]]).order('id asc') + p1, p2 = Post.limit(2).order("id asc").to_a + assert_equal [p1, p2], Post.where(["id in (?)", [p1, p2]]).order("id asc") + assert_equal [p1, p2], Post.where(["id in (?)", [p1, p2.id]]).order("id asc") end def test_select_value @@ -1015,36 +1070,36 @@ class FinderTest < ActiveRecord::TestCase [["1", "1", nil, "37signals"], ["2", "1", "2", "Summit"], ["3", "1", "1", "Microsoft"]], - Company.connection.select_rows("SELECT id, firm_id, client_of, name FROM companies WHERE id IN (1,2,3) ORDER BY id").map! {|i| i.map! {|j| j.to_s unless j.nil?}}) + Company.connection.select_rows("SELECT id, firm_id, client_of, name FROM companies WHERE id IN (1,2,3) ORDER BY id").map! { |i| i.map! { |j| j.to_s unless j.nil? } }) assert_equal [["1", "37signals"], ["2", "Summit"], ["3", "Microsoft"]], - Company.connection.select_rows("SELECT id, name FROM companies WHERE id IN (1,2,3) ORDER BY id").map! {|i| i.map! {|j| j.to_s unless j.nil?}} + Company.connection.select_rows("SELECT id, name FROM companies WHERE id IN (1,2,3) ORDER BY id").map! { |i| i.map! { |j| j.to_s unless j.nil? } } end def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct assert_equal 2, Post.includes(authors: :author_address). where.not(author_addresses: { id: nil }). - order('author_addresses.id DESC').limit(2).to_a.size + order("author_addresses.id DESC").limit(2).to_a.size assert_equal 3, Post.includes(author: :author_address, authors: :author_address). where.not(author_addresses_authors: { id: nil }). - order('author_addresses_authors.id DESC').limit(3).to_a.size + order("author_addresses_authors.id DESC").limit(3).to_a.size end def test_find_with_nil_inside_set_passed_for_one_attribute client_of = Company. where(client_of: [2, 1, nil], - name: ['37signals', 'Summit', 'Microsoft']). - order('client_of DESC'). + name: ["37signals", "Summit", "Microsoft"]). + order("client_of DESC"). map(&:client_of) - assert client_of.include?(nil) + assert_includes client_of, nil assert_equal [2, 1].sort, client_of.compact.sort end def test_find_with_nil_inside_set_passed_for_attribute client_of = Company. where(client_of: [nil]). - order('client_of DESC'). + order("client_of DESC"). map(&:client_of) assert_equal [], client_of.compact @@ -1052,20 +1107,30 @@ class FinderTest < ActiveRecord::TestCase def test_with_limiting_with_custom_select posts = Post.references(:authors).merge( - :includes => :author, :select => 'posts.*, authors.id as "author_id"', - :limit => 3, :order => 'posts.id' + includes: :author, select: 'posts.*, authors.id as "author_id"', + limit: 3, order: "posts.id" ).to_a assert_equal 3, posts.size assert_equal [0, 1, 1], posts.map(&:author_id).sort end + def test_find_one_message_on_primary_key + e = assert_raises(ActiveRecord::RecordNotFound) do + Car.find(0) + end + assert_equal 0, e.id + assert_equal "id", e.primary_key + assert_equal "Car", e.model + assert_equal "Couldn't find Car with 'id'=0", e.message + end + def test_find_one_message_with_custom_primary_key table_with_custom_primary_key do |model| model.primary_key = :name e = assert_raises(ActiveRecord::RecordNotFound) do - model.find 'Hello World!' + model.find "Hello World!" end - assert_equal %Q{Couldn't find MercedesCar with 'name'=Hello World!}, e.message + assert_equal "Couldn't find MercedesCar with 'name'=Hello World!", e.message end end @@ -1073,9 +1138,9 @@ class FinderTest < ActiveRecord::TestCase table_with_custom_primary_key do |model| model.primary_key = :name e = assert_raises(ActiveRecord::RecordNotFound) do - model.find 'Hello', 'World!' + model.find "Hello", "World!" end - assert_equal %Q{Couldn't find all MercedesCars with 'name': (Hello, World!) (found 0 results, but was looking for 2)}, e.message + assert_equal "Couldn't find all MercedesCars with 'name': (Hello, World!) (found 0 results, but was looking for 2)", e.message end end @@ -1086,7 +1151,7 @@ class FinderTest < ActiveRecord::TestCase end def test_finder_with_offset_string - assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.offset("3").to_a } + assert_nothing_raised { Topic.offset("3").to_a } end test "find_by with hash conditions returns the first matching record" do @@ -1098,7 +1163,7 @@ class FinderTest < ActiveRecord::TestCase end test "find_by with multi-arg conditions returns the first matching record" do - assert_equal posts(:eager_other), Post.find_by('id = ?', posts(:eager_other).id) + assert_equal posts(:eager_other), Post.find_by("id = ?", posts(:eager_other).id) end test "find_by returns nil if the record is missing" do @@ -1123,7 +1188,7 @@ class FinderTest < ActiveRecord::TestCase end test "find_by! with multi-arg conditions returns the first matching record" do - assert_equal posts(:eager_other), Post.find_by!('id = ?', posts(:eager_other).id) + assert_equal posts(:eager_other), Post.find_by!("id = ?", posts(:eager_other).id) end test "find_by! doesn't have implicit ordering" do @@ -1160,7 +1225,7 @@ class FinderTest < ActiveRecord::TestCase def table_with_custom_primary_key yield(Class.new(Toy) do def self.name - 'MercedesCar' + "MercedesCar" end end) end @@ -1169,5 +1234,4 @@ class FinderTest < ActiveRecord::TestCase err = assert_raises(exception_class) { block.call } assert_match message, err.message end - end diff --git a/activerecord/test/cases/fixture_set/file_test.rb b/activerecord/test/cases/fixture_set/file_test.rb index 242e7a9bec..cf2a73595a 100644 --- a/activerecord/test/cases/fixture_set/file_test.rb +++ b/activerecord/test/cases/fixture_set/file_test.rb @@ -1,5 +1,5 @@ -require 'cases/helper' -require 'tempfile' +require "cases/helper" +require "tempfile" module ActiveRecord class FixtureSet @@ -15,7 +15,7 @@ module ActiveRecord called = true assert_equal 6, fh.to_a.length end - assert called, 'block called' + assert called, "block called" end def test_names @@ -32,7 +32,7 @@ module ActiveRecord def test_values File.open(::File.join(FIXTURES_ROOT, "accounts.yml")) do |fh| assert_equal [1,2,3,4,5,6].sort, fh.to_a.map(&:last).map { |x| - x['id'] + x["id"] }.sort end end @@ -45,7 +45,7 @@ module ActiveRecord end def test_empty_file - tmp_yaml ['empty', 'yml'], '' do |t| + tmp_yaml ["empty", "yml"], "" do |t| assert_equal [], File.open(t.path) { |fh| fh.to_a } end end @@ -53,7 +53,7 @@ module ActiveRecord # A valid YAML file is not necessarily a value Fixture file. Make sure # an exception is raised if the format is not valid Fixture format. def test_wrong_fixture_format_string - tmp_yaml ['empty', 'yml'], 'qwerty' do |t| + tmp_yaml ["empty", "yml"], "qwerty" do |t| assert_raises(ActiveRecord::Fixture::FormatError) do File.open(t.path) { |fh| fh.to_a } end @@ -61,7 +61,7 @@ module ActiveRecord end def test_wrong_fixture_format_nested - tmp_yaml ['empty', 'yml'], 'one: two' do |t| + tmp_yaml ["empty", "yml"], "one: two" do |t| assert_raises(ActiveRecord::Fixture::FormatError) do File.open(t.path) { |fh| fh.to_a } end @@ -75,9 +75,9 @@ module ActiveRecord end end yaml = "one:\n name: <%= fixture_helper %>\n" - tmp_yaml ['curious', 'yml'], yaml do |t| + tmp_yaml ["curious", "yml"], yaml do |t| golden = - [["one", {"name" => "Fixture helper"}]] + [["one", { "name" => "Fixture helper" }]] assert_equal golden, File.open(t.path) { |fh| fh.to_a } end ActiveRecord::FixtureSet.context_class.class_eval do @@ -95,15 +95,15 @@ one: File: <%= File.name %> END - golden = [['one', { - 'ActiveRecord' => 'constant', - 'ActiveRecord_FixtureSet' => 'constant', - 'FixtureSet' => nil, - 'ActiveRecord_FixtureSet_File' => 'constant', - 'File' => 'File' + golden = [["one", { + "ActiveRecord" => "constant", + "ActiveRecord_FixtureSet" => "constant", + "FixtureSet" => nil, + "ActiveRecord_FixtureSet_File" => "constant", + "File" => "File" }]] - tmp_yaml ['curious', 'yml'], yaml do |t| + tmp_yaml ["curious", "yml"], yaml do |t| assert_equal golden, File.open(t.path) { |fh| fh.to_a } end end @@ -113,8 +113,8 @@ END def test_independent_render_contexts yaml1 = "<% def leaked_method; 'leak'; end %>\n" yaml2 = "one:\n name: <%= leaked_method %>\n" - tmp_yaml ['leaky', 'yml'], yaml1 do |t1| - tmp_yaml ['curious', 'yml'], yaml2 do |t2| + tmp_yaml ["leaky", "yml"], yaml1 do |t1| + tmp_yaml ["curious", "yml"], yaml2 do |t2| File.open(t1.path) { |fh| fh.to_a } assert_raises(NameError) do File.open(t2.path) { |fh| fh.to_a } @@ -124,27 +124,33 @@ END end def test_removes_fixture_config_row - File.open(::File.join(FIXTURES_ROOT, 'other_posts.yml')) do |fh| - assert_equal(['second_welcome'], fh.each.map { |name, _| name }) + File.open(::File.join(FIXTURES_ROOT, "other_posts.yml")) do |fh| + assert_equal(["second_welcome"], fh.each.map { |name, _| name }) end end def test_extracts_model_class_from_config_row - File.open(::File.join(FIXTURES_ROOT, 'other_posts.yml')) do |fh| - assert_equal 'Post', fh.model_class + File.open(::File.join(FIXTURES_ROOT, "other_posts.yml")) do |fh| + assert_equal "Post", fh.model_class end end - private - def tmp_yaml(name, contents) - t = Tempfile.new name - t.binmode - t.write contents - t.close - yield t - ensure - t.close true + def test_erb_filename + filename = "filename.yaml" + erb = File.new(filename).send(:prepare_erb, "<% Rails.env %>\n") + assert_equal erb.filename, filename end + + private + def tmp_yaml(name, contents) + t = Tempfile.new name + t.binmode + t.write contents + t.close + yield t + ensure + t.close true + end end end end diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index da934ab8fe..3f111447ff 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -1,31 +1,31 @@ -require 'cases/helper' -require 'models/admin' -require 'models/admin/account' -require 'models/admin/randomly_named_c1' -require 'models/admin/user' -require 'models/binary' -require 'models/book' -require 'models/bulb' -require 'models/category' -require 'models/comment' -require 'models/company' -require 'models/computer' -require 'models/course' -require 'models/developer' -require 'models/doubloon' -require 'models/joke' -require 'models/matey' -require 'models/parrot' -require 'models/pirate' -require 'models/post' -require 'models/randomly_named_c1' -require 'models/reply' -require 'models/ship' -require 'models/task' -require 'models/topic' -require 'models/traffic_light' -require 'models/treasure' -require 'tempfile' +require "cases/helper" +require "models/admin" +require "models/admin/account" +require "models/admin/randomly_named_c1" +require "models/admin/user" +require "models/binary" +require "models/book" +require "models/bulb" +require "models/category" +require "models/comment" +require "models/company" +require "models/computer" +require "models/course" +require "models/developer" +require "models/doubloon" +require "models/joke" +require "models/matey" +require "models/parrot" +require "models/pirate" +require "models/post" +require "models/randomly_named_c1" +require "models/reply" +require "models/ship" +require "models/task" +require "models/topic" +require "models/traffic_light" +require "models/treasure" +require "tempfile" class FixturesTest < ActiveRecord::TestCase self.use_instantiated_fixtures = true @@ -53,12 +53,12 @@ class FixturesTest < ActiveRecord::TestCase end def test_broken_yaml_exception - badyaml = Tempfile.new ['foo', '.yml'] - badyaml.write 'a: : ' + badyaml = Tempfile.new ["foo", ".yml"] + badyaml.write "a: : " badyaml.flush dir = File.dirname badyaml.path - name = File.basename badyaml.path, '.yml' + name = File.basename badyaml.path, ".yml" assert_raises(ActiveRecord::Fixture::FormatError) do ActiveRecord::FixtureSet.create_fixtures(dir, name) end @@ -69,8 +69,8 @@ class FixturesTest < ActiveRecord::TestCase def test_create_fixtures fixtures = ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT, "parrots") - assert Parrot.find_by_name('Curious George'), 'George is not in the database' - assert fixtures.detect { |f| f.name == 'parrots' }, "no fixtures named 'parrots' in #{fixtures.map(&:name).inspect}" + assert Parrot.find_by_name("Curious George"), "George is not in the database" + assert fixtures.detect { |f| f.name == "parrots" }, "no fixtures named 'parrots' in #{fixtures.map(&:name).inspect}" end def test_multiple_clean_fixtures @@ -81,10 +81,10 @@ class FixturesTest < ActiveRecord::TestCase end def test_create_symbol_fixtures - fixtures = ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT, :collections, :collections => Course) { Course.connection } + fixtures = ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT, :collections, collections: Course) { Course.connection } - assert Course.find_by_name('Collection'), 'course is not in the database' - assert fixtures.detect { |f| f.name == 'collections' }, "no fixtures named 'collections' in #{fixtures.map(&:name).inspect}" + assert Course.find_by_name("Collection"), "course is not in the database" + assert fixtures.detect { |f| f.name == "collections" }, "no fixtures named 'collections' in #{fixtures.map(&:name).inspect}" end def test_attributes @@ -115,10 +115,10 @@ class FixturesTest < ActiveRecord::TestCase t.column :bonus_time, :time t.column :last_read, :date t.column :content, :string - t.column :approved, :boolean, :default => true - t.column :replies_count, :integer, :default => 0 + t.column :approved, :boolean, default: true + t.column :replies_count, :integer, default: 0 t.column :parent_id, :integer - t.column :type, :string, :limit => 50 + t.column :type, :string, limit: 50 end # Store existing prefix/suffix @@ -126,8 +126,8 @@ class FixturesTest < ActiveRecord::TestCase old_suffix = ActiveRecord::Base.table_name_suffix # Set a prefix/suffix we can test against - ActiveRecord::Base.table_name_prefix = 'prefix_' - ActiveRecord::Base.table_name_suffix = '_suffix' + ActiveRecord::Base.table_name_prefix = "prefix_" + ActiveRecord::Base.table_name_suffix = "_suffix" other_topic_klass = Class.new(ActiveRecord::Base) do def self.name @@ -170,7 +170,7 @@ class FixturesTest < ActiveRecord::TestCase def test_logger_level_invariant level = ActiveRecord::Base.logger.level - create_fixtures('topics') + create_fixtures("topics") assert_equal level, ActiveRecord::Base.logger.level end @@ -229,11 +229,11 @@ class FixturesTest < ActiveRecord::TestCase def test_omap_fixtures assert_nothing_raised do - fixtures = ActiveRecord::FixtureSet.new(Account.connection, 'categories', Category, FIXTURES_ROOT + "/categories_ordered") + fixtures = ActiveRecord::FixtureSet.new(Account.connection, "categories", Category, FIXTURES_ROOT + "/categories_ordered") fixtures.each.with_index do |(name, fixture), i| assert_equal "fixture_no_#{i}", name - assert_equal "Category #{i}", fixture['name'] + assert_equal "Category #{i}", fixture["name"] end end end @@ -249,8 +249,8 @@ class FixturesTest < ActiveRecord::TestCase end def test_binary_in_fixtures - data = File.open(ASSETS_ROOT + "/flowers.jpg", 'rb') { |f| f.read } - data.force_encoding('ASCII-8BIT') + data = File.open(ASSETS_ROOT + "/flowers.jpg", "rb") { |f| f.read } + data.force_encoding("ASCII-8BIT") data.freeze assert_equal data, @flowers.data end @@ -260,8 +260,8 @@ class FixturesTest < ActiveRecord::TestCase end def test_fixtures_are_set_up_with_database_env_variable - db_url_tmp = ENV['DATABASE_URL'] - ENV['DATABASE_URL'] = "sqlite3::memory:" + db_url_tmp = ENV["DATABASE_URL"] + ENV["DATABASE_URL"] = "sqlite3::memory:" ActiveRecord::Base.stub(:configurations, {}) do test_case = Class.new(ActiveRecord::TestCase) do fixtures :accounts @@ -276,11 +276,11 @@ class FixturesTest < ActiveRecord::TestCase assert result.passed?, "Expected #{result.name} to pass:\n#{result}" end ensure - ENV['DATABASE_URL'] = db_url_tmp + ENV["DATABASE_URL"] = db_url_tmp end end -class HasManyThroughFixture < ActiveSupport::TestCase +class HasManyThroughFixture < ActiveRecord::TestCase def make_model(name) Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } } end @@ -291,17 +291,17 @@ class HasManyThroughFixture < ActiveSupport::TestCase treasure = make_model "Treasure" pt.table_name = "parrots_treasures" - pt.belongs_to :parrot, :anonymous_class => parrot - pt.belongs_to :treasure, :anonymous_class => treasure + pt.belongs_to :parrot, anonymous_class: parrot + pt.belongs_to :treasure, anonymous_class: treasure - parrot.has_many :parrot_treasures, :anonymous_class => pt - parrot.has_many :treasures, :through => :parrot_treasures + parrot.has_many :parrot_treasures, anonymous_class: pt + parrot.has_many :treasures, through: :parrot_treasures - parrots = File.join FIXTURES_ROOT, 'parrots' + parrots = File.join FIXTURES_ROOT, "parrots" fs = ActiveRecord::FixtureSet.new parrot.connection, "parrots", parrot, parrots rows = fs.table_rows - assert_equal load_has_and_belongs_to_many['parrots_treasures'], rows['parrots_treasures'] + assert_equal load_has_and_belongs_to_many["parrots_treasures"], rows["parrots_treasures"] end def test_has_many_through_with_renamed_table @@ -309,24 +309,24 @@ class HasManyThroughFixture < ActiveSupport::TestCase parrot = make_model "Parrot" treasure = make_model "Treasure" - pt.belongs_to :parrot, :anonymous_class => parrot - pt.belongs_to :treasure, :anonymous_class => treasure + pt.belongs_to :parrot, anonymous_class: parrot + pt.belongs_to :treasure, anonymous_class: treasure - parrot.has_many :parrot_treasures, :anonymous_class => pt - parrot.has_many :treasures, :through => :parrot_treasures + parrot.has_many :parrot_treasures, anonymous_class: pt + parrot.has_many :treasures, through: :parrot_treasures - parrots = File.join FIXTURES_ROOT, 'parrots' + parrots = File.join FIXTURES_ROOT, "parrots" fs = ActiveRecord::FixtureSet.new parrot.connection, "parrots", parrot, parrots rows = fs.table_rows - assert_equal load_has_and_belongs_to_many['parrots_treasures'], rows['parrot_treasures'] + assert_equal load_has_and_belongs_to_many["parrots_treasures"], rows["parrot_treasures"] end def load_has_and_belongs_to_many parrot = make_model "Parrot" parrot.has_and_belongs_to_many :treasures - parrots = File.join FIXTURES_ROOT, 'parrots' + parrots = File.join FIXTURES_ROOT, "parrots" fs = ActiveRecord::FixtureSet.new parrot.connection, "parrots", parrot, parrots fs.table_rows @@ -339,7 +339,7 @@ if Account.connection.respond_to?(:reset_pk_sequence!) fixtures :companies def setup - @instances = [Account.new(:credit_limit => 50), Company.new(:name => 'RoR Consulting'), Course.new(name: 'Test')] + @instances = [Account.new(credit_limit: 50), Company.new(name: "RoR Consulting"), Course.new(name: "Test")] ActiveRecord::FixtureSet.reset_cache # make sure tables get reinitialized end @@ -368,7 +368,7 @@ if Account.connection.respond_to?(:reset_pk_sequence!) def test_create_fixtures_resets_sequences_when_not_cached @instances.each do |instance| max_id = create_fixtures(instance.class.table_name).first.fixtures.inject(0) do |_max_id, (_, fixture)| - fixture_id = fixture['id'].to_i + fixture_id = fixture["id"].to_i fixture_id > _max_id ? fixture_id : _max_id end @@ -414,7 +414,7 @@ class FixturesWithoutInstantiationTest < ActiveRecord::TestCase def test_reloading_fixtures_through_accessor_methods topic = Struct.new(:title) assert_equal "The First Topic", topics(:first).title - assert_called(@loaded_fixtures['topics']['first'], :find, returns: topic.new("Fresh Topic!")) do + assert_called(@loaded_fixtures["topics"]["first"], :find, returns: topic.new("Fresh Topic!")) do assert_equal "Fresh Topic!", topics(:first, true).title end end @@ -479,7 +479,6 @@ class SetupSubclassTest < SetupTest end end - class OverlappingFixturesTest < ActiveRecord::TestCase fixtures :topics, :developers fixtures :developers, :accounts @@ -510,13 +509,13 @@ class OverRideFixtureMethodTest < ActiveRecord::TestCase def topics(name) topic = super - topic.title = 'omg' + topic.title = "omg" topic end def test_fixture_methods_can_be_overridden x = topics :first - assert_equal 'omg', x.title + assert_equal "omg", x.title end end @@ -553,7 +552,7 @@ class SetFixtureClassPrevailsTest < ActiveRecord::TestCase end class CheckSetTableNameFixturesTest < ActiveRecord::TestCase - set_fixture_class :funny_jokes => Joke + set_fixture_class funny_jokes: Joke fixtures :funny_jokes # Set to false to blow away fixtures cache and ensure our fixtures are loaded # and thus takes into account our set_fixture_class @@ -565,7 +564,7 @@ class CheckSetTableNameFixturesTest < ActiveRecord::TestCase end class FixtureNameIsNotTableNameFixturesTest < ActiveRecord::TestCase - set_fixture_class :items => Book + set_fixture_class items: Book fixtures :items # Set to false to blow away fixtures cache and ensure our fixtures are loaded # and thus takes into account our set_fixture_class @@ -577,7 +576,7 @@ class FixtureNameIsNotTableNameFixturesTest < ActiveRecord::TestCase end class FixtureNameIsNotTableNameMultipleFixturesTest < ActiveRecord::TestCase - set_fixture_class :items => Book, :funny_jokes => Joke + set_fixture_class items: Book, funny_jokes: Joke fixtures :items, :funny_jokes # Set to false to blow away fixtures cache and ensure our fixtures are loaded # and thus takes into account our set_fixture_class @@ -593,7 +592,7 @@ class FixtureNameIsNotTableNameMultipleFixturesTest < ActiveRecord::TestCase end class CustomConnectionFixturesTest < ActiveRecord::TestCase - set_fixture_class :courses => Course + set_fixture_class courses: Course fixtures :courses self.use_transactional_tests = false @@ -608,7 +607,7 @@ class CustomConnectionFixturesTest < ActiveRecord::TestCase end class TransactionalFixturesOnCustomConnectionTest < ActiveRecord::TestCase - set_fixture_class :courses => Course + set_fixture_class courses: Course fixtures :courses self.use_transactional_tests = true @@ -622,6 +621,46 @@ class TransactionalFixturesOnCustomConnectionTest < ActiveRecord::TestCase end end +class TransactionalFixturesOnConnectionNotification < ActiveRecord::TestCase + self.use_transactional_tests = true + self.use_instantiated_fixtures = false + + def test_transaction_created_on_connection_notification + connection = stub(transaction_open?: false) + connection.expects(:begin_transaction).with(joinable: false) + fire_connection_notification(connection) + end + + def test_notification_established_transactions_are_rolled_back + # Mocha is not thread-safe so define our own stub to test + connection = Class.new do + attr_accessor :rollback_transaction_called + def transaction_open?; true; end + def begin_transaction(*args); end + def rollback_transaction(*args) + @rollback_transaction_called = true + end + end.new + fire_connection_notification(connection) + teardown_fixtures + assert(connection.rollback_transaction_called, "Expected <mock connection>#rollback_transaction to be called but was not") + end + + private + + def fire_connection_notification(connection) + ActiveRecord::Base.connection_handler.stubs(:retrieve_connection).with("book").returns(connection) + message_bus = ActiveSupport::Notifications.instrumenter + payload = { + spec_name: "book", + config: nil, + connection_id: connection.object_id + } + + message_bus.instrument("!connection.active_record", payload) {} + end +end + class InvalidTableNameFixturesTest < ActiveRecord::TestCase fixtures :funny_jokes # Set to false to blow away fixtures cache and ensure our fixtures are loaded @@ -636,7 +675,7 @@ class InvalidTableNameFixturesTest < ActiveRecord::TestCase end class CheckEscapedYamlFixturesTest < ActiveRecord::TestCase - set_fixture_class :funny_jokes => Joke + set_fixture_class funny_jokes: Joke fixtures :funny_jokes # Set to false to blow away fixtures cache and ensure our fixtures are loaded # and thus takes into account our set_fixture_class @@ -679,7 +718,7 @@ class FixturesBrokenRollbackTest < ActiveRecord::TestCase private def load_fixtures(config) - raise 'argh' + raise "argh" end end @@ -689,7 +728,7 @@ class LoadAllFixturesTest < ActiveRecord::TestCase self.class.fixtures :all if File.symlink? FIXTURES_ROOT + "/all/admin" - assert_equal %w(admin/accounts admin/users developers people tasks), fixture_table_names.sort + assert_equal %w(admin/accounts admin/users developers namespaced/accounts people tasks), fixture_table_names.sort end ensure ActiveRecord::FixtureSet.reset_cache @@ -698,11 +737,11 @@ end class LoadAllFixturesWithPathnameTest < ActiveRecord::TestCase def test_all_there - self.class.fixture_path = Pathname.new(FIXTURES_ROOT).join('all') + self.class.fixture_path = Pathname.new(FIXTURES_ROOT).join("all") self.class.fixtures :all if File.symlink? FIXTURES_ROOT + "/all/admin" - assert_equal %w(admin/accounts admin/users developers people tasks), fixture_table_names.sort + assert_equal %w(admin/accounts admin/users developers namespaced/accounts people tasks), fixture_table_names.sort end ensure ActiveRecord::FixtureSet.reset_cache @@ -720,18 +759,18 @@ class FasterFixturesTest < ActiveRecord::TestCase end def test_cache - assert ActiveRecord::FixtureSet.fixture_is_cached?(ActiveRecord::Base.connection, 'categories') - assert ActiveRecord::FixtureSet.fixture_is_cached?(ActiveRecord::Base.connection, 'authors') + assert ActiveRecord::FixtureSet.fixture_is_cached?(ActiveRecord::Base.connection, "categories") + assert ActiveRecord::FixtureSet.fixture_is_cached?(ActiveRecord::Base.connection, "authors") assert_no_queries do - create_fixtures('categories') - create_fixtures('authors') + create_fixtures("categories") + create_fixtures("authors") end - load_extra_fixture('posts') - assert ActiveRecord::FixtureSet.fixture_is_cached?(ActiveRecord::Base.connection, 'posts') + load_extra_fixture("posts") + assert ActiveRecord::FixtureSet.fixture_is_cached?(ActiveRecord::Base.connection, "posts") self.class.setup_fixture_accessors :posts - assert_equal 'Welcome to the weblog', posts(:welcome).title + assert_equal "Welcome to the weblog", posts(:welcome).title end end @@ -739,9 +778,9 @@ class FoxyFixturesTest < ActiveRecord::TestCase fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers, :developers, :"admin/accounts", :"admin/users", :live_parrots, :dead_parrots, :books - if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' - require 'models/uuid_parent' - require 'models/uuid_child' + if ActiveRecord::Base.connection.adapter_name == "PostgreSQL" + require "models/uuid_parent" + require "models/uuid_child" fixtures :uuid_parents, :uuid_children end @@ -758,8 +797,8 @@ class FoxyFixturesTest < ActiveRecord::TestCase assert_equal 207281424, ActiveRecord::FixtureSet.identify(:ruby) assert_equal 1066363776, ActiveRecord::FixtureSet.identify(:sapphire_2) - assert_equal 'f92b6bda-0d0d-5fe1-9124-502b18badded', ActiveRecord::FixtureSet.identify(:daddy, :uuid) - assert_equal 'b4b10018-ad47-595d-b42f-d8bdaa6d01bf', ActiveRecord::FixtureSet.identify(:sonny, :uuid) + assert_equal "f92b6bda-0d0d-5fe1-9124-502b18badded", ActiveRecord::FixtureSet.identify(:daddy, :uuid) + assert_equal "b4b10018-ad47-595d-b42f-d8bdaa6d01bf", ActiveRecord::FixtureSet.identify(:sonny, :uuid) end TIMESTAMP_COLUMNS = %w(created_at created_on updated_at updated_on) @@ -857,7 +896,7 @@ class FoxyFixturesTest < ActiveRecord::TestCase assert_equal("X marks the spot!", pirates(:mark).catchphrase) end - def test_supports_label_interpolation_for_fixnum_label + def test_supports_label_interpolation_for_integer_label assert_equal("#1 pirate!", pirates(1).catchphrase) end @@ -884,7 +923,7 @@ class FoxyFixturesTest < ActiveRecord::TestCase end def test_namespaced_models - assert admin_accounts(:signals37).users.include?(admin_users(:david)) + assert_includes admin_accounts(:signals37).users, admin_users(:david) assert_equal 2, admin_accounts(:signals37).users.size end @@ -913,10 +952,10 @@ class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase ClassNameThatDoesNotFollowCONVENTIONS, :'admin/randomly_named_a9' => Admin::ClassNameThatDoesNotFollowCONVENTIONS1, - 'admin/randomly_named_b0' => + "admin/randomly_named_b0" => Admin::ClassNameThatDoesNotFollowCONVENTIONS2 - fixtures :randomly_named_a9, 'admin/randomly_named_a9', + fixtures :randomly_named_a9, "admin/randomly_named_a9", :'admin/randomly_named_b0' def test_named_accessor_for_randomly_named_fixture_and_class @@ -932,8 +971,8 @@ class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase end def test_table_name_is_defined_in_the_model - assert_equal 'randomly_named_table2', ActiveRecord::FixtureSet::all_loaded_fixtures["admin/randomly_named_a9"].table_name - assert_equal 'randomly_named_table2', Admin::ClassNameThatDoesNotFollowCONVENTIONS1.table_name + assert_equal "randomly_named_table2", ActiveRecord::FixtureSet::all_loaded_fixtures["admin/randomly_named_a9"].table_name + assert_equal "randomly_named_table2", Admin::ClassNameThatDoesNotFollowCONVENTIONS1.table_name end end @@ -965,10 +1004,10 @@ class FixtureClassNamesTest < ActiveRecord::TestCase end def teardown - self.fixture_class_names.replace(@saved_cache) + fixture_class_names.replace(@saved_cache) end test "fixture_class_names returns nil for unregistered identifier" do - assert_nil self.fixture_class_names['unregistered_identifier'] + assert_nil fixture_class_names["unregistered_identifier"] end end diff --git a/activerecord/test/cases/forbidden_attributes_protection_test.rb b/activerecord/test/cases/forbidden_attributes_protection_test.rb index 91921469b8..75c3493527 100644 --- a/activerecord/test/cases/forbidden_attributes_protection_test.rb +++ b/activerecord/test/cases/forbidden_attributes_protection_test.rb @@ -1,11 +1,11 @@ -require 'cases/helper' -require 'active_support/core_ext/hash/indifferent_access' +require "cases/helper" +require "active_support/core_ext/hash/indifferent_access" -require 'models/company' -require 'models/person' -require 'models/ship' -require 'models/ship_part' -require 'models/treasure' +require "models/company" +require "models/person" +require "models/ship" +require "models/ship_part" +require "models/treasure" class ProtectedParams attr_accessor :permitted @@ -44,40 +44,40 @@ end class ForbiddenAttributesProtectionTest < ActiveRecord::TestCase def test_forbidden_attributes_cannot_be_used_for_mass_assignment - params = ProtectedParams.new(first_name: 'Guille', gender: 'm') + params = ProtectedParams.new(first_name: "Guille", gender: "m") assert_raises(ActiveModel::ForbiddenAttributesError) do Person.new(params) end end def test_permitted_attributes_can_be_used_for_mass_assignment - params = ProtectedParams.new(first_name: 'Guille', gender: 'm') + params = ProtectedParams.new(first_name: "Guille", gender: "m") params.permit! person = Person.new(params) - assert_equal 'Guille', person.first_name - assert_equal 'm', person.gender + assert_equal "Guille", person.first_name + assert_equal "m", person.gender end def test_forbidden_attributes_cannot_be_used_for_sti_inheritance_column - params = ProtectedParams.new(type: 'Client') + params = ProtectedParams.new(type: "Client") assert_raises(ActiveModel::ForbiddenAttributesError) do Company.new(params) end end def test_permitted_attributes_can_be_used_for_sti_inheritance_column - params = ProtectedParams.new(type: 'Client') + params = ProtectedParams.new(type: "Client") params.permit! person = Company.new(params) assert_equal person.class, Client end def test_regular_hash_should_still_be_used_for_mass_assignment - person = Person.new(first_name: 'Guille', gender: 'm') + person = Person.new(first_name: "Guille", gender: "m") - assert_equal 'Guille', person.first_name - assert_equal 'm', person.gender + assert_equal "Guille", person.first_name + assert_equal "m", person.gender end def test_blank_attributes_should_not_raise @@ -86,7 +86,7 @@ class ForbiddenAttributesProtectionTest < ActiveRecord::TestCase end def test_create_with_checks_permitted - params = ProtectedParams.new(first_name: 'Guille', gender: 'm') + params = ProtectedParams.new(first_name: "Guille", gender: "m") assert_raises(ActiveModel::ForbiddenAttributesError) do Person.create_with(params).create! @@ -94,21 +94,21 @@ class ForbiddenAttributesProtectionTest < ActiveRecord::TestCase end def test_create_with_works_with_permitted_params - params = ProtectedParams.new(first_name: 'Guille').permit! + params = ProtectedParams.new(first_name: "Guille").permit! person = Person.create_with(params).create! - assert_equal 'Guille', person.first_name + assert_equal "Guille", person.first_name end def test_create_with_works_with_params_values - params = ProtectedParams.new(first_name: 'Guille') + params = ProtectedParams.new(first_name: "Guille") person = Person.create_with(first_name: params[:first_name]).create! - assert_equal 'Guille', person.first_name + assert_equal "Guille", person.first_name end def test_where_checks_permitted - params = ProtectedParams.new(first_name: 'Guille', gender: 'm') + params = ProtectedParams.new(first_name: "Guille", gender: "m") assert_raises(ActiveModel::ForbiddenAttributesError) do Person.where(params).create! @@ -116,21 +116,21 @@ class ForbiddenAttributesProtectionTest < ActiveRecord::TestCase end def test_where_works_with_permitted_params - params = ProtectedParams.new(first_name: 'Guille').permit! + params = ProtectedParams.new(first_name: "Guille").permit! person = Person.where(params).create! - assert_equal 'Guille', person.first_name + assert_equal "Guille", person.first_name end def test_where_works_with_params_values - params = ProtectedParams.new(first_name: 'Guille') + params = ProtectedParams.new(first_name: "Guille") person = Person.where(first_name: params[:first_name]).create! - assert_equal 'Guille', person.first_name + assert_equal "Guille", person.first_name end def test_where_not_checks_permitted - params = ProtectedParams.new(first_name: 'Guille', gender: 'm') + params = ProtectedParams.new(first_name: "Guille", gender: "m") assert_raises(ActiveModel::ForbiddenAttributesError) do Person.where().not(params) @@ -138,9 +138,9 @@ class ForbiddenAttributesProtectionTest < ActiveRecord::TestCase end def test_where_not_works_with_permitted_params - params = ProtectedParams.new(first_name: 'Guille').permit! + params = ProtectedParams.new(first_name: "Guille").permit! Person.create!(params) - assert_empty Person.where.not(params).select {|p| p.first_name == 'Guille' } + assert_empty Person.where.not(params).select { |p| p.first_name == "Guille" } end def test_strong_params_style_objects_work_with_singular_associations @@ -161,5 +161,4 @@ class ForbiddenAttributesProtectionTest < ActiveRecord::TestCase assert_equal "Necklace", part.trinkets[0].name assert_equal "Spoon", part.trinkets[1].name end - end diff --git a/activerecord/test/cases/habtm_destroy_order_test.rb b/activerecord/test/cases/habtm_destroy_order_test.rb index 2ce0de360e..365d4576dd 100644 --- a/activerecord/test/cases/habtm_destroy_order_test.rb +++ b/activerecord/test/cases/habtm_destroy_order_test.rb @@ -4,21 +4,21 @@ require "models/student" class HabtmDestroyOrderTest < ActiveRecord::TestCase test "may not delete a lesson with students" do - sicp = Lesson.new(:name => "SICP") - ben = Student.new(:name => "Ben Bitdiddle") + sicp = Lesson.new(name: "SICP") + ben = Student.new(name: "Ben Bitdiddle") sicp.students << ben sicp.save! assert_raises LessonError do - assert_no_difference('Lesson.count') do + assert_no_difference("Lesson.count") do sicp.destroy end end assert !sicp.destroyed? end - test 'should not raise error if have foreign key in the join table' do - student = Student.new(:name => "Ben Bitdiddle") - lesson = Lesson.new(:name => "SICP") + test "should not raise error if have foreign key in the join table" do + student = Student.new(name: "Ben Bitdiddle") + lesson = Lesson.new(name: "SICP") lesson.students << student lesson.save! assert_nothing_raised do @@ -29,8 +29,8 @@ class HabtmDestroyOrderTest < ActiveRecord::TestCase test "not destroying a student with lessons leaves student<=>lesson association intact" do # test a normal before_destroy doesn't destroy the habtm joins begin - sicp = Lesson.new(:name => "SICP") - ben = Student.new(:name => "Ben Bitdiddle") + sicp = Lesson.new(name: "SICP") + ben = Student.new(name: "Ben Bitdiddle") # add a before destroy to student Student.class_eval do before_destroy do @@ -49,8 +49,8 @@ class HabtmDestroyOrderTest < ActiveRecord::TestCase test "not destroying a lesson with students leaves student<=>lesson association intact" do # test a more aggressive before_destroy doesn't destroy the habtm joins and still throws the exception - sicp = Lesson.new(:name => "SICP") - ben = Student.new(:name => "Ben Bitdiddle") + sicp = Lesson.new(name: "SICP") + ben = Student.new(name: "Ben Bitdiddle") sicp.students << ben sicp.save! assert_raises LessonError do diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 95f8706d73..f1d69a215a 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -1,19 +1,15 @@ -require File.expand_path('../../../../load_paths', __FILE__) +require "config" -require 'config' +require "stringio" -require 'active_support/testing/autorun' -require 'active_support/testing/method_call_assertions' -require 'stringio' +require "active_record" +require "cases/test_case" +require "active_support/dependencies" +require "active_support/logger" +require "active_support/core_ext/string/strip" -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' +require "support/config" +require "support/connection" # TODO: Move all these random hacks into the ARTest namespace and into the support/ dir @@ -29,7 +25,7 @@ I18n.enforce_available_locales = false ARTest.connect # Quote "type" if it's a reserved word for the current connection. -QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type') +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 @@ -51,18 +47,18 @@ def subsecond_precision_supported? end def mysql_enforcing_gtid_consistency? - current_adapter?(:Mysql2Adapter) && 'ON' == ActiveRecord::Base.connection.show_variable('enforce_gtid_consistency') + current_adapter?(:Mysql2Adapter) && "ON" == ActiveRecord::Base.connection.show_variable("enforce_gtid_consistency") end def supports_savepoints? ActiveRecord::Base.connection.supports_savepoints? end -def with_env_tz(new_tz = 'US/Eastern') - old_tz, ENV['TZ'] = ENV['TZ'], new_tz +def with_env_tz(new_tz = "US/Eastern") + old_tz, ENV["TZ"] = ENV["TZ"], new_tz yield ensure - old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') + old_tz ? ENV["TZ"] = old_tz : ENV.delete("TZ") end def with_timezone_config(cfg) @@ -136,21 +132,6 @@ def disable_extension!(extension, connection) connection.reconnect! end -require "cases/validations_repair_helper" -class ActiveSupport::TestCase - include ActiveRecord::TestFixtures - include ActiveRecord::ValidationsRepairHelper - include ActiveSupport::Testing::MethodCallAssertions - - self.fixture_path = FIXTURES_ROOT - self.use_instantiated_fixtures = false - self.use_transactional_tests = true - - def create_fixtures(*fixture_set_names, &block) - ActiveRecord::FixtureSet.create_fixtures(ActiveSupport::TestCase.fixture_path, fixture_set_names, fixture_class_names, &block) - end -end - def load_schema # silence verbose schema loading original_stdout = $stdout @@ -190,17 +171,17 @@ end module InTimeZone private - def in_time_zone(zone) - old_zone = Time.zone - old_tz = ActiveRecord::Base.time_zone_aware_attributes - - Time.zone = zone ? ActiveSupport::TimeZone[zone] : nil - ActiveRecord::Base.time_zone_aware_attributes = !zone.nil? - yield - ensure - Time.zone = old_zone - ActiveRecord::Base.time_zone_aware_attributes = old_tz - end + def in_time_zone(zone) + old_zone = Time.zone + old_tz = ActiveRecord::Base.time_zone_aware_attributes + + Time.zone = zone ? ActiveSupport::TimeZone[zone] : nil + ActiveRecord::Base.time_zone_aware_attributes = !zone.nil? + yield + ensure + Time.zone = old_zone + ActiveRecord::Base.time_zone_aware_attributes = old_tz + end end -require 'mocha/setup' # FIXME: stop using mocha +require "mocha/setup" # FIXME: stop using mocha diff --git a/activerecord/test/cases/hot_compatibility_test.rb b/activerecord/test/cases/hot_compatibility_test.rb index 5ba9a1029a..e107ff2362 100644 --- a/activerecord/test/cases/hot_compatibility_test.rb +++ b/activerecord/test/cases/hot_compatibility_test.rb @@ -1,7 +1,9 @@ -require 'cases/helper' +require "cases/helper" +require "support/connection_helper" class HotCompatibilityTest < ActiveRecord::TestCase self.use_transactional_tests = false + include ConnectionHelper setup do @klass = Class.new(ActiveRecord::Base) do @@ -10,7 +12,7 @@ class HotCompatibilityTest < ActiveRecord::TestCase t.string :bar end - def self.name; 'HotCompatibility'; end + def self.name; "HotCompatibility"; end end end @@ -33,22 +35,108 @@ class HotCompatibilityTest < ActiveRecord::TestCase # but we can successfully create a record so long as we don't # reference the removed column - record = @klass.create! foo: 'foo' + record = @klass.create! foo: "foo" record.reload - assert_equal 'foo', record.foo + assert_equal "foo", record.foo end test "update after remove_column" do - record = @klass.create! foo: 'foo' + record = @klass.create! foo: "foo" assert_equal 3, @klass.columns.length @klass.connection.remove_column :hot_compatibilities, :bar assert_equal 3, @klass.columns.length record.reload - assert_equal 'foo', record.foo - record.foo = 'bar' + assert_equal "foo", record.foo + record.foo = "bar" record.save! record.reload - assert_equal 'bar', record.foo + assert_equal "bar", record.foo end + + if current_adapter?(:PostgreSQLAdapter) + test "cleans up after prepared statement failure in a transaction" do + with_two_connections do |original_connection, ddl_connection| + record = @klass.create! bar: "bar" + + # prepare the reload statement in a transaction + @klass.transaction do + record.reload + end + + assert get_prepared_statement_cache(@klass.connection).any?, + "expected prepared statement cache to have something in it" + + # add a new column + ddl_connection.add_column :hot_compatibilities, :baz, :string + + assert_raise(ActiveRecord::PreparedStatementCacheExpired) do + @klass.transaction do + record.reload + end + end + + assert_empty get_prepared_statement_cache(@klass.connection), + "expected prepared statement cache to be empty but it wasn't" + end + end + + test "cleans up after prepared statement failure in nested transactions" do + with_two_connections do |original_connection, ddl_connection| + record = @klass.create! bar: "bar" + + # prepare the reload statement in a transaction + @klass.transaction do + record.reload + end + + assert get_prepared_statement_cache(@klass.connection).any?, + "expected prepared statement cache to have something in it" + + # add a new column + ddl_connection.add_column :hot_compatibilities, :baz, :string + + assert_raise(ActiveRecord::PreparedStatementCacheExpired) do + @klass.transaction do + @klass.transaction do + @klass.transaction do + record.reload + end + end + end + end + + assert_empty get_prepared_statement_cache(@klass.connection), + "expected prepared statement cache to be empty but it wasn't" + end + end + end + + private + + def get_prepared_statement_cache(connection) + connection.instance_variable_get(:@statements) + .instance_variable_get(:@cache)[Process.pid] + end + + # Rails will automatically clear the prepared statements on the connection + # that runs the migration, so we use two connections to simulate what would + # actually happen on a production system; we'd have one connection running the + # migration from the rake task ("ddl_connection" here), and we'd have another + # connection in a web worker. + def with_two_connections + run_without_connection do |original_connection| + ActiveRecord::Base.establish_connection(original_connection.merge(pool_size: 2)) + begin + ddl_connection = ActiveRecord::Base.connection_pool.checkout + begin + yield original_connection, ddl_connection + ensure + ActiveRecord::Base.connection_pool.checkin ddl_connection + end + ensure + ActiveRecord::Base.clear_all_connections! + end + end + end end diff --git a/activerecord/test/cases/i18n_test.rb b/activerecord/test/cases/i18n_test.rb index a428f1d87b..7f03c5b23d 100644 --- a/activerecord/test/cases/i18n_test.rb +++ b/activerecord/test/cases/i18n_test.rb @@ -1,45 +1,44 @@ require "cases/helper" -require 'models/topic' -require 'models/reply' +require "models/topic" +require "models/reply" class ActiveRecordI18nTests < ActiveRecord::TestCase - def setup I18n.backend = I18n::Backend::Simple.new end def test_translated_model_attributes - I18n.backend.store_translations 'en', :activerecord => {:attributes => {:topic => {:title => 'topic title attribute'} } } - assert_equal 'topic title attribute', Topic.human_attribute_name('title') + I18n.backend.store_translations "en", activerecord: { attributes: { topic: { title: "topic title attribute" } } } + assert_equal "topic title attribute", Topic.human_attribute_name("title") end def test_translated_model_attributes_with_symbols - I18n.backend.store_translations 'en', :activerecord => {:attributes => {:topic => {:title => 'topic title attribute'} } } - assert_equal 'topic title attribute', Topic.human_attribute_name(:title) + I18n.backend.store_translations "en", activerecord: { attributes: { topic: { title: "topic title attribute" } } } + assert_equal "topic title attribute", Topic.human_attribute_name(:title) end def test_translated_model_attributes_with_sti - I18n.backend.store_translations 'en', :activerecord => {:attributes => {:reply => {:title => 'reply title attribute'} } } - assert_equal 'reply title attribute', Reply.human_attribute_name('title') + I18n.backend.store_translations "en", activerecord: { attributes: { reply: { title: "reply title attribute" } } } + assert_equal "reply title attribute", Reply.human_attribute_name("title") end def test_translated_model_attributes_with_sti_fallback - I18n.backend.store_translations 'en', :activerecord => {:attributes => {:topic => {:title => 'topic title attribute'} } } - assert_equal 'topic title attribute', Reply.human_attribute_name('title') + I18n.backend.store_translations "en", activerecord: { attributes: { topic: { title: "topic title attribute" } } } + assert_equal "topic title attribute", Reply.human_attribute_name("title") end def test_translated_model_names - I18n.backend.store_translations 'en', :activerecord => {:models => {:topic => 'topic model'} } - assert_equal 'topic model', Topic.model_name.human + I18n.backend.store_translations "en", activerecord: { models: { topic: "topic model" } } + assert_equal "topic model", Topic.model_name.human end def test_translated_model_names_with_sti - I18n.backend.store_translations 'en', :activerecord => {:models => {:reply => 'reply model'} } - assert_equal 'reply model', Reply.model_name.human + I18n.backend.store_translations "en", activerecord: { models: { reply: "reply model" } } + assert_equal "reply model", Reply.model_name.human end def test_translated_model_names_with_sti_fallback - I18n.backend.store_translations 'en', :activerecord => {:models => {:topic => 'topic model'} } - assert_equal 'topic model', Reply.model_name.human + I18n.backend.store_translations "en", activerecord: { models: { topic: "topic model" } } + assert_equal "topic model", Reply.model_name.human end end diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index 7da6842047..9ad4664567 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -1,13 +1,13 @@ -require 'cases/helper' -require 'models/author' -require 'models/company' -require 'models/person' -require 'models/post' -require 'models/project' -require 'models/subscriber' -require 'models/vegetables' -require 'models/shop' -require 'models/sponsor' +require "cases/helper" +require "models/author" +require "models/company" +require "models/person" +require "models/post" +require "models/project" +require "models/subscriber" +require "models/vegetables" +require "models/shop" +require "models/sponsor" module InheritanceTestHelper def with_store_full_sti_class(&block) @@ -33,7 +33,7 @@ class InheritanceTest < ActiveRecord::TestCase def test_class_with_store_full_sti_class_returns_full_name with_store_full_sti_class do - assert_equal 'Namespaced::Company', Namespaced::Company.sti_name + assert_equal "Namespaced::Company", Namespaced::Company.sti_name end end @@ -42,37 +42,37 @@ class InheritanceTest < ActiveRecord::TestCase company = company.dup company.extend(Module.new { def _read_attribute(name) - return ' ' if name == 'type' + return " " if name == "type" super end }) company.save! company = Company.all.to_a.find { |x| x.id == company.id } - assert_equal ' ', company.type + assert_equal " ", company.type end def test_class_without_store_full_sti_class_returns_demodulized_name without_store_full_sti_class do - assert_equal 'Company', Namespaced::Company.sti_name + assert_equal "Company", Namespaced::Company.sti_name end end def test_compute_type_success - assert_equal Author, ActiveRecord::Base.send(:compute_type, 'Author') + assert_equal Author, ActiveRecord::Base.send(:compute_type, "Author") end def test_compute_type_nonexistent_constant e = assert_raises NameError do - ActiveRecord::Base.send :compute_type, 'NonexistentModel' + ActiveRecord::Base.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 ActiveRecord::Base::NonexistentModel", e.message + assert_equal "ActiveRecord::Base::NonexistentModel", e.name end def test_compute_type_no_method_error - ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise NoMethodError }) do + ActiveSupport::Dependencies.stub(:safe_constantize, proc { raise NoMethodError }) do assert_raises NoMethodError do - ActiveRecord::Base.send :compute_type, 'InvalidModel' + ActiveRecord::Base.send :compute_type, "InvalidModel" end end end @@ -87,19 +87,19 @@ class InheritanceTest < ActiveRecord::TestCase error = e end - ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise e }) do + ActiveSupport::Dependencies.stub(:safe_constantize, proc { raise e }) do exception = assert_raises NameError do - ActiveRecord::Base.send :compute_type, 'InvalidModel' + ActiveRecord::Base.send :compute_type, "InvalidModel" end assert_equal error.message, exception.message end end def test_compute_type_argument_error - ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise ArgumentError }) do + ActiveSupport::Dependencies.stub(:safe_constantize, proc { raise ArgumentError }) do assert_raises ArgumentError do - ActiveRecord::Base.send :compute_type, 'InvalidModel' + ActiveRecord::Base.send :compute_type, "InvalidModel" end end end @@ -107,14 +107,14 @@ class InheritanceTest < ActiveRecord::TestCase def test_should_store_demodulized_class_name_with_store_full_sti_class_option_disabled without_store_full_sti_class do item = Namespaced::Company.new - assert_equal 'Company', item[:type] + assert_equal "Company", item[:type] end end def test_should_store_full_class_name_with_store_full_sti_class_option_enabled with_store_full_sti_class do item = Namespaced::Company.new - assert_equal 'Namespaced::Company', item[:type] + assert_equal "Namespaced::Company", item[:type] end end @@ -154,9 +154,9 @@ class InheritanceTest < ActiveRecord::TestCase def test_company_descends_from_active_record assert !ActiveRecord::Base.descends_from_active_record? - assert AbstractCompany.descends_from_active_record?, 'AbstractCompany should descend from ActiveRecord::Base' - assert Company.descends_from_active_record?, 'Company should descend from ActiveRecord::Base' - assert !Class.new(Company).descends_from_active_record?, 'Company subclass should not descend from ActiveRecord::Base' + assert AbstractCompany.descends_from_active_record?, "AbstractCompany should descend from ActiveRecord::Base" + assert Company.descends_from_active_record?, "Company should descend from ActiveRecord::Base" + assert !Class.new(Company).descends_from_active_record?, "Company subclass should not descend from ActiveRecord::Base" end def test_abstract_class @@ -214,7 +214,7 @@ class InheritanceTest < ActiveRecord::TestCase def test_becomes_and_change_tracking_for_inheritance_columns cucumber = Vegetable.find(1) cabbage = cucumber.becomes!(Cabbage) - assert_equal ['Cucumber', 'Cabbage'], cabbage.custom_type_change + assert_equal ["Cucumber", "Cabbage"], cabbage.custom_type_change end def test_alt_becomes_bang_resets_inheritance_type_column @@ -229,13 +229,13 @@ class InheritanceTest < ActiveRecord::TestCase end def test_inheritance_find_all - companies = Company.all.merge!(:order => 'id').to_a + companies = Company.all.merge!(order: "id").to_a assert_kind_of Firm, companies[0], "37signals should be a firm" assert_kind_of Client, companies[1], "Summit should be a client" end def test_alt_inheritance_find_all - companies = Vegetable.all.merge!(:order => 'id').to_a + companies = Vegetable.all.merge!(order: "id").to_a assert_kind_of Cucumber, companies[0] assert_kind_of Cabbage, companies[1] end @@ -250,7 +250,7 @@ class InheritanceTest < ActiveRecord::TestCase end def test_alt_inheritance_save - cabbage = Cabbage.new(:name => 'Savoy') + cabbage = Cabbage.new(name: "Savoy") cabbage.save! savoy = Vegetable.find(cabbage.id) @@ -263,12 +263,12 @@ class InheritanceTest < ActiveRecord::TestCase end def test_inheritance_new_with_base_class - company = Company.new(:type => 'Company') + company = Company.new(type: "Company") assert_equal Company, company.class end def test_inheritance_new_with_subclass - firm = Company.new(:type => 'Firm') + firm = Company.new(type: "Firm") assert_equal Firm, firm.class end @@ -287,17 +287,17 @@ class InheritanceTest < ActiveRecord::TestCase end def test_new_with_invalid_type - assert_raise(ActiveRecord::SubclassNotFound) { Company.new(:type => 'InvalidType') } + assert_raise(ActiveRecord::SubclassNotFound) { Company.new(type: "InvalidType") } end def test_new_with_unrelated_type - assert_raise(ActiveRecord::SubclassNotFound) { Company.new(:type => 'Account') } + assert_raise(ActiveRecord::SubclassNotFound) { Company.new(type: "Account") } end def test_new_with_unrelated_namespaced_type without_store_full_sti_class do e = assert_raises ActiveRecord::SubclassNotFound do - Namespaced::Company.new(type: 'Firm') + Namespaced::Company.new(type: "Firm") end assert_equal "Invalid single-table inheritance type: Namespaced::Firm is not a subclass of Namespaced::Company", e.message @@ -305,21 +305,21 @@ class InheritanceTest < ActiveRecord::TestCase end def test_new_with_complex_inheritance - assert_nothing_raised { Client.new(type: 'VerySpecialClient') } + assert_nothing_raised { Client.new(type: "VerySpecialClient") } end def test_new_without_storing_full_sti_class without_store_full_sti_class do - item = Company.new(type: 'SpecialCo') + item = Company.new(type: "SpecialCo") assert_instance_of Company::SpecialCo, item end end def test_new_with_autoload_paths - path = File.expand_path('../../models/autoloadable', __FILE__) + path = File.expand_path("../../models/autoloadable", __FILE__) ActiveSupport::Dependencies.autoload_paths << path - firm = Company.new(:type => 'ExtraFirm') + firm = Company.new(type: "ExtraFirm") assert_equal ExtraFirm, firm.class ensure ActiveSupport::Dependencies.autoload_paths.reject! { |p| p == path } @@ -352,7 +352,7 @@ class InheritanceTest < ActiveRecord::TestCase Client.update_all "name = 'I am a client'" assert_equal "I am a client", Client.first.name # Order by added as otherwise Oracle tests were failing because of different order of results - assert_equal "37signals", Firm.all.merge!(:order => "id").to_a.first.name + assert_equal "37signals", Firm.all.merge!(order: "id").to_a.first.name end def test_alt_update_all_within_inheritance @@ -374,51 +374,51 @@ class InheritanceTest < ActiveRecord::TestCase end def test_find_first_within_inheritance - assert_kind_of Firm, Company.all.merge!(:where => "name = '37signals'").first - assert_kind_of Firm, Firm.all.merge!(:where => "name = '37signals'").first - assert_nil Client.all.merge!(:where => "name = '37signals'").first + assert_kind_of Firm, Company.all.merge!(where: "name = '37signals'").first + assert_kind_of Firm, Firm.all.merge!(where: "name = '37signals'").first + assert_nil Client.all.merge!(where: "name = '37signals'").first end def test_alt_find_first_within_inheritance - assert_kind_of Cabbage, Vegetable.all.merge!(:where => "name = 'his cabbage'").first - assert_kind_of Cabbage, Cabbage.all.merge!(:where => "name = 'his cabbage'").first - assert_nil Cucumber.all.merge!(:where => "name = 'his cabbage'").first + assert_kind_of Cabbage, Vegetable.all.merge!(where: "name = 'his cabbage'").first + assert_kind_of Cabbage, Cabbage.all.merge!(where: "name = 'his cabbage'").first + assert_nil Cucumber.all.merge!(where: "name = 'his cabbage'").first end def test_complex_inheritance very_special_client = VerySpecialClient.create("name" => "veryspecial") assert_equal very_special_client, VerySpecialClient.where("name = 'veryspecial'").first - assert_equal very_special_client, SpecialClient.all.merge!(:where => "name = 'veryspecial'").first - assert_equal very_special_client, Company.all.merge!(:where => "name = 'veryspecial'").first - assert_equal very_special_client, Client.all.merge!(:where => "name = 'veryspecial'").first - assert_equal 1, Client.all.merge!(:where => "name = 'Summit'").to_a.size + assert_equal very_special_client, SpecialClient.all.merge!(where: "name = 'veryspecial'").first + assert_equal very_special_client, Company.all.merge!(where: "name = 'veryspecial'").first + assert_equal very_special_client, Client.all.merge!(where: "name = 'veryspecial'").first + assert_equal 1, Client.all.merge!(where: "name = 'Summit'").to_a.size assert_equal very_special_client, Client.find(very_special_client.id) end def test_alt_complex_inheritance king_cole = KingCole.create("name" => "uniform heads") assert_equal king_cole, KingCole.where("name = 'uniform heads'").first - assert_equal king_cole, GreenCabbage.all.merge!(:where => "name = 'uniform heads'").first - assert_equal king_cole, Cabbage.all.merge!(:where => "name = 'uniform heads'").first - assert_equal king_cole, Vegetable.all.merge!(:where => "name = 'uniform heads'").first - assert_equal 1, Cabbage.all.merge!(:where => "name = 'his cabbage'").to_a.size + assert_equal king_cole, GreenCabbage.all.merge!(where: "name = 'uniform heads'").first + assert_equal king_cole, Cabbage.all.merge!(where: "name = 'uniform heads'").first + assert_equal king_cole, Vegetable.all.merge!(where: "name = 'uniform heads'").first + assert_equal 1, Cabbage.all.merge!(where: "name = 'his cabbage'").to_a.size assert_equal king_cole, Cabbage.find(king_cole.id) end def test_eager_load_belongs_to_something_inherited - account = Account.all.merge!(:includes => :firm).find(1) + account = Account.all.merge!(includes: :firm).find(1) assert account.association(:firm).loaded?, "association was not eager loaded" end def test_alt_eager_loading - cabbage = RedCabbage.all.merge!(:includes => :seller).find(4) + cabbage = RedCabbage.all.merge!(includes: :seller).find(4) assert cabbage.association(:seller).loaded?, "association was not eager loaded" end def test_eager_load_belongs_to_primary_key_quoting con = Account.connection assert_sql(/#{con.quote_table_name('companies')}.#{con.quote_column_name('id')} = 1/) do - Account.all.merge!(:includes => :firm).find(1) + Account.all.merge!(includes: :firm).find(1) end end @@ -428,7 +428,7 @@ class InheritanceTest < ActiveRecord::TestCase def test_inheritance_without_mapping assert_kind_of SpecialSubscriber, SpecialSubscriber.find("webster132") - assert_nothing_raised { s = SpecialSubscriber.new("name" => "And breaaaaathe!"); s.id = 'roger'; s.save } + assert_nothing_raised { s = SpecialSubscriber.new("name" => "And breaaaaathe!"); s.id = "roger"; s.save } end def test_scope_inherited_properly @@ -441,12 +441,7 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase include InheritanceTestHelper fixtures :companies - def setup - ActiveSupport::Dependencies.log_activity = true - end - teardown do - ActiveSupport::Dependencies.log_activity = false self.class.const_remove :FirmOnTheFly rescue nil Firm.const_remove :FirmOnTheFly rescue nil end @@ -454,7 +449,7 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase def test_instantiation_doesnt_try_to_require_corresponding_file without_store_full_sti_class do foo = Firm.first.clone - foo.type = 'FirmOnTheFly' + foo.type = "FirmOnTheFly" foo.save! # Should fail without FirmOnTheFly in the type condition. @@ -475,30 +470,30 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase end def test_sti_type_from_attributes_disabled_in_non_sti_class - phone = Shop::Product::Type.new(name: 'Phone') - product = Shop::Product.new(:type => phone) + phone = Shop::Product::Type.new(name: "Phone") + product = Shop::Product.new(type: phone) assert product.save end def test_inheritance_new_with_subclass_as_default original_type = Company.columns_hash["type"].default - ActiveRecord::Base.connection.change_column_default :companies, :type, 'Firm' + ActiveRecord::Base.connection.change_column_default :companies, :type, "Firm" Company.reset_column_information firm = Company.new # without arguments - assert_equal 'Firm', firm.type + assert_equal "Firm", firm.type assert_instance_of Firm, firm - firm = Company.new(firm_name: 'Shri Hans Plastic') # with arguments - assert_equal 'Firm', firm.type + firm = Company.new(firm_name: "Shri Hans Plastic") # with arguments + assert_equal "Firm", firm.type assert_instance_of Firm, firm client = Client.new - assert_equal 'Client', client.type + assert_equal "Client", client.type assert_instance_of Client, client - firm = Company.new(type: 'Client') # overwrite the default type - assert_equal 'Client', firm.type + firm = Company.new(type: "Client") # overwrite the default type + assert_equal "Client", firm.type assert_instance_of Client, firm ensure ActiveRecord::Base.connection.change_column_default :companies, :type, original_type @@ -507,9 +502,8 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase end class InheritanceAttributeTest < ActiveRecord::TestCase - class Company < ActiveRecord::Base - self.table_name = 'companies' + self.table_name = "companies" attribute :type, :string, default: "InheritanceAttributeTest::Startup" end @@ -521,11 +515,11 @@ class InheritanceAttributeTest < ActiveRecord::TestCase def test_inheritance_new_with_subclass_as_default startup = Company.new # without arguments - assert_equal 'InheritanceAttributeTest::Startup', startup.type + assert_equal "InheritanceAttributeTest::Startup", startup.type assert_instance_of Startup, startup - empire = Company.new(type: 'InheritanceAttributeTest::Empire') # without arguments - assert_equal 'InheritanceAttributeTest::Empire', empire.type + empire = Company.new(type: "InheritanceAttributeTest::Empire") # without arguments + assert_equal "InheritanceAttributeTest::Empire", empire.type assert_instance_of Empire, empire end end @@ -560,7 +554,7 @@ class InheritanceAttributeMappingTest < ActiveRecord::TestCase end class Company < ActiveRecord::Base - self.table_name = 'companies' + self.table_name = "companies" attribute :type, :omg_sti end @@ -568,18 +562,18 @@ class InheritanceAttributeMappingTest < ActiveRecord::TestCase class Empire < Company; end class Sponsor < ActiveRecord::Base - self.table_name = 'sponsors' + self.table_name = "sponsors" attribute :sponsorable_type, :omg_sti belongs_to :sponsorable, polymorphic: true end def test_sti_with_custom_type - Startup.create! name: 'a Startup' - Empire.create! name: 'an Empire' + Startup.create! name: "a Startup" + Empire.create! name: "an Empire" assert_equal [["a Startup", "omg_inheritance_attribute_mapping_test/startup"], - ["an Empire", "omg_inheritance_attribute_mapping_test/empire"]], ActiveRecord::Base.connection.select_rows('SELECT name, type FROM companies').sort + ["an Empire", "omg_inheritance_attribute_mapping_test/empire"]], ActiveRecord::Base.connection.select_rows("SELECT name, type FROM companies").sort assert_equal [["a Startup", "InheritanceAttributeMappingTest::Startup"], ["an Empire", "InheritanceAttributeMappingTest::Empire"]], Company.all.map { |a| [a.name, a.type] }.sort @@ -588,17 +582,17 @@ class InheritanceAttributeMappingTest < ActiveRecord::TestCase startup.save! assert_equal [["a Startup", "omg_inheritance_attribute_mapping_test/empire"], - ["an Empire", "omg_inheritance_attribute_mapping_test/empire"]], ActiveRecord::Base.connection.select_rows('SELECT name, type FROM companies').sort + ["an Empire", "omg_inheritance_attribute_mapping_test/empire"]], ActiveRecord::Base.connection.select_rows("SELECT name, type FROM companies").sort assert_equal [["a Startup", "InheritanceAttributeMappingTest::Empire"], ["an Empire", "InheritanceAttributeMappingTest::Empire"]], Company.all.map { |a| [a.name, a.type] }.sort end def test_polymorphic_associations_custom_type - startup = Startup.create! name: 'a Startup' + startup = Startup.create! name: "a Startup" sponsor = Sponsor.create! sponsorable: startup - assert_equal ["omg_inheritance_attribute_mapping_test/company"], ActiveRecord::Base.connection.select_values('SELECT sponsorable_type FROM sponsors') + assert_equal ["omg_inheritance_attribute_mapping_test/company"], ActiveRecord::Base.connection.select_values("SELECT sponsorable_type FROM sponsors") sponsor = Sponsor.first assert_equal startup, sponsor.sponsorable diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb index 08a186ae07..00457965d7 100644 --- a/activerecord/test/cases/integration_test.rb +++ b/activerecord/test/cases/integration_test.rb @@ -1,10 +1,10 @@ -require 'cases/helper' -require 'models/company' -require 'models/developer' -require 'models/computer' -require 'models/owner' -require 'models/pet' +require "cases/helper" +require "models/company" +require "models/developer" +require "models/computer" +require "models/owner" +require "models/pet" class IntegrationTest < ActiveRecord::TestCase fixtures :companies, :developers, :owners, :pets @@ -21,53 +21,79 @@ class IntegrationTest < ActiveRecord::TestCase def test_to_param_returns_id_if_not_persisted_but_id_is_set client = Client.new client.id = 1 - assert_equal '1', client.to_param + assert_equal "1", client.to_param end def test_to_param_class_method firm = Firm.find(4) - assert_equal '4-flamboyant-software', firm.to_param + assert_equal "4-flamboyant-software", firm.to_param + end + + def test_to_param_class_method_truncates_words_properly + firm = Firm.find(4) + firm.name << ", Inc." + assert_equal "4-flamboyant-software", firm.to_param + end + + def test_to_param_class_method_truncates_after_parameterize + firm = Firm.find(4) + firm.name = "Huey, Dewey, & Louie LLC" + # 123456789T123456789v + assert_equal "4-huey-dewey-louie-llc", firm.to_param + end + + def test_to_param_class_method_truncates_after_parameterize_with_hyphens + firm = Firm.find(4) + firm.name = "Door-to-Door Wash-n-Fold Service" + # 123456789T123456789v + assert_equal "4-door-to-door-wash-n", firm.to_param end def test_to_param_class_method_truncates firm = Firm.find(4) - firm.name = 'a ' * 100 - assert_equal '4-a-a-a-a-a-a-a-a-a', firm.to_param + firm.name = "a " * 100 + assert_equal "4-a-a-a-a-a-a-a-a-a-a", firm.to_param end def test_to_param_class_method_truncates_edge_case firm = Firm.find(4) - firm.name = 'David HeinemeierHansson' - assert_equal '4-david', firm.to_param + firm.name = "David HeinemeierHansson" + assert_equal "4-david", firm.to_param + end + + def test_to_param_class_method_truncates_case_shown_in_doc + firm = Firm.find(4) + firm.name = "David Heinemeier Hansson" + assert_equal "4-david-heinemeier", firm.to_param end def test_to_param_class_method_squishes firm = Firm.find(4) firm.name = "ab \n" * 100 - assert_equal '4-ab-ab-ab-ab-ab-ab', firm.to_param + assert_equal "4-ab-ab-ab-ab-ab-ab-ab", firm.to_param end def test_to_param_class_method_multibyte_character firm = Firm.find(4) firm.name = "æˆ¦å ´ãƒ¶åŽŸ ã²ãŸãŽ" - assert_equal '4', firm.to_param + assert_equal "4", firm.to_param end def test_to_param_class_method_uses_default_if_blank firm = Firm.find(4) firm.name = nil - assert_equal '4', firm.to_param - firm.name = ' ' - assert_equal '4', firm.to_param + assert_equal "4", firm.to_param + firm.name = " " + assert_equal "4", firm.to_param end def test_to_param_class_method_uses_default_if_not_persisted - firm = Firm.new(name: 'Fancy Shirts') + firm = Firm.new(name: "Fancy Shirts") assert_equal nil, firm.to_param end def test_to_param_with_no_arguments - assert_equal 'Firm', Firm.to_param + assert_equal "Firm", Firm.to_param end def test_cache_key_for_existing_record_is_not_timezone_dependent @@ -146,4 +172,10 @@ class IntegrationTest < ActiveRecord::TestCase owner = owners(:blackbeard) assert_equal "owners/#{owner.id}-#{owner.happy_at.utc.to_s(:usec)}", owner.cache_key(:updated_at, :happy_at) end + + def test_cache_key_when_named_timestamp_is_nil + owner = owners(:blackbeard) + owner.happy_at = nil + assert_equal "owners/#{owner.id}", owner.cache_key(:happy_at) + end end diff --git a/activerecord/test/cases/invalid_connection_test.rb b/activerecord/test/cases/invalid_connection_test.rb index a16b52751a..1367af2859 100644 --- a/activerecord/test/cases/invalid_connection_test.rb +++ b/activerecord/test/cases/invalid_connection_test.rb @@ -1,24 +1,24 @@ require "cases/helper" if current_adapter?(:Mysql2Adapter) -class TestAdapterWithInvalidConnection < ActiveRecord::TestCase - self.use_transactional_tests = false + class TestAdapterWithInvalidConnection < ActiveRecord::TestCase + self.use_transactional_tests = false - class Bird < ActiveRecord::Base - end + class Bird < ActiveRecord::Base + end - def setup - # Can't just use current adapter; sqlite3 will create a database - # file on the fly. - Bird.establish_connection adapter: 'mysql2', database: 'i_do_not_exist' - end + def setup + # Can't just use current adapter; sqlite3 will create a database + # file on the fly. + Bird.establish_connection adapter: "mysql2", database: "i_do_not_exist" + end - teardown do - Bird.remove_connection - end + teardown do + Bird.remove_connection + end - test "inspect on Model class does not raise" do - assert_equal "#{Bird.name} (call '#{Bird.name}.connection' to establish a connection)", Bird.inspect + test "inspect on Model class does not raise" do + assert_equal "#{Bird.name} (call '#{Bird.name}.connection' to establish a connection)", Bird.inspect + end end end -end diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb index e030f6c588..9d5aace7db 100644 --- a/activerecord/test/cases/invertible_migration_test.rb +++ b/activerecord/test/cases/invertible_migration_test.rb @@ -6,7 +6,7 @@ end module ActiveRecord class InvertibleMigrationTest < ActiveRecord::TestCase class SilentMigration < ActiveRecord::Migration::Current - def write(text = '') + def write(text = "") # sssshhhhh!! end end @@ -151,6 +151,14 @@ module ActiveRecord end end + class RevertCustomForeignKeyTable < SilentMigration + def change + change_table(:horses) do |t| + t.references :owner, foreign_key: { to_table: :developers } + end + end + end + setup do @verbose_was, ActiveRecord::Migration.verbose = ActiveRecord::Migration.verbose, false end @@ -217,7 +225,7 @@ module ActiveRecord InvertibleMigration.new.migrate :up received = [] migration = InvertibleByPartsMigration.new - migration.test = ->(dir){ + migration.test = ->(dir) { ActiveSupport::Deprecation.silence do assert migration.connection.table_exists?("horses") assert migration.connection.table_exists?("new_horses") @@ -283,21 +291,21 @@ module ActiveRecord migration1.migrate(:up) migration2.migrate(:up) - assert_equal true, Horse.connection.extension_enabled?('hstore') + assert_equal true, Horse.connection.extension_enabled?("hstore") migration3.migrate(:up) - assert_equal false, Horse.connection.extension_enabled?('hstore') + assert_equal false, Horse.connection.extension_enabled?("hstore") migration3.migrate(:down) - assert_equal true, Horse.connection.extension_enabled?('hstore') + assert_equal true, Horse.connection.extension_enabled?("hstore") migration2.migrate(:down) - assert_equal false, Horse.connection.extension_enabled?('hstore') + assert_equal false, Horse.connection.extension_enabled?("hstore") end end def test_revert_order - block = Proc.new{|t| t.string :name } + block = Proc.new { |t| t.string :name } recorder = ActiveRecord::Migration::CommandRecorder.new(ActiveRecord::Base.connection) recorder.instance_eval do create_table("apples", &block) @@ -343,14 +351,21 @@ module ActiveRecord end def test_migrate_down_with_table_name_prefix - ActiveRecord::Base.table_name_prefix = 'p_' - ActiveRecord::Base.table_name_suffix = '_s' + ActiveRecord::Base.table_name_prefix = "p_" + ActiveRecord::Base.table_name_suffix = "_s" 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" } ensure - ActiveRecord::Base.table_name_prefix = ActiveRecord::Base.table_name_suffix = '' + ActiveRecord::Base.table_name_prefix = ActiveRecord::Base.table_name_suffix = "" + end + + def test_migrations_can_handle_foreign_keys_to_specific_tables + migration = RevertCustomForeignKeyTable.new + InvertibleMigration.migrate(:up) + migration.migrate(:up) + migration.migrate(:down) end # MySQL 5.7 and Oracle do not allow to create duplicate indexes on the same columns @@ -367,6 +382,5 @@ module ActiveRecord "horses_index_named index should not exist" end end - end end diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb index a222675918..b06fed4f0d 100644 --- a/activerecord/test/cases/json_serialization_test.rb +++ b/activerecord/test/cases/json_serialization_test.rb @@ -1,21 +1,21 @@ require "cases/helper" -require 'models/contact' -require 'models/post' -require 'models/author' -require 'models/tagging' -require 'models/tag' -require 'models/comment' +require "models/contact" +require "models/post" +require "models/author" +require "models/tagging" +require "models/tag" +require "models/comment" module JsonSerializationHelpers private - def set_include_root_in_json(value) - original_root_in_json = ActiveRecord::Base.include_root_in_json - ActiveRecord::Base.include_root_in_json = value - yield - ensure - ActiveRecord::Base.include_root_in_json = original_root_in_json - end + def set_include_root_in_json(value) + original_root_in_json = ActiveRecord::Base.include_root_in_json + ActiveRecord::Base.include_root_in_json = value + yield + ensure + ActiveRecord::Base.include_root_in_json = original_root_in_json + end end class JsonSerializationTest < ActiveRecord::TestCase @@ -27,18 +27,18 @@ class JsonSerializationTest < ActiveRecord::TestCase def setup @contact = Contact.new( - :name => 'Konata Izumi', - :age => 16, - :avatar => 'binarydata', - :created_at => Time.utc(2006, 8, 1), - :awesome => true, - :preferences => { :shows => 'anime' } + name: "Konata Izumi", + age: 16, + avatar: "binarydata", + created_at: Time.utc(2006, 8, 1), + awesome: true, + preferences: { shows: "anime" } ) end def test_should_demodulize_root_in_json set_include_root_in_json(true) do - @contact = NamespacedContact.new name: 'whatever' + @contact = NamespacedContact.new name: "whatever" json = @contact.to_json assert_match %r{^\{"namespaced_contact":\{}, json end @@ -51,7 +51,7 @@ class JsonSerializationTest < ActiveRecord::TestCase assert_match %r{^\{"contact":\{}, json assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json - assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}) assert_match %r{"awesome":true}, json assert_match %r{"preferences":\{"shows":"anime"\}}, json end @@ -62,28 +62,28 @@ class JsonSerializationTest < ActiveRecord::TestCase assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json - assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}) assert_match %r{"awesome":true}, json assert_match %r{"preferences":\{"shows":"anime"\}}, json end def test_should_allow_attribute_filtering_with_only - json = @contact.to_json(:only => [:name, :age]) + json = @contact.to_json(only: [:name, :age]) assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json assert_no_match %r{"awesome":true}, json - assert !json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_not_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}) assert_no_match %r{"preferences":\{"shows":"anime"\}}, json end def test_should_allow_attribute_filtering_with_except - json = @contact.to_json(:except => [:name, :age]) + json = @contact.to_json(except: [:name, :age]) assert_no_match %r{"name":"Konata Izumi"}, json assert_no_match %r{"age":16}, json assert_match %r{"awesome":true}, json - assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}) assert_match %r{"preferences":\{"shows":"anime"\}}, json end @@ -93,10 +93,10 @@ class JsonSerializationTest < ActiveRecord::TestCase def @contact.favorite_quote; "Constraints are liberating"; end # Single method. - assert_match %r{"label":"Has cheezburger"}, @contact.to_json(:only => :name, :methods => :label) + assert_match %r{"label":"Has cheezburger"}, @contact.to_json(only: :name, methods: :label) # Both methods. - methods_json = @contact.to_json(:only => :name, :methods => [:label, :favorite_quote]) + methods_json = @contact.to_json(only: :name, methods: [:label, :favorite_quote]) assert_match %r{"label":"Has cheezburger"}, methods_json assert_match %r{"favorite_quote":"Constraints are liberating"}, methods_json end @@ -125,7 +125,7 @@ class JsonSerializationTest < ActiveRecord::TestCase def test_does_not_include_inheritance_column_from_sti @contact = ContactSti.new(@contact.attributes) - assert_equal 'ContactSti', @contact.type + assert_equal "ContactSti", @contact.type json = @contact.to_json assert_match %r{"name":"Konata Izumi"}, json @@ -135,7 +135,7 @@ class JsonSerializationTest < ActiveRecord::TestCase def test_serializable_hash_with_default_except_option_and_excluding_inheritance_column_from_sti @contact = ContactSti.new(@contact.attributes) - assert_equal 'ContactSti', @contact.type + assert_equal "ContactSti", @contact.type def @contact.serializable_hash(options={}) super({ except: %w(age) }.merge!(options)) @@ -149,7 +149,7 @@ class JsonSerializationTest < ActiveRecord::TestCase end def test_serializable_hash_should_not_modify_options_in_argument - options = { :only => :name } + options = { only: :name } @contact.serializable_hash(options) assert_nil options[:except] @@ -167,7 +167,7 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase end def test_includes_uses_association_name - json = @david.to_json(:include => :posts) + json = @david.to_json(include: :posts) assert_match %r{"posts":\[}, json @@ -183,7 +183,7 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase end def test_includes_uses_association_name_and_applies_attribute_filters - json = @david.to_json(:include => { :posts => { :only => :title } }) + json = @david.to_json(include: { posts: { only: :title } }) assert_match %r{"name":"David"}, json assert_match %r{"posts":\[}, json @@ -196,7 +196,7 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase end def test_includes_fetches_second_level_associations - json = @david.to_json(:include => { :posts => { :include => { :comments => { :only => :body } } } }) + json = @david.to_json(include: { posts: { include: { comments: { only: :body } } } }) assert_match %r{"name":"David"}, json assert_match %r{"posts":\[}, json @@ -209,12 +209,12 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase def test_includes_fetches_nth_level_associations json = @david.to_json( - :include => { - :posts => { - :include => { - :taggings => { - :include => { - :tag => { :only => :name } + include: { + posts: { + include: { + taggings: { + include: { + tag: { only: :name } } } } @@ -230,8 +230,8 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase def test_includes_doesnt_merge_opts_from_base json = @david.to_json( - :only => :id, - :include => :posts + only: :id, + include: :posts ) assert_match %{"title":"Welcome to the weblog"}, json @@ -239,7 +239,7 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase def test_should_not_call_methods_on_associations_that_dont_respond def @david.favorite_quote; "Constraints are liberating"; end - json = @david.to_json(:include => :posts, :methods => :favorite_quote) + json = @david.to_json(include: :posts, methods: :favorite_quote) assert !@david.posts.first.respond_to?(:favorite_quote) assert_match %r{"favorite_quote":"Constraints are liberating"}, json @@ -267,15 +267,15 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase def test_should_allow_includes_for_list_of_authors authors = [@david, @mary] json = ActiveSupport::JSON.encode(authors, - :only => :name, - :include => { - :posts => { :only => :id } + only: :name, + include: { + posts: { only: :id } } ) ['"name":"David"', '"posts":[', '{"id":1}', '{"id":2}', '{"id":4}', '{"id":5}', '{"id":6}', '"name":"Mary"', '"posts":[', '{"id":7}', '{"id":9}'].each do |fragment| - assert json.include?(fragment), json + assert_includes json, fragment, json end end diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index 4fe76e563a..0579df0a07 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -1,18 +1,18 @@ -require 'thread' +require "thread" require "cases/helper" -require 'models/person' -require 'models/job' -require 'models/reader' -require 'models/ship' -require 'models/legacy_thing' -require 'models/personal_legacy_thing' -require 'models/reference' -require 'models/string_key_object' -require 'models/car' -require 'models/bulb' -require 'models/engine' -require 'models/wheel' -require 'models/treasure' +require "models/person" +require "models/job" +require "models/reader" +require "models/ship" +require "models/legacy_thing" +require "models/personal_legacy_thing" +require "models/reference" +require "models/string_key_object" +require "models/car" +require "models/bulb" +require "models/engine" +require "models/wheel" +require "models/treasure" class LockWithoutDefault < ActiveRecord::Base; end @@ -33,7 +33,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase p1 = Person.find(1) assert_equal 0, p1.lock_version - p1.first_name = 'anika2' + p1.first_name = "anika2" p1.save! assert_equal 1, p1.lock_version @@ -45,12 +45,12 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_equal 0, s1.lock_version assert_equal 0, s2.lock_version - s1.name = 'updated record' + s1.name = "updated record" s1.save! assert_equal 1, s1.lock_version assert_equal 0, s2.lock_version - s2.name = 'doubly updated record' + s2.name = "doubly updated record" assert_raise(ActiveRecord::StaleObjectError) { s2.save! } end @@ -60,7 +60,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_equal 0, s1.lock_version assert_equal 0, s2.lock_version - s1.name = 'updated record' + s1.name = "updated record" s1.save! assert_equal 1, s1.lock_version assert_equal 0, s2.lock_version @@ -78,12 +78,12 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_equal 0, p1.lock_version assert_equal 0, p2.lock_version - p1.first_name = 'stu' + p1.first_name = "stu" p1.save! assert_equal 1, p1.lock_version assert_equal 0, p2.lock_version - p2.first_name = 'sue' + p2.first_name = "sue" assert_raise(ActiveRecord::StaleObjectError) { p2.save! } end @@ -94,7 +94,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_equal 0, p1.lock_version assert_equal 0, p2.lock_version - p1.first_name = 'stu' + p1.first_name = "stu" p1.save! assert_equal 1, p1.lock_version assert_equal 0, p2.lock_version @@ -113,60 +113,58 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_equal 0, p1.lock_version assert_equal 0, p2.lock_version - p1.first_name = 'stu' + p1.first_name = "stu" p1.save! assert_equal 1, p1.lock_version assert_equal 0, p2.lock_version - p2.first_name = 'sue' + p2.first_name = "sue" assert_raise(ActiveRecord::StaleObjectError) { p2.save! } - p2.first_name = 'sue2' + p2.first_name = "sue2" assert_raise(ActiveRecord::StaleObjectError) { p2.save! } end def test_lock_new - p1 = Person.new(:first_name => 'anika') + p1 = Person.new(first_name: "anika") assert_equal 0, p1.lock_version - p1.first_name = 'anika2' + p1.first_name = "anika2" p1.save! p2 = Person.find(p1.id) assert_equal 0, p1.lock_version assert_equal 0, p2.lock_version - p1.first_name = 'anika3' + p1.first_name = "anika3" p1.save! assert_equal 1, p1.lock_version assert_equal 0, p2.lock_version - p2.first_name = 'sue' + p2.first_name = "sue" assert_raise(ActiveRecord::StaleObjectError) { p2.save! } end def test_lock_exception_record - p1 = Person.new(:first_name => 'mira') + p1 = Person.new(first_name: "mira") assert_equal 0, p1.lock_version - p1.first_name = 'mira2' + p1.first_name = "mira2" p1.save! p2 = Person.find(p1.id) assert_equal 0, p1.lock_version assert_equal 0, p2.lock_version - p1.first_name = 'mira3' + p1.first_name = "mira3" p1.save! - p2.first_name = 'sue' + p2.first_name = "sue" error = assert_raise(ActiveRecord::StaleObjectError) { p2.save! } assert_equal(error.record.object_id, p2.object_id) end - def test_lock_new_with_nil - p1 = Person.new(:first_name => 'anika') + def test_lock_new_when_explicitly_passing_nil + p1 = Person.new(first_name: "anika", lock_version: nil) p1.save! - p1.lock_version = nil # simulate bad fixture or column with no default - p1.save! - assert_equal 1, p1.lock_version + assert_equal 0, p1.lock_version end def test_touch_existing_lock @@ -175,12 +173,13 @@ class OptimisticLockingTest < ActiveRecord::TestCase p1.touch assert_equal 1, p1.lock_version + assert_not p1.changed?, "Changes should have been cleared" end def test_touch_stale_object - person = Person.create!(first_name: 'Mehmet Emin') + person = Person.create!(first_name: "Mehmet Emin") stale_person = Person.find(person.id) - person.update_attribute(:gender, 'M') + person.update_attribute(:gender, "M") assert_raises(ActiveRecord::StaleObjectError) do stale_person.touch @@ -203,11 +202,11 @@ class OptimisticLockingTest < ActiveRecord::TestCase end def test_lock_column_is_mass_assignable - p1 = Person.create(:first_name => 'bianca') + p1 = Person.create(first_name: "bianca") assert_equal 0, p1.lock_version assert_equal p1.lock_version, Person.new(p1.attributes).lock_version - p1.first_name = 'bianca2' + p1.first_name = "bianca2" p1.save! assert_equal 1, p1.lock_version assert_equal p1.lock_version, Person.new(p1.attributes).lock_version @@ -215,28 +214,155 @@ class OptimisticLockingTest < ActiveRecord::TestCase def test_lock_without_default_sets_version_to_zero t1 = LockWithoutDefault.new + + assert_equal 0, t1.lock_version + assert_nil t1.lock_version_before_type_cast + + t1.save! + t1.reload + + assert_equal 0, t1.lock_version + assert_equal 0, t1.lock_version_before_type_cast + end + + def test_lock_without_default_should_work_with_null_in_the_database + ActiveRecord::Base.connection.execute("INSERT INTO lock_without_defaults(title) VALUES('title1')") + t1 = LockWithoutDefault.last + t2 = LockWithoutDefault.last + + assert_equal 0, t1.lock_version + assert_nil t1.lock_version_before_type_cast + assert_equal 0, t2.lock_version + assert_nil t2.lock_version_before_type_cast + + t1.title = "new title1" + t2.title = "new title2" + + assert_nothing_raised { t1.save! } + assert_equal 1, t1.lock_version + assert_equal "new title1", t1.title + + assert_raise(ActiveRecord::StaleObjectError) { t2.save! } + assert_equal 0, t2.lock_version + assert_equal "new title2", t2.title + end + + def test_lock_without_default_should_update_with_lock_col + t1 = LockWithoutDefault.create(title: "title1", lock_version: 6) + + assert_equal 6, t1.lock_version + + t1.update(lock_version: 0) + t1.reload + assert_equal 0, t1.lock_version + end + + def test_lock_without_default_queries_count + t1 = LockWithoutDefault.create(title: "title1") - t1.save - t1 = LockWithoutDefault.find(t1.id) + assert_equal "title1", t1.title assert_equal 0, t1.lock_version + + assert_queries(1) { t1.update(title: "title2") } + + t1.reload + assert_equal "title2", t1.title + assert_equal 1, t1.lock_version + + assert_queries(1) { t1.update(title: "title3", lock_version: 6) } + + t1.reload + assert_equal "title3", t1.title + assert_equal 6, t1.lock_version + + t2 = LockWithoutDefault.new(title: "title1") + + assert_queries(1) { t2.save! } + + t2.reload + assert_equal "title1", t2.title + assert_equal 0, t2.lock_version end def test_lock_with_custom_column_without_default_sets_version_to_zero t1 = LockWithCustomColumnWithoutDefault.new + assert_equal 0, t1.custom_lock_version assert_nil t1.custom_lock_version_before_type_cast t1.save! t1.reload + + assert_equal 0, t1.custom_lock_version + assert_equal 0, t1.custom_lock_version_before_type_cast + end + + def test_lock_with_custom_column_without_default_should_work_with_null_in_the_database + ActiveRecord::Base.connection.execute("INSERT INTO lock_without_defaults_cust(title) VALUES('title1')") + + t1 = LockWithCustomColumnWithoutDefault.last + t2 = LockWithCustomColumnWithoutDefault.last + + assert_equal 0, t1.custom_lock_version + assert_nil t1.custom_lock_version_before_type_cast + assert_equal 0, t2.custom_lock_version + assert_nil t2.custom_lock_version_before_type_cast + + t1.title = "new title1" + t2.title = "new title2" + + assert_nothing_raised { t1.save! } + assert_equal 1, t1.custom_lock_version + assert_equal "new title1", t1.title + + assert_raise(ActiveRecord::StaleObjectError) { t2.save! } + assert_equal 0, t2.custom_lock_version + assert_equal "new title2", t2.title + end + + def test_lock_with_custom_column_without_default_should_update_with_lock_col + t1 = LockWithCustomColumnWithoutDefault.create(title: "title1", custom_lock_version: 6) + + assert_equal 6, t1.custom_lock_version + + t1.update(custom_lock_version: 0) + t1.reload + + assert_equal 0, t1.custom_lock_version + end + + def test_lock_with_custom_column_without_default_queries_count + t1 = LockWithCustomColumnWithoutDefault.create(title: "title1") + + assert_equal "title1", t1.title assert_equal 0, t1.custom_lock_version - assert [0, "0"].include?(t1.custom_lock_version_before_type_cast) + + assert_queries(1) { t1.update(title: "title2") } + + t1.reload + assert_equal "title2", t1.title + assert_equal 1, t1.custom_lock_version + + assert_queries(1) { t1.update(title: "title3", custom_lock_version: 6) } + + t1.reload + assert_equal "title3", t1.title + assert_equal 6, t1.custom_lock_version + + t2 = LockWithCustomColumnWithoutDefault.new(title: "title1") + + assert_queries(1) { t2.save! } + + t2.reload + assert_equal "title1", t2.title + assert_equal 0, t2.custom_lock_version end def test_readonly_attributes - assert_equal Set.new([ 'name' ]), ReadonlyNameShip.readonly_attributes + assert_equal Set.new([ "name" ]), ReadonlyNameShip.readonly_attributes - s = ReadonlyNameShip.create(:name => "unchangeable name") + s = ReadonlyNameShip.create(name: "unchangeable name") s.reload assert_equal "unchangeable name", s.name @@ -255,7 +381,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase # is nothing else being updated. def test_update_without_attributes_does_not_only_update_lock_version assert_nothing_raised do - p1 = Person.create!(:first_name => 'anika') + p1 = Person.create!(first_name: "anika") lock_version = p1.lock_version p1.save p1.reload @@ -266,17 +392,17 @@ class OptimisticLockingTest < ActiveRecord::TestCase def test_polymorphic_destroy_with_dependencies_and_lock_version car = Car.create! - assert_difference 'car.wheels.count' do + assert_difference "car.wheels.count" do car.wheels << Wheel.create! end - assert_difference 'car.wheels.count', -1 do + assert_difference "car.wheels.count", -1 do car.reload.destroy end assert car.destroyed? end def test_removing_has_and_belongs_to_many_associations_upon_destroy - p = RichPerson.create! first_name: 'Jon' + p = RichPerson.create! first_name: "Jon" p.treasures.create! assert !p.treasures.empty? p.destroy @@ -300,7 +426,7 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase # of a test (see test_increment_counter_*). self.use_transactional_tests = false - { :lock_version => Person, :custom_lock_version => LegacyThing }.each do |name, model| + { lock_version: Person, custom_lock_version: LegacyThing }.each do |name, model| define_method("test_increment_counter_updates_#{name}") do counter_test model, 1 do |id| model.increment_counter :test_count, id @@ -315,7 +441,7 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase define_method("test_update_counters_updates_#{name}") do counter_test model, 1 do |id| - model.update_counters id, :test_count => 1 + model.update_counters id, test_count: 1 end end end @@ -323,13 +449,13 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase # See Lighthouse ticket #1966 def test_destroy_dependents # Establish dependent relationship between Person and PersonalLegacyThing - add_counter_column_to(Person, 'personal_legacy_things_count') + add_counter_column_to(Person, "personal_legacy_things_count") PersonalLegacyThing.reset_column_information # Make sure that counter incrementing doesn't cause problems - p1 = Person.new(:first_name => 'fjord') + p1 = Person.new(first_name: "fjord") p1.save! - t = PersonalLegacyThing.new(:person => p1) + t = PersonalLegacyThing.new(person: p1) t.save! p1.reload assert_equal 1, p1.personal_legacy_things_count @@ -338,14 +464,14 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase assert_raises(ActiveRecord::RecordNotFound) { Person.find(p1.id) } assert_raises(ActiveRecord::RecordNotFound) { PersonalLegacyThing.find(t.id) } ensure - remove_counter_column_from(Person, 'personal_legacy_things_count') + remove_counter_column_from(Person, "personal_legacy_things_count") PersonalLegacyThing.reset_column_information end private - def add_counter_column_to(model, col='test_count') - model.connection.add_column model.table_name, col, :integer, :null => false, :default => 0 + def add_counter_column_to(model, col="test_count") + model.connection.add_column model.table_name, col, :integer, null: false, default: 0 model.reset_column_information end @@ -368,7 +494,6 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase end end - # TODO: test against the generated SQL since testing locking behavior itself # is so cumbersome. Will deadlock Ruby threads if the underlying db.execute # blocks, so separate script called by Kernel#system is needed. @@ -410,7 +535,7 @@ unless in_memory_db? assert_nothing_raised do Person.transaction do person = Person.find 1 - old, person.first_name = person.first_name, 'fooman' + old, person.first_name = person.first_name, "fooman" person.lock! assert_equal old, person.first_name end @@ -420,19 +545,19 @@ unless in_memory_db? def test_with_lock_commits_transaction person = Person.find 1 person.with_lock do - person.first_name = 'fooman' + person.first_name = "fooman" person.save! end - assert_equal 'fooman', person.reload.first_name + assert_equal "fooman", person.reload.first_name end def test_with_lock_rolls_back_transaction person = Person.find 1 old = person.first_name person.with_lock do - person.first_name = 'fooman' + person.first_name = "fooman" person.save! - raise 'oops' + raise "oops" end rescue nil assert_equal old, person.reload.first_name end @@ -441,8 +566,8 @@ unless in_memory_db? def test_lock_sending_custom_lock_statement Person.transaction do person = Person.find(1) - assert_sql(/LIMIT \$\d FOR SHARE NOWAIT/) do - person.lock!('FOR SHARE NOWAIT') + assert_sql(/LIMIT \$?\d FOR SHARE NOWAIT/) do + person.lock!("FOR SHARE NOWAIT") end end end @@ -455,33 +580,34 @@ unless in_memory_db? end protected - def duel(zzz = 5) - t0, t1, t2, t3 = nil, nil, nil, nil - - a = Thread.new do - t0 = Time.now - Person.transaction do - yield - sleep zzz # block thread 2 for zzz seconds - end - t1 = Time.now - end - b = Thread.new do - sleep zzz / 2.0 # ensure thread 1 tx starts first - t2 = Time.now - Person.transaction { yield } - t3 = Time.now - end + def duel(zzz = 5) + t0, t1, t2, t3 = nil, nil, nil, nil - a.join - b.join + a = Thread.new do + t0 = Time.now + Person.transaction do + yield + sleep zzz # block thread 2 for zzz seconds + end + t1 = Time.now + end - assert t1 > t0 + zzz - assert t2 > t0 - assert t3 > t2 - [t0.to_f..t1.to_f, t2.to_f..t3.to_f] + b = Thread.new do + sleep zzz / 2.0 # ensure thread 1 tx starts first + t2 = Time.now + Person.transaction { yield } + t3 = Time.now end + + a.join + b.join + + assert t1 > t0 + zzz + assert t2 > t0 + assert t3 > t2 + [t0.to_f..t1.to_f, t2.to_f..t3.to_f] + end end end end diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb index 707a2d1da1..90ad970e16 100644 --- a/activerecord/test/cases/log_subscriber_test.rb +++ b/activerecord/test/cases/log_subscriber_test.rb @@ -30,7 +30,7 @@ class LogSubscriberTest < ActiveRecord::TestCase super end - def debug message + def debug(message) @debugs << message end end @@ -60,20 +60,20 @@ class LogSubscriberTest < ActiveRecord::TestCase logger = TestDebugLogSubscriber.new assert_equal 0, logger.debugs.length - logger.sql(event.new(0, sql: 'hi mom!')) + logger.sql(event.new(0, sql: "hi mom!")) assert_equal 1, logger.debugs.length - logger.sql(event.new(0, sql: 'hi mom!', name: 'foo')) + logger.sql(event.new(0, sql: "hi mom!", name: "foo")) assert_equal 2, logger.debugs.length - logger.sql(event.new(0, sql: 'hi mom!', name: 'SCHEMA')) + logger.sql(event.new(0, sql: "hi mom!", name: "SCHEMA")) assert_equal 2, logger.debugs.length end def test_sql_statements_are_not_squeezed event = Struct.new(:duration, :payload) logger = TestDebugLogSubscriber.new - logger.sql(event.new(0, sql: 'ruby rails')) + logger.sql(event.new(0, sql: "ruby rails")) assert_match(/ruby rails/, logger.debugs.first) end @@ -103,7 +103,7 @@ class LogSubscriberTest < ActiveRecord::TestCase logger.sql(event.new(0, sql: verb.to_s)) assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last) - logger.sql(event.new(0, {sql: verb.to_s, name: "SQL"})) + logger.sql(event.new(0, sql: verb.to_s, name: "SQL")) assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA}SQL \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last) end end @@ -113,13 +113,13 @@ class LogSubscriberTest < ActiveRecord::TestCase logger = TestDebugLogSubscriber.new logger.colorize_logging = true SQL_COLORINGS.each do |verb, _| - logger.sql(event.new(0, {sql: verb.to_s, name: "Model Load"})) + logger.sql(event.new(0, sql: verb.to_s, name: "Model Load")) assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}Model Load \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last) - logger.sql(event.new(0, {sql: verb.to_s, name: "Model Exists"})) + logger.sql(event.new(0, sql: verb.to_s, name: "Model Exists")) assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}Model Exists \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last) - logger.sql(event.new(0, {sql: verb.to_s, name: "ANY SPECIFIC NAME"})) + logger.sql(event.new(0, sql: verb.to_s, name: "ANY SPECIFIC NAME")) assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}ANY SPECIFIC NAME \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last) end end @@ -211,9 +211,15 @@ class LogSubscriberTest < ActiveRecord::TestCase if ActiveRecord::Base.connection.prepared_statements def test_binary_data_is_not_logged - Binary.create(data: 'some binary data') + Binary.create(data: "some binary data") wait assert_match(/<16 bytes of binary data>/, @logger.logged(:debug).join) end + + def test_binary_data_hash + Binary.create(data: { a: 1 }) + wait + assert_match(/<7 bytes of binary data>/, @logger.logged(:debug).join) + end end end diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb index d6963b48d7..bdb90eaa74 100644 --- a/activerecord/test/cases/migration/change_schema_test.rb +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -1,4 +1,4 @@ -require 'cases/helper' +require "cases/helper" module ActiveRecord class Migration @@ -40,7 +40,7 @@ module ActiveRecord def test_create_table_with_not_null_column connection.create_table :testings do |t| - t.column :foo, :string, :null => false + t.column :foo, :string, null: false end assert_raises(ActiveRecord::StatementInvalid) do @@ -53,11 +53,11 @@ module ActiveRecord mysql = current_adapter?(:Mysql2Adapter) connection.create_table :testings do |t| - t.column :one, :string, :default => "hello" - t.column :two, :boolean, :default => true - t.column :three, :boolean, :default => false - t.column :four, :integer, :default => 1 - t.column :five, :text, :default => "hello" unless mysql + t.column :one, :string, default: "hello" + t.column :two, :boolean, default: true + t.column :three, :boolean, default: false + t.column :four, :integer, default: 1 + t.column :five, :text, default: "hello" unless mysql end columns = connection.columns(:testings) @@ -70,14 +70,14 @@ module ActiveRecord assert_equal "hello", one.default assert_equal true, connection.lookup_cast_type_from_column(two).deserialize(two.default) assert_equal false, connection.lookup_cast_type_from_column(three).deserialize(three.default) - assert_equal '1', four.default + assert_equal "1", four.default assert_equal "hello", five.default unless mysql end if current_adapter?(:PostgreSQLAdapter) def test_add_column_with_array connection.create_table :testings - connection.add_column :testings, :foo, :string, :array => true + connection.add_column :testings, :foo, :string, array: true columns = connection.columns(:testings) array_column = columns.detect { |c| c.name == "foo" } @@ -87,7 +87,7 @@ module ActiveRecord def test_create_table_with_array_column connection.create_table :testings do |t| - t.string :foo, :array => true + t.string :foo, array: true end columns = connection.columns(:testings) @@ -105,9 +105,9 @@ module ActiveRecord eight = columns.detect { |c| c.name == "eight_int" } if current_adapter?(:OracleAdapter) - assert_equal 'NUMBER(19)', eight.sql_type + assert_equal "NUMBER(19)", eight.sql_type elsif current_adapter?(:SQLite3Adapter) - assert_equal 'bigint', eight.sql_type + assert_equal "bigint", eight.sql_type else assert_equal :integer, eight.type assert_equal 8, eight.limit @@ -118,13 +118,13 @@ module ActiveRecord def test_create_table_with_limits connection.create_table :testings do |t| - t.column :foo, :string, :limit => 255 + t.column :foo, :string, limit: 255 t.column :default_int, :integer - t.column :one_int, :integer, :limit => 1 - t.column :four_int, :integer, :limit => 4 - t.column :eight_int, :integer, :limit => 8 + t.column :one_int, :integer, limit: 1 + t.column :four_int, :integer, limit: 4 + t.column :eight_int, :integer, limit: 8 end columns = connection.columns(:testings) @@ -137,20 +137,20 @@ module ActiveRecord eight = columns.detect { |c| c.name == "eight_int" } if current_adapter?(:PostgreSQLAdapter) - assert_equal 'integer', default.sql_type - assert_equal 'smallint', one.sql_type - assert_equal 'integer', four.sql_type - assert_equal 'bigint', eight.sql_type + assert_equal "integer", default.sql_type + assert_equal "smallint", one.sql_type + assert_equal "integer", four.sql_type + assert_equal "bigint", eight.sql_type elsif current_adapter?(:Mysql2Adapter) - assert_match 'int(11)', default.sql_type - assert_match 'tinyint', one.sql_type - assert_match 'int', four.sql_type - assert_match 'bigint', eight.sql_type + assert_match "int(11)", default.sql_type + assert_match "tinyint", one.sql_type + assert_match "int", four.sql_type + assert_match "bigint", eight.sql_type elsif current_adapter?(:OracleAdapter) - assert_equal 'NUMBER(38)', default.sql_type - assert_equal 'NUMBER(1)', one.sql_type - assert_equal 'NUMBER(4)', four.sql_type - assert_equal 'NUMBER(8)', eight.sql_type + assert_equal "NUMBER(38)", default.sql_type + assert_equal "NUMBER(1)", one.sql_type + assert_equal "NUMBER(4)", four.sql_type + assert_equal "NUMBER(8)", eight.sql_type end end @@ -200,8 +200,8 @@ module ActiveRecord end created_columns = connection.columns(table_name) - created_at_column = created_columns.detect {|c| c.name == 'created_at' } - updated_at_column = created_columns.detect {|c| c.name == 'updated_at' } + created_at_column = created_columns.detect { |c| c.name == "created_at" } + updated_at_column = created_columns.detect { |c| c.name == "updated_at" } assert !created_at_column.null assert !updated_at_column.null @@ -213,8 +213,8 @@ module ActiveRecord end created_columns = connection.columns(table_name) - created_at_column = created_columns.detect {|c| c.name == 'created_at' } - updated_at_column = created_columns.detect {|c| c.name == 'updated_at' } + created_at_column = created_columns.detect { |c| c.name == "created_at" } + updated_at_column = created_columns.detect { |c| c.name == "updated_at" } assert created_at_column.null assert updated_at_column.null @@ -231,7 +231,7 @@ module ActiveRecord connection.create_table :testings do |t| t.column :foo, :string end - connection.add_column :testings, :bar, :string, :null => false + connection.add_column :testings, :bar, :string, null: false assert_raise(ActiveRecord::StatementInvalid) do connection.execute "insert into testings (foo, bar) values ('hello', NULL)" @@ -246,7 +246,7 @@ module ActiveRecord 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" } + assert_nothing_raised { connection.add_column :testings, :bar, :string, null: false, default: "default" } 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)" @@ -259,14 +259,14 @@ module ActiveRecord end klass = Class.new(ActiveRecord::Base) - klass.table_name = 'testings' + klass.table_name = "testings" - assert_equal :datetime, klass.columns_hash['foo'].type + assert_equal :datetime, klass.columns_hash["foo"].type if current_adapter?(:PostgreSQLAdapter) - assert_equal 'timestamp without time zone', klass.columns_hash['foo'].sql_type + assert_equal "timestamp without time zone", klass.columns_hash["foo"].sql_type else - assert_equal klass.connection.type_to_sql('datetime'), klass.columns_hash['foo'].sql_type + assert_equal klass.connection.type_to_sql("datetime"), klass.columns_hash["foo"].sql_type end end @@ -275,7 +275,7 @@ module ActiveRecord t.column :select, :string end - connection.change_column :testings, :select, :string, :limit => 10 + connection.change_column :testings, :select, :string, limit: 10 # Oracle needs primary key value from sequence if current_adapter?(:OracleAdapter) @@ -290,17 +290,17 @@ module ActiveRecord t.column :title, :string end person_klass = Class.new(ActiveRecord::Base) - person_klass.table_name = 'testings' + person_klass.table_name = "testings" - person_klass.connection.add_column "testings", "wealth", :integer, :null => false, :default => 99 + person_klass.connection.add_column "testings", "wealth", :integer, null: false, default: 99 person_klass.reset_column_information assert_equal 99, person_klass.column_defaults["wealth"] assert_equal false, person_klass.columns_hash["wealth"].null # Oracle needs primary key value from sequence if current_adapter?(:OracleAdapter) - assert_nothing_raised {person_klass.connection.execute("insert into testings (id, title) values (testings_seq.nextval, 'tester')")} + assert_nothing_raised { person_klass.connection.execute("insert into testings (id, title) values (testings_seq.nextval, 'tester')") } else - assert_nothing_raised {person_klass.connection.execute("insert into testings (title) values ('tester')")} + assert_nothing_raised { person_klass.connection.execute("insert into testings (title) values ('tester')") } end # change column default to see that column doesn't lose its not null definition @@ -317,19 +317,19 @@ module ActiveRecord assert_equal false, person_klass.columns_hash["money"].null # change column - person_klass.connection.change_column "testings", "money", :integer, :null => false, :default => 1000 + person_klass.connection.change_column "testings", "money", :integer, null: false, default: 1000 person_klass.reset_column_information assert_equal 1000, person_klass.column_defaults["money"] assert_equal false, person_klass.columns_hash["money"].null # change column, make it nullable and clear default - person_klass.connection.change_column "testings", "money", :integer, :null => true, :default => nil + person_klass.connection.change_column "testings", "money", :integer, null: true, default: nil person_klass.reset_column_information assert_nil person_klass.columns_hash["money"].default assert_equal true, person_klass.columns_hash["money"].null # change_column_null, make it not nullable and set null values to a default value - person_klass.connection.execute('UPDATE testings SET money = NULL') + person_klass.connection.execute("UPDATE testings SET money = NULL") person_klass.connection.change_column_null "testings", "money", false, 2000 person_klass.reset_column_information assert_nil person_klass.columns_hash["money"].default @@ -346,9 +346,9 @@ module ActiveRecord end notnull_migration.new.suppress_messages do notnull_migration.migrate(:up) - assert_equal false, connection.columns(:testings).find{ |c| c.name == "foo"}.null + assert_equal false, connection.columns(:testings).find { |c| c.name == "foo" }.null notnull_migration.migrate(:down) - assert connection.columns(:testings).find{ |c| c.name == "foo"}.null + assert connection.columns(:testings).find { |c| c.name == "foo" }.null end end end @@ -365,7 +365,7 @@ module ActiveRecord def test_column_exists_with_type connection.create_table :testings do |t| t.column :foo, :string - t.column :bar, :decimal, :precision => 8, :scale => 2 + t.column :bar, :decimal, precision: 8, scale: 2 end assert connection.column_exists?(:testings, :foo, :string) @@ -380,7 +380,7 @@ module ActiveRecord t.column :foo, :string, limit: 100 t.column :bar, :decimal, precision: 8, scale: 2 t.column :taggable_id, :integer, null: false - t.column :taggable_type, :string, default: 'Photo' + t.column :taggable_type, :string, default: "Photo" end assert connection.column_exists?(:testings, :foo, :string, limit: 100) @@ -389,7 +389,7 @@ module ActiveRecord assert_not connection.column_exists?(:testings, :bar, :decimal, precision: nil, scale: nil) assert connection.column_exists?(:testings, :taggable_id, :integer, null: false) assert_not connection.column_exists?(:testings, :taggable_id, :integer, null: true) - assert connection.column_exists?(:testings, :taggable_type, :string, default: 'Photo') + assert connection.column_exists?(:testings, :taggable_type, :string, default: "Photo") assert_not connection.column_exists?(:testings, :taggable_type, :string, default: nil) end @@ -415,13 +415,13 @@ module ActiveRecord end private - def testing_table_with_only_foo_attribute - connection.create_table :testings, :id => false do |t| - t.column :foo, :string - end + def testing_table_with_only_foo_attribute + connection.create_table :testings, id: false do |t| + t.column :foo, :string + end - yield - end + yield + end end if ActiveRecord::Base.connection.supports_foreign_keys? diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb index 2f9c50141f..ec817a579b 100644 --- a/activerecord/test/cases/migration/change_table_test.rb +++ b/activerecord/test/cases/migration/change_table_test.rb @@ -95,7 +95,7 @@ module ActiveRecord def test_remove_timestamps_creates_updated_at_and_created_at with_change_table do |t| @connection.expect :remove_timestamps, nil, [:delete_me, { null: true }] - t.remove_timestamps({ null: true }) + t.remove_timestamps(null: true) end end @@ -157,8 +157,8 @@ module ActiveRecord def test_column_creates_column_with_options with_change_table do |t| - @connection.expect :add_column, nil, [:delete_me, :bar, :integer, {:null => false}] - t.column :bar, :integer, :null => false + @connection.expect :add_column, nil, [:delete_me, :bar, :integer, { null: false }] + t.column :bar, :integer, null: false end end @@ -171,8 +171,8 @@ module ActiveRecord def test_index_creates_index_with_options with_change_table do |t| - @connection.expect :add_index, nil, [:delete_me, :bar, {:unique => true}] - t.index :bar, :unique => true + @connection.expect :add_index, nil, [:delete_me, :bar, { unique: true }] + t.index :bar, unique: true end end @@ -185,8 +185,8 @@ module ActiveRecord def test_index_exists_with_options with_change_table do |t| - @connection.expect :index_exists?, nil, [:delete_me, :bar, {:unique => true}] - t.index_exists?(:bar, :unique => true) + @connection.expect :index_exists?, nil, [:delete_me, :bar, { unique: true }] + t.index_exists?(:bar, unique: true) end end @@ -206,8 +206,8 @@ module ActiveRecord def test_change_changes_column_with_options with_change_table do |t| - @connection.expect :change_column, nil, [:delete_me, :bar, :string, {:null => true}] - t.change :bar, :string, :null => true + @connection.expect :change_column, nil, [:delete_me, :bar, :string, { null: true }] + t.change :bar, :string, null: true end end @@ -234,8 +234,8 @@ module ActiveRecord def test_remove_index_removes_index_with_options with_change_table do |t| - @connection.expect :remove_index, nil, [:delete_me, {:unique => true}] - t.remove_index :unique => true + @connection.expect :remove_index, nil, [:delete_me, { unique: true }] + t.remove_index unique: true end end diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb index c7a1b81a75..48df931543 100644 --- a/activerecord/test/cases/migration/column_attributes_test.rb +++ b/activerecord/test/cases/migration/column_attributes_test.rb @@ -9,7 +9,7 @@ module ActiveRecord def test_add_column_newline_default string = "foo\nbar" - add_column 'test_models', 'command', :string, :default => string + add_column "test_models", "command", :string, default: string TestModel.reset_column_information assert_equal string, TestModel.new.command @@ -18,10 +18,10 @@ module ActiveRecord def test_add_remove_single_field_using_string_arguments assert_no_column TestModel, :last_name - add_column 'test_models', 'last_name', :string + add_column "test_models", "last_name", :string assert_column TestModel, :last_name - remove_column 'test_models', 'last_name' + remove_column "test_models", "last_name" assert_no_column TestModel, :last_name end @@ -47,7 +47,7 @@ module ActiveRecord def test_unabstracted_database_dependent_types add_column :test_models, :intelligence_quotient, :tinyint TestModel.reset_column_information - assert_match(/tinyint/, TestModel.columns_hash['intelligence_quotient'].sql_type) + assert_match(/tinyint/, TestModel.columns_hash["intelligence_quotient"].sql_type) end end @@ -56,15 +56,13 @@ module ActiveRecord # functionality. This allows us to more easily catch INSERT being broken, # but SELECT actually working fine. def test_native_decimal_insert_manual_vs_automatic - correct_value = '0012345678901234567890.0123456789'.to_d + correct_value = "0012345678901234567890.0123456789".to_d - connection.add_column "test_models", "wealth", :decimal, :precision => '30', :scale => '10' + connection.add_column "test_models", "wealth", :decimal, precision: "30", scale: "10" # Do a manual insertion if current_adapter?(:OracleAdapter) connection.execute "insert into test_models (id, wealth) values (people_seq.nextval, 12345678901234567890.0123456789)" - elsif current_adapter?(:PostgreSQLAdapter) - connection.execute "insert into test_models (wealth) values (12345678901234567890.0123456789)" else connection.execute "insert into test_models (wealth) values (12345678901234567890.0123456789)" end @@ -74,15 +72,13 @@ module ActiveRecord assert_kind_of BigDecimal, row.wealth # If this assert fails, that means the SELECT is broken! - unless current_adapter?(:SQLite3Adapter) - assert_equal correct_value, row.wealth - end + assert_equal correct_value, row.wealth # Reset to old state TestModel.delete_all # Now use the Rails insertion - TestModel.create :wealth => BigDecimal.new("12345678901234567890.0123456789") + TestModel.create wealth: BigDecimal.new("12345678901234567890.0123456789") # SELECT row = TestModel.first @@ -94,26 +90,26 @@ module ActiveRecord end def test_add_column_with_precision_and_scale - connection.add_column 'test_models', 'wealth', :decimal, :precision => 9, :scale => 7 + connection.add_column "test_models", "wealth", :decimal, precision: 9, scale: 7 - wealth_column = TestModel.columns_hash['wealth'] + wealth_column = TestModel.columns_hash["wealth"] assert_equal 9, wealth_column.precision assert_equal 7, wealth_column.scale end if current_adapter?(:SQLite3Adapter) def test_change_column_preserve_other_column_precision_and_scale - connection.add_column 'test_models', 'last_name', :string - connection.add_column 'test_models', 'wealth', :decimal, :precision => 9, :scale => 7 + connection.add_column "test_models", "last_name", :string + connection.add_column "test_models", "wealth", :decimal, precision: 9, scale: 7 - wealth_column = TestModel.columns_hash['wealth'] + wealth_column = TestModel.columns_hash["wealth"] assert_equal 9, wealth_column.precision assert_equal 7, wealth_column.scale - connection.change_column 'test_models', 'last_name', :string, :null => false + connection.change_column "test_models", "last_name", :string, null: false TestModel.reset_column_information - wealth_column = TestModel.columns_hash['wealth'] + wealth_column = TestModel.columns_hash["wealth"] assert_equal 9, wealth_column.precision assert_equal 7, wealth_column.scale end @@ -126,21 +122,21 @@ module ActiveRecord add_column "test_models", "bio", :text add_column "test_models", "age", :integer add_column "test_models", "height", :float - add_column "test_models", "wealth", :decimal, :precision => '30', :scale => '10' + add_column "test_models", "wealth", :decimal, precision: "30", scale: "10" add_column "test_models", "birthday", :datetime add_column "test_models", "favorite_day", :date add_column "test_models", "moment_of_truth", :datetime add_column "test_models", "male", :boolean - TestModel.create :first_name => 'bob', :last_name => 'bobsen', - :bio => "I was born ....", :age => 18, :height => 1.78, - :wealth => BigDecimal.new("12345678901234567890.0123456789"), - :birthday => 18.years.ago, :favorite_day => 10.days.ago, - :moment_of_truth => "1782-10-10 21:40:18", :male => true + TestModel.create first_name: "bob", last_name: "bobsen", + bio: "I was born ....", age: 18, height: 1.78, + wealth: BigDecimal.new("12345678901234567890.0123456789"), + birthday: 18.years.ago, favorite_day: 10.days.ago, + moment_of_truth: "1782-10-10 21:40:18", male: true bob = TestModel.first - assert_equal 'bob', bob.first_name - assert_equal 'bobsen', bob.last_name + assert_equal "bob", bob.first_name + assert_equal "bobsen", bob.last_name assert_equal "I was born ....", bob.bio assert_equal 18, bob.age @@ -154,16 +150,9 @@ module ActiveRecord assert_equal String, bob.first_name.class assert_equal String, bob.last_name.class assert_equal String, bob.bio.class - assert_equal Fixnum, bob.age.class + assert_kind_of Integer, bob.age assert_equal Time, bob.birthday.class - - if current_adapter?(:OracleAdapter) - # Oracle doesn't differentiate between date/time - assert_equal Time, bob.favorite_day.class - else - assert_equal Date, bob.favorite_day.class - end - + assert_equal Date, bob.favorite_day.class assert_instance_of TrueClass, bob.male? assert_kind_of BigDecimal, bob.wealth end @@ -171,10 +160,10 @@ module ActiveRecord if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter) def test_out_of_range_limit_should_raise - assert_raise(ActiveRecordError) { add_column :test_models, :integer_too_big, :integer, :limit => 10 } + assert_raise(ActiveRecordError) { add_column :test_models, :integer_too_big, :integer, limit: 10 } unless current_adapter?(:PostgreSQLAdapter) - assert_raise(ActiveRecordError) { add_column :test_models, :text_too_big, :integer, :limit => 0xfffffffff } + assert_raise(ActiveRecordError) { add_column :test_models, :text_too_big, :text, limit: 0xfffffffff } end end end diff --git a/activerecord/test/cases/migration/column_positioning_test.rb b/activerecord/test/cases/migration/column_positioning_test.rb index 8294da0373..f2162d91b1 100644 --- a/activerecord/test/cases/migration/column_positioning_test.rb +++ b/activerecord/test/cases/migration/column_positioning_test.rb @@ -1,4 +1,4 @@ -require 'cases/helper' +require "cases/helper" module ActiveRecord class Migration @@ -11,7 +11,7 @@ module ActiveRecord @connection = ActiveRecord::Base.connection - connection.create_table :testings, :id => false do |t| + connection.create_table :testings, id: false do |t| t.column :first, :integer t.column :second, :integer t.column :third, :integer @@ -34,20 +34,20 @@ module ActiveRecord end def test_add_column_with_positioning_first - conn.add_column :testings, :new_col, :integer, :first => true + conn.add_column :testings, :new_col, :integer, first: true assert_equal %w(new_col first second third), conn.columns(:testings).map(&:name) end def test_add_column_with_positioning_after - conn.add_column :testings, :new_col, :integer, :after => :first + conn.add_column :testings, :new_col, :integer, after: :first assert_equal %w(first new_col second third), conn.columns(:testings).map(&:name) end def test_change_column_with_positioning - conn.change_column :testings, :second, :integer, :first => true + conn.change_column :testings, :second, :integer, first: true assert_equal %w(second first third), conn.columns(:testings).map(&:name) - conn.change_column :testings, :second, :integer, :after => :third + conn.change_column :testings, :second, :integer, after: :third assert_equal %w(first third second), conn.columns(:testings).map(&:name) end end diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb index fca1cb7e97..55c06da411 100644 --- a/activerecord/test/cases/migration/columns_test.rb +++ b/activerecord/test/cases/migration/columns_test.rb @@ -13,7 +13,7 @@ module ActiveRecord add_column "test_models", "girlfriend", :string TestModel.reset_column_information - TestModel.create :girlfriend => 'bobette' + TestModel.create girlfriend: "bobette" rename_column "test_models", "girlfriend", "exgirlfriend" @@ -28,12 +28,12 @@ module ActiveRecord def test_rename_column_using_symbol_arguments add_column :test_models, :first_name, :string - TestModel.create :first_name => 'foo' + TestModel.create first_name: "foo" rename_column :test_models, :first_name, :nick_name TestModel.reset_column_information - assert TestModel.column_names.include?("nick_name") - assert_equal ['foo'], TestModel.all.map(&:nick_name) + assert_includes TestModel.column_names, "nick_name" + assert_equal ["foo"], TestModel.all.map(&:nick_name) end # FIXME: another integration test. We should decouple this from the @@ -41,25 +41,25 @@ module ActiveRecord def test_rename_column add_column "test_models", "first_name", "string" - TestModel.create :first_name => 'foo' + TestModel.create first_name: "foo" rename_column "test_models", "first_name", "nick_name" TestModel.reset_column_information - assert TestModel.column_names.include?("nick_name") - assert_equal ['foo'], TestModel.all.map(&:nick_name) + assert_includes TestModel.column_names, "nick_name" + assert_equal ["foo"], TestModel.all.map(&:nick_name) end def test_rename_column_preserves_default_value_not_null - add_column 'test_models', 'salary', :integer, :default => 70000 + add_column "test_models", "salary", :integer, default: 70000 default_before = connection.columns("test_models").find { |c| c.name == "salary" }.default - assert_equal '70000', default_before + assert_equal "70000", default_before rename_column "test_models", "salary", "annual_salary" - assert TestModel.column_names.include?("annual_salary") + assert_includes TestModel.column_names, "annual_salary" default_after = connection.columns("test_models").find { |c| c.name == "annual_salary" }.default - assert_equal '70000', default_after + assert_equal "70000", default_after end if current_adapter?(:Mysql2Adapter) @@ -74,30 +74,31 @@ module ActiveRecord def test_rename_nonexistent_column exception = if current_adapter?(:PostgreSQLAdapter, :OracleAdapter) - ActiveRecord::StatementInvalid - else - ActiveRecord::ActiveRecordError - end + ActiveRecord::StatementInvalid + else + ActiveRecord::ActiveRecordError + end + assert_raise(exception) do rename_column "test_models", "nonexistent", "should_fail" end end def test_rename_column_with_sql_reserved_word - add_column 'test_models', 'first_name', :string + add_column "test_models", "first_name", :string rename_column "test_models", "first_name", "group" - assert TestModel.column_names.include?("group") + assert_includes TestModel.column_names, "group" end def test_rename_column_with_an_index add_column "test_models", :hat_name, :string add_index :test_models, :hat_name - assert_equal 1, connection.indexes('test_models').size + assert_equal 1, connection.indexes("test_models").size rename_column "test_models", "hat_name", "name" - assert_equal ['index_test_models_on_name'], connection.indexes('test_models').map(&:name) + assert_equal ["index_test_models_on_name"], connection.indexes("test_models").map(&:name) end def test_rename_column_with_multi_column_index @@ -105,153 +106,153 @@ module ActiveRecord add_column "test_models", :hat_style, :string, limit: 100 add_index "test_models", ["hat_style", "hat_size"], unique: true - rename_column "test_models", "hat_size", 'size' + rename_column "test_models", "hat_size", "size" if current_adapter? :OracleAdapter - assert_equal ['i_test_models_hat_style_size'], connection.indexes('test_models').map(&:name) + assert_equal ["i_test_models_hat_style_size"], connection.indexes("test_models").map(&:name) else - assert_equal ['index_test_models_on_hat_style_and_size'], connection.indexes('test_models').map(&:name) + assert_equal ["index_test_models_on_hat_style_and_size"], connection.indexes("test_models").map(&:name) end - rename_column "test_models", "hat_style", 'style' + rename_column "test_models", "hat_style", "style" if current_adapter? :OracleAdapter - assert_equal ['i_test_models_style_size'], connection.indexes('test_models').map(&:name) + assert_equal ["i_test_models_style_size"], connection.indexes("test_models").map(&:name) else - assert_equal ['index_test_models_on_style_and_size'], connection.indexes('test_models').map(&:name) + assert_equal ["index_test_models_on_style_and_size"], connection.indexes("test_models").map(&:name) end end def test_rename_column_does_not_rename_custom_named_index add_column "test_models", :hat_name, :string - add_index :test_models, :hat_name, :name => 'idx_hat_name' + add_index :test_models, :hat_name, name: "idx_hat_name" - assert_equal 1, connection.indexes('test_models').size + assert_equal 1, connection.indexes("test_models").size rename_column "test_models", "hat_name", "name" - assert_equal ['idx_hat_name'], connection.indexes('test_models').map(&:name) + assert_equal ["idx_hat_name"], connection.indexes("test_models").map(&:name) end def test_remove_column_with_index add_column "test_models", :hat_name, :string add_index :test_models, :hat_name - assert_equal 1, connection.indexes('test_models').size + assert_equal 1, connection.indexes("test_models").size remove_column("test_models", "hat_name") - assert_equal 0, connection.indexes('test_models').size + assert_equal 0, connection.indexes("test_models").size end def test_remove_column_with_multi_column_index add_column "test_models", :hat_size, :integer - add_column "test_models", :hat_style, :string, :limit => 100 - add_index "test_models", ["hat_style", "hat_size"], :unique => true + add_column "test_models", :hat_style, :string, limit: 100 + add_index "test_models", ["hat_style", "hat_size"], unique: true - assert_equal 1, connection.indexes('test_models').size + assert_equal 1, connection.indexes("test_models").size remove_column("test_models", "hat_size") # Every database and/or database adapter has their own behavior # if it drops the multi-column index when any of the indexed columns dropped by remove_column. if current_adapter?(:PostgreSQLAdapter, :OracleAdapter) - assert_equal [], connection.indexes('test_models').map(&:name) + assert_equal [], connection.indexes("test_models").map(&:name) else - assert_equal ['index_test_models_on_hat_style_and_hat_size'], connection.indexes('test_models').map(&:name) + assert_equal ["index_test_models_on_hat_style_and_hat_size"], connection.indexes("test_models").map(&:name) end end def test_change_type_of_not_null_column - change_column "test_models", "updated_at", :datetime, :null => false - change_column "test_models", "updated_at", :datetime, :null => false + change_column "test_models", "updated_at", :datetime, null: false + change_column "test_models", "updated_at", :datetime, null: false TestModel.reset_column_information - assert_equal false, TestModel.columns_hash['updated_at'].null + assert_equal false, TestModel.columns_hash["updated_at"].null ensure - change_column "test_models", "updated_at", :datetime, :null => true + change_column "test_models", "updated_at", :datetime, null: true end def test_change_column_nullability add_column "test_models", "funny", :boolean assert TestModel.columns_hash["funny"].null, "Column 'funny' must initially allow nulls" - change_column "test_models", "funny", :boolean, :null => false, :default => true + change_column "test_models", "funny", :boolean, null: false, default: true TestModel.reset_column_information assert_not TestModel.columns_hash["funny"].null, "Column 'funny' must *not* allow nulls at this point" - change_column "test_models", "funny", :boolean, :null => true + change_column "test_models", "funny", :boolean, null: true TestModel.reset_column_information assert TestModel.columns_hash["funny"].null, "Column 'funny' must allow nulls again at this point" end def test_change_column - add_column 'test_models', 'age', :integer - add_column 'test_models', 'approved', :boolean, :default => true + add_column "test_models", "age", :integer + add_column "test_models", "approved", :boolean, default: true old_columns = connection.columns(TestModel.table_name) - assert old_columns.find { |c| c.name == 'age' && c.type == :integer } + assert old_columns.find { |c| c.name == "age" && c.type == :integer } change_column "test_models", "age", :string new_columns = connection.columns(TestModel.table_name) - assert_not new_columns.find { |c| c.name == 'age' and c.type == :integer } - assert new_columns.find { |c| c.name == 'age' and c.type == :string } + assert_not new_columns.find { |c| c.name == "age" && c.type == :integer } + assert new_columns.find { |c| c.name == "age" && c.type == :string } old_columns = connection.columns(TestModel.table_name) assert old_columns.find { |c| default = connection.lookup_cast_type_from_column(c).deserialize(c.default) - c.name == 'approved' && c.type == :boolean && default == true + c.name == "approved" && c.type == :boolean && default == true } - change_column :test_models, :approved, :boolean, :default => false + change_column :test_models, :approved, :boolean, default: false new_columns = connection.columns(TestModel.table_name) assert_not new_columns.find { |c| default = connection.lookup_cast_type_from_column(c).deserialize(c.default) - c.name == 'approved' and c.type == :boolean and default == true + c.name == "approved" && c.type == :boolean && default == true } assert new_columns.find { |c| default = connection.lookup_cast_type_from_column(c).deserialize(c.default) - c.name == 'approved' and c.type == :boolean and default == false + c.name == "approved" && c.type == :boolean && default == false } - change_column :test_models, :approved, :boolean, :default => true + change_column :test_models, :approved, :boolean, default: true end def test_change_column_with_nil_default - add_column "test_models", "contributor", :boolean, :default => true + add_column "test_models", "contributor", :boolean, default: true assert TestModel.new.contributor? - change_column "test_models", "contributor", :boolean, :default => nil + change_column "test_models", "contributor", :boolean, default: nil TestModel.reset_column_information assert_not TestModel.new.contributor? assert_nil TestModel.new.contributor end def test_change_column_with_new_default - add_column "test_models", "administrator", :boolean, :default => true + add_column "test_models", "administrator", :boolean, default: true assert TestModel.new.administrator? - change_column "test_models", "administrator", :boolean, :default => false + change_column "test_models", "administrator", :boolean, default: false TestModel.reset_column_information assert_not TestModel.new.administrator? end def test_change_column_with_custom_index_name add_column "test_models", "category", :string - add_index :test_models, :category, name: 'test_models_categories_idx' + add_index :test_models, :category, name: "test_models_categories_idx" - assert_equal ['test_models_categories_idx'], connection.indexes('test_models').map(&:name) - change_column "test_models", "category", :string, null: false, default: 'article' + assert_equal ["test_models_categories_idx"], connection.indexes("test_models").map(&:name) + change_column "test_models", "category", :string, null: false, default: "article" - assert_equal ['test_models_categories_idx'], connection.indexes('test_models').map(&:name) + assert_equal ["test_models_categories_idx"], connection.indexes("test_models").map(&:name) end def test_change_column_with_long_index_name - table_name_prefix = 'test_models_' - long_index_name = table_name_prefix + ('x' * (connection.allowed_index_name_length - table_name_prefix.length)) + table_name_prefix = "test_models_" + long_index_name = table_name_prefix + ("x" * (connection.allowed_index_name_length - table_name_prefix.length)) add_column "test_models", "category", :string add_index :test_models, :category, name: long_index_name - change_column "test_models", "category", :string, null: false, default: 'article' + change_column "test_models", "category", :string, null: false, default: "article" - assert_equal [long_index_name], connection.indexes('test_models').map(&:name) + assert_equal [long_index_name], connection.indexes("test_models").map(&:name) end def test_change_column_default @@ -287,7 +288,7 @@ module ActiveRecord remove_column("my_table", "col_two") rename_column("my_table", "col_one", "col_three") - assert_equal 'my_table_id', connection.primary_key('my_table') + assert_equal "my_table_id", connection.primary_key("my_table") ensure connection.drop_table(:my_table) rescue nil end diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb index 1e3529db54..802a969cb7 100644 --- a/activerecord/test/cases/migration/command_recorder_test.rb +++ b/activerecord/test/cases/migration/command_recorder_test.rb @@ -25,26 +25,26 @@ module ActiveRecord recorder = CommandRecorder.new(Class.new { def create_table(name); end }.new) - assert recorder.respond_to?(:create_table), 'respond_to? create_table' + assert recorder.respond_to?(:create_table), "respond_to? create_table" recorder.send(:create_table, :horses) assert_equal [[:create_table, [:horses], nil]], recorder.commands end def test_unknown_commands_delegate recorder = Struct.new(:foo) - recorder = CommandRecorder.new(recorder.new('bar')) - assert_equal 'bar', recorder.foo + recorder = CommandRecorder.new(recorder.new("bar")) + assert_equal "bar", recorder.foo end def test_inverse_of_raise_exception_on_unknown_commands assert_raises(ActiveRecord::IrreversibleMigration) do - @recorder.inverse_of :execute, ['some sql'] + @recorder.inverse_of :execute, ["some sql"] end end def test_irreversible_commands_raise_exception assert_raises(ActiveRecord::IrreversibleMigration) do - @recorder.revert{ @recorder.execute 'some sql' } + @recorder.revert { @recorder.execute "some sql" } end end @@ -58,12 +58,12 @@ module ActiveRecord @recorder.record :create_table, [:hello] @recorder.record :create_table, [:world] end - tables = @recorder.commands.map{|_cmd, args, _block| args} + tables = @recorder.commands.map { |_cmd, args, _block| args } assert_equal [[:world], [:hello]], tables end def test_revert_order - block = Proc.new{|t| t.string :name } + block = Proc.new { |t| t.string :name } @recorder.instance_eval do create_table("apples", &block) revert do @@ -115,13 +115,13 @@ module ActiveRecord end def test_invert_create_table_with_options_and_block - block = Proc.new{} + block = Proc.new {} drop_table = @recorder.inverse_of :create_table, [:people_reminders, id: false], &block assert_equal [:drop_table, [:people_reminders, id: false], block], drop_table end def test_invert_drop_table - block = Proc.new{} + block = Proc.new {} create_table = @recorder.inverse_of :drop_table, [:people_reminders, id: false], &block assert_equal [:create_table, [:people_reminders, id: false], block], create_table end @@ -143,7 +143,7 @@ module ActiveRecord end def test_invert_drop_join_table - block = Proc.new{} + block = Proc.new {} create_join_table = @recorder.inverse_of :drop_join_table, [:musics, :artists, table_name: :catalog], &block assert_equal [:create_join_table, [:musics, :artists, table_name: :catalog], block], create_join_table end @@ -166,7 +166,7 @@ module ActiveRecord def test_invert_change_column_default assert_raises(ActiveRecord::IrreversibleMigration) do - @recorder.inverse_of :change_column_default, [:table, :column, 'default_value'] + @recorder.inverse_of :change_column_default, [:table, :column, "default_value"] end end @@ -203,17 +203,17 @@ module ActiveRecord def test_invert_add_index remove = @recorder.inverse_of :add_index, [:table, [:one, :two]] - assert_equal [:remove_index, [:table, {column: [:one, :two]}]], remove + assert_equal [:remove_index, [:table, { column: [:one, :two] }]], remove end def test_invert_add_index_with_name remove = @recorder.inverse_of :add_index, [:table, [:one, :two], name: "new_index"] - assert_equal [:remove_index, [:table, {name: "new_index"}]], remove + assert_equal [:remove_index, [:table, { name: "new_index" }]], remove end def test_invert_add_index_with_no_options remove = @recorder.inverse_of :add_index, [:table, [:one, :two]] - assert_equal [:remove_index, [:table, {column: [:one, :two]}]], remove + assert_equal [:remove_index, [:table, { column: [:one, :two] }]], remove end def test_invert_remove_index @@ -222,17 +222,17 @@ module ActiveRecord end def test_invert_remove_index_with_column - add = @recorder.inverse_of :remove_index, [:table, {column: [:one, :two], options: true}] + add = @recorder.inverse_of :remove_index, [:table, { column: [:one, :two], options: true }] assert_equal [:add_index, [:table, [:one, :two], options: true]], add end def test_invert_remove_index_with_name - add = @recorder.inverse_of :remove_index, [:table, {column: [:one, :two], name: "new_index"}] + add = @recorder.inverse_of :remove_index, [:table, { column: [:one, :two], name: "new_index" }] assert_equal [:add_index, [:table, [:one, :two], name: "new_index"]], add end def test_invert_remove_index_with_no_special_options - add = @recorder.inverse_of :remove_index, [:table, {column: [:one, :two]}] + add = @recorder.inverse_of :remove_index, [:table, { column: [:one, :two] }] assert_equal [:add_index, [:table, [:one, :two], {}]], add end @@ -254,7 +254,7 @@ module ActiveRecord def test_invert_remove_timestamps add = @recorder.inverse_of :remove_timestamps, [:table, { null: true }] - assert_equal [:add_timestamps, [:table, {null: true }], nil], add + assert_equal [:add_timestamps, [:table, { null: true }], nil], add end def test_invert_add_reference @@ -283,13 +283,13 @@ module ActiveRecord end def test_invert_enable_extension - disable = @recorder.inverse_of :enable_extension, ['uuid-ossp'] - assert_equal [:disable_extension, ['uuid-ossp'], nil], disable + disable = @recorder.inverse_of :enable_extension, ["uuid-ossp"] + assert_equal [:disable_extension, ["uuid-ossp"], nil], disable end def test_invert_disable_extension - enable = @recorder.inverse_of :disable_extension, ['uuid-ossp'] - assert_equal [:enable_extension, ['uuid-ossp'], nil], enable + enable = @recorder.inverse_of :disable_extension, ["uuid-ossp"] + assert_equal [:enable_extension, ["uuid-ossp"], nil], enable end def test_invert_add_foreign_key diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb index 6d5b6243db..0a4b604601 100644 --- a/activerecord/test/cases/migration/compatibility_test.rb +++ b/activerecord/test/cases/migration/compatibility_test.rb @@ -1,4 +1,4 @@ -require 'cases/helper' +require "cases/helper" module ActiveRecord class Migration @@ -13,19 +13,19 @@ module ActiveRecord ActiveRecord::Migration.verbose = false connection.create_table :testings do |t| - t.column :foo, :string, :limit => 100 - t.column :bar, :string, :limit => 100 + t.column :foo, :string, limit: 100 + t.column :bar, :string, limit: 100 end end teardown do connection.drop_table :testings rescue nil ActiveRecord::Migration.verbose = @verbose_was - ActiveRecord::SchemaMigration.delete_all + ActiveRecord::SchemaMigration.delete_all rescue nil end def test_migration_doesnt_remove_named_index - connection.add_index :testings, :foo, :name => "custom_index_name" + connection.add_index :testings, :foo, name: "custom_index_name" migration = Class.new(ActiveRecord::Migration[4.2]) { def version; 101 end @@ -83,8 +83,8 @@ module ActiveRecord ActiveRecord::Migrator.new(:up, [migration]).migrate - assert connection.columns(:more_testings).find { |c| c.name == 'created_at' }.null - assert connection.columns(:more_testings).find { |c| c.name == 'updated_at' }.null + assert connection.columns(:more_testings).find { |c| c.name == "created_at" }.null + assert connection.columns(:more_testings).find { |c| c.name == "updated_at" }.null ensure connection.drop_table :more_testings rescue nil end @@ -98,8 +98,20 @@ module ActiveRecord ActiveRecord::Migrator.new(:up, [migration]).migrate - assert connection.columns(:testings).find { |c| c.name == 'created_at' }.null - assert connection.columns(:testings).find { |c| c.name == 'updated_at' }.null + assert connection.columns(:testings).find { |c| c.name == "created_at" }.null + 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 + } + + assert_deprecated do + migration.migrate :up + end 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 0a7b57455c..f14d68f12b 100644 --- a/activerecord/test/cases/migration/create_join_table_test.rb +++ b/activerecord/test/cases/migration/create_join_table_test.rb @@ -1,4 +1,4 @@ -require 'cases/helper' +require "cases/helper" module ActiveRecord class Migration @@ -31,13 +31,13 @@ module ActiveRecord end def test_create_join_table_with_strings - connection.create_join_table 'artists', 'musics' + connection.create_join_table "artists", "musics" assert_equal %w(artist_id music_id), connection.columns(:artists_musics).map(&:name).sort end def test_create_join_table_with_symbol_and_string - connection.create_join_table :artists, 'musics' + connection.create_join_table :artists, "musics" assert_equal %w(artist_id music_id), connection.columns(:artists_musics).map(&:name).sort end @@ -55,13 +55,13 @@ module ActiveRecord end def test_create_join_table_with_the_table_name_as_string - connection.create_join_table :artists, :musics, table_name: 'catalog' + connection.create_join_table :artists, :musics, table_name: "catalog" assert_equal %w(artist_id music_id), connection.columns(:catalog).map(&:name).sort end def test_create_join_table_with_column_options - connection.create_join_table :artists, :musics, column_options: {null: true} + connection.create_join_table :artists, :musics, column_options: { null: true } assert_equal [true, true], connection.columns(:artists_musics).map(&:null) end @@ -84,51 +84,58 @@ module ActiveRecord connection.create_join_table :artists, :musics connection.drop_join_table :artists, :musics - ActiveSupport::Deprecation.silence { assert !connection.table_exists?('artists_musics') } + ActiveSupport::Deprecation.silence { 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' + connection.drop_join_table "artists", "musics" - ActiveSupport::Deprecation.silence { assert !connection.table_exists?('artists_musics') } + ActiveSupport::Deprecation.silence { 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') } + ActiveSupport::Deprecation.silence { 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') } + ActiveSupport::Deprecation.silence { 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' + 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') } + ActiveSupport::Deprecation.silence { 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} + 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') } + ActiveSupport::Deprecation.silence { 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') } + connection.create_join_table "audio_artists", "audio_musics" + ActiveSupport::Deprecation.silence { 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" } + 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" } + end + end + + if current_adapter?(:PostgreSQLAdapter) + def test_create_join_table_with_uuid + connection.create_join_table :artists, :musics, column_options: { type: :uuid } + assert_equal [:uuid, :uuid], connection.columns(:artists_musics).map(&:type) end end diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb index 01162dcefe..cab2069754 100644 --- a/activerecord/test/cases/migration/foreign_key_test.rb +++ b/activerecord/test/cases/migration/foreign_key_test.rb @@ -1,304 +1,316 @@ -require 'cases/helper' -require 'support/ddl_helper' -require 'support/schema_dumping_helper' +require "cases/helper" +require "support/ddl_helper" +require "support/schema_dumping_helper" if ActiveRecord::Base.connection.supports_foreign_keys? -module ActiveRecord - class Migration - class ForeignKeyTest < ActiveRecord::TestCase - include DdlHelper - include SchemaDumpingHelper - include ActiveSupport::Testing::Stream - - class Rocket < ActiveRecord::Base - end - - class Astronaut < ActiveRecord::Base - end - - setup do - @connection = ActiveRecord::Base.connection - @connection.create_table "rockets", force: true do |t| - t.string :name + module ActiveRecord + class Migration + class ForeignKeyTest < ActiveRecord::TestCase + include DdlHelper + include SchemaDumpingHelper + include ActiveSupport::Testing::Stream + + class Rocket < ActiveRecord::Base end - @connection.create_table "astronauts", force: true do |t| - t.string :name - t.references :rocket + class Astronaut < ActiveRecord::Base end - end - teardown do - if defined?(@connection) - @connection.drop_table "astronauts", if_exists: true - @connection.drop_table "rockets", if_exists: true - end - end - - def test_foreign_keys - foreign_keys = @connection.foreign_keys("fk_test_has_fk") - assert_equal 1, foreign_keys.size + setup do + @connection = ActiveRecord::Base.connection + @connection.create_table "rockets", force: true do |t| + t.string :name + end - 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 - end + @connection.create_table "astronauts", force: true do |t| + t.string :name + t.references :rocket + end + end - def test_add_foreign_key_inferes_column - @connection.add_foreign_key :astronauts, :rockets + teardown do + if defined?(@connection) + @connection.drop_table "astronauts", if_exists: true + @connection.drop_table "rockets", if_exists: true + end + end - foreign_keys = @connection.foreign_keys("astronauts") - assert_equal 1, foreign_keys.size + def test_foreign_keys + foreign_keys = @connection.foreign_keys("fk_test_has_fk") + assert_equal 1, foreign_keys.size - fk = foreign_keys.first - assert_equal "astronauts", fk.from_table - assert_equal "rockets", fk.to_table - assert_equal "rocket_id", fk.column - assert_equal "id", fk.primary_key - assert_equal("fk_rails_78146ddd2e", fk.name) - end + 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 + end - def test_add_foreign_key_with_column - @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id" + def test_add_foreign_key_inferes_column + @connection.add_foreign_key :astronauts, :rockets - foreign_keys = @connection.foreign_keys("astronauts") - assert_equal 1, foreign_keys.size + foreign_keys = @connection.foreign_keys("astronauts") + assert_equal 1, foreign_keys.size - fk = foreign_keys.first - assert_equal "astronauts", fk.from_table - assert_equal "rockets", fk.to_table - assert_equal "rocket_id", fk.column - assert_equal "id", fk.primary_key - assert_equal("fk_rails_78146ddd2e", fk.name) - end + fk = foreign_keys.first + assert_equal "astronauts", fk.from_table + assert_equal "rockets", fk.to_table + assert_equal "rocket_id", fk.column + assert_equal "id", fk.primary_key + assert_equal("fk_rails_78146ddd2e", fk.name) + end - def test_add_foreign_key_with_non_standard_primary_key - with_example_table @connection, "space_shuttles", "pk integer PRIMARY KEY" do - @connection.add_foreign_key(:astronauts, :space_shuttles, - column: "rocket_id", primary_key: "pk", name: "custom_pk") + def test_add_foreign_key_with_column + @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id" foreign_keys = @connection.foreign_keys("astronauts") assert_equal 1, foreign_keys.size fk = foreign_keys.first assert_equal "astronauts", fk.from_table - assert_equal "space_shuttles", fk.to_table - assert_equal "pk", fk.primary_key - - @connection.remove_foreign_key :astronauts, name: "custom_pk" + assert_equal "rockets", fk.to_table + assert_equal "rocket_id", fk.column + assert_equal "id", fk.primary_key + assert_equal("fk_rails_78146ddd2e", fk.name) end - end - def test_add_on_delete_restrict_foreign_key - @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_delete: :restrict + def test_add_foreign_key_with_non_standard_primary_key + with_example_table @connection, "space_shuttles", "pk integer PRIMARY KEY" do + @connection.add_foreign_key(:astronauts, :space_shuttles, + column: "rocket_id", primary_key: "pk", name: "custom_pk") + + foreign_keys = @connection.foreign_keys("astronauts") + assert_equal 1, foreign_keys.size - foreign_keys = @connection.foreign_keys("astronauts") - assert_equal 1, foreign_keys.size + fk = foreign_keys.first + assert_equal "astronauts", fk.from_table + assert_equal "space_shuttles", fk.to_table + assert_equal "pk", fk.primary_key - fk = foreign_keys.first - if current_adapter?(:Mysql2Adapter) - # ON DELETE RESTRICT is the default on MySQL - assert_equal nil, fk.on_delete - else - assert_equal :restrict, fk.on_delete + @connection.remove_foreign_key :astronauts, name: "custom_pk" + end end - end - def test_add_on_delete_cascade_foreign_key - @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_delete: :cascade + def test_add_on_delete_restrict_foreign_key + @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_delete: :restrict + + foreign_keys = @connection.foreign_keys("astronauts") + assert_equal 1, foreign_keys.size + + fk = foreign_keys.first + if current_adapter?(:Mysql2Adapter) + # ON DELETE RESTRICT is the default on MySQL + assert_equal nil, fk.on_delete + else + assert_equal :restrict, fk.on_delete + end + end - foreign_keys = @connection.foreign_keys("astronauts") - assert_equal 1, foreign_keys.size + def test_add_on_delete_cascade_foreign_key + @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_delete: :cascade - fk = foreign_keys.first - assert_equal :cascade, fk.on_delete - end + foreign_keys = @connection.foreign_keys("astronauts") + assert_equal 1, foreign_keys.size - def test_add_on_delete_nullify_foreign_key - @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_delete: :nullify + fk = foreign_keys.first + assert_equal :cascade, fk.on_delete + end - foreign_keys = @connection.foreign_keys("astronauts") - assert_equal 1, foreign_keys.size + def test_add_on_delete_nullify_foreign_key + @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_delete: :nullify - fk = foreign_keys.first - assert_equal :nullify, fk.on_delete - end + foreign_keys = @connection.foreign_keys("astronauts") + assert_equal 1, foreign_keys.size - def test_on_update_and_on_delete_raises_with_invalid_values - assert_raises ArgumentError do - @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_delete: :invalid + fk = foreign_keys.first + assert_equal :nullify, fk.on_delete end - assert_raises ArgumentError do - @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_update: :invalid + def test_on_update_and_on_delete_raises_with_invalid_values + assert_raises ArgumentError do + @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_delete: :invalid + end + + assert_raises ArgumentError do + @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_update: :invalid + end end - end - def test_add_foreign_key_with_on_update - @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_update: :nullify + def test_add_foreign_key_with_on_update + @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_update: :nullify - foreign_keys = @connection.foreign_keys("astronauts") - assert_equal 1, foreign_keys.size + foreign_keys = @connection.foreign_keys("astronauts") + assert_equal 1, foreign_keys.size - fk = foreign_keys.first - assert_equal :nullify, fk.on_update - end + fk = foreign_keys.first + assert_equal :nullify, fk.on_update + end - def test_foreign_key_exists - @connection.add_foreign_key :astronauts, :rockets + def test_foreign_key_exists + @connection.add_foreign_key :astronauts, :rockets - assert @connection.foreign_key_exists?(:astronauts, :rockets) - assert_not @connection.foreign_key_exists?(:astronauts, :stars) - end + assert @connection.foreign_key_exists?(:astronauts, :rockets) + assert_not @connection.foreign_key_exists?(:astronauts, :stars) + end - def test_foreign_key_exists_by_column - @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id" + def test_foreign_key_exists_by_column + @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id" - assert @connection.foreign_key_exists?(:astronauts, column: "rocket_id") - assert_not @connection.foreign_key_exists?(:astronauts, column: "star_id") - end + assert @connection.foreign_key_exists?(:astronauts, column: "rocket_id") + assert_not @connection.foreign_key_exists?(:astronauts, column: "star_id") + end - def test_foreign_key_exists_by_name - @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk" + def test_foreign_key_exists_by_name + @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk" - assert @connection.foreign_key_exists?(:astronauts, name: "fancy_named_fk") - assert_not @connection.foreign_key_exists?(:astronauts, name: "other_fancy_named_fk") - end + assert @connection.foreign_key_exists?(:astronauts, name: "fancy_named_fk") + assert_not @connection.foreign_key_exists?(:astronauts, name: "other_fancy_named_fk") + end - def test_remove_foreign_key_inferes_column - @connection.add_foreign_key :astronauts, :rockets + def test_remove_foreign_key_inferes_column + @connection.add_foreign_key :astronauts, :rockets - assert_equal 1, @connection.foreign_keys("astronauts").size - @connection.remove_foreign_key :astronauts, :rockets - assert_equal [], @connection.foreign_keys("astronauts") - end + assert_equal 1, @connection.foreign_keys("astronauts").size + @connection.remove_foreign_key :astronauts, :rockets + assert_equal [], @connection.foreign_keys("astronauts") + end - def test_remove_foreign_key_by_column - @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id" + def test_remove_foreign_key_by_column + @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id" - assert_equal 1, @connection.foreign_keys("astronauts").size - @connection.remove_foreign_key :astronauts, column: "rocket_id" - assert_equal [], @connection.foreign_keys("astronauts") - end + assert_equal 1, @connection.foreign_keys("astronauts").size + @connection.remove_foreign_key :astronauts, column: "rocket_id" + assert_equal [], @connection.foreign_keys("astronauts") + end - def test_remove_foreign_key_by_symbol_column - @connection.add_foreign_key :astronauts, :rockets, column: :rocket_id + def test_remove_foreign_key_by_symbol_column + @connection.add_foreign_key :astronauts, :rockets, column: :rocket_id - assert_equal 1, @connection.foreign_keys("astronauts").size - @connection.remove_foreign_key :astronauts, column: :rocket_id - assert_equal [], @connection.foreign_keys("astronauts") - end + assert_equal 1, @connection.foreign_keys("astronauts").size + @connection.remove_foreign_key :astronauts, column: :rocket_id + assert_equal [], @connection.foreign_keys("astronauts") + end - def test_remove_foreign_key_by_name - @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk" + def test_remove_foreign_key_by_name + @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk" - assert_equal 1, @connection.foreign_keys("astronauts").size - @connection.remove_foreign_key :astronauts, name: "fancy_named_fk" - assert_equal [], @connection.foreign_keys("astronauts") - end + assert_equal 1, @connection.foreign_keys("astronauts").size + @connection.remove_foreign_key :astronauts, name: "fancy_named_fk" + assert_equal [], @connection.foreign_keys("astronauts") + end - def test_remove_foreign_non_existing_foreign_key_raises - assert_raises ArgumentError do - @connection.remove_foreign_key :astronauts, :rockets + def test_remove_foreign_non_existing_foreign_key_raises + assert_raises ArgumentError do + @connection.remove_foreign_key :astronauts, :rockets + end end - end - def test_schema_dumping - @connection.add_foreign_key :astronauts, :rockets - output = dump_table_schema "astronauts" - assert_match %r{\s+add_foreign_key "astronauts", "rockets"$}, output - end + def test_schema_dumping + @connection.add_foreign_key :astronauts, :rockets + output = dump_table_schema "astronauts" + assert_match %r{\s+add_foreign_key "astronauts", "rockets"$}, output + end - def test_schema_dumping_with_options - output = dump_table_schema "fk_test_has_fk" - assert_match %r{\s+add_foreign_key "fk_test_has_fk", "fk_test_has_pk", column: "fk_id", primary_key: "pk_id", name: "fk_name"$}, output - end + def test_schema_dumping_with_options + output = dump_table_schema "fk_test_has_fk" + assert_match %r{\s+add_foreign_key "fk_test_has_fk", "fk_test_has_pk", column: "fk_id", primary_key: "pk_id", name: "fk_name"$}, output + end - def test_schema_dumping_on_delete_and_on_update_options - @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_delete: :nullify, on_update: :cascade + def test_schema_dumping_on_delete_and_on_update_options + @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_delete: :nullify, on_update: :cascade - output = dump_table_schema "astronauts" - assert_match %r{\s+add_foreign_key "astronauts",.+on_update: :cascade,.+on_delete: :nullify$}, output - end + output = dump_table_schema "astronauts" + assert_match %r{\s+add_foreign_key "astronauts",.+on_update: :cascade,.+on_delete: :nullify$}, output + end + + class CreateCitiesAndHousesMigration < ActiveRecord::Migration::Current + def change + create_table("cities") { |t| } - class CreateCitiesAndHousesMigration < ActiveRecord::Migration::Current - def change - create_table("cities") { |t| } + create_table("houses") do |t| + t.column :city_id, :integer + end + add_foreign_key :houses, :cities, column: "city_id" - create_table("houses") do |t| - t.column :city_id, :integer + # remove and re-add to test that schema is updated and not accidentally cached + remove_foreign_key :houses, :cities + add_foreign_key :houses, :cities, column: "city_id", on_delete: :cascade end - add_foreign_key :houses, :cities, column: "city_id" end - end - def test_add_foreign_key_is_reversible - migration = CreateCitiesAndHousesMigration.new - silence_stream($stdout) { migration.migrate(:up) } - assert_equal 1, @connection.foreign_keys("houses").size - ensure - silence_stream($stdout) { migration.migrate(:down) } - end + def test_add_foreign_key_is_reversible + migration = CreateCitiesAndHousesMigration.new + silence_stream($stdout) { migration.migrate(:up) } + assert_equal 1, @connection.foreign_keys("houses").size + ensure + silence_stream($stdout) { migration.migrate(:down) } + end + + def test_foreign_key_constraint_is_not_cached_incorrectly + migration = CreateCitiesAndHousesMigration.new + silence_stream($stdout) { migration.migrate(:up) } + output = dump_table_schema "houses" + assert_match %r{\s+add_foreign_key "houses",.+on_delete: :cascade$}, output + ensure + silence_stream($stdout) { migration.migrate(:down) } + end - class CreateSchoolsAndClassesMigration < ActiveRecord::Migration::Current - def change - create_table(:schools) + class CreateSchoolsAndClassesMigration < ActiveRecord::Migration::Current + def change + create_table(:schools) - create_table(:classes) do |t| - t.column :school_id, :integer + create_table(:classes) do |t| + t.column :school_id, :integer + end + add_foreign_key :classes, :schools end - add_foreign_key :classes, :schools end - end - def test_add_foreign_key_with_prefix - ActiveRecord::Base.table_name_prefix = 'p_' - migration = CreateSchoolsAndClassesMigration.new - silence_stream($stdout) { migration.migrate(:up) } - assert_equal 1, @connection.foreign_keys("p_classes").size - ensure - silence_stream($stdout) { migration.migrate(:down) } - ActiveRecord::Base.table_name_prefix = nil - end + def test_add_foreign_key_with_prefix + ActiveRecord::Base.table_name_prefix = "p_" + migration = CreateSchoolsAndClassesMigration.new + silence_stream($stdout) { migration.migrate(:up) } + assert_equal 1, @connection.foreign_keys("p_classes").size + ensure + silence_stream($stdout) { migration.migrate(:down) } + ActiveRecord::Base.table_name_prefix = nil + end - def test_add_foreign_key_with_suffix - ActiveRecord::Base.table_name_suffix = '_s' - migration = CreateSchoolsAndClassesMigration.new - silence_stream($stdout) { migration.migrate(:up) } - assert_equal 1, @connection.foreign_keys("classes_s").size - ensure - silence_stream($stdout) { migration.migrate(:down) } - ActiveRecord::Base.table_name_suffix = nil + def test_add_foreign_key_with_suffix + ActiveRecord::Base.table_name_suffix = "_s" + migration = CreateSchoolsAndClassesMigration.new + silence_stream($stdout) { migration.migrate(:up) } + assert_equal 1, @connection.foreign_keys("classes_s").size + ensure + silence_stream($stdout) { migration.migrate(:down) } + ActiveRecord::Base.table_name_suffix = nil + end end - end end -end else -module ActiveRecord - class Migration - class NoForeignKeySupportTest < ActiveRecord::TestCase - setup do - @connection = ActiveRecord::Base.connection - end + module ActiveRecord + class Migration + class NoForeignKeySupportTest < ActiveRecord::TestCase + setup do + @connection = ActiveRecord::Base.connection + end - def test_add_foreign_key_should_be_noop - @connection.add_foreign_key :clubs, :categories - end + def test_add_foreign_key_should_be_noop + @connection.add_foreign_key :clubs, :categories + end - def test_remove_foreign_key_should_be_noop - @connection.remove_foreign_key :clubs, :categories - end + def test_remove_foreign_key_should_be_noop + @connection.remove_foreign_key :clubs, :categories + end - def test_foreign_keys_should_raise_not_implemented - assert_raises NotImplementedError do - @connection.foreign_keys("clubs") + def test_foreign_keys_should_raise_not_implemented + assert_raises NotImplementedError do + @connection.foreign_keys("clubs") + end end end end end end -end diff --git a/activerecord/test/cases/migration/helper.rb b/activerecord/test/cases/migration/helper.rb index ad85684c0b..9c0fa7339d 100644 --- a/activerecord/test/cases/migration/helper.rb +++ b/activerecord/test/cases/migration/helper.rb @@ -33,7 +33,7 @@ module ActiveRecord private - delegate(*CONNECTION_METHODS, to: :connection) + delegate(*CONNECTION_METHODS, to: :connection) end end end diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb index 5abd37bfa2..0f975026b8 100644 --- a/activerecord/test/cases/migration/index_test.rb +++ b/activerecord/test/cases/migration/index_test.rb @@ -1,4 +1,4 @@ -require 'cases/helper' +require "cases/helper" module ActiveRecord class Migration @@ -11,12 +11,12 @@ module ActiveRecord @table_name = :testings connection.create_table table_name do |t| - t.column :foo, :string, :limit => 100 - t.column :bar, :string, :limit => 100 + t.column :foo, :string, limit: 100 + t.column :bar, :string, limit: 100 t.string :first_name - t.string :last_name, :limit => 100 - t.string :key, :limit => 100 + t.string :last_name, limit: 100 + t.string :key, limit: 100 t.boolean :administrator end end @@ -28,32 +28,31 @@ module ActiveRecord def test_rename_index # keep the names short to make Oracle and similar behave - connection.add_index(table_name, [:foo], :name => 'old_idx') - connection.rename_index(table_name, 'old_idx', 'new_idx') + 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_not connection.index_name_exists?(table_name, "old_idx", false) + assert connection.index_name_exists?(table_name, "new_idx", true) end def test_rename_index_too_long - too_long_index_name = good_index_name + 'x' + too_long_index_name = good_index_name + "x" # keep the names short to make Oracle and similar behave - connection.add_index(table_name, [:foo], :name => 'old_idx') + connection.add_index(table_name, [:foo], name: "old_idx") e = assert_raises(ArgumentError) { - connection.rename_index(table_name, 'old_idx', too_long_index_name) + connection.rename_index(table_name, "old_idx", too_long_index_name) } 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", false) end - def test_double_add_index - connection.add_index(table_name, [:foo], :name => 'some_idx') + connection.add_index(table_name, [:foo], name: "some_idx") assert_raises(ArgumentError) { - connection.add_index(table_name, [:foo], :name => 'some_idx') + connection.add_index(table_name, [:foo], name: "some_idx") } end @@ -69,7 +68,7 @@ module ActiveRecord end def test_add_index_does_not_accept_too_long_index_names - too_long_index_name = good_index_name + 'x' + too_long_index_name = good_index_name + "x" e = assert_raises(ArgumentError) { connection.add_index(table_name, "foo", name: too_long_index_name) @@ -77,11 +76,11 @@ 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) - connection.add_index(table_name, "foo", :name => good_index_name) + connection.add_index(table_name, "foo", name: good_index_name) end def test_internal_index_with_name_matching_database_limit - good_index_name = 'x' * connection.index_name_length + 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) @@ -89,11 +88,11 @@ module ActiveRecord end def test_index_symbol_names - connection.add_index table_name, :foo, :name => :symbol_index_name - assert connection.index_exists?(table_name, :foo, :name => :symbol_index_name) + connection.add_index table_name, :foo, name: :symbol_index_name + assert connection.index_exists?(table_name, :foo, name: :symbol_index_name) - connection.remove_index table_name, :name => :symbol_index_name - assert_not connection.index_exists?(table_name, :foo, :name => :symbol_index_name) + connection.remove_index table_name, name: :symbol_index_name + assert_not connection.index_exists?(table_name, :foo, name: :symbol_index_name) end def test_index_exists @@ -122,21 +121,21 @@ module ActiveRecord end def test_unique_index_exists - connection.add_index :testings, :foo, :unique => true + connection.add_index :testings, :foo, unique: true - assert connection.index_exists?(:testings, :foo, :unique => true) + assert connection.index_exists?(:testings, :foo, unique: true) end def test_named_index_exists - connection.add_index :testings, :foo, :name => "custom_index_name" + connection.add_index :testings, :foo, name: "custom_index_name" assert connection.index_exists?(:testings, :foo) - assert connection.index_exists?(:testings, :foo, :name => "custom_index_name") - assert !connection.index_exists?(:testings, :foo, :name => "other_index_name") + assert connection.index_exists?(:testings, :foo, name: "custom_index_name") + assert !connection.index_exists?(:testings, :foo, name: "other_index_name") end def test_remove_named_index - connection.add_index :testings, :foo, :name => "custom_index_name" + connection.add_index :testings, :foo, name: "custom_index_name" assert connection.index_exists?(:testings, :foo) connection.remove_index :testings, :foo @@ -144,7 +143,7 @@ module ActiveRecord end def test_add_index_attribute_length_limit - connection.add_index :testings, [:foo, :bar], :length => {:foo => 10, :bar => nil} + connection.add_index :testings, [:foo, :bar], length: { foo: 10, bar: nil } assert connection.index_exists?(:testings, [:foo, :bar]) end @@ -154,53 +153,53 @@ module ActiveRecord connection.remove_index("testings", "last_name") connection.add_index("testings", ["last_name", "first_name"]) - connection.remove_index("testings", :column => ["last_name", "first_name"]) + connection.remove_index("testings", column: ["last_name", "first_name"]) # Oracle adapter cannot have specified index name larger than 30 characters # Oracle adapter is shortening index name when just column list is given unless current_adapter?(:OracleAdapter) connection.add_index("testings", ["last_name", "first_name"]) - connection.remove_index("testings", :name => :index_testings_on_last_name_and_first_name) + connection.remove_index("testings", name: :index_testings_on_last_name_and_first_name) connection.add_index("testings", ["last_name", "first_name"]) connection.remove_index("testings", "last_name_and_first_name") end connection.add_index("testings", ["last_name", "first_name"]) connection.remove_index("testings", ["last_name", "first_name"]) - connection.add_index("testings", ["last_name"], :length => 10) + connection.add_index("testings", ["last_name"], length: 10) connection.remove_index("testings", "last_name") - connection.add_index("testings", ["last_name"], :length => {:last_name => 10}) + connection.add_index("testings", ["last_name"], length: { last_name: 10 }) connection.remove_index("testings", ["last_name"]) - connection.add_index("testings", ["last_name", "first_name"], :length => 10) + connection.add_index("testings", ["last_name", "first_name"], length: 10) connection.remove_index("testings", ["last_name", "first_name"]) - connection.add_index("testings", ["last_name", "first_name"], :length => {:last_name => 10, :first_name => 20}) + connection.add_index("testings", ["last_name", "first_name"], length: { last_name: 10, first_name: 20 }) connection.remove_index("testings", ["last_name", "first_name"]) - connection.add_index("testings", ["key"], :name => "key_idx", :unique => true) - connection.remove_index("testings", :name => "key_idx", :unique => true) + connection.add_index("testings", ["key"], name: "key_idx", unique: true) + connection.remove_index("testings", name: "key_idx", unique: true) - connection.add_index("testings", %w(last_name first_name administrator), :name => "named_admin") - connection.remove_index("testings", :name => "named_admin") + connection.add_index("testings", %w(last_name first_name administrator), name: "named_admin") + connection.remove_index("testings", name: "named_admin") # Selected adapters support index sort order if current_adapter?(:SQLite3Adapter, :Mysql2Adapter, :PostgreSQLAdapter) - connection.add_index("testings", ["last_name"], :order => {:last_name => :desc}) + connection.add_index("testings", ["last_name"], order: { last_name: :desc }) connection.remove_index("testings", ["last_name"]) - connection.add_index("testings", ["last_name", "first_name"], :order => {:last_name => :desc}) + connection.add_index("testings", ["last_name", "first_name"], order: { last_name: :desc }) connection.remove_index("testings", ["last_name", "first_name"]) - connection.add_index("testings", ["last_name", "first_name"], :order => {:last_name => :desc, :first_name => :asc}) + connection.add_index("testings", ["last_name", "first_name"], order: { last_name: :desc, first_name: :asc }) connection.remove_index("testings", ["last_name", "first_name"]) - connection.add_index("testings", ["last_name", "first_name"], :order => :desc) + connection.add_index("testings", ["last_name", "first_name"], order: :desc) connection.remove_index("testings", ["last_name", "first_name"]) end end if current_adapter?(:PostgreSQLAdapter) def test_add_partial_index - connection.add_index("testings", "last_name", :where => "first_name = 'john doe'") + connection.add_index("testings", "last_name", where: "first_name = 'john doe'") assert connection.index_exists?("testings", "last_name") connection.remove_index("testings", "last_name") @@ -210,9 +209,8 @@ module ActiveRecord private def good_index_name - 'x' * connection.allowed_index_name_length + "x" * connection.allowed_index_name_length end - end end end diff --git a/activerecord/test/cases/migration/logger_test.rb b/activerecord/test/cases/migration/logger_test.rb index bf6e684887..3d7c7ad469 100644 --- a/activerecord/test/cases/migration/logger_test.rb +++ b/activerecord/test/cases/migration/logger_test.rb @@ -8,7 +8,7 @@ module ActiveRecord Migration = Struct.new(:name, :version) do def disable_ddl_transaction; false end - def migrate direction + def migrate(direction) # do nothing end end @@ -26,7 +26,7 @@ module ActiveRecord def test_migration_should_be_run_without_logger previous_logger = ActiveRecord::Base.logger ActiveRecord::Base.logger = nil - migrations = [Migration.new('a', 1), Migration.new('b', 2), Migration.new('c', 3)] + migrations = [Migration.new("a", 1), Migration.new("b", 2), Migration.new("c", 3)] ActiveRecord::Migrator.new(:up, migrations).migrate ensure ActiveRecord::Base.logger = previous_logger diff --git a/activerecord/test/cases/migration/pending_migrations_test.rb b/activerecord/test/cases/migration/pending_migrations_test.rb index 4f5589f32a..61f5a061b0 100644 --- a/activerecord/test/cases/migration/pending_migrations_test.rb +++ b/activerecord/test/cases/migration/pending_migrations_test.rb @@ -1,4 +1,4 @@ -require 'cases/helper' +require "cases/helper" module ActiveRecord class Migration diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb index b01415afb2..528811db49 100644 --- a/activerecord/test/cases/migration/references_foreign_key_test.rb +++ b/activerecord/test/cases/migration/references_foreign_key_test.rb @@ -1,170 +1,216 @@ -require 'cases/helper' +require "cases/helper" 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 + 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 + teardown do + @connection.drop_table "testings", if_exists: true + @connection.drop_table "testing_parents", if_exists: true + end - test "foreign keys can be created with the table" do - @connection.create_table :testings do |t| - t.references :testing_parent, foreign_key: true + test "foreign keys can be created with the table" do + @connection.create_table :testings do |t| + t.references :testing_parent, foreign_key: true + end + + fk = @connection.foreign_keys("testings").first + assert_equal "testings", fk.from_table + assert_equal "testing_parents", fk.to_table end - fk = @connection.foreign_keys("testings").first - assert_equal "testings", fk.from_table - assert_equal "testing_parents", fk.to_table - end + test "no foreign key is created by default" do + @connection.create_table :testings do |t| + t.references :testing_parent + end - test "no foreign key is created by default" do - @connection.create_table :testings do |t| - t.references :testing_parent + assert_equal [], @connection.foreign_keys("testings") end - assert_equal [], @connection.foreign_keys("testings") - end + test "foreign keys can be created in one query when index is not added" do + assert_queries(1) do + @connection.create_table :testings do |t| + t.references :testing_parent, foreign_key: true, index: false + end + end + end - test "foreign keys can be created in one query when index is not added" do - assert_queries(1) do + test "options hash can be passed" do + @connection.change_table :testing_parents do |t| + t.integer :other_id + t.index :other_id, unique: true + end @connection.create_table :testings do |t| - t.references :testing_parent, foreign_key: true, index: false + t.references :testing_parent, foreign_key: { primary_key: :other_id } end + + fk = @connection.foreign_keys("testings").find { |k| k.to_table == "testing_parents" } + assert_equal "other_id", fk.primary_key end - end - test "options hash can be passed" do - @connection.change_table :testing_parents do |t| - t.integer :other_id - t.index :other_id, unique: true + test "to_table option can be passed" do + @connection.create_table :testings do |t| + t.references :parent, foreign_key: { to_table: :testing_parents } + end + fks = @connection.foreign_keys("testings") + assert_equal([["testings", "testing_parents", "parent_id"]], + fks.map { |fk| [fk.from_table, fk.to_table, fk.column] }) end - @connection.create_table :testings do |t| - t.references :testing_parent, foreign_key: { primary_key: :other_id } + + test "foreign keys cannot be added to polymorphic relations when creating the table" do + @connection.create_table :testings do |t| + assert_raises(ArgumentError) do + t.references :testing_parent, polymorphic: true, foreign_key: true + end + end end - fk = @connection.foreign_keys("testings").find { |k| k.to_table == "testing_parents" } - assert_equal "other_id", fk.primary_key - end + test "foreign keys can be created while changing the table" do + @connection.create_table :testings + @connection.change_table :testings do |t| + t.references :testing_parent, foreign_key: true + end - test "to_table option can be passed" do - @connection.create_table :testings do |t| - t.references :parent, foreign_key: { to_table: :testing_parents } + fk = @connection.foreign_keys("testings").first + assert_equal "testings", fk.from_table + assert_equal "testing_parents", fk.to_table end - fks = @connection.foreign_keys("testings") - assert_equal([["testings", "testing_parents", "parent_id"]], - fks.map {|fk| [fk.from_table, fk.to_table, fk.column] }) - end - test "foreign keys cannot be added to polymorphic relations when creating the table" do - @connection.create_table :testings do |t| - assert_raises(ArgumentError) do - t.references :testing_parent, polymorphic: true, foreign_key: true + test "foreign keys are not added by default when changing the table" do + @connection.create_table :testings + @connection.change_table :testings do |t| + t.references :testing_parent end - end - end - test "foreign keys can be created while changing the table" do - @connection.create_table :testings - @connection.change_table :testings do |t| - t.references :testing_parent, foreign_key: true + assert_equal [], @connection.foreign_keys("testings") end - fk = @connection.foreign_keys("testings").first - assert_equal "testings", fk.from_table - assert_equal "testing_parents", fk.to_table - end + 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 + end + @connection.create_table :testings + @connection.change_table :testings do |t| + t.references :testing_parent, foreign_key: { primary_key: :other_id } + end - test "foreign keys are not added by default when changing the table" do - @connection.create_table :testings - @connection.change_table :testings do |t| - t.references :testing_parent + fk = @connection.foreign_keys("testings").find { |k| k.to_table == "testing_parents" } + assert_equal "other_id", fk.primary_key end - assert_equal [], @connection.foreign_keys("testings") - end + test "foreign keys cannot be added to polymorphic relations when changing the table" do + @connection.create_table :testings + @connection.change_table :testings do |t| + assert_raises(ArgumentError) do + t.references :testing_parent, polymorphic: true, foreign_key: true + end + end + end - 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 + test "foreign key column can be removed" do + @connection.create_table :testings do |t| + t.references :testing_parent, index: true, foreign_key: true + end + + assert_difference "@connection.foreign_keys('testings').size", -1 do + @connection.remove_reference :testings, :testing_parent, foreign_key: true + end end - @connection.create_table :testings - @connection.change_table :testings do |t| - t.references :testing_parent, foreign_key: { primary_key: :other_id } + + test "foreign key methods respect pluralize_table_names" do + begin + original_pluralize_table_names = ActiveRecord::Base.pluralize_table_names + ActiveRecord::Base.pluralize_table_names = false + @connection.create_table :testing + @connection.change_table :testing_parents do |t| + t.references :testing, foreign_key: true + end + + fk = @connection.foreign_keys("testing_parents").first + assert_equal "testing_parents", fk.from_table + assert_equal "testing", fk.to_table + + assert_difference "@connection.foreign_keys('testing_parents').size", -1 do + @connection.remove_reference :testing_parents, :testing, foreign_key: true + end + ensure + ActiveRecord::Base.pluralize_table_names = original_pluralize_table_names + @connection.drop_table "testing", if_exists: true + end end - fk = @connection.foreign_keys("testings").find { |k| k.to_table == "testing_parents" } - assert_equal "other_id", fk.primary_key - end + class CreateDogsMigration < ActiveRecord::Migration::Current + def change + create_table :dog_owners - test "foreign keys cannot be added to polymorphic relations when changing the table" do - @connection.create_table :testings - @connection.change_table :testings do |t| - assert_raises(ArgumentError) do - t.references :testing_parent, polymorphic: true, foreign_key: true + create_table :dogs do |t| + t.references :dog_owner, foreign_key: true + end end end - end - test "foreign key column can be removed" do - @connection.create_table :testings do |t| - t.references :testing_parent, index: true, foreign_key: true + def test_references_foreign_key_with_prefix + ActiveRecord::Base.table_name_prefix = "p_" + migration = CreateDogsMigration.new + silence_stream($stdout) { migration.migrate(:up) } + assert_equal 1, @connection.foreign_keys("p_dogs").size + ensure + silence_stream($stdout) { migration.migrate(:down) } + ActiveRecord::Base.table_name_prefix = nil end - assert_difference "@connection.foreign_keys('testings').size", -1 do - @connection.remove_reference :testings, :testing_parent, foreign_key: true + def test_references_foreign_key_with_suffix + ActiveRecord::Base.table_name_suffix = "_s" + migration = CreateDogsMigration.new + silence_stream($stdout) { migration.migrate(:up) } + assert_equal 1, @connection.foreign_keys("dogs_s").size + ensure + silence_stream($stdout) { migration.migrate(:down) } + ActiveRecord::Base.table_name_suffix = nil end - end - test "foreign key methods respect pluralize_table_names" do - begin - original_pluralize_table_names = ActiveRecord::Base.pluralize_table_names - ActiveRecord::Base.pluralize_table_names = false - @connection.create_table :testing - @connection.change_table :testing_parents do |t| - t.references :testing, foreign_key: true + 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 end - fk = @connection.foreign_keys("testing_parents").first - assert_equal "testing_parents", fk.from_table - assert_equal "testing", fk.to_table + fks = @connection.foreign_keys("testings") - assert_difference "@connection.foreign_keys('testing_parents').size", -1 do - @connection.remove_reference :testing_parents, :testing, foreign_key: true - end - ensure - ActiveRecord::Base.pluralize_table_names = original_pluralize_table_names - @connection.drop_table "testing", if_exists: true + 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) end end end end -end else -class ReferencesWithoutForeignKeySupportTest < 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 + class ReferencesWithoutForeignKeySupportTest < ActiveRecord::TestCase + setup do + @connection = ActiveRecord::Base.connection + @connection.create_table(:testing_parents, force: true) + end - test "ignores foreign keys defined with the table" do - @connection.create_table :testings do |t| - t.references :testing_parent, foreign_key: true + teardown do + @connection.drop_table("testings", if_exists: true) + @connection.drop_table("testing_parents", if_exists: true) end - assert_includes @connection.data_sources, "testings" + test "ignores foreign keys defined with the table" do + @connection.create_table :testings do |t| + t.references :testing_parent, foreign_key: true + end + + assert_includes @connection.data_sources, "testings" + end end end -end diff --git a/activerecord/test/cases/migration/references_index_test.rb b/activerecord/test/cases/migration/references_index_test.rb index a9a7f0f4c4..2866cabab6 100644 --- a/activerecord/test/cases/migration/references_index_test.rb +++ b/activerecord/test/cases/migration/references_index_test.rb @@ -1,4 +1,4 @@ -require 'cases/helper' +require "cases/helper" module ActiveRecord class Migration @@ -17,10 +17,10 @@ module ActiveRecord def test_creates_index connection.create_table table_name do |t| - t.references :foo, :index => true + t.references :foo, index: true end - assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) + assert connection.index_exists?(table_name, :foo_id, name: :index_testings_on_foo_id) end def test_creates_index_by_default_even_if_index_option_is_not_passed @@ -28,31 +28,31 @@ module ActiveRecord t.references :foo end - assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) + assert connection.index_exists?(table_name, :foo_id, name: :index_testings_on_foo_id) end def test_does_not_create_index_explicit connection.create_table table_name do |t| - t.references :foo, :index => false + t.references :foo, index: false end - assert_not connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) + assert_not connection.index_exists?(table_name, :foo_id, name: :index_testings_on_foo_id) end def test_creates_index_with_options connection.create_table table_name do |t| - t.references :foo, :index => {:name => :index_testings_on_yo_momma} - t.references :bar, :index => {:unique => true} + t.references :foo, index: { name: :index_testings_on_yo_momma } + t.references :bar, index: { unique: true } end - assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_yo_momma) - assert connection.index_exists?(table_name, :bar_id, :name => :index_testings_on_bar_id, :unique => true) + assert connection.index_exists?(table_name, :foo_id, name: :index_testings_on_yo_momma) + assert connection.index_exists?(table_name, :bar_id, name: :index_testings_on_bar_id, unique: true) end unless current_adapter? :OracleAdapter def test_creates_polymorphic_index connection.create_table table_name do |t| - t.references :foo, :polymorphic => true, :index => true + t.references :foo, polymorphic: true, index: true end assert connection.index_exists?(table_name, [:foo_type, :foo_id], name: :index_testings_on_foo_type_and_foo_id) @@ -62,10 +62,10 @@ module ActiveRecord def test_creates_index_for_existing_table connection.create_table table_name connection.change_table table_name do |t| - t.references :foo, :index => true + t.references :foo, index: true end - assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) + assert connection.index_exists?(table_name, :foo_id, name: :index_testings_on_foo_id) end def test_creates_index_for_existing_table_even_if_index_option_is_not_passed @@ -74,23 +74,23 @@ module ActiveRecord t.references :foo end - assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) + assert connection.index_exists?(table_name, :foo_id, name: :index_testings_on_foo_id) end def test_does_not_create_index_for_existing_table_explicit connection.create_table table_name connection.change_table table_name do |t| - t.references :foo, :index => false + t.references :foo, index: false end - assert_not connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) + assert_not connection.index_exists?(table_name, :foo_id, name: :index_testings_on_foo_id) end unless current_adapter? :OracleAdapter def test_creates_polymorphic_index_for_existing_table connection.create_table table_name connection.change_table table_name do |t| - t.references :foo, :polymorphic => true, :index => true + t.references :foo, polymorphic: true, index: true end assert connection.index_exists?(table_name, [:foo_type, :foo_id], name: :index_testings_on_foo_type_and_foo_id) diff --git a/activerecord/test/cases/migration/references_statements_test.rb b/activerecord/test/cases/migration/references_statements_test.rb index b9ce6bbc55..8fbe60f24e 100644 --- a/activerecord/test/cases/migration/references_statements_test.rb +++ b/activerecord/test/cases/migration/references_statements_test.rb @@ -35,7 +35,7 @@ module ActiveRecord assert_not index_exists?(table_name, :user_id) end - def test_create_reference_id_index_even_if_index_option_is_passed + def test_create_reference_id_index_even_if_index_option_is_not_passed add_reference table_name, :user assert index_exists?(table_name, :user_id) end @@ -46,13 +46,18 @@ module ActiveRecord end def test_creates_reference_type_column_with_default - add_reference table_name, :taggable, polymorphic: { default: 'Photo' }, index: true - assert column_exists?(table_name, :taggable_type, :string, default: 'Photo') + add_reference table_name, :taggable, polymorphic: { default: "Photo" }, index: true + assert column_exists?(table_name, :taggable_type, :string, default: "Photo") end def test_creates_named_index - add_reference table_name, :tag, index: { name: 'index_taggings_on_tag_id' } - assert index_exists?(table_name, :tag_id, name: 'index_taggings_on_tag_id') + add_reference table_name, :tag, index: { name: "index_taggings_on_tag_id" } + assert index_exists?(table_name, :tag_id, name: "index_taggings_on_tag_id") + end + + def test_creates_named_unique_index + add_reference table_name, :tag, index: { name: "index_taggings_on_tag_id", unique: true } + assert index_exists?(table_name, :tag_id, name: "index_taggings_on_tag_id", unique: true ) end def test_creates_reference_id_with_specified_type @@ -105,12 +110,12 @@ module ActiveRecord private - def with_polymorphic_column - add_column table_name, :supplier_type, :string - add_index table_name, [:supplier_id, :supplier_type] + def with_polymorphic_column + add_column table_name, :supplier_type, :string + add_index table_name, [:supplier_id, :supplier_type] - yield - end + yield + end end end end diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb index b926a92849..fc4f700916 100644 --- a/activerecord/test/cases/migration/rename_table_test.rb +++ b/activerecord/test/cases/migration/rename_table_test.rb @@ -9,9 +9,9 @@ module ActiveRecord def setup super - add_column 'test_models', :url, :string - remove_column 'test_models', :created_at - remove_column 'test_models', :updated_at + add_column "test_models", :url, :string + remove_column "test_models", :created_at + remove_column "test_models", :updated_at end def teardown @@ -31,7 +31,7 @@ module ActiveRecord # Using explicit id in insert for compatibility across all databases connection.execute "INSERT INTO 'references' (url, created_at, updated_at) VALUES ('http://rubyonrails.com', 0, 0)" - assert_equal 'http://rubyonrails.com', connection.select_value("SELECT url FROM 'references' WHERE id=1") + assert_equal "http://rubyonrails.com", connection.select_value("SELECT url FROM 'references' WHERE id=1") ensure return unless renamed connection.rename_table :references, :test_models @@ -45,7 +45,7 @@ module ActiveRecord connection.execute "INSERT INTO octopi (#{connection.quote_column_name('id')}, #{connection.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" - assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1") + assert_equal "http://www.foreverflying.com/octopus-black7.jpg", connection.select_value("SELECT url FROM octopi WHERE id=1") end def test_rename_table_with_an_index @@ -55,18 +55,18 @@ module ActiveRecord connection.execute "INSERT INTO octopi (#{connection.quote_column_name('id')}, #{connection.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" - assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1") + assert_equal "http://www.foreverflying.com/octopus-black7.jpg", connection.select_value("SELECT url FROM octopi WHERE id=1") index = connection.indexes(:octopi).first - assert index.columns.include?("url") - assert_equal 'index_octopi_on_url', index.name + assert_includes index.columns, "url" + assert_equal "index_octopi_on_url", index.name end def test_rename_table_does_not_rename_custom_named_index - add_index :test_models, :url, name: 'special_url_idx' + add_index :test_models, :url, name: "special_url_idx" rename_table :test_models, :octopi - assert_equal ['special_url_idx'], connection.indexes(:octopi).map(&:name) + assert_equal ["special_url_idx"], connection.indexes(:octopi).map(&:name) end end @@ -74,7 +74,7 @@ module ActiveRecord def test_rename_table_for_postgresql_should_also_rename_default_sequence rename_table :test_models, :octopi - pk, seq = connection.pk_and_sequence_for('octopi') + pk, seq = connection.pk_and_sequence_for("octopi") assert_equal ConnectionAdapters::PostgreSQL::Name.new("public", "octopi_#{pk}_seq"), seq end diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index bae0467e72..151f3c8efd 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -1,12 +1,12 @@ -require 'cases/helper' -require 'cases/migration/helper' -require 'bigdecimal/util' -require 'concurrent/atomic/count_down_latch' +require "cases/helper" +require "cases/migration/helper" +require "bigdecimal/util" +require "concurrent/atomic/count_down_latch" -require 'models/person' -require 'models/topic' -require 'models/developer' -require 'models/computer' +require "models/person" +require "models/topic" +require "models/developer" +require "models/computer" require MIGRATIONS_ROOT + "/valid/2_we_need_reminders" require MIGRATIONS_ROOT + "/rename/1_we_need_things" @@ -59,7 +59,7 @@ class MigrationTest < ActiveRecord::TestCase %w(last_name key bio age height wealth birthday favorite_day moment_of_truth male administrator funny).each do |column| - Person.connection.remove_column('people', column) rescue nil + Person.connection.remove_column("people", column) rescue nil end Person.connection.remove_column("people", "first_name") rescue nil Person.connection.remove_column("people", "middle_name") rescue nil @@ -69,6 +69,10 @@ class MigrationTest < ActiveRecord::TestCase ActiveRecord::Migration.verbose = @verbose_was end + def test_migration_version_matches_component_version + assert_equal ActiveRecord::VERSION::STRING.to_f, ActiveRecord::Migration.current_version + end + def test_migrator_versions migrations_path = MIGRATIONS_ROOT + "/valid" old_path = ActiveRecord::Migrator.migrations_paths @@ -89,7 +93,7 @@ class MigrationTest < ActiveRecord::TestCase end def test_migration_detection_without_schema_migration_table - ActiveRecord::Base.connection.drop_table 'schema_migrations', if_exists: true + ActiveRecord::Base.connection.drop_table "schema_migrations", if_exists: true migrations_path = MIGRATIONS_ROOT + "/valid" old_path = ActiveRecord::Migrator.migrations_paths @@ -124,7 +128,7 @@ class MigrationTest < ActiveRecord::TestCase assert_not_equal temp_conn, Person.connection - temp_conn.create_table :testings2, :force => true do |t| + temp_conn.create_table :testings2, force: true do |t| t.column :foo, :string end ensure @@ -156,11 +160,11 @@ class MigrationTest < ActiveRecord::TestCase BigNumber.reset_column_information assert BigNumber.create( - :bank_balance => 1586.43, - :big_bank_balance => BigDecimal("1000234000567.95"), - :world_population => 6000000000, - :my_house_population => 3, - :value_of_e => BigDecimal("2.7182818284590452353602875") + bank_balance: 1586.43, + big_bank_balance: BigDecimal("1000234000567.95"), + world_population: 6000000000, + my_house_population: 3, + value_of_e: BigDecimal("2.7182818284590452353602875") ) b = BigNumber.first @@ -176,7 +180,7 @@ class MigrationTest < ActiveRecord::TestCase # is_a?(Bignum) assert_kind_of Integer, b.world_population assert_equal 6000000000, b.world_population - assert_kind_of Fixnum, b.my_house_population + assert_kind_of Integer, b.my_house_population assert_equal 3, b.my_house_population assert_kind_of BigDecimal, b.bank_balance assert_equal BigDecimal("1586.43"), b.bank_balance @@ -200,7 +204,7 @@ class MigrationTest < ActiveRecord::TestCase assert_in_delta BigDecimal("2.71828182845905"), b.value_of_e, 0.00000000000001 else # - SQL standard is an integer - assert_kind_of Fixnum, b.value_of_e + assert_kind_of Integer, b.value_of_e assert_equal 2, b.value_of_e end @@ -244,22 +248,22 @@ class MigrationTest < ActiveRecord::TestCase def test_instance_based_migration_up migration = MockMigration.new - assert !migration.went_up, 'have not gone up' - assert !migration.went_down, 'have not gone down' + assert !migration.went_up, "have not gone up" + assert !migration.went_down, "have not gone down" migration.migrate :up - assert migration.went_up, 'have gone up' - assert !migration.went_down, 'have not gone down' + assert migration.went_up, "have gone up" + assert !migration.went_down, "have not gone down" end def test_instance_based_migration_down migration = MockMigration.new - assert !migration.went_up, 'have not gone up' - assert !migration.went_down, 'have not gone down' + assert !migration.went_up, "have not gone up" + assert !migration.went_down, "have not gone down" migration.migrate :down - assert !migration.went_up, 'have gone up' - assert migration.went_down, 'have not gone down' + assert !migration.went_up, "have gone up" + assert migration.went_down, "have not gone down" end if ActiveRecord::Base.connection.supports_ddl_transactions? @@ -270,7 +274,7 @@ class MigrationTest < ActiveRecord::TestCase def version; 100 end def migrate(x) add_column "people", "last_name", :string - raise 'Something broke' + raise "Something broke" end }.new @@ -291,7 +295,7 @@ class MigrationTest < ActiveRecord::TestCase def version; 100 end def migrate(x) add_column "people", "last_name", :string - raise 'Something broke' + raise "Something broke" end }.new @@ -309,12 +313,12 @@ class MigrationTest < ActiveRecord::TestCase assert_no_column Person, :last_name migration = Class.new(ActiveRecord::Migration::Current) { - self.disable_ddl_transaction! + disable_ddl_transaction! def version; 101 end def migrate(x) add_column "people", "last_name", :string - raise 'Something broke' + raise "Something broke" end }.new @@ -326,8 +330,8 @@ class MigrationTest < ActiveRecord::TestCase "without ddl transactions, the Migrator should not rollback on error but it did." ensure Person.reset_column_information - if Person.column_names.include?('last_name') - Person.connection.remove_column('people', 'last_name') + if Person.column_names.include?("last_name") + Person.connection.remove_column("people", "last_name") end end end @@ -397,7 +401,6 @@ class MigrationTest < ActiveRecord::TestCase ENV["RACK_ENV"] = original_rack_env end - def test_migration_sets_internal_metadata_even_when_fully_migrated current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call migrations_path = MIGRATIONS_ROOT + "/valid" @@ -424,25 +427,27 @@ class MigrationTest < ActiveRecord::TestCase ENV["RACK_ENV"] = original_rack_env end - def test_rename_internal_metadata_table - original_internal_metadata_table_name = ActiveRecord::Base.internal_metadata_table_name - - ActiveRecord::Base.internal_metadata_table_name = "active_record_internal_metadatas" - Reminder.reset_table_name + def test_internal_metadata_stores_environment_when_other_data_exists + ActiveRecord::InternalMetadata.delete_all + ActiveRecord::InternalMetadata[:foo] = "bar" - ActiveRecord::Base.internal_metadata_table_name = original_internal_metadata_table_name - Reminder.reset_table_name + current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call + migrations_path = MIGRATIONS_ROOT + "/valid" + old_path = ActiveRecord::Migrator.migrations_paths + ActiveRecord::Migrator.migrations_paths = migrations_path - assert_equal "ar_internal_metadata", ActiveRecord::InternalMetadata.table_name + current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call + ActiveRecord::Migrator.up(migrations_path) + assert_equal current_env, ActiveRecord::InternalMetadata[:environment] + assert_equal "bar", ActiveRecord::InternalMetadata[:foo] ensure - ActiveRecord::Base.internal_metadata_table_name = original_internal_metadata_table_name - Reminder.reset_table_name + ActiveRecord::Migrator.migrations_paths = old_path end def test_proper_table_name_on_migration reminder_class = new_isolated_reminder_class migration = ActiveRecord::Migration.new - assert_equal "table", migration.proper_table_name('table') + assert_equal "table", migration.proper_table_name("table") assert_equal "table", migration.proper_table_name(:table) assert_equal "reminders", migration.proper_table_name(reminder_class) reminder_class.reset_table_name @@ -451,26 +456,26 @@ class MigrationTest < ActiveRecord::TestCase # Use the model's own prefix/suffix if a model is given ActiveRecord::Base.table_name_prefix = "ARprefix_" ActiveRecord::Base.table_name_suffix = "_ARsuffix" - reminder_class.table_name_prefix = 'prefix_' - reminder_class.table_name_suffix = '_suffix' + reminder_class.table_name_prefix = "prefix_" + reminder_class.table_name_suffix = "_suffix" reminder_class.reset_table_name assert_equal "prefix_reminders_suffix", migration.proper_table_name(reminder_class) - reminder_class.table_name_prefix = '' - reminder_class.table_name_suffix = '' + reminder_class.table_name_prefix = "" + reminder_class.table_name_suffix = "" reminder_class.reset_table_name # Use AR::Base's prefix/suffix if string or symbol is given ActiveRecord::Base.table_name_prefix = "prefix_" ActiveRecord::Base.table_name_suffix = "_suffix" reminder_class.reset_table_name - assert_equal "prefix_table_suffix", migration.proper_table_name('table', migration.table_name_options) + assert_equal "prefix_table_suffix", migration.proper_table_name("table", migration.table_name_options) assert_equal "prefix_table_suffix", migration.proper_table_name(:table, migration.table_name_options) end def test_rename_table_with_prefix_and_suffix assert !Thing.table_exists? - ActiveRecord::Base.table_name_prefix = 'p_' - ActiveRecord::Base.table_name_suffix = '_s' + ActiveRecord::Base.table_name_prefix = "p_" + ActiveRecord::Base.table_name_suffix = "_s" Thing.reset_table_name Thing.reset_sequence_name WeNeedThings.up @@ -490,8 +495,8 @@ class MigrationTest < ActiveRecord::TestCase def test_add_drop_table_with_prefix_and_suffix assert !Reminder.table_exists? - ActiveRecord::Base.table_name_prefix = 'prefix_' - ActiveRecord::Base.table_name_suffix = '_suffix' + ActiveRecord::Base.table_name_prefix = "prefix_" + ActiveRecord::Base.table_name_suffix = "_suffix" Reminder.reset_table_name Reminder.reset_sequence_name Reminder.reset_column_information @@ -508,7 +513,7 @@ class MigrationTest < ActiveRecord::TestCase def test_create_table_with_binary_column assert_nothing_raised { Person.connection.create_table :binary_testings do |t| - t.column "data", :binary, :null => false + t.column "data", :binary, null: false end } @@ -516,13 +521,12 @@ class MigrationTest < ActiveRecord::TestCase data_column = columns.detect { |c| c.name == "data" } assert_nil data_column.default - + ensure Person.connection.drop_table :binary_testings, if_exists: true end unless mysql_enforcing_gtid_consistency? def test_create_table_with_query - Person.connection.drop_table :table_from_query_testings rescue nil Person.connection.create_table(:person, force: true) Person.connection.create_table :table_from_query_testings, as: "SELECT id FROM person" @@ -530,12 +534,11 @@ class MigrationTest < ActiveRecord::TestCase columns = Person.connection.columns(:table_from_query_testings) assert_equal 1, columns.length assert_equal "id", columns.first.name - + ensure Person.connection.drop_table :table_from_query_testings rescue nil end def test_create_table_with_query_from_relation - Person.connection.drop_table :table_from_query_testings rescue nil Person.connection.create_table(:person, force: true) Person.connection.create_table :table_from_query_testings, as: Person.select(:id) @@ -543,11 +546,28 @@ class MigrationTest < ActiveRecord::TestCase columns = Person.connection.columns(:table_from_query_testings) assert_equal 1, columns.length assert_equal "id", columns.first.name - + ensure Person.connection.drop_table :table_from_query_testings rescue nil end end + if current_adapter?(:SQLite3Adapter) + def test_allows_sqlite3_rollback_on_invalid_column_type + Person.connection.create_table :something, force: true do |t| + t.column :number, :integer + t.column :name, :string + t.column :foo, :bar + end + assert Person.connection.column_exists?(:something, :foo) + assert_nothing_raised { Person.connection.remove_column :something, :foo, :bar } + assert !Person.connection.column_exists?(:something, :foo) + assert Person.connection.column_exists?(:something, :name) + assert Person.connection.column_exists?(:something, :number) + ensure + Person.connection.drop_table :something, if_exists: true + end + end + if current_adapter? :OracleAdapter def test_create_table_with_custom_sequence_name # table name is 29 chars, the standard sequence name will @@ -555,7 +575,7 @@ class MigrationTest < ActiveRecord::TestCase assert_nothing_raised do begin Person.connection.create_table :table_with_name_thats_just_ok do |t| - t.column :foo, :string, :null => false + t.column :foo, :string, null: false end ensure Person.connection.drop_table :table_with_name_thats_just_ok rescue nil @@ -566,15 +586,15 @@ class MigrationTest < ActiveRecord::TestCase assert_nothing_raised do begin Person.connection.create_table :table_with_name_thats_just_ok, - :sequence_name => 'suitably_short_seq' do |t| - t.column :foo, :string, :null => false + sequence_name: "suitably_short_seq" do |t| + t.column :foo, :string, null: false end Person.connection.execute("select suitably_short_seq.nextval from dual") ensure Person.connection.drop_table :table_with_name_thats_just_ok, - :sequence_name => 'suitably_short_seq' rescue nil + sequence_name: "suitably_short_seq" rescue nil end end @@ -586,25 +606,30 @@ class MigrationTest < ActiveRecord::TestCase end if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter) - def test_out_of_range_limit_should_raise - Person.connection.drop_table :test_limits rescue nil + def test_out_of_range_integer_limit_should_raise e = assert_raise(ActiveRecord::ActiveRecordError, "integer limit didn't raise") do - Person.connection.create_table :test_integer_limits, :force => true do |t| - t.column :bigone, :integer, :limit => 10 + Person.connection.create_table :test_integer_limits, force: true do |t| + t.column :bigone, :integer, limit: 10 end end assert_match(/No integer type has byte size 10/, e.message) + ensure + Person.connection.drop_table :test_integer_limits, if_exists: true + end + end - unless current_adapter?(:PostgreSQLAdapter) - assert_raise(ActiveRecord::ActiveRecordError, "text limit didn't raise") do - Person.connection.create_table :test_text_limits, :force => true do |t| - t.column :bigtext, :text, :limit => 0xfffffffff - end + if current_adapter?(:Mysql2Adapter) + def test_out_of_range_text_limit_should_raise + e = assert_raise(ActiveRecord::ActiveRecordError, "text limit didn't raise") do + Person.connection.create_table :test_text_limits, force: true do |t| + t.text :bigtext, limit: 0xfffffffff end end - Person.connection.drop_table :test_limits rescue nil + assert_match(/No text type has byte length #{0xfffffffff}/, e.message) + ensure + Person.connection.drop_table :test_text_limits, if_exists: true end end @@ -718,15 +743,15 @@ end class ReservedWordsMigrationTest < ActiveRecord::TestCase def test_drop_index_from_table_named_values connection = Person.connection - connection.create_table :values, :force => true do |t| + connection.create_table :values, force: true do |t| t.integer :value end assert_nothing_raised do connection.add_index :values, :value - connection.remove_index :values, :column => :value + connection.remove_index :values, column: :value end - + ensure connection.drop_table :values rescue nil end end @@ -738,11 +763,11 @@ class ExplicitlyNamedIndexMigrationTest < ActiveRecord::TestCase t.integer :value end - assert_nothing_raised ArgumentError do - connection.add_index :values, :value, name: 'a_different_name' - connection.remove_index :values, column: :value, name: 'a_different_name' + assert_nothing_raised do + connection.add_index :values, :value, name: "a_different_name" + connection.remove_index :values, column: :value, name: "a_different_name" end - + ensure connection.drop_table :values rescue nil end end @@ -751,7 +776,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter? class BulkAlterTableMigrationsTest < ActiveRecord::TestCase def setup @connection = Person.connection - @connection.create_table(:delete_me, :force => true) {|t| } + @connection.create_table(:delete_me, force: true) { |t| } Person.reset_column_information Person.reset_sequence_name end @@ -765,15 +790,15 @@ if ActiveRecord::Base.connection.supports_bulk_alter? with_bulk_change_table do |t| t.column :name, :string t.string :qualification, :experience - t.integer :age, :default => 0 + t.integer :age, default: 0 t.date :birthdate t.timestamps null: true end end assert_equal 8, columns.size - [:name, :qualification, :experience].each {|s| assert_equal :string, column(s).type } - assert_equal '0', column(:age).default + [:name, :qualification, :experience].each { |s| assert_equal :string, column(s).type } + assert_equal "0", column(:age).default end def test_removing_columns @@ -781,7 +806,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter? t.string :qualification, :experience end - [:qualification, :experience].each {|c| assert column(c) } + [:qualification, :experience].each { |c| assert column(c) } assert_queries(1) do with_bulk_change_table do |t| @@ -790,7 +815,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter? end end - [:qualification, :experience].each {|c| assert ! column(c) } + [:qualification, :experience].each { |c| assert ! column(c) } assert column(:qualification_experience) end @@ -804,7 +829,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter? # Adding an index fires a query every time to check if an index already exists or not assert_queries(3) do with_bulk_change_table do |t| - t.index :username, :unique => true, :name => :awesome_username_index + t.index :username, unique: true, name: :awesome_username_index t.index [:name, :age] end end @@ -812,7 +837,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter? assert_equal 2, indexes.size name_age_index = index(:index_delete_me_on_name_and_age) - assert_equal ['name', 'age'].sort, name_age_index.columns.sort + assert_equal ["name", "age"].sort, name_age_index.columns.sort assert ! name_age_index.unique assert index(:awesome_username_index).unique @@ -829,7 +854,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter? assert_queries(3) do with_bulk_change_table do |t| t.remove_index :name - t.index :name, :name => :new_name_index, :unique => true + t.index :name, name: :new_name_index, unique: true end end @@ -851,43 +876,43 @@ if ActiveRecord::Base.connection.supports_bulk_alter? # One query for columns (delete_me table) # One query for primary key (delete_me table) # One query to do the bulk change - assert_queries(3, :ignore_none => true) do + assert_queries(3, ignore_none: true) do with_bulk_change_table do |t| - t.change :name, :string, :default => 'NONAME' + t.change :name, :string, default: "NONAME" t.change :birthdate, :datetime end end - assert_equal 'NONAME', column(:name).default + assert_equal "NONAME", column(:name).default assert_equal :datetime, column(:birthdate).type end protected - def with_bulk_change_table - # Reset columns/indexes cache as we're changing the table - @columns = @indexes = nil + def with_bulk_change_table + # Reset columns/indexes cache as we're changing the table + @columns = @indexes = nil - Person.connection.change_table(:delete_me, :bulk => true) do |t| - yield t + Person.connection.change_table(:delete_me, bulk: true) do |t| + yield t + end end - end - def column(name) - columns.detect {|c| c.name == name.to_s } - end + def column(name) + columns.detect { |c| c.name == name.to_s } + end - def columns - @columns ||= Person.connection.columns('delete_me') - end + def columns + @columns ||= Person.connection.columns("delete_me") + end - def index(name) - indexes.detect {|i| i.name == name.to_s } - end + def index(name) + indexes.detect { |i| i.name == name.to_s } + end - def indexes - @indexes ||= Person.connection.indexes('delete_me') - end + def indexes + @indexes ||= Person.connection.indexes("delete_me") + end end # AlterTableMigrationsTest end @@ -909,7 +934,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase @migrations_path = MIGRATIONS_ROOT + "/valid" @existing_migrations = Dir[@migrations_path + "/*.rb"] - copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy"}) + copied = ActiveRecord::Migration.copy(@migrations_path, bukkits: MIGRATIONS_ROOT + "/to_copy") assert File.exist?(@migrations_path + "/4_people_have_hobbies.bukkits.rb") assert File.exist?(@migrations_path + "/5_people_have_descriptions.bukkits.rb") assert_equal [@migrations_path + "/4_people_have_hobbies.bukkits.rb", @migrations_path + "/5_people_have_descriptions.bukkits.rb"], copied.map(&:filename) @@ -918,7 +943,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase assert_equal expected, IO.readlines(@migrations_path + "/4_people_have_hobbies.bukkits.rb")[0].chomp files_count = Dir[@migrations_path + "/*.rb"].length - copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy"}) + copied = ActiveRecord::Migration.copy(@migrations_path, bukkits: MIGRATIONS_ROOT + "/to_copy") assert_equal files_count, Dir[@migrations_path + "/*.rb"].length assert copied.empty? ensure @@ -951,7 +976,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase @existing_migrations = Dir[@migrations_path + "/*.rb"] travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do - copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) + copied = ActiveRecord::Migration.copy(@migrations_path, bukkits: MIGRATIONS_ROOT + "/to_copy_with_timestamps") assert File.exist?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb") assert File.exist?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb") expected = [@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb", @@ -959,7 +984,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase assert_equal expected, copied.map(&:filename) files_count = Dir[@migrations_path + "/*.rb"].length - copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) + copied = ActiveRecord::Migration.copy(@migrations_path, bukkits: MIGRATIONS_ROOT + "/to_copy_with_timestamps") assert_equal files_count, Dir[@migrations_path + "/*.rb"].length assert copied.empty? end @@ -996,12 +1021,12 @@ class CopyMigrationsTest < ActiveRecord::TestCase @existing_migrations = Dir[@migrations_path + "/*.rb"] travel_to(Time.utc(2010, 2, 20, 10, 10, 10)) do - ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) + ActiveRecord::Migration.copy(@migrations_path, bukkits: MIGRATIONS_ROOT + "/to_copy_with_timestamps") assert File.exist?(@migrations_path + "/20100301010102_people_have_hobbies.bukkits.rb") assert File.exist?(@migrations_path + "/20100301010103_people_have_descriptions.bukkits.rb") files_count = Dir[@migrations_path + "/*.rb"].length - copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) + copied = ActiveRecord::Migration.copy(@migrations_path, bukkits: MIGRATIONS_ROOT + "/to_copy_with_timestamps") assert_equal files_count, Dir[@migrations_path + "/*.rb"].length assert copied.empty? end @@ -1014,7 +1039,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase @migrations_path = MIGRATIONS_ROOT + "/valid" @existing_migrations = Dir[@migrations_path + "/*.rb"] - copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/magic"}) + copied = ActiveRecord::Migration.copy(@migrations_path, bukkits: MIGRATIONS_ROOT + "/magic") assert File.exist?(@migrations_path + "/4_currencies_have_symbols.bukkits.rb") assert_equal [@migrations_path + "/4_currencies_have_symbols.bukkits.rb"], copied.map(&:filename) @@ -1022,7 +1047,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase assert_equal expected, IO.readlines(@migrations_path + "/4_currencies_have_symbols.bukkits.rb")[0..1].join.chomp files_count = Dir[@migrations_path + "/*.rb"].length - copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/magic"}) + copied = ActiveRecord::Migration.copy(@migrations_path, bukkits: MIGRATIONS_ROOT + "/magic") assert_equal files_count, Dir[@migrations_path + "/*.rb"].length assert copied.empty? ensure @@ -1039,7 +1064,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase skipped = [] on_skip = Proc.new { |name, migration| skipped << "#{name} #{migration.name}" } - copied = ActiveRecord::Migration.copy(@migrations_path, sources, :on_skip => on_skip) + copied = ActiveRecord::Migration.copy(@migrations_path, sources, on_skip: on_skip) assert_equal 2, copied.length assert_equal 1, skipped.length @@ -1057,8 +1082,8 @@ class CopyMigrationsTest < ActiveRecord::TestCase skipped = [] on_skip = Proc.new { |name, migration| skipped << "#{name} #{migration.name}" } - copied = ActiveRecord::Migration.copy(@migrations_path, sources, :on_skip => on_skip) - ActiveRecord::Migration.copy(@migrations_path, sources, :on_skip => on_skip) + copied = ActiveRecord::Migration.copy(@migrations_path, sources, on_skip: on_skip) + ActiveRecord::Migration.copy(@migrations_path, sources, on_skip: on_skip) assert_equal 2, copied.length assert_equal 0, skipped.length @@ -1071,7 +1096,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase @existing_migrations = [] travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do - copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) + copied = ActiveRecord::Migration.copy(@migrations_path, bukkits: MIGRATIONS_ROOT + "/to_copy_with_timestamps") assert File.exist?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb") assert File.exist?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb") assert_equal 2, copied.length @@ -1086,7 +1111,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase @existing_migrations = [] travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do - copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) + copied = ActiveRecord::Migration.copy(@migrations_path, bukkits: MIGRATIONS_ROOT + "/to_copy_with_timestamps") assert File.exist?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb") assert File.exist?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb") assert_equal 2, copied.length @@ -1104,4 +1129,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase ActiveRecord::Base.logger = old end + def test_unknown_migration_version_should_raise_an_argument_error + assert_raise(ArgumentError) { ActiveRecord::Migration[1.0] } + end end diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb index 86eca53141..1ba18bc9c2 100644 --- a/activerecord/test/cases/migrator_test.rb +++ b/activerecord/test/cases/migrator_test.rb @@ -9,7 +9,7 @@ class MigratorTest < ActiveRecord::TestCase class Sensor < ActiveRecord::Migration::Current attr_reader :went_up, :went_down - def initialize name = self.class.name, version = nil + def initialize(name = self.class.name, version = nil) super @went_up = false @went_down = false @@ -46,21 +46,21 @@ class MigratorTest < ActiveRecord::TestCase def test_migrator_with_duplicate_names assert_raises(ActiveRecord::DuplicateMigrationNameError, "Multiple migrations have the name Chunky") do - list = [ActiveRecord::Migration.new('Chunky'), ActiveRecord::Migration.new('Chunky')] + list = [ActiveRecord::Migration.new("Chunky"), ActiveRecord::Migration.new("Chunky")] ActiveRecord::Migrator.new(:up, list) end end def test_migrator_with_duplicate_versions assert_raises(ActiveRecord::DuplicateMigrationVersionError) do - list = [ActiveRecord::Migration.new('Foo', 1), ActiveRecord::Migration.new('Bar', 1)] + list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 1)] ActiveRecord::Migrator.new(:up, list) end end def test_migrator_with_missing_version_numbers assert_raises(ActiveRecord::UnknownMigrationVersionError) do - list = [ActiveRecord::Migration.new('Foo', 1), ActiveRecord::Migration.new('Bar', 2)] + list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)] ActiveRecord::Migrator.new(:up, list, 3).run end end @@ -68,7 +68,7 @@ class MigratorTest < ActiveRecord::TestCase def test_finds_migrations migrations = ActiveRecord::Migrator.migrations(MIGRATIONS_ROOT + "/valid") - [[1, 'ValidPeopleHaveLastNames'], [2, 'WeNeedReminders'], [3, 'InnocentJointable']].each_with_index do |pair, i| + [[1, "ValidPeopleHaveLastNames"], [2, "WeNeedReminders"], [3, "InnocentJointable"]].each_with_index do |pair, i| assert_equal migrations[i].version, pair.first assert_equal migrations[i].name, pair.last end @@ -77,14 +77,14 @@ class MigratorTest < ActiveRecord::TestCase def test_finds_migrations_in_subdirectories migrations = ActiveRecord::Migrator.migrations(MIGRATIONS_ROOT + "/valid_with_subdirectories") - [[1, 'ValidPeopleHaveLastNames'], [2, 'WeNeedReminders'], [3, 'InnocentJointable']].each_with_index do |pair, i| + [[1, "ValidPeopleHaveLastNames"], [2, "WeNeedReminders"], [3, "InnocentJointable"]].each_with_index do |pair, i| assert_equal migrations[i].version, pair.first assert_equal migrations[i].name, pair.last end end def test_finds_migrations_from_two_directories - directories = [MIGRATIONS_ROOT + '/valid_with_timestamps', MIGRATIONS_ROOT + '/to_copy_with_timestamps'] + directories = [MIGRATIONS_ROOT + "/valid_with_timestamps", MIGRATIONS_ROOT + "/to_copy_with_timestamps"] migrations = ActiveRecord::Migrator.migrations directories [[20090101010101, "PeopleHaveHobbies"], @@ -92,15 +92,15 @@ class MigratorTest < ActiveRecord::TestCase [20100101010101, "ValidWithTimestampsPeopleHaveLastNames"], [20100201010101, "ValidWithTimestampsWeNeedReminders"], [20100301010101, "ValidWithTimestampsInnocentJointable"]].each_with_index do |pair, i| - assert_equal pair.first, migrations[i].version - assert_equal pair.last, migrations[i].name + assert_equal pair.first, migrations[i].version + assert_equal pair.last, migrations[i].name end end def test_finds_migrations_in_numbered_directory - migrations = ActiveRecord::Migrator.migrations [MIGRATIONS_ROOT + '/10_urban'] + migrations = ActiveRecord::Migrator.migrations [MIGRATIONS_ROOT + "/10_urban"] assert_equal 9, migrations[0].version - assert_equal 'AddExpressions', migrations[0].name + assert_equal "AddExpressions", migrations[0].name end def test_relative_migrations @@ -109,14 +109,14 @@ class MigratorTest < ActiveRecord::TestCase end migration_proxy = list.find { |item| - item.name == 'ValidPeopleHaveLastNames' + item.name == "ValidPeopleHaveLastNames" } - assert migration_proxy, 'should find pending migration' + assert migration_proxy, "should find pending migration" end def test_finds_pending_migrations - ActiveRecord::SchemaMigration.create!(:version => '1') - migration_list = [ActiveRecord::Migration.new('foo', 1), ActiveRecord::Migration.new('bar', 3)] + ActiveRecord::SchemaMigration.create!(version: "1") + migration_list = [ActiveRecord::Migration.new("foo", 1), ActiveRecord::Migration.new("bar", 3)] migrations = ActiveRecord::Migrator.new(:up, migration_list).pending_migrations assert_equal 1, migrations.size @@ -124,21 +124,21 @@ class MigratorTest < ActiveRecord::TestCase end def test_migrator_interleaved_migrations - pass_one = [Sensor.new('One', 1)] + pass_one = [Sensor.new("One", 1)] ActiveRecord::Migrator.new(:up, pass_one).migrate assert pass_one.first.went_up assert_not pass_one.first.went_down - pass_two = [Sensor.new('One', 1), Sensor.new('Three', 3)] + pass_two = [Sensor.new("One", 1), Sensor.new("Three", 3)] ActiveRecord::Migrator.new(:up, pass_two).migrate assert_not pass_two[0].went_up assert pass_two[1].went_up assert pass_two.all? { |x| !x.went_down } - pass_three = [Sensor.new('One', 1), - Sensor.new('Two', 2), - Sensor.new('Three', 3)] + pass_three = [Sensor.new("One", 1), + Sensor.new("Two", 2), + Sensor.new("Three", 3)] ActiveRecord::Migrator.new(:down, pass_three).migrate assert pass_three[0].went_down @@ -165,7 +165,7 @@ class MigratorTest < ActiveRecord::TestCase end def test_current_version - ActiveRecord::SchemaMigration.create!(:version => '1000') + ActiveRecord::SchemaMigration.create!(version: "1000") assert_equal 1000, ActiveRecord::Migrator.current_version end @@ -313,9 +313,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') } + ActiveSupport::Deprecation.silence { assert_not ActiveRecord::Base.connection.table_exists?("schema_migrations") } migrator.migrate("valid", 1) - ActiveSupport::Deprecation.silence { assert ActiveRecord::Base.connection.table_exists?('schema_migrations') } + ActiveSupport::Deprecation.silence { assert ActiveRecord::Base.connection.table_exists?("schema_migrations") } end def test_migrator_forward @@ -332,7 +332,7 @@ class MigratorTest < ActiveRecord::TestCase def test_only_loads_pending_migrations # migrate up to 1 - ActiveRecord::SchemaMigration.create!(:version => '1') + ActiveRecord::SchemaMigration.create!(version: "1") calls, migrator = migrator_class(3) migrator.migrate("valid", nil) @@ -357,32 +357,32 @@ class MigratorTest < ActiveRecord::TestCase end private - def m(name, version) - x = Sensor.new name, version - x.extend(Module.new { - define_method(:up) { yield(:up, x); super() } - define_method(:down) { yield(:down, x); super() } - }) if block_given? - end + def m(name, version) + x = Sensor.new name, version + x.extend(Module.new { + define_method(:up) { yield(:up, x); super() } + define_method(:down) { yield(:down, x); super() } + }) if block_given? + end - def sensors(count) - calls = [] - migrations = count.times.map { |i| - m(nil, i + 1) { |c,migration| - calls << [c, migration.version] + def sensors(count) + calls = [] + migrations = count.times.map { |i| + m(nil, i + 1) { |c,migration| + calls << [c, migration.version] + } } - } - [calls, migrations] - end + [calls, migrations] + end - def migrator_class(count) - calls, migrations = sensors(count) + def migrator_class(count) + calls, migrations = sensors(count) - migrator = Class.new(ActiveRecord::Migrator).extend(Module.new { - define_method(:migrations) { |paths| - migrations - } - }) - [calls, migrator] - end + migrator = Class.new(ActiveRecord::Migrator).extend(Module.new { + define_method(:migrations) { |paths| + migrations + } + }) + [calls, migrator] + end end diff --git a/activerecord/test/cases/mixin_test.rb b/activerecord/test/cases/mixin_test.rb index 7ebdcac711..a8af8e30f7 100644 --- a/activerecord/test/cases/mixin_test.rb +++ b/activerecord/test/cases/mixin_test.rb @@ -41,13 +41,12 @@ class TouchTest < ActiveRecord::TestCase old_updated_at = stamped.updated_at - travel 5.minutes do - stamped.lft_will_change! - stamped.save + travel 5.minutes + stamped.lft_will_change! + stamped.save - assert_equal Time.now, stamped.updated_at - assert_equal old_updated_at, stamped.created_at - end + assert_equal Time.now, stamped.updated_at + assert_equal old_updated_at, stamped.created_at end def test_create_turned_off @@ -64,5 +63,4 @@ class TouchTest < ActiveRecord::TestCase ensure Mixin.record_timestamps = true end - end diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb index 7f31325f47..f8a7bab35f 100644 --- a/activerecord/test/cases/modules_test.rb +++ b/activerecord/test/cases/modules_test.rb @@ -1,8 +1,8 @@ require "cases/helper" -require 'models/company_in_module' -require 'models/shop' -require 'models/developer' -require 'models/computer' +require "models/company_in_module" +require "models/shop" +require "models/developer" +require "models/computer" class ModulesTest < ActiveRecord::TestCase fixtures :accounts, :companies, :projects, :developers, :collections, :products, :variants @@ -31,7 +31,7 @@ class ModulesTest < ActiveRecord::TestCase def test_module_spanning_associations firm = MyApplication::Business::Firm.first assert !firm.clients.empty?, "Firm should have clients" - assert_nil firm.class.table_name.match('::'), "Firm shouldn't have the module appear in its table name" + assert_nil firm.class.table_name.match("::"), "Firm shouldn't have the module appear in its table name" end def test_module_spanning_has_and_belongs_to_many_associations @@ -41,7 +41,7 @@ class ModulesTest < ActiveRecord::TestCase end def test_associations_spanning_cross_modules - account = MyApplication::Billing::Account.all.merge!(:order => 'id').first + account = MyApplication::Billing::Account.all.merge!(order: "id").first assert_kind_of MyApplication::Business::Firm, account.firm assert_kind_of MyApplication::Billing::Firm, account.qualified_billing_firm assert_kind_of MyApplication::Billing::Firm, account.unqualified_billing_firm @@ -50,20 +50,20 @@ class ModulesTest < ActiveRecord::TestCase end def test_find_account_and_include_company - account = MyApplication::Billing::Account.all.merge!(:includes => :firm).find(1) + account = MyApplication::Billing::Account.all.merge!(includes: :firm).find(1) assert_kind_of MyApplication::Business::Firm, account.firm end def test_table_name - assert_equal 'accounts', MyApplication::Billing::Account.table_name, 'table_name for ActiveRecord model in module' - assert_equal 'companies', MyApplication::Business::Client.table_name, 'table_name for ActiveRecord model subclass' - assert_equal 'company_contacts', MyApplication::Business::Client::Contact.table_name, 'table_name for ActiveRecord model enclosed by another ActiveRecord model' + assert_equal "accounts", MyApplication::Billing::Account.table_name, "table_name for ActiveRecord model in module" + assert_equal "companies", MyApplication::Business::Client.table_name, "table_name for ActiveRecord model subclass" + assert_equal "company_contacts", MyApplication::Business::Client::Contact.table_name, "table_name for ActiveRecord model enclosed by another ActiveRecord model" end def test_assign_ids firm = MyApplication::Business::Firm.first - assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do + assert_nothing_raised do firm.client_ids = [MyApplication::Business::Client.first.id] end end @@ -72,9 +72,9 @@ class ModulesTest < ActiveRecord::TestCase def test_eager_loading_in_modules clients = [] - assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do - clients << MyApplication::Business::Client.references(:accounts).merge!(:includes => {:firm => :account}, :where => 'accounts.id IS NOT NULL').find(3) - clients << MyApplication::Business::Client.includes(:firm => :account).find(3) + assert_nothing_raised do + clients << MyApplication::Business::Client.references(:accounts).merge!(includes: { firm: :account }, where: "accounts.id IS NOT NULL").find(3) + clients << MyApplication::Business::Client.includes(firm: :account).find(3) end clients.each do |client| @@ -85,9 +85,9 @@ class ModulesTest < ActiveRecord::TestCase end def test_module_table_name_prefix - assert_equal 'prefixed_companies', MyApplication::Business::Prefixed::Company.table_name, 'inferred table_name for ActiveRecord model in module with table_name_prefix' - assert_equal 'prefixed_companies', MyApplication::Business::Prefixed::Nested::Company.table_name, 'table_name for ActiveRecord model in nested module with a parent table_name_prefix' - assert_equal 'companies', MyApplication::Business::Prefixed::Firm.table_name, 'explicit table_name for ActiveRecord model in module with table_name_prefix should not be prefixed' + assert_equal "prefixed_companies", MyApplication::Business::Prefixed::Company.table_name, "inferred table_name for ActiveRecord model in module with table_name_prefix" + assert_equal "prefixed_companies", MyApplication::Business::Prefixed::Nested::Company.table_name, "table_name for ActiveRecord model in nested module with a parent table_name_prefix" + assert_equal "companies", MyApplication::Business::Prefixed::Firm.table_name, "explicit table_name for ActiveRecord model in module with table_name_prefix should not be prefixed" end def test_module_table_name_prefix_with_global_prefix @@ -101,21 +101,21 @@ class ModulesTest < ActiveRecord::TestCase MyApplication::Business::Prefixed::Nested::Company, MyApplication::Billing::Account ] - ActiveRecord::Base.table_name_prefix = 'global_' + ActiveRecord::Base.table_name_prefix = "global_" classes.each(&:reset_table_name) - assert_equal 'global_companies', MyApplication::Business::Company.table_name, 'inferred table_name for ActiveRecord model in module without table_name_prefix' - assert_equal 'prefixed_companies', MyApplication::Business::Prefixed::Company.table_name, 'inferred table_name for ActiveRecord model in module with table_name_prefix' - assert_equal 'prefixed_companies', MyApplication::Business::Prefixed::Nested::Company.table_name, 'table_name for ActiveRecord model in nested module with a parent table_name_prefix' - assert_equal 'companies', MyApplication::Business::Prefixed::Firm.table_name, 'explicit table_name for ActiveRecord model in module with table_name_prefix should not be prefixed' + assert_equal "global_companies", MyApplication::Business::Company.table_name, "inferred table_name for ActiveRecord model in module without table_name_prefix" + assert_equal "prefixed_companies", MyApplication::Business::Prefixed::Company.table_name, "inferred table_name for ActiveRecord model in module with table_name_prefix" + assert_equal "prefixed_companies", MyApplication::Business::Prefixed::Nested::Company.table_name, "table_name for ActiveRecord model in nested module with a parent table_name_prefix" + assert_equal "companies", MyApplication::Business::Prefixed::Firm.table_name, "explicit table_name for ActiveRecord model in module with table_name_prefix should not be prefixed" ensure - ActiveRecord::Base.table_name_prefix = '' + ActiveRecord::Base.table_name_prefix = "" classes.each(&:reset_table_name) end def test_module_table_name_suffix - assert_equal 'companies_suffixed', MyApplication::Business::Suffixed::Company.table_name, 'inferred table_name for ActiveRecord model in module with table_name_suffix' - assert_equal 'companies_suffixed', MyApplication::Business::Suffixed::Nested::Company.table_name, 'table_name for ActiveRecord model in nested module with a parent table_name_suffix' - assert_equal 'companies', MyApplication::Business::Suffixed::Firm.table_name, 'explicit table_name for ActiveRecord model in module with table_name_suffix should not be suffixed' + assert_equal "companies_suffixed", MyApplication::Business::Suffixed::Company.table_name, "inferred table_name for ActiveRecord model in module with table_name_suffix" + assert_equal "companies_suffixed", MyApplication::Business::Suffixed::Nested::Company.table_name, "table_name for ActiveRecord model in nested module with a parent table_name_suffix" + assert_equal "companies", MyApplication::Business::Suffixed::Firm.table_name, "explicit table_name for ActiveRecord model in module with table_name_suffix should not be suffixed" end def test_module_table_name_suffix_with_global_suffix @@ -129,14 +129,14 @@ class ModulesTest < ActiveRecord::TestCase MyApplication::Business::Suffixed::Nested::Company, MyApplication::Billing::Account ] - ActiveRecord::Base.table_name_suffix = '_global' + ActiveRecord::Base.table_name_suffix = "_global" classes.each(&:reset_table_name) - assert_equal 'companies_global', MyApplication::Business::Company.table_name, 'inferred table_name for ActiveRecord model in module without table_name_suffix' - assert_equal 'companies_suffixed', MyApplication::Business::Suffixed::Company.table_name, 'inferred table_name for ActiveRecord model in module with table_name_suffix' - assert_equal 'companies_suffixed', MyApplication::Business::Suffixed::Nested::Company.table_name, 'table_name for ActiveRecord model in nested module with a parent table_name_suffix' - assert_equal 'companies', MyApplication::Business::Suffixed::Firm.table_name, 'explicit table_name for ActiveRecord model in module with table_name_suffix should not be suffixed' + assert_equal "companies_global", MyApplication::Business::Company.table_name, "inferred table_name for ActiveRecord model in module without table_name_suffix" + assert_equal "companies_suffixed", MyApplication::Business::Suffixed::Company.table_name, "inferred table_name for ActiveRecord model in module with table_name_suffix" + assert_equal "companies_suffixed", MyApplication::Business::Suffixed::Nested::Company.table_name, "table_name for ActiveRecord model in nested module with a parent table_name_suffix" + assert_equal "companies", MyApplication::Business::Suffixed::Firm.table_name, "explicit table_name for ActiveRecord model in module with table_name_suffix should not be suffixed" ensure - ActiveRecord::Base.table_name_suffix = '' + ActiveRecord::Base.table_name_suffix = "" classes.each(&:reset_table_name) end diff --git a/activerecord/test/cases/multiparameter_attributes_test.rb b/activerecord/test/cases/multiparameter_attributes_test.rb index ae18573126..b2f76398df 100644 --- a/activerecord/test/cases/multiparameter_attributes_test.rb +++ b/activerecord/test/cases/multiparameter_attributes_test.rb @@ -1,6 +1,6 @@ require "cases/helper" -require 'models/topic' -require 'models/customer' +require "models/topic" +require "models/customer" class MultiParameterAttributeTest < ActiveRecord::TestCase fixtures :topics @@ -11,15 +11,13 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase topic.attributes = attributes # note that extra #to_date call allows test to pass for Oracle, which # treats dates/times the same - assert_date_from_db Date.new(2004, 6, 24), topic.last_read.to_date + assert_equal Date.new(2004, 6, 24), topic.last_read.to_date end def test_multiparameter_attributes_on_date_with_empty_year attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "24" } topic = Topic.find(1) topic.attributes = attributes - # note that extra #to_date call allows test to pass for Oracle, which - # treats dates/times the same assert_nil topic.last_read end @@ -27,8 +25,6 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "24" } topic = Topic.find(1) topic.attributes = attributes - # note that extra #to_date call allows test to pass for Oracle, which - # treats dates/times the same assert_nil topic.last_read end @@ -36,8 +32,6 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "" } topic = Topic.find(1) topic.attributes = attributes - # note that extra #to_date call allows test to pass for Oracle, which - # treats dates/times the same assert_nil topic.last_read end @@ -45,8 +39,6 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "" } topic = Topic.find(1) topic.attributes = attributes - # note that extra #to_date call allows test to pass for Oracle, which - # treats dates/times the same assert_nil topic.last_read end @@ -54,8 +46,6 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "" } topic = Topic.find(1) topic.attributes = attributes - # note that extra #to_date call allows test to pass for Oracle, which - # treats dates/times the same assert_nil topic.last_read end @@ -63,8 +53,6 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "24" } topic = Topic.find(1) topic.attributes = attributes - # note that extra #to_date call allows test to pass for Oracle, which - # treats dates/times the same assert_nil topic.last_read end @@ -214,6 +202,20 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase Topic.reset_column_information end + def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes_and_invalid_time_params + with_timezone_config aware_attributes: true do + Topic.reset_column_information + attributes = { + "written_on(1i)" => "2004", "written_on(2i)" => "", "written_on(3i)" => "" + } + topic = Topic.find(1) + topic.attributes = attributes + assert_nil topic.written_on + end + ensure + Topic.reset_column_information + end + def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes_false with_timezone_config default: :local, aware_attributes: false, zone: -28800 do attributes = { diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb index 39cdcf5403..e3bb51bd77 100644 --- a/activerecord/test/cases/multiple_db_test.rb +++ b/activerecord/test/cases/multiple_db_test.rb @@ -1,7 +1,7 @@ require "cases/helper" -require 'models/entrant' -require 'models/bird' -require 'models/course' +require "models/entrant" +require "models/bird" +require "models/course" class MultipleDbTest < ActiveRecord::TestCase self.use_transactional_tests = false @@ -24,6 +24,13 @@ class MultipleDbTest < ActiveRecord::TestCase assert_equal(ActiveRecord::Base.connection, Entrant.connection) end + def test_swapping_the_connection + old_spec_name, Course.connection_specification_name = Course.connection_specification_name, "primary" + assert_equal(Entrant.connection, Course.connection) + ensure + Course.connection_specification_name = old_spec_name + end + def test_find c1 = Course.find(1) assert_equal "Ruby Development", c1.name @@ -53,7 +60,7 @@ class MultipleDbTest < ActiveRecord::TestCase ActiveSupport::Dependencies.clear Object.send(:remove_const, :Course) - require_dependency 'models/course' + require_dependency "models/course" assert Course.connection end @@ -89,8 +96,8 @@ class MultipleDbTest < ActiveRecord::TestCase end def test_connection - assert_equal Entrant.arel_engine.connection, Bird.arel_engine.connection - assert_not_equal Entrant.arel_engine.connection, Course.arel_engine.connection + assert_equal Entrant.arel_engine.connection.object_id, Bird.arel_engine.connection.object_id + assert_not_equal Entrant.arel_engine.connection.object_id, Course.arel_engine.connection.object_id end unless in_memory_db? @@ -104,7 +111,7 @@ class MultipleDbTest < ActiveRecord::TestCase def test_associations_should_work_when_model_has_no_connection begin ActiveRecord::Base.remove_connection - assert_nothing_raised ActiveRecord::ConnectionNotEstablished do + assert_nothing_raised do College.first.courses.first end ensure diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 6fbc6196cc..a9c3733c20 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -9,11 +9,11 @@ require "models/man" require "models/interest" require "models/owner" require "models/pet" -require 'active_support/hash_with_indifferent_access' +require "active_support/hash_with_indifferent_access" class TestNestedAttributesInGeneral < ActiveRecord::TestCase teardown do - Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc(&:empty?) + Pirate.accepts_nested_attributes_for :ship, allow_destroy: true, reject_if: proc(&:empty?) end def test_base_should_have_an_empty_nested_attributes_options @@ -30,28 +30,28 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase end def test_should_not_build_a_new_record_using_reject_all_even_if_destroy_is_given - pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") - pirate.birds_with_reject_all_blank_attributes = [{:name => '', :color => '', :_destroy => '0'}] + pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?") + pirate.birds_with_reject_all_blank_attributes = [{ name: "", color: "", _destroy: "0" }] pirate.save! assert pirate.birds_with_reject_all_blank.empty? end def test_should_not_build_a_new_record_if_reject_all_blank_returns_false - pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") - pirate.birds_with_reject_all_blank_attributes = [{:name => '', :color => ''}] + pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?") + pirate.birds_with_reject_all_blank_attributes = [{ name: "", color: "" }] pirate.save! assert pirate.birds_with_reject_all_blank.empty? end def test_should_build_a_new_record_if_reject_all_blank_does_not_return_false - pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") - pirate.birds_with_reject_all_blank_attributes = [{:name => 'Tweetie', :color => ''}] + pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?") + pirate.birds_with_reject_all_blank_attributes = [{ name: "Tweetie", color: "" }] pirate.save! assert_equal 1, pirate.birds_with_reject_all_blank.count - assert_equal 'Tweetie', pirate.birds_with_reject_all_blank.first.name + assert_equal "Tweetie", pirate.birds_with_reject_all_blank.first.name end def test_should_raise_an_ArgumentError_for_non_existing_associations @@ -61,88 +61,95 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase assert_equal "No association found for name `honesty'. Has it been defined yet?", exception.message end + def test_should_raise_an_UnknownAttributeError_for_non_existing_nested_attributes + exception = assert_raise ActiveModel::UnknownAttributeError do + Pirate.new(ship_attributes: { sail: true }) + end + assert_equal "unknown attribute 'sail' for Ship.", exception.message + end + def test_should_disable_allow_destroy_by_default Pirate.accepts_nested_attributes_for :ship pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?") - ship = pirate.create_ship(name: 'Nights Dirty Lightning') + ship = pirate.create_ship(name: "Nights Dirty Lightning") - pirate.update(ship_attributes: { '_destroy' => true, :id => ship.id }) + pirate.update(ship_attributes: { "_destroy" => true, :id => ship.id }) - assert_nothing_raised(ActiveRecord::RecordNotFound) { pirate.ship.reload } + assert_nothing_raised { pirate.ship.reload } end def test_a_model_should_respond_to_underscore_destroy_and_return_if_it_is_marked_for_destruction - ship = Ship.create!(:name => 'Nights Dirty Lightning') + ship = Ship.create!(name: "Nights Dirty Lightning") assert !ship._destroy ship.mark_for_destruction assert ship._destroy end def test_reject_if_method_without_arguments - Pirate.accepts_nested_attributes_for :ship, :reject_if => :new_record? + Pirate.accepts_nested_attributes_for :ship, reject_if: :new_record? - pirate = Pirate.new(:catchphrase => "Stop wastin' me time") - pirate.ship_attributes = { :name => 'Black Pearl' } - assert_no_difference('Ship.count') { pirate.save! } + pirate = Pirate.new(catchphrase: "Stop wastin' me time") + pirate.ship_attributes = { name: "Black Pearl" } + assert_no_difference("Ship.count") { pirate.save! } end def test_reject_if_method_with_arguments - Pirate.accepts_nested_attributes_for :ship, :reject_if => :reject_empty_ships_on_create + Pirate.accepts_nested_attributes_for :ship, reject_if: :reject_empty_ships_on_create - pirate = Pirate.new(:catchphrase => "Stop wastin' me time") - pirate.ship_attributes = { :name => 'Red Pearl', :_reject_me_if_new => true } - assert_no_difference('Ship.count') { pirate.save! } + pirate = Pirate.new(catchphrase: "Stop wastin' me time") + pirate.ship_attributes = { name: "Red Pearl", _reject_me_if_new: true } + assert_no_difference("Ship.count") { pirate.save! } # pirate.reject_empty_ships_on_create returns false for saved pirate records # in the previous step note that pirate gets saved but ship fails - pirate.ship_attributes = { :name => 'Red Pearl', :_reject_me_if_new => true } - assert_difference('Ship.count') { pirate.save! } + pirate.ship_attributes = { name: "Red Pearl", _reject_me_if_new: true } + assert_difference("Ship.count") { pirate.save! } end def test_reject_if_with_indifferent_keys - Pirate.accepts_nested_attributes_for :ship, :reject_if => proc {|attributes| attributes[:name].blank? } + Pirate.accepts_nested_attributes_for :ship, reject_if: proc { |attributes| attributes[:name].blank? } - pirate = Pirate.new(:catchphrase => "Stop wastin' me time") - pirate.ship_attributes = { :name => 'Hello Pearl' } - assert_difference('Ship.count') { pirate.save! } + pirate = Pirate.new(catchphrase: "Stop wastin' me time") + pirate.ship_attributes = { name: "Hello Pearl" } + assert_difference("Ship.count") { pirate.save! } end def test_reject_if_with_a_proc_which_returns_true_always_for_has_one - Pirate.accepts_nested_attributes_for :ship, :reject_if => proc {|attributes| true } + Pirate.accepts_nested_attributes_for :ship, reject_if: proc { |attributes| true } pirate = Pirate.new(catchphrase: "Stop wastin' me time") - ship = pirate.create_ship(name: 's1') - pirate.update({ship_attributes: { name: 's2', id: ship.id } }) - assert_equal 's1', ship.reload.name + ship = pirate.create_ship(name: "s1") + pirate.update(ship_attributes: { name: "s2", id: ship.id }) + assert_equal "s1", ship.reload.name end def test_reuse_already_built_new_record pirate = Pirate.new ship_built_first = pirate.build_ship - pirate.ship_attributes = { name: 'Ship 1' } + pirate.ship_attributes = { name: "Ship 1" } assert_equal ship_built_first.object_id, pirate.ship.object_id end def test_do_not_allow_assigning_foreign_key_when_reusing_existing_new_record pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?") pirate.build_ship - pirate.ship_attributes = { name: 'Ship 1', pirate_id: pirate.id + 1 } + pirate.ship_attributes = { name: "Ship 1", pirate_id: pirate.id + 1 } assert_equal pirate.id, pirate.ship.pirate_id end def test_reject_if_with_a_proc_which_returns_true_always_for_has_many - Man.accepts_nested_attributes_for :interests, :reject_if => proc {|attributes| true } + Man.accepts_nested_attributes_for :interests, reject_if: proc { |attributes| true } man = Man.create(name: "John") - interest = man.interests.create(topic: 'photography') - man.update({interests_attributes: { topic: 'gardening', id: interest.id } }) - assert_equal 'photography', interest.reload.topic + interest = man.interests.create(topic: "photography") + man.update(interests_attributes: { topic: "gardening", id: interest.id }) + assert_equal "photography", interest.reload.topic end def test_destroy_works_independent_of_reject_if - Man.accepts_nested_attributes_for :interests, :reject_if => proc {|attributes| true }, :allow_destroy => true + Man.accepts_nested_attributes_for :interests, reject_if: proc { |attributes| true }, allow_destroy: true man = Man.create(name: "Jon") - interest = man.interests.create(topic: 'the ladies') - man.update({interests_attributes: { _destroy: "1", id: interest.id } }) + interest = man.interests.create(topic: "the ladies") + man.update(interests_attributes: { _destroy: "1", id: interest.id }) assert man.reload.interests.empty? end @@ -161,27 +168,27 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase def test_has_many_association_updating_a_single_record Man.accepts_nested_attributes_for(:interests) - man = Man.create(name: 'John') - interest = man.interests.create(topic: 'photography') - man.update({interests_attributes: {topic: 'gardening', id: interest.id}}) - assert_equal 'gardening', interest.reload.topic + man = Man.create(name: "John") + interest = man.interests.create(topic: "photography") + man.update(interests_attributes: { topic: "gardening", id: interest.id }) + assert_equal "gardening", interest.reload.topic end def test_reject_if_with_blank_nested_attributes_id # When using a select list to choose an existing 'ship' id, with include_blank: true - Pirate.accepts_nested_attributes_for :ship, :reject_if => proc {|attributes| attributes[:id].blank? } + Pirate.accepts_nested_attributes_for :ship, reject_if: proc { |attributes| attributes[:id].blank? } - pirate = Pirate.new(:catchphrase => "Stop wastin' me time") - pirate.ship_attributes = { :id => "" } - assert_nothing_raised(ActiveRecord::RecordNotFound) { pirate.save! } + pirate = Pirate.new(catchphrase: "Stop wastin' me time") + pirate.ship_attributes = { id: "" } + assert_nothing_raised { pirate.save! } end def test_first_and_array_index_zero_methods_return_the_same_value_when_nested_attributes_are_set_to_update_existing_record Man.accepts_nested_attributes_for(:interests) - man = Man.create(:name => "John") - interest = man.interests.create :topic => 'gardening' + man = Man.create(name: "John") + interest = man.interests.create topic: "gardening" man = Man.find man.id - man.interests_attributes = [{:id => interest.id, :topic => 'gardening'}] + man.interests_attributes = [{ id: interest.id, topic: "gardening" }] assert_equal man.interests.first.topic, man.interests[0].topic end @@ -189,11 +196,11 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase mean_pirate_class = Class.new(Pirate) do accepts_nested_attributes_for :parrot def parrot_attributes=(attrs) - super(attrs.merge(:color => "blue")) + super(attrs.merge(color: "blue")) end end mean_pirate = mean_pirate_class.new - mean_pirate.parrot_attributes = { :name => "James" } + mean_pirate.parrot_attributes = { name: "James" } assert_equal "James", mean_pirate.parrot.name assert_equal "blue", mean_pirate.parrot.color end @@ -205,20 +212,20 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase accepts_nested_attributes_for :parrot end mean_pirate = mean_pirate_class.new - mean_pirate.parrot_attributes = { :name => "James" } + mean_pirate.parrot_attributes = { name: "James" } assert_equal "James", mean_pirate.parrot.name end end class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase def setup - @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") - @ship = @pirate.create_ship(:name => 'Nights Dirty Lightning') + @pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?") + @ship = @pirate.create_ship(name: "Nights Dirty Lightning") end def test_should_raise_argument_error_if_trying_to_build_polymorphic_belongs_to exception = assert_raise ArgumentError do - Treasure.new(:name => 'pearl', :looter_attributes => {:catchphrase => "Arrr"}) + Treasure.new(name: "pearl", looter_attributes: { catchphrase: "Arrr" }) end assert_equal "Cannot build association `looter'. Are you trying to build a polymorphic one-to-one association?", exception.message end @@ -229,15 +236,15 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase def test_should_build_a_new_record_if_there_is_no_id @ship.destroy - @pirate.reload.ship_attributes = { :name => 'Davy Jones Gold Dagger' } + @pirate.reload.ship_attributes = { name: "Davy Jones Gold Dagger" } assert !@pirate.ship.persisted? - assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name + assert_equal "Davy Jones Gold Dagger", @pirate.ship.name end def test_should_not_build_a_new_record_if_there_is_no_id_and_destroy_is_truthy @ship.destroy - @pirate.reload.ship_attributes = { :name => 'Davy Jones Gold Dagger', :_destroy => '1' } + @pirate.reload.ship_attributes = { name: "Davy Jones Gold Dagger", _destroy: "1" } assert_nil @pirate.ship end @@ -250,54 +257,54 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase end def test_should_replace_an_existing_record_if_there_is_no_id - @pirate.reload.ship_attributes = { :name => 'Davy Jones Gold Dagger' } + @pirate.reload.ship_attributes = { name: "Davy Jones Gold Dagger" } assert !@pirate.ship.persisted? - assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name - assert_equal 'Nights Dirty Lightning', @ship.name + assert_equal "Davy Jones Gold Dagger", @pirate.ship.name + assert_equal "Nights Dirty Lightning", @ship.name end def test_should_not_replace_an_existing_record_if_there_is_no_id_and_destroy_is_truthy - @pirate.reload.ship_attributes = { :name => 'Davy Jones Gold Dagger', :_destroy => '1' } + @pirate.reload.ship_attributes = { name: "Davy Jones Gold Dagger", _destroy: "1" } assert_equal @ship, @pirate.ship - assert_equal 'Nights Dirty Lightning', @pirate.ship.name + assert_equal "Nights Dirty Lightning", @pirate.ship.name end def test_should_modify_an_existing_record_if_there_is_a_matching_id - @pirate.reload.ship_attributes = { :id => @ship.id, :name => 'Davy Jones Gold Dagger' } + @pirate.reload.ship_attributes = { id: @ship.id, name: "Davy Jones Gold Dagger" } assert_equal @ship, @pirate.ship - assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name + assert_equal "Davy Jones Gold Dagger", @pirate.ship.name end def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record exception = assert_raise ActiveRecord::RecordNotFound do - @pirate.ship_attributes = { :id => 1234567890 } + @pirate.ship_attributes = { id: 1234567890 } end assert_equal "Couldn't find Ship with ID=1234567890 for Pirate with ID=#{@pirate.id}", exception.message end def test_should_take_a_hash_with_string_keys_and_update_the_associated_model - @pirate.reload.ship_attributes = { 'id' => @ship.id, 'name' => 'Davy Jones Gold Dagger' } + @pirate.reload.ship_attributes = { "id" => @ship.id, "name" => "Davy Jones Gold Dagger" } assert_equal @ship, @pirate.ship - assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name + assert_equal "Davy Jones Gold Dagger", @pirate.ship.name end def test_should_modify_an_existing_record_if_there_is_a_matching_composite_id - @ship.stub(:id, 'ABC1X') do - @pirate.ship_attributes = { :id => @ship.id, :name => 'Davy Jones Gold Dagger' } + @ship.stub(:id, "ABC1X") do + @pirate.ship_attributes = { id: @ship.id, name: "Davy Jones Gold Dagger" } - assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name + assert_equal "Davy Jones Gold Dagger", @pirate.ship.name end end def test_should_destroy_an_existing_record_if_there_is_a_matching_id_and_destroy_is_truthy @pirate.ship.destroy - [1, '1', true, 'true'].each do |truth| - ship = @pirate.reload.create_ship(name: 'Mister Pablo') + [1, "1", true, "true"].each do |truth| + ship = @pirate.reload.create_ship(name: "Mister Pablo") @pirate.update(ship_attributes: { id: ship.id, _destroy: truth }) assert_nil @pirate.reload.ship @@ -306,7 +313,7 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase end def test_should_not_destroy_an_existing_record_if_destroy_is_not_truthy - [nil, '0', 0, 'false', false].each do |not_truth| + [nil, "0", 0, "false", false].each do |not_truth| @pirate.update(ship_attributes: { id: @pirate.ship.id, _destroy: not_truth }) assert_equal @ship, @pirate.reload.ship @@ -314,32 +321,32 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase end def test_should_not_destroy_an_existing_record_if_allow_destroy_is_false - Pirate.accepts_nested_attributes_for :ship, :allow_destroy => false, :reject_if => proc(&:empty?) + Pirate.accepts_nested_attributes_for :ship, allow_destroy: false, reject_if: proc(&:empty?) - @pirate.update(ship_attributes: { id: @pirate.ship.id, _destroy: '1' }) + @pirate.update(ship_attributes: { id: @pirate.ship.id, _destroy: "1" }) assert_equal @ship, @pirate.reload.ship - Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc(&:empty?) + Pirate.accepts_nested_attributes_for :ship, allow_destroy: true, reject_if: proc(&:empty?) end def test_should_also_work_with_a_HashWithIndifferentAccess - @pirate.ship_attributes = ActiveSupport::HashWithIndifferentAccess.new(:id => @ship.id, :name => 'Davy Jones Gold Dagger') + @pirate.ship_attributes = ActiveSupport::HashWithIndifferentAccess.new(id: @ship.id, name: "Davy Jones Gold Dagger") assert @pirate.ship.persisted? - assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name + assert_equal "Davy Jones Gold Dagger", @pirate.ship.name end def test_should_work_with_update_as_well - @pirate.update({ catchphrase: 'Arr', ship_attributes: { id: @ship.id, name: 'Mister Pablo' } }) + @pirate.update(catchphrase: "Arr", ship_attributes: { id: @ship.id, name: "Mister Pablo" }) @pirate.reload - assert_equal 'Arr', @pirate.catchphrase - assert_equal 'Mister Pablo', @pirate.ship.name + assert_equal "Arr", @pirate.catchphrase + assert_equal "Mister Pablo", @pirate.ship.name end def test_should_not_destroy_the_associated_model_until_the_parent_is_saved - @pirate.attributes = { :ship_attributes => { :id => @ship.id, :_destroy => '1' } } + @pirate.attributes = { ship_attributes: { id: @ship.id, _destroy: "1" } } assert !@pirate.ship.destroyed? assert @pirate.ship.marked_for_destruction? @@ -355,56 +362,55 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase end def test_should_accept_update_only_option - @pirate.update(update_only_ship_attributes: { id: @pirate.ship.id, name: 'Mayflower' }) + @pirate.update(update_only_ship_attributes: { id: @pirate.ship.id, name: "Mayflower" }) end def test_should_create_new_model_when_nothing_is_there_and_update_only_is_true @ship.delete - @pirate.reload.update(update_only_ship_attributes: { name: 'Mayflower' }) + @pirate.reload.update(update_only_ship_attributes: { name: "Mayflower" }) assert_not_nil @pirate.ship end def test_should_update_existing_when_update_only_is_true_and_no_id_is_given @ship.delete - @ship = @pirate.create_update_only_ship(name: 'Nights Dirty Lightning') + @ship = @pirate.create_update_only_ship(name: "Nights Dirty Lightning") - @pirate.update(update_only_ship_attributes: { name: 'Mayflower' }) + @pirate.update(update_only_ship_attributes: { name: "Mayflower" }) - assert_equal 'Mayflower', @ship.reload.name + assert_equal "Mayflower", @ship.reload.name assert_equal @ship, @pirate.reload.ship end def test_should_update_existing_when_update_only_is_true_and_id_is_given @ship.delete - @ship = @pirate.create_update_only_ship(name: 'Nights Dirty Lightning') + @ship = @pirate.create_update_only_ship(name: "Nights Dirty Lightning") - @pirate.update(update_only_ship_attributes: { name: 'Mayflower', id: @ship.id }) + @pirate.update(update_only_ship_attributes: { name: "Mayflower", id: @ship.id }) - assert_equal 'Mayflower', @ship.reload.name + assert_equal "Mayflower", @ship.reload.name assert_equal @ship, @pirate.reload.ship end def test_should_destroy_existing_when_update_only_is_true_and_id_is_given_and_is_marked_for_destruction - Pirate.accepts_nested_attributes_for :update_only_ship, :update_only => true, :allow_destroy => true + Pirate.accepts_nested_attributes_for :update_only_ship, update_only: true, allow_destroy: true @ship.delete - @ship = @pirate.create_update_only_ship(name: 'Nights Dirty Lightning') + @ship = @pirate.create_update_only_ship(name: "Nights Dirty Lightning") - @pirate.update(update_only_ship_attributes: { name: 'Mayflower', id: @ship.id, _destroy: true }) + @pirate.update(update_only_ship_attributes: { name: "Mayflower", id: @ship.id, _destroy: true }) assert_nil @pirate.reload.ship assert_raise(ActiveRecord::RecordNotFound) { Ship.find(@ship.id) } - Pirate.accepts_nested_attributes_for :update_only_ship, :update_only => true, :allow_destroy => false + Pirate.accepts_nested_attributes_for :update_only_ship, update_only: true, allow_destroy: false end - end class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase def setup - @ship = Ship.new(:name => 'Nights Dirty Lightning') - @pirate = @ship.build_pirate(:catchphrase => 'Aye') + @ship = Ship.new(name: "Nights Dirty Lightning") + @pirate = @ship.build_pirate(catchphrase: "Aye") @ship.save! end @@ -414,15 +420,15 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase def test_should_build_a_new_record_if_there_is_no_id @pirate.destroy - @ship.reload.pirate_attributes = { :catchphrase => 'Arr' } + @ship.reload.pirate_attributes = { catchphrase: "Arr" } assert !@ship.pirate.persisted? - assert_equal 'Arr', @ship.pirate.catchphrase + assert_equal "Arr", @ship.pirate.catchphrase end def test_should_not_build_a_new_record_if_there_is_no_id_and_destroy_is_truthy @pirate.destroy - @ship.reload.pirate_attributes = { :catchphrase => 'Arr', :_destroy => '1' } + @ship.reload.pirate_attributes = { catchphrase: "Arr", _destroy: "1" } assert_nil @ship.pirate end @@ -435,53 +441,53 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase end def test_should_replace_an_existing_record_if_there_is_no_id - @ship.reload.pirate_attributes = { :catchphrase => 'Arr' } + @ship.reload.pirate_attributes = { catchphrase: "Arr" } assert !@ship.pirate.persisted? - assert_equal 'Arr', @ship.pirate.catchphrase - assert_equal 'Aye', @pirate.catchphrase + assert_equal "Arr", @ship.pirate.catchphrase + assert_equal "Aye", @pirate.catchphrase end def test_should_not_replace_an_existing_record_if_there_is_no_id_and_destroy_is_truthy - @ship.reload.pirate_attributes = { :catchphrase => 'Arr', :_destroy => '1' } + @ship.reload.pirate_attributes = { catchphrase: "Arr", _destroy: "1" } assert_equal @pirate, @ship.pirate - assert_equal 'Aye', @ship.pirate.catchphrase + assert_equal "Aye", @ship.pirate.catchphrase end def test_should_modify_an_existing_record_if_there_is_a_matching_id - @ship.reload.pirate_attributes = { :id => @pirate.id, :catchphrase => 'Arr' } + @ship.reload.pirate_attributes = { id: @pirate.id, catchphrase: "Arr" } assert_equal @pirate, @ship.pirate - assert_equal 'Arr', @ship.pirate.catchphrase + assert_equal "Arr", @ship.pirate.catchphrase end def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record exception = assert_raise ActiveRecord::RecordNotFound do - @ship.pirate_attributes = { :id => 1234567890 } + @ship.pirate_attributes = { id: 1234567890 } end assert_equal "Couldn't find Pirate with ID=1234567890 for Ship with ID=#{@ship.id}", exception.message end def test_should_take_a_hash_with_string_keys_and_update_the_associated_model - @ship.reload.pirate_attributes = { 'id' => @pirate.id, 'catchphrase' => 'Arr' } + @ship.reload.pirate_attributes = { "id" => @pirate.id, "catchphrase" => "Arr" } assert_equal @pirate, @ship.pirate - assert_equal 'Arr', @ship.pirate.catchphrase + assert_equal "Arr", @ship.pirate.catchphrase end def test_should_modify_an_existing_record_if_there_is_a_matching_composite_id - @pirate.stub(:id, 'ABC1X') do - @ship.pirate_attributes = { :id => @pirate.id, :catchphrase => 'Arr' } + @pirate.stub(:id, "ABC1X") do + @ship.pirate_attributes = { id: @pirate.id, catchphrase: "Arr" } - assert_equal 'Arr', @ship.pirate.catchphrase + assert_equal "Arr", @ship.pirate.catchphrase end end def test_should_destroy_an_existing_record_if_there_is_a_matching_id_and_destroy_is_truthy @ship.pirate.destroy - [1, '1', true, 'true'].each do |truth| - pirate = @ship.reload.create_pirate(catchphrase: 'Arr') + [1, "1", true, "true"].each do |truth| + pirate = @ship.reload.create_pirate(catchphrase: "Arr") @ship.update(pirate_attributes: { id: pirate.id, _destroy: truth }) assert_raise(ActiveRecord::RecordNotFound) { pirate.reload } end @@ -502,34 +508,34 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase end def test_should_not_destroy_an_existing_record_if_destroy_is_not_truthy - [nil, '0', 0, 'false', false].each do |not_truth| + [nil, "0", 0, "false", false].each do |not_truth| @ship.update(pirate_attributes: { id: @ship.pirate.id, _destroy: not_truth }) - assert_nothing_raised(ActiveRecord::RecordNotFound) { @ship.pirate.reload } + assert_nothing_raised { @ship.pirate.reload } end end def test_should_not_destroy_an_existing_record_if_allow_destroy_is_false - Ship.accepts_nested_attributes_for :pirate, :allow_destroy => false, :reject_if => proc(&:empty?) + Ship.accepts_nested_attributes_for :pirate, allow_destroy: false, reject_if: proc(&:empty?) - @ship.update(pirate_attributes: { id: @ship.pirate.id, _destroy: '1' }) - assert_nothing_raised(ActiveRecord::RecordNotFound) { @ship.pirate.reload } + @ship.update(pirate_attributes: { id: @ship.pirate.id, _destroy: "1" }) + assert_nothing_raised { @ship.pirate.reload } ensure - Ship.accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc(&:empty?) + Ship.accepts_nested_attributes_for :pirate, allow_destroy: true, reject_if: proc(&:empty?) end def test_should_work_with_update_as_well - @ship.update({ name: 'Mister Pablo', pirate_attributes: { catchphrase: 'Arr' } }) + @ship.update(name: "Mister Pablo", pirate_attributes: { catchphrase: "Arr" }) @ship.reload - assert_equal 'Mister Pablo', @ship.name - assert_equal 'Arr', @ship.pirate.catchphrase + assert_equal "Mister Pablo", @ship.name + assert_equal "Arr", @ship.pirate.catchphrase end def test_should_not_destroy_the_associated_model_until_the_parent_is_saved pirate = @ship.pirate - @ship.attributes = { :pirate_attributes => { :id => pirate.id, '_destroy' => true } } - assert_nothing_raised(ActiveRecord::RecordNotFound) { Pirate.find(pirate.id) } + @ship.attributes = { pirate_attributes: { :id => pirate.id, "_destroy" => true } } + assert_nothing_raised { Pirate.find(pirate.id) } @ship.save assert_raise(ActiveRecord::RecordNotFound) { Pirate.find(pirate.id) } end @@ -540,40 +546,40 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase def test_should_create_new_model_when_nothing_is_there_and_update_only_is_true @pirate.delete - @ship.reload.attributes = { :update_only_pirate_attributes => { :catchphrase => 'Arr' } } + @ship.reload.attributes = { update_only_pirate_attributes: { catchphrase: "Arr" } } assert !@ship.update_only_pirate.persisted? end def test_should_update_existing_when_update_only_is_true_and_no_id_is_given @pirate.delete - @pirate = @ship.create_update_only_pirate(catchphrase: 'Aye') + @pirate = @ship.create_update_only_pirate(catchphrase: "Aye") - @ship.update(update_only_pirate_attributes: { catchphrase: 'Arr' }) - assert_equal 'Arr', @pirate.reload.catchphrase + @ship.update(update_only_pirate_attributes: { catchphrase: "Arr" }) + assert_equal "Arr", @pirate.reload.catchphrase assert_equal @pirate, @ship.reload.update_only_pirate end def test_should_update_existing_when_update_only_is_true_and_id_is_given @pirate.delete - @pirate = @ship.create_update_only_pirate(catchphrase: 'Aye') + @pirate = @ship.create_update_only_pirate(catchphrase: "Aye") - @ship.update(update_only_pirate_attributes: { catchphrase: 'Arr', id: @pirate.id }) + @ship.update(update_only_pirate_attributes: { catchphrase: "Arr", id: @pirate.id }) - assert_equal 'Arr', @pirate.reload.catchphrase + assert_equal "Arr", @pirate.reload.catchphrase assert_equal @pirate, @ship.reload.update_only_pirate end def test_should_destroy_existing_when_update_only_is_true_and_id_is_given_and_is_marked_for_destruction - Ship.accepts_nested_attributes_for :update_only_pirate, :update_only => true, :allow_destroy => true + Ship.accepts_nested_attributes_for :update_only_pirate, update_only: true, allow_destroy: true @pirate.delete - @pirate = @ship.create_update_only_pirate(catchphrase: 'Aye') + @pirate = @ship.create_update_only_pirate(catchphrase: "Aye") - @ship.update(update_only_pirate_attributes: { catchphrase: 'Arr', id: @pirate.id, _destroy: true }) + @ship.update(update_only_pirate_attributes: { catchphrase: "Arr", id: @pirate.id, _destroy: true }) assert_raise(ActiveRecord::RecordNotFound) { @pirate.reload } - Ship.accepts_nested_attributes_for :update_only_pirate, :update_only => true, :allow_destroy => false + Ship.accepts_nested_attributes_for :update_only_pirate, update_only: true, allow_destroy: false end end @@ -582,11 +588,17 @@ module NestedAttributesOnACollectionAssociationTests assert_respond_to @pirate, association_setter end + def test_should_raise_an_UnknownAttributeError_for_non_existing_nested_attributes_for_has_many + exception = assert_raise ActiveModel::UnknownAttributeError do + @pirate.parrots_attributes = [{ peg_leg: true }] + end + assert_equal "unknown attribute 'peg_leg' for Parrot.", exception.message + end + def test_should_save_only_one_association_on_create - pirate = Pirate.create!({ - :catchphrase => 'Arr', - association_getter => { 'foo' => { :name => 'Grace OMalley' } } - }) + pirate = Pirate.create!( + :catchphrase => "Arr", + association_getter => { "foo" => { name: "Grace OMalley" } }) assert_equal 1, pirate.reload.send(@association_name).count end @@ -594,90 +606,90 @@ module NestedAttributesOnACollectionAssociationTests def test_should_take_a_hash_with_string_keys_and_assign_the_attributes_to_the_associated_models @alternate_params[association_getter].stringify_keys! @pirate.update @alternate_params - assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.reload.name, @child_2.reload.name] + assert_equal ["Grace OMalley", "Privateers Greed"], [@child_1.reload.name, @child_2.reload.name] end def test_should_take_an_array_and_assign_the_attributes_to_the_associated_models @pirate.send(association_setter, @alternate_params[association_getter].values) @pirate.save - assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.reload.name, @child_2.reload.name] + assert_equal ["Grace OMalley", "Privateers Greed"], [@child_1.reload.name, @child_2.reload.name] end def test_should_also_work_with_a_HashWithIndifferentAccess - @pirate.send(association_setter, ActiveSupport::HashWithIndifferentAccess.new('foo' => ActiveSupport::HashWithIndifferentAccess.new(:id => @child_1.id, :name => 'Grace OMalley'))) + @pirate.send(association_setter, ActiveSupport::HashWithIndifferentAccess.new("foo" => ActiveSupport::HashWithIndifferentAccess.new(id: @child_1.id, name: "Grace OMalley"))) @pirate.save - assert_equal 'Grace OMalley', @child_1.reload.name + assert_equal "Grace OMalley", @child_1.reload.name end def test_should_take_a_hash_and_assign_the_attributes_to_the_associated_models @pirate.attributes = @alternate_params - assert_equal 'Grace OMalley', @pirate.send(@association_name).first.name - assert_equal 'Privateers Greed', @pirate.send(@association_name).last.name + assert_equal "Grace OMalley", @pirate.send(@association_name).first.name + assert_equal "Privateers Greed", @pirate.send(@association_name).last.name end def test_should_not_load_association_when_updating_existing_records @pirate.reload - @pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }]) + @pirate.send(association_setter, [{ id: @child_1.id, name: "Grace OMalley" }]) assert ! @pirate.send(@association_name).loaded? @pirate.save assert ! @pirate.send(@association_name).loaded? - assert_equal 'Grace OMalley', @child_1.reload.name + assert_equal "Grace OMalley", @child_1.reload.name end def test_should_not_overwrite_unsaved_updates_when_loading_association @pirate.reload - @pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }]) - assert_equal 'Grace OMalley', @pirate.send(@association_name).send(:load_target).find { |r| r.id == @child_1.id }.name + @pirate.send(association_setter, [{ id: @child_1.id, name: "Grace OMalley" }]) + assert_equal "Grace OMalley", @pirate.send(@association_name).load_target.find { |r| r.id == @child_1.id }.name end def test_should_preserve_order_when_not_overwriting_unsaved_updates @pirate.reload - @pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }]) - assert_equal @child_1.id, @pirate.send(@association_name).send(:load_target).first.id + @pirate.send(association_setter, [{ id: @child_1.id, name: "Grace OMalley" }]) + assert_equal @child_1.id, @pirate.send(@association_name).load_target.first.id end def test_should_refresh_saved_records_when_not_overwriting_unsaved_updates @pirate.reload - record = @pirate.class.reflect_on_association(@association_name).klass.new(name: 'Grace OMalley') + record = @pirate.class.reflect_on_association(@association_name).klass.new(name: "Grace OMalley") @pirate.send(@association_name) << record record.save! - @pirate.send(@association_name).last.update!(name: 'Polly') - assert_equal 'Polly', @pirate.send(@association_name).send(:load_target).last.name + @pirate.send(@association_name).last.update!(name: "Polly") + assert_equal "Polly", @pirate.send(@association_name).load_target.last.name end def test_should_not_remove_scheduled_destroys_when_loading_association @pirate.reload - @pirate.send(association_setter, [{ :id => @child_1.id, :_destroy => '1' }]) - assert @pirate.send(@association_name).send(:load_target).find { |r| r.id == @child_1.id }.marked_for_destruction? + @pirate.send(association_setter, [{ id: @child_1.id, _destroy: "1" }]) + assert @pirate.send(@association_name).load_target.find { |r| r.id == @child_1.id }.marked_for_destruction? end def test_should_take_a_hash_with_composite_id_keys_and_assign_the_attributes_to_the_associated_models - @child_1.stub(:id, 'ABC1X') do - @child_2.stub(:id, 'ABC2X') do + @child_1.stub(:id, "ABC1X") do + @child_2.stub(:id, "ABC2X") do @pirate.attributes = { association_getter => [ - { :id => @child_1.id, :name => 'Grace OMalley' }, - { :id => @child_2.id, :name => 'Privateers Greed' } + { id: @child_1.id, name: "Grace OMalley" }, + { id: @child_2.id, name: "Privateers Greed" } ] } - assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.name, @child_2.name] + assert_equal ["Grace OMalley", "Privateers Greed"], [@child_1.name, @child_2.name] end end end def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record exception = assert_raise ActiveRecord::RecordNotFound do - @pirate.attributes = { association_getter => [{ :id => 1234567890 }] } + @pirate.attributes = { association_getter => [{ id: 1234567890 }] } end assert_equal "Couldn't find #{@child_1.class.name} with ID=1234567890 for Pirate with ID=#{@pirate.id}", exception.message end def test_should_raise_RecordNotFound_if_an_id_belonging_to_a_different_record_is_given - other_pirate = Pirate.create! catchphrase: 'Ahoy!' - other_child = other_pirate.send(@association_name).create! name: 'Buccaneers Servant' + other_pirate = Pirate.create! catchphrase: "Ahoy!" + other_child = other_pirate.send(@association_name).create! name: "Buccaneers Servant" exception = assert_raise ActiveRecord::RecordNotFound do @pirate.attributes = { association_getter => [{ id: other_child.id }] } @@ -688,19 +700,19 @@ module NestedAttributesOnACollectionAssociationTests def test_should_automatically_build_new_associated_models_for_each_entry_in_a_hash_where_the_id_is_missing @pirate.send(@association_name).destroy_all @pirate.reload.attributes = { - association_getter => { 'foo' => { :name => 'Grace OMalley' }, 'bar' => { :name => 'Privateers Greed' }} + association_getter => { "foo" => { name: "Grace OMalley" }, "bar" => { name: "Privateers Greed" } } } assert !@pirate.send(@association_name).first.persisted? - assert_equal 'Grace OMalley', @pirate.send(@association_name).first.name + assert_equal "Grace OMalley", @pirate.send(@association_name).first.name assert !@pirate.send(@association_name).last.persisted? - assert_equal 'Privateers Greed', @pirate.send(@association_name).last.name + assert_equal "Privateers Greed", @pirate.send(@association_name).last.name end def test_should_not_assign_destroy_key_to_a_record - assert_nothing_raised ActiveRecord::UnknownAttributeError do - @pirate.send(association_setter, { 'foo' => { '_destroy' => '0' }}) + assert_nothing_raised do + @pirate.send(association_setter, "foo" => { "_destroy" => "0" }) end end @@ -708,17 +720,17 @@ module NestedAttributesOnACollectionAssociationTests @pirate.send(@association_name).destroy_all @pirate.reload.attributes = { association_getter => { - 'foo' => { :name => 'Grace OMalley' }, - 'bar' => { :name => 'Privateers Greed', '_destroy' => '1' } + "foo" => { name: "Grace OMalley" }, + "bar" => { :name => "Privateers Greed", "_destroy" => "1" } } } assert_equal 1, @pirate.send(@association_name).length - assert_equal 'Grace OMalley', @pirate.send(@association_name).first.name + assert_equal "Grace OMalley", @pirate.send(@association_name).first.name end def test_should_ignore_new_associated_records_if_a_reject_if_proc_returns_false - @alternate_params[association_getter]['baz'] = {} + @alternate_params[association_getter]["baz"] = {} assert_no_difference("@pirate.send(@association_name).count") do @pirate.attributes = @alternate_params end @@ -726,16 +738,16 @@ module NestedAttributesOnACollectionAssociationTests def test_should_sort_the_hash_by_the_keys_before_building_new_associated_models attributes = {} - attributes['123726353'] = { :name => 'Grace OMalley' } - attributes['2'] = { :name => 'Privateers Greed' } # 2 is lower then 123726353 + attributes["123726353"] = { name: "Grace OMalley" } + attributes["2"] = { name: "Privateers Greed" } # 2 is lower then 123726353 @pirate.send(association_setter, attributes) - assert_equal ['Posideons Killer', 'Killer bandita Dionne', 'Privateers Greed', 'Grace OMalley'].to_set, @pirate.send(@association_name).map(&:name).to_set + assert_equal ["Posideons Killer", "Killer bandita Dionne", "Privateers Greed", "Grace OMalley"].to_set, @pirate.send(@association_name).map(&:name).to_set end def test_should_raise_an_argument_error_if_something_else_than_a_hash_is_passed - assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, {}) } - assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, Hash.new) } + assert_nothing_raised { @pirate.send(association_setter, {}) } + assert_nothing_raised { @pirate.send(association_setter, Hash.new) } exception = assert_raise ArgumentError do @pirate.send(association_setter, "foo") @@ -744,47 +756,47 @@ module NestedAttributesOnACollectionAssociationTests end def test_should_work_with_update_as_well - @pirate.update(catchphrase: 'Arr', - association_getter => { 'foo' => { :id => @child_1.id, :name => 'Grace OMalley' }}) + @pirate.update(catchphrase: "Arr", + association_getter => { "foo" => { id: @child_1.id, name: "Grace OMalley" } }) - assert_equal 'Grace OMalley', @child_1.reload.name + assert_equal "Grace OMalley", @child_1.reload.name end def test_should_update_existing_records_and_add_new_ones_that_have_no_id - @alternate_params[association_getter]['baz'] = { name: 'Buccaneers Servant' } - assert_difference('@pirate.send(@association_name).count', +1) do + @alternate_params[association_getter]["baz"] = { name: "Buccaneers Servant" } + assert_difference("@pirate.send(@association_name).count", +1) do @pirate.update @alternate_params end - assert_equal ['Grace OMalley', 'Privateers Greed', 'Buccaneers Servant'].to_set, @pirate.reload.send(@association_name).map(&:name).to_set + assert_equal ["Grace OMalley", "Privateers Greed", "Buccaneers Servant"].to_set, @pirate.reload.send(@association_name).map(&:name).to_set end def test_should_be_possible_to_destroy_a_record - ['1', 1, 'true', true].each do |true_variable| - record = @pirate.reload.send(@association_name).create!(:name => 'Grace OMalley') + ["1", 1, "true", true].each do |true_variable| + record = @pirate.reload.send(@association_name).create!(name: "Grace OMalley") @pirate.send(association_setter, - @alternate_params[association_getter].merge('baz' => { :id => record.id, '_destroy' => true_variable }) + @alternate_params[association_getter].merge("baz" => { :id => record.id, "_destroy" => true_variable }) ) - assert_difference('@pirate.send(@association_name).count', -1) do + assert_difference("@pirate.send(@association_name).count", -1) do @pirate.save end end end def test_should_not_destroy_the_associated_model_with_a_non_truthy_argument - [nil, '', '0', 0, 'false', false].each do |false_variable| - @alternate_params[association_getter]['foo']['_destroy'] = false_variable - assert_no_difference('@pirate.send(@association_name).count') do + [nil, "", "0", 0, "false", false].each do |false_variable| + @alternate_params[association_getter]["foo"]["_destroy"] = false_variable + assert_no_difference("@pirate.send(@association_name).count") do @pirate.update(@alternate_params) end end end def test_should_not_destroy_the_associated_model_until_the_parent_is_saved - assert_no_difference('@pirate.send(@association_name).count') do - @pirate.send(association_setter, @alternate_params[association_getter].merge('baz' => { :id => @child_1.id, '_destroy' => true })) + assert_no_difference("@pirate.send(@association_name).count") do + @pirate.send(association_setter, @alternate_params[association_getter].merge("baz" => { :id => @child_1.id, "_destroy" => true })) end - assert_difference('@pirate.send(@association_name).count', -1) { @pirate.save } + assert_difference("@pirate.send(@association_name).count", -1) { @pirate.save } end def test_should_automatically_enable_autosave_on_the_association @@ -798,10 +810,10 @@ module NestedAttributesOnACollectionAssociationTests repair_validations(Interest) do Interest.validates_presence_of(:man) - assert_difference 'Man.count' do - assert_difference 'Interest.count', 2 do - man = Man.create!(:name => 'John', - :interests_attributes => [{:topic=>'Cars'}, {:topic=>'Sports'}]) + assert_difference "Man.count" do + assert_difference "Interest.count", 2 do + man = Man.create!(name: "John", + interests_attributes: [{ topic: "Cars" }, { topic: "Sports" }]) assert_equal 2, man.interests.count end end @@ -809,8 +821,8 @@ module NestedAttributesOnACollectionAssociationTests end def test_can_use_symbols_as_object_identifier - @pirate.attributes = { :parrots_attributes => { :foo => { :name => 'Lovely Day' }, :bar => { :name => 'Blown Away' } } } - assert_nothing_raised(NoMethodError) { @pirate.save! } + @pirate.attributes = { parrots_attributes: { foo: { name: "Lovely Day" }, bar: { name: "Blown Away" } } } + assert_nothing_raised { @pirate.save! } end def test_numeric_column_changes_from_zero_to_no_empty_string @@ -818,22 +830,22 @@ module NestedAttributesOnACollectionAssociationTests repair_validations(Interest) do Interest.validates_numericality_of(:zine_id) - man = Man.create(name: 'John') - interest = man.interests.create(topic: 'bar', zine_id: 0) + man = Man.create(name: "John") + interest = man.interests.create(topic: "bar", zine_id: 0) assert interest.save - assert !man.update({interests_attributes: { id: interest.id, zine_id: 'foo' }}) + assert !man.update(interests_attributes: { id: interest.id, zine_id: "foo" }) end end private - def association_setter - @association_setter ||= "#{@association_name}_attributes=".to_sym - end + def association_setter + @association_setter ||= "#{@association_name}_attributes=".to_sym + end - def association_getter - @association_getter ||= "#{@association_name}_attributes".to_sym - end + def association_getter + @association_getter ||= "#{@association_name}_attributes".to_sym + end end class TestNestedAttributesOnAHasManyAssociation < ActiveRecord::TestCase @@ -841,16 +853,16 @@ class TestNestedAttributesOnAHasManyAssociation < ActiveRecord::TestCase @association_type = :has_many @association_name = :birds - @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") - @pirate.birds.create!(:name => 'Posideons Killer') - @pirate.birds.create!(:name => 'Killer bandita Dionne') + @pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?") + @pirate.birds.create!(name: "Posideons Killer") + @pirate.birds.create!(name: "Killer bandita Dionne") @child_1, @child_2 = @pirate.birds @alternate_params = { - :birds_attributes => { - 'foo' => { :id => @child_1.id, :name => 'Grace OMalley' }, - 'bar' => { :id => @child_2.id, :name => 'Privateers Greed' } + birds_attributes: { + "foo" => { id: @child_1.id, name: "Grace OMalley" }, + "bar" => { id: @child_2.id, name: "Privateers Greed" } } } end @@ -863,16 +875,16 @@ class TestNestedAttributesOnAHasAndBelongsToManyAssociation < ActiveRecord::Test @association_type = :has_and_belongs_to_many @association_name = :parrots - @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") - @pirate.parrots.create!(:name => 'Posideons Killer') - @pirate.parrots.create!(:name => 'Killer bandita Dionne') + @pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?") + @pirate.parrots.create!(name: "Posideons Killer") + @pirate.parrots.create!(name: "Killer bandita Dionne") @child_1, @child_2 = @pirate.parrots @alternate_params = { - :parrots_attributes => { - 'foo' => { :id => @child_1.id, :name => 'Grace OMalley' }, - 'bar' => { :id => @child_2.id, :name => 'Privateers Greed' } + parrots_attributes: { + "foo" => { id: @child_1.id, name: "Grace OMalley" }, + "bar" => { id: @child_2.id, name: "Privateers Greed" } } } end @@ -882,33 +894,33 @@ end module NestedAttributesLimitTests def teardown - Pirate.accepts_nested_attributes_for :parrots, :allow_destroy => true, :reject_if => proc(&:empty?) + Pirate.accepts_nested_attributes_for :parrots, allow_destroy: true, reject_if: proc(&:empty?) end def test_limit_with_less_records - @pirate.attributes = { :parrots_attributes => { 'foo' => { :name => 'Big Big Love' } } } - assert_difference('Parrot.count') { @pirate.save! } + @pirate.attributes = { parrots_attributes: { "foo" => { name: "Big Big Love" } } } + assert_difference("Parrot.count") { @pirate.save! } end def test_limit_with_number_exact_records - @pirate.attributes = { :parrots_attributes => { 'foo' => { :name => 'Lovely Day' }, 'bar' => { :name => 'Blown Away' } } } - assert_difference('Parrot.count', 2) { @pirate.save! } + @pirate.attributes = { parrots_attributes: { "foo" => { name: "Lovely Day" }, "bar" => { name: "Blown Away" } } } + assert_difference("Parrot.count", 2) { @pirate.save! } end def test_limit_with_exceeding_records assert_raises(ActiveRecord::NestedAttributes::TooManyRecords) do - @pirate.attributes = { :parrots_attributes => { 'foo' => { :name => 'Lovely Day' }, - 'bar' => { :name => 'Blown Away' }, - 'car' => { :name => 'The Happening' }} } + @pirate.attributes = { parrots_attributes: { "foo" => { name: "Lovely Day" }, + "bar" => { name: "Blown Away" }, + "car" => { name: "The Happening" } } } end end end class TestNestedAttributesLimitNumeric < ActiveRecord::TestCase def setup - Pirate.accepts_nested_attributes_for :parrots, :limit => 2 + Pirate.accepts_nested_attributes_for :parrots, limit: 2 - @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") + @pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?") end include NestedAttributesLimitTests @@ -916,9 +928,9 @@ end class TestNestedAttributesLimitSymbol < ActiveRecord::TestCase def setup - Pirate.accepts_nested_attributes_for :parrots, :limit => :parrots_limit + Pirate.accepts_nested_attributes_for :parrots, limit: :parrots_limit - @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?", :parrots_limit => 2) + @pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?", parrots_limit: 2) end include NestedAttributesLimitTests @@ -926,9 +938,9 @@ end class TestNestedAttributesLimitProc < ActiveRecord::TestCase def setup - Pirate.accepts_nested_attributes_for :parrots, :limit => proc { 2 } + Pirate.accepts_nested_attributes_for :parrots, limit: proc { 2 } - @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") + @pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?") end include NestedAttributesLimitTests @@ -938,45 +950,44 @@ class TestNestedAttributesWithNonStandardPrimaryKeys < ActiveRecord::TestCase fixtures :owners, :pets def setup - Owner.accepts_nested_attributes_for :pets, :allow_destroy => true + Owner.accepts_nested_attributes_for :pets, allow_destroy: true @owner = owners(:ashley) @pet1, @pet2 = pets(:chew), pets(:mochi) @params = { - :pets_attributes => { - '0' => { :id => @pet1.id, :name => 'Foo' }, - '1' => { :id => @pet2.id, :name => 'Bar' } + pets_attributes: { + "0" => { id: @pet1.id, name: "Foo" }, + "1" => { id: @pet2.id, name: "Bar" } } } end def test_should_update_existing_records_with_non_standard_primary_key @owner.update(@params) - assert_equal ['Foo', 'Bar'], @owner.pets.map(&:name) + assert_equal ["Foo", "Bar"], @owner.pets.map(&:name) end def test_attr_accessor_of_child_should_be_value_provided_during_update @owner = owners(:ashley) @pet1 = pets(:chew) - attributes = {:pets_attributes => { "1"=> { :id => @pet1.id, - :name => "Foo2", - :current_user => "John", - :_destroy=>true }}} + attributes = { pets_attributes: { "1"=> { id: @pet1.id, + name: "Foo2", + current_user: "John", + _destroy: true } } } @owner.update(attributes) - assert_equal 'John', Pet.after_destroy_output + assert_equal "John", Pet.after_destroy_output end - end class TestHasOneAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveRecord::TestCase self.use_transactional_tests = false unless supports_savepoints? def setup - @pirate = Pirate.create!(:catchphrase => "My baby takes tha mornin' train!") - @ship = @pirate.create_ship(:name => "The good ship Dollypop") - @part = @ship.parts.create!(:name => "Mast") - @trinket = @part.trinkets.create!(:name => "Necklace") + @pirate = Pirate.create!(catchphrase: "My baby takes tha mornin' train!") + @ship = @pirate.create_ship(name: "The good ship Dollypop") + @part = @ship.parts.create!(name: "Mast") + @trinket = @part.trinkets.create!(name: "Necklace") end test "when great-grandchild changed in memory, saving parent should save great-grandchild" do @@ -986,25 +997,25 @@ class TestHasOneAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveRe end test "when great-grandchild changed via attributes, saving parent should save great-grandchild" do - @pirate.attributes = {:ship_attributes => {:id => @ship.id, :parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:id => @trinket.id, :name => "changed"}]}]}} + @pirate.attributes = { ship_attributes: { id: @ship.id, parts_attributes: [{ id: @part.id, trinkets_attributes: [{ id: @trinket.id, name: "changed" }] }] } } @pirate.save assert_equal "changed", @trinket.reload.name end test "when great-grandchild marked_for_destruction via attributes, saving parent should destroy great-grandchild" do - @pirate.attributes = {:ship_attributes => {:id => @ship.id, :parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:id => @trinket.id, :_destroy => true}]}]}} - assert_difference('@part.trinkets.count', -1) { @pirate.save } + @pirate.attributes = { ship_attributes: { id: @ship.id, parts_attributes: [{ id: @part.id, trinkets_attributes: [{ id: @trinket.id, _destroy: true }] }] } } + assert_difference("@part.trinkets.count", -1) { @pirate.save } end test "when great-grandchild added via attributes, saving parent should create great-grandchild" do - @pirate.attributes = {:ship_attributes => {:id => @ship.id, :parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:name => "created"}]}]}} - assert_difference('@part.trinkets.count', 1) { @pirate.save } + @pirate.attributes = { ship_attributes: { id: @ship.id, parts_attributes: [{ id: @part.id, trinkets_attributes: [{ name: "created" }] }] } } + assert_difference("@part.trinkets.count", 1) { @pirate.save } end test "when extra records exist for associations, validate (which calls nested_records_changed_for_autosave?) should not load them up" do @trinket.name = "changed" - Ship.create!(:pirate => @pirate, :name => "The Black Rock") - ShipPart.create!(:ship => @ship, :name => "Stern") + Ship.create!(pirate: @pirate, name: "The Black Rock") + ShipPart.create!(ship: @ship, name: "Stern") assert_no_queries { @pirate.valid? } end end @@ -1013,27 +1024,27 @@ class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveR self.use_transactional_tests = false unless supports_savepoints? def setup - @ship = Ship.create!(:name => "The good ship Dollypop") - @part = @ship.parts.create!(:name => "Mast") - @trinket = @part.trinkets.create!(:name => "Necklace") + @ship = Ship.create!(name: "The good ship Dollypop") + @part = @ship.parts.create!(name: "Mast") + @trinket = @part.trinkets.create!(name: "Necklace") end test "if association is not loaded and association record is saved and then in memory record attributes should be saved" do - @ship.parts_attributes=[{:id => @part.id,:name =>'Deck'}] + @ship.parts_attributes=[{ id: @part.id,name: "Deck" }] assert_equal 1, @ship.association(:parts).target.size - assert_equal 'Deck', @ship.parts[0].name + assert_equal "Deck", @ship.parts[0].name end test "if association is not loaded and child doesn't change and I am saving a grandchild then in memory record should be used" do - @ship.parts_attributes=[{:id => @part.id,:trinkets_attributes =>[{:id => @trinket.id, :name => 'Ruby'}]}] + @ship.parts_attributes=[{ id: @part.id,trinkets_attributes: [{ id: @trinket.id, name: "Ruby" }] }] assert_equal 1, @ship.association(:parts).target.size - assert_equal 'Mast', @ship.parts[0].name + assert_equal "Mast", @ship.parts[0].name assert_no_difference("@ship.parts[0].association(:trinkets).target.size") do @ship.parts[0].association(:trinkets).target.size end - assert_equal 'Ruby', @ship.parts[0].trinkets[0].name + assert_equal "Ruby", @ship.parts[0].trinkets[0].name @ship.save - assert_equal 'Ruby', @ship.parts[0].trinkets[0].name + assert_equal "Ruby", @ship.parts[0].trinkets[0].name end test "when grandchild changed in memory, saving parent should save grandchild" do @@ -1043,25 +1054,25 @@ class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveR end test "when grandchild changed via attributes, saving parent should save grandchild" do - @ship.attributes = {:parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:id => @trinket.id, :name => "changed"}]}]} + @ship.attributes = { parts_attributes: [{ id: @part.id, trinkets_attributes: [{ id: @trinket.id, name: "changed" }] }] } @ship.save assert_equal "changed", @trinket.reload.name end test "when grandchild marked_for_destruction via attributes, saving parent should destroy grandchild" do - @ship.attributes = {:parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:id => @trinket.id, :_destroy => true}]}]} - assert_difference('@part.trinkets.count', -1) { @ship.save } + @ship.attributes = { parts_attributes: [{ id: @part.id, trinkets_attributes: [{ id: @trinket.id, _destroy: true }] }] } + assert_difference("@part.trinkets.count", -1) { @ship.save } end test "when grandchild added via attributes, saving parent should create grandchild" do - @ship.attributes = {:parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:name => "created"}]}]} - assert_difference('@part.trinkets.count', 1) { @ship.save } + @ship.attributes = { parts_attributes: [{ id: @part.id, trinkets_attributes: [{ name: "created" }] }] } + assert_difference("@part.trinkets.count", 1) { @ship.save } end test "when extra records exist for associations, validate (which calls nested_records_changed_for_autosave?) should not load them up" do @trinket.name = "changed" - Ship.create!(:name => "The Black Rock") - ShipPart.create!(:ship => @ship, :name => "Stern") + Ship.create!(name: "The Black Rock") + ShipPart.create!(ship: @ship, name: "Stern") assert_no_queries { @ship.valid? } end diff --git a/activerecord/test/cases/nested_attributes_with_callbacks_test.rb b/activerecord/test/cases/nested_attributes_with_callbacks_test.rb index 43a69928b6..8954e8c7e3 100644 --- a/activerecord/test/cases/nested_attributes_with_callbacks_test.rb +++ b/activerecord/test/cases/nested_attributes_with_callbacks_test.rb @@ -4,24 +4,24 @@ require "models/bird" class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase Pirate.has_many(:birds_with_add_load, - :class_name => "Bird", - :before_add => proc { |p,b| + class_name: "Bird", + before_add: proc { |p,b| @@add_callback_called << b p.birds_with_add_load.to_a }) Pirate.has_many(:birds_with_add, - :class_name => "Bird", - :before_add => proc { |p,b| @@add_callback_called << b }) + class_name: "Bird", + before_add: proc { |p,b| @@add_callback_called << b }) Pirate.accepts_nested_attributes_for(:birds_with_add_load, :birds_with_add, - :allow_destroy => true) + allow_destroy: true) def setup @@add_callback_called = [] @pirate = Pirate.new.tap do |pirate| pirate.catchphrase = "Don't call me!" - pirate.birds_attributes = [{:name => 'Bird1'},{:name => 'Bird2'}] + pirate.birds_attributes = [{ name: "Bird1" },{ name: "Bird2" }] pirate.save! end @birds = @pirate.birds.to_a @@ -46,17 +46,17 @@ class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase end def new_bird_attributes - [{'name' => "New Bird"}] + [{ "name" => "New Bird" }] end def destroy_bird_attributes - [{'id' => bird_to_destroy.id.to_s, "_destroy" => true}] + [{ "id" => bird_to_destroy.id.to_s, "_destroy" => true }] end def update_new_and_destroy_bird_attributes - [{'id' => @birds[0].id.to_s, 'name' => 'New Name'}, - {'name' => "New Bird"}, - {'id' => bird_to_destroy.id.to_s, "_destroy" => true}] + [{ "id" => @birds[0].id.to_s, "name" => "New Name" }, + { "name" => "New Bird" }, + { "id" => bird_to_destroy.id.to_s, "_destroy" => true }] end # Characterizing when :before_add callback is called @@ -136,9 +136,9 @@ class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase def assert_assignment_affects_records_in_target(association_name) association = @pirate.send(association_name) - assert association.detect {|b| b == bird_to_update }.name_changed?, - 'Update record not updated' - assert association.detect {|b| b == bird_to_destroy }.marked_for_destruction?, - 'Destroy record not marked for destruction' + assert association.detect { |b| b == bird_to_update }.name_changed?, + "Update record not updated" + assert association.detect { |b| b == bird_to_destroy }.marked_for_destruction?, + "Destroy record not marked for destruction" end end diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 56092aaa0c..688c3ed2b1 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -1,30 +1,30 @@ require "cases/helper" -require 'models/aircraft' -require 'models/post' -require 'models/comment' -require 'models/author' -require 'models/topic' -require 'models/reply' -require 'models/category' -require 'models/company' -require 'models/developer' -require 'models/computer' -require 'models/project' -require 'models/minimalistic' -require 'models/warehouse_thing' -require 'models/parrot' -require 'models/minivan' -require 'models/owner' -require 'models/person' -require 'models/pet' -require 'models/ship' -require 'models/toy' -require 'models/admin' -require 'models/admin/user' -require 'rexml/document' +require "models/aircraft" +require "models/post" +require "models/comment" +require "models/author" +require "models/topic" +require "models/reply" +require "models/category" +require "models/company" +require "models/developer" +require "models/computer" +require "models/project" +require "models/minimalistic" +require "models/warehouse_thing" +require "models/parrot" +require "models/minivan" +require "models/owner" +require "models/person" +require "models/pet" +require "models/ship" +require "models/toy" +require "models/admin" +require "models/admin/user" +require "rexml/document" class PersistenceTest < ActiveRecord::TestCase - fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :author_addresses, :categorizations, :categories, :posts, :minivans, :pets, :toys + fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, "warehouse-things", :authors, :author_addresses, :categorizations, :categories, :posts, :minivans, :pets, :toys # Oracle UPDATE does not support ORDER BY unless current_adapter?(:OracleAdapter) @@ -39,18 +39,18 @@ class PersistenceTest < ActiveRecord::TestCase assert_equal authors(:david).id + 1, authors(:mary).id # make sure there is going to be a duplicate PK error test_update_with_order_succeeds = lambda do |order| begin - Author.order(order).update_all('id = id + 1') + Author.order(order).update_all("id = id + 1") rescue ActiveRecord::ActiveRecordError false end end - if test_update_with_order_succeeds.call('id DESC') - assert !test_update_with_order_succeeds.call('id ASC') # test that this wasn't a fluke and using an incorrect order results in an exception + if test_update_with_order_succeeds.call("id DESC") + assert !test_update_with_order_succeeds.call("id ASC") # test that this wasn't a fluke and using an incorrect order results in an exception else # test that we're failing because the current Arel's engine doesn't support UPDATE ORDER BY queries is using subselects instead assert_sql(/\AUPDATE .+ \(SELECT .* ORDER BY id DESC\)\Z/i) do - test_update_with_order_succeeds.call('id DESC') + test_update_with_order_succeeds.call("id DESC") end end end @@ -83,7 +83,7 @@ class PersistenceTest < ActiveRecord::TestCase end def test_delete_all_with_joins_and_where_part_is_hash - where_args = {:toys => {:name => 'Bone'}} + where_args = { toys: { name: "Bone" } } count = Pet.joins(:toys).where(where_args).count assert_equal count, 1 @@ -91,7 +91,7 @@ class PersistenceTest < ActiveRecord::TestCase end def test_delete_all_with_joins_and_where_part_is_not_hash - where_args = ['toys.name = ?', 'Bone'] + where_args = ["toys.name = ?", "Bone"] count = Pet.joins(:toys).where(where_args).count assert_equal count, 1 @@ -133,10 +133,10 @@ class PersistenceTest < ActiveRecord::TestCase def test_destroy_all conditions = "author_name = 'Mary'" - topics_by_mary = Topic.all.merge!(:where => conditions, :order => 'id').to_a + topics_by_mary = Topic.all.merge!(where: conditions, order: "id").to_a assert ! topics_by_mary.empty? - assert_difference('Topic.count', -topics_by_mary.size) do + assert_difference("Topic.count", -topics_by_mary.size) do destroyed = Topic.where(conditions).destroy_all.sort_by(&:id) assert_equal topics_by_mary, destroyed assert destroyed.all?(&:frozen?), "destroyed topics should be frozen" @@ -144,9 +144,9 @@ class PersistenceTest < ActiveRecord::TestCase end def test_destroy_many - clients = Client.all.merge!(:order => 'id').find([2, 3]) + clients = Client.all.merge!(order: "id").find([2, 3]) - assert_difference('Client.count', -2) do + assert_difference("Client.count", -2) do destroyed = Client.destroy([2, 3]).sort_by(&:id) assert_equal clients, destroyed assert destroyed.all?(&:frozen?), "destroyed clients should be frozen" @@ -159,7 +159,7 @@ class PersistenceTest < ActiveRecord::TestCase end def test_becomes_includes_errors - company = Company.new(:name => nil) + company = Company.new(name: nil) assert !company.valid? original_errors = company.errors client = company.becomes(Client) @@ -170,7 +170,7 @@ class PersistenceTest < ActiveRecord::TestCase child_class = Class.new(Admin::User) do store_accessor :settings, :foo - def self.name; 'Admin::ChildUser'; end + def self.name; "Admin::ChildUser"; end end admin = Admin::User.new @@ -231,7 +231,7 @@ class PersistenceTest < ActiveRecord::TestCase end def test_save! - topic = Topic.new(:title => "New Topic") + topic = Topic.new(title: "New Topic") assert topic.save! reply = WrongReply.new @@ -261,7 +261,7 @@ class PersistenceTest < ActiveRecord::TestCase end def test_save_for_record_with_only_primary_key_that_is_provided - assert_nothing_raised { Minimalistic.create!(:id => 2) } + assert_nothing_raised { Minimalistic.create!(id: 2) } end def test_save_with_duping_of_destroyed_object @@ -281,9 +281,9 @@ class PersistenceTest < ActiveRecord::TestCase def test_create_columns_not_equal_attributes topic = Topic.instantiate( - 'attributes' => { - 'title' => 'Another New Topic', - 'does_not_exist' => 'test' + "attributes" => { + "title" => "Another New Topic", + "does_not_exist" => "test" } ) assert_nothing_raised { topic.save } @@ -330,8 +330,8 @@ class PersistenceTest < ActiveRecord::TestCase topic.title = "Still another topic" topic.save - topic_reloaded = Topic.instantiate(topic.attributes.merge('does_not_exist' => 'test')) - topic_reloaded.title = 'A New Topic' + topic_reloaded = Topic.instantiate(topic.attributes.merge("does_not_exist" => "test")) + topic_reloaded.title = "A New Topic" assert_nothing_raised { topic_reloaded.save } end @@ -371,7 +371,7 @@ class PersistenceTest < ActiveRecord::TestCase def test_update_after_create klass = Class.new(Topic) do - def self.name; 'Topic'; end + def self.name; "Topic"; end after_create do update_attribute("author_name", "David") end @@ -387,24 +387,24 @@ class PersistenceTest < ActiveRecord::TestCase def test_update_attribute_does_not_run_sql_if_attribute_is_not_changed klass = Class.new(Topic) do - def self.name; 'Topic'; end + def self.name; "Topic"; end end - topic = klass.create(title: 'Another New Topic') + topic = klass.create(title: "Another New Topic") assert_queries(0) do - topic.update_attribute(:title, 'Another New Topic') + assert topic.update_attribute(:title, "Another New Topic") end end def test_update_does_not_run_sql_if_record_has_not_changed - topic = Topic.create(title: 'Another New Topic') - assert_queries(0) { topic.update(title: 'Another New Topic') } - assert_queries(0) { topic.update_attributes(title: 'Another New Topic') } + topic = Topic.create(title: "Another New Topic") + assert_queries(0) { assert topic.update(title: "Another New Topic") } + assert_queries(0) { assert topic.update_attributes(title: "Another New Topic") } end def test_delete topic = Topic.find(1) - assert_equal topic, topic.delete, 'topic.delete did not return self' - assert topic.frozen?, 'topic not frozen after delete' + assert_equal topic, topic.delete, "topic.delete did not return self" + assert topic.frozen?, "topic not frozen after delete" assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) } end @@ -415,15 +415,15 @@ class PersistenceTest < ActiveRecord::TestCase def test_destroy topic = Topic.find(1) - assert_equal topic, topic.destroy, 'topic.destroy did not return self' - assert topic.frozen?, 'topic not frozen after destroy' + assert_equal topic, topic.destroy, "topic.destroy did not return self" + assert topic.frozen?, "topic not frozen after destroy" assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) } end def test_destroy! topic = Topic.find(1) - assert_equal topic, topic.destroy!, 'topic.destroy! did not return self' - assert topic.frozen?, 'topic not frozen after destroy!' + assert_equal topic, topic.destroy!, "topic.destroy! did not return self" + assert topic.frozen?, "topic not frozen after destroy!" assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) } end @@ -436,17 +436,17 @@ class PersistenceTest < ActiveRecord::TestCase assert_equal "bulk updated!", Topic.find(1).content assert_equal "bulk updated!", Topic.find(2).content - assert_equal Topic.count, Topic.update_all(['content = ?', 'bulk updated again!']) + assert_equal Topic.count, Topic.update_all(["content = ?", "bulk updated again!"]) assert_equal "bulk updated again!", Topic.find(1).content assert_equal "bulk updated again!", Topic.find(2).content - assert_equal Topic.count, Topic.update_all(['content = ?', nil]) + assert_equal Topic.count, Topic.update_all(["content = ?", nil]) assert_nil Topic.find(1).content end def test_update_all_with_hash assert_not_nil Topic.find(1).last_read - assert_equal Topic.count, Topic.update_all(:content => 'bulk updated with hash!', :last_read => nil) + assert_equal Topic.count, Topic.update_all(content: "bulk updated with hash!", last_read: nil) assert_equal "bulk updated with hash!", Topic.find(1).content assert_equal "bulk updated with hash!", Topic.find(2).content assert_nil Topic.find(1).last_read @@ -454,7 +454,7 @@ class PersistenceTest < ActiveRecord::TestCase end def test_update_all_with_non_standard_table_name - assert_equal 1, WarehouseThing.where(id: 1).update_all(['value = ?', 0]) + assert_equal 1, WarehouseThing.where(id: 1).update_all(["value = ?", 0]) assert_equal 0, WarehouseThing.find(1).value end @@ -496,20 +496,20 @@ class PersistenceTest < ActiveRecord::TestCase end def test_update_attribute_for_readonly_attribute - minivan = Minivan.find('m1') - assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_attribute(:color, 'black') } + minivan = Minivan.find("m1") + assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_attribute(:color, "black") } end def test_update_attribute_with_one_updated t = Topic.first - t.update_attribute(:title, 'super_title') - assert_equal 'super_title', t.title + t.update_attribute(:title, "super_title") + assert_equal "super_title", t.title assert !t.changed?, "topic should not have changed" assert !t.title_changed?, "title should not have changed" - assert_nil t.title_change, 'title change should be nil' + assert_nil t.title_change, "title change should be nil" t.reload - assert_equal 'super_title', t.title + assert_equal "super_title", t.title end def test_update_attribute_for_updated_at_on @@ -569,17 +569,17 @@ class PersistenceTest < ActiveRecord::TestCase end def test_update_column_with_model_having_primary_key_other_than_id - minivan = Minivan.find('m1') - new_name = 'sebavan' + minivan = Minivan.find("m1") + new_name = "sebavan" minivan.update_column(:name, new_name) assert_equal new_name, minivan.name end def test_update_column_for_readonly_attribute - minivan = Minivan.find('m1') + minivan = Minivan.find("m1") prev_color = minivan.color - assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_column(:color, 'black') } + assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_column(:color, "black") } assert_equal prev_color, minivan.color end @@ -598,31 +598,31 @@ class PersistenceTest < ActiveRecord::TestCase end def test_update_column_with_one_changed_and_one_updated - t = Topic.order('id').limit(1).first + t = Topic.order("id").limit(1).first author_name = t.author_name - t.author_name = 'John' - t.update_column(:title, 'super_title') - assert_equal 'John', t.author_name - assert_equal 'super_title', t.title + t.author_name = "John" + t.update_column(:title, "super_title") + assert_equal "John", t.author_name + assert_equal "super_title", t.title assert t.changed?, "topic should have changed" assert t.author_name_changed?, "author_name should have changed" t.reload assert_equal author_name, t.author_name - assert_equal 'super_title', t.title + assert_equal "super_title", t.title end def test_update_column_with_default_scope developer = DeveloperCalledDavid.first - developer.name = 'John' + developer.name = "John" developer.save! - assert developer.update_column(:name, 'Will'), 'did not update record due to default scope' + assert developer.update_column(:name, "Will"), "did not update record due to default scope" end def test_update_columns topic = Topic.find(1) - topic.update_columns({ "approved" => true, title: "Sebastian Topic" }) + topic.update_columns("approved" => true, title: "Sebastian Topic") assert topic.approved? assert_equal "Sebastian Topic", topic.title topic.reload @@ -643,35 +643,35 @@ class PersistenceTest < ActiveRecord::TestCase def test_update_columns_should_raise_exception_if_new_record topic = Topic.new - assert_raises(ActiveRecord::ActiveRecordError) { topic.update_columns({ approved: false }) } + assert_raises(ActiveRecord::ActiveRecordError) { topic.update_columns(approved: false) } end def test_update_columns_should_not_leave_the_object_dirty topic = Topic.find(1) - topic.update({ "content" => "--- Have a nice day\n...\n", :author_name => "Jose" }) + topic.update("content" => "--- Have a nice day\n...\n", :author_name => "Jose") topic.reload - topic.update_columns({ content: "--- You too\n...\n", "author_name" => "Sebastian" }) + topic.update_columns(content: "--- You too\n...\n", "author_name" => "Sebastian") assert_equal [], topic.changed topic.reload - topic.update_columns({ content: "--- Have a nice day\n...\n", author_name: "Jose" }) + topic.update_columns(content: "--- Have a nice day\n...\n", author_name: "Jose") assert_equal [], topic.changed end def test_update_columns_with_model_having_primary_key_other_than_id - minivan = Minivan.find('m1') - new_name = 'sebavan' + minivan = Minivan.find("m1") + new_name = "sebavan" minivan.update_columns(name: new_name) assert_equal new_name, minivan.name end def test_update_columns_with_one_readonly_attribute - minivan = Minivan.find('m1') + minivan = Minivan.find("m1") prev_color = minivan.color prev_name = minivan.name - assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_columns({ name: "My old minivan", color: 'black' }) } + assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_columns(name: "My old minivan", color: "black") } assert_equal prev_color, minivan.color assert_equal prev_name, minivan.name @@ -697,18 +697,18 @@ class PersistenceTest < ActiveRecord::TestCase end def test_update_columns_with_one_changed_and_one_updated - t = Topic.order('id').limit(1).first + t = Topic.order("id").limit(1).first author_name = t.author_name - t.author_name = 'John' - t.update_columns(title: 'super_title') - assert_equal 'John', t.author_name - assert_equal 'super_title', t.title + t.author_name = "John" + t.update_columns(title: "super_title") + assert_equal "John", t.author_name + assert_equal "super_title", t.title assert t.changed?, "topic should have changed" assert t.author_name_changed?, "author_name should have changed" t.reload assert_equal author_name, t.author_name - assert_equal 'super_title', t.title + assert_equal "super_title", t.title end def test_update_columns_changing_id @@ -726,10 +726,10 @@ class PersistenceTest < ActiveRecord::TestCase def test_update_columns_with_default_scope developer = DeveloperCalledDavid.first - developer.name = 'John' + developer.name = "John" developer.save! - assert developer.update_columns(name: 'Will'), 'did not update record due to default scope' + assert developer.update_columns(name: "Will"), "did not update record due to default scope" end def test_update @@ -840,7 +840,7 @@ class PersistenceTest < ActiveRecord::TestCase end def test_persisted_returns_boolean - developer = Developer.new(:name => "Jose") + developer = Developer.new(name: "Jose") assert_equal false, developer.persisted? developer.save! assert_equal true, developer.persisted? @@ -909,7 +909,7 @@ class PersistenceTest < ActiveRecord::TestCase end def test_reload_removes_custom_selects - post = Post.select('posts.*, 1 as wibble').last! + post = Post.select("posts.*, 1 as wibble").last! assert_equal 1, post[:wibble] assert_nil post.reload[:wibble] @@ -930,8 +930,8 @@ class PersistenceTest < ActiveRecord::TestCase def test_reload_via_querycache ActiveRecord::Base.connection.enable_query_cache! ActiveRecord::Base.connection.clear_query_cache - assert ActiveRecord::Base.connection.query_cache_enabled, 'cache should be on' - parrot = Parrot.create(:name => 'Shane') + assert ActiveRecord::Base.connection.query_cache_enabled, "cache should be on" + parrot = Parrot.create(name: "Shane") # populate the cache with the SELECT result found_parrot = Parrot.find(parrot.id) @@ -940,16 +940,16 @@ class PersistenceTest < ActiveRecord::TestCase # Manually update the 'name' attribute in the DB directly assert_equal 1, ActiveRecord::Base.connection.query_cache.length ActiveRecord::Base.uncached do - found_parrot.name = 'Mary' + found_parrot.name = "Mary" found_parrot.save end # Now reload, and verify that it gets the DB version, and not the querycache version found_parrot.reload - assert_equal 'Mary', found_parrot.name + assert_equal "Mary", found_parrot.name found_parrot = Parrot.find(parrot.id) - assert_equal 'Mary', found_parrot.name + assert_equal "Mary", found_parrot.name ensure ActiveRecord::Base.connection.disable_query_cache! end @@ -967,16 +967,15 @@ class PersistenceTest < ActiveRecord::TestCase self.table_name = :widgets end - instance = widget.create!({ - name: 'Bob', + instance = widget.create!( + name: "Bob", created_at: 1.day.ago, - updated_at: 1.day.ago - }) + updated_at: 1.day.ago) created_at = instance.created_at updated_at = instance.updated_at - instance.name = 'Barb' + instance.name = "Barb" instance.save!(touch: false) assert_equal instance.created_at, created_at assert_equal instance.updated_at, updated_at diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb index bca50dd008..f1b0d08765 100644 --- a/activerecord/test/cases/pooled_connections_test.rb +++ b/activerecord/test/cases/pooled_connections_test.rb @@ -18,7 +18,7 @@ class PooledConnectionsTest < ActiveRecord::TestCase # Will deadlock due to lack of Monitor timeouts in 1.9 def checkout_checkin_connections(pool_size, threads) - ActiveRecord::Base.establish_connection(@connection.merge({:pool => pool_size, :checkout_timeout => 0.5})) + ActiveRecord::Base.establish_connection(@connection.merge(pool: pool_size, checkout_timeout: 0.5)) @connection_count = 0 @timed_out = 0 threads.times do @@ -36,7 +36,7 @@ class PooledConnectionsTest < ActiveRecord::TestCase end def checkout_checkin_connections_loop(pool_size, loops) - ActiveRecord::Base.establish_connection(@connection.merge({:pool => pool_size, :checkout_timeout => 0.5})) + ActiveRecord::Base.establish_connection(@connection.merge(pool: pool_size, checkout_timeout: 0.5)) @connection_count = 0 @timed_out = 0 loops.times do @@ -66,7 +66,7 @@ class PooledConnectionsTest < ActiveRecord::TestCase end def test_pooled_connection_remove - ActiveRecord::Base.establish_connection(@connection.merge({:pool => 2, :checkout_timeout => 0.5})) + ActiveRecord::Base.establish_connection(@connection.merge(pool: 2, checkout_timeout: 0.5)) old_connection = ActiveRecord::Base.connection extra_connection = ActiveRecord::Base.connection_pool.checkout ActiveRecord::Base.connection_pool.remove(extra_connection) @@ -75,7 +75,7 @@ class PooledConnectionsTest < ActiveRecord::TestCase private - def add_record(name) - ActiveRecord::Base.connection_pool.with_connection { Project.create! :name => name } - end + def add_record(name) + ActiveRecord::Base.connection_pool.with_connection { Project.create! name: name } + end end unless in_memory_db? diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index b918b36b94..31d612abd1 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -1,12 +1,12 @@ require "cases/helper" -require 'support/schema_dumping_helper' -require 'models/topic' -require 'models/reply' -require 'models/subscriber' -require 'models/movie' -require 'models/keyboard' -require 'models/mixed_case_monkey' -require 'models/dashboard' +require "support/schema_dumping_helper" +require "models/topic" +require "models/reply" +require "models/subscriber" +require "models/movie" +require "models/keyboard" +require "models/mixed_case_monkey" +require "models/dashboard" class PrimaryKeysTest < ActiveRecord::TestCase fixtures :topics, :subscribers, :movies, :mixed_case_monkeys @@ -54,9 +54,9 @@ class PrimaryKeysTest < ActiveRecord::TestCase def test_customized_primary_key_auto_assigns_on_save Keyboard.delete_all - keyboard = Keyboard.new(:name => 'HHKB') + keyboard = Keyboard.new(name: "HHKB") assert_nothing_raised { keyboard.save! } - assert_equal keyboard.id, Keyboard.find_by_name('HHKB').id + assert_equal keyboard.id, Keyboard.find_by_name("HHKB").id end def test_customized_primary_key_can_be_get_before_saving @@ -67,9 +67,9 @@ class PrimaryKeysTest < ActiveRecord::TestCase def test_customized_string_primary_key_settable_before_save subscriber = Subscriber.new - assert_nothing_raised { subscriber.id = 'webster123' } - assert_equal 'webster123', subscriber.id - assert_equal 'webster123', subscriber.nick + assert_nothing_raised { subscriber.id = "webster123" } + assert_equal "webster123", subscriber.id + assert_equal "webster123", subscriber.nick end def test_string_key @@ -114,7 +114,7 @@ class PrimaryKeysTest < ActiveRecord::TestCase 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) } + 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) } @@ -130,7 +130,7 @@ class PrimaryKeysTest < ActiveRecord::TestCase end def test_supports_primary_key - assert_nothing_raised NoMethodError do + assert_nothing_raised do ActiveRecord::Base.connection.supports_primary_key? end end @@ -138,15 +138,15 @@ class PrimaryKeysTest < ActiveRecord::TestCase 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' + self.table_name = "developers" end - assert_equal 'id', klass.primary_key + assert_equal "id", klass.primary_key end def test_primary_key_returns_nil_if_it_does_not_exist klass = Class.new(ActiveRecord::Base) do - self.table_name = 'developers_projects' + self.table_name = "developers_projects" end assert_nil klass.primary_key @@ -166,12 +166,20 @@ class PrimaryKeysTest < ActiveRecord::TestCase end def test_primary_key_update_with_custom_key_name - dashboard = Dashboard.create!(dashboard_id: '1') - dashboard.id = '2' + dashboard = Dashboard.create!(dashboard_id: "1") + dashboard.id = "2" dashboard.save! dashboard = Dashboard.first - assert_equal '2', dashboard.id + assert_equal "2", dashboard.id + end + + def test_create_without_primary_key_no_extra_query + klass = Class.new(ActiveRecord::Base) do + self.table_name = "dashboards" + end + klass.create! # warmup schema cache + assert_queries(3, ignore_none: true) { klass.create! } end if current_adapter?(:PostgreSQLAdapter) @@ -197,13 +205,13 @@ class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase connection = ActiveRecord::Base.remove_connection model = Class.new(ActiveRecord::Base) - model.primary_key = 'foo' + model.primary_key = "foo" - assert_equal 'foo', model.primary_key + assert_equal "foo", model.primary_key ActiveRecord::Base.establish_connection(connection) - assert_equal 'foo', model.primary_key + assert_equal "foo", model.primary_key end end end @@ -228,9 +236,10 @@ class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase def test_any_type_primary_key assert_equal "code", Barcode.primary_key - column_type = Barcode.type_for_attribute(Barcode.primary_key) - assert_equal :string, column_type.type - assert_equal 42, column_type.limit + column = Barcode.column_for_attribute(Barcode.primary_key) + assert_not column.null + assert_equal :string, column.type + assert_equal 42, column.limit end test "schema dump primary key includes type and options" do @@ -246,6 +255,7 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase def setup @connection = ActiveRecord::Base.connection + @connection.schema_cache.clear! @connection.create_table(:barcodes, primary_key: ["region", "code"], force: true) do |t| t.string :region t.integer :code @@ -261,10 +271,15 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase end def test_primary_key_issues_warning + model = Class.new(ActiveRecord::Base) do + def self.table_name + "barcodes" + end + end warning = capture(:stderr) do - assert_nil @connection.primary_key("barcodes") + assert_nil model.primary_key end - assert_match(/WARNING: Rails does not support composite primary key\./, warning) + assert_match(/WARNING: Active Record does not support composite primary key\./, warning) end def test_collectly_dump_composite_primary_key @@ -274,18 +289,6 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase end if current_adapter?(:Mysql2Adapter) - class PrimaryKeyWithAnsiQuotesTest < ActiveRecord::TestCase - self.use_transactional_tests = false - - def test_primary_key_method_with_ansi_quotes - con = ActiveRecord::Base.connection - con.execute("SET SESSION sql_mode='ANSI_QUOTES'") - assert_equal "id", con.primary_key("topics") - ensure - con.reconnect! - end - end - class PrimaryKeyBigintNilDefaultTest < ActiveRecord::TestCase include SchemaDumpingHelper @@ -301,7 +304,7 @@ if current_adapter?(:Mysql2Adapter) end test "primary key with bigint allows default override via nil" do - column = @connection.columns(:bigint_defaults).find { |c| c.name == 'id' } + column = @connection.columns(:bigint_defaults).find { |c| c.name == "id" } assert column.bigint? assert_not column.auto_increment? end @@ -350,16 +353,16 @@ if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter) test "schema dump primary key with bigserial" do schema = dump_table_schema "widgets" if current_adapter?(:PostgreSQLAdapter) - assert_match %r{create_table "widgets", id: :bigserial}, schema + assert_match %r{create_table "widgets", id: :bigserial, force: :cascade}, schema else - assert_match %r{create_table "widgets", id: :bigint}, schema + assert_match %r{create_table "widgets", id: :bigint, force: :cascade}, schema end 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) - column = @connection.columns(:widgets).find { |c| c.name == 'id' } + column = @connection.columns(:widgets).find { |c| c.name == "id" } assert column.auto_increment? assert_equal :integer, column.type assert_equal 8, column.limit diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index d84653e4c9..29b2deea26 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -1,22 +1,45 @@ require "cases/helper" -require 'models/topic' -require 'models/task' -require 'models/category' -require 'models/post' -require 'rack' +require "models/topic" +require "models/task" +require "models/category" +require "models/post" +require "rack" class QueryCacheTest < ActiveRecord::TestCase + self.use_transactional_tests = false + fixtures :tasks, :topics, :categories, :posts, :categories_posts - teardown do + class ShouldNotHaveExceptionsLogger < ActiveRecord::LogSubscriber + attr_reader :logger + + def initialize + super + @logger = ::Logger.new File::NULL + @exception = false + end + + def exception? + @exception + end + + def sql(event) + super + rescue + @exception = true + end + end + + def teardown Task.connection.clear_query_cache ActiveRecord::Base.connection.disable_query_cache! + super end def test_exceptional_middleware_clears_and_disables_cache_on_error - assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off' + assert !ActiveRecord::Base.connection.query_cache_enabled, "cache off" - mw = ActiveRecord::QueryCache.new lambda { |env| + mw = middleware { |env| Task.find 1 Task.find 1 assert_equal 1, ActiveRecord::Base.connection.query_cache.length @@ -25,44 +48,55 @@ class QueryCacheTest < ActiveRecord::TestCase assert_raises(RuntimeError) { mw.call({}) } assert_equal 0, ActiveRecord::Base.connection.query_cache.length - assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off' + assert !ActiveRecord::Base.connection.query_cache_enabled, "cache off" end - def test_exceptional_middleware_leaves_enabled_cache_alone - ActiveRecord::Base.connection.enable_query_cache! + def test_exceptional_middleware_cleans_up_correct_cache + connection = ActiveRecord::Base.connection + called = false - mw = ActiveRecord::QueryCache.new lambda { |env| - raise "lol borked" + mw = middleware { |env| + Task.find 1 + Task.find 1 + assert_equal 1, connection.query_cache.length + + # Checkin connection early + ActiveRecord::Base.clear_active_connections! + # Make sure ActiveRecord::Base.connection doesn't checkout the same connection + ActiveRecord::Base.connection_pool.remove(connection) + + called = true } - assert_raises(RuntimeError) { mw.call({}) } + mw.call({}) - assert ActiveRecord::Base.connection.query_cache_enabled, 'cache on' + assert called + assert_equal 0, connection.query_cache.length + assert !connection.query_cache_enabled, "cache off" end - def test_exceptional_middleware_assigns_original_connection_id_on_error - connection_id = ActiveRecord::Base.connection_id + def test_exceptional_middleware_leaves_enabled_cache_alone + ActiveRecord::Base.connection.enable_query_cache! - mw = ActiveRecord::QueryCache.new lambda { |env| - ActiveRecord::Base.connection_id = self.object_id + mw = middleware { |env| raise "lol borked" } assert_raises(RuntimeError) { mw.call({}) } - assert_equal connection_id, ActiveRecord::Base.connection_id + assert ActiveRecord::Base.connection.query_cache_enabled, "cache on" end def test_middleware_delegates called = false - mw = ActiveRecord::QueryCache.new lambda { |env| + mw = middleware { |env| called = true [200, {}, nil] } mw.call({}) - assert called, 'middleware should delegate' + assert called, "middleware should delegate" end def test_middleware_caches - mw = ActiveRecord::QueryCache.new lambda { |env| + mw = middleware { |env| Task.find 1 Task.find 1 assert_equal 1, ActiveRecord::Base.connection.query_cache.length @@ -72,52 +106,15 @@ class QueryCacheTest < ActiveRecord::TestCase end def test_cache_enabled_during_call - assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off' + assert !ActiveRecord::Base.connection.query_cache_enabled, "cache off" - mw = ActiveRecord::QueryCache.new lambda { |env| - assert ActiveRecord::Base.connection.query_cache_enabled, 'cache on' + mw = middleware { |env| + assert ActiveRecord::Base.connection.query_cache_enabled, "cache on" [200, {}, nil] } mw.call({}) end - def test_cache_on_during_body_write - streaming = Class.new do - def each - yield ActiveRecord::Base.connection.query_cache_enabled - end - end - - mw = ActiveRecord::QueryCache.new lambda { |env| - [200, {}, streaming.new] - } - body = mw.call({}).last - body.each { |x| assert x, 'cache should be on' } - body.close - assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled' - end - - def test_cache_off_after_close - mw = ActiveRecord::QueryCache.new lambda { |env| [200, {}, nil] } - body = mw.call({}).last - - assert ActiveRecord::Base.connection.query_cache_enabled, 'cache enabled' - body.close - assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled' - end - - def test_cache_clear_after_close - mw = ActiveRecord::QueryCache.new lambda { |env| - Post.first - [200, {}, nil] - } - body = mw.call({}).last - - assert !ActiveRecord::Base.connection.query_cache.empty?, 'cache not empty' - body.close - assert ActiveRecord::Base.connection.query_cache.empty?, 'cache should be empty' - end - def test_cache_passing_a_relation post = Post.first Post.cache do @@ -168,9 +165,21 @@ class QueryCacheTest < ActiveRecord::TestCase end end + def test_cache_does_not_raise_exceptions + logger = ShouldNotHaveExceptionsLogger.new + subscriber = ActiveSupport::Notifications.subscribe "sql.active_record", logger + + ActiveRecord::Base.cache do + assert_queries(1) { Task.find(1); Task.find(1) } + end + + assert_not_predicate logger, :exception? + ensure + ActiveSupport::Notifications.unsubscribe subscriber + end + def test_cache_is_flat Task.cache do - Topic.columns # don't count this query assert_queries(1) { Topic.find(1); Topic.find(1); } end @@ -181,13 +190,12 @@ class QueryCacheTest < ActiveRecord::TestCase def test_cache_does_not_wrap_string_results_in_arrays Task.cache do - # Oracle adapter returns count() as Fixnum or Float + # Oracle adapter returns count() as Integer or Float if current_adapter?(:OracleAdapter) assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") elsif current_adapter?(:SQLite3Adapter, :Mysql2Adapter, :PostgreSQLAdapter) # Future versions of the sqlite3 adapter will return numeric - assert_instance_of Fixnum, - Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") + assert_instance_of 0.class, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") else assert_instance_of String, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") end @@ -213,37 +221,80 @@ class QueryCacheTest < ActiveRecord::TestCase ActiveRecord::Base.configurations = conf 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? + + Task.cache do + Task.connection # warmup postgresql connection setup queries + assert_queries(2) { Task.find(1); Task.find(1) } + 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 ActiveRecord::Base.connection.enable_query_cache! post = Post.first Post.transaction do - post.update_attributes(title: 'rollback') - assert_equal 1, Post.where(title: 'rollback').to_a.count + post.update_attributes(title: "rollback") + assert_equal 1, Post.where(title: "rollback").to_a.count raise ActiveRecord::Rollback end - assert_equal 0, Post.where(title: 'rollback').to_a.count + assert_equal 0, Post.where(title: "rollback").to_a.count ActiveRecord::Base.connection.uncached do - assert_equal 0, Post.where(title: 'rollback').to_a.count + assert_equal 0, Post.where(title: "rollback").to_a.count end begin Post.transaction do - post.update_attributes(title: 'rollback') - assert_equal 1, Post.where(title: 'rollback').to_a.count - raise 'broken' + post.update_attributes(title: "rollback") + assert_equal 1, Post.where(title: "rollback").to_a.count + raise "broken" end rescue Exception end - assert_equal 0, Post.where(title: 'rollback').to_a.count + assert_equal 0, Post.where(title: "rollback").to_a.count ActiveRecord::Base.connection.uncached do - assert_equal 0, Post.where(title: 'rollback').to_a.count + assert_equal 0, Post.where(title: "rollback").to_a.count end end + + def test_query_cached_even_when_types_are_reset + Task.cache do + # Warm the cache + Task.find(1) + + Task.connection.type_map.clear + + # Preload the type cache again (so we don't have those queries issued during our assertions) + Task.connection.send(:initialize_type_map, Task.connection.type_map) + + # Clear places where type information is cached + Task.reset_column_information + Task.initialize_find_by_cache + + assert_queries(0) do + Task.find(1) + end + end + end + + private + def middleware(&app) + executor = Class.new(ActiveSupport::Executor) + ActiveRecord::QueryCache.install_executor_hooks executor + lambda { |env| executor.wrap { app.call(env) } } + end end class QueryCacheExpiryTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index 6d91f96bf6..296dafacc2 100644 --- a/activerecord/test/cases/quoting_test.rb +++ b/activerecord/test/cases/quoting_test.rb @@ -16,20 +16,20 @@ module ActiveRecord end def test_quote_column_name - assert_equal "foo", @quoter.quote_column_name('foo') + assert_equal "foo", @quoter.quote_column_name("foo") end def test_quote_table_name - assert_equal "foo", @quoter.quote_table_name('foo') + assert_equal "foo", @quoter.quote_table_name("foo") end def test_quote_table_name_calls_quote_column_name @quoter.extend(Module.new { def quote_column_name(string) - 'lol' + "lol" end }) - assert_equal 'lol', @quoter.quote_table_name('foo') + assert_equal "lol", @quoter.quote_table_name("foo") end def test_quote_string @@ -86,7 +86,7 @@ module ActiveRecord end def test_quote_nil - assert_equal 'NULL', @quoter.quote(nil, nil) + assert_equal "NULL", @quoter.quote(nil, nil) end def test_quote_true @@ -102,9 +102,9 @@ module ActiveRecord assert_equal float.to_s, @quoter.quote(float, nil) end - def test_quote_fixnum - fixnum = 1 - assert_equal fixnum.to_s, @quoter.quote(fixnum, nil) + def test_quote_integer + integer = 1 + assert_equal integer.to_s, @quoter.quote(integer, nil) end def test_quote_bignum @@ -114,11 +114,11 @@ module ActiveRecord 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, nil) end def test_dates_and_times - @quoter.extend(Module.new { def quoted_date(value) 'lol' end }) + @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) @@ -133,21 +133,33 @@ module ActiveRecord end def test_quote_string_no_column - assert_equal "'lo\\\\l'", @quoter.quote('lo\l', nil) + assert_equal "'lo\\\\l'", @quoter.quote('lo\l') end def test_quote_as_mb_chars_no_column string = ActiveSupport::Multibyte::Chars.new('lo\l') - assert_equal "'lo\\\\l'", @quoter.quote(string, nil) - end - - def test_string_with_crazy_column - assert_equal "'lo\\\\l'", @quoter.quote('lo\l') + assert_equal "'lo\\\\l'", @quoter.quote(string) end def test_quote_duration assert_equal "1800", @quoter.quote(30.minutes) end end + + class QuoteBooleanTest < ActiveRecord::TestCase + def setup + @connection = ActiveRecord::Base.connection + end + + def test_quote_returns_frozen_string + assert_predicate @connection.quote(true), :frozen? + assert_predicate @connection.quote(false), :frozen? + end + + def test_type_cast_returns_frozen_value + assert_predicate @connection.type_cast(true), :frozen? + assert_predicate @connection.type_cast(false), :frozen? + end + end end end diff --git a/activerecord/test/cases/readonly_test.rb b/activerecord/test/cases/readonly_test.rb index 5f6eb41240..a93061b516 100644 --- a/activerecord/test/cases/readonly_test.rb +++ b/activerecord/test/cases/readonly_test.rb @@ -1,13 +1,13 @@ require "cases/helper" -require 'models/author' -require 'models/post' -require 'models/comment' -require 'models/developer' -require 'models/computer' -require 'models/project' -require 'models/reader' -require 'models/person' -require 'models/ship' +require "models/author" +require "models/post" +require "models/comment" +require "models/developer" +require "models/computer" +require "models/project" +require "models/reader" +require "models/person" +require "models/ship" class ReadOnlyTest < ActiveRecord::TestCase fixtures :authors, :posts, :comments, :developers, :projects, :developers_projects, :people, :readers @@ -20,9 +20,9 @@ class ReadOnlyTest < ActiveRecord::TestCase assert dev.readonly? assert_nothing_raised do - dev.name = 'Luscious forbidden fruit.' + dev.name = "Luscious forbidden fruit." assert !dev.save - dev.name = 'Forbidden.' + dev.name = "Forbidden." end e = assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save } @@ -35,7 +35,6 @@ class ReadOnlyTest < ActiveRecord::TestCase assert_equal "Developer is marked as readonly", e.message end - def test_find_with_readonly_option Developer.all.each { |d| assert !d.readonly? } Developer.readonly(false).each { |d| assert !d.readonly? } @@ -44,11 +43,11 @@ class ReadOnlyTest < ActiveRecord::TestCase end def test_find_with_joins_option_does_not_imply_readonly - Developer.joins(' ').each { |d| assert_not d.readonly? } - Developer.joins(' ').readonly(true).each { |d| assert d.readonly? } + Developer.joins(" ").each { |d| assert_not d.readonly? } + Developer.joins(" ").readonly(true).each { |d| assert d.readonly? } - Developer.joins(', projects').each { |d| assert_not d.readonly? } - Developer.joins(', projects').readonly(true).each { |d| assert d.readonly? } + Developer.joins(", projects").each { |d| assert_not d.readonly? } + Developer.joins(", projects").readonly(true).each { |d| assert d.readonly? } end def test_has_many_find_readonly @@ -77,13 +76,13 @@ class ReadOnlyTest < ActiveRecord::TestCase end def test_readonly_scoping - Post.where('1=1').scoping do + Post.where("1=1").scoping do assert !Post.find(1).readonly? assert Post.readonly(true).find(1).readonly? assert !Post.readonly(false).find(1).readonly? end - Post.joins(' ').scoping do + Post.joins(" ").scoping do assert !Post.find(1).readonly? assert Post.readonly.find(1).readonly? assert !Post.readonly(false).find(1).readonly? @@ -92,7 +91,7 @@ class ReadOnlyTest < ActiveRecord::TestCase # Oracle barfs on this because the join includes unqualified and # conflicting column names unless current_adapter?(:OracleAdapter) - Post.joins(', developers').scoping do + Post.joins(", developers").scoping do assert_not Post.find(1).readonly? assert Post.readonly.find(1).readonly? assert !Post.readonly(false).find(1).readonly? diff --git a/activerecord/test/cases/reaper_test.rb b/activerecord/test/cases/reaper_test.rb index cccfc6774e..249878b67d 100644 --- a/activerecord/test/cases/reaper_test.rb +++ b/activerecord/test/cases/reaper_test.rb @@ -60,7 +60,7 @@ module ActiveRecord def test_connection_pool_starts_reaper spec = ActiveRecord::Base.connection_pool.spec.dup - spec.config[:reaping_frequency] = '0.0001' + spec.config[:reaping_frequency] = "0.0001" pool = ConnectionPool.new spec diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index 710c86b151..5dac3d064b 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -1,30 +1,30 @@ require "cases/helper" -require 'models/topic' -require 'models/customer' -require 'models/company' -require 'models/company_in_module' -require 'models/ship' -require 'models/pirate' -require 'models/price_estimate' -require 'models/essay' -require 'models/author' -require 'models/organization' -require 'models/post' -require 'models/tagging' -require 'models/category' -require 'models/book' -require 'models/subscriber' -require 'models/subscription' -require 'models/tag' -require 'models/sponsor' -require 'models/edge' -require 'models/hotel' -require 'models/chef' -require 'models/department' -require 'models/cake_designer' -require 'models/drink_designer' -require 'models/mocktail_designer' -require 'models/recipe' +require "models/topic" +require "models/customer" +require "models/company" +require "models/company_in_module" +require "models/ship" +require "models/pirate" +require "models/price_estimate" +require "models/essay" +require "models/author" +require "models/organization" +require "models/post" +require "models/tagging" +require "models/category" +require "models/book" +require "models/subscriber" +require "models/subscription" +require "models/tag" +require "models/sponsor" +require "models/edge" +require "models/hotel" +require "models/chef" +require "models/department" +require "models/cake_designer" +require "models/drink_designer" +require "models/mocktail_designer" +require "models/recipe" class ReflectionTest < ActiveRecord::TestCase include ActiveRecord::Reflection @@ -100,7 +100,7 @@ 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" }, ActiveRecord::Base) assert_nothing_raised do assert_equal MyApplication::Business::Company, reflection.klass end @@ -108,28 +108,28 @@ class ReflectionTest < ActiveRecord::TestCase def test_irregular_reflection_class_name ActiveSupport::Inflector.inflections do |inflect| - inflect.irregular 'plural_irregular', 'plurales_irregulares' + inflect.irregular "plural_irregular", "plurales_irregulares" end - reflection = ActiveRecord::Reflection.create(:has_many, 'plurales_irregulares', nil, {}, ActiveRecord::Base) - assert_equal 'PluralIrregular', reflection.class_name + reflection = ActiveRecord::Reflection.create(:has_many, "plurales_irregulares", nil, {}, ActiveRecord::Base) + assert_equal "PluralIrregular", reflection.class_name end def test_aggregation_reflection reflection_for_address = AggregateReflection.new( - :address, nil, { :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ] }, Customer + :address, nil, { mapping: [ %w(address_street street), %w(address_city city), %w(address_country country) ] }, Customer ) reflection_for_balance = AggregateReflection.new( - :balance, nil, { :class_name => "Money", :mapping => %w(balance amount) }, Customer + :balance, nil, { class_name: "Money", mapping: %w(balance amount) }, Customer ) reflection_for_gps_location = AggregateReflection.new( - :gps_location, nil, { }, Customer + :gps_location, nil, {}, Customer ) - assert Customer.reflect_on_all_aggregations.include?(reflection_for_gps_location) - assert Customer.reflect_on_all_aggregations.include?(reflection_for_balance) - assert Customer.reflect_on_all_aggregations.include?(reflection_for_address) + assert_includes Customer.reflect_on_all_aggregations, reflection_for_gps_location + assert_includes Customer.reflect_on_all_aggregations, reflection_for_balance + assert_includes Customer.reflect_on_all_aggregations, reflection_for_address assert_equal reflection_for_address, Customer.reflect_on_aggregation(:address) @@ -148,31 +148,31 @@ class ReflectionTest < ActiveRecord::TestCase end def test_has_many_reflection - reflection_for_clients = ActiveRecord::Reflection.create(:has_many, :clients, nil, { :order => "id", :dependent => :destroy }, Firm) + reflection_for_clients = ActiveRecord::Reflection.create(:has_many, :clients, nil, { order: "id", dependent: :destroy }, Firm) assert_equal reflection_for_clients, Firm.reflect_on_association(:clients) assert_equal Client, Firm.reflect_on_association(:clients).klass - assert_equal 'companies', Firm.reflect_on_association(:clients).table_name + assert_equal "companies", Firm.reflect_on_association(:clients).table_name assert_equal Client, Firm.reflect_on_association(:clients_of_firm).klass - assert_equal 'companies', Firm.reflect_on_association(:clients_of_firm).table_name + assert_equal "companies", Firm.reflect_on_association(:clients_of_firm).table_name end def test_has_one_reflection - reflection_for_account = ActiveRecord::Reflection.create(:has_one, :account, nil, { :foreign_key => "firm_id", :dependent => :destroy }, Firm) + reflection_for_account = ActiveRecord::Reflection.create(:has_one, :account, nil, { foreign_key: "firm_id", dependent: :destroy }, Firm) assert_equal reflection_for_account, Firm.reflect_on_association(:account) assert_equal Account, Firm.reflect_on_association(:account).klass - assert_equal 'accounts', Firm.reflect_on_association(:account).table_name + assert_equal "accounts", Firm.reflect_on_association(:account).table_name end def test_belongs_to_inferred_foreign_key_from_assoc_name Company.belongs_to :foo assert_equal "foo_id", Company.reflect_on_association(:foo).foreign_key - Company.belongs_to :bar, :class_name => "Xyzzy" + Company.belongs_to :bar, class_name: "Xyzzy" assert_equal "bar_id", Company.reflect_on_association(:bar).foreign_key - Company.belongs_to :baz, :class_name => "Xyzzy", :foreign_key => "xyzzy_id" + Company.belongs_to :baz, class_name: "Xyzzy", foreign_key: "xyzzy_id" assert_equal "xyzzy_id", Company.reflect_on_association(:baz).foreign_key end @@ -181,45 +181,45 @@ class ReflectionTest < ActiveRecord::TestCase assert_reflection MyApplication::Business::Firm, :clients_of_firm, - :klass => MyApplication::Business::Client, - :class_name => 'Client', - :table_name => 'companies' + klass: MyApplication::Business::Client, + class_name: "Client", + table_name: "companies" assert_reflection MyApplication::Billing::Account, :firm, - :klass => MyApplication::Business::Firm, - :class_name => 'MyApplication::Business::Firm', - :table_name => 'companies' + klass: MyApplication::Business::Firm, + class_name: "MyApplication::Business::Firm", + table_name: "companies" assert_reflection MyApplication::Billing::Account, :qualified_billing_firm, - :klass => MyApplication::Billing::Firm, - :class_name => 'MyApplication::Billing::Firm', - :table_name => 'companies' + klass: MyApplication::Billing::Firm, + class_name: "MyApplication::Billing::Firm", + table_name: "companies" assert_reflection MyApplication::Billing::Account, :unqualified_billing_firm, - :klass => MyApplication::Billing::Firm, - :class_name => 'Firm', - :table_name => 'companies' + klass: MyApplication::Billing::Firm, + class_name: "Firm", + table_name: "companies" assert_reflection MyApplication::Billing::Account, :nested_qualified_billing_firm, - :klass => MyApplication::Billing::Nested::Firm, - :class_name => 'MyApplication::Billing::Nested::Firm', - :table_name => 'companies' + klass: MyApplication::Billing::Nested::Firm, + class_name: "MyApplication::Billing::Nested::Firm", + table_name: "companies" assert_reflection MyApplication::Billing::Account, :nested_unqualified_billing_firm, - :klass => MyApplication::Billing::Nested::Firm, - :class_name => 'Nested::Firm', - :table_name => 'companies' + klass: MyApplication::Billing::Nested::Firm, + class_name: "Nested::Firm", + table_name: "companies" ensure ActiveRecord::Base.store_full_sti_class = true end def test_reflection_should_not_raise_error_when_compared_to_other_object - assert_not_equal Object.new, Firm._reflections['clients'] + assert_not_equal Object.new, Firm._reflections["clients"] end def test_reflections_should_return_keys_as_strings @@ -227,7 +227,7 @@ class ReflectionTest < ActiveRecord::TestCase end def test_has_and_belongs_to_many_reflection - assert_equal :has_and_belongs_to_many, Category.reflections['posts'].macro + assert_equal :has_and_belongs_to_many, Category.reflections["posts"].macro assert_equal :posts, Category.reflect_on_all_associations(:has_and_belongs_to_many).first.name end @@ -366,21 +366,21 @@ class ReflectionTest < ActiveRecord::TestCase end def test_always_validate_association_if_explicit - assert ActiveRecord::Reflection.create(:has_one, :client, nil, { :validate => true }, Firm).validate? - assert ActiveRecord::Reflection.create(:belongs_to, :client, nil, { :validate => true }, Firm).validate? - assert ActiveRecord::Reflection.create(:has_many, :clients, nil, { :validate => true }, Firm).validate? + assert ActiveRecord::Reflection.create(:has_one, :client, nil, { validate: true }, Firm).validate? + assert ActiveRecord::Reflection.create(:belongs_to, :client, nil, { validate: true }, Firm).validate? + assert ActiveRecord::Reflection.create(:has_many, :clients, nil, { validate: true }, Firm).validate? end def test_validate_association_if_autosave - assert ActiveRecord::Reflection.create(:has_one, :client, nil, { :autosave => true }, Firm).validate? - assert ActiveRecord::Reflection.create(:belongs_to, :client, nil, { :autosave => true }, Firm).validate? - assert ActiveRecord::Reflection.create(:has_many, :clients, nil, { :autosave => true }, Firm).validate? + assert ActiveRecord::Reflection.create(:has_one, :client, nil, { autosave: true }, Firm).validate? + assert ActiveRecord::Reflection.create(:belongs_to, :client, nil, { autosave: true }, Firm).validate? + assert ActiveRecord::Reflection.create(:has_many, :clients, nil, { autosave: true }, Firm).validate? end def test_never_validate_association_if_explicit - assert !ActiveRecord::Reflection.create(:has_one, :client, nil, { :autosave => true, :validate => false }, Firm).validate? - assert !ActiveRecord::Reflection.create(:belongs_to, :client, nil, { :autosave => true, :validate => false }, Firm).validate? - assert !ActiveRecord::Reflection.create(:has_many, :clients, nil, { :autosave => true, :validate => false }, Firm).validate? + assert !ActiveRecord::Reflection.create(:has_one, :client, nil, { autosave: true, validate: false }, Firm).validate? + assert !ActiveRecord::Reflection.create(:belongs_to, :client, nil, { autosave: true, validate: false }, Firm).validate? + assert !ActiveRecord::Reflection.create(:has_many, :clients, nil, { autosave: true, validate: false }, Firm).validate? end def test_foreign_key @@ -399,62 +399,62 @@ class ReflectionTest < ActiveRecord::TestCase 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) + category = Struct.new(:table_name, :pluralize_table_names).new("categories", true) + product = Struct.new(:table_name, :pluralize_table_names).new("products", true) reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, {}, product) reflection.stub(:klass, category) do - assert_equal 'categories_products', reflection.join_table + assert_equal "categories_products", reflection.join_table end reflection = ActiveRecord::Reflection.create(:has_many, :products, nil, {}, category) reflection.stub(:klass, product) do - assert_equal 'categories_products', reflection.join_table + assert_equal "categories_products", reflection.join_table end end def test_join_table_with_common_prefix - category = Struct.new(:table_name, :pluralize_table_names).new('catalog_categories', true) - product = Struct.new(:table_name, :pluralize_table_names).new('catalog_products', true) + category = Struct.new(:table_name, :pluralize_table_names).new("catalog_categories", true) + product = Struct.new(:table_name, :pluralize_table_names).new("catalog_products", true) reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, {}, product) reflection.stub(:klass, category) do - assert_equal 'catalog_categories_products', reflection.join_table + assert_equal "catalog_categories_products", reflection.join_table end reflection = ActiveRecord::Reflection.create(:has_many, :products, nil, {}, category) reflection.stub(:klass, product) do - assert_equal 'catalog_categories_products', reflection.join_table + assert_equal "catalog_categories_products", reflection.join_table end end def test_join_table_with_different_prefix - category = Struct.new(:table_name, :pluralize_table_names).new('catalog_categories', true) - page = Struct.new(:table_name, :pluralize_table_names).new('content_pages', true) + category = Struct.new(:table_name, :pluralize_table_names).new("catalog_categories", true) + page = Struct.new(:table_name, :pluralize_table_names).new("content_pages", true) reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, {}, page) reflection.stub(:klass, category) do - assert_equal 'catalog_categories_content_pages', reflection.join_table + assert_equal "catalog_categories_content_pages", reflection.join_table end reflection = ActiveRecord::Reflection.create(:has_many, :pages, nil, {}, category) reflection.stub(:klass, page) do - assert_equal 'catalog_categories_content_pages', reflection.join_table + assert_equal "catalog_categories_content_pages", reflection.join_table end end def test_join_table_can_be_overridden - category = Struct.new(:table_name, :pluralize_table_names).new('categories', true) - product = Struct.new(:table_name, :pluralize_table_names).new('products', true) + category = Struct.new(:table_name, :pluralize_table_names).new("categories", true) + product = Struct.new(:table_name, :pluralize_table_names).new("products", true) - reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, { :join_table => 'product_categories' }, product) + reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, { join_table: "product_categories" }, product) reflection.stub(:klass, category) do - assert_equal 'product_categories', reflection.join_table + assert_equal "product_categories", reflection.join_table end - reflection = ActiveRecord::Reflection.create(:has_many, :products, nil, { :join_table => 'product_categories' }, category) + reflection = ActiveRecord::Reflection.create(:has_many, :products, nil, { join_table: "product_categories" }, category) reflection.stub(:klass, product) do - assert_equal 'product_categories', reflection.join_table + assert_equal "product_categories", reflection.join_table end end @@ -474,7 +474,7 @@ class ReflectionTest < ActiveRecord::TestCase department.chefs.create! assert_nothing_raised do - assert_equal department.chefs, Hotel.includes(['departments' => 'chefs']).first.chefs + assert_equal department.chefs, Hotel.includes(["departments" => "chefs"]).first.chefs end end diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb index f0e07e0731..d2382b9bb2 100644 --- a/activerecord/test/cases/relation/delegation_test.rb +++ b/activerecord/test/cases/relation/delegation_test.rb @@ -1,6 +1,6 @@ -require 'cases/helper' -require 'models/post' -require 'models/comment' +require "cases/helper" +require "models/post" +require "models/comment" module ActiveRecord class DelegationTest < ActiveRecord::TestCase @@ -18,7 +18,7 @@ module ActiveRecord target.public_send(method, 1) end elsif method_arity == 1 - target.public_send(method, 1) + target.public_send(method, 1) else raise NotImplementedError end diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb index 60a806c05a..278dac8171 100644 --- a/activerecord/test/cases/relation/merging_test.rb +++ b/activerecord/test/cases/relation/merging_test.rb @@ -1,20 +1,20 @@ -require 'cases/helper' -require 'models/author' -require 'models/comment' -require 'models/developer' -require 'models/computer' -require 'models/post' -require 'models/project' -require 'models/rating' +require "cases/helper" +require "models/author" +require "models/comment" +require "models/developer" +require "models/computer" +require "models/post" +require "models/project" +require "models/rating" class RelationMergingTest < ActiveRecord::TestCase fixtures :developers, :comments, :authors, :posts def test_relation_merging - devs = Developer.where("salary >= 80000").merge(Developer.limit(2)).merge(Developer.order('id ASC').where("id < 3")) + devs = Developer.where("salary >= 80000").merge(Developer.limit(2)).merge(Developer.order("id ASC").where("id < 3")) assert_equal [developers(:david), developers(:jamis)], devs.to_a - dev_with_count = Developer.limit(1).merge(Developer.order('id DESC')).merge(Developer.select('developers.*')) + dev_with_count = Developer.limit(1).merge(Developer.order("id DESC")).merge(Developer.select("developers.*")) assert_equal [developers(:poor_jamis)], dev_with_count.to_a end @@ -34,10 +34,10 @@ class RelationMergingTest < ActiveRecord::TestCase def test_relation_merging_with_arel_equalities_keeps_last_equality_with_non_attribute_left_hand salary_attr = Developer.arel_table[:salary] devs = Developer.where( - Arel::Nodes::NamedFunction.new('abs', [salary_attr]).eq(80000) + Arel::Nodes::NamedFunction.new("abs", [salary_attr]).eq(80000) ).merge( Developer.where( - Arel::Nodes::NamedFunction.new('abs', [salary_attr]).eq(9000) + Arel::Nodes::NamedFunction.new("abs", [salary_attr]).eq(9000) ) ) assert_equal [developers(:poor_jamis)], devs.to_a @@ -45,8 +45,8 @@ class RelationMergingTest < ActiveRecord::TestCase def test_relation_merging_with_eager_load relations = [] - relations << Post.order('comments.id DESC').merge(Post.eager_load(:last_comment)).merge(Post.all) - relations << Post.eager_load(:last_comment).merge(Post.order('comments.id DESC')).merge(Post.all) + relations << Post.order("comments.id DESC").merge(Post.eager_load(:last_comment)).merge(Post.all) + relations << Post.eager_load(:last_comment).merge(Post.order("comments.id DESC")).merge(Post.all) relations.each do |posts| post = posts.find { |p| p.id == 1 } @@ -66,14 +66,14 @@ class RelationMergingTest < ActiveRecord::TestCase end def test_relation_merging_with_joins - comments = Comment.joins(:post).where(:body => 'Thank you for the welcome').merge(Post.where(:body => 'Such a lovely day')) + comments = Comment.joins(:post).where(body: "Thank you for the welcome").merge(Post.where(body: "Such a lovely day")) assert_equal 1, comments.count end def test_relation_merging_with_association assert_queries(2) do # one for loading post, and another one merged query - post = Post.where(:body => 'Such a lovely day').first - comments = Comment.where(:body => 'Thank you for the welcome').merge(post.comments) + post = Post.where(body: "Such a lovely day").first + comments = Comment.where(body: "Thank you for the welcome").merge(post.comments) assert_equal 1, comments.count end end @@ -86,9 +86,9 @@ class RelationMergingTest < ActiveRecord::TestCase merged = left.merge(right) assert_equal expected, merged.bound_attributes - assert !merged.to_sql.include?("omg") - assert merged.to_sql.include?("wtf") - assert merged.to_sql.include?("bbq") + assert_not_includes merged.to_sql, "omg" + assert_includes merged.to_sql, "wtf" + assert_includes merged.to_sql, "bbq" end def test_merging_reorders_bind_params diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb index ffb2da7a26..966ae83a3f 100644 --- a/activerecord/test/cases/relation/mutation_test.rb +++ b/activerecord/test/cases/relation/mutation_test.rb @@ -1,8 +1,8 @@ -require 'cases/helper' -require 'models/post' +require "cases/helper" +require "models/post" module ActiveRecord - class RelationMutationTest < ActiveSupport::TestCase + class RelationMutationTest < ActiveRecord::TestCase class FakeKlass < Struct.new(:table_name, :name) extend ActiveRecord::Delegation::DelegateCache inherited self @@ -33,7 +33,7 @@ module ActiveRecord end def relation - @relation ||= Relation.new FakeKlass.new('posts'), Post.arel_table, Post.predicate_builder + @relation ||= Relation.new FakeKlass.new("posts"), Post.arel_table, Post.predicate_builder end (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope, :select, :left_joins]).each do |method| @@ -44,16 +44,16 @@ module ActiveRecord end test "#_select!" do - assert relation.public_send("_select!", :foo).equal?(relation) - assert_equal [:foo], relation.public_send("select_values") + assert relation._select!(:foo).equal?(relation) + assert_equal [:foo], relation.select_values end - test '#order!' do - assert relation.order!('name ASC').equal?(relation) - assert_equal ['name ASC'], relation.order_values + test "#order!" do + assert relation.order!("name ASC").equal?(relation) + assert_equal ["name ASC"], relation.order_values end - test '#order! with symbol prepends the table name' do + test "#order! with symbol prepends the table name" do assert relation.order!(:name).equal?(relation) node = relation.order_values.first assert node.ascending? @@ -61,7 +61,7 @@ module ActiveRecord assert_equal "posts", node.expr.relation.name end - test '#order! on non-string does not attempt regexp match for references' do + test "#order! on non-string does not attempt regexp match for references" do obj = Object.new assert_not_called(obj, :=~) do assert relation.order!(obj) @@ -69,12 +69,12 @@ module ActiveRecord end end - test '#references!' do + test "#references!" do assert relation.references!(:foo).equal?(relation) - assert relation.references_values.include?('foo') + assert_includes relation.references_values, "foo" end - test 'extending!' do + test "extending!" do mod, mod2 = Module.new, Module.new assert relation.extending!(mod).equal?(relation) @@ -85,7 +85,7 @@ module ActiveRecord assert_equal [mod, mod2], relation.extending_values end - test 'extending! with empty args' do + test "extending! with empty args" do relation.extending! assert_equal [], relation.extending_values end @@ -97,25 +97,25 @@ module ActiveRecord end end - test '#from!' do - assert relation.from!('foo').equal?(relation) - assert_equal 'foo', relation.from_clause.value + test "#from!" do + assert relation.from!("foo").equal?(relation) + assert_equal "foo", relation.from_clause.value end - test '#lock!' do - assert relation.lock!('foo').equal?(relation) - assert_equal 'foo', relation.lock_value + test "#lock!" do + assert relation.lock!("foo").equal?(relation) + assert_equal "foo", relation.lock_value end - test '#reorder!' do - @relation = self.relation.order('foo') + test "#reorder!" do + @relation = self.relation.order("foo") - assert relation.reorder!('bar').equal?(relation) - assert_equal ['bar'], relation.order_values + assert relation.reorder!("bar").equal?(relation) + assert_equal ["bar"], relation.order_values assert relation.reordering_value end - test '#reorder! with symbol prepends the table name' do + test "#reorder! with symbol prepends the table name" do assert relation.reorder!(:name).equal?(relation) node = relation.order_values.first @@ -124,42 +124,41 @@ module ActiveRecord assert_equal "posts", node.expr.relation.name end - test 'reverse_order!' do - @relation = Post.order('title ASC, comments_count DESC') + test "reverse_order!" do + @relation = Post.order("title ASC, comments_count DESC") relation.reverse_order! - assert_equal 'title DESC', relation.order_values.first - assert_equal 'comments_count ASC', relation.order_values.last - + assert_equal "title DESC", relation.order_values.first + assert_equal "comments_count ASC", relation.order_values.last relation.reverse_order! - assert_equal 'title ASC', relation.order_values.first - assert_equal 'comments_count DESC', relation.order_values.last + assert_equal "title ASC", relation.order_values.first + assert_equal "comments_count DESC", relation.order_values.last end - test 'create_with!' do - assert relation.create_with!(foo: 'bar').equal?(relation) - assert_equal({foo: 'bar'}, relation.create_with_value) + test "create_with!" do + assert relation.create_with!(foo: "bar").equal?(relation) + assert_equal({ foo: "bar" }, relation.create_with_value) end - test 'test_merge!' do + test "test_merge!" do assert relation.merge!(select: :foo).equal?(relation) assert_equal [:foo], relation.select_values end - test 'merge with a proc' do + test "merge with a proc" do assert_equal [:foo], relation.merge(-> { select(:foo) }).select_values end - test 'none!' do + test "none!" do assert relation.none!.equal?(relation) assert_equal [NullRelation], relation.extending_values assert relation.is_a?(NullRelation) end - test 'distinct!' do + test "distinct!" do relation.distinct! :foo assert_equal :foo, relation.distinct_value @@ -168,7 +167,7 @@ module ActiveRecord end end - test 'uniq! was replaced by distinct!' do + test "uniq! was replaced by distinct!" do assert_deprecated(/use distinct! instead/) do relation.uniq! :foo end diff --git a/activerecord/test/cases/relation/or_test.rb b/activerecord/test/cases/relation/or_test.rb index 28a0862f91..2796595523 100644 --- a/activerecord/test/cases/relation/or_test.rb +++ b/activerecord/test/cases/relation/or_test.rb @@ -1,28 +1,28 @@ require "cases/helper" -require 'models/post' +require "models/post" module ActiveRecord class OrTest < ActiveRecord::TestCase fixtures :posts def test_or_with_relation - expected = Post.where('id = 1 or id = 2').to_a - assert_equal expected, Post.where('id = 1').or(Post.where('id = 2')).to_a + expected = Post.where("id = 1 or id = 2").to_a + assert_equal expected, Post.where("id = 1").or(Post.where("id = 2")).to_a end def test_or_identity - expected = Post.where('id = 1').to_a - assert_equal expected, Post.where('id = 1').or(Post.where('id = 1')).to_a + expected = Post.where("id = 1").to_a + assert_equal expected, Post.where("id = 1").or(Post.where("id = 1")).to_a end def test_or_with_null_left - expected = Post.where('id = 1').to_a - assert_equal expected, Post.none.or(Post.where('id = 1')).to_a + expected = Post.where("id = 1").to_a + assert_equal expected, Post.none.or(Post.where("id = 1")).to_a end def test_or_with_null_right - expected = Post.where('id = 1').to_a - assert_equal expected, Post.where('id = 1').or(Post.none).to_a + expected = Post.where("id = 1").to_a + assert_equal expected, Post.where("id = 1").or(Post.none).to_a end def test_or_with_bind_params @@ -36,51 +36,57 @@ module ActiveRecord def test_or_without_left_where expected = Post.all - assert_equal expected, Post.or(Post.where('id = 1')).to_a + assert_equal expected, Post.or(Post.where("id = 1")).to_a end def test_or_without_right_where expected = Post.all - assert_equal expected, Post.where('id = 1').or(Post.all).to_a + assert_equal expected, Post.where("id = 1").or(Post.all).to_a end def test_or_preserves_other_querying_methods - expected = Post.where('id = 1 or id = 2 or id = 3').order('body asc').to_a - partial = Post.order('body asc') - assert_equal expected, partial.where('id = 1').or(partial.where(:id => [2, 3])).to_a - assert_equal expected, Post.order('body asc').where('id = 1').or(Post.order('body asc').where(:id => [2, 3])).to_a + expected = Post.where("id = 1 or id = 2 or id = 3").order("body asc").to_a + partial = Post.order("body asc") + assert_equal expected, partial.where("id = 1").or(partial.where(id: [2, 3])).to_a + assert_equal expected, Post.order("body asc").where("id = 1").or(Post.order("body asc").where(id: [2, 3])).to_a end def test_or_with_incompatible_relations error = assert_raises ArgumentError do - Post.order('body asc').where('id = 1').or(Post.order('id desc').where(:id => [2, 3])).to_a + Post.order("body asc").where("id = 1").or(Post.order("id desc").where(id: [2, 3])).to_a end assert_equal "Relation passed to #or must be structurally compatible. Incompatible values: [:order]", error.message end def test_or_when_grouping - groups = Post.where('id < 10').group('body').select('body, COUNT(*) AS c') - expected = groups.having("COUNT(*) > 1 OR body like 'Such%'").to_a.map {|o| [o.body, o.c] } - assert_equal expected, groups.having('COUNT(*) > 1').or(groups.having("body like 'Such%'")).to_a.map {|o| [o.body, o.c] } + groups = Post.where("id < 10").group("body").select("body, COUNT(*) AS c") + expected = groups.having("COUNT(*) > 1 OR body like 'Such%'").to_a.map { |o| [o.body, o.c] } + assert_equal expected, groups.having("COUNT(*) > 1").or(groups.having("body like 'Such%'")).to_a.map { |o| [o.body, o.c] } end def test_or_with_named_scope expected = Post.where("id = 1 or body LIKE '\%a\%'").to_a - assert_equal expected, Post.where('id = 1').or(Post.containing_the_letter_a) + assert_equal expected, Post.where("id = 1").or(Post.containing_the_letter_a) end def test_or_inside_named_scope - expected = Post.where("body LIKE '\%a\%' OR title LIKE ?", "%'%").order('id DESC').to_a + expected = Post.where("body LIKE '\%a\%' OR title LIKE ?", "%'%").order("id DESC").to_a assert_equal expected, Post.order(id: :desc).typographically_interesting end def test_or_on_loaded_relation - expected = Post.where('id = 1 or id = 2').to_a - p = Post.where('id = 1') + expected = Post.where("id = 1 or id = 2").to_a + p = Post.where("id = 1") p.load assert_equal p.loaded?, true - assert_equal expected, p.or(Post.where('id = 2')).to_a + assert_equal expected, p.or(Post.where("id = 2")).to_a + end + + def test_or_with_non_relation_object_raises_error + assert_raises ArgumentError do + Post.where(id: [1, 2, 3]).or(title: "Rails") + end end end end diff --git a/activerecord/test/cases/relation/predicate_builder_test.rb b/activerecord/test/cases/relation/predicate_builder_test.rb index 8f62014622..48758dc148 100644 --- a/activerecord/test/cases/relation/predicate_builder_test.rb +++ b/activerecord/test/cases/relation/predicate_builder_test.rb @@ -1,11 +1,11 @@ require "cases/helper" -require 'models/topic' +require "models/topic" module ActiveRecord class PredicateBuilderTest < ActiveRecord::TestCase def test_registering_new_handlers Topic.predicate_builder.register_handler(Regexp, proc do |column, value| - Arel::Nodes::InfixOperation.new('~', column, Arel.sql(value.source)) + Arel::Nodes::InfixOperation.new("~", column, Arel.sql(value.source)) end) assert_match %r{["`]topics["`]\.["`]title["`] ~ rails}i, Topic.where(title: /rails/).to_sql diff --git a/activerecord/test/cases/relation/record_fetch_warning_test.rb b/activerecord/test/cases/relation/record_fetch_warning_test.rb index 53daf436e5..62ca051431 100644 --- a/activerecord/test/cases/relation/record_fetch_warning_test.rb +++ b/activerecord/test/cases/relation/record_fetch_warning_test.rb @@ -1,28 +1,40 @@ -require 'cases/helper' -require 'models/post' +require "cases/helper" +require "models/post" +require "active_record/relation/record_fetch_warning" module ActiveRecord class RecordFetchWarningTest < ActiveRecord::TestCase fixtures :posts - def test_warn_on_records_fetched_greater_than - original_logger = ActiveRecord::Base.logger - original_warn_on_records_fetched_greater_than = ActiveRecord::Base.warn_on_records_fetched_greater_than + def setup + @original_logger = ActiveRecord::Base.logger + @original_warn_on_records_fetched_greater_than = ActiveRecord::Base.warn_on_records_fetched_greater_than + @log = StringIO.new + end + + def teardown + ActiveRecord::Base.logger = @original_logger + ActiveRecord::Base.warn_on_records_fetched_greater_than = @original_warn_on_records_fetched_greater_than + end - log = StringIO.new - ActiveRecord::Base.logger = ActiveSupport::Logger.new(log) + def test_warn_on_records_fetched_greater_than_allowed_limit + ActiveRecord::Base.logger = ActiveSupport::Logger.new(@log) ActiveRecord::Base.logger.level = Logger::WARN + ActiveRecord::Base.warn_on_records_fetched_greater_than = 1 - require 'active_record/relation/record_fetch_warning' + Post.all.to_a - ActiveRecord::Base.warn_on_records_fetched_greater_than = 1 + assert_match(/Query fetched/, @log.string) + end + + def test_does_not_warn_on_records_fetched_less_than_allowed_limit + ActiveRecord::Base.logger = ActiveSupport::Logger.new(@log) + ActiveRecord::Base.logger.level = Logger::WARN + ActiveRecord::Base.warn_on_records_fetched_greater_than = 100 Post.all.to_a - assert_match(/Query fetched/, log.string) - ensure - ActiveRecord::Base.logger = original_logger - ActiveRecord::Base.warn_on_records_fetched_greater_than = original_warn_on_records_fetched_greater_than + assert_no_match(/Query fetched/, @log.string) end end end diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb index 27bbd80f79..a96d1ae5b5 100644 --- a/activerecord/test/cases/relation/where_chain_test.rb +++ b/activerecord/test/cases/relation/where_chain_test.rb @@ -1,6 +1,6 @@ -require 'cases/helper' -require 'models/post' -require 'models/comment' +require "cases/helper" +require "models/post" +require "models/comment" module ActiveRecord class WhereChainTest < ActiveRecord::TestCase @@ -8,12 +8,12 @@ module ActiveRecord def setup super - @name = 'title' + @name = "title" end def test_not_inverts_where_clause - relation = Post.where.not(title: 'hello') - expected_where_clause = Post.where(title: 'hello').where_clause.invert + relation = Post.where.not(title: "hello") + expected_where_clause = Post.where(title: "hello").where_clause.invert assert_equal expected_where_clause, relation.where_clause end @@ -26,54 +26,54 @@ module ActiveRecord def test_association_not_eq expected = Arel::Nodes::Grouping.new(Comment.arel_table[@name].not_eq(Arel::Nodes::BindParam.new)) - relation = Post.joins(:comments).where.not(comments: {title: 'hello'}) + relation = Post.joins(:comments).where.not(comments: { title: "hello" }) assert_equal(expected.to_sql, relation.where_clause.ast.to_sql) end def test_not_eq_with_preceding_where - relation = Post.where(title: 'hello').where.not(title: 'world') + relation = Post.where(title: "hello").where.not(title: "world") expected_where_clause = - Post.where(title: 'hello').where_clause + - Post.where(title: 'world').where_clause.invert + Post.where(title: "hello").where_clause + + Post.where(title: "world").where_clause.invert assert_equal expected_where_clause, relation.where_clause end def test_not_eq_with_succeeding_where - relation = Post.where.not(title: 'hello').where(title: 'world') + relation = Post.where.not(title: "hello").where(title: "world") expected_where_clause = - Post.where(title: 'hello').where_clause.invert + - Post.where(title: 'world').where_clause + Post.where(title: "hello").where_clause.invert + + Post.where(title: "world").where_clause assert_equal expected_where_clause, relation.where_clause end def test_chaining_multiple - relation = Post.where.not(author_id: [1, 2]).where.not(title: 'ruby on rails') + relation = Post.where.not(author_id: [1, 2]).where.not(title: "ruby on rails") expected_where_clause = Post.where(author_id: [1, 2]).where_clause.invert + - Post.where(title: 'ruby on rails').where_clause.invert + Post.where(title: "ruby on rails").where_clause.invert assert_equal expected_where_clause, relation.where_clause end def test_rewhere_with_one_condition - relation = Post.where(title: 'hello').where(title: 'world').rewhere(title: 'alone') - expected = Post.where(title: 'alone') + relation = Post.where(title: "hello").where(title: "world").rewhere(title: "alone") + expected = Post.where(title: "alone") assert_equal expected.where_clause, relation.where_clause end def test_rewhere_with_multiple_overwriting_conditions - relation = Post.where(title: 'hello').where(body: 'world').rewhere(title: 'alone', body: 'again') - expected = Post.where(title: 'alone', body: 'again') + relation = Post.where(title: "hello").where(body: "world").rewhere(title: "alone", body: "again") + expected = Post.where(title: "alone", body: "again") assert_equal expected.where_clause, relation.where_clause end def test_rewhere_with_one_overwriting_condition_and_one_unrelated - relation = Post.where(title: 'hello').where(body: 'world').rewhere(title: 'alone') - expected = Post.where(body: 'world', title: 'alone') + relation = Post.where(title: "hello").where(body: "world").rewhere(title: "alone") + expected = Post.where(body: "world", title: "alone") assert_equal expected.where_clause, relation.where_clause end diff --git a/activerecord/test/cases/relation/where_clause_test.rb b/activerecord/test/cases/relation/where_clause_test.rb index c20ed94d90..d8e4c304f0 100644 --- a/activerecord/test/cases/relation/where_clause_test.rb +++ b/activerecord/test/cases/relation/where_clause_test.rb @@ -140,7 +140,7 @@ class ActiveRecord::Relation test "ast removes any empty strings" do where_clause = WhereClause.new([table["id"].in([1, 2, 3])], []) - where_clause_with_empty = WhereClause.new([table["id"].in([1, 2, 3]), ''], []) + where_clause_with_empty = WhereClause.new([table["id"].in([1, 2, 3]), ""], []) assert_equal where_clause.ast, where_clause_with_empty.ast end @@ -167,16 +167,16 @@ class ActiveRecord::Relation private - def table - Arel::Table.new("table") - end + def table + Arel::Table.new("table") + end - def bind_param - Arel::Nodes::BindParam.new - end + def bind_param + Arel::Nodes::BindParam.new + end - def attribute(name, value) - ActiveRecord::Attribute.with_cast_value(name, value, ActiveRecord::Type::Value.new) - end + def attribute(name, value) + ActiveRecord::Attribute.with_cast_value(name, value, ActiveRecord::Type::Value.new) + end end end diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index bc6378b90e..925af49ffe 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -2,11 +2,12 @@ require "cases/helper" require "models/author" require "models/binary" require "models/cake_designer" +require "models/car" require "models/chef" +require "models/post" require "models/comment" require "models/edge" require "models/essay" -require "models/post" require "models/price_estimate" require "models/topic" require "models/treasure" @@ -14,11 +15,11 @@ require "models/vertex" module ActiveRecord class WhereTest < ActiveRecord::TestCase - fixtures :posts, :edges, :authors, :binaries, :essays + fixtures :posts, :edges, :authors, :binaries, :essays, :cars, :treasures, :price_estimates def test_where_copies_bind_params author = authors(:david) - posts = author.posts.where('posts.id != 1') + posts = author.posts.where("posts.id != 1") joined = Post.where(id: posts) assert_operator joined.length, :>, 0 @@ -48,7 +49,7 @@ module ActiveRecord end def test_rewhere_on_root - assert_equal posts(:welcome), Post.rewhere(title: 'Welcome to the weblog').first + assert_equal posts(:welcome), Post.rewhere(title: "Welcome to the weblog").first end def test_belongs_to_shallow_where @@ -96,7 +97,7 @@ module ActiveRecord treasure = Treasure.new treasure.id = 1 - expected = PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: 1) + expected = PriceEstimate.where(estimate_of_type: "Treasure", estimate_of_id: 1) actual = PriceEstimate.where(estimate_of: treasure) assert_equal expected.to_sql, actual.to_sql @@ -108,14 +109,25 @@ module ActiveRecord hidden = HiddenTreasure.new hidden.id = 2 - expected = PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: [treasure, hidden]) + expected = PriceEstimate.where(estimate_of_type: "Treasure", estimate_of_id: [treasure, hidden]) actual = PriceEstimate.where(estimate_of: [treasure, hidden]) assert_equal expected.to_sql, actual.to_sql end + def test_polymorphic_array_where_multiple_types + treasure_1 = treasures(:diamond) + treasure_2 = treasures(:sapphire) + car = cars(:honda) + + expected = [price_estimates(:diamond), price_estimates(:sapphire_1), price_estimates(:sapphire_2), price_estimates(:honda)].sort + actual = PriceEstimate.where(estimate_of: [treasure_1, treasure_2, car]).to_a.sort + + assert_equal expected, actual + end + def test_polymorphic_nested_relation_where - expected = PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: Treasure.where(id: [1,2])) + expected = PriceEstimate.where(estimate_of_type: "Treasure", estimate_of_id: Treasure.where(id: [1,2])) actual = PriceEstimate.where(estimate_of: Treasure.where(id: [1,2])) assert_equal expected.to_sql, actual.to_sql @@ -125,7 +137,7 @@ module ActiveRecord treasure = HiddenTreasure.new treasure.id = 1 - expected = PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: 1) + expected = PriceEstimate.where(estimate_of_type: "Treasure", estimate_of_id: 1) actual = PriceEstimate.where(estimate_of: treasure) assert_equal expected.to_sql, actual.to_sql @@ -135,7 +147,7 @@ module ActiveRecord thing = Post.new thing.id = 1 - expected = Treasure.where(price_estimates: { thing_type: 'Post', thing_id: 1 }).joins(:price_estimates) + expected = Treasure.where(price_estimates: { thing_type: "Post", thing_id: 1 }).joins(:price_estimates) actual = Treasure.where(price_estimates: { thing: thing }).joins(:price_estimates) assert_equal expected.to_sql, actual.to_sql @@ -145,7 +157,7 @@ module ActiveRecord treasure = HiddenTreasure.new treasure.id = 1 - expected = Treasure.where(price_estimates: { estimate_of_type: 'Treasure', estimate_of_id: 1 }).joins(:price_estimates) + expected = Treasure.where(price_estimates: { estimate_of_type: "Treasure", estimate_of_id: 1 }).joins(:price_estimates) actual = Treasure.where(price_estimates: { estimate_of: treasure }).joins(:price_estimates) assert_equal expected.to_sql, actual.to_sql @@ -170,40 +182,40 @@ module ActiveRecord treasure.id = 1 decorated_treasure = treasure_decorator.new(treasure) - expected = PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: 1) + expected = PriceEstimate.where(estimate_of_type: "Treasure", estimate_of_id: 1) actual = PriceEstimate.where(estimate_of: decorated_treasure) assert_equal expected.to_sql, actual.to_sql end def test_aliased_attribute - expected = Topic.where(heading: 'The First Topic') - actual = Topic.where(title: 'The First Topic') + expected = Topic.where(heading: "The First Topic") + actual = Topic.where(title: "The First Topic") assert_equal expected.to_sql, actual.to_sql end def test_where_error - assert_raises(ActiveRecord::StatementInvalid) do - Post.where(:id => { 'posts.author_id' => 10 }).first + assert_nothing_raised do + Post.where(id: { "posts.author_id" => 10 }).first end end def test_where_with_table_name post = Post.first - assert_equal post, Post.where(:posts => { 'id' => post.id }).first + assert_equal post, Post.where(posts: { "id" => post.id }).first end def test_where_with_table_name_and_empty_hash - assert_equal 0, Post.where(:posts => {}).count + assert_equal 0, Post.where(posts: {}).count end def test_where_with_table_name_and_empty_array - assert_equal 0, Post.where(:id => []).count + assert_equal 0, Post.where(id: []).count end def test_where_with_empty_hash_and_no_foreign_key - assert_equal 0, Edge.where(:sink => {}).count + assert_equal 0, Edge.where(sink: {}).count end def test_where_with_blank_conditions @@ -213,32 +225,32 @@ module ActiveRecord end def test_where_with_integer_for_string_column - count = Post.where(:title => 0).count + count = Post.where(title: 0).count assert_equal 0, count end def test_where_with_float_for_string_column - count = Post.where(:title => 0.0).count + count = Post.where(title: 0.0).count assert_equal 0, count end def test_where_with_boolean_for_string_column - count = Post.where(:title => false).count + count = Post.where(title: false).count assert_equal 0, count end def test_where_with_decimal_for_string_column - count = Post.where(:title => BigDecimal.new(0)).count + count = Post.where(title: BigDecimal.new(0)).count assert_equal 0, count end def test_where_with_duration_for_string_column - count = Post.where(:title => 0.seconds).count + count = Post.where(title: 0.seconds).count assert_equal 0, count end def test_where_with_integer_for_binary_column - count = Binary.where(:data => 0).count + count = Binary.where(data: 0).count assert_equal 0, count end diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index 03583344a8..23d27ab90a 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -1,8 +1,8 @@ require "cases/helper" -require 'models/post' -require 'models/comment' -require 'models/author' -require 'models/rating' +require "models/post" +require "models/comment" +require "models/author" +require "models/rating" module ActiveRecord class RelationTest < ActiveRecord::TestCase @@ -18,7 +18,7 @@ module ActiveRecord end def self.table_name - 'fake_table' + "fake_table" end def self.sanitize_sql_for_order(sql) @@ -30,7 +30,7 @@ module ActiveRecord relation = Relation.new(FakeKlass, :b, nil) assert_equal FakeKlass, relation.klass assert_equal :b, relation.table - assert !relation.loaded, 'relation is not loaded' + assert !relation.loaded, "relation is not loaded" end def test_responds_to_model_and_returns_klass @@ -40,9 +40,10 @@ module ActiveRecord def test_initialize_single_values relation = Relation.new(FakeKlass, :b, nil) - (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |method| + (Relation::SINGLE_VALUE_METHODS - [:create_with, :readonly]).each do |method| assert_nil relation.send("#{method}_value"), method.to_s end + assert_equal false, relation.readonly_value value = relation.create_with_value assert_equal({}, value) assert_predicate value, :frozen? @@ -70,7 +71,7 @@ module ActiveRecord def test_has_values relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) relation.where! relation.table[:id].eq(10) - assert_equal({:id => 10}, relation.where_values_hash) + assert_equal({ id: 10 }, relation.where_values_hash) end def test_values_wrong_table @@ -89,8 +90,8 @@ module ActiveRecord end def test_table_name_delegates_to_klass - relation = Relation.new(FakeKlass.new('posts'), :b, Post.predicate_builder) - assert_equal 'posts', relation.table_name + relation = Relation.new(FakeKlass.new("posts"), :b, Post.predicate_builder) + assert_equal "posts", relation.table_name end def test_scope_for_create @@ -100,7 +101,7 @@ module ActiveRecord def test_create_with_value relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) - hash = { :hello => 'world' } + hash = { hello: "world" } relation.create_with_value = hash assert_equal hash, relation.scope_for_create end @@ -108,8 +109,8 @@ module ActiveRecord def test_create_with_value_with_wheres relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) relation.where! relation.table[:id].eq(10) - relation.create_with_value = {:hello => 'world'} - assert_equal({:hello => 'world', :id => 10}, relation.scope_for_create) + relation.create_with_value = { hello: "world" } + assert_equal({ hello: "world", id: 10 }, relation.scope_for_create) end # FIXME: is this really wanted or expected behavior? @@ -120,7 +121,7 @@ module ActiveRecord relation.where! relation.table[:id].eq(10) assert_equal({}, relation.scope_for_create) - relation.create_with_value = {:hello => 'world'} + relation.create_with_value = { hello: "world" } assert_equal({}, relation.scope_for_create) end @@ -145,48 +146,48 @@ module ActiveRecord relation = Relation.new(FakeKlass, :b, nil) assert_equal [], relation.references_values relation = relation.references(:foo).references(:omg, :lol) - assert_equal ['foo', 'omg', 'lol'], relation.references_values + assert_equal ["foo", "omg", "lol"], relation.references_values end def test_references_values_dont_duplicate relation = Relation.new(FakeKlass, :b, nil) relation = relation.references(:foo).references(:foo) - assert_equal ['foo'], relation.references_values + assert_equal ["foo"], relation.references_values end - test 'merging a hash into a relation' do + test "merging a hash into a relation" do relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) - relation = relation.merge where: {name: :lol}, readonly: true + relation = relation.merge where: { name: :lol }, readonly: true - assert_equal({"name"=>:lol}, relation.where_clause.to_h) + assert_equal({ "name"=>:lol }, relation.where_clause.to_h) assert_equal true, relation.readonly_value end - test 'merging an empty hash into a relation' do + test "merging an empty hash into a relation" do assert_equal Relation::WhereClause.empty, Relation.new(FakeKlass, :b, nil).merge({}).where_clause end - test 'merging a hash with unknown keys raises' do - assert_raises(ArgumentError) { Relation::HashMerger.new(nil, omg: 'lol') } + test "merging a hash with unknown keys raises" do + assert_raises(ArgumentError) { Relation::HashMerger.new(nil, omg: "lol") } end - test 'merging nil or false raises' do + test "merging nil or false raises" do relation = Relation.new(FakeKlass, :b, nil) e = assert_raises(ArgumentError) do relation = relation.merge nil end - assert_equal 'invalid argument: nil.', e.message + assert_equal "invalid argument: nil.", e.message e = assert_raises(ArgumentError) do relation = relation.merge false end - assert_equal 'invalid argument: false.', e.message + assert_equal "invalid argument: false.", e.message end - test '#values returns a dup of the values' do + test "#values returns a dup of the values" do relation = Relation.new(Post, Post.arel_table, Post.predicate_builder).where!(name: :foo) values = relation.values @@ -194,22 +195,22 @@ module ActiveRecord assert_not_nil relation.where_clause end - test 'relations can be created with a values hash' do + test "relations can be created with a values hash" do relation = Relation.new(FakeKlass, :b, nil, select: [:foo]) assert_equal [:foo], relation.select_values end - test 'merging a hash interpolates conditions' do + test "merging a hash interpolates conditions" do klass = Class.new(FakeKlass) do def self.sanitize_sql(args) - raise unless args == ['foo = ?', 'bar'] - 'foo = bar' + raise unless args == ["foo = ?", "bar"] + "foo = bar" end end relation = Relation.new(klass, :b, nil) - relation.merge!(where: ['foo = ?', 'bar']) - assert_equal Relation::WhereClause.new(['foo = bar'], []), relation.where_clause + relation.merge!(where: ["foo = ?", "bar"]) + assert_equal Relation::WhereClause.new(["foo = bar"], []), relation.where_clause end def test_merging_readonly_false @@ -223,7 +224,7 @@ module ActiveRecord def test_relation_merging_with_merged_joins_as_symbols special_comments_with_ratings = SpecialComment.joins(:ratings) posts_with_special_comments_with_ratings = Post.group("posts.id").joins(:special_comments).merge(special_comments_with_ratings) - assert_equal 3, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count.length + assert_equal({ 2=>1, 4=>3, 5=>1 }, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count) end def test_relation_merging_with_joins_as_join_dependency_pick_proper_parent @@ -273,7 +274,7 @@ module ActiveRecord join_string = "LEFT OUTER JOIN #{Rating.quoted_table_name} ON #{SpecialComment.quoted_table_name}.id = #{Rating.quoted_table_name}.comment_id" special_comments_with_ratings = SpecialComment.joins join_string posts_with_special_comments_with_ratings = Post.group("posts.id").joins(:special_comments).merge(special_comments_with_ratings) - assert_equal 3, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count.length + assert_equal({ 2=>1, 4=>3, 5=>1 }, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count) end class EnsureRoundTripTypeCasting < ActiveRecord::Type::Value @@ -293,7 +294,7 @@ module ActiveRecord end class UpdateAllTestModel < ActiveRecord::Base - self.table_name = 'posts' + self.table_name = "posts" attribute :body, EnsureRoundTripTypeCasting.new end @@ -306,23 +307,23 @@ module ActiveRecord private - def skip_if_sqlite3_version_includes_quoting_bug - if sqlite3_version_includes_quoting_bug? - skip <<-ERROR.squish - You are using an outdated version of SQLite3 which has a bug in - quoted column names. Please update SQLite3 and rebuild the sqlite3 - ruby gem - ERROR + def skip_if_sqlite3_version_includes_quoting_bug + if sqlite3_version_includes_quoting_bug? + skip <<-ERROR.squish + You are using an outdated version of SQLite3 which has a bug in + quoted column names. Please update SQLite3 and rebuild the sqlite3 + ruby gem + ERROR + end end - end - def sqlite3_version_includes_quoting_bug? - if current_adapter?(:SQLite3Adapter) - selected_quoted_column_names = ActiveRecord::Base.connection.exec_query( - 'SELECT "join" FROM (SELECT id AS "join" FROM posts) subquery' - ).columns - ["join"] != selected_quoted_column_names + def sqlite3_version_includes_quoting_bug? + if current_adapter?(:SQLite3Adapter) + selected_quoted_column_names = ActiveRecord::Base.connection.exec_query( + 'SELECT "join" FROM (SELECT id AS "join" FROM posts) subquery' + ).columns + ["join"] != selected_quoted_column_names + end end - end end end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 090b885dd5..2e18c43b1b 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -1,21 +1,21 @@ require "cases/helper" -require 'models/tag' -require 'models/tagging' -require 'models/post' -require 'models/topic' -require 'models/comment' -require 'models/author' -require 'models/entrant' -require 'models/developer' -require 'models/computer' -require 'models/reply' -require 'models/company' -require 'models/bird' -require 'models/car' -require 'models/engine' -require 'models/tyre' -require 'models/minivan' -require 'models/aircraft' +require "models/tag" +require "models/tagging" +require "models/post" +require "models/topic" +require "models/comment" +require "models/author" +require "models/entrant" +require "models/developer" +require "models/computer" +require "models/reply" +require "models/company" +require "models/bird" +require "models/car" +require "models/engine" +require "models/tyre" +require "models/minivan" +require "models/aircraft" require "models/possession" require "models/reader" require "models/categorization" @@ -27,19 +27,19 @@ class RelationTest < ActiveRecord::TestCase class TopicWithCallbacks < ActiveRecord::Base self.table_name = :topics - before_update { |topic| topic.author_name = 'David' if topic.author_name.blank? } + before_update { |topic| topic.author_name = "David" if topic.author_name.blank? } end def test_do_not_double_quote_string_id van = Minivan.last assert van - assert_equal van.id, Minivan.where(:minivan_id => van).to_a.first.minivan_id + assert_equal van.id, Minivan.where(minivan_id: van).to_a.first.minivan_id end def test_do_not_double_quote_string_id_with_array van = Minivan.last assert van - assert_equal van, Minivan.where(:minivan_id => [van]).to_a.first + assert_equal van, Minivan.where(minivan_id: [van]).to_a.first end def test_two_scopes_with_includes_should_not_drop_any_include @@ -54,12 +54,12 @@ class RelationTest < ActiveRecord::TestCase end def test_dynamic_finder - x = Post.where('author_id = ?', 1) - assert x.klass.respond_to?(:find_by_id), '@klass should handle dynamic finders' + x = Post.where("author_id = ?", 1) + assert x.klass.respond_to?(:find_by_id), "@klass should handle dynamic finders" end def test_multivalue_where - posts = Post.where('author_id = ? AND id = ?', 1, 1) + posts = Post.where("author_id = ? AND id = ?", 1, 1) assert_equal 1, posts.to_a.size end @@ -101,7 +101,7 @@ class RelationTest < ActiveRecord::TestCase end def test_scoped_first - topics = Topic.all.order('id ASC') + topics = Topic.all.order("id ASC") assert_queries(1) do 2.times { assert_equal "The First Topic", topics.first.title } @@ -111,7 +111,7 @@ class RelationTest < ActiveRecord::TestCase end def test_loaded_first - topics = Topic.all.order('id ASC') + topics = Topic.all.order("id ASC") topics.to_a # force load assert_no_queries do @@ -122,7 +122,7 @@ class RelationTest < ActiveRecord::TestCase end def test_loaded_first_with_limit - topics = Topic.all.order('id ASC') + topics = Topic.all.order("id ASC") topics.to_a # force load assert_no_queries do @@ -134,7 +134,7 @@ class RelationTest < ActiveRecord::TestCase end def test_first_get_more_than_available - topics = Topic.all.order('id ASC') + topics = Topic.all.order("id ASC") unloaded_first = topics.first(10) topics.to_a # force load @@ -154,7 +154,7 @@ class RelationTest < ActiveRecord::TestCase assert topics.loaded? original_size = topics.to_a.size - Topic.create! :title => 'fake' + Topic.create! title: "fake" assert_queries(1) { topics.reload } assert_equal original_size + 1, topics.size @@ -162,17 +162,17 @@ class RelationTest < ActiveRecord::TestCase end def test_finding_with_subquery - relation = Topic.where(:approved => true) - assert_equal relation.to_a, Topic.select('*').from(relation).to_a - assert_equal relation.to_a, Topic.select('subquery.*').from(relation).to_a - assert_equal relation.to_a, Topic.select('a.*').from(relation, :a).to_a + relation = Topic.where(approved: true) + assert_equal relation.to_a, Topic.select("*").from(relation).to_a + assert_equal relation.to_a, Topic.select("subquery.*").from(relation).to_a + assert_equal relation.to_a, Topic.select("a.*").from(relation, :a).to_a end def test_finding_with_subquery_with_binds relation = Post.first.comments - assert_equal relation.to_a, Comment.select('*').from(relation).to_a - assert_equal relation.to_a, Comment.select('subquery.*').from(relation).to_a - assert_equal relation.to_a, Comment.select('a.*').from(relation, :a).to_a + assert_equal relation.to_a, Comment.select("*").from(relation).to_a + assert_equal relation.to_a, Comment.select("subquery.*").from(relation).to_a + assert_equal relation.to_a, Comment.select("a.*").from(relation, :a).to_a end def test_finding_with_subquery_without_select_does_not_change_the_select @@ -183,25 +183,25 @@ class RelationTest < ActiveRecord::TestCase end def test_select_with_subquery_in_from_does_not_use_original_table_name - relation = Comment.group(:type).select('COUNT(post_id) AS post_count, type') - subquery = Comment.from(relation).select('type','post_count') + relation = Comment.group(:type).select("COUNT(post_id) AS post_count, type") + subquery = Comment.from(relation).select("type","post_count") assert_equal(relation.map(&:post_count).sort,subquery.map(&:post_count).sort) end def test_group_with_subquery_in_from_does_not_use_original_table_name - relation = Comment.group(:type).select('COUNT(post_id) AS post_count,type') - subquery = Comment.from(relation).group('type').average("post_count") + relation = Comment.group(:type).select("COUNT(post_id) AS post_count,type") + subquery = Comment.from(relation).group("type").average("post_count") assert_equal(relation.map(&:post_count).sort,subquery.values.sort) end def test_finding_with_conditions - assert_equal ["David"], Author.where(:name => 'David').map(&:name) - assert_equal ['Mary'], Author.where(["name = ?", 'Mary']).map(&:name) - assert_equal ['Mary'], Author.where("name = ?", 'Mary').map(&:name) + assert_equal ["David"], Author.where(name: "David").map(&:name) + assert_equal ["Mary"], Author.where(["name = ?", "Mary"]).map(&:name) + assert_equal ["Mary"], Author.where("name = ?", "Mary").map(&:name) end def test_finding_with_order - topics = Topic.order('id') + topics = Topic.order("id") assert_equal 5, topics.to_a.size assert_equal topics(:first).title, topics.first.title end @@ -213,13 +213,13 @@ class RelationTest < ActiveRecord::TestCase end def test_finding_with_assoc_order - topics = Topic.order(:id => :desc) + topics = Topic.order(id: :desc) assert_equal 5, topics.to_a.size assert_equal topics(:fifth).title, topics.first.title end def test_finding_with_reverted_assoc_order - topics = Topic.order(:id => :asc).reverse_order + topics = Topic.order(id: :asc).reverse_order assert_equal 5, topics.to_a.size assert_equal topics(:fifth).title, topics.first.title end @@ -240,6 +240,15 @@ class RelationTest < ActiveRecord::TestCase assert_raises(ActiveRecord::IrreversibleOrderError) do Topic.order("concat(author_name, title)").reverse_order end + assert_raises(ActiveRecord::IrreversibleOrderError) do + Topic.order("concat(lower(author_name), title)").reverse_order + end + assert_raises(ActiveRecord::IrreversibleOrderError) do + Topic.order("concat(author_name, lower(title))").reverse_order + end + assert_raises(ActiveRecord::IrreversibleOrderError) do + Topic.order("concat(lower(author_name), title, length(title)").reverse_order + end end def test_reverse_order_with_nulls_first_or_last @@ -258,7 +267,7 @@ class RelationTest < ActiveRecord::TestCase end def test_order_with_hash_and_symbol_generates_the_same_sql - assert_equal Topic.order(:id).to_sql, Topic.order(:id => :asc).to_sql + assert_equal Topic.order(:id).to_sql, Topic.order(id: :asc).to_sql end def test_finding_with_desc_order_with_string @@ -268,7 +277,7 @@ class RelationTest < ActiveRecord::TestCase end def test_finding_with_asc_order_with_string - topics = Topic.order(id: 'asc') + topics = Topic.order(id: "asc") assert_equal 5, topics.to_a.size assert_equal [topics(:first), topics(:second), topics(:third), topics(:fourth), topics(:fifth)], topics.to_a end @@ -296,7 +305,7 @@ class RelationTest < ActiveRecord::TestCase end def test_finding_with_order_concatenated - topics = Topic.order('author_name').order('title') + topics = Topic.order("author_name").order("title") assert_equal 5, topics.to_a.size assert_equal topics(:fourth).title, topics.first.title end @@ -314,19 +323,19 @@ class RelationTest < ActiveRecord::TestCase end def test_finding_with_reorder - topics = Topic.order('author_name').order('title').reorder('id').to_a + topics = Topic.order("author_name").order("title").reorder("id").to_a topics_titles = topics.map(&:title) - assert_equal ['The First Topic', 'The Second Topic of the day', 'The Third Topic of the day', 'The Fourth Topic of the day', 'The Fifth Topic of the day'], topics_titles + assert_equal ["The First Topic", "The Second Topic of the day", "The Third Topic of the day", "The Fourth Topic of the day", "The Fifth Topic of the day"], topics_titles end def test_finding_with_reorder_by_aliased_attributes - topics = Topic.order('author_name').reorder(:heading) + topics = Topic.order("author_name").reorder(:heading) assert_equal 5, topics.to_a.size assert_equal topics(:fifth).title, topics.first.title end def test_finding_with_assoc_reorder_by_aliased_attributes - topics = Topic.order('author_name').reorder(heading: :desc) + topics = Topic.order("author_name").reorder(heading: :desc) assert_equal 5, topics.to_a.size assert_equal topics(:third).title, topics.first.title end @@ -358,6 +367,12 @@ class RelationTest < ActiveRecord::TestCase def test_finding_with_sanitized_order query = Tag.order(["field(id, ?)", [1,3,2]]).to_sql assert_match(/field\(id, 1,3,2\)/, query) + + query = Tag.order(["field(id, ?)", []]).to_sql + assert_match(/field\(id, NULL\)/, query) + + query = Tag.order(["field(id, ?)", nil]).to_sql + assert_match(/field\(id, NULL\)/, query) end def test_finding_with_order_limit_and_offset @@ -378,7 +393,7 @@ class RelationTest < ActiveRecord::TestCase end def test_select_with_block - even_ids = Developer.all.select {|d| d.id % 2 == 0 }.map(&:id) + even_ids = Developer.all.select { |d| d.id % 2 == 0 }.map(&:id) assert_equal [2, 4, 6, 8, 10], even_ids.sort end @@ -391,7 +406,7 @@ class RelationTest < ActiveRecord::TestCase def test_none_chainable assert_no_queries(ignore_none: false) do - assert_equal [], Developer.none.where(:name => 'David') + assert_equal [], Developer.none.where(name: "David") end end @@ -405,7 +420,7 @@ class RelationTest < ActiveRecord::TestCase assert_no_queries(ignore_none: false) do assert_equal [], Developer.none.pluck(:id, :name) assert_equal 0, Developer.none.delete_all - assert_equal 0, Developer.none.update_all(:name => 'David') + assert_equal 0, Developer.none.update_all(name: "David") assert_equal 0, Developer.none.delete(1) assert_equal false, Developer.none.exists?(1) end @@ -427,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_equal nil, Developer.none.calculate(:average, "salary") end end @@ -437,7 +452,7 @@ class RelationTest < ActiveRecord::TestCase end def test_null_relation_where_values_hash - assert_equal({ 'salary' => 100_000 }, Developer.none.where(salary: 100_000).where_values_hash) + assert_equal({ "salary" => 100_000 }, Developer.none.where(salary: 100_000).where_values_hash) end def test_null_relation_sum @@ -504,23 +519,23 @@ class RelationTest < ActiveRecord::TestCase end def test_finding_with_hash_conditions_on_joined_table - firms = DependentFirm.joins(:account).where({:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }}).to_a + firms = DependentFirm.joins(:account).where(name: "RailsCore", accounts: { credit_limit: 55..60 }).to_a assert_equal 1, firms.size assert_equal companies(:rails_core), firms.first end def test_find_all_with_join - developers_on_project_one = Developer.joins('LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id'). - where('project_id=1').to_a + developers_on_project_one = Developer.joins("LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id"). + where("project_id=1").to_a assert_equal 3, developers_on_project_one.length developer_names = developers_on_project_one.map(&:name) - assert developer_names.include?('David') - assert developer_names.include?('Jamis') + assert_includes developer_names, "David" + assert_includes developer_names, "Jamis" end def test_find_on_hash_conditions - assert_equal Topic.all.merge!(:where => {:approved => false}).to_a, Topic.where({ :approved => false }).to_a + assert_equal Topic.all.merge!(where: { approved: false }).to_a, Topic.where(approved: false).to_a end def test_joins_with_string_array @@ -559,7 +574,7 @@ class RelationTest < ActiveRecord::TestCase def test_respond_to_delegates_to_relation relation = Topic.all fake_arel = Struct.new(:responds) { - def respond_to? method, access = false + def respond_to?(method, access = false) responds << [method, access] end }.new [] @@ -593,8 +608,8 @@ class RelationTest < ActiveRecord::TestCase end def test_eager_association_loading_of_stis_with_multiple_references - authors = Author.eager_load(:posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } }). - order('comments.body, very_special_comments_posts.body').where('posts.id = 4').to_a + authors = Author.eager_load(posts: { special_comments: { post: [ :special_comments, :very_special_comment ] } }). + order("comments.body, very_special_comments_posts.body").where("posts.id = 4").to_a assert_equal [authors(:david)], authors assert_no_queries do @@ -605,27 +620,27 @@ class RelationTest < ActiveRecord::TestCase def test_find_with_preloaded_associations assert_queries(2) do - posts = Post.preload(:comments).order('posts.id') + posts = Post.preload(:comments).order("posts.id") assert posts.first.comments.first end assert_queries(2) do - posts = Post.preload(:comments).order('posts.id') + posts = Post.preload(:comments).order("posts.id") assert posts.first.comments.first end assert_queries(2) do - posts = Post.preload(:author).order('posts.id') + posts = Post.preload(:author).order("posts.id") assert posts.first.author end assert_queries(2) do - posts = Post.preload(:author).order('posts.id') + posts = Post.preload(:author).order("posts.id") assert posts.first.author end assert_queries(3) do - posts = Post.preload(:author, :comments).order('posts.id') + posts = Post.preload(:author, :comments).order("posts.id") assert posts.first.author assert posts.first.comments.first end @@ -640,58 +655,58 @@ class RelationTest < ActiveRecord::TestCase def test_find_with_included_associations assert_queries(2) do - posts = Post.includes(:comments).order('posts.id') + posts = Post.includes(:comments).order("posts.id") assert posts.first.comments.first end assert_queries(2) do - posts = Post.all.includes(:comments).order('posts.id') + posts = Post.all.includes(:comments).order("posts.id") assert posts.first.comments.first end assert_queries(2) do - posts = Post.includes(:author).order('posts.id') + posts = Post.includes(:author).order("posts.id") assert posts.first.author end assert_queries(3) do - posts = Post.includes(:author, :comments).order('posts.id') + posts = Post.includes(:author, :comments).order("posts.id") assert posts.first.author assert posts.first.comments.first end end def test_default_scope_with_conditions_string - assert_equal Developer.where(name: 'David').map(&:id).sort, DeveloperCalledDavid.all.map(&:id).sort + assert_equal Developer.where(name: "David").map(&:id).sort, DeveloperCalledDavid.all.map(&:id).sort assert_nil DeveloperCalledDavid.create!.name end def test_default_scope_with_conditions_hash - assert_equal Developer.where(name: 'Jamis').map(&:id).sort, DeveloperCalledJamis.all.map(&:id).sort - assert_equal 'Jamis', DeveloperCalledJamis.create!.name + assert_equal Developer.where(name: "Jamis").map(&:id).sort, DeveloperCalledJamis.all.map(&:id).sort + assert_equal "Jamis", DeveloperCalledJamis.create!.name end def test_default_scoping_finder_methods - developers = DeveloperCalledDavid.order('id').map(&:id).sort - assert_equal Developer.where(name: 'David').map(&:id).sort, developers + developers = DeveloperCalledDavid.order("id").map(&:id).sort + assert_equal Developer.where(name: "David").map(&:id).sort, developers end def test_includes_with_select - query = Post.select('comments_count AS ranking').order('ranking').includes(:comments) + query = Post.select("comments_count AS ranking").order("ranking").includes(:comments) .where(comments: { id: 1 }) - assert_equal ['comments_count AS ranking'], query.select_values + assert_equal ["comments_count AS ranking"], query.select_values assert_equal 1, query.to_a.size end def test_preloading_with_associations_and_merges - post = Post.create! title: 'Uhuu', body: 'body' + post = Post.create! title: "Uhuu", body: "body" reader = Reader.create! post_id: post.id, person_id: 1 - comment = Comment.create! post_id: post.id, body: 'body' + comment = Comment.create! post_id: post.id, body: "body" assert !comment.respond_to?(:readers) - post_rel = Post.preload(:readers).joins(:readers).where(title: 'Uhuu') + post_rel = Post.preload(:readers).joins(:readers).where(title: "Uhuu") result_comment = Comment.joins(:post).merge(post_rel).to_a.first assert_equal comment, result_comment @@ -700,7 +715,7 @@ class RelationTest < ActiveRecord::TestCase assert_equal [reader], result_comment.post.readers.to_a end - post_rel = Post.includes(:readers).where(title: 'Uhuu') + post_rel = Post.includes(:readers).where(title: "Uhuu") result_comment = Comment.joins(:post).merge(post_rel).first assert_equal comment, result_comment @@ -711,17 +726,17 @@ class RelationTest < ActiveRecord::TestCase end def test_preloading_with_associations_default_scopes_and_merges - post = Post.create! title: 'Uhuu', body: 'body' + post = Post.create! title: "Uhuu", body: "body" reader = Reader.create! post_id: post.id, person_id: 1 - post_rel = PostWithPreloadDefaultScope.preload(:readers).joins(:readers).where(title: 'Uhuu') + post_rel = PostWithPreloadDefaultScope.preload(:readers).joins(:readers).where(title: "Uhuu") result_post = PostWithPreloadDefaultScope.all.merge(post_rel).to_a.first assert_no_queries do assert_equal [reader], result_post.readers.to_a end - post_rel = PostWithIncludesDefaultScope.includes(:readers).where(title: 'Uhuu') + post_rel = PostWithIncludesDefaultScope.includes(:readers).where(title: "Uhuu") result_post = PostWithIncludesDefaultScope.all.merge(post_rel).to_a.first assert_no_queries do @@ -733,11 +748,11 @@ class RelationTest < ActiveRecord::TestCase posts = Post.preload(:comments) post = posts.find { |p| p.id == 1 } assert_equal 2, post.comments.size - assert post.comments.include?(comments(:greetings)) + assert_includes post.comments, comments(:greetings) post = Post.where("posts.title = 'Welcome to the weblog'").preload(:comments).first assert_equal 2, post.comments.size - assert post.comments.include?(comments(:greetings)) + assert_includes post.comments, comments(:greetings) posts = Post.preload(:last_comment) post = posts.find { |p| p.id == 1 } @@ -746,9 +761,9 @@ class RelationTest < ActiveRecord::TestCase def test_to_sql_on_eager_join expected = assert_sql { - Post.eager_load(:last_comment).order('comments.id DESC').to_a + Post.eager_load(:last_comment).order("comments.id DESC").to_a }.first - actual = Post.eager_load(:last_comment).order('comments.id DESC').to_sql + actual = Post.eager_load(:last_comment).order("comments.id DESC").to_sql assert_equal expected, actual end @@ -759,7 +774,7 @@ class RelationTest < ActiveRecord::TestCase end def test_loading_with_one_association_with_non_preload - posts = Post.eager_load(:last_comment).order('comments.id DESC') + posts = Post.eager_load(:last_comment).order("comments.id DESC") post = posts.find { |p| p.id == 1 } assert_equal Post.find(1).last_comment, post.last_comment end @@ -783,40 +798,40 @@ class RelationTest < ActiveRecord::TestCase author = Author.all.find_by_id!(authors(:david).id) assert_equal "David", author.name - assert_raises(ActiveRecord::RecordNotFound) { Author.all.find_by_id_and_name!(20, 'invalid') } + assert_raises(ActiveRecord::RecordNotFound) { Author.all.find_by_id_and_name!(20, "invalid") } end def test_find_id authors = Author.all david = authors.find(authors(:david).id) - assert_equal 'David', david.name + assert_equal "David", david.name - assert_raises(ActiveRecord::RecordNotFound) { authors.where(:name => 'lifo').find('42') } + assert_raises(ActiveRecord::RecordNotFound) { authors.where(name: "lifo").find("42") } end def test_find_ids - authors = Author.order('id ASC') + authors = Author.order("id ASC") results = authors.find(authors(:david).id, authors(:mary).id) assert_kind_of Array, results assert_equal 2, results.size - assert_equal 'David', results[0].name - assert_equal 'Mary', results[1].name + assert_equal "David", results[0].name + assert_equal "Mary", results[1].name assert_equal results, authors.find([authors(:david).id, authors(:mary).id]) - assert_raises(ActiveRecord::RecordNotFound) { authors.where(:name => 'lifo').find(authors(:david).id, '42') } - assert_raises(ActiveRecord::RecordNotFound) { authors.find(['42', 43]) } + assert_raises(ActiveRecord::RecordNotFound) { authors.where(name: "lifo").find(authors(:david).id, "42") } + assert_raises(ActiveRecord::RecordNotFound) { authors.find(["42", 43]) } end def test_find_in_empty_array - authors = Author.all.where(:id => []) + authors = Author.all.where(id: []) assert authors.to_a.blank? end def test_where_with_ar_object author = Author.first - authors = Author.all.where(:id => author) + authors = Author.all.where(id: author) assert_equal 1, authors.to_a.length end @@ -829,9 +844,9 @@ class RelationTest < ActiveRecord::TestCase class Mary < Author; end def test_find_by_classname - Author.create!(:name => Mary.name) + Author.create!(name: Mary.name) assert_deprecated do - assert_equal 1, Author.where(:name => Mary).size + assert_equal 1, Author.where(name: Mary).size end end @@ -844,25 +859,25 @@ class RelationTest < ActiveRecord::TestCase def test_find_all_using_where_twice_should_or_the_relation david = authors(:david) relation = Author.unscoped - relation = relation.where(:name => david.name) - relation = relation.where(:name => 'Santiago') - relation = relation.where(:id => david.id) + relation = relation.where(name: david.name) + relation = relation.where(name: "Santiago") + relation = relation.where(id: david.id) assert_equal [], relation.to_a end def test_multi_where_ands_queries relation = Author.unscoped david = authors(:david) - sql = relation.where(:name => david.name).where(:name => 'Santiago').to_sql - assert_match('AND', sql) + sql = relation.where(name: david.name).where(name: "Santiago").to_sql + assert_match("AND", sql) end def test_find_all_with_multiple_should_use_and david = authors(:david) relation = [ - { :name => david.name }, - { :name => 'Santiago' }, - { :name => 'tenderlove' }, + { name: david.name }, + { name: "Santiago" }, + { name: "tenderlove" }, ].inject(Author.unscoped) do |memo, param| memo.where(param) end @@ -881,17 +896,17 @@ class RelationTest < ActiveRecord::TestCase # switching the lines below would succeed in current rails # assert_queries(2) { assert_queries(1) { - relation = Author.where(:id => Author.where(:id => david.id)) + relation = Author.where(id: Author.where(id: david.id)) assert_equal [david], relation.to_a } assert_queries(1) { - relation = Author.where('id in (?)', Author.where(id: david).select(:id)) + relation = Author.where("id in (?)", Author.where(id: david).select(:id)) assert_equal [david], relation.to_a } assert_queries(1) do - relation = Author.where('id in (:author_ids)', author_ids: Author.where(id: david).select(:id)) + relation = Author.where("id in (:author_ids)", author_ids: Author.where(id: david).select(:id)) assert_equal [david], relation.to_a end end @@ -906,13 +921,13 @@ class RelationTest < ActiveRecord::TestCase end assert_queries(1) do - relation = Post.where('id in (?)', david.posts.select(:id)) - assert_equal davids_posts, relation.order(:id).to_a, 'should process Relation as bind variables' + relation = Post.where("id in (?)", david.posts.select(:id)) + assert_equal davids_posts, relation.order(:id).to_a, "should process Relation as bind variables" end assert_queries(1) do - relation = Post.where('id in (:post_ids)', post_ids: david.posts.select(:id)) - assert_equal davids_posts, relation.order(:id).to_a, 'should process Relation as named bind variables' + relation = Post.where("id in (:post_ids)", post_ids: david.posts.select(:id)) + assert_equal davids_posts, relation.order(:id).to_a, "should process Relation as named bind variables" end end @@ -921,7 +936,7 @@ class RelationTest < ActiveRecord::TestCase # switching the lines below would succeed in current rails # assert_queries(2) { assert_queries(1) { - relation = Minivan.where(:minivan_id => Minivan.where(:name => cool_first.name)) + relation = Minivan.where(minivan_id: Minivan.where(name: cool_first.name)) assert_equal [cool_first], relation.to_a } end @@ -929,10 +944,10 @@ class RelationTest < ActiveRecord::TestCase def test_find_all_using_where_with_relation_does_not_alter_select_values david = authors(:david) - subquery = Author.where(:id => david.id) + subquery = Author.where(id: david.id) assert_queries(1) { - relation = Author.where(:id => subquery) + relation = Author.where(id: subquery) assert_equal [david], relation.to_a } @@ -942,22 +957,21 @@ class RelationTest < ActiveRecord::TestCase def test_find_all_using_where_with_relation_with_joins david = authors(:david) assert_queries(1) { - relation = Author.where(:id => Author.joins(:posts).where(:id => david.id)) + relation = Author.where(id: Author.joins(:posts).where(id: david.id)) assert_equal [david], relation.to_a } end - def test_find_all_using_where_with_relation_with_select_to_build_subquery david = authors(:david) assert_queries(1) { - relation = Author.where(:name => Author.where(:id => david.id).select(:name)) + relation = Author.where(name: Author.where(id: david.id).select(:name)) assert_equal [david], relation.to_a } end def test_exists - davids = Author.where(:name => 'David') + davids = Author.where(name: "David") assert davids.exists? assert davids.exists?(authors(:david).id) assert ! davids.exists?(authors(:mary).id) @@ -965,7 +979,7 @@ class RelationTest < ActiveRecord::TestCase assert ! davids.exists?(42) assert ! davids.exists?(davids.new.id) - fake = Author.where(:name => 'fake author') + fake = Author.where(name: "fake author") assert ! fake.exists? assert ! fake.exists?(authors(:david).id) end @@ -988,13 +1002,13 @@ class RelationTest < ActiveRecord::TestCase end def test_destroy_all - davids = Author.where(:name => 'David') + davids = Author.where(name: "David") # Force load assert_equal [authors(:david)], davids.to_a assert davids.loaded? - assert_difference('Author.count', -1) { davids.destroy_all } + assert_difference("Author.count", -1) { davids.destroy_all } assert_equal [], davids.to_a assert davids.loaded? @@ -1002,31 +1016,31 @@ class RelationTest < ActiveRecord::TestCase def test_destroy_all_with_conditions_is_deprecated assert_deprecated do - assert_difference('Author.count', -1) { Author.destroy_all(name: 'David') } + assert_difference("Author.count", -1) { Author.destroy_all(name: "David") } end end def test_delete_all - davids = Author.where(:name => 'David') + davids = Author.where(name: "David") - assert_difference('Author.count', -1) { davids.delete_all } + assert_difference("Author.count", -1) { davids.delete_all } 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') } + assert_difference("Author.count", -1) { Author.delete_all(name: "David") } end end def test_delete_all_loaded - davids = Author.where(:name => 'David') + davids = Author.where(name: "David") # Force load assert_equal [authors(:david)], davids.to_a assert davids.loaded? - assert_difference('Author.count', -1) { davids.delete_all } + assert_difference("Author.count", -1) { davids.delete_all } assert_equal [], davids.to_a assert davids.loaded? @@ -1036,7 +1050,7 @@ class RelationTest < ActiveRecord::TestCase assert_raises(ActiveRecord::ActiveRecordError) { Author.limit(10).delete_all } assert_raises(ActiveRecord::ActiveRecordError) { Author.distinct.delete_all } assert_raises(ActiveRecord::ActiveRecordError) { Author.group(:name).delete_all } - assert_raises(ActiveRecord::ActiveRecordError) { Author.having('SUM(id) < 3').delete_all } + assert_raises(ActiveRecord::ActiveRecordError) { Author.having("SUM(id) < 3").delete_all } assert_raises(ActiveRecord::ActiveRecordError) { Author.offset(10).delete_all } end @@ -1076,8 +1090,13 @@ class RelationTest < ActiveRecord::TestCase assert_equal 11, posts.count(:all) assert_equal 11, posts.count(:id) - assert_equal 1, posts.where('comments_count > 1').count - assert_equal 9, posts.where(:comments_count => 0).count + assert_equal 1, posts.where("comments_count > 1").count + assert_equal 9, posts.where(comments_count: 0).count + end + + def test_count_with_block + posts = Post.all + assert_equal 10, posts.count { |p| p.comments_count.even? } end def test_count_on_association_relation @@ -1106,24 +1125,24 @@ class RelationTest < ActiveRecord::TestCase Post.tagged_with(tag.id).update_all title: "rofl" list = Post.tagged_with(tag.id).all.to_a assert_operator list.length, :>, 0 - list.each { |post| assert_equal 'rofl', post.title } + list.each { |post| assert_equal "rofl", post.title } end def test_count_explicit_columns - Post.update_all(:comments_count => nil) + Post.update_all(comments_count: nil) posts = Post.all - assert_equal [0], posts.select('comments_count').where('id is not null').group('id').order('id').count.values.uniq - assert_equal 0, posts.where('id is not null').select('comments_count').count + assert_equal [0], posts.select("comments_count").where("id is not null").group("id").order("id").count.values.uniq + assert_equal 0, posts.where("id is not null").select("comments_count").count - assert_equal 11, posts.select('comments_count').count('id') - assert_equal 0, posts.select('comments_count').count + assert_equal 11, posts.select("comments_count").count("id") + assert_equal 0, posts.select("comments_count").count assert_equal 0, posts.count(:comments_count) - assert_equal 0, posts.count('comments_count') + assert_equal 0, posts.count("comments_count") end def test_multiple_selects - post = Post.all.select('comments_count').select('title').order("id ASC").first + post = Post.all.select("comments_count").select("title").order("id ASC").first assert_equal "Welcome to the weblog", post.title assert_equal 2, post.comments_count end @@ -1134,7 +1153,7 @@ class RelationTest < ActiveRecord::TestCase assert_queries(1) { assert_equal 11, posts.size } assert ! posts.loaded? - best_posts = posts.where(:comments_count => 0) + best_posts = posts.where(comments_count: 0) best_posts.to_a # force load assert_no_queries { assert_equal 9, best_posts.size } end @@ -1145,7 +1164,7 @@ class RelationTest < ActiveRecord::TestCase assert_queries(1) { assert_equal 10, posts.size } assert ! posts.loaded? - best_posts = posts.where(:comments_count => 0) + best_posts = posts.where(comments_count: 0) best_posts.to_a # force load assert_no_queries { assert_equal 9, best_posts.size } end @@ -1168,7 +1187,7 @@ class RelationTest < ActiveRecord::TestCase end def test_count_complex_chained_relations - posts = Post.select('comments_count').where('id is not null').group("author_id").where("comments_count > 0") + posts = Post.select("comments_count").where("id is not null").group("author_id").where("comments_count > 0") expected = { 1 => 2 } assert_equal expected, posts.count @@ -1180,11 +1199,11 @@ class RelationTest < ActiveRecord::TestCase assert_queries(1) { assert_equal false, posts.empty? } assert ! posts.loaded? - no_posts = posts.where(:title => "") + no_posts = posts.where(title: "") assert_queries(1) { assert_equal true, no_posts.empty? } assert ! no_posts.loaded? - best_posts = posts.where(:comments_count => 0) + best_posts = posts.where(comments_count: 0) best_posts.to_a # force load assert_no_queries { assert_equal false, best_posts.empty? } end @@ -1195,7 +1214,7 @@ class RelationTest < ActiveRecord::TestCase assert_queries(1) { assert_equal false, posts.empty? } assert ! posts.loaded? - no_posts = posts.where(:title => "") + no_posts = posts.where(title: "") assert_queries(1) { assert_equal true, no_posts.empty? } assert ! no_posts.loaded? end @@ -1209,14 +1228,14 @@ class RelationTest < ActiveRecord::TestCase # the SHOW TABLES result to be cached so we don't have to do it again in the block. # # This is obviously a rubbish fix but it's the best I can come up with for now... - posts.where(:id => nil).any? + posts.where(id: nil).any? assert_queries(3) do assert posts.any? # Uses COUNT() - assert ! posts.where(:id => nil).any? + assert ! posts.where(id: nil).any? - assert posts.any? {|p| p.id > 0 } - assert ! posts.any? {|p| p.id <= 0 } + assert posts.any? { |p| p.id > 0 } + assert ! posts.any? { |p| p.id <= 0 } end assert posts.loaded? @@ -1227,8 +1246,8 @@ class RelationTest < ActiveRecord::TestCase assert_queries(2) do assert posts.many? # Uses COUNT() - assert posts.many? {|p| p.id > 0 } - assert ! posts.many? {|p| p.id < 2 } + assert posts.many? { |p| p.id > 0 } + assert ! posts.many? { |p| p.id < 2 } end assert posts.loaded? @@ -1250,8 +1269,8 @@ class RelationTest < ActiveRecord::TestCase assert ! posts.loaded? assert_queries(1) do - assert posts.none? {|p| p.id < 0 } - assert ! posts.none? {|p| p.id == 1 } + assert posts.none? { |p| p.id < 0 } + assert ! posts.none? { |p| p.id == 1 } end assert posts.loaded? @@ -1266,13 +1285,23 @@ class RelationTest < ActiveRecord::TestCase assert ! posts.loaded? assert_queries(1) do - assert ! posts.one? {|p| p.id < 3 } - assert posts.one? {|p| p.id == 1 } + assert ! posts.one? { |p| p.id < 3 } + assert posts.one? { |p| p.id == 1 } end assert posts.loaded? end + def test_to_a_should_dup_target + posts = Post.all + + original_size = posts.size + removed = posts.to_a.pop + + assert_equal original_size, posts.size + assert_includes posts.to_a, removed + end + def test_build posts = Post.all @@ -1281,11 +1310,11 @@ class RelationTest < ActiveRecord::TestCase end def test_scoped_build - posts = Post.where(:title => 'You told a lie') + posts = Post.where(title: "You told a lie") post = posts.new assert_kind_of Post, post - assert_equal 'You told a lie', post.title + assert_equal "You told a lie", post.title end def test_create @@ -1295,9 +1324,9 @@ class RelationTest < ActiveRecord::TestCase assert_kind_of Bird, sparrow assert !sparrow.persisted? - hen = birds.where(:name => 'hen').create + hen = birds.where(name: "hen").create assert hen.persisted? - assert_equal 'hen', hen.name + assert_equal "hen", hen.name end def test_create_bang @@ -1305,201 +1334,201 @@ class RelationTest < ActiveRecord::TestCase assert_raises(ActiveRecord::RecordInvalid) { birds.create! } - hen = birds.where(:name => 'hen').create! + hen = birds.where(name: "hen").create! assert_kind_of Bird, hen assert hen.persisted? - assert_equal 'hen', hen.name + assert_equal "hen", hen.name end def test_first_or_create - parrot = Bird.where(:color => 'green').first_or_create(:name => 'parrot') + parrot = Bird.where(color: "green").first_or_create(name: "parrot") assert_kind_of Bird, parrot assert parrot.persisted? - assert_equal 'parrot', parrot.name - assert_equal 'green', parrot.color + assert_equal "parrot", parrot.name + assert_equal "green", parrot.color - same_parrot = Bird.where(:color => 'green').first_or_create(:name => 'parakeet') + same_parrot = Bird.where(color: "green").first_or_create(name: "parakeet") assert_kind_of Bird, same_parrot assert same_parrot.persisted? assert_equal parrot, same_parrot end def test_first_or_create_with_no_parameters - parrot = Bird.where(:color => 'green').first_or_create + parrot = Bird.where(color: "green").first_or_create assert_kind_of Bird, parrot assert !parrot.persisted? - assert_equal 'green', parrot.color + assert_equal "green", parrot.color end def test_first_or_create_with_block - parrot = Bird.where(:color => 'green').first_or_create { |bird| bird.name = 'parrot' } + parrot = Bird.where(color: "green").first_or_create { |bird| bird.name = "parrot" } assert_kind_of Bird, parrot assert parrot.persisted? - assert_equal 'green', parrot.color - assert_equal 'parrot', parrot.name + assert_equal "green", parrot.color + assert_equal "parrot", parrot.name - same_parrot = Bird.where(:color => 'green').first_or_create { |bird| bird.name = 'parakeet' } + same_parrot = Bird.where(color: "green").first_or_create { |bird| bird.name = "parakeet" } assert_equal parrot, same_parrot end def test_first_or_create_with_array - several_green_birds = Bird.where(:color => 'green').first_or_create([{:name => 'parrot'}, {:name => 'parakeet'}]) + several_green_birds = Bird.where(color: "green").first_or_create([{ name: "parrot" }, { name: "parakeet" }]) assert_kind_of Array, several_green_birds several_green_birds.each { |bird| assert bird.persisted? } - same_parrot = Bird.where(:color => 'green').first_or_create([{:name => 'hummingbird'}, {:name => 'macaw'}]) + same_parrot = Bird.where(color: "green").first_or_create([{ name: "hummingbird" }, { name: "macaw" }]) assert_kind_of Bird, same_parrot assert_equal several_green_birds.first, same_parrot end def test_first_or_create_bang_with_valid_options - parrot = Bird.where(:color => 'green').first_or_create!(:name => 'parrot') + parrot = Bird.where(color: "green").first_or_create!(name: "parrot") assert_kind_of Bird, parrot assert parrot.persisted? - assert_equal 'parrot', parrot.name - assert_equal 'green', parrot.color + assert_equal "parrot", parrot.name + assert_equal "green", parrot.color - same_parrot = Bird.where(:color => 'green').first_or_create!(:name => 'parakeet') + same_parrot = Bird.where(color: "green").first_or_create!(name: "parakeet") assert_kind_of Bird, same_parrot assert same_parrot.persisted? assert_equal parrot, same_parrot end def test_first_or_create_bang_with_invalid_options - assert_raises(ActiveRecord::RecordInvalid) { Bird.where(:color => 'green').first_or_create!(:pirate_id => 1) } + assert_raises(ActiveRecord::RecordInvalid) { Bird.where(color: "green").first_or_create!(pirate_id: 1) } end def test_first_or_create_bang_with_no_parameters - assert_raises(ActiveRecord::RecordInvalid) { Bird.where(:color => 'green').first_or_create! } + assert_raises(ActiveRecord::RecordInvalid) { Bird.where(color: "green").first_or_create! } end def test_first_or_create_bang_with_valid_block - parrot = Bird.where(:color => 'green').first_or_create! { |bird| bird.name = 'parrot' } + parrot = Bird.where(color: "green").first_or_create! { |bird| bird.name = "parrot" } assert_kind_of Bird, parrot assert parrot.persisted? - assert_equal 'green', parrot.color - assert_equal 'parrot', parrot.name + assert_equal "green", parrot.color + assert_equal "parrot", parrot.name - same_parrot = Bird.where(:color => 'green').first_or_create! { |bird| bird.name = 'parakeet' } + same_parrot = Bird.where(color: "green").first_or_create! { |bird| bird.name = "parakeet" } assert_equal parrot, same_parrot end def test_first_or_create_bang_with_invalid_block assert_raise(ActiveRecord::RecordInvalid) do - Bird.where(:color => 'green').first_or_create! { |bird| bird.pirate_id = 1 } + Bird.where(color: "green").first_or_create! { |bird| bird.pirate_id = 1 } end end def test_first_or_create_with_valid_array - several_green_birds = Bird.where(:color => 'green').first_or_create!([{:name => 'parrot'}, {:name => 'parakeet'}]) + several_green_birds = Bird.where(color: "green").first_or_create!([{ name: "parrot" }, { name: "parakeet" }]) assert_kind_of Array, several_green_birds several_green_birds.each { |bird| assert bird.persisted? } - same_parrot = Bird.where(:color => 'green').first_or_create!([{:name => 'hummingbird'}, {:name => 'macaw'}]) + same_parrot = Bird.where(color: "green").first_or_create!([{ name: "hummingbird" }, { name: "macaw" }]) assert_kind_of Bird, same_parrot assert_equal several_green_birds.first, same_parrot end def test_first_or_create_with_invalid_array - assert_raises(ActiveRecord::RecordInvalid) { Bird.where(:color => 'green').first_or_create!([ {:name => 'parrot'}, {:pirate_id => 1} ]) } + assert_raises(ActiveRecord::RecordInvalid) { Bird.where(color: "green").first_or_create!([ { name: "parrot" }, { pirate_id: 1 } ]) } end def test_first_or_initialize - parrot = Bird.where(:color => 'green').first_or_initialize(:name => 'parrot') + parrot = Bird.where(color: "green").first_or_initialize(name: "parrot") assert_kind_of Bird, parrot assert !parrot.persisted? assert parrot.valid? assert parrot.new_record? - assert_equal 'parrot', parrot.name - assert_equal 'green', parrot.color + assert_equal "parrot", parrot.name + assert_equal "green", parrot.color end def test_first_or_initialize_with_no_parameters - parrot = Bird.where(:color => 'green').first_or_initialize + parrot = Bird.where(color: "green").first_or_initialize assert_kind_of Bird, parrot assert !parrot.persisted? assert !parrot.valid? assert parrot.new_record? - assert_equal 'green', parrot.color + assert_equal "green", parrot.color end def test_first_or_initialize_with_block - parrot = Bird.where(:color => 'green').first_or_initialize { |bird| bird.name = 'parrot' } + parrot = Bird.where(color: "green").first_or_initialize { |bird| bird.name = "parrot" } assert_kind_of Bird, parrot assert !parrot.persisted? assert parrot.valid? assert parrot.new_record? - assert_equal 'green', parrot.color - assert_equal 'parrot', parrot.name + assert_equal "green", parrot.color + assert_equal "parrot", parrot.name end def test_find_or_create_by - assert_nil Bird.find_by(name: 'bob') + assert_nil Bird.find_by(name: "bob") - bird = Bird.find_or_create_by(name: 'bob') + bird = Bird.find_or_create_by(name: "bob") assert bird.persisted? - assert_equal bird, Bird.find_or_create_by(name: 'bob') + assert_equal bird, Bird.find_or_create_by(name: "bob") end def test_find_or_create_by_with_create_with - assert_nil Bird.find_by(name: 'bob') + assert_nil Bird.find_by(name: "bob") - bird = Bird.create_with(color: 'green').find_or_create_by(name: 'bob') + bird = Bird.create_with(color: "green").find_or_create_by(name: "bob") assert bird.persisted? - assert_equal 'green', bird.color + assert_equal "green", bird.color - assert_equal bird, Bird.create_with(color: 'blue').find_or_create_by(name: 'bob') + assert_equal bird, Bird.create_with(color: "blue").find_or_create_by(name: "bob") end def test_find_or_create_by! - assert_raises(ActiveRecord::RecordInvalid) { Bird.find_or_create_by!(color: 'green') } + assert_raises(ActiveRecord::RecordInvalid) { Bird.find_or_create_by!(color: "green") } end def test_find_or_initialize_by - assert_nil Bird.find_by(name: 'bob') + assert_nil Bird.find_by(name: "bob") - bird = Bird.find_or_initialize_by(name: 'bob') + bird = Bird.find_or_initialize_by(name: "bob") assert bird.new_record? bird.save! - assert_equal bird, Bird.find_or_initialize_by(name: 'bob') + assert_equal bird, Bird.find_or_initialize_by(name: "bob") end def test_explicit_create_scope - hens = Bird.where(:name => 'hen') - assert_equal 'hen', hens.new.name + hens = Bird.where(name: "hen") + assert_equal "hen", hens.new.name - hens = hens.create_with(:name => 'cock') - assert_equal 'cock', hens.new.name + hens = hens.create_with(name: "cock") + assert_equal "cock", hens.new.name end def test_except - relation = Post.where(:author_id => 1).order('id ASC').limit(1) + relation = Post.where(author_id: 1).order("id ASC").limit(1) assert_equal [posts(:welcome)], relation.to_a author_posts = relation.except(:order, :limit) - assert_equal Post.where(:author_id => 1).to_a, author_posts.to_a + assert_equal Post.where(author_id: 1).to_a, author_posts.to_a all_posts = relation.except(:where, :order, :limit) assert_equal Post.all, all_posts end def test_only - relation = Post.where(:author_id => 1).order('id ASC').limit(1) + relation = Post.where(author_id: 1).order("id ASC").limit(1) assert_equal [posts(:welcome)], relation.to_a author_posts = relation.only(:where) - assert_equal Post.where(:author_id => 1).to_a, author_posts.to_a + assert_equal Post.where(author_id: 1).to_a, author_posts.to_a all_posts = relation.only(:limit) - assert_equal Post.limit(1).to_a.first, all_posts.first + assert_equal Post.limit(1).to_a, all_posts.to_a end def test_anonymous_extension - relation = Post.where(:author_id => 1).order('id ASC').extending do + relation = Post.where(author_id: 1).order("id ASC").extending do def author - 'lifo' + "lifo" end end @@ -1508,7 +1537,7 @@ class RelationTest < ActiveRecord::TestCase end def test_named_extension - relation = Post.where(:author_id => 1).order('id ASC').extending(Post::NamedExtension) + relation = Post.where(author_id: 1).order("id ASC").extending(Post::NamedExtension) assert_equal "lifo", relation.author assert_equal "lifo", relation.limit(1).author end @@ -1518,29 +1547,29 @@ class RelationTest < ActiveRecord::TestCase end def test_default_scope_order_with_scope_order - assert_equal 'zyke', CoolCar.order_using_new_style.limit(1).first.name - assert_equal 'zyke', FastCar.order_using_new_style.limit(1).first.name + assert_equal "zyke", CoolCar.order_using_new_style.limit(1).first.name + assert_equal "zyke", FastCar.order_using_new_style.limit(1).first.name end def test_order_using_scoping - car1 = CoolCar.order('id DESC').scoping do - CoolCar.all.merge!(order: 'id asc').first + car1 = CoolCar.order("id DESC").scoping do + CoolCar.all.merge!(order: "id asc").first end - assert_equal 'zyke', car1.name + assert_equal "zyke", car1.name - car2 = FastCar.order('id DESC').scoping do - FastCar.all.merge!(order: 'id asc').first + car2 = FastCar.order("id DESC").scoping do + FastCar.all.merge!(order: "id asc").first end - assert_equal 'zyke', car2.name + assert_equal "zyke", car2.name end def test_unscoped_block_style - assert_equal 'honda', CoolCar.unscoped { CoolCar.order_using_new_style.limit(1).first.name} - assert_equal 'honda', FastCar.unscoped { FastCar.order_using_new_style.limit(1).first.name} + assert_equal "honda", CoolCar.unscoped { CoolCar.order_using_new_style.limit(1).first.name } + assert_equal "honda", FastCar.unscoped { FastCar.order_using_new_style.limit(1).first.name } end def test_intersection_with_array - relation = Author.where(:name => "David") + relation = Author.where(name: "David") rails_author = relation.first assert_equal [rails_author], [rails_author] & relation @@ -1552,7 +1581,7 @@ class RelationTest < ActiveRecord::TestCase end def test_ordering_with_extra_spaces - assert_equal authors(:david), Author.order('id DESC , name DESC').last + assert_equal authors(:david), Author.order("id DESC , name DESC").last end def test_update_all_with_blank_argument @@ -1560,87 +1589,87 @@ class RelationTest < ActiveRecord::TestCase end def test_update_all_with_joins - comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id) + comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id) count = comments.count - assert_equal count, comments.update_all(:post_id => posts(:thinking).id) + assert_equal count, comments.update_all(post_id: posts(:thinking).id) assert_equal posts(:thinking), comments(:greetings).post end def test_update_all_with_joins_and_limit - comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id).limit(1) - assert_equal 1, comments.update_all(:post_id => posts(:thinking).id) + comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).limit(1) + assert_equal 1, comments.update_all(post_id: posts(:thinking).id) end def test_update_all_with_joins_and_limit_and_order - comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id).order('comments.id').limit(1) - assert_equal 1, comments.update_all(:post_id => posts(:thinking).id) + comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).order("comments.id").limit(1) + assert_equal 1, comments.update_all(post_id: posts(:thinking).id) assert_equal posts(:thinking), comments(:greetings).post assert_equal posts(:welcome), comments(:more_greetings).post end def test_update_all_with_joins_and_offset - all_comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id) + all_comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id) count = all_comments.count comments = all_comments.offset(1) - assert_equal count - 1, comments.update_all(:post_id => posts(:thinking).id) + assert_equal count - 1, comments.update_all(post_id: posts(:thinking).id) end def test_update_all_with_joins_and_offset_and_order - all_comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id).order('posts.id', 'comments.id') + all_comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).order("posts.id", "comments.id") count = all_comments.count comments = all_comments.offset(1) - assert_equal count - 1, comments.update_all(:post_id => posts(:thinking).id) + assert_equal count - 1, comments.update_all(post_id: posts(:thinking).id) assert_equal posts(:thinking), comments(:more_greetings).post assert_equal posts(:welcome), comments(:greetings).post end def test_update_on_relation - topic1 = TopicWithCallbacks.create! title: 'arel', author_name: nil - topic2 = TopicWithCallbacks.create! title: 'activerecord', author_name: nil + topic1 = TopicWithCallbacks.create! title: "arel", author_name: nil + topic2 = TopicWithCallbacks.create! title: "activerecord", author_name: nil topics = TopicWithCallbacks.where(id: [topic1.id, topic2.id]) - topics.update(title: 'adequaterecord') + topics.update(title: "adequaterecord") - assert_equal 'adequaterecord', topic1.reload.title - assert_equal 'adequaterecord', topic2.reload.title + assert_equal "adequaterecord", topic1.reload.title + assert_equal "adequaterecord", topic2.reload.title # Testing that the before_update callbacks have run - assert_equal 'David', topic1.reload.author_name - assert_equal 'David', topic2.reload.author_name + assert_equal "David", topic1.reload.author_name + assert_equal "David", topic2.reload.author_name end def test_update_on_relation_passing_active_record_object_is_deprecated - topic = Topic.create!(title: 'Foo', author_name: nil) + topic = Topic.create!(title: "Foo", author_name: nil) assert_deprecated(/update/) do - Topic.where(id: topic.id).update(topic, title: 'Bar') + Topic.where(id: topic.id).update(topic, title: "Bar") end end def test_distinct - tag1 = Tag.create(:name => 'Foo') - tag2 = Tag.create(:name => 'Foo') + tag1 = Tag.create(name: "Foo") + tag2 = Tag.create(name: "Foo") - query = Tag.select(:name).where(:id => [tag1.id, tag2.id]) + query = Tag.select(:name).where(id: [tag1.id, tag2.id]) - assert_equal ['Foo', 'Foo'], query.map(&:name) + 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) } + 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) } + 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_equal ["Foo", "Foo"], query.distinct(true).distinct(false).map(&:name) assert_deprecated do - assert_equal ['Foo', 'Foo'], query.uniq(true).uniq(false).map(&:name) + assert_equal ["Foo", "Foo"], query.uniq(true).uniq(false).map(&:name) end end def test_doesnt_add_having_values_if_options_are_blank - scope = Post.having('') + scope = Post.having("") assert scope.having_clause.empty? scope = Post.having([]) @@ -1680,62 +1709,62 @@ class RelationTest < ActiveRecord::TestCase end def test_automatically_added_where_references - scope = Post.where(:comments => { :body => "Bla" }) - assert_equal ['comments'], scope.references_values + scope = Post.where(comments: { body: "Bla" }) + assert_equal ["comments"], scope.references_values - scope = Post.where('comments.body' => 'Bla') - assert_equal ['comments'], scope.references_values + scope = Post.where("comments.body" => "Bla") + assert_equal ["comments"], scope.references_values end def test_automatically_added_where_not_references scope = Post.where.not(comments: { body: "Bla" }) - assert_equal ['comments'], scope.references_values + assert_equal ["comments"], scope.references_values - scope = Post.where.not('comments.body' => 'Bla') - assert_equal ['comments'], scope.references_values + scope = Post.where.not("comments.body" => "Bla") + assert_equal ["comments"], scope.references_values end def test_automatically_added_having_references - scope = Post.having(:comments => { :body => "Bla" }) - assert_equal ['comments'], scope.references_values + scope = Post.having(comments: { body: "Bla" }) + assert_equal ["comments"], scope.references_values - scope = Post.having('comments.body' => 'Bla') - assert_equal ['comments'], scope.references_values + scope = Post.having("comments.body" => "Bla") + assert_equal ["comments"], scope.references_values end def test_automatically_added_order_references - scope = Post.order('comments.body') - assert_equal ['comments'], scope.references_values + scope = Post.order("comments.body") + assert_equal ["comments"], scope.references_values - scope = Post.order('comments.body', 'yaks.body') - assert_equal ['comments', 'yaks'], scope.references_values + scope = Post.order("comments.body", "yaks.body") + assert_equal ["comments", "yaks"], scope.references_values # Don't infer yaks, let's not go down that road again... - scope = Post.order('comments.body, yaks.body') - assert_equal ['comments'], scope.references_values + scope = Post.order("comments.body, yaks.body") + assert_equal ["comments"], scope.references_values - scope = Post.order('comments.body asc') - assert_equal ['comments'], scope.references_values + scope = Post.order("comments.body asc") + assert_equal ["comments"], scope.references_values - scope = Post.order('foo(comments.body)') + scope = Post.order("foo(comments.body)") assert_equal [], scope.references_values end def test_automatically_added_reorder_references - scope = Post.reorder('comments.body') + scope = Post.reorder("comments.body") assert_equal %w(comments), scope.references_values - scope = Post.reorder('comments.body', 'yaks.body') + scope = Post.reorder("comments.body", "yaks.body") assert_equal %w(comments yaks), scope.references_values # Don't infer yaks, let's not go down that road again... - scope = Post.reorder('comments.body, yaks.body') + scope = Post.reorder("comments.body, yaks.body") assert_equal %w(comments), scope.references_values - scope = Post.reorder('comments.body asc') + scope = Post.reorder("comments.body asc") assert_equal %w(comments), scope.references_values - scope = Post.reorder('foo(comments.body)') + scope = Post.reorder("foo(comments.body)") assert_equal [], scope.references_values end @@ -1782,7 +1811,7 @@ class RelationTest < ActiveRecord::TestCase end test "find_by with multi-arg conditions returns the first matching record" do - assert_equal posts(:eager_other), Post.order(:id).find_by('author_id = ?', 2) + assert_equal posts(:eager_other), Post.order(:id).find_by("author_id = ?", 2) end test "find_by returns nil if the record is missing" do @@ -1806,7 +1835,7 @@ class RelationTest < ActiveRecord::TestCase end test "find_by! with multi-arg conditions returns the first matching record" do - assert_equal posts(:eager_other), Post.order(:id).find_by!('author_id = ?', 2) + assert_equal posts(:eager_other), Post.order(:id).find_by!("author_id = ?", 2) end test "find_by! doesn't have implicit ordering" do @@ -1828,7 +1857,7 @@ class RelationTest < ActiveRecord::TestCase relation.to_a assert_raises(ActiveRecord::ImmutableRelation) do - relation.where! 'foo' + relation.where! "foo" end end @@ -1846,7 +1875,7 @@ class RelationTest < ActiveRecord::TestCase relation.to_a assert_raises(ActiveRecord::ImmutableRelation) do - relation.merge! where: 'foo' + relation.merge! where: "foo" end end @@ -1888,19 +1917,19 @@ class RelationTest < ActiveRecord::TestCase end end - test 'using a custom table affects the wheres' do - table_alias = Post.arel_table.alias('omg_posts') + test "using a custom table affects the wheres" do + table_alias = Post.arel_table.alias("omg_posts") table_metadata = ActiveRecord::TableMetadata.new(Post, table_alias) predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata) relation = ActiveRecord::Relation.new(Post, table_alias, predicate_builder) - relation.where!(:foo => "bar") + relation.where!(foo: "bar") node = relation.arel.constraints.first.grep(Arel::Attributes::Attribute).first assert_equal table_alias, node.relation end - test '#load' do + test "#load" do relation = Post.all assert_queries(1) do assert_equal relation, relation.load @@ -1908,9 +1937,9 @@ class RelationTest < ActiveRecord::TestCase assert_no_queries { relation.to_a } end - test 'group with select and includes' do - authors_count = Post.select('author_id, COUNT(author_id) AS num_posts'). - group('author_id').order('author_id').includes(:author).to_a + test "group with select and includes" do + authors_count = Post.select("author_id, COUNT(author_id) AS num_posts"). + group("author_id").order("author_id").includes(:author).to_a assert_no_queries do result = authors_count.map do |post| @@ -1936,7 +1965,7 @@ class RelationTest < ActiveRecord::TestCase def test_unscope_removes_binds left = Post.where(id: Arel::Nodes::BindParam.new) - column = Post.columns_hash['id'] + column = Post.columns_hash["id"] left.bind_values += [[column, 20]] relation = left.unscope(where: :id) @@ -1971,6 +2000,24 @@ class RelationTest < ActiveRecord::TestCase end def test_relation_join_method - assert_equal 'Thank you for the welcome,Thank you again for the welcome', Post.first.comments.join(",") + assert_equal "Thank you for the welcome,Thank you again for the welcome", Post.first.comments.join(",") + end + + def test_connection_adapters_can_reorder_binds + posts = Post.limit(1).offset(2) + + stubbed_connection = Post.connection.dup + def stubbed_connection.combine_bind_parameters(**kwargs) + offset = kwargs[:offset] + kwargs[:offset] = kwargs[:limit] + kwargs[:limit] = offset + super(**kwargs) + end + + posts.define_singleton_method(:connection) do + stubbed_connection + end + + assert_equal 2, posts.to_a.length end end diff --git a/activerecord/test/cases/reload_models_test.rb b/activerecord/test/cases/reload_models_test.rb index 431fbf1297..5dc9d6d8b7 100644 --- a/activerecord/test/cases/reload_models_test.rb +++ b/activerecord/test/cases/reload_models_test.rb @@ -1,13 +1,13 @@ require "cases/helper" -require 'models/owner' -require 'models/pet' +require "models/owner" +require "models/pet" class ReloadModelsTest < ActiveRecord::TestCase fixtures :pets, :owners def test_has_one_with_reload - pet = Pet.find_by_name('parrot') - pet.owner = Owner.find_by_name('ashley') + pet = Pet.find_by_name("parrot") + pet.owner = Owner.find_by_name("ashley") # Reload the class Owner, simulating auto-reloading of model classes in a # development environment. Note that meanwhile the class Pet is not @@ -15,8 +15,8 @@ class ReloadModelsTest < ActiveRecord::TestCase Object.class_eval { remove_const :Owner } Kernel.load(File.expand_path(File.join(File.dirname(__FILE__), "../models/owner.rb"))) - pet = Pet.find_by_name('parrot') - pet.owner = Owner.find_by_name('ashley') - assert_equal pet.owner, Owner.find_by_name('ashley') + pet = Pet.find_by_name("parrot") + pet.owner = Owner.find_by_name("ashley") + assert_equal pet.owner, Owner.find_by_name("ashley") end end diff --git a/activerecord/test/cases/result_test.rb b/activerecord/test/cases/result_test.rb index dec01dfa76..949086fda0 100644 --- a/activerecord/test/cases/result_test.rb +++ b/activerecord/test/cases/result_test.rb @@ -3,10 +3,10 @@ require "cases/helper" module ActiveRecord class ResultTest < ActiveRecord::TestCase def result - Result.new(['col_1', 'col_2'], [ - ['row 1 col 1', 'row 1 col 2'], - ['row 2 col 1', 'row 2 col 2'], - ['row 3 col 1', 'row 3 col 2'], + Result.new(["col_1", "col_2"], [ + ["row 1 col 1", "row 1 col 2"], + ["row 2 col 1", "row 2 col 2"], + ["row 3 col 1", "row 3 col 2"], ]) end @@ -16,21 +16,31 @@ module ActiveRecord test "to_hash returns row_hashes" do assert_equal [ - {'col_1' => 'row 1 col 1', 'col_2' => 'row 1 col 2'}, - {'col_1' => 'row 2 col 1', 'col_2' => 'row 2 col 2'}, - {'col_1' => 'row 3 col 1', 'col_2' => 'row 3 col 2'}, + { "col_1" => "row 1 col 1", "col_2" => "row 1 col 2" }, + { "col_1" => "row 2 col 1", "col_2" => "row 2 col 2" }, + { "col_1" => "row 3 col 1", "col_2" => "row 3 col 2" }, ], result.to_hash end + test "first returns first row as a hash" do + assert_equal( + { "col_1" => "row 1 col 1", "col_2" => "row 1 col 2" }, result.first) + end + + test "last returns last row as a hash" do + assert_equal( + { "col_1" => "row 3 col 1", "col_2" => "row 3 col 2" }, result.last) + end + test "each with block returns row hashes" do result.each do |row| - assert_equal ['col_1', 'col_2'], row.keys + assert_equal ["col_1", "col_2"], row.keys end end test "each without block returns an enumerator" do result.each.with_index do |row, index| - assert_equal ['col_1', 'col_2'], row.keys + assert_equal ["col_1", "col_2"], row.keys assert_kind_of Integer, index end end diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb index 239f63d27b..464bb12ccb 100644 --- a/activerecord/test/cases/sanitize_test.rb +++ b/activerecord/test/cases/sanitize_test.rb @@ -1,7 +1,7 @@ require "cases/helper" -require 'models/binary' -require 'models/author' -require 'models/post' +require "models/binary" +require "models/author" +require "models/post" class SanitizeTest < ActiveRecord::TestCase def setup @@ -36,42 +36,42 @@ class SanitizeTest < ActiveRecord::TestCase end def test_sanitize_sql_array_handles_relations - david = Author.create!(name: 'David') + david = Author.create!(name: "David") david_posts = david.posts.select(:id) sub_query_pattern = /\(\bselect\b.*?\bwhere\b.*?\)/i - select_author_sql = Post.send(:sanitize_sql_array, ['id in (?)', david_posts]) - assert_match(sub_query_pattern, select_author_sql, 'should sanitize `Relation` as subquery for bind variables') + select_author_sql = Post.send(:sanitize_sql_array, ["id in (?)", david_posts]) + assert_match(sub_query_pattern, select_author_sql, "should sanitize `Relation` as subquery for bind variables") - select_author_sql = Post.send(:sanitize_sql_array, ['id in (:post_ids)', post_ids: david_posts]) - assert_match(sub_query_pattern, select_author_sql, 'should sanitize `Relation` as subquery for named bind variables') + select_author_sql = Post.send(:sanitize_sql_array, ["id in (:post_ids)", post_ids: david_posts]) + assert_match(sub_query_pattern, select_author_sql, "should sanitize `Relation` as subquery for named bind variables") end def test_sanitize_sql_array_handles_empty_statement - select_author_sql = Post.send(:sanitize_sql_array, ['']) - assert_equal('', select_author_sql) + select_author_sql = Post.send(:sanitize_sql_array, [""]) + assert_equal("", select_author_sql) end def test_sanitize_sql_like - assert_equal '100\%', Binary.send(:sanitize_sql_like, '100%') - assert_equal 'snake\_cased\_string', Binary.send(:sanitize_sql_like, 'snake_cased_string') + assert_equal '100\%', Binary.send(:sanitize_sql_like, "100%") + assert_equal 'snake\_cased\_string', Binary.send(:sanitize_sql_like, "snake_cased_string") assert_equal 'C:\\\\Programs\\\\MsPaint', Binary.send(:sanitize_sql_like, 'C:\\Programs\\MsPaint') - assert_equal 'normal string 42', Binary.send(:sanitize_sql_like, 'normal string 42') + assert_equal "normal string 42", Binary.send(:sanitize_sql_like, "normal string 42") end def test_sanitize_sql_like_with_custom_escape_character - assert_equal '100!%', Binary.send(:sanitize_sql_like, '100%', '!') - assert_equal 'snake!_cased!_string', Binary.send(:sanitize_sql_like, 'snake_cased_string', '!') - assert_equal 'great!!', Binary.send(:sanitize_sql_like, 'great!', '!') - assert_equal 'C:\\Programs\\MsPaint', Binary.send(:sanitize_sql_like, 'C:\\Programs\\MsPaint', '!') - assert_equal 'normal string 42', Binary.send(:sanitize_sql_like, 'normal string 42', '!') + assert_equal "100!%", Binary.send(:sanitize_sql_like, "100%", "!") + assert_equal "snake!_cased!_string", Binary.send(:sanitize_sql_like, "snake_cased_string", "!") + assert_equal "great!!", Binary.send(:sanitize_sql_like, "great!", "!") + assert_equal 'C:\\Programs\\MsPaint', Binary.send(:sanitize_sql_like, 'C:\\Programs\\MsPaint', "!") + assert_equal "normal string 42", Binary.send(:sanitize_sql_like, "normal string 42", "!") end def test_sanitize_sql_like_example_use_case searchable_post = Class.new(Post) do def self.search(term) - where("title LIKE ?", sanitize_sql_like(term, '!')) + where("title LIKE ?", sanitize_sql_like(term, "!")) end end @@ -81,25 +81,25 @@ class SanitizeTest < ActiveRecord::TestCase end def test_bind_arity - assert_nothing_raised { bind '' } - assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '', 1 } + assert_nothing_raised { bind "" } + assert_raise(ActiveRecord::PreparedStatementInvalid) { bind "", 1 } - assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?' } - assert_nothing_raised { bind '?', 1 } - assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1 } + assert_raise(ActiveRecord::PreparedStatementInvalid) { bind "?" } + assert_nothing_raised { bind "?", 1 } + assert_raise(ActiveRecord::PreparedStatementInvalid) { bind "?", 1, 1 } end def test_named_bind_variables - assert_equal '1', bind(':a', :a => 1) # ' ruby-mode - assert_equal '1 1', bind(':a :a', :a => 1) # ' ruby-mode + assert_equal "1", bind(":a", a: 1) # ' ruby-mode + assert_equal "1 1", bind(":a :a", a: 1) # ' ruby-mode - assert_nothing_raised { bind("'+00:00'", :foo => "bar") } + assert_nothing_raised { bind("'+00:00'", foo: "bar") } end def test_named_bind_arity - assert_nothing_raised { bind "name = :name", { name: "37signals" } } - assert_nothing_raised { bind "name = :name", { name: "37signals", id: 1 } } - assert_raise(ActiveRecord::PreparedStatementInvalid) { bind "name = :name", { id: 1 } } + assert_nothing_raised { bind "name = :name", name: "37signals" } + assert_nothing_raised { bind "name = :name", name: "37signals", id: 1 } + assert_raise(ActiveRecord::PreparedStatementInvalid) { bind "name = :name", id: 1 } end class SimpleEnumerable @@ -117,50 +117,50 @@ class SanitizeTest < ActiveRecord::TestCase def test_bind_enumerable quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')}) - assert_equal '1,2,3', bind('?', [1, 2, 3]) - assert_equal quoted_abc, bind('?', %w(a b c)) + assert_equal "1,2,3", bind("?", [1, 2, 3]) + assert_equal quoted_abc, bind("?", %w(a b c)) - assert_equal '1,2,3', bind(':a', :a => [1, 2, 3]) - assert_equal quoted_abc, bind(':a', :a => %w(a b c)) # ' + assert_equal "1,2,3", bind(":a", a: [1, 2, 3]) + assert_equal quoted_abc, bind(":a", a: %w(a b c)) # ' - assert_equal '1,2,3', bind('?', SimpleEnumerable.new([1, 2, 3])) - assert_equal quoted_abc, bind('?', SimpleEnumerable.new(%w(a b c))) + assert_equal "1,2,3", bind("?", SimpleEnumerable.new([1, 2, 3])) + assert_equal quoted_abc, bind("?", SimpleEnumerable.new(%w(a b c))) - assert_equal '1,2,3', bind(':a', :a => SimpleEnumerable.new([1, 2, 3])) - assert_equal quoted_abc, bind(':a', :a => SimpleEnumerable.new(%w(a b c))) # ' + assert_equal "1,2,3", bind(":a", a: SimpleEnumerable.new([1, 2, 3])) + assert_equal quoted_abc, bind(":a", a: SimpleEnumerable.new(%w(a b c))) # ' end def test_bind_empty_enumerable quoted_nil = ActiveRecord::Base.connection.quote(nil) - assert_equal quoted_nil, bind('?', []) - assert_equal " in (#{quoted_nil})", bind(' in (?)', []) - assert_equal "foo in (#{quoted_nil})", bind('foo in (?)', []) + assert_equal quoted_nil, bind("?", []) + assert_equal " in (#{quoted_nil})", bind(" in (?)", []) + assert_equal "foo in (#{quoted_nil})", bind("foo in (?)", []) end def test_bind_empty_string - quoted_empty = ActiveRecord::Base.connection.quote('') - assert_equal quoted_empty, bind('?', '') + quoted_empty = ActiveRecord::Base.connection.quote("") + assert_equal quoted_empty, bind("?", "") end def test_bind_chars quoted_bambi = ActiveRecord::Base.connection.quote("Bambi") quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote("Bambi\nand\nThumper") - assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi") - assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper") - assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi".mb_chars) - assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper".mb_chars) + assert_equal "name=#{quoted_bambi}", bind("name=?", "Bambi") + assert_equal "name=#{quoted_bambi_and_thumper}", bind("name=?", "Bambi\nand\nThumper") + assert_equal "name=#{quoted_bambi}", bind("name=?", "Bambi".mb_chars) + assert_equal "name=#{quoted_bambi_and_thumper}", bind("name=?", "Bambi\nand\nThumper".mb_chars) end def test_bind_record o = Struct.new(:quoted_id).new(1) - assert_equal '1', bind('?', o) + assert_equal "1", bind("?", o) os = [o] * 3 - assert_equal '1,1,1', bind('?', os) + assert_equal "1,1,1", bind("?", os) end def test_named_bind_with_postgresql_type_casts - l = Proc.new { bind(":a::integer '2009-01-01'::date", :a => '10') } + l = Proc.new { bind(":a::integer '2009-01-01'::date", a: "10") } assert_nothing_raised(&l) assert_equal "#{ActiveRecord::Base.connection.quote('10')}::integer '2009-01-01'::date", l.call end diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 8def74e75b..ae3a5651a1 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'support/schema_dumping_helper' +require "support/schema_dumping_helper" class SchemaDumperTest < ActiveRecord::TestCase include SchemaDumpingHelper @@ -20,7 +20,7 @@ class SchemaDumperTest < ActiveRecord::TestCase def test_dump_schema_information_outputs_lexically_ordered_versions versions = %w{ 20100101010101 20100201010101 20100301010101 } versions.reverse_each do |v| - ActiveRecord::SchemaMigration.create!(:version => v) + ActiveRecord::SchemaMigration.create!(version: v) end schema_info = ActiveRecord::Base.connection.dump_schema_information @@ -29,14 +29,29 @@ class SchemaDumperTest < ActiveRecord::TestCase ActiveRecord::SchemaMigration.delete_all end - def test_magic_comment - assert_match "# encoding: #{Encoding.default_external.name}", standard_dump + if current_adapter?(:SQLite3Adapter) + %w{3.7.8 3.7.11 3.7.12}.each do |version_string| + test "dumps schema version for sqlite version #{version_string}" do + version = ActiveRecord::ConnectionAdapters::SQLite3Adapter::Version.new(version_string) + ActiveRecord::Base.connection.stubs(:sqlite_version).returns(version) + + versions = %w{ 20100101010101 20100201010101 20100301010101 } + versions.reverse_each do |v| + ActiveRecord::SchemaMigration.create!(version: v) + end + + schema_info = ActiveRecord::Base.connection.dump_schema_information + assert_match(/20100201010101.*20100301010101/m, schema_info) + ActiveRecord::SchemaMigration.delete_all + end + end end def test_schema_dump output = standard_dump assert_match %r{create_table "accounts"}, output assert_match %r{create_table "authors"}, output + assert_no_match %r{(?<=, ) do \|t\|}, output assert_no_match %r{create_table "schema_migrations"}, output assert_no_match %r{create_table "ar_internal_metadata"}, output end @@ -56,38 +71,35 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{create_table "CamelCase"}, output end - def assert_line_up(lines, pattern, required = false) + def assert_no_line_up(lines, pattern) return assert(true) if lines.empty? matches = lines.map { |line| line.match(pattern) } - assert matches.all? if required matches.compact! return assert(true) if matches.empty? - assert_equal 1, matches.map{ |match| match.offset(0).first }.uniq.length + line_matches = lines.map { |line| [line, line.match(pattern)] }.select { |line, match| match } + assert line_matches.all? { |line, match| + start = match.offset(0).first + line[start - 2..start - 1] == ", " + } end def column_definition_lines(output = standard_dump) - output.scan(/^( *)create_table.*?\n(.*?)^\1end/m).map{ |m| m.last.split(/\n/) } + output.scan(/^( *)create_table.*?\n(.*?)^\1end/m).map { |m| m.last.split(/\n/) } end - def test_types_line_up + def test_types_no_line_up column_definition_lines.each do |column_set| next if column_set.empty? - lengths = column_set.map do |column| - if match = column.match(/\bt\.\w+\s+"/) - match[0].length - end - end.compact - - assert_equal 1, lengths.uniq.length + assert column_set.all? { |column| !column.match(/\bt\.\w+\s{2,}/) } end end - def test_arguments_line_up + def test_arguments_no_line_up column_definition_lines.each do |column_set| - assert_line_up(column_set, /default: /) - assert_line_up(column_set, /limit: /) - assert_line_up(column_set, /null: /) + assert_no_line_up(column_set, /default: /) + assert_no_line_up(column_set, /limit: /) + assert_no_line_up(column_set, /null: /) end end @@ -147,15 +159,15 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{c_int_7.*limit: 7}, output assert_match %r{c_int_8.*limit: 8}, output else - assert_match %r{c_int_5.*limit: 8}, output - assert_match %r{c_int_6.*limit: 8}, output - assert_match %r{c_int_7.*limit: 8}, output - assert_match %r{c_int_8.*limit: 8}, output + assert_match %r{t\.bigint\s+"c_int_5"$}, output + assert_match %r{t\.bigint\s+"c_int_6"$}, output + assert_match %r{t\.bigint\s+"c_int_7"$}, output + assert_match %r{t\.bigint\s+"c_int_8"$}, output end end def test_schema_dump_with_string_ignored_table - output = dump_all_table_schema(['accounts']) + output = dump_all_table_schema(["accounts"]) assert_no_match %r{create_table "accounts"}, output assert_match %r{create_table "authors"}, output assert_no_match %r{create_table "schema_migrations"}, output @@ -171,24 +183,26 @@ class SchemaDumperTest < ActiveRecord::TestCase end def test_schema_dumps_index_columns_in_right_order - index_definition = standard_dump.split(/\n/).grep(/add_index.*companies/).first.strip - if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter) - assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index", using: :btree', index_definition + index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_index/).first.strip + if current_adapter?(:PostgreSQLAdapter) + assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", order: { rating: :desc }, using: :btree', index_definition + elsif current_adapter?(:Mysql2Adapter) + assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", length: { type: 10 }, using: :btree', index_definition else - assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index"', index_definition + assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index"', index_definition end end def test_schema_dumps_partial_indices - index_definition = standard_dump.split(/\n/).grep(/add_index.*company_partial_index/).first.strip + index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_partial_index/).first.strip if current_adapter?(:PostgreSQLAdapter) - assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition + assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition elsif current_adapter?(:Mysql2Adapter) - assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition + assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition elsif current_adapter?(:SQLite3Adapter) && ActiveRecord::Base.connection.supports_partial_index? - assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "rating > 10"', index_definition + assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "rating > 10"', index_definition else - assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index"', index_definition + assert_equal 't.index ["firm_id", "type"], name: "company_partial_index"', index_definition end end @@ -204,12 +218,17 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{t\.boolean\s+"has_fun",.+default: false}, output end - if current_adapter?(:Mysql2Adapter) - def test_schema_dump_should_add_default_value_for_mysql_text_field - output = standard_dump - assert_match %r{t\.text\s+"body",\s+limit: 65535,\s+null: false$}, output - end + def test_schema_dump_does_not_include_limit_for_text_field + output = standard_dump + assert_match %r{t\.text\s+"params"$}, output + end + + def test_schema_dump_does_not_include_limit_for_binary_field + output = standard_dump + assert_match %r{t\.binary\s+"data"$}, output + end + if current_adapter?(:Mysql2Adapter) def test_schema_dump_includes_length_for_mysql_binary_fields output = standard_dump assert_match %r{t\.binary\s+"var_binary",\s+limit: 255$}, output @@ -219,11 +238,11 @@ class SchemaDumperTest < ActiveRecord::TestCase def test_schema_dump_includes_length_for_mysql_blob_and_text_fields output = standard_dump assert_match %r{t\.blob\s+"tiny_blob",\s+limit: 255$}, output - assert_match %r{t\.binary\s+"normal_blob",\s+limit: 65535$}, output + assert_match %r{t\.binary\s+"normal_blob"$}, output assert_match %r{t\.binary\s+"medium_blob",\s+limit: 16777215$}, output assert_match %r{t\.binary\s+"long_blob",\s+limit: 4294967295$}, output assert_match %r{t\.text\s+"tiny_text",\s+limit: 255$}, output - assert_match %r{t\.text\s+"normal_text",\s+limit: 65535$}, output + assert_match %r{t\.text\s+"normal_text"$}, output assert_match %r{t\.text\s+"medium_text",\s+limit: 16777215$}, output assert_match %r{t\.text\s+"long_text",\s+limit: 4294967295$}, output end @@ -235,8 +254,8 @@ class SchemaDumperTest < ActiveRecord::TestCase def test_schema_dumps_index_type output = standard_dump - assert_match %r{add_index "key_tests", \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext}, output - assert_match %r{add_index "key_tests", \["pizza"\], name: "index_key_tests_on_pizza", using: :btree}, output + assert_match %r{t\.index \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext}, output + assert_match %r{t\.index \["pizza"\], name: "index_key_tests_on_pizza", using: :btree}, output end end @@ -248,12 +267,12 @@ class SchemaDumperTest < ActiveRecord::TestCase if current_adapter?(:PostgreSQLAdapter) def test_schema_dump_includes_bigint_default output = standard_dump - assert_match %r{t\.integer\s+"bigint_default",\s+limit: 8,\s+default: 0}, output + 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 - assert_match %r{t\.integer\s+"big_int_data_points\",\s+limit: 8,\s+array: true}, output + assert_match %r{t\.bigint\s+"big_int_data_points\",\s+array: true}, output end def test_schema_dump_allows_array_of_decimal_defaults @@ -261,11 +280,16 @@ class SchemaDumperTest < ActiveRecord::TestCase 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 + assert_equal 't.index "lower((name)::text)", name: "company_expression_index", using: :btree', index_definition + end + if ActiveRecord::Base.connection.supports_extensions? def test_schema_dump_includes_extensions connection = ActiveRecord::Base.connection - connection.stubs(:extensions).returns(['hstore']) + connection.stubs(:extensions).returns(["hstore"]) output = perform_schema_dump assert_match "# These are extensions that must be enabled", output assert_match %r{enable_extension "hstore"}, output @@ -323,9 +347,9 @@ class SchemaDumperTest < ActiveRecord::TestCase create_table("dogs") do |t| t.column :name, :string t.column :owner_id, :integer + t.index [:name] + t.foreign_key :dog_owners, column: "owner_id" if supports_foreign_keys? end - add_index "dogs", [:name] - add_foreign_key :dogs, :dog_owners, column: "owner_id" if supports_foreign_keys? end def down drop_table("dogs") @@ -335,8 +359,8 @@ class SchemaDumperTest < ActiveRecord::TestCase def test_schema_dump_with_table_name_prefix_and_suffix original, $stdout = $stdout, StringIO.new - ActiveRecord::Base.table_name_prefix = 'foo_' - ActiveRecord::Base.table_name_suffix = '_bar' + ActiveRecord::Base.table_name_prefix = "foo_" + ActiveRecord::Base.table_name_suffix = "_bar" migration = CreateDogMigration.new migration.migrate(:up) @@ -354,7 +378,7 @@ class SchemaDumperTest < ActiveRecord::TestCase ensure migration.migrate(:down) - ActiveRecord::Base.table_name_suffix = ActiveRecord::Base.table_name_prefix = '' + ActiveRecord::Base.table_name_suffix = ActiveRecord::Base.table_name_prefix = "" $stdout = original end @@ -372,7 +396,7 @@ class SchemaDumperTest < ActiveRecord::TestCase original_table_name_prefix = ActiveRecord::Base.table_name_prefix original_schema_dumper_ignore_tables = ActiveRecord::SchemaDumper.ignore_tables - ActiveRecord::Base.table_name_prefix = 'omg_' + ActiveRecord::Base.table_name_prefix = "omg_" ActiveRecord::SchemaDumper.ignore_tables = ["cats"] migration = create_cat_migration.new migration.migrate(:up) @@ -398,23 +422,37 @@ class SchemaDumperDefaultsTest < ActiveRecord::TestCase @connection = ActiveRecord::Base.connection @connection.create_table :defaults, force: true do |t| t.string :string_with_default, default: "Hello!" - t.date :date_with_default, default: '2014-06-05' + t.date :date_with_default, default: "2014-06-05" t.datetime :datetime_with_default, default: "2014-06-05 07:17:04" t.time :time_with_default, default: "07:17:04" end + + if current_adapter?(:PostgreSQLAdapter) + @connection.create_table :infinity_defaults, force: true do |t| + t.float :float_with_inf_default, default: Float::INFINITY + t.float :float_with_nan_default, default: Float::NAN + end + end end teardown do return unless @connection - @connection.drop_table 'defaults', if_exists: true + @connection.drop_table "defaults", if_exists: true end def test_schema_dump_defaults_with_universally_supported_types - output = dump_table_schema('defaults') + output = dump_table_schema("defaults") assert_match %r{t\.string\s+"string_with_default",.*?default: "Hello!"}, output assert_match %r{t\.date\s+"date_with_default",\s+default: '2014-06-05'}, output assert_match %r{t\.datetime\s+"datetime_with_default",\s+default: '2014-06-05 07:17:04'}, output assert_match %r{t\.time\s+"time_with_default",\s+default: '2000-01-01 07:17:04'}, output end + + def test_schema_dump_with_float_column_infinity_default + skip unless current_adapter?(:PostgreSQLAdapter) + output = dump_table_schema('infinity_defaults') + assert_match %r{t\.float\s+"float_with_inf_default",\s+default: ::Float::INFINITY}, output + assert_match %r{t\.float\s+"float_with_nan_default",\s+default: ::Float::NAN}, output + end end diff --git a/activerecord/test/cases/schema_loading_test.rb b/activerecord/test/cases/schema_loading_test.rb new file mode 100644 index 0000000000..3d92a5e104 --- /dev/null +++ b/activerecord/test/cases/schema_loading_test.rb @@ -0,0 +1,52 @@ +require "cases/helper" + +module SchemaLoadCounter + extend ActiveSupport::Concern + + module ClassMethods + attr_accessor :load_schema_calls + + def load_schema! + self.load_schema_calls ||= 0 + self.load_schema_calls +=1 + super + end + end +end + +class SchemaLoadingTest < ActiveRecord::TestCase + def test_basic_model_is_loaded_once + klass = define_model + klass.new + assert_equal 1, klass.load_schema_calls + end + + def test_model_with_custom_lock_is_loaded_once + klass = define_model do |c| + c.table_name = :lock_without_defaults_cust + c.locking_column = :custom_lock_version + end + klass.new + assert_equal 1, klass.load_schema_calls + end + + def test_model_with_changed_custom_lock_is_loaded_twice + klass = define_model do |c| + c.table_name = :lock_without_defaults_cust + end + klass.new + klass.locking_column = :custom_lock_version + klass.new + assert_equal 2, klass.load_schema_calls + end + + private + + def define_model + Class.new(ActiveRecord::Base) do + include SchemaLoadCounter + self.table_name = :lock_without_defaults + yield self if block_given? + end + end +end diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb index c918cbdef5..b3dc979720 100644 --- a/activerecord/test/cases/scoping/default_scoping_test.rb +++ b/activerecord/test/cases/scoping/default_scoping_test.rb @@ -1,15 +1,16 @@ -require 'cases/helper' -require 'models/post' -require 'models/comment' -require 'models/developer' -require 'models/computer' -require 'models/vehicle' +require "cases/helper" +require "models/post" +require "models/comment" +require "models/developer" +require "models/computer" +require "models/vehicle" +require "models/cat" class DefaultScopingTest < ActiveRecord::TestCase fixtures :developers, :posts, :comments def test_default_scope - expected = Developer.all.merge!(:order => 'salary DESC').to_a.collect(&:salary) + expected = Developer.all.merge!(order: "salary DESC").to_a.collect(&:salary) received = DeveloperOrderedBySalary.all.collect(&:salary) assert_equal expected, received end @@ -48,20 +49,20 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_default_scope_with_conditions_string - assert_equal Developer.where(name: 'David').map(&:id).sort, DeveloperCalledDavid.all.map(&:id).sort + assert_equal Developer.where(name: "David").map(&:id).sort, DeveloperCalledDavid.all.map(&:id).sort assert_equal nil, DeveloperCalledDavid.create!.name end def test_default_scope_with_conditions_hash - assert_equal Developer.where(name: 'Jamis').map(&:id).sort, DeveloperCalledJamis.all.map(&:id).sort - assert_equal 'Jamis', DeveloperCalledJamis.create!.name + assert_equal Developer.where(name: "Jamis").map(&:id).sort, DeveloperCalledJamis.all.map(&:id).sort + assert_equal "Jamis", DeveloperCalledJamis.create!.name end unless in_memory_db? def test_default_scoping_with_threads 2.times do Thread.new { - assert DeveloperOrderedBySalary.all.to_sql.include?('salary DESC') + assert_includes DeveloperOrderedBySalary.all.to_sql, "salary DESC" DeveloperOrderedBySalary.connection.close }.join end @@ -70,37 +71,37 @@ class DefaultScopingTest < ActiveRecord::TestCase def test_default_scope_with_inheritance wheres = InheritedPoorDeveloperCalledJamis.all.where_values_hash - assert_equal "Jamis", wheres['name'] - assert_equal 50000, wheres['salary'] + assert_equal "Jamis", wheres["name"] + assert_equal 50000, wheres["salary"] end def test_default_scope_with_module_includes wheres = ModuleIncludedPoorDeveloperCalledJamis.all.where_values_hash - assert_equal "Jamis", wheres['name'] - assert_equal 50000, wheres['salary'] + assert_equal "Jamis", wheres["name"] + assert_equal 50000, wheres["salary"] end def test_default_scope_with_multiple_calls wheres = MultiplePoorDeveloperCalledJamis.all.where_values_hash - assert_equal "Jamis", wheres['name'] - assert_equal 50000, wheres['salary'] + assert_equal "Jamis", wheres["name"] + assert_equal 50000, wheres["salary"] end def test_scope_overwrites_default - expected = Developer.all.merge!(order: 'salary DESC, name DESC').to_a.collect(&:name) + expected = Developer.all.merge!(order: "salary DESC, name DESC").to_a.collect(&:name) received = DeveloperOrderedBySalary.by_name.to_a.collect(&:name) assert_equal expected, received end def test_reorder_overrides_default_scope_order - expected = Developer.order('name DESC').collect(&:name) - received = DeveloperOrderedBySalary.reorder('name DESC').collect(&:name) + expected = Developer.order("name DESC").collect(&:name) + received = DeveloperOrderedBySalary.reorder("name DESC").collect(&:name) assert_equal expected, received end def test_order_after_reorder_combines_orders - expected = Developer.order('name DESC, id DESC').collect { |dev| [dev.name, dev.id] } - received = Developer.order('name ASC').reorder('name DESC').order('id DESC').collect { |dev| [dev.name, dev.id] } + expected = Developer.order("name DESC, id DESC").collect { |dev| [dev.name, dev.id] } + received = Developer.order("name ASC").reorder("name DESC").order("id DESC").collect { |dev| [dev.name, dev.id] } assert_equal expected, received end @@ -111,107 +112,107 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_unscope_after_reordering_and_combining - expected = Developer.order('id DESC, name DESC').collect { |dev| [dev.name, dev.id] } - received = DeveloperOrderedBySalary.reorder('name DESC').unscope(:order).order('id DESC, name DESC').collect { |dev| [dev.name, dev.id] } + expected = Developer.order("id DESC, name DESC").collect { |dev| [dev.name, dev.id] } + received = DeveloperOrderedBySalary.reorder("name DESC").unscope(:order).order("id DESC, name DESC").collect { |dev| [dev.name, dev.id] } assert_equal expected, received expected_2 = Developer.all.collect { |dev| [dev.name, dev.id] } - received_2 = Developer.order('id DESC, name DESC').unscope(:order).collect { |dev| [dev.name, dev.id] } + received_2 = Developer.order("id DESC, name DESC").unscope(:order).collect { |dev| [dev.name, dev.id] } assert_equal expected_2, received_2 expected_3 = Developer.all.collect { |dev| [dev.name, dev.id] } - received_3 = Developer.reorder('name DESC').unscope(:order).collect { |dev| [dev.name, dev.id] } + received_3 = Developer.reorder("name DESC").unscope(:order).collect { |dev| [dev.name, dev.id] } assert_equal expected_3, received_3 end def test_unscope_with_where_attributes - expected = Developer.order('salary DESC').collect(&:name) - received = DeveloperOrderedBySalary.where(name: 'David').unscope(where: :name).collect(&:name) + expected = Developer.order("salary DESC").collect(&:name) + received = DeveloperOrderedBySalary.where(name: "David").unscope(where: :name).collect(&:name) assert_equal expected, received - expected_2 = Developer.order('salary DESC').collect(&:name) - received_2 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope({:where => :name}, :select).collect(&:name) + expected_2 = Developer.order("salary DESC").collect(&:name) + received_2 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope({ where: :name }, :select).collect(&:name) assert_equal expected_2, received_2 - expected_3 = Developer.order('salary DESC').collect(&:name) + expected_3 = Developer.order("salary DESC").collect(&:name) received_3 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope(:select, :where).collect(&:name) assert_equal expected_3, received_3 - expected_4 = Developer.order('salary DESC').collect(&:name) + expected_4 = Developer.order("salary DESC").collect(&:name) received_4 = DeveloperOrderedBySalary.where.not("name" => "Jamis").unscope(where: :name).collect(&:name) assert_equal expected_4, received_4 - expected_5 = Developer.order('salary DESC').collect(&:name) + expected_5 = Developer.order("salary DESC").collect(&:name) received_5 = DeveloperOrderedBySalary.where.not("name" => ["Jamis", "David"]).unscope(where: :name).collect(&:name) assert_equal expected_5, received_5 - expected_6 = Developer.order('salary DESC').collect(&:name) - received_6 = DeveloperOrderedBySalary.where(Developer.arel_table['name'].eq('David')).unscope(where: :name).collect(&:name) + expected_6 = Developer.order("salary DESC").collect(&:name) + received_6 = DeveloperOrderedBySalary.where(Developer.arel_table["name"].eq("David")).unscope(where: :name).collect(&:name) assert_equal expected_6, received_6 - expected_7 = Developer.order('salary DESC').collect(&:name) - received_7 = DeveloperOrderedBySalary.where(Developer.arel_table[:name].eq('David')).unscope(where: :name).collect(&:name) + expected_7 = Developer.order("salary DESC").collect(&:name) + received_7 = DeveloperOrderedBySalary.where(Developer.arel_table[:name].eq("David")).unscope(where: :name).collect(&:name) assert_equal expected_7, received_7 end def test_unscope_comparison_where_clauses # unscoped for WHERE (`developers`.`id` <= 2) - expected = Developer.order('salary DESC').collect(&:name) + expected = Developer.order("salary DESC").collect(&:name) received = DeveloperOrderedBySalary.where(id: -Float::INFINITY..2).unscope(where: :id).collect { |dev| dev.name } assert_equal expected, received # unscoped for WHERE (`developers`.`id` < 2) - expected = Developer.order('salary DESC').collect(&:name) + expected = Developer.order("salary DESC").collect(&:name) received = DeveloperOrderedBySalary.where(id: -Float::INFINITY...2).unscope(where: :id).collect { |dev| dev.name } assert_equal expected, received end def test_unscope_multiple_where_clauses - expected = Developer.order('salary DESC').collect(&:name) - received = DeveloperOrderedBySalary.where(name: 'Jamis').where(id: 1).unscope(where: [:name, :id]).collect(&:name) + expected = Developer.order("salary DESC").collect(&:name) + received = DeveloperOrderedBySalary.where(name: "Jamis").where(id: 1).unscope(where: [:name, :id]).collect(&:name) assert_equal expected, received end def test_unscope_string_where_clauses_involved - dev_relation = Developer.order('salary DESC').where("created_at > ?", 1.year.ago) + dev_relation = Developer.order("salary DESC").where("created_at > ?", 1.year.ago) expected = dev_relation.collect(&:name) - dev_ordered_relation = DeveloperOrderedBySalary.where(name: 'Jamis').where("created_at > ?", 1.year.ago) + dev_ordered_relation = DeveloperOrderedBySalary.where(name: "Jamis").where("created_at > ?", 1.year.ago) received = dev_ordered_relation.unscope(where: [:name]).collect(&:name) assert_equal expected, received end def test_unscope_with_grouping_attributes - expected = Developer.order('salary DESC').collect(&:name) + expected = Developer.order("salary DESC").collect(&:name) received = DeveloperOrderedBySalary.group(:name).unscope(:group).collect(&:name) assert_equal expected, received - expected_2 = Developer.order('salary DESC').collect(&:name) + expected_2 = Developer.order("salary DESC").collect(&:name) received_2 = DeveloperOrderedBySalary.group("name").unscope(:group).collect(&:name) assert_equal expected_2, received_2 end def test_unscope_with_limit_in_query - expected = Developer.order('salary DESC').collect(&:name) + expected = Developer.order("salary DESC").collect(&:name) received = DeveloperOrderedBySalary.limit(1).unscope(:limit).collect(&:name) assert_equal expected, received end def test_order_to_unscope_reordering - scope = DeveloperOrderedBySalary.order('salary DESC, name ASC').reverse_order.unscope(:order) - assert !(scope.to_sql =~ /order/i) + scope = DeveloperOrderedBySalary.order("salary DESC, name ASC").reverse_order.unscope(:order) + assert !/order/i.match?(scope.to_sql) end def test_unscope_reverse_order expected = Developer.all.collect(&:name) - received = Developer.order('salary DESC').reverse_order.unscope(:order).collect(&:name) + received = Developer.order("salary DESC").reverse_order.unscope(:order).collect(&:name) assert_equal expected, received end def test_unscope_select - expected = Developer.order('salary ASC').collect(&:name) - received = Developer.order('salary DESC').reverse_order.select(:name).unscope(:select).collect(&:name) + expected = Developer.order("salary ASC").collect(&:name) + received = Developer.order("salary DESC").reverse_order.select(:name).unscope(:select).collect(&:name) assert_equal expected, received expected_2 = Developer.all.collect(&:id) @@ -227,7 +228,7 @@ class DefaultScopingTest < ActiveRecord::TestCase def test_unscope_joins_and_select_on_developers_projects expected = Developer.all.collect(&:name) - received = Developer.joins('JOIN developers_projects ON id = developer_id').select(:id).unscope(:joins, :select).collect(&:name) + received = Developer.joins("JOIN developers_projects ON id = developer_id").select(:id).unscope(:joins, :select).collect(&:name) assert_equal expected, received end @@ -248,8 +249,8 @@ class DefaultScopingTest < ActiveRecord::TestCase scope :by_name, -> name { unscope(where: :name).where(name: name) } end - expected = developer_klass.where(name: 'Jamis').collect { |dev| [dev.name, dev.id] } - received = developer_klass.where(name: 'David').by_name('Jamis').collect { |dev| [dev.name, dev.id] } + expected = developer_klass.where(name: "Jamis").collect { |dev| [dev.name, dev.id] } + received = developer_klass.where(name: "David").by_name("Jamis").collect { |dev| [dev.name, dev.id] } assert_equal expected, received end @@ -263,11 +264,11 @@ class DefaultScopingTest < ActiveRecord::TestCase end assert_raises(ArgumentError) do - Developer.order('name DESC').reverse_order.unscope(:reverse_order) + Developer.order("name DESC").reverse_order.unscope(:reverse_order) end assert_raises(ArgumentError) do - Developer.order('name DESC').where(name: "Jamis").unscope() + Developer.order("name DESC").where(name: "Jamis").unscope() end end @@ -302,35 +303,35 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_order_in_default_scope_should_not_prevail - expected = Developer.all.merge!(order: 'salary desc').to_a.collect(&:salary) - received = DeveloperOrderedBySalary.all.merge!(order: 'salary').to_a.collect(&:salary) + expected = Developer.all.merge!(order: "salary desc").to_a.collect(&:salary) + received = DeveloperOrderedBySalary.all.merge!(order: "salary").to_a.collect(&:salary) assert_equal expected, received end def test_create_attribute_overwrites_default_scoping - assert_equal 'David', PoorDeveloperCalledJamis.create!(:name => 'David').name - assert_equal 200000, PoorDeveloperCalledJamis.create!(:name => 'David', :salary => 200000).salary + assert_equal "David", PoorDeveloperCalledJamis.create!(name: "David").name + assert_equal 200000, PoorDeveloperCalledJamis.create!(name: "David", salary: 200000).salary end def test_create_attribute_overwrites_default_values - assert_equal nil, PoorDeveloperCalledJamis.create!(:salary => nil).salary - assert_equal 50000, PoorDeveloperCalledJamis.create!(:name => 'David').salary + assert_equal nil, PoorDeveloperCalledJamis.create!(salary: nil).salary + assert_equal 50000, PoorDeveloperCalledJamis.create!(name: "David").salary end def test_default_scope_attribute - jamis = PoorDeveloperCalledJamis.new(:name => 'David') + jamis = PoorDeveloperCalledJamis.new(name: "David") assert_equal 50000, jamis.salary end def test_where_attribute - aaron = PoorDeveloperCalledJamis.where(:salary => 20).new(:name => 'Aaron') + aaron = PoorDeveloperCalledJamis.where(salary: 20).new(name: "Aaron") assert_equal 20, aaron.salary - assert_equal 'Aaron', aaron.name + assert_equal "Aaron", aaron.name end def test_where_attribute_merge - aaron = PoorDeveloperCalledJamis.where(:name => 'foo').new(:name => 'Aaron') - assert_equal 'Aaron', aaron.name + aaron = PoorDeveloperCalledJamis.where(name: "foo").new(name: "Aaron") + assert_equal "Aaron", aaron.name end def test_scope_composed_by_limit_and_then_offset_is_equal_to_scope_composed_by_offset_and_then_limit @@ -340,33 +341,33 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_create_with_merge - aaron = PoorDeveloperCalledJamis.create_with(:name => 'foo', :salary => 20).merge( - PoorDeveloperCalledJamis.create_with(:name => 'Aaron')).new + aaron = PoorDeveloperCalledJamis.create_with(name: "foo", salary: 20).merge( + PoorDeveloperCalledJamis.create_with(name: "Aaron")).new assert_equal 20, aaron.salary - assert_equal 'Aaron', aaron.name + assert_equal "Aaron", aaron.name - aaron = PoorDeveloperCalledJamis.create_with(:name => 'foo', :salary => 20). - create_with(:name => 'Aaron').new + aaron = PoorDeveloperCalledJamis.create_with(name: "foo", salary: 20). + create_with(name: "Aaron").new assert_equal 20, aaron.salary - assert_equal 'Aaron', aaron.name + assert_equal "Aaron", aaron.name end def test_create_with_reset - jamis = PoorDeveloperCalledJamis.create_with(:name => 'Aaron').create_with(nil).new - assert_equal 'Jamis', jamis.name + jamis = PoorDeveloperCalledJamis.create_with(name: "Aaron").create_with(nil).new + assert_equal "Jamis", jamis.name end # FIXME: I don't know if this is *desired* behavior, but it is *today's* # behavior. def test_create_with_empty_hash_will_not_reset - jamis = PoorDeveloperCalledJamis.create_with(:name => 'Aaron').create_with({}).new - assert_equal 'Aaron', jamis.name + jamis = PoorDeveloperCalledJamis.create_with(name: "Aaron").create_with({}).new + assert_equal "Aaron", jamis.name end def test_unscoped_with_named_scope_should_not_have_default_scope assert_equal [DeveloperCalledJamis.find(developers(:poor_jamis).id)], DeveloperCalledJamis.poor - assert DeveloperCalledJamis.unscoped.poor.include?(developers(:david).becomes(DeveloperCalledJamis)) + assert_includes DeveloperCalledJamis.unscoped.poor, developers(:david).becomes(DeveloperCalledJamis) assert_equal 11, DeveloperCalledJamis.unscoped.length assert_equal 1, DeveloperCalledJamis.poor.length @@ -408,9 +409,9 @@ class DefaultScopingTest < ActiveRecord::TestCase def test_default_scope_include_with_count d = DeveloperWithIncludes.create! - d.audit_logs.create! :message => 'foo' + d.audit_logs.create! message: "foo" - assert_equal 1, DeveloperWithIncludes.where(:audit_logs => { :message => 'foo' }).count + assert_equal 1, DeveloperWithIncludes.where(audit_logs: { message: "foo" }).count end def test_default_scope_with_references_works_through_collection_association @@ -473,9 +474,9 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_sti_conditions_are_not_carried_in_default_scope - ConditionalStiPost.create! body: '' - SubConditionalStiPost.create! body: '' - SubConditionalStiPost.create! title: 'Hello world', body: '' + ConditionalStiPost.create! body: "" + SubConditionalStiPost.create! body: "" + SubConditionalStiPost.create! title: "Hello world", body: "" assert_equal 2, ConditionalStiPost.count assert_equal 2, ConditionalStiPost.all.to_a.size @@ -485,4 +486,15 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal 1, SubConditionalStiPost.all.to_a.size assert_equal 2, SubConditionalStiPost.unscope(where: :title).to_a.size end + + def test_with_abstract_class_scope_should_be_executed_in_correct_context + vegetarian_pattern, gender_pattern = if current_adapter?(:Mysql2Adapter) + [/`lions`.`is_vegetarian`/, /`lions`.`gender`/] + else + [/"lions"."is_vegetarian"/, /"lions"."gender"/] + end + + assert_match vegetarian_pattern, Lion.all.to_sql + assert_match gender_pattern, Lion.female.to_sql + end end diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index acba97bbb8..58e1310ab0 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -1,11 +1,11 @@ require "cases/helper" -require 'models/post' -require 'models/topic' -require 'models/comment' -require 'models/reply' -require 'models/author' -require 'models/developer' -require 'models/computer' +require "models/post" +require "models/topic" +require "models/comment" +require "models/reply" +require "models/author" +require "models/developer" +require "models/computer" class NamedScopingTest < ActiveRecord::TestCase fixtures :posts, :authors, :topics, :comments, :author_addresses @@ -33,8 +33,8 @@ class NamedScopingTest < ActiveRecord::TestCase all_posts.to_a new_post = Topic.create! - assert !all_posts.include?(new_post) - assert all_posts.reload.include?(new_post) + assert_not_includes all_posts, new_post + assert_includes all_posts.reload, new_post end def test_delegates_finds_and_calculations_to_the_base_class @@ -46,11 +46,18 @@ class NamedScopingTest < ActiveRecord::TestCase assert_equal Topic.average(:replies_count), Topic.base.average(:replies_count) end + def test_calling_merge_at_first_in_scope + Topic.class_eval do + scope :calling_merge_at_first_in_scope, Proc.new { merge(Topic.replied) } + end + assert_equal Topic.calling_merge_at_first_in_scope.to_a, Topic.replied.to_a + end + def test_method_missing_priority_when_delegating klazz = Class.new(ActiveRecord::Base) do self.table_name = "topics" - scope :since, Proc.new { where('written_on >= ?', Time.now - 1.day) } - scope :to, Proc.new { where('written_on <= ?', Time.now) } + scope :since, Proc.new { where("written_on >= ?", Time.now - 1.day) } + scope :to, Proc.new { where("written_on <= ?", Time.now) } end assert_equal klazz.to.since.to_a, klazz.since.to.to_a end @@ -62,10 +69,10 @@ class NamedScopingTest < ActiveRecord::TestCase end def test_scopes_with_options_limit_finds_to_those_matching_the_criteria_specified - assert !Topic.all.merge!(:where => {:approved => true}).to_a.empty? + assert !Topic.all.merge!(where: { approved: true }).to_a.empty? - assert_equal Topic.all.merge!(:where => {:approved => true}).to_a, Topic.approved - assert_equal Topic.where(:approved => true).count, Topic.approved.count + assert_equal Topic.all.merge!(where: { approved: true }).to_a, Topic.approved + assert_equal Topic.where(approved: true).count, Topic.approved.count end def test_scopes_with_string_name_can_be_composed @@ -75,8 +82,8 @@ class NamedScopingTest < ActiveRecord::TestCase end def test_scopes_are_composable - assert_equal((approved = Topic.all.merge!(:where => {:approved => true}).to_a), Topic.approved) - assert_equal((replied = Topic.all.merge!(:where => 'replies_count > 0').to_a), Topic.replied) + assert_equal((approved = Topic.all.merge!(where: { approved: true }).to_a), Topic.approved) + assert_equal((replied = Topic.all.merge!(where: "replies_count > 0").to_a), Topic.replied) assert !(approved == replied) assert !(approved & replied).empty? @@ -84,8 +91,8 @@ class NamedScopingTest < ActiveRecord::TestCase end def test_procedural_scopes - topics_written_before_the_third = Topic.where('written_on < ?', topics(:third).written_on) - topics_written_before_the_second = Topic.where('written_on < ?', topics(:second).written_on) + topics_written_before_the_third = Topic.where("written_on < ?", topics(:third).written_on) + topics_written_before_the_second = Topic.where("written_on < ?", topics(:second).written_on) assert_not_equal topics_written_before_the_second, topics_written_before_the_third assert_equal topics_written_before_the_third, Topic.written_before(topics(:third).written_on) @@ -101,7 +108,7 @@ class NamedScopingTest < ActiveRecord::TestCase def test_scope_with_object objects = Topic.with_object assert_operator objects.length, :>, 0 - assert objects.all?(&:approved?), 'all objects should be approved' + assert objects.all?(&:approved?), "all objects should be approved" end def test_has_many_associations_have_access_to_scopes @@ -231,9 +238,9 @@ class NamedScopingTest < ActiveRecord::TestCase end def test_many_should_return_false_if_none_or_one - topics = Topic.base.where(:id => 0) + topics = Topic.base.where(id: 0) assert !topics.many? - topics = Topic.base.where(:id => 1) + topics = Topic.base.where(id: 1) assert !topics.many? end @@ -273,7 +280,7 @@ class NamedScopingTest < ActiveRecord::TestCase def test_should_build_on_top_of_chained_scopes topic = Topic.approved.by_lifo.build({}) assert topic.approved - assert_equal 'lifo', topic.author_name + assert_equal "lifo", topic.author_name end def test_reserved_scope_names @@ -301,7 +308,7 @@ class NamedScopingTest < ActiveRecord::TestCase :relation, # private class method on AR::Base :new, # redefined class method on AR::Base :all, # a default scope - :public, # some imporant methods on Module and Class + :public, # some important methods on Module and Class :protected, :private, :name, @@ -320,12 +327,12 @@ class NamedScopingTest < ActiveRecord::TestCase conflicts.each do |name| e = assert_raises(ArgumentError, "scope `#{name}` should not be allowed") do - klass.class_eval { scope name, ->{ where(approved: true) } } + klass.class_eval { scope name, -> { where(approved: true) } } end assert_match(/You tried to define a scope named \"#{name}\" on the model/, e.message) e = assert_raises(ArgumentError, "scope `#{name}` should not be allowed") do - subklass.class_eval { scope name, ->{ where(approved: true) } } + subklass.class_eval { scope name, -> { where(approved: true) } } end assert_match(/You tried to define a scope named \"#{name}\" on the model/, e.message) end @@ -333,12 +340,12 @@ class NamedScopingTest < ActiveRecord::TestCase non_conflicts.each do |name| assert_nothing_raised do silence_warnings do - klass.class_eval { scope name, ->{ where(approved: true) } } + klass.class_eval { scope name, -> { where(approved: true) } } end end assert_nothing_raised do - subklass.class_eval { scope name, ->{ where(approved: true) } } + subklass.class_eval { scope name, -> { where(approved: true) } } end end end @@ -350,7 +357,7 @@ class NamedScopingTest < ActiveRecord::TestCase klass = Class.new(ActiveRecord::Base) do self.table_name = "topics" scope :"title containing space", -> { where("title LIKE '% %'") } - scope :approved, -> { where(:approved => true) } + scope :approved, -> { where(approved: true) } end assert_equal klass.send(:"title containing space"), klass.where("title LIKE '% %'") assert_equal klass.approved.send(:"title containing space"), klass.approved.where("title LIKE '% %'") @@ -365,7 +372,7 @@ class NamedScopingTest < ActiveRecord::TestCase end def test_should_use_where_in_query_for_scope - assert_equal Developer.where(name: 'Jamis').to_set, Developer.where(id: Developer.jamises).to_set + assert_equal Developer.where(name: "Jamis").to_set, Developer.where(id: Developer.jamises).to_set end def test_size_should_use_count_when_results_are_not_loaded @@ -424,12 +431,12 @@ class NamedScopingTest < ActiveRecord::TestCase assert_equal 4, Topic.approved.count assert_queries(5) do - Topic.approved.find_each(:batch_size => 1) {|t| assert t.approved? } + Topic.approved.find_each(batch_size: 1) { |t| assert t.approved? } end assert_queries(3) do - Topic.approved.find_in_batches(:batch_size => 2) do |group| - group.each {|t| assert t.approved? } + Topic.approved.find_in_batches(batch_size: 2) do |group| + group.each { |t| assert t.approved? } end end end @@ -455,13 +462,13 @@ class NamedScopingTest < ActiveRecord::TestCase [:public_method, :protected_method, :private_method].each do |reserved_method| assert Topic.respond_to?(reserved_method, true) ActiveRecord::Base.logger.expects(:warn) - silence_warnings { Topic.scope(reserved_method, -> { }) } + silence_warnings { Topic.scope(reserved_method, -> {}) } end end def test_scopes_on_relations # Topic.replied - approved_topics = Topic.all.approved.order('id DESC') + approved_topics = Topic.all.approved.order("id DESC") assert_equal topics(:fifth), approved_topics.first replied_approved_topics = approved_topics.replied @@ -469,7 +476,7 @@ class NamedScopingTest < ActiveRecord::TestCase end def test_index_on_scope - approved = Topic.approved.order('id ASC') + approved = Topic.approved.order("id ASC") assert_equal topics(:second), approved[0] assert approved.loaded? end @@ -510,7 +517,7 @@ class NamedScopingTest < ActiveRecord::TestCase def test_scopes_to_get_newest post = posts(:welcome) old_last_comment = post.comments.newest - new_comment = post.comments.create(:body => "My new comment") + new_comment = post.comments.create(body: "My new comment") assert_equal new_comment, post.comments.newest assert_not_equal old_last_comment, post.comments.newest end @@ -533,15 +540,28 @@ class NamedScopingTest < ActiveRecord::TestCase def test_eager_default_scope_relations_are_remove klass = Class.new(ActiveRecord::Base) - klass.table_name = 'posts' + klass.table_name = "posts" assert_raises(ArgumentError) do - klass.send(:default_scope, klass.where(:id => posts(:welcome).id)) + klass.send(:default_scope, klass.where(id: posts(:welcome).id)) end end def test_subclass_merges_scopes_properly - assert_equal 1, SpecialComment.where(body: 'go crazy').created.count + assert_equal 1, SpecialComment.where(body: "go crazy").created.count + end + + def test_model_class_should_respond_to_none + assert !Topic.none? + Topic.delete_all + assert Topic.none? end + def test_model_class_should_respond_to_one + assert !Topic.one? + Topic.delete_all + assert !Topic.one? + Topic.create! + assert Topic.one? + end end diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb index c15d57460b..27b4583457 100644 --- a/activerecord/test/cases/scoping/relation_scoping_test.rb +++ b/activerecord/test/cases/scoping/relation_scoping_test.rb @@ -1,13 +1,13 @@ require "cases/helper" -require 'models/post' -require 'models/author' -require 'models/developer' -require 'models/computer' -require 'models/project' -require 'models/comment' -require 'models/category' -require 'models/person' -require 'models/reference' +require "models/post" +require "models/author" +require "models/developer" +require "models/computer" +require "models/project" +require "models/comment" +require "models/category" +require "models/person" +require "models/reference" class RelationScopingTest < ActiveRecord::TestCase fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects @@ -109,7 +109,7 @@ class RelationScopingTest < ActiveRecord::TestCase def test_scope_select_concatenates Developer.select("id, name").scoping do - developer = Developer.select('salary').where("name = 'David'").first + developer = Developer.select("salary").where("name = 'David'").first assert_equal 80000, developer.salary assert developer.has_attribute?(:id) assert developer.has_attribute?(:name) @@ -122,7 +122,7 @@ class RelationScopingTest < ActiveRecord::TestCase assert_equal 1, Developer.count end - Developer.where('salary = 100000').scoping do + Developer.where("salary = 100000").scoping do assert_equal 8, Developer.count assert_equal 1, Developer.where("name LIKE 'fixture_1%'").count end @@ -131,49 +131,49 @@ class RelationScopingTest < ActiveRecord::TestCase def test_scoped_find_include # with the include, will retrieve only developers for the given project scoped_developers = Developer.includes(:projects).scoping do - Developer.where('projects.id' => 2).to_a + Developer.where("projects.id" => 2).to_a end - assert scoped_developers.include?(developers(:david)) - assert !scoped_developers.include?(developers(:jamis)) + assert_includes scoped_developers, developers(:david) + assert_not_includes scoped_developers, developers(:jamis) assert_equal 1, scoped_developers.size end def test_scoped_find_joins - scoped_developers = Developer.joins('JOIN developers_projects ON id = developer_id').scoping do - Developer.where('developers_projects.project_id = 2').to_a + scoped_developers = Developer.joins("JOIN developers_projects ON id = developer_id").scoping do + Developer.where("developers_projects.project_id = 2").to_a end - assert scoped_developers.include?(developers(:david)) - assert !scoped_developers.include?(developers(:jamis)) + assert_includes scoped_developers, developers(:david) + assert_not_includes scoped_developers, developers(:jamis) assert_equal 1, scoped_developers.size assert_equal developers(:david).attributes, scoped_developers.first.attributes end def test_scoped_create_with_where - new_comment = VerySpecialComment.where(:post_id => 1).scoping do - VerySpecialComment.create :body => "Wonderful world" + new_comment = VerySpecialComment.where(post_id: 1).scoping do + VerySpecialComment.create body: "Wonderful world" end assert_equal 1, new_comment.post_id - assert Post.find(1).comments.include?(new_comment) + assert_includes Post.find(1).comments, new_comment end def test_scoped_create_with_create_with - new_comment = VerySpecialComment.create_with(:post_id => 1).scoping do - VerySpecialComment.create :body => "Wonderful world" + new_comment = VerySpecialComment.create_with(post_id: 1).scoping do + VerySpecialComment.create body: "Wonderful world" end assert_equal 1, new_comment.post_id - assert Post.find(1).comments.include?(new_comment) + assert_includes Post.find(1).comments, new_comment end def test_scoped_create_with_create_with_has_higher_priority - new_comment = VerySpecialComment.where(:post_id => 2).create_with(:post_id => 1).scoping do - VerySpecialComment.create :body => "Wonderful world" + new_comment = VerySpecialComment.where(post_id: 2).create_with(post_id: 1).scoping do + VerySpecialComment.create body: "Wonderful world" end assert_equal 1, new_comment.post_id - assert Post.find(1).comments.include?(new_comment) + assert_includes Post.find(1).comments, new_comment end def test_ensure_that_method_scoping_is_correctly_restored @@ -193,7 +193,7 @@ class RelationScopingTest < ActiveRecord::TestCase end def test_update_all_default_scope_filters_on_joins - DeveloperFilteredOnJoins.update_all(:salary => 65000) + DeveloperFilteredOnJoins.update_all(salary: 65000) assert_equal 65000, Developer.find(developers(:david).id).salary # has not changed jamis @@ -228,18 +228,25 @@ class RelationScopingTest < ActiveRecord::TestCase assert SpecialComment.all.any? end end + + def test_circular_joins_with_current_scope_does_not_crash + posts = Post.joins(comments: :post).scoping do + Post.current_scope.first(10) + end + assert_equal posts, Post.joins(comments: :post).first(10) + end end class NestedRelationScopingTest < ActiveRecord::TestCase fixtures :authors, :developers, :projects, :comments, :posts def test_merge_options - Developer.where('salary = 80000').scoping do + Developer.where("salary = 80000").scoping do Developer.limit(10).scoping do devs = Developer.all sql = devs.to_sql - assert_match '(salary = 80000)', sql - assert_match 'LIMIT 10', sql + assert_match "(salary = 80000)", sql + assert_match(/LIMIT 10|ROWNUM <= 10|FETCH FIRST 10 ROWS ONLY/, sql) end end end @@ -253,39 +260,39 @@ class NestedRelationScopingTest < ActiveRecord::TestCase end def test_replace_options - Developer.where(:name => 'David').scoping do + Developer.where(name: "David").scoping do Developer.unscoped do - assert_equal 'Jamis', Developer.where(:name => 'Jamis').first[:name] + assert_equal "Jamis", Developer.where(name: "Jamis").first[:name] end - assert_equal 'David', Developer.first[:name] + assert_equal "David", Developer.first[:name] end end def test_three_level_nested_exclusive_scoped_find Developer.where("name = 'Jamis'").scoping do - assert_equal 'Jamis', Developer.first.name + assert_equal "Jamis", Developer.first.name Developer.unscoped.where("name = 'David'") do - assert_equal 'David', Developer.first.name + assert_equal "David", Developer.first.name Developer.unscoped.where("name = 'Maiha'") do assert_equal nil, Developer.first end # ensure that scoping is restored - assert_equal 'David', Developer.first.name + assert_equal "David", Developer.first.name end # ensure that scoping is restored - assert_equal 'Jamis', Developer.first.name + assert_equal "Jamis", Developer.first.name end end def test_nested_scoped_create - comment = Comment.create_with(:post_id => 1).scoping do - Comment.create_with(:post_id => 2).scoping do - Comment.create :body => "Hey guys, nested scopes are broken. Please fix!" + comment = Comment.create_with(post_id: 1).scoping do + Comment.create_with(post_id: 2).scoping do + Comment.create body: "Hey guys, nested scopes are broken. Please fix!" end end @@ -293,15 +300,15 @@ class NestedRelationScopingTest < ActiveRecord::TestCase end def test_nested_exclusive_scope_for_create - comment = Comment.create_with(:body => "Hey guys, nested scopes are broken. Please fix!").scoping do - Comment.unscoped.create_with(:post_id => 1).scoping do + comment = Comment.create_with(body: "Hey guys, nested scopes are broken. Please fix!").scoping do + Comment.unscoped.create_with(post_id: 1).scoping do assert Comment.new.body.blank? - Comment.create :body => "Hey guys" + Comment.create body: "Hey guys" end end assert_equal 1, comment.post_id - assert_equal 'Hey guys', comment.body + assert_equal "Hey guys", comment.body end end @@ -313,24 +320,24 @@ class HasManyScopingTest < ActiveRecord::TestCase end def test_forwarding_of_static_methods - assert_equal 'a comment...', Comment.what_are_you - assert_equal 'a comment...', @welcome.comments.what_are_you + assert_equal "a comment...", Comment.what_are_you + assert_equal "a comment...", @welcome.comments.what_are_you end def test_forwarding_to_scoped - assert_equal 4, Comment.search_by_type('Comment').size - assert_equal 2, @welcome.comments.search_by_type('Comment').size + assert_equal 4, Comment.search_by_type("Comment").size + assert_equal 2, @welcome.comments.search_by_type("Comment").size end def test_nested_scope_finder - Comment.where('1=0').scoping do + Comment.where("1=0").scoping do assert_equal 0, @welcome.comments.count - assert_equal 'a comment...', @welcome.comments.what_are_you + assert_equal "a comment...", @welcome.comments.what_are_you end - Comment.where('1=1').scoping do + Comment.where("1=1").scoping do assert_equal 2, @welcome.comments.count - assert_equal 'a comment...', @welcome.comments.what_are_you + assert_equal "a comment...", @welcome.comments.what_are_you end end @@ -345,7 +352,7 @@ class HasManyScopingTest < ActiveRecord::TestCase end def test_should_maintain_default_scope_on_eager_loaded_associations - michael = Person.where(:id => people(:michael).id).includes(:bad_references).first + michael = Person.where(id: people(:michael).id).includes(:bad_references).first magician = BadReference.find(1) assert_equal [magician], michael.bad_references end @@ -359,19 +366,19 @@ class HasAndBelongsToManyScopingTest < ActiveRecord::TestCase end def test_forwarding_of_static_methods - assert_equal 'a category...', Category.what_are_you - assert_equal 'a category...', @welcome.categories.what_are_you + assert_equal "a category...", Category.what_are_you + assert_equal "a category...", @welcome.categories.what_are_you end def test_nested_scope_finder - Category.where('1=0').scoping do + Category.where("1=0").scoping do assert_equal 0, @welcome.categories.count - assert_equal 'a category...', @welcome.categories.what_are_you + assert_equal "a category...", @welcome.categories.what_are_you end - Category.where('1=1').scoping do + Category.where("1=1").scoping do assert_equal 2, @welcome.categories.count - assert_equal 'a category...', @welcome.categories.what_are_you + assert_equal "a category...", @welcome.categories.what_are_you end end end diff --git a/activerecord/test/cases/secure_token_test.rb b/activerecord/test/cases/secure_token_test.rb index e731443fc2..eda0229c26 100644 --- a/activerecord/test/cases/secure_token_test.rb +++ b/activerecord/test/cases/secure_token_test.rb @@ -1,5 +1,5 @@ -require 'cases/helper' -require 'models/user' +require "cases/helper" +require "models/user" class SecureTokenTest < ActiveRecord::TestCase setup do diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb index 14b80f4df4..ec33ad38f2 100644 --- a/activerecord/test/cases/serialization_test.rb +++ b/activerecord/test/cases/serialization_test.rb @@ -1,9 +1,9 @@ require "cases/helper" -require 'models/contact' -require 'models/topic' -require 'models/book' -require 'models/author' -require 'models/post' +require "models/contact" +require "models/topic" +require "models/book" +require "models/author" +require "models/post" class SerializationTest < ActiveRecord::TestCase fixtures :books @@ -12,14 +12,14 @@ class SerializationTest < ActiveRecord::TestCase def setup @contact_attributes = { - :name => 'aaron stack', - :age => 25, - :avatar => 'binarydata', - :created_at => Time.utc(2006, 8, 1), - :awesome => false, - :preferences => { :gem => '<strong>ruby</strong>' }, - :alternative_id => nil, - :id => nil + name: "aaron stack", + age: 25, + avatar: "binarydata", + created_at: Time.utc(2006, 8, 1), + awesome: false, + preferences: { gem: "<strong>ruby</strong>" }, + alternative_id: nil, + id: nil } end @@ -38,7 +38,7 @@ class SerializationTest < ActiveRecord::TestCase def test_serialize_should_allow_attribute_only_filtering FORMATS.each do |format| - @serialized = Contact.new(@contact_attributes).send("to_#{format}", :only => [ :age, :name ]) + @serialized = Contact.new(@contact_attributes).send("to_#{format}", only: [ :age, :name ]) contact = Contact.new.send("from_#{format}", @serialized) assert_equal @contact_attributes[:name], contact.name, "For #{format}" assert_nil contact.avatar, "For #{format}" @@ -47,7 +47,7 @@ class SerializationTest < ActiveRecord::TestCase def test_serialize_should_allow_attribute_except_filtering FORMATS.each do |format| - @serialized = Contact.new(@contact_attributes).send("to_#{format}", :except => [ :age, :name ]) + @serialized = Contact.new(@contact_attributes).send("to_#{format}", except: [ :age, :name ]) contact = Contact.new.send("from_#{format}", @serialized) assert_nil contact.name, "For #{format}" assert_nil contact.age, "For #{format}" @@ -60,7 +60,7 @@ class SerializationTest < ActiveRecord::TestCase ActiveRecord::Base.include_root_in_json = true klazz = Class.new(ActiveRecord::Base) - klazz.table_name = 'topics' + klazz.table_name = "topics" assert klazz.include_root_in_json klazz.include_root_in_json = false @@ -73,7 +73,7 @@ class SerializationTest < ActiveRecord::TestCase def test_read_attribute_for_serialization_with_format_without_method_missing klazz = Class.new(ActiveRecord::Base) - klazz.table_name = 'books' + klazz.table_name = "books" book = klazz.new assert_nil book.read_attribute_for_serialization(:format) @@ -81,18 +81,18 @@ class SerializationTest < ActiveRecord::TestCase def test_read_attribute_for_serialization_with_format_after_init klazz = Class.new(ActiveRecord::Base) - klazz.table_name = 'books' + klazz.table_name = "books" - book = klazz.new(format: 'paperback') - assert_equal 'paperback', book.read_attribute_for_serialization(:format) + book = klazz.new(format: "paperback") + assert_equal "paperback", book.read_attribute_for_serialization(:format) end def test_read_attribute_for_serialization_with_format_after_find klazz = Class.new(ActiveRecord::Base) - klazz.table_name = 'books' + klazz.table_name = "books" book = klazz.find(books(:awdr).id) - assert_equal 'paperback', book.read_attribute_for_serialization(:format) + assert_equal "paperback", book.read_attribute_for_serialization(:format) end def test_find_records_by_serialized_attributes_through_join diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb index 6056156698..bebd856faf 100644 --- a/activerecord/test/cases/serialized_attribute_test.rb +++ b/activerecord/test/cases/serialized_attribute_test.rb @@ -1,10 +1,10 @@ -require 'cases/helper' -require 'models/topic' -require 'models/reply' -require 'models/person' -require 'models/traffic_light' -require 'models/post' -require 'bcrypt' +require "cases/helper" +require "models/topic" +require "models/reply" +require "models/person" +require "models/traffic_light" +require "models/post" +require "bcrypt" class SerializedAttributeTest < ActiveRecord::TestCase fixtures :topics, :posts @@ -25,7 +25,7 @@ class SerializedAttributeTest < ActiveRecord::TestCase def test_serialized_attribute Topic.serialize("content", MyObject) - myobj = MyObject.new('value1', 'value2') + myobj = MyObject.new("value1", "value2") topic = Topic.create("content" => myobj) assert_equal(myobj, topic.content) @@ -36,7 +36,7 @@ class SerializedAttributeTest < ActiveRecord::TestCase def test_serialized_attribute_in_base_class Topic.serialize("content", Hash) - hash = { 'content1' => 'value1', 'content2' => 'value2' } + hash = { "content1" => "value1", "content2" => "value2" } important_topic = ImportantTopic.create("content" => hash) assert_equal(hash, important_topic.content) @@ -97,7 +97,7 @@ class SerializedAttributeTest < ActiveRecord::TestCase end def test_serialized_attribute_declared_in_subclass - hash = { 'important1' => 'value1', 'important2' => 'value2' } + hash = { "important1" => "value1", "important2" => "value2" } important_topic = ImportantTopic.create("important" => hash) assert_equal(hash, important_topic.important) @@ -124,26 +124,26 @@ class SerializedAttributeTest < ActiveRecord::TestCase end def test_nil_not_serialized_without_class_constraint - assert Topic.new(:content => nil).save - assert_equal 1, Topic.where(:content => nil).count + assert Topic.new(content: nil).save + assert_equal 1, Topic.where(content: nil).count end def test_nil_not_serialized_with_class_constraint Topic.serialize :content, Hash - assert Topic.new(:content => nil).save - assert_equal 1, Topic.where(:content => nil).count + assert Topic.new(content: nil).save + assert_equal 1, Topic.where(content: nil).count end def test_serialized_attribute_should_raise_exception_on_assignment_with_wrong_type Topic.serialize(:content, Hash) assert_raise(ActiveRecord::SerializationTypeMismatch) do - Topic.new(content: 'string') + Topic.new(content: "string") end end def test_should_raise_exception_on_serialized_attribute_with_type_mismatch - myobj = MyObject.new('value1', 'value2') - topic = Topic.new(:content => myobj) + myobj = MyObject.new("value1", "value2") + topic = Topic.new(content: myobj) assert topic.save Topic.serialize(:content, Hash) assert_raise(ActiveRecord::SerializationTypeMismatch) { Topic.find(topic.id).content } @@ -152,11 +152,18 @@ class SerializedAttributeTest < ActiveRecord::TestCase def test_serialized_attribute_with_class_constraint settings = { "color" => "blue" } Topic.serialize(:content, Hash) - topic = Topic.new(:content => settings) + topic = Topic.new(content: settings) assert topic.save assert_equal(settings, Topic.find(topic.id).content) end + def test_where_by_serialized_attribute_with_hash + settings = { "color" => "green" } + Topic.serialize(:content, Hash) + topic = Topic.create!(content: settings) + assert_equal topic, Topic.where(content: settings).take + end + def test_serialized_default_class Topic.serialize(:content, Hash) topic = Topic.new @@ -175,14 +182,14 @@ class SerializedAttributeTest < ActiveRecord::TestCase end def test_serialized_boolean_value_true - topic = Topic.new(:content => true) + topic = Topic.new(content: true) assert topic.save topic = topic.reload assert_equal topic.content, true end def test_serialized_boolean_value_false - topic = Topic.new(:content => false) + topic = Topic.new(content: false) assert topic.save topic = topic.reload assert_equal topic.content, false @@ -200,18 +207,18 @@ class SerializedAttributeTest < ActiveRecord::TestCase end Topic.serialize(:content, some_class) - topic = Topic.new(:content => some_class.new('my value')) + topic = Topic.new(content: some_class.new("my value")) topic.save! topic.reload assert_kind_of some_class, topic.content - assert_equal topic.content, some_class.new('my value') + assert_equal topic.content, some_class.new("my value") end def test_serialize_attribute_via_select_method_when_time_zone_available with_timezone_config aware_attributes: true do Topic.serialize(:content, MyObject) - myobj = MyObject.new('value1', 'value2') + myobj = MyObject.new("value1", "value2") topic = Topic.create(content: myobj) assert_equal(myobj, Topic.select(:content).find(topic.id).content) @@ -220,8 +227,8 @@ class SerializedAttributeTest < ActiveRecord::TestCase end def test_serialize_attribute_can_be_serialized_in_an_integer_column - insures = ['life'] - person = SerializedPerson.new(first_name: 'David', insures: insures) + insures = ["life"] + person = SerializedPerson.new(first_name: "David", insures: insures) assert person.save person = person.reload assert_equal(insures, person.insures) @@ -295,4 +302,37 @@ class SerializedAttributeTest < ActiveRecord::TestCase topic.update_attribute :content, nil assert_equal [topic], Topic.where(content: nil) end + + def test_mutation_detection_does_not_double_serialize + coder = Object.new + def coder.dump(value) + return if value.nil? + value + " encoded" + end + def coder.load(value) + return if value.nil? + value.gsub(" encoded", "") + end + type = Class.new(ActiveModel::Type::Value) do + include ActiveModel::Type::Helpers::Mutable + + def serialize(value) + return if value.nil? + value + " serialized" + end + + def deserialize(value) + return if value.nil? + value.gsub(" serialized", "") + end + end.new + model = Class.new(Topic) do + attribute :foo, type + serialize :foo, coder + end + + topic = model.create!(foo: "bar") + topic.foo + refute topic.changed? + end end diff --git a/activerecord/test/cases/statement_cache_test.rb b/activerecord/test/cases/statement_cache_test.rb index a704b861cb..f45f63c68e 100644 --- a/activerecord/test/cases/statement_cache_test.rb +++ b/activerecord/test/cases/statement_cache_test.rb @@ -1,8 +1,8 @@ -require 'cases/helper' -require 'models/book' -require 'models/liquid' -require 'models/molecule' -require 'models/electron' +require "cases/helper" +require "models/book" +require "models/liquid" +require "models/molecule" +require "models/electron" module ActiveRecord class StatementCacheTest < ActiveRecord::TestCase @@ -16,7 +16,7 @@ module ActiveRecord Book.create(name: "my other book") cache = StatementCache.create(Book.connection) do |params| - Book.where(:name => params.bind) + Book.where(name: params.bind) end b = cache.execute([ "my book" ], Book, Book.connection) @@ -25,7 +25,6 @@ module ActiveRecord assert_equal "my other book", b[0].name end - def test_statement_cache_id b1 = Book.create(name: "my book") b2 = Book.create(name: "my other book") @@ -65,12 +64,12 @@ module ActiveRecord def test_statement_cache_with_complex_statement cache = ActiveRecord::StatementCache.create(Book.connection) do |params| - Liquid.joins(:molecules => :electrons).where('molecules.name' => 'dioxane', 'electrons.name' => 'lepton') + Liquid.joins(molecules: :electrons).where("molecules.name" => "dioxane", "electrons.name" => "lepton") end - salty = Liquid.create(name: 'salty') - molecule = salty.molecules.create(name: 'dioxane') - molecule.electrons.create(name: 'lepton') + salty = Liquid.create(name: "salty") + molecule = salty.molecules.create(name: "dioxane") + molecule.electrons.create(name: "lepton") liquids = cache.execute([], Book, Book.connection) assert_equal "salty", liquids[0].name @@ -94,5 +93,17 @@ module ActiveRecord additional_books = cache.execute([], Book, Book.connection) assert first_books != additional_books end + + def test_unprepared_statements_dont_share_a_cache_with_prepared_statements + Book.create(name: "my book") + Book.create(name: "my other book") + + book = Book.find_by(name: "my book") + other_book = Book.connection.unprepared_statement do + Book.find_by(name: "my other book") + end + + refute_equal book, other_book + end end end diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb index ab63f5825c..633a8a0ebc 100644 --- a/activerecord/test/cases/store_test.rb +++ b/activerecord/test/cases/store_test.rb @@ -1,55 +1,55 @@ -require 'cases/helper' -require 'models/admin' -require 'models/admin/user' +require "cases/helper" +require "models/admin" +require "models/admin/user" class StoreTest < ActiveRecord::TestCase fixtures :'admin/users' setup do - @john = Admin::User.create!(:name => 'John Doe', :color => 'black', :remember_login => true, :height => 'tall', :is_a_good_guy => true) + @john = Admin::User.create!(name: "John Doe", color: "black", remember_login: true, height: "tall", is_a_good_guy: true) end test "reading store attributes through accessors" do - assert_equal 'black', @john.color + assert_equal "black", @john.color assert_nil @john.homepage end test "writing store attributes through accessors" do - @john.color = 'red' - @john.homepage = '37signals.com' + @john.color = "red" + @john.homepage = "37signals.com" - assert_equal 'red', @john.color - assert_equal '37signals.com', @john.homepage + assert_equal "red", @john.color + assert_equal "37signals.com", @john.homepage end test "accessing attributes not exposed by accessors" do - @john.settings[:icecream] = 'graeters' + @john.settings[:icecream] = "graeters" @john.save - assert_equal 'graeters', @john.reload.settings[:icecream] + assert_equal "graeters", @john.reload.settings[:icecream] end test "overriding a read accessor" do - @john.settings[:phone_number] = '1234567890' + @john.settings[:phone_number] = "1234567890" - assert_equal '(123) 456-7890', @john.phone_number + assert_equal "(123) 456-7890", @john.phone_number end test "overriding a read accessor using super" do @john.settings[:color] = nil - assert_equal 'red', @john.color + assert_equal "red", @john.color end test "updating the store will mark it as changed" do - @john.color = 'red' + @john.color = "red" assert @john.settings_changed? end test "updating the store populates the changed array correctly" do - @john.color = 'red' - assert_equal 'black', @john.settings_change[0]['color'] - assert_equal 'red', @john.settings_change[1]['color'] + @john.color = "red" + assert_equal "black", @john.settings_change[0]["color"] + assert_equal "red", @john.settings_change[1]["color"] end test "updating the store won't mark it as changed if an attribute isn't changed" do @@ -67,74 +67,74 @@ class StoreTest < ActiveRecord::TestCase end test "overriding a write accessor" do - @john.phone_number = '(123) 456-7890' + @john.phone_number = "(123) 456-7890" - assert_equal '1234567890', @john.settings[:phone_number] + assert_equal "1234567890", @john.settings[:phone_number] end test "overriding a write accessor using super" do - @john.color = 'yellow' + @john.color = "yellow" - assert_equal 'blue', @john.color + assert_equal "blue", @john.color end test "preserve store attributes data in HashWithIndifferentAccess format without any conversion" do - @john.json_data = ActiveSupport::HashWithIndifferentAccess.new(:height => 'tall', 'weight' => 'heavy') - @john.height = 'low' + @john.json_data = ActiveSupport::HashWithIndifferentAccess.new(:height => "tall", "weight" => "heavy") + @john.height = "low" assert_equal true, @john.json_data.instance_of?(ActiveSupport::HashWithIndifferentAccess) - assert_equal 'low', @john.json_data[:height] - assert_equal 'low', @john.json_data['height'] - assert_equal 'heavy', @john.json_data[:weight] - assert_equal 'heavy', @john.json_data['weight'] + assert_equal "low", @john.json_data[:height] + assert_equal "low", @john.json_data["height"] + assert_equal "heavy", @john.json_data[:weight] + assert_equal "heavy", @john.json_data["weight"] end test "convert store attributes from Hash to HashWithIndifferentAccess saving the data and access attributes indifferently" do - user = Admin::User.find_by_name('Jamis') - assert_equal 'symbol', user.settings[:symbol] - assert_equal 'symbol', user.settings['symbol'] - assert_equal 'string', user.settings[:string] - assert_equal 'string', user.settings['string'] + user = Admin::User.find_by_name("Jamis") + assert_equal "symbol", user.settings[:symbol] + assert_equal "symbol", user.settings["symbol"] + assert_equal "string", user.settings[:string] + assert_equal "string", user.settings["string"] assert_equal true, user.settings.instance_of?(ActiveSupport::HashWithIndifferentAccess) - user.height = 'low' - assert_equal 'symbol', user.settings[:symbol] - assert_equal 'symbol', user.settings['symbol'] - assert_equal 'string', user.settings[:string] - assert_equal 'string', user.settings['string'] + user.height = "low" + assert_equal "symbol", user.settings[:symbol] + assert_equal "symbol", user.settings["symbol"] + assert_equal "string", user.settings[:string] + assert_equal "string", user.settings["string"] assert_equal true, user.settings.instance_of?(ActiveSupport::HashWithIndifferentAccess) end test "convert store attributes from any format other than Hash or HashWithIndifferentAccess losing the data" do @john.json_data = "somedata" - @john.height = 'low' + @john.height = "low" assert_equal true, @john.json_data.instance_of?(ActiveSupport::HashWithIndifferentAccess) - assert_equal 'low', @john.json_data[:height] - assert_equal 'low', @john.json_data['height'] - assert_equal false, @john.json_data.delete_if { |k, v| k == 'height' }.any? + assert_equal "low", @john.json_data[:height] + assert_equal "low", @john.json_data["height"] + assert_equal false, @john.json_data.delete_if { |k, v| k == "height" }.any? end test "reading store attributes through accessors encoded with JSON" do - assert_equal 'tall', @john.height + assert_equal "tall", @john.height assert_nil @john.weight end test "writing store attributes through accessors encoded with JSON" do - @john.height = 'short' - @john.weight = 'heavy' + @john.height = "short" + @john.weight = "heavy" - assert_equal 'short', @john.height - assert_equal 'heavy', @john.weight + assert_equal "short", @john.height + assert_equal "heavy", @john.weight end test "accessing attributes not exposed by accessors encoded with JSON" do - @john.json_data['somestuff'] = 'somecoolstuff' + @john.json_data["somestuff"] = "somecoolstuff" @john.save - assert_equal 'somecoolstuff', @john.reload.json_data['somestuff'] + assert_equal "somecoolstuff", @john.reload.json_data["somestuff"] end test "updating the store will mark it as changed encoded with JSON" do - @john.height = 'short' + @john.height = "short" assert @john.json_data_changed? end @@ -177,6 +177,7 @@ class StoreTest < ActiveRecord::TestCase assert_equal [:color], first_model.stored_attributes[:data] assert_equal [:color, :width, :height], second_model.stored_attributes[:data] assert_equal [:color, :area, :volume], third_model.stored_attributes[:data] + assert_equal [:color], first_model.stored_attributes[:data] end test "YAML coder initializes the store when a Nil value is given" do diff --git a/activerecord/test/cases/suppressor_test.rb b/activerecord/test/cases/suppressor_test.rb index 72c5c16555..a7d16b7cdb 100644 --- a/activerecord/test/cases/suppressor_test.rb +++ b/activerecord/test/cases/suppressor_test.rb @@ -1,6 +1,6 @@ -require 'cases/helper' -require 'models/notification' -require 'models/user' +require "cases/helper" +require "models/notification" +require "models/user" class SuppressorTest < ActiveRecord::TestCase def test_suppresses_create @@ -15,22 +15,22 @@ class SuppressorTest < ActiveRecord::TestCase end def test_suppresses_update - user = User.create! token: 'asdf' + user = User.create! token: "asdf" User.suppress do - user.update token: 'ghjkl' - assert_equal 'asdf', user.reload.token + user.update token: "ghjkl" + assert_equal "asdf", user.reload.token - user.update! token: 'zxcvbnm' - assert_equal 'asdf', user.reload.token + user.update! token: "zxcvbnm" + assert_equal "asdf", user.reload.token - user.token = 'qwerty' + user.token = "qwerty" user.save - assert_equal 'asdf', user.reload.token + assert_equal "asdf", user.reload.token - user.token = 'uiop' + user.token = "uiop" user.save! - assert_equal 'asdf', user.reload.token + assert_equal "asdf", user.reload.token end end @@ -46,7 +46,30 @@ class SuppressorTest < ActiveRecord::TestCase Notification.suppress { UserWithNotification.create! } assert_difference -> { Notification.count } do - Notification.create! + Notification.create!(message: "New Comment") + end + end + + def test_suppresses_validations_on_create + assert_no_difference -> { Notification.count } do + Notification.suppress do + User.create + User.create! + User.new.save + User.new.save! + end + end + end + + def test_suppresses_when_nested_multiple_times + assert_no_difference -> { Notification.count } do + Notification.suppress do + Notification.suppress {} + Notification.create + Notification.create! + Notification.new.save + Notification.new.save! + end end end end diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb index 49df6628eb..d847a02679 100644 --- a/activerecord/test/cases/tasks/database_tasks_test.rb +++ b/activerecord/test/cases/tasks/database_tasks_test.rb @@ -1,5 +1,5 @@ -require 'cases/helper' -require 'active_record/tasks/database_tasks' +require "cases/helper" +require "active_record/tasks/database_tasks" module ActiveRecord module DatabaseTasksSetupper @@ -8,6 +8,13 @@ module ActiveRecord ActiveRecord::Tasks::MySQLDatabaseTasks.stubs(:new).returns @mysql_tasks ActiveRecord::Tasks::PostgreSQLDatabaseTasks.stubs(:new).returns @postgresql_tasks ActiveRecord::Tasks::SQLiteDatabaseTasks.stubs(:new).returns @sqlite_tasks + + $stdout, @original_stdout = StringIO.new, $stdout + $stderr, @original_stderr = StringIO.new, $stderr + end + + def teardown + $stdout, $stderr = @original_stdout, @original_stderr end end @@ -23,7 +30,7 @@ module ActiveRecord protected_environments = ActiveRecord::Base.protected_environments.dup current_env = ActiveRecord::Migrator.current_environment - assert !protected_environments.include?(current_env) + assert_not_includes protected_environments, current_env # Assert no error ActiveRecord::Tasks::DatabaseTasks.check_protected_environments! @@ -57,12 +64,12 @@ module ActiveRecord instance.expects(:structure_dump).with("awesome-file.sql") ActiveRecord::Tasks::DatabaseTasks.register_task(/foo/, klazz) - ActiveRecord::Tasks::DatabaseTasks.structure_dump({'adapter' => :foo}, "awesome-file.sql") + ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => :foo }, "awesome-file.sql") end def test_unregistered_task assert_raise(ActiveRecord::Tasks::DatabaseNotSupported) do - ActiveRecord::Tasks::DatabaseTasks.structure_dump({'adapter' => :bar}, "awesome-file.sql") + ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => :bar }, "awesome-file.sql") end end end @@ -73,20 +80,20 @@ module ActiveRecord ADAPTERS_TASKS.each do |k, v| define_method("test_#{k}_create") do eval("@#{v}").expects(:create) - ActiveRecord::Tasks::DatabaseTasks.create 'adapter' => k + ActiveRecord::Tasks::DatabaseTasks.create "adapter" => k end end end class DatabaseTasksCreateAllTest < ActiveRecord::TestCase def setup - @configurations = {'development' => {'database' => 'my-db'}} + @configurations = { "development" => { "database" => "my-db" } } ActiveRecord::Base.stubs(:configurations).returns(@configurations) end def test_ignores_configurations_without_databases - @configurations['development'].merge!('database' => nil) + @configurations["development"].merge!("database" => nil) ActiveRecord::Tasks::DatabaseTasks.expects(:create).never @@ -94,7 +101,7 @@ module ActiveRecord end def test_ignores_remote_databases - @configurations['development'].merge!('host' => 'my.server.tld') + @configurations["development"].merge!("host" => "my.server.tld") $stderr.stubs(:puts).returns(nil) ActiveRecord::Tasks::DatabaseTasks.expects(:create).never @@ -103,15 +110,15 @@ module ActiveRecord end def test_warning_for_remote_databases - @configurations['development'].merge!('host' => 'my.server.tld') + @configurations["development"].merge!("host" => "my.server.tld") - $stderr.expects(:puts).with('This task only modifies local databases. my-db is on a remote host.') + $stderr.expects(:puts).with("This task only modifies local databases. my-db is on a remote host.") ActiveRecord::Tasks::DatabaseTasks.create_all end def test_creates_configurations_with_local_ip - @configurations['development'].merge!('host' => '127.0.0.1') + @configurations["development"].merge!("host" => "127.0.0.1") ActiveRecord::Tasks::DatabaseTasks.expects(:create) @@ -119,7 +126,7 @@ module ActiveRecord end def test_creates_configurations_with_local_host - @configurations['development'].merge!('host' => 'localhost') + @configurations["development"].merge!("host" => "localhost") ActiveRecord::Tasks::DatabaseTasks.expects(:create) @@ -127,7 +134,7 @@ module ActiveRecord end def test_creates_configurations_with_blank_hosts - @configurations['development'].merge!('host' => nil) + @configurations["development"].merge!("host" => nil) ActiveRecord::Tasks::DatabaseTasks.expects(:create) @@ -138,9 +145,9 @@ module ActiveRecord class DatabaseTasksCreateCurrentTest < ActiveRecord::TestCase def setup @configurations = { - 'development' => {'database' => 'dev-db'}, - 'test' => {'database' => 'test-db'}, - 'production' => {'database' => 'prod-db'} + "development" => { "database" => "dev-db" }, + "test" => { "database" => "test-db" }, + "production" => { "database" => "prod-db" } } ActiveRecord::Base.stubs(:configurations).returns(@configurations) @@ -149,33 +156,37 @@ module ActiveRecord def test_creates_current_environment_database ActiveRecord::Tasks::DatabaseTasks.expects(:create). - with('database' => 'prod-db') + with("database" => "prod-db") ActiveRecord::Tasks::DatabaseTasks.create_current( - ActiveSupport::StringInquirer.new('production') + ActiveSupport::StringInquirer.new("production") ) end def test_creates_test_and_development_databases_when_env_was_not_specified ActiveRecord::Tasks::DatabaseTasks.expects(:create). - with('database' => 'dev-db') + with("database" => "dev-db") ActiveRecord::Tasks::DatabaseTasks.expects(:create). - with('database' => 'test-db') - ENV.expects(:[]).with('RAILS_ENV').returns(nil) + with("database" => "test-db") ActiveRecord::Tasks::DatabaseTasks.create_current( - ActiveSupport::StringInquirer.new('development') + ActiveSupport::StringInquirer.new("development") ) end - def test_creates_only_development_database_when_rails_env_is_development + def test_creates_test_and_development_databases_when_rails_env_is_development + old_env = ENV["RAILS_ENV"] + ENV["RAILS_ENV"] = "development" ActiveRecord::Tasks::DatabaseTasks.expects(:create). - with('database' => 'dev-db') - ENV.expects(:[]).with('RAILS_ENV').returns('development') + with("database" => "dev-db") + ActiveRecord::Tasks::DatabaseTasks.expects(:create). + with("database" => "test-db") ActiveRecord::Tasks::DatabaseTasks.create_current( - ActiveSupport::StringInquirer.new('development') + ActiveSupport::StringInquirer.new("development") ) + ensure + ENV["RAILS_ENV"] = old_env end def test_establishes_connection_for_the_given_environment @@ -184,7 +195,7 @@ module ActiveRecord ActiveRecord::Base.expects(:establish_connection).with(:development) ActiveRecord::Tasks::DatabaseTasks.create_current( - ActiveSupport::StringInquirer.new('development') + ActiveSupport::StringInquirer.new("development") ) end end @@ -195,20 +206,20 @@ module ActiveRecord ADAPTERS_TASKS.each do |k, v| define_method("test_#{k}_drop") do eval("@#{v}").expects(:drop) - ActiveRecord::Tasks::DatabaseTasks.drop 'adapter' => k + ActiveRecord::Tasks::DatabaseTasks.drop "adapter" => k end end end class DatabaseTasksDropAllTest < ActiveRecord::TestCase def setup - @configurations = {:development => {'database' => 'my-db'}} + @configurations = { development: { "database" => "my-db" } } ActiveRecord::Base.stubs(:configurations).returns(@configurations) end def test_ignores_configurations_without_databases - @configurations[:development].merge!('database' => nil) + @configurations[:development].merge!("database" => nil) ActiveRecord::Tasks::DatabaseTasks.expects(:drop).never @@ -216,7 +227,7 @@ module ActiveRecord end def test_ignores_remote_databases - @configurations[:development].merge!('host' => 'my.server.tld') + @configurations[:development].merge!("host" => "my.server.tld") $stderr.stubs(:puts).returns(nil) ActiveRecord::Tasks::DatabaseTasks.expects(:drop).never @@ -225,15 +236,15 @@ module ActiveRecord end def test_warning_for_remote_databases - @configurations[:development].merge!('host' => 'my.server.tld') + @configurations[:development].merge!("host" => "my.server.tld") - $stderr.expects(:puts).with('This task only modifies local databases. my-db is on a remote host.') + $stderr.expects(:puts).with("This task only modifies local databases. my-db is on a remote host.") ActiveRecord::Tasks::DatabaseTasks.drop_all end def test_drops_configurations_with_local_ip - @configurations[:development].merge!('host' => '127.0.0.1') + @configurations[:development].merge!("host" => "127.0.0.1") ActiveRecord::Tasks::DatabaseTasks.expects(:drop) @@ -241,7 +252,7 @@ module ActiveRecord end def test_drops_configurations_with_local_host - @configurations[:development].merge!('host' => 'localhost') + @configurations[:development].merge!("host" => "localhost") ActiveRecord::Tasks::DatabaseTasks.expects(:drop) @@ -249,7 +260,7 @@ module ActiveRecord end def test_drops_configurations_with_blank_hosts - @configurations[:development].merge!('host' => nil) + @configurations[:development].merge!("host" => nil) ActiveRecord::Tasks::DatabaseTasks.expects(:drop) @@ -260,9 +271,9 @@ module ActiveRecord class DatabaseTasksDropCurrentTest < ActiveRecord::TestCase def setup @configurations = { - 'development' => {'database' => 'dev-db'}, - 'test' => {'database' => 'test-db'}, - 'production' => {'database' => 'prod-db'} + "development" => { "database" => "dev-db" }, + "test" => { "database" => "test-db" }, + "production" => { "database" => "prod-db" } } ActiveRecord::Base.stubs(:configurations).returns(@configurations) @@ -270,49 +281,66 @@ module ActiveRecord def test_drops_current_environment_database ActiveRecord::Tasks::DatabaseTasks.expects(:drop). - with('database' => 'prod-db') + with("database" => "prod-db") ActiveRecord::Tasks::DatabaseTasks.drop_current( - ActiveSupport::StringInquirer.new('production') + ActiveSupport::StringInquirer.new("production") ) end def test_drops_test_and_development_databases_when_env_was_not_specified ActiveRecord::Tasks::DatabaseTasks.expects(:drop). - with('database' => 'dev-db') + with("database" => "dev-db") ActiveRecord::Tasks::DatabaseTasks.expects(:drop). - with('database' => 'test-db') - ENV.expects(:[]).with('RAILS_ENV').returns(nil) + with("database" => "test-db") ActiveRecord::Tasks::DatabaseTasks.drop_current( - ActiveSupport::StringInquirer.new('development') + ActiveSupport::StringInquirer.new("development") ) end - def test_drops_only_development_database_when_rails_env_is_development + def test_drops_testand_development_databases_when_rails_env_is_development + old_env = ENV["RAILS_ENV"] + ENV["RAILS_ENV"] = "development" ActiveRecord::Tasks::DatabaseTasks.expects(:drop). - with('database' => 'dev-db') - ENV.expects(:[]).with('RAILS_ENV').returns('development') + with("database" => "dev-db") + ActiveRecord::Tasks::DatabaseTasks.expects(:drop). + with("database" => "test-db") ActiveRecord::Tasks::DatabaseTasks.drop_current( - ActiveSupport::StringInquirer.new('development') + ActiveSupport::StringInquirer.new("development") ) + ensure + ENV["RAILS_ENV"] = old_env end end class DatabaseTasksMigrateTest < ActiveRecord::TestCase + self.use_transactional_tests = false + + def setup + ActiveRecord::Tasks::DatabaseTasks.migrations_paths = "custom/path" + end + + def teardown + ActiveRecord::Tasks::DatabaseTasks.migrations_paths = nil + end + def test_migrate_receives_correct_env_vars - verbose, version = ENV['VERBOSE'], ENV['VERSION'] + verbose, version = ENV["VERBOSE"], ENV["VERSION"] - ActiveRecord::Tasks::DatabaseTasks.migrations_paths = 'custom/path' - ENV['VERBOSE'] = 'false' - ENV['VERSION'] = '4' + ENV["VERBOSE"] = "false" + ENV["VERSION"] = "4" - ActiveRecord::Migrator.expects(:migrate).with('custom/path', 4) + ActiveRecord::Migrator.expects(:migrate).with("custom/path", 4) ActiveRecord::Tasks::DatabaseTasks.migrate ensure - ActiveRecord::Tasks::DatabaseTasks.migrations_paths = nil - ENV['VERBOSE'], ENV['VERSION'] = verbose, version + ENV["VERBOSE"], ENV["VERSION"] = verbose, version + end + + def test_migrate_clears_schema_cache_afterward + ActiveRecord::Base.expects(:clear_cache!) + ActiveRecord::Tasks::DatabaseTasks.migrate end end @@ -322,7 +350,7 @@ module ActiveRecord ADAPTERS_TASKS.each do |k, v| define_method("test_#{k}_purge") do eval("@#{v}").expects(:purge) - ActiveRecord::Tasks::DatabaseTasks.purge 'adapter' => k + ActiveRecord::Tasks::DatabaseTasks.purge "adapter" => k end end end @@ -330,27 +358,27 @@ module ActiveRecord class DatabaseTasksPurgeCurrentTest < ActiveRecord::TestCase def test_purges_current_environment_database configurations = { - 'development' => {'database' => 'dev-db'}, - 'test' => {'database' => 'test-db'}, - 'production' => {'database' => 'prod-db'} + "development" => { "database" => "dev-db" }, + "test" => { "database" => "test-db" }, + "production" => { "database" => "prod-db" } } ActiveRecord::Base.stubs(:configurations).returns(configurations) ActiveRecord::Tasks::DatabaseTasks.expects(:purge). - with('database' => 'prod-db') + with("database" => "prod-db") ActiveRecord::Base.expects(:establish_connection).with(:production) - ActiveRecord::Tasks::DatabaseTasks.purge_current('production') + ActiveRecord::Tasks::DatabaseTasks.purge_current("production") end end class DatabaseTasksPurgeAllTest < ActiveRecord::TestCase def test_purge_all_local_configurations - configurations = {:development => {'database' => 'my-db'}} + configurations = { development: { "database" => "my-db" } } ActiveRecord::Base.stubs(:configurations).returns(configurations) ActiveRecord::Tasks::DatabaseTasks.expects(:purge). - with('database' => 'my-db') + with("database" => "my-db") ActiveRecord::Tasks::DatabaseTasks.purge_all end @@ -362,7 +390,7 @@ module ActiveRecord ADAPTERS_TASKS.each do |k, v| define_method("test_#{k}_charset") do eval("@#{v}").expects(:charset) - ActiveRecord::Tasks::DatabaseTasks.charset 'adapter' => k + ActiveRecord::Tasks::DatabaseTasks.charset "adapter" => k end end end @@ -373,7 +401,7 @@ module ActiveRecord ADAPTERS_TASKS.each do |k, v| define_method("test_#{k}_collation") do eval("@#{v}").expects(:collation) - ActiveRecord::Tasks::DatabaseTasks.collation 'adapter' => k + ActiveRecord::Tasks::DatabaseTasks.collation "adapter" => k end end end @@ -384,7 +412,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") - ActiveRecord::Tasks::DatabaseTasks.structure_dump({'adapter' => k}, "awesome-file.sql") + ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => k }, "awesome-file.sql") end end end @@ -395,7 +423,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") - ActiveRecord::Tasks::DatabaseTasks.structure_load({'adapter' => k}, "awesome-file.sql") + ActiveRecord::Tasks::DatabaseTasks.structure_load({ "adapter" => k }, "awesome-file.sql") end end end @@ -409,15 +437,15 @@ module ActiveRecord class DatabaseTasksCheckSchemaFileDefaultsTest < ActiveRecord::TestCase def test_check_schema_file_defaults - ActiveRecord::Tasks::DatabaseTasks.stubs(:db_dir).returns('/tmp') - assert_equal '/tmp/schema.rb', ActiveRecord::Tasks::DatabaseTasks.schema_file + ActiveRecord::Tasks::DatabaseTasks.stubs(:db_dir).returns("/tmp") + assert_equal "/tmp/schema.rb", ActiveRecord::Tasks::DatabaseTasks.schema_file end end class DatabaseTasksCheckSchemaFileSpecifiedFormatsTest < ActiveRecord::TestCase - {ruby: 'schema.rb', sql: 'structure.sql'}.each_pair do |fmt, filename| + { ruby: "schema.rb", sql: "structure.sql" }.each_pair do |fmt, filename| define_method("test_check_schema_file_for_#{fmt}_format") do - ActiveRecord::Tasks::DatabaseTasks.stubs(:db_dir).returns('/tmp') + ActiveRecord::Tasks::DatabaseTasks.stubs(:db_dir).returns("/tmp") assert_equal "/tmp/#{filename}", ActiveRecord::Tasks::DatabaseTasks.schema_file(fmt) end end diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb index 1632f04854..dbe935808e 100644 --- a/activerecord/test/cases/tasks/mysql_rake_test.rb +++ b/activerecord/test/cases/tasks/mysql_rake_test.rb @@ -1,312 +1,345 @@ -require 'cases/helper' +require "cases/helper" +require "active_record/tasks/database_tasks" if current_adapter?(:Mysql2Adapter) -module ActiveRecord - class MysqlDBCreateTest < ActiveRecord::TestCase - def setup - @connection = stub(:create_database => true) - @configuration = { - 'adapter' => 'mysql2', - 'database' => 'my-app-db' - } - - ActiveRecord::Base.stubs(:connection).returns(@connection) - ActiveRecord::Base.stubs(:establish_connection).returns(true) - end - - def test_establishes_connection_without_database - ActiveRecord::Base.expects(:establish_connection). - with('adapter' => 'mysql2', 'database' => nil) + module ActiveRecord + class MysqlDBCreateTest < ActiveRecord::TestCase + def setup + @connection = stub(create_database: true) + @configuration = { + "adapter" => "mysql2", + "database" => "my-app-db" + } + + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:establish_connection).returns(true) + + $stdout, @original_stdout = StringIO.new, $stdout + $stderr, @original_stderr = StringIO.new, $stderr + end - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end + def teardown + $stdout, $stderr = @original_stdout, @original_stderr + end - def test_creates_database_with_no_default_options - @connection.expects(:create_database). - with('my-app-db', {}) + def test_establishes_connection_without_database + ActiveRecord::Base.expects(:establish_connection). + with("adapter" => "mysql2", "database" => nil) - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end - - def test_creates_database_with_given_encoding - @connection.expects(:create_database). - with('my-app-db', charset: 'latin1') + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end - ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge('encoding' => 'latin1') - end + def test_creates_database_with_no_default_options + @connection.expects(:create_database). + with("my-app-db", {}) - def test_creates_database_with_given_collation - @connection.expects(:create_database). - with('my-app-db', collation: 'latin1_swedish_ci') + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end - ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge('collation' => 'latin1_swedish_ci') - end + def test_creates_database_with_given_encoding + @connection.expects(:create_database). + with("my-app-db", charset: "latin1") - def test_establishes_connection_to_database - ActiveRecord::Base.expects(:establish_connection).with(@configuration) + ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge("encoding" => "latin1") + end - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end + def test_creates_database_with_given_collation + @connection.expects(:create_database). + with("my-app-db", collation: "latin1_swedish_ci") - def test_create_when_database_exists_outputs_info_to_stderr - $stderr.expects(:puts).with("my-app-db already exists").once + ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge("collation" => "latin1_swedish_ci") + end - ActiveRecord::Base.connection.stubs(:create_database).raises( - ActiveRecord::StatementInvalid.new("Can't create database 'dev'; database exists:") - ) + def test_establishes_connection_to_database + ActiveRecord::Base.expects(:establish_connection).with(@configuration) - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end - end + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end - class MysqlDBCreateAsRootTest < ActiveRecord::TestCase - def setup - @connection = stub("Connection", create_database: true) - @error = Mysql2::Error.new("Invalid permissions") - @configuration = { - 'adapter' => 'mysql2', - 'database' => 'my-app-db', - 'username' => 'pat', - 'password' => 'wossname' - } - - $stdin.stubs(:gets).returns("secret\n") - $stdout.stubs(:print).returns(nil) - @error.stubs(:errno).returns(1045) - ActiveRecord::Base.stubs(:connection).returns(@connection) - ActiveRecord::Base.stubs(:establish_connection). - raises(@error). - then.returns(true) - end + def test_when_database_created_successfully_outputs_info_to_stdout + ActiveRecord::Tasks::DatabaseTasks.create @configuration - def test_root_password_is_requested - assert_permissions_granted_for("pat") - $stdin.expects(:gets).returns("secret\n") + assert_equal $stdout.string, "Created database 'my-app-db'\n" + end - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end + def test_create_when_database_exists_outputs_info_to_stderr + ActiveRecord::Base.connection.stubs(:create_database).raises( + ActiveRecord::Tasks::DatabaseAlreadyExists + ) - def test_connection_established_as_root - assert_permissions_granted_for("pat") - ActiveRecord::Base.expects(:establish_connection).with( - 'adapter' => 'mysql2', - 'database' => nil, - 'username' => 'root', - 'password' => 'secret' - ) + ActiveRecord::Tasks::DatabaseTasks.create @configuration - ActiveRecord::Tasks::DatabaseTasks.create @configuration + assert_equal $stderr.string, "Database 'my-app-db' already exists\n" + end end - def test_database_created_by_root - assert_permissions_granted_for("pat") - @connection.expects(:create_database). - with('my-app-db', {}) + class MysqlDBCreateAsRootTest < ActiveRecord::TestCase + def setup + @connection = stub("Connection", create_database: true) + @error = Mysql2::Error.new("Invalid permissions") + @configuration = { + "adapter" => "mysql2", + "database" => "my-app-db", + "username" => "pat", + "password" => "wossname" + } + + $stdin.stubs(:gets).returns("secret\n") + $stdout.stubs(:print).returns(nil) + @error.stubs(:errno).returns(1045) + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:establish_connection). + raises(@error). + then.returns(true) + + $stdout, @original_stdout = StringIO.new, $stdout + $stderr, @original_stderr = StringIO.new, $stderr + end - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end + def teardown + $stdout, $stderr = @original_stdout, @original_stderr + end - def test_grant_privileges_for_normal_user - assert_permissions_granted_for("pat") - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end + def test_root_password_is_requested + assert_permissions_granted_for("pat") + $stdin.expects(:gets).returns("secret\n") - def test_do_not_grant_privileges_for_root_user - @configuration['username'] = 'root' - @configuration['password'] = '' - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end - def test_connection_established_as_normal_user - assert_permissions_granted_for("pat") - ActiveRecord::Base.expects(:establish_connection).returns do + def test_connection_established_as_root + assert_permissions_granted_for("pat") ActiveRecord::Base.expects(:establish_connection).with( - 'adapter' => 'mysql2', - 'database' => 'my-app-db', - 'username' => 'pat', - 'password' => 'secret' + "adapter" => "mysql2", + "database" => nil, + "username" => "root", + "password" => "secret" ) - raise @error + ActiveRecord::Tasks::DatabaseTasks.create @configuration end - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end + def test_database_created_by_root + assert_permissions_granted_for("pat") + @connection.expects(:create_database). + with("my-app-db", {}) - def test_sends_output_to_stderr_when_other_errors - @error.stubs(:errno).returns(42) + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end - $stderr.expects(:puts).at_least_once.returns(nil) + def test_grant_privileges_for_normal_user + assert_permissions_granted_for("pat") + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end + def test_do_not_grant_privileges_for_root_user + @configuration["username"] = "root" + @configuration["password"] = "" + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end - private + def test_connection_established_as_normal_user + assert_permissions_granted_for("pat") + ActiveRecord::Base.expects(:establish_connection).returns do + ActiveRecord::Base.expects(:establish_connection).with( + "adapter" => "mysql2", + "database" => "my-app-db", + "username" => "pat", + "password" => "secret" + ) - def assert_permissions_granted_for(db_user) - db_name = @configuration['database'] - db_password = @configuration['password'] - @connection.expects(:execute).with("GRANT ALL PRIVILEGES ON #{db_name}.* TO '#{db_user}'@'localhost' IDENTIFIED BY '#{db_password}' WITH GRANT OPTION;") - end - end + raise @error + end - class MySQLDBDropTest < ActiveRecord::TestCase - def setup - @connection = stub(:drop_database => true) - @configuration = { - 'adapter' => 'mysql2', - 'database' => 'my-app-db' - } + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end - ActiveRecord::Base.stubs(:connection).returns(@connection) - ActiveRecord::Base.stubs(:establish_connection).returns(true) - end + def test_sends_output_to_stderr_when_other_errors + @error.stubs(:errno).returns(42) - def test_establishes_connection_to_mysql_database - ActiveRecord::Base.expects(:establish_connection).with @configuration + $stderr.expects(:puts).at_least_once.returns(nil) - ActiveRecord::Tasks::DatabaseTasks.drop @configuration - end + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end - def test_drops_database - @connection.expects(:drop_database).with('my-app-db') + private - ActiveRecord::Tasks::DatabaseTasks.drop @configuration + def assert_permissions_granted_for(db_user) + db_name = @configuration["database"] + db_password = @configuration["password"] + @connection.expects(:execute).with("GRANT ALL PRIVILEGES ON #{db_name}.* TO '#{db_user}'@'localhost' IDENTIFIED BY '#{db_password}' WITH GRANT OPTION;") + end end - end - class MySQLPurgeTest < ActiveRecord::TestCase - def setup - @connection = stub(:recreate_database => true) - @configuration = { - 'adapter' => 'mysql2', - 'database' => 'test-db' - } + class MySQLDBDropTest < ActiveRecord::TestCase + def setup + @connection = stub(drop_database: true) + @configuration = { + "adapter" => "mysql2", + "database" => "my-app-db" + } - ActiveRecord::Base.stubs(:connection).returns(@connection) - ActiveRecord::Base.stubs(:establish_connection).returns(true) - end + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:establish_connection).returns(true) - def test_establishes_connection_to_the_appropriate_database - ActiveRecord::Base.expects(:establish_connection).with(@configuration) + $stdout, @original_stdout = StringIO.new, $stdout + $stderr, @original_stderr = StringIO.new, $stderr + end - ActiveRecord::Tasks::DatabaseTasks.purge @configuration - end + def teardown + $stdout, $stderr = @original_stdout, @original_stderr + end - def test_recreates_database_with_no_default_options - @connection.expects(:recreate_database). - with('test-db', {}) + def test_establishes_connection_to_mysql_database + ActiveRecord::Base.expects(:establish_connection).with @configuration - ActiveRecord::Tasks::DatabaseTasks.purge @configuration - end + ActiveRecord::Tasks::DatabaseTasks.drop @configuration + end - def test_recreates_database_with_the_given_options - @connection.expects(:recreate_database). - with('test-db', charset: 'latin', collation: 'latin1_swedish_ci') + def test_drops_database + @connection.expects(:drop_database).with("my-app-db") - ActiveRecord::Tasks::DatabaseTasks.purge @configuration.merge( - 'encoding' => 'latin', 'collation' => 'latin1_swedish_ci') - end - end + ActiveRecord::Tasks::DatabaseTasks.drop @configuration + end - class MysqlDBCharsetTest < ActiveRecord::TestCase - def setup - @connection = stub(:create_database => true) - @configuration = { - 'adapter' => 'mysql2', - 'database' => 'my-app-db' - } + def test_when_database_dropped_successfully_outputs_info_to_stdout + ActiveRecord::Tasks::DatabaseTasks.drop @configuration - ActiveRecord::Base.stubs(:connection).returns(@connection) - ActiveRecord::Base.stubs(:establish_connection).returns(true) + assert_equal $stdout.string, "Dropped database 'my-app-db'\n" + end end - def test_db_retrieves_charset - @connection.expects(:charset) - ActiveRecord::Tasks::DatabaseTasks.charset @configuration - end - end + class MySQLPurgeTest < ActiveRecord::TestCase + def setup + @connection = stub(recreate_database: true) + @configuration = { + "adapter" => "mysql2", + "database" => "test-db" + } - class MysqlDBCollationTest < ActiveRecord::TestCase - def setup - @connection = stub(:create_database => true) - @configuration = { - 'adapter' => 'mysql2', - 'database' => 'my-app-db' - } + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:establish_connection).returns(true) + end - ActiveRecord::Base.stubs(:connection).returns(@connection) - ActiveRecord::Base.stubs(:establish_connection).returns(true) - end + def test_establishes_connection_to_the_appropriate_database + ActiveRecord::Base.expects(:establish_connection).with(@configuration) - def test_db_retrieves_collation - @connection.expects(:collation) - ActiveRecord::Tasks::DatabaseTasks.collation @configuration + ActiveRecord::Tasks::DatabaseTasks.purge @configuration + end + + def test_recreates_database_with_no_default_options + @connection.expects(:recreate_database). + with("test-db", {}) + + ActiveRecord::Tasks::DatabaseTasks.purge @configuration + end + + def test_recreates_database_with_the_given_options + @connection.expects(:recreate_database). + with("test-db", charset: "latin", collation: "latin1_swedish_ci") + + ActiveRecord::Tasks::DatabaseTasks.purge @configuration.merge( + "encoding" => "latin", "collation" => "latin1_swedish_ci") + end end - end - class MySQLStructureDumpTest < ActiveRecord::TestCase - def setup - @configuration = { - 'adapter' => 'mysql2', - 'database' => 'test-db' - } + class MysqlDBCharsetTest < ActiveRecord::TestCase + def setup + @connection = stub(create_database: true) + @configuration = { + "adapter" => "mysql2", + "database" => "my-app-db" + } + + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:establish_connection).returns(true) + end + + def test_db_retrieves_charset + @connection.expects(:charset) + ActiveRecord::Tasks::DatabaseTasks.charset @configuration + end end - def test_structure_dump - filename = "awesome-file.sql" - Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "--routines", "test-db").returns(true) + class MysqlDBCollationTest < ActiveRecord::TestCase + def setup + @connection = stub(create_database: true) + @configuration = { + "adapter" => "mysql2", + "database" => "my-app-db" + } + + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:establish_connection).returns(true) + end - ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename) + def test_db_retrieves_collation + @connection.expects(:collation) + ActiveRecord::Tasks::DatabaseTasks.collation @configuration + end end - def test_warn_when_external_structure_dump_command_execution_fails - filename = "awesome-file.sql" - Kernel.expects(:system) - .with("mysqldump", "--result-file", filename, "--no-data", "--routines", "test-db") - .returns(false) + class MySQLStructureDumpTest < ActiveRecord::TestCase + def setup + @configuration = { + "adapter" => "mysql2", + "database" => "test-db" + } + end + + def test_structure_dump + filename = "awesome-file.sql" + Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "test-db").returns(true) - e = assert_raise(RuntimeError) { ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename) - } - assert_match(/^failed to execute: `mysqldump`$/, e.message) - end + end - def test_structure_dump_with_port_number - filename = "awesome-file.sql" - Kernel.expects(:system).with("mysqldump", "--port=10000", "--result-file", filename, "--no-data", "--routines", "test-db").returns(true) + def test_warn_when_external_structure_dump_command_execution_fails + filename = "awesome-file.sql" + Kernel.expects(:system) + .with("mysqldump", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "test-db") + .returns(false) - ActiveRecord::Tasks::DatabaseTasks.structure_dump( - @configuration.merge('port' => 10000), - filename) - end + e = assert_raise(RuntimeError) { + ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename) + } + assert_match(/^failed to execute: `mysqldump`$/, e.message) + end - def test_structure_dump_with_ssl - filename = "awesome-file.sql" - Kernel.expects(:system).with("mysqldump", "--ssl-ca=ca.crt", "--result-file", filename, "--no-data", "--routines", "test-db").returns(true) + def test_structure_dump_with_port_number + filename = "awesome-file.sql" + Kernel.expects(:system).with("mysqldump", "--port=10000", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "test-db").returns(true) - ActiveRecord::Tasks::DatabaseTasks.structure_dump( - @configuration.merge("sslca" => "ca.crt"), - filename) - end - end + ActiveRecord::Tasks::DatabaseTasks.structure_dump( + @configuration.merge("port" => 10000), + filename) + end - class MySQLStructureLoadTest < ActiveRecord::TestCase - def setup - @configuration = { - 'adapter' => 'mysql2', - 'database' => 'test-db' - } + def test_structure_dump_with_ssl + filename = "awesome-file.sql" + Kernel.expects(:system).with("mysqldump", "--ssl-ca=ca.crt", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "test-db").returns(true) + + ActiveRecord::Tasks::DatabaseTasks.structure_dump( + @configuration.merge("sslca" => "ca.crt"), + filename) + end end - 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) + class MySQLStructureLoadTest < ActiveRecord::TestCase + def setup + @configuration = { + "adapter" => "mysql2", + "database" => "test-db" + } + end + + 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) - ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename) + ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename) + end 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 ba53f340ae..b8c8ec88f0 100644 --- a/activerecord/test/cases/tasks/postgresql_rake_test.rb +++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb @@ -1,278 +1,304 @@ -require 'cases/helper' +require "cases/helper" +require "active_record/tasks/database_tasks" if current_adapter?(:PostgreSQLAdapter) -module ActiveRecord - class PostgreSQLDBCreateTest < ActiveRecord::TestCase - def setup - @connection = stub(:create_database => true) - @configuration = { - 'adapter' => 'postgresql', - 'database' => 'my-app-db' - } - - ActiveRecord::Base.stubs(:connection).returns(@connection) - ActiveRecord::Base.stubs(:establish_connection).returns(true) - end - - def test_establishes_connection_to_postgresql_database - ActiveRecord::Base.expects(:establish_connection).with( - 'adapter' => 'postgresql', - 'database' => 'postgres', - 'schema_search_path' => 'public' - ) + module ActiveRecord + class PostgreSQLDBCreateTest < ActiveRecord::TestCase + def setup + @connection = stub(create_database: true) + @configuration = { + "adapter" => "postgresql", + "database" => "my-app-db" + } + + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:establish_connection).returns(true) + + $stdout, @original_stdout = StringIO.new, $stdout + $stderr, @original_stderr = StringIO.new, $stderr + end - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end + def teardown + $stdout, $stderr = @original_stdout, @original_stderr + end - def test_creates_database_with_default_encoding - @connection.expects(:create_database). - with('my-app-db', @configuration.merge('encoding' => 'utf8')) + def test_establishes_connection_to_postgresql_database + ActiveRecord::Base.expects(:establish_connection).with( + "adapter" => "postgresql", + "database" => "postgres", + "schema_search_path" => "public" + ) - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end - def test_creates_database_with_given_encoding - @connection.expects(:create_database). - with('my-app-db', @configuration.merge('encoding' => 'latin')) + def test_creates_database_with_default_encoding + @connection.expects(:create_database). + with("my-app-db", @configuration.merge("encoding" => "utf8")) - ActiveRecord::Tasks::DatabaseTasks.create @configuration. - merge('encoding' => 'latin') - end + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end - def test_creates_database_with_given_collation_and_ctype - @connection.expects(:create_database). - with('my-app-db', @configuration.merge('encoding' => 'utf8', 'collation' => 'ja_JP.UTF8', 'ctype' => 'ja_JP.UTF8')) + def test_creates_database_with_given_encoding + @connection.expects(:create_database). + with("my-app-db", @configuration.merge("encoding" => "latin")) - ActiveRecord::Tasks::DatabaseTasks.create @configuration. - merge('collation' => 'ja_JP.UTF8', 'ctype' => 'ja_JP.UTF8') - end + ActiveRecord::Tasks::DatabaseTasks.create @configuration. + merge("encoding" => "latin") + end - def test_establishes_connection_to_new_database - ActiveRecord::Base.expects(:establish_connection).with(@configuration) + def test_creates_database_with_given_collation_and_ctype + @connection.expects(:create_database). + with("my-app-db", @configuration.merge("encoding" => "utf8", "collation" => "ja_JP.UTF8", "ctype" => "ja_JP.UTF8")) - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end + ActiveRecord::Tasks::DatabaseTasks.create @configuration. + merge("collation" => "ja_JP.UTF8", "ctype" => "ja_JP.UTF8") + end - def test_db_create_with_error_prints_message - ActiveRecord::Base.stubs(:establish_connection).raises(Exception) + def test_establishes_connection_to_new_database + ActiveRecord::Base.expects(:establish_connection).with(@configuration) - $stderr.stubs(:puts).returns(true) - $stderr.expects(:puts). - with("Couldn't create database for #{@configuration.inspect}") + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end - assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration } - end + def test_db_create_with_error_prints_message + ActiveRecord::Base.stubs(:establish_connection).raises(Exception) - def test_create_when_database_exists_outputs_info_to_stderr - $stderr.expects(:puts).with("my-app-db already exists").once + $stderr.stubs(:puts).returns(true) + $stderr.expects(:puts). + with("Couldn't create database for #{@configuration.inspect}") - ActiveRecord::Base.connection.stubs(:create_database).raises( - ActiveRecord::StatementInvalid.new('database "my-app-db" already exists') - ) + assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration } + end - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end - end + def test_when_database_created_successfully_outputs_info_to_stdout + ActiveRecord::Tasks::DatabaseTasks.create @configuration - class PostgreSQLDBDropTest < ActiveRecord::TestCase - def setup - @connection = stub(:drop_database => true) - @configuration = { - 'adapter' => 'postgresql', - 'database' => 'my-app-db' - } + assert_equal $stdout.string, "Created database 'my-app-db'\n" + end - ActiveRecord::Base.stubs(:connection).returns(@connection) - ActiveRecord::Base.stubs(:establish_connection).returns(true) - end + def test_create_when_database_exists_outputs_info_to_stderr + ActiveRecord::Base.connection.stubs(:create_database).raises( + ActiveRecord::Tasks::DatabaseAlreadyExists + ) - def test_establishes_connection_to_postgresql_database - ActiveRecord::Base.expects(:establish_connection).with( - 'adapter' => 'postgresql', - 'database' => 'postgres', - 'schema_search_path' => 'public' - ) + ActiveRecord::Tasks::DatabaseTasks.create @configuration - ActiveRecord::Tasks::DatabaseTasks.drop @configuration + assert_equal $stderr.string, "Database 'my-app-db' already exists\n" + end end - def test_drops_database - @connection.expects(:drop_database).with('my-app-db') + class PostgreSQLDBDropTest < ActiveRecord::TestCase + def setup + @connection = stub(drop_database: true) + @configuration = { + "adapter" => "postgresql", + "database" => "my-app-db" + } - ActiveRecord::Tasks::DatabaseTasks.drop @configuration - end - end + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:establish_connection).returns(true) - class PostgreSQLPurgeTest < ActiveRecord::TestCase - def setup - @connection = stub(:create_database => true, :drop_database => true) - @configuration = { - 'adapter' => 'postgresql', - 'database' => 'my-app-db' - } - - ActiveRecord::Base.stubs(:connection).returns(@connection) - ActiveRecord::Base.stubs(:clear_active_connections!).returns(true) - ActiveRecord::Base.stubs(:establish_connection).returns(true) - end + $stdout, @original_stdout = StringIO.new, $stdout + $stderr, @original_stderr = StringIO.new, $stderr + end - def test_clears_active_connections - ActiveRecord::Base.expects(:clear_active_connections!) + def teardown + $stdout, $stderr = @original_stdout, @original_stderr + end - ActiveRecord::Tasks::DatabaseTasks.purge @configuration - end + def test_establishes_connection_to_postgresql_database + ActiveRecord::Base.expects(:establish_connection).with( + "adapter" => "postgresql", + "database" => "postgres", + "schema_search_path" => "public" + ) - def test_establishes_connection_to_postgresql_database - ActiveRecord::Base.expects(:establish_connection).with( - 'adapter' => 'postgresql', - 'database' => 'postgres', - 'schema_search_path' => 'public' - ) + ActiveRecord::Tasks::DatabaseTasks.drop @configuration + end - ActiveRecord::Tasks::DatabaseTasks.purge @configuration - end + def test_drops_database + @connection.expects(:drop_database).with("my-app-db") - def test_drops_database - @connection.expects(:drop_database).with('my-app-db') + ActiveRecord::Tasks::DatabaseTasks.drop @configuration + end - ActiveRecord::Tasks::DatabaseTasks.purge @configuration + 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" + end end - def test_creates_database - @connection.expects(:create_database). - with('my-app-db', @configuration.merge('encoding' => 'utf8')) + class PostgreSQLPurgeTest < ActiveRecord::TestCase + def setup + @connection = stub(create_database: true, drop_database: true) + @configuration = { + "adapter" => "postgresql", + "database" => "my-app-db" + } + + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:clear_active_connections!).returns(true) + ActiveRecord::Base.stubs(:establish_connection).returns(true) + end - ActiveRecord::Tasks::DatabaseTasks.purge @configuration - end + def test_clears_active_connections + ActiveRecord::Base.expects(:clear_active_connections!) - def test_establishes_connection - ActiveRecord::Base.expects(:establish_connection).with(@configuration) + ActiveRecord::Tasks::DatabaseTasks.purge @configuration + end - ActiveRecord::Tasks::DatabaseTasks.purge @configuration - end - end + def test_establishes_connection_to_postgresql_database + ActiveRecord::Base.expects(:establish_connection).with( + "adapter" => "postgresql", + "database" => "postgres", + "schema_search_path" => "public" + ) - class PostgreSQLDBCharsetTest < ActiveRecord::TestCase - def setup - @connection = stub(:create_database => true) - @configuration = { - 'adapter' => 'postgresql', - 'database' => 'my-app-db' - } + ActiveRecord::Tasks::DatabaseTasks.purge @configuration + end - ActiveRecord::Base.stubs(:connection).returns(@connection) - ActiveRecord::Base.stubs(:establish_connection).returns(true) - end + def test_drops_database + @connection.expects(:drop_database).with("my-app-db") - def test_db_retrieves_charset - @connection.expects(:encoding) - ActiveRecord::Tasks::DatabaseTasks.charset @configuration - end - end + ActiveRecord::Tasks::DatabaseTasks.purge @configuration + end - class PostgreSQLDBCollationTest < ActiveRecord::TestCase - def setup - @connection = stub(:create_database => true) - @configuration = { - 'adapter' => 'postgresql', - 'database' => 'my-app-db' - } + def test_creates_database + @connection.expects(:create_database). + with("my-app-db", @configuration.merge("encoding" => "utf8")) - ActiveRecord::Base.stubs(:connection).returns(@connection) - ActiveRecord::Base.stubs(:establish_connection).returns(true) - end + ActiveRecord::Tasks::DatabaseTasks.purge @configuration + end - def test_db_retrieves_collation - @connection.expects(:collation) - ActiveRecord::Tasks::DatabaseTasks.collation @configuration - end - end + def test_establishes_connection + ActiveRecord::Base.expects(:establish_connection).with(@configuration) - class PostgreSQLStructureDumpTest < ActiveRecord::TestCase - def setup - @connection = stub(:structure_dump => true) - @configuration = { - 'adapter' => 'postgresql', - 'database' => 'my-app-db' - } - @filename = "awesome-file.sql" - - ActiveRecord::Base.stubs(:connection).returns(@connection) - ActiveRecord::Base.stubs(:establish_connection).returns(true) - Kernel.stubs(:system) - File.stubs(:open) + ActiveRecord::Tasks::DatabaseTasks.purge @configuration + end end - def test_structure_dump - Kernel.expects(:system).with('pg_dump', '-s', '-x', '-O', '-f', @filename, 'my-app-db').returns(true) + class PostgreSQLDBCharsetTest < ActiveRecord::TestCase + def setup + @connection = stub(create_database: true) + @configuration = { + "adapter" => "postgresql", + "database" => "my-app-db" + } - ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename) + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:establish_connection).returns(true) + end + + def test_db_retrieves_charset + @connection.expects(:encoding) + ActiveRecord::Tasks::DatabaseTasks.charset @configuration + end end - def test_structure_dump_with_schema_search_path - @configuration['schema_search_path'] = 'foo,bar' + class PostgreSQLDBCollationTest < ActiveRecord::TestCase + def setup + @connection = stub(create_database: true) + @configuration = { + "adapter" => "postgresql", + "database" => "my-app-db" + } - Kernel.expects(:system).with('pg_dump', '-s', '-x', '-O', '-f', @filename, '--schema=foo', '--schema=bar', 'my-app-db').returns(true) + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:establish_connection).returns(true) + end - ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename) + def test_db_retrieves_collation + @connection.expects(:collation) + ActiveRecord::Tasks::DatabaseTasks.collation @configuration + end end - def test_structure_dump_with_schema_search_path_and_dump_schemas_all - @configuration['schema_search_path'] = 'foo,bar' + class PostgreSQLStructureDumpTest < ActiveRecord::TestCase + def setup + @connection = stub(structure_dump: true) + @configuration = { + "adapter" => "postgresql", + "database" => "my-app-db" + } + @filename = "awesome-file.sql" + + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:establish_connection).returns(true) + Kernel.stubs(:system) + File.stubs(:open) + end - Kernel.expects(:system).with("pg_dump", '-s', '-x', '-O', '-f', @filename, 'my-app-db').returns(true) + def test_structure_dump + Kernel.expects(:system).with("pg_dump", "-s", "-x", "-O", "-f", @filename, "my-app-db").returns(true) - with_dump_schemas(:all) do ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename) end - end - def test_structure_dump_with_dump_schemas_string - Kernel.expects(:system).with("pg_dump", '-s', '-x', '-O', '-f', @filename, '--schema=foo', '--schema=bar', "my-app-db").returns(true) + def test_structure_dump_with_schema_search_path + @configuration["schema_search_path"] = "foo,bar" + + Kernel.expects(:system).with("pg_dump", "-s", "-x", "-O", "-f", @filename, "--schema=foo", "--schema=bar", "my-app-db").returns(true) - with_dump_schemas('foo,bar') do ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename) end - end - private + def test_structure_dump_with_schema_search_path_and_dump_schemas_all + @configuration["schema_search_path"] = "foo,bar" - def with_dump_schemas(value, &block) - old_dump_schemas = ActiveRecord::Base.dump_schemas - ActiveRecord::Base.dump_schemas = value - yield - ensure - ActiveRecord::Base.dump_schemas = old_dump_schemas - end - end + Kernel.expects(:system).with("pg_dump", "-s", "-x", "-O", "-f", @filename, "my-app-db").returns(true) - class PostgreSQLStructureLoadTest < ActiveRecord::TestCase - def setup - @connection = stub - @configuration = { - 'adapter' => 'postgresql', - 'database' => 'my-app-db' - } - - ActiveRecord::Base.stubs(:connection).returns(@connection) - ActiveRecord::Base.stubs(:establish_connection).returns(true) - Kernel.stubs(:system) - end + with_dump_schemas(:all) do + ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename) + end + end + + def test_structure_dump_with_dump_schemas_string + Kernel.expects(:system).with("pg_dump", "-s", "-x", "-O", "-f", @filename, "--schema=foo", "--schema=bar", "my-app-db").returns(true) + + with_dump_schemas("foo,bar") do + ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename) + end + end - def test_structure_load - filename = "awesome-file.sql" - Kernel.expects(:system).with('psql', '-q', '-f', filename, @configuration['database']).returns(true) + private - ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename) + def with_dump_schemas(value, &block) + old_dump_schemas = ActiveRecord::Base.dump_schemas + ActiveRecord::Base.dump_schemas = value + yield + ensure + ActiveRecord::Base.dump_schemas = old_dump_schemas + end end - def test_structure_load_accepts_path_with_spaces - filename = "awesome file.sql" - Kernel.expects(:system).with('psql', '-q', '-f', filename, @configuration['database']).returns(true) + class PostgreSQLStructureLoadTest < ActiveRecord::TestCase + def setup + @connection = stub + @configuration = { + "adapter" => "postgresql", + "database" => "my-app-db" + } + + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:establish_connection).returns(true) + Kernel.stubs(:system) + end + + def test_structure_load + 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 - ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename) + 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 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 0aea0c3b38..141048bfe7 100644 --- a/activerecord/test/cases/tasks/sqlite_rake_test.rb +++ b/activerecord/test/cases/tasks/sqlite_rake_test.rb @@ -1,193 +1,220 @@ -require 'cases/helper' -require 'pathname' +require "cases/helper" +require "active_record/tasks/database_tasks" +require "pathname" if current_adapter?(:SQLite3Adapter) -module ActiveRecord - class SqliteDBCreateTest < ActiveRecord::TestCase - def setup - @database = 'db_create.sqlite3' - @connection = stub :connection - @configuration = { - 'adapter' => 'sqlite3', - 'database' => @database - } - - File.stubs(:exist?).returns(false) - ActiveRecord::Base.stubs(:connection).returns(@connection) - ActiveRecord::Base.stubs(:establish_connection).returns(true) - end + module ActiveRecord + class SqliteDBCreateTest < ActiveRecord::TestCase + def setup + @database = "db_create.sqlite3" + @connection = stub :connection + @configuration = { + "adapter" => "sqlite3", + "database" => @database + } + + File.stubs(:exist?).returns(false) + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:establish_connection).returns(true) + + $stdout, @original_stdout = StringIO.new, $stdout + $stderr, @original_stderr = StringIO.new, $stderr + end - def test_db_checks_database_exists - File.expects(:exist?).with(@database).returns(false) + def teardown + $stdout, $stderr = @original_stdout, @original_stderr + end - ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root' - end + def test_db_checks_database_exists + File.expects(:exist?).with(@database).returns(false) - def test_db_create_when_file_exists - File.stubs(:exist?).returns(true) + ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root" + end - $stderr.expects(:puts).with("#{@database} already exists") + def test_when_db_created_successfully_outputs_info_to_stdout + ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root" - ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root' - end + assert_equal $stdout.string, "Created database '#{@database}'\n" + end - def test_db_create_with_file_does_nothing - File.stubs(:exist?).returns(true) - $stderr.stubs(:puts).returns(nil) + def test_db_create_when_file_exists + File.stubs(:exist?).returns(true) - ActiveRecord::Base.expects(:establish_connection).never + ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root" - ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root' - end + assert_equal $stderr.string, "Database '#{@database}' already exists\n" + end - def test_db_create_establishes_a_connection - ActiveRecord::Base.expects(:establish_connection).with(@configuration) + def test_db_create_with_file_does_nothing + File.stubs(:exist?).returns(true) + $stderr.stubs(:puts).returns(nil) - ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root' - end + ActiveRecord::Base.expects(:establish_connection).never - def test_db_create_with_error_prints_message - ActiveRecord::Base.stubs(:establish_connection).raises(Exception) + ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root" + end - $stderr.stubs(:puts).returns(true) - $stderr.expects(:puts). - with("Couldn't create database for #{@configuration.inspect}") + def test_db_create_establishes_a_connection + ActiveRecord::Base.expects(:establish_connection).with(@configuration) - assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root' } - end - end + ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root" + end - class SqliteDBDropTest < ActiveRecord::TestCase - def setup - @database = "db_create.sqlite3" - @path = stub(:to_s => '/absolute/path', :absolute? => true) - @configuration = { - 'adapter' => 'sqlite3', - 'database' => @database - } - - Pathname.stubs(:new).returns(@path) - File.stubs(:join).returns('/former/relative/path') - FileUtils.stubs(:rm).returns(true) - end + def test_db_create_with_error_prints_message + ActiveRecord::Base.stubs(:establish_connection).raises(Exception) - def test_creates_path_from_database - Pathname.expects(:new).with(@database).returns(@path) + $stderr.stubs(:puts).returns(true) + $stderr.expects(:puts). + with("Couldn't create database for #{@configuration.inspect}") - ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root' + assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root" } + end end - def test_removes_file_with_absolute_path - File.stubs(:exist?).returns(true) - @path.stubs(:absolute?).returns(true) + class SqliteDBDropTest < ActiveRecord::TestCase + def setup + @database = "db_create.sqlite3" + @path = stub(to_s: "/absolute/path", absolute?: true) + @configuration = { + "adapter" => "sqlite3", + "database" => @database + } - FileUtils.expects(:rm).with('/absolute/path') + Pathname.stubs(:new).returns(@path) + File.stubs(:join).returns("/former/relative/path") + FileUtils.stubs(:rm).returns(true) - ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root' - end + $stdout, @original_stdout = StringIO.new, $stdout + $stderr, @original_stderr = StringIO.new, $stderr + end - def test_generates_absolute_path_with_given_root - @path.stubs(:absolute?).returns(false) + def teardown + $stdout, $stderr = @original_stdout, @original_stderr + end - File.expects(:join).with('/rails/root', @path). - returns('/former/relative/path') + def test_creates_path_from_database + Pathname.expects(:new).with(@database).returns(@path) - ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root' - end + ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root" + end - def test_removes_file_with_relative_path - File.stubs(:exist?).returns(true) - @path.stubs(:absolute?).returns(false) + def test_removes_file_with_absolute_path + File.stubs(:exist?).returns(true) + @path.stubs(:absolute?).returns(true) - FileUtils.expects(:rm).with('/former/relative/path') + FileUtils.expects(:rm).with("/absolute/path") - ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root' - end - end + ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root" + end - class SqliteDBCharsetTest < ActiveRecord::TestCase - def setup - @database = 'db_create.sqlite3' - @connection = stub :connection - @configuration = { - 'adapter' => 'sqlite3', - 'database' => @database - } - - File.stubs(:exist?).returns(false) - ActiveRecord::Base.stubs(:connection).returns(@connection) - ActiveRecord::Base.stubs(:establish_connection).returns(true) - end + def test_generates_absolute_path_with_given_root + @path.stubs(:absolute?).returns(false) - def test_db_retrieves_charset - @connection.expects(:encoding) - ActiveRecord::Tasks::DatabaseTasks.charset @configuration, '/rails/root' - end - end + File.expects(:join).with("/rails/root", @path). + returns("/former/relative/path") - class SqliteDBCollationTest < ActiveRecord::TestCase - def setup - @database = 'db_create.sqlite3' - @connection = stub :connection - @configuration = { - 'adapter' => 'sqlite3', - 'database' => @database - } - - File.stubs(:exist?).returns(false) - ActiveRecord::Base.stubs(:connection).returns(@connection) - ActiveRecord::Base.stubs(:establish_connection).returns(true) - end + ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root" + end + + def test_removes_file_with_relative_path + File.stubs(:exist?).returns(true) + @path.stubs(:absolute?).returns(false) - def test_db_retrieves_collation - assert_raise NoMethodError do - ActiveRecord::Tasks::DatabaseTasks.collation @configuration, '/rails/root' + FileUtils.expects(:rm).with("/former/relative/path") + + ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root" + end + + 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" end end - end - class SqliteStructureDumpTest < ActiveRecord::TestCase - def setup - @database = "db_create.sqlite3" - @configuration = { - 'adapter' => 'sqlite3', - 'database' => @database - } + class SqliteDBCharsetTest < ActiveRecord::TestCase + def setup + @database = "db_create.sqlite3" + @connection = stub :connection + @configuration = { + "adapter" => "sqlite3", + "database" => @database + } + + File.stubs(:exist?).returns(false) + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:establish_connection).returns(true) + end + + def test_db_retrieves_charset + @connection.expects(:encoding) + ActiveRecord::Tasks::DatabaseTasks.charset @configuration, "/rails/root" + end end - def test_structure_dump - dbfile = @database - filename = "awesome-file.sql" + class SqliteDBCollationTest < ActiveRecord::TestCase + def setup + @database = "db_create.sqlite3" + @connection = stub :connection + @configuration = { + "adapter" => "sqlite3", + "database" => @database + } - ActiveRecord::Tasks::DatabaseTasks.structure_dump @configuration, filename, '/rails/root' - assert File.exist?(dbfile) - assert File.exist?(filename) - ensure - FileUtils.rm_f(filename) - FileUtils.rm_f(dbfile) + File.stubs(:exist?).returns(false) + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:establish_connection).returns(true) + end + + def test_db_retrieves_collation + assert_raise NoMethodError do + ActiveRecord::Tasks::DatabaseTasks.collation @configuration, "/rails/root" + end + end end - end - class SqliteStructureLoadTest < ActiveRecord::TestCase - def setup - @database = "db_create.sqlite3" - @configuration = { - 'adapter' => 'sqlite3', - 'database' => @database - } + class SqliteStructureDumpTest < ActiveRecord::TestCase + def setup + @database = "db_create.sqlite3" + @configuration = { + "adapter" => "sqlite3", + "database" => @database + } + end + + def test_structure_dump + dbfile = @database + filename = "awesome-file.sql" + + ActiveRecord::Tasks::DatabaseTasks.structure_dump @configuration, filename, "/rails/root" + assert File.exist?(dbfile) + assert File.exist?(filename) + ensure + FileUtils.rm_f(filename) + FileUtils.rm_f(dbfile) + end end - def test_structure_load - dbfile = @database - filename = "awesome-file.sql" + class SqliteStructureLoadTest < ActiveRecord::TestCase + def setup + @database = "db_create.sqlite3" + @configuration = { + "adapter" => "sqlite3", + "database" => @database + } + end + + def test_structure_load + dbfile = @database + filename = "awesome-file.sql" - open(filename, 'w') { |f| f.puts("select datetime('now', 'localtime');") } - ActiveRecord::Tasks::DatabaseTasks.structure_load @configuration, filename, '/rails/root' - assert File.exist?(dbfile) - ensure - FileUtils.rm_f(filename) - FileUtils.rm_f(dbfile) + open(filename, "w") { |f| f.puts("select datetime('now', 'localtime');") } + ActiveRecord::Tasks::DatabaseTasks.structure_load @configuration, filename, "/rails/root" + assert File.exist?(dbfile) + ensure + FileUtils.rm_f(filename) + FileUtils.rm_f(dbfile) + end end end end -end diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb index 87299c0dab..a4fc69fcd3 100644 --- a/activerecord/test/cases/test_case.rb +++ b/activerecord/test/cases/test_case.rb @@ -1,19 +1,31 @@ -require 'active_support/test_case' -require 'active_support/testing/stream' +require "active_support/test_case" +require "active_support/testing/autorun" +require "active_support/testing/method_call_assertions" +require "active_support/testing/stream" +require "active_record/fixtures" + +require "cases/validations_repair_helper" module ActiveRecord # = Active Record Test Case # # Defines some test assertions to test against SQL queries. class TestCase < ActiveSupport::TestCase #:nodoc: + include ActiveSupport::Testing::MethodCallAssertions include ActiveSupport::Testing::Stream + include ActiveRecord::TestFixtures + include ActiveRecord::ValidationsRepairHelper - def teardown - SQLCounter.clear_log + self.fixture_path = FIXTURES_ROOT + self.use_instantiated_fixtures = false + self.use_transactional_tests = true + + def create_fixtures(*fixture_set_names, &block) + ActiveRecord::FixtureSet.create_fixtures(ActiveRecord::TestCase.fixture_path, fixture_set_names, fixture_class_names, &block) end - def assert_date_from_db(expected, actual, message = nil) - assert_equal expected.to_s, actual.to_s, message + def teardown + SQLCounter.clear_log end def capture_sql @@ -27,7 +39,7 @@ module ActiveRecord ensure failed_patterns = [] patterns_to_match.each do |pattern| - failed_patterns << pattern unless SQLCounter.log_all.any?{ |sql| pattern === sql } + failed_patterns << pattern unless SQLCounter.log_all.any? { |sql| pattern === sql } end assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map(&:inspect).join(', ')} not found.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}" end @@ -89,7 +101,7 @@ module ActiveRecord def clear_log; self.log = []; self.log_all = []; end end - self.clear_log + clear_log self.ignored_sql = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/] @@ -112,16 +124,13 @@ module ActiveRecord end def call(name, start, finish, message_id, values) - sql = values[:sql] - - # FIXME: this seems bad. we should probably have a better way to indicate - # the query was cached - return if 'CACHE' == values[:name] + return if values[:cached] + sql = values[:sql] self.class.log_all << sql - self.class.log << sql unless ignore =~ sql + self.class.log << sql unless ignore.match?(sql) end end - ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new) + ActiveSupport::Notifications.subscribe("sql.active_record", SQLCounter.new) end diff --git a/activerecord/test/cases/test_fixtures_test.rb b/activerecord/test/cases/test_fixtures_test.rb index 1970fe82d0..14a5faa85e 100644 --- a/activerecord/test/cases/test_fixtures_test.rb +++ b/activerecord/test/cases/test_fixtures_test.rb @@ -1,4 +1,4 @@ -require 'cases/helper' +require "cases/helper" class TestFixturesTest < ActiveRecord::TestCase setup do @@ -7,7 +7,7 @@ class TestFixturesTest < ActiveRecord::TestCase end def test_deprecated_use_transactional_fixtures= - assert_deprecated 'use use_transactional_tests= instead' do + assert_deprecated "use use_transactional_tests= instead" do @klass.use_transactional_fixtures = true end end diff --git a/activerecord/test/cases/time_precision_test.rb b/activerecord/test/cases/time_precision_test.rb index 3b6e4dcc2b..03f6c234e8 100644 --- a/activerecord/test/cases/time_precision_test.rb +++ b/activerecord/test/cases/time_precision_test.rb @@ -1,84 +1,83 @@ -require 'cases/helper' -require 'support/schema_dumping_helper' +require "cases/helper" +require "support/schema_dumping_helper" -if ActiveRecord::Base.connection.supports_datetime_with_precision? -class TimePrecisionTest < ActiveRecord::TestCase - include SchemaDumpingHelper - self.use_transactional_tests = false +if subsecond_precision_supported? + class TimePrecisionTest < ActiveRecord::TestCase + include SchemaDumpingHelper + self.use_transactional_tests = false - class Foo < ActiveRecord::Base; end + class Foo < ActiveRecord::Base; end - setup do - @connection = ActiveRecord::Base.connection - Foo.reset_column_information - end - - teardown do - @connection.drop_table :foos, if_exists: true - end + setup do + @connection = ActiveRecord::Base.connection + Foo.reset_column_information + end - def test_time_data_type_with_precision - @connection.create_table(:foos, force: true) - @connection.add_column :foos, :start, :time, precision: 3 - @connection.add_column :foos, :finish, :time, precision: 6 - assert_equal 3, Foo.columns_hash['start'].precision - assert_equal 6, Foo.columns_hash['finish'].precision - end + teardown do + @connection.drop_table :foos, if_exists: true + end - def test_passing_precision_to_time_does_not_set_limit - @connection.create_table(:foos, force: true) do |t| - t.time :start, precision: 3 - t.time :finish, precision: 6 + def test_time_data_type_with_precision + @connection.create_table(:foos, force: true) + @connection.add_column :foos, :start, :time, precision: 3 + @connection.add_column :foos, :finish, :time, precision: 6 + assert_equal 3, Foo.columns_hash["start"].precision + assert_equal 6, Foo.columns_hash["finish"].precision end - assert_nil Foo.columns_hash['start'].limit - assert_nil Foo.columns_hash['finish'].limit - end - def test_invalid_time_precision_raises_error - assert_raises ActiveRecord::ActiveRecordError do + def test_passing_precision_to_time_does_not_set_limit @connection.create_table(:foos, force: true) do |t| - t.time :start, precision: 7 - t.time :finish, precision: 7 + t.time :start, precision: 3 + t.time :finish, precision: 6 end + assert_nil Foo.columns_hash["start"].limit + assert_nil Foo.columns_hash["finish"].limit end - end - def test_formatting_time_according_to_precision - @connection.create_table(:foos, force: true) do |t| - t.time :start, precision: 0 - t.time :finish, precision: 4 + def test_invalid_time_precision_raises_error + assert_raises ActiveRecord::ActiveRecordError do + @connection.create_table(:foos, force: true) do |t| + t.time :start, precision: 7 + t.time :finish, precision: 7 + end + end end - time = ::Time.utc(2000, 1, 1, 12, 30, 0, 999999) - Foo.create!(start: time, finish: time) - assert foo = Foo.find_by(start: time) - assert_equal 1, Foo.where(finish: time).count - assert_equal time.to_s, foo.start.to_s - assert_equal time.to_s, foo.finish.to_s - assert_equal 000000, foo.start.usec - assert_equal 999900, foo.finish.usec - end - def test_schema_dump_includes_time_precision - @connection.create_table(:foos, force: true) do |t| - t.time :start, precision: 4 - t.time :finish, precision: 6 + def test_formatting_time_according_to_precision + @connection.create_table(:foos, force: true) do |t| + t.time :start, precision: 0 + t.time :finish, precision: 4 + end + time = ::Time.utc(2000, 1, 1, 12, 30, 0, 999999) + Foo.create!(start: time, finish: time) + assert foo = Foo.find_by(start: time) + assert_equal 1, Foo.where(finish: time).count + assert_equal time.to_s, foo.start.to_s + assert_equal time.to_s, foo.finish.to_s + assert_equal 000000, foo.start.usec + assert_equal 999900, foo.finish.usec end - output = dump_table_schema("foos") - assert_match %r{t\.time\s+"start",\s+precision: 4$}, output - assert_match %r{t\.time\s+"finish",\s+precision: 6$}, output - end - if current_adapter?(:PostgreSQLAdapter) - def test_time_precision_with_zero_should_be_dumped + def test_schema_dump_includes_time_precision @connection.create_table(:foos, force: true) do |t| - t.time :start, precision: 0 - t.time :finish, precision: 0 + t.time :start, precision: 4 + t.time :finish, precision: 6 end output = dump_table_schema("foos") - assert_match %r{t\.time\s+"start",\s+precision: 0$}, output - assert_match %r{t\.time\s+"finish",\s+precision: 0$}, output + assert_match %r{t\.time\s+"start",\s+precision: 4$}, output + assert_match %r{t\.time\s+"finish",\s+precision: 6$}, output end - end -end + if current_adapter?(:PostgreSQLAdapter) + def test_time_precision_with_zero_should_be_dumped + @connection.create_table(:foos, force: true) do |t| + t.time :start, precision: 0 + t.time :finish, precision: 0 + end + output = dump_table_schema("foos") + assert_match %r{t\.time\s+"start",\s+precision: 0$}, output + assert_match %r{t\.time\s+"finish",\s+precision: 0$}, output + end + end + end end diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb index 970f6bcf4a..cd83518e84 100644 --- a/activerecord/test/cases/timestamp_test.rb +++ b/activerecord/test/cases/timestamp_test.rb @@ -1,12 +1,12 @@ -require 'cases/helper' -require 'support/ddl_helper' -require 'models/developer' -require 'models/computer' -require 'models/owner' -require 'models/pet' -require 'models/toy' -require 'models/car' -require 'models/task' +require "cases/helper" +require "support/ddl_helper" +require "models/developer" +require "models/computer" +require "models/owner" +require "models/pet" +require "models/toy" +require "models/car" +require "models/task" class TimestampTest < ActiveRecord::TestCase fixtures :developers, :owners, :pets, :toys, :cars, :tasks @@ -38,8 +38,8 @@ class TimestampTest < ActiveRecord::TestCase assert_not_equal @previously_updated_at, @developer.updated_at assert_equal previous_salary + 10000, @developer.salary - assert @developer.salary_changed?, 'developer salary should have changed' - assert @developer.changed?, 'developer should be marked as changed' + assert @developer.salary_changed?, "developer salary should have changed" + assert @developer.changed?, "developer should be marked as changed" @developer.reload assert_equal previous_salary, @developer.salary end @@ -74,12 +74,12 @@ class TimestampTest < ActiveRecord::TestCase end def test_touching_updates_timestamp_with_given_time - previously_updated_at = @developer.updated_at - new_time = Time.utc(2015, 2, 16, 0, 0, 0) - @developer.touch(time: new_time) + previously_updated_at = @developer.updated_at + new_time = Time.utc(2015, 2, 16, 0, 0, 0) + @developer.touch(time: new_time) - assert_not_equal previously_updated_at, @developer.updated_at - assert_equal new_time, @developer.updated_at + assert_not_equal previously_updated_at, @developer.updated_at + assert_equal new_time, @developer.updated_at end def test_touching_an_attribute_updates_timestamp @@ -88,8 +88,8 @@ class TimestampTest < ActiveRecord::TestCase @developer.touch(:created_at) end - assert !@developer.created_at_changed? , 'created_at should not be changed' - assert !@developer.changed?, 'record should not be changed' + assert !@developer.created_at_changed? , "created_at should not be changed" + assert !@developer.changed?, "record should not be changed" assert_not_equal previously_created_at, @developer.created_at assert_not_equal @previously_updated_at, @developer.updated_at end @@ -98,14 +98,17 @@ class TimestampTest < ActiveRecord::TestCase task = Task.first previous_value = task.ending task.touch(:ending) + + now = Time.now.change(usec: 0) + assert_not_equal previous_value, task.ending - assert_in_delta Time.now, task.ending, 1 + assert_in_delta now, task.ending, 1 end def test_touching_an_attribute_updates_timestamp_with_given_time - previously_updated_at = @developer.updated_at + previously_updated_at = @developer.updated_at previously_created_at = @developer.created_at - new_time = Time.utc(2015, 2, 16, 4, 54, 0) + new_time = Time.utc(2015, 2, 16, 4, 54, 0) @developer.touch(:created_at, time: new_time) assert_not_equal previously_created_at, @developer.created_at @@ -120,10 +123,12 @@ class TimestampTest < ActiveRecord::TestCase previous_ending = task.ending task.touch(:starting, :ending) + now = Time.now.change(usec: 0) + assert_not_equal previous_starting, task.starting assert_not_equal previous_ending, task.ending - assert_in_delta Time.now, task.starting, 1 - assert_in_delta Time.now, task.ending, 1 + assert_in_delta now, task.starting, 1 + assert_in_delta now, task.ending, 1 end def test_touching_a_record_without_timestamps_is_unexceptional @@ -223,7 +228,7 @@ class TimestampTest < ActiveRecord::TestCase def test_saving_a_new_record_belonging_to_invalid_parent_with_touch_should_not_raise_exception klass = Class.new(Owner) do - def self.name; 'Owner'; end + def self.name; "Owner"; end validate { errors.add(:base, :invalid) } end @@ -235,8 +240,8 @@ class TimestampTest < ActiveRecord::TestCase def test_saving_a_record_with_a_belongs_to_that_specifies_touching_a_specific_attribute_the_parent_should_update_that_attribute klass = Class.new(ActiveRecord::Base) do - def self.name; 'Pet'; end - belongs_to :owner, :touch => :happy_at + def self.name; "Pet"; end + belongs_to :owner, touch: :happy_at end pet = klass.first @@ -251,8 +256,8 @@ class TimestampTest < ActiveRecord::TestCase def test_touching_a_record_with_a_belongs_to_that_uses_a_counter_cache_should_update_the_parent klass = Class.new(ActiveRecord::Base) do - def self.name; 'Pet'; end - belongs_to :owner, :counter_cache => :use_count, :touch => true + def self.name; "Pet"; end + belongs_to :owner, counter_cache: :use_count, touch: true end pet = klass.first @@ -270,8 +275,8 @@ class TimestampTest < ActiveRecord::TestCase def test_touching_a_record_touches_parent_record_and_grandparent_record klass = Class.new(ActiveRecord::Base) do - def self.name; 'Toy'; end - belongs_to :pet, :touch => true + def self.name; "Toy"; end + belongs_to :pet, touch: true end toy = klass.first @@ -288,12 +293,12 @@ class TimestampTest < ActiveRecord::TestCase def test_touching_a_record_touches_polymorphic_record klass = Class.new(ActiveRecord::Base) do - def self.name; 'Toy'; end + def self.name; "Toy"; end end wheel_klass = Class.new(ActiveRecord::Base) do - def self.name; 'Wheel'; end - belongs_to :wheelable, :polymorphic => true, :touch => true + def self.name; "Wheel"; end + belongs_to :wheelable, polymorphic: true, touch: true end toy = klass.first @@ -310,7 +315,7 @@ class TimestampTest < ActiveRecord::TestCase def test_changing_parent_of_a_record_touches_both_new_and_old_parent_record klass = Class.new(ActiveRecord::Base) do - def self.name; 'Toy'; end + def self.name; "Toy"; end belongs_to :pet, touch: true end @@ -336,12 +341,12 @@ class TimestampTest < ActiveRecord::TestCase def test_changing_parent_of_a_record_touches_both_new_and_old_polymorphic_parent_record_changes_within_same_class car_class = Class.new(ActiveRecord::Base) do - def self.name; 'Car'; end + def self.name; "Car"; end end wheel_class = Class.new(ActiveRecord::Base) do - def self.name; 'Wheel'; end - belongs_to :wheelable, :polymorphic => true, :touch => true + def self.name; "Wheel"; end + belongs_to :wheelable, polymorphic: true, touch: true end car1 = car_class.find(1) @@ -363,16 +368,16 @@ class TimestampTest < ActiveRecord::TestCase def test_changing_parent_of_a_record_touches_both_new_and_old_polymorphic_parent_record_changes_with_other_class car_class = Class.new(ActiveRecord::Base) do - def self.name; 'Car'; end + def self.name; "Car"; end end toy_class = Class.new(ActiveRecord::Base) do - def self.name; 'Toy'; end + def self.name; "Toy"; end end wheel_class = Class.new(ActiveRecord::Base) do - def self.name; 'Wheel'; end - belongs_to :wheelable, :polymorphic => true, :touch => true + def self.name; "Wheel"; end + belongs_to :wheelable, polymorphic: true, touch: true end car = car_class.find(1) @@ -394,7 +399,7 @@ class TimestampTest < ActiveRecord::TestCase def test_clearing_association_touches_the_old_record klass = Class.new(ActiveRecord::Base) do - def self.name; 'Toy'; end + def self.name; "Toy"; end belongs_to :pet, touch: true end @@ -414,14 +419,14 @@ class TimestampTest < ActiveRecord::TestCase def test_timestamp_column_values_are_present_in_the_callbacks klass = Class.new(ActiveRecord::Base) do - self.table_name = 'people' + self.table_name = "people" before_create do self.born_at = self.created_at end end - person = klass.create first_name: 'David' + person = klass.create first_name: "David" assert_not_equal person.born_at, nil end @@ -457,11 +462,11 @@ class TimestampTest < ActiveRecord::TestCase def test_index_is_created_for_both_timestamps ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| - t.timestamps(:foos, null: true, index: true) + t.timestamps null: true, index: true end - indexes = ActiveRecord::Base.connection.indexes('foos') - assert_equal ['created_at', 'updated_at'], indexes.flat_map(&:columns).sort + indexes = ActiveRecord::Base.connection.indexes("foos") + assert_equal ["created_at", "updated_at"], indexes.flat_map(&:columns).sort ensure ActiveRecord::Base.connection.drop_table(:foos) end diff --git a/activerecord/test/cases/touch_later_test.rb b/activerecord/test/cases/touch_later_test.rb index b47769eed7..d1e8c649d9 100644 --- a/activerecord/test/cases/touch_later_test.rb +++ b/activerecord/test/cases/touch_later_test.rb @@ -1,9 +1,9 @@ -require 'cases/helper' -require 'models/invoice' -require 'models/line_item' -require 'models/topic' -require 'models/node' -require 'models/tree' +require "cases/helper" +require "models/invoice" +require "models/line_item" +require "models/topic" +require "models/node" +require "models/tree" class TouchLaterTest < ActiveRecord::TestCase fixtures :nodes, :trees @@ -24,6 +24,15 @@ class TouchLaterTest < ActiveRecord::TestCase assert_not invoice.changed? end + def test_touch_later_respects_no_touching_policy + time = Time.now.utc - 25.days + topic = Topic.create!(updated_at: time, created_at: time) + Topic.no_touching do + topic.touch_later + end + assert_equal time.to_i, topic.updated_at.to_i + end + def test_touch_later_update_the_attributes time = Time.now.utc - 25.days topic = Topic.create!(updated_at: time, created_at: time) diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index 8a7f19293d..bd50fe55e9 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -1,7 +1,7 @@ require "cases/helper" -require 'models/owner' -require 'models/pet' -require 'models/topic' +require "models/owner" +require "models/pet" +require "models/topic" class TransactionCallbacksTest < ActiveRecord::TestCase fixtures :topics, :owners, :pets @@ -17,7 +17,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase attr_accessor :save_on_after_create after_create do - self.save! if save_on_after_create + save! if save_on_after_create end def history @@ -68,17 +68,17 @@ class TransactionCallbacksTest < ActiveRecord::TestCase def do_before_commit(on) blocks = @before_commit[on] if defined?(@before_commit) - blocks.each{|b| b.call(self)} if blocks + blocks.each { |b| b.call(self) } if blocks end def do_after_commit(on) blocks = @after_commit[on] if defined?(@after_commit) - blocks.each{|b| b.call(self)} if blocks + blocks.each { |b| b.call(self) } if blocks end def do_after_rollback(on) blocks = @after_rollback[on] if defined?(@after_rollback) - blocks.each{|b| b.call(self)} if blocks + blocks.each { |b| b.call(self) } if blocks end end @@ -88,7 +88,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase # FIXME: Test behavior, not implementation. def test_before_commit_exception_should_pop_transaction_stack - @first.before_commit_block { raise 'better pop this txn from the stack!' } + @first.before_commit_block { raise "better pop this txn from the stack!" } original_txn = @first.class.connection.current_transaction @@ -101,8 +101,8 @@ class TransactionCallbacksTest < ActiveRecord::TestCase end def test_call_after_commit_after_transaction_commits - @first.after_commit_block{|r| r.history << :after_commit} - @first.after_rollback_block{|r| r.history << :after_rollback} + @first.after_commit_block { |r| r.history << :after_commit } + @first.after_rollback_block { |r| r.history << :after_rollback } @first.save! assert_equal [:after_commit], @first.history @@ -123,7 +123,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase end def test_only_call_after_commit_on_create_after_transaction_commits_for_new_record - new_record = TopicWithCallbacks.new(:title => "New topic", :written_on => Date.today) + new_record = TopicWithCallbacks.new(title: "New topic", written_on: Date.today) add_transaction_execution_blocks new_record new_record.save! @@ -131,17 +131,17 @@ class TransactionCallbacksTest < ActiveRecord::TestCase end def test_only_call_after_commit_on_create_after_transaction_commits_for_new_record_if_create_succeeds_creating_through_association - topic = TopicWithCallbacks.create!(:title => "New topic", :written_on => Date.today) + topic = TopicWithCallbacks.create!(title: "New topic", written_on: Date.today) reply = topic.replies.create assert_equal [], reply.history end def test_only_call_after_commit_on_create_and_doesnt_leaky - r = ReplyWithCallbacks.new(content: 'foo') + r = ReplyWithCallbacks.new(content: "foo") r.save_on_after_create = true r.save! - r.content = 'bar' + r.content = "bar" r.save! r.save! assert_equal [:commit_on_create], r.history @@ -155,7 +155,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase end def test_only_call_after_commit_on_top_level_transactions - @first.after_commit_block{|r| r.history << :after_commit} + @first.after_commit_block { |r| r.history << :after_commit } assert @first.history.empty? @first.transaction do @@ -168,8 +168,8 @@ class TransactionCallbacksTest < ActiveRecord::TestCase end def test_call_after_rollback_after_transaction_rollsback - @first.after_commit_block{|r| r.history << :after_commit} - @first.after_rollback_block{|r| r.history << :after_rollback} + @first.after_commit_block { |r| r.history << :after_commit } + @first.after_rollback_block { |r| r.history << :after_rollback } Topic.transaction do @first.save! @@ -213,7 +213,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase end def test_only_call_after_rollback_on_create_after_transaction_rollsback_for_new_record - new_record = TopicWithCallbacks.new(:title => "New topic", :written_on => Date.today) + new_record = TopicWithCallbacks.new(title: "New topic", written_on: Date.today) add_transaction_execution_blocks new_record Topic.transaction do @@ -245,18 +245,18 @@ class TransactionCallbacksTest < ActiveRecord::TestCase def test_only_call_after_rollback_on_records_rolled_back_to_a_savepoint def @first.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end def @first.commits(i=0); @commits ||= 0; @commits += i if i; end - @first.after_rollback_block{|r| r.rollbacks(1)} - @first.after_commit_block{|r| r.commits(1)} + @first.after_rollback_block { |r| r.rollbacks(1) } + @first.after_commit_block { |r| r.commits(1) } second = TopicWithCallbacks.find(3) def second.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end def second.commits(i=0); @commits ||= 0; @commits += i if i; end - second.after_rollback_block{|r| r.rollbacks(1)} - second.after_commit_block{|r| r.commits(1)} + second.after_rollback_block { |r| r.rollbacks(1) } + second.after_commit_block { |r| r.commits(1) } Topic.transaction do @first.save! - Topic.transaction(:requires_new => true) do + Topic.transaction(requires_new: true) do second.save! raise ActiveRecord::Rollback end @@ -272,16 +272,16 @@ class TransactionCallbacksTest < ActiveRecord::TestCase def @first.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end def @first.commits(i=0); @commits ||= 0; @commits += i if i; end - @first.after_rollback_block{|r| r.rollbacks(1)} - @first.after_commit_block{|r| r.commits(1)} + @first.after_rollback_block { |r| r.rollbacks(1) } + @first.after_commit_block { |r| r.commits(1) } Topic.transaction do @first.save - Topic.transaction(:requires_new => true) do + Topic.transaction(requires_new: true) do @first.save! raise ActiveRecord::Rollback end - Topic.transaction(:requires_new => true) do + Topic.transaction(requires_new: true) do @first.save! raise ActiveRecord::Rollback end @@ -292,7 +292,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase end def test_after_commit_callback_should_not_swallow_errors - @first.after_commit_block{ fail "boom" } + @first.after_commit_block { fail "boom" } assert_raises(RuntimeError) do Topic.transaction do @first.save! @@ -303,8 +303,8 @@ class TransactionCallbacksTest < ActiveRecord::TestCase def test_after_commit_callback_when_raise_should_not_restore_state first = TopicWithCallbacks.new second = TopicWithCallbacks.new - first.after_commit_block{ fail "boom" } - second.after_commit_block{ fail "boom" } + first.after_commit_block { fail "boom" } + second.after_commit_block { fail "boom" } begin Topic.transaction do @@ -322,7 +322,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase def test_after_rollback_callback_should_not_swallow_errors_when_set_to_raise error_class = Class.new(StandardError) - @first.after_rollback_block{ raise error_class } + @first.after_rollback_block { raise error_class } assert_raises(error_class) do Topic.transaction do @first.save! @@ -336,8 +336,8 @@ class TransactionCallbacksTest < ActiveRecord::TestCase first = TopicWithCallbacks.new second = TopicWithCallbacks.new - first.after_rollback_block{ raise error_class } - second.after_rollback_block{ raise error_class } + first.after_rollback_block { raise error_class } + second.after_rollback_block { raise error_class } begin Topic.transaction do @@ -355,13 +355,13 @@ class TransactionCallbacksTest < ActiveRecord::TestCase def test_after_rollback_callbacks_should_validate_on_condition assert_raise(ArgumentError) { Topic.after_rollback(on: :save) } - e = assert_raise(ArgumentError) { Topic.after_rollback(on: 'create') } + e = assert_raise(ArgumentError) { Topic.after_rollback(on: "create") } assert_match(/:on conditions for after_commit and after_rollback callbacks have to be one of \[:create, :destroy, :update\]/, e.message) end def test_after_commit_callbacks_should_validate_on_condition assert_raise(ArgumentError) { Topic.after_commit(on: :save) } - e = assert_raise(ArgumentError) { Topic.after_commit(on: 'create') } + e = assert_raise(ArgumentError) { Topic.after_commit(on: "create") } assert_match(/:on conditions for after_commit and after_rollback callbacks have to be one of \[:create, :destroy, :update\]/, e.message) end @@ -449,9 +449,7 @@ class CallbacksOnMultipleActionsTest < ActiveRecord::TestCase end end - class TransactionEnrollmentCallbacksTest < ActiveRecord::TestCase - class TopicWithoutTransactionalEnrollmentCallbacks < ActiveRecord::Base self.table_name = :topics @@ -470,7 +468,7 @@ class TransactionEnrollmentCallbacksTest < ActiveRecord::TestCase def test_commit_does_not_run_transactions_callbacks_without_enrollment @topic.transaction do - @topic.content = 'foo' + @topic.content = "foo" @topic.save! end assert @topic.history.empty? @@ -479,7 +477,7 @@ class TransactionEnrollmentCallbacksTest < ActiveRecord::TestCase def test_commit_run_transactions_callbacks_with_explicit_enrollment @topic.transaction do 2.times do - @topic.content = 'foo' + @topic.content = "foo" @topic.save! end @topic.class.connection.add_transaction_record(@topic) @@ -489,7 +487,7 @@ class TransactionEnrollmentCallbacksTest < ActiveRecord::TestCase def test_rollback_does_not_run_transactions_callbacks_without_enrollment @topic.transaction do - @topic.content = 'foo' + @topic.content = "foo" @topic.save! raise ActiveRecord::Rollback end @@ -499,7 +497,7 @@ class TransactionEnrollmentCallbacksTest < ActiveRecord::TestCase def test_rollback_run_transactions_callbacks_with_explicit_enrollment @topic.transaction do 2.times do - @topic.content = 'foo' + @topic.content = "foo" @topic.save! end @topic.class.connection.add_transaction_record(@topic) diff --git a/activerecord/test/cases/transaction_isolation_test.rb b/activerecord/test/cases/transaction_isolation_test.rb index 2f7d208ed2..58abfadaf4 100644 --- a/activerecord/test/cases/transaction_isolation_test.rb +++ b/activerecord/test/cases/transaction_isolation_test.rb @@ -1,4 +1,4 @@ -require 'cases/helper' +require "cases/helper" unless ActiveRecord::Base.connection.supports_transaction_isolation? class TransactionIsolationUnsupportedTest < ActiveRecord::TestCase @@ -9,7 +9,7 @@ unless ActiveRecord::Base.connection.supports_transaction_isolation? test "setting the isolation level raises an error" do assert_raises(ActiveRecord::TransactionIsolationError) do - Tag.transaction(isolation: :serializable) { } + Tag.transaction(isolation: :serializable) {} end end end @@ -20,11 +20,11 @@ if ActiveRecord::Base.connection.supports_transaction_isolation? self.use_transactional_tests = false class Tag < ActiveRecord::Base - self.table_name = 'tags' + self.table_name = "tags" end class Tag2 < ActiveRecord::Base - self.table_name = 'tags' + self.table_name = "tags" end setup do @@ -63,18 +63,18 @@ if ActiveRecord::Base.connection.supports_transaction_isolation? # We are testing that a nonrepeatable read does not happen if ActiveRecord::Base.connection.transaction_isolation_levels.include?(:repeatable_read) test "repeatable read" do - tag = Tag.create(name: 'jon') + tag = Tag.create(name: "jon") Tag.transaction(isolation: :repeatable_read) do tag.reload - Tag2.find(tag.id).update(name: 'emily') + Tag2.find(tag.id).update(name: "emily") tag.reload - assert_equal 'jon', tag.name + assert_equal "jon", tag.name end tag.reload - assert_equal 'emily', tag.name + assert_equal "emily", tag.name end end @@ -90,7 +90,7 @@ if ActiveRecord::Base.connection.supports_transaction_isolation? test "setting isolation when joining a transaction raises an error" do Tag.transaction do assert_raises(ActiveRecord::TransactionIsolationError) do - Tag.transaction(isolation: :serializable) { } + Tag.transaction(isolation: :serializable) {} end end end @@ -98,7 +98,7 @@ if ActiveRecord::Base.connection.supports_transaction_isolation? test "setting isolation when starting a nested transaction raises error" do Tag.transaction do assert_raises(ActiveRecord::TransactionIsolationError) do - Tag.transaction(requires_new: true, isolation: :serializable) { } + Tag.transaction(requires_new: true, isolation: :serializable) {} end end end diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 791b895d02..834365660f 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -1,12 +1,12 @@ require "cases/helper" -require 'models/topic' -require 'models/reply' -require 'models/developer' -require 'models/computer' -require 'models/book' -require 'models/author' -require 'models/post' -require 'models/movie' +require "models/topic" +require "models/reply" +require "models/developer" +require "models/computer" +require "models/book" +require "models/author" +require "models/post" +require "models/movie" class TransactionTest < ActiveRecord::TestCase self.use_transactional_tests = false @@ -144,7 +144,7 @@ class TransactionTest < ActiveRecord::TestCase def test_raising_exception_in_callback_rollbacks_in_save def @first.after_save_for_transaction - raise 'Make the transaction rollback' + raise "Make the transaction rollback" end @first.approved = true @@ -170,7 +170,7 @@ class TransactionTest < ActiveRecord::TestCase topic = Topic.new def topic.after_save_for_transaction - raise 'Make the transaction rollback' + raise "Make the transaction rollback" end assert_raises(RuntimeError) do @@ -181,7 +181,7 @@ class TransactionTest < ActiveRecord::TestCase end def test_transaction_state_is_cleared_when_record_is_persisted - author = Author.create! name: 'foo' + author = Author.create! name: "foo" author.name = nil assert_not author.save assert_not author.new_record? @@ -230,7 +230,7 @@ class TransactionTest < ActiveRecord::TestCase send("add_cancelling_before_#{filter}_with_db_side_effect_to_topic", @first) nbooks_before_save = Book.count original_author_name = @first.author_name - @first.author_name += '_this_should_not_end_up_in_the_db' + @first.author_name += "_this_should_not_end_up_in_the_db" status = @first.save assert !status assert_equal original_author_name, @first.reload.author_name @@ -241,7 +241,7 @@ class TransactionTest < ActiveRecord::TestCase send("add_cancelling_before_#{filter}_with_db_side_effect_to_topic", @first) nbooks_before_save = Book.count original_author_name = @first.author_name - @first.author_name += '_this_should_not_end_up_in_the_db' + @first.author_name += "_this_should_not_end_up_in_the_db" begin @first.save! @@ -256,18 +256,18 @@ class TransactionTest < ActiveRecord::TestCase def test_callback_rollback_in_create topic = Class.new(Topic) { def after_create_for_transaction - raise 'Make the transaction rollback' + raise "Make the transaction rollback" end } - new_topic = topic.new(:title => "A new topic", - :author_name => "Ben", - :author_email_address => "ben@example.com", - :written_on => "2003-07-16t15:28:11.2233+01:00", - :last_read => "2004-04-15", - :bonus_time => "2005-01-30t15:28:00.00+01:00", - :content => "Have a nice day", - :approved => false) + new_topic = topic.new(title: "A new topic", + author_name: "Ben", + author_email_address: "ben@example.com", + written_on: "2003-07-16t15:28:11.2233+01:00", + last_read: "2004-04-15", + bonus_time: "2005-01-30t15:28:00.00+01:00", + content: "Have a nice day", + approved: false) new_record_snapshot = !new_topic.persisted? id_present = new_topic.has_attribute?(Topic.primary_key) @@ -291,7 +291,7 @@ class TransactionTest < ActiveRecord::TestCase end } - new_topic = topic.create(:title => "A new topic") + new_topic = topic.create(title: "A new topic") assert !new_topic.persisted?, "The topic should not be persisted" assert_nil new_topic.id, "The topic should not have an ID" end @@ -329,7 +329,7 @@ class TransactionTest < ActiveRecord::TestCase def test_invalid_keys_for_transaction assert_raise ArgumentError do - Topic.transaction :nested => true do + Topic.transaction nested: true do end end end @@ -342,7 +342,7 @@ class TransactionTest < ActiveRecord::TestCase @second.save! begin - Topic.transaction :requires_new => true do + Topic.transaction requires_new: true do @first.happy = false @first.save! raise @@ -363,7 +363,7 @@ class TransactionTest < ActiveRecord::TestCase @second.save! begin - @second.transaction :requires_new => true do + @second.transaction requires_new: true do @first.happy = false @first.save! raise @@ -403,17 +403,17 @@ class TransactionTest < ActiveRecord::TestCase @first.save! begin - Topic.transaction :requires_new => true do + Topic.transaction requires_new: true do @first.content = "Two" @first.save! begin - Topic.transaction :requires_new => true do + Topic.transaction requires_new: true do @first.content = "Three" @first.save! begin - Topic.transaction :requires_new => true do + Topic.transaction requires_new: true do @first.content = "Four" @first.save! raise @@ -493,7 +493,7 @@ class TransactionTest < ActiveRecord::TestCase def test_rollback_when_commit_raises assert_called(Topic.connection, :begin_db_transaction) do - Topic.connection.stub(:commit_db_transaction, ->{ raise('OH NOES') }) do + Topic.connection.stub(:commit_db_transaction, -> { raise("OH NOES") }) do assert_called(Topic.connection, :rollback_db_transaction) do e = assert_raise RuntimeError do @@ -501,22 +501,23 @@ class TransactionTest < ActiveRecord::TestCase # do nothing end end - assert_equal 'OH NOES', e.message + assert_equal "OH NOES", e.message end end end end def test_rollback_when_saving_a_frozen_record - topic = Topic.new(:title => 'test') + topic = Topic.new(title: "test") topic.freeze e = assert_raise(RuntimeError) { topic.save } - assert_match(/frozen/i, e.message) # Not good enough, but we can't do much - # about it since there is no specific error - # for frozen objects. - assert !topic.persisted?, 'not persisted' + # Not good enough, but we can't do much + # about it since there is no specific error + # for frozen objects. + assert_match(/frozen/i, e.message) + assert !topic.persisted?, "not persisted" assert_nil topic.id - assert topic.frozen?, 'not frozen' + assert topic.frozen?, "not frozen" end def test_rollback_when_thread_killed @@ -549,12 +550,12 @@ class TransactionTest < ActiveRecord::TestCase def test_restore_active_record_state_for_all_records_in_a_transaction topic_without_callbacks = Class.new(ActiveRecord::Base) do - self.table_name = 'topics' + self.table_name = "topics" end - topic_1 = Topic.new(:title => 'test_1') - topic_2 = Topic.new(:title => 'test_2') - topic_3 = topic_without_callbacks.new(:title => 'test_3') + topic_1 = Topic.new(title: "test_1") + topic_2 = Topic.new(title: "test_2") + topic_3 = topic_without_callbacks.new(title: "test_3") Topic.transaction do assert topic_1.save @@ -562,27 +563,27 @@ class TransactionTest < ActiveRecord::TestCase assert topic_3.save @first.save @second.destroy - assert topic_1.persisted?, 'persisted' + assert topic_1.persisted?, "persisted" assert_not_nil topic_1.id - assert topic_2.persisted?, 'persisted' + assert topic_2.persisted?, "persisted" assert_not_nil topic_2.id - assert topic_3.persisted?, 'persisted' + assert topic_3.persisted?, "persisted" assert_not_nil topic_3.id - assert @first.persisted?, 'persisted' + assert @first.persisted?, "persisted" assert_not_nil @first.id - assert @second.destroyed?, 'destroyed' + assert @second.destroyed?, "destroyed" raise ActiveRecord::Rollback end - assert !topic_1.persisted?, 'not persisted' + assert !topic_1.persisted?, "not persisted" assert_nil topic_1.id - assert !topic_2.persisted?, 'not persisted' + assert !topic_2.persisted?, "not persisted" assert_nil topic_2.id - assert !topic_3.persisted?, 'not persisted' + assert !topic_3.persisted?, "not persisted" assert_nil topic_3.id - assert @first.persisted?, 'persisted' + assert @first.persisted?, "persisted" assert_not_nil @first.id - assert !@second.destroyed?, 'not destroyed' + assert !@second.destroyed?, "not destroyed" end def test_restore_frozen_state_after_double_destroy @@ -606,7 +607,7 @@ class TransactionTest < ActiveRecord::TestCase topic.destroy raise ActiveRecord::Rollback end - assert topic.frozen?, 'frozen' + assert topic.frozen?, "frozen" end def test_rollback_for_freshly_persisted_records @@ -615,7 +616,7 @@ class TransactionTest < ActiveRecord::TestCase topic.destroy raise ActiveRecord::Rollback end - assert topic.persisted?, 'persisted' + assert topic.persisted?, "persisted" end def test_sqlite_add_column_in_transaction @@ -629,27 +630,27 @@ class TransactionTest < ActiveRecord::TestCase assert_nothing_raised do Topic.reset_column_information - Topic.connection.add_column('topics', 'stuff', :string) - assert Topic.column_names.include?('stuff') + Topic.connection.add_column("topics", "stuff", :string) + assert_includes Topic.column_names, "stuff" Topic.reset_column_information - Topic.connection.remove_column('topics', 'stuff') - assert !Topic.column_names.include?('stuff') + Topic.connection.remove_column("topics", "stuff") + assert_not_includes Topic.column_names, "stuff" end if Topic.connection.supports_ddl_transactions? assert_nothing_raised do - Topic.transaction { Topic.connection.add_column('topics', 'stuff', :string) } + Topic.transaction { Topic.connection.add_column("topics", "stuff", :string) } end else Topic.transaction do - assert_raise(ActiveRecord::StatementInvalid) { Topic.connection.add_column('topics', 'stuff', :string) } + assert_raise(ActiveRecord::StatementInvalid) { Topic.connection.add_column("topics", "stuff", :string) } raise ActiveRecord::Rollback end end ensure begin - Topic.connection.remove_column('topics', 'stuff') + Topic.connection.remove_column("topics", "stuff") rescue ensure Topic.reset_column_information @@ -687,12 +688,12 @@ class TransactionTest < ActiveRecord::TestCase def test_transaction_rollback_with_primarykeyless_tables connection = ActiveRecord::Base.connection connection.create_table(:transaction_without_primary_keys, force: true, id: false) do |t| - t.integer :thing_id + t.integer :thing_id end klass = Class.new(ActiveRecord::Base) do - self.table_name = 'transaction_without_primary_keys' - after_commit { } # necessary to trigger the has_transactional_callbacks branch + self.table_name = "transaction_without_primary_keys" + after_commit {} # necessary to trigger the has_transactional_callbacks branch end assert_no_difference(-> { klass.count }) do @@ -702,20 +703,20 @@ class TransactionTest < ActiveRecord::TestCase end end ensure - connection.drop_table 'transaction_without_primary_keys', if_exists: true + connection.drop_table "transaction_without_primary_keys", if_exists: true end private - %w(validation save destroy).each do |filter| - define_method("add_cancelling_before_#{filter}_with_db_side_effect_to_topic") do |topic| - meta = class << topic; self; end - meta.send("define_method", "before_#{filter}_for_transaction") do - Book.create - throw(:abort) + %w(validation save destroy).each do |filter| + define_method("add_cancelling_before_#{filter}_with_db_side_effect_to_topic") do |topic| + meta = class << topic; self; end + meta.send("define_method", "before_#{filter}_for_transaction") do + Book.create + throw(:abort) + end end end - end end class TransactionsWithTransactionalFixturesTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/type/date_time_test.rb b/activerecord/test/cases/type/date_time_test.rb index bc4900e1c2..6848619ece 100644 --- a/activerecord/test/cases/type/date_time_test.rb +++ b/activerecord/test/cases/type/date_time_test.rb @@ -3,7 +3,7 @@ require "models/task" module ActiveRecord module Type - class IntegerTest < ActiveRecord::TestCase + class DateTimeTest < ActiveRecord::TestCase def test_datetime_seconds_precision_applied_to_timestamp skip "This test is invalid if subsecond precision isn't supported" unless subsecond_precision_supported? p = Task.create!(starting: ::Time.now) diff --git a/activerecord/test/cases/type/integer_test.rb b/activerecord/test/cases/type/integer_test.rb index c0932d5357..368b6d7199 100644 --- a/activerecord/test/cases/type/integer_test.rb +++ b/activerecord/test/cases/type/integer_test.rb @@ -6,13 +6,13 @@ module ActiveRecord class IntegerTest < ActiveRecord::TestCase test "casting ActiveRecord models" do type = Type::Integer.new - firm = Firm.create(:name => 'Apple') + firm = Firm.create(name: "Apple") assert_nil type.cast(firm) end test "values which are out of range can be re-assigned" do klass = Class.new(ActiveRecord::Base) do - self.table_name = 'posts' + self.table_name = "posts" attribute :foo, :integer end model = klass.new diff --git a/activerecord/test/cases/type/string_test.rb b/activerecord/test/cases/type/string_test.rb index 6fe6d46711..a95da864fa 100644 --- a/activerecord/test/cases/type/string_test.rb +++ b/activerecord/test/cases/type/string_test.rb @@ -1,21 +1,21 @@ -require 'cases/helper' +require "cases/helper" module ActiveRecord class StringTypeTest < ActiveRecord::TestCase test "string mutations are detected" do klass = Class.new(Base) - klass.table_name = 'authors' + klass.table_name = "authors" - author = klass.create!(name: 'Sean') + author = klass.create!(name: "Sean") assert_not author.changed? - author.name << ' Griffin' + author.name << " Griffin" assert author.name_changed? author.save! author.reload - assert_equal 'Sean Griffin', author.name + assert_equal "Sean Griffin", author.name assert_not author.changed? end end diff --git a/activerecord/test/cases/type/type_map_test.rb b/activerecord/test/cases/type/type_map_test.rb index 172c6dfc4c..2959d36466 100644 --- a/activerecord/test/cases/type/type_map_test.rb +++ b/activerecord/test/cases/type/type_map_test.rb @@ -15,7 +15,7 @@ module ActiveRecord mapping.register_type(/boolean/i, boolean) - assert_equal mapping.lookup('boolean'), boolean + assert_equal mapping.lookup("boolean"), boolean end def test_overriding_registered_types @@ -26,7 +26,7 @@ module ActiveRecord mapping.register_type(/time/i, time) mapping.register_type(/time/i, timestamp) - assert_equal mapping.lookup('time'), timestamp + assert_equal mapping.lookup("time"), timestamp end def test_fuzzy_lookup @@ -35,7 +35,7 @@ module ActiveRecord mapping.register_type(/varchar/i, string) - assert_equal mapping.lookup('varchar(20)'), string + assert_equal mapping.lookup("varchar(20)"), string end def test_aliasing_types @@ -43,9 +43,9 @@ module ActiveRecord mapping = TypeMap.new mapping.register_type(/string/i, string) - mapping.alias_type(/varchar/i, 'string') + mapping.alias_type(/varchar/i, "string") - assert_equal mapping.lookup('varchar'), string + assert_equal mapping.lookup("varchar"), string end def test_changing_type_changes_aliases @@ -54,20 +54,20 @@ module ActiveRecord mapping = TypeMap.new mapping.register_type(/timestamp/i, time) - mapping.alias_type(/datetime/i, 'timestamp') + mapping.alias_type(/datetime/i, "timestamp") mapping.register_type(/timestamp/i, timestamp) - assert_equal mapping.lookup('datetime'), timestamp + assert_equal mapping.lookup("datetime"), timestamp end def test_aliases_keep_metadata mapping = TypeMap.new mapping.register_type(/decimal/i) { |sql_type| sql_type } - mapping.alias_type(/number/i, 'decimal') + mapping.alias_type(/number/i, "decimal") - assert_equal mapping.lookup('number(20)'), 'decimal(20)' - assert_equal mapping.lookup('number'), 'decimal' + assert_equal mapping.lookup("number(20)"), "decimal(20)" + assert_equal mapping.lookup("number"), "decimal" end def test_register_proc @@ -76,15 +76,15 @@ module ActiveRecord mapping = TypeMap.new mapping.register_type(/varchar/i) do |type| - if type.include?('(') + if type.include?("(") string else binary end end - assert_equal mapping.lookup('varchar(20)'), string - assert_equal mapping.lookup('varchar'), binary + assert_equal mapping.lookup("varchar(20)"), string + assert_equal mapping.lookup("varchar"), binary end def test_additional_lookup_args @@ -92,16 +92,16 @@ module ActiveRecord mapping.register_type(/varchar/i) do |type, limit| if limit > 255 - 'text' + "text" else - 'string' + "string" end end - mapping.alias_type(/string/i, 'varchar') + mapping.alias_type(/string/i, "varchar") - assert_equal mapping.lookup('varchar', 200), 'string' - assert_equal mapping.lookup('varchar', 400), 'text' - assert_equal mapping.lookup('string', 400), 'text' + assert_equal mapping.lookup("varchar", 200), "string" + assert_equal mapping.lookup("varchar", 400), "text" + assert_equal mapping.lookup("string", 400), "text" end def test_requires_value_or_block @@ -115,13 +115,13 @@ module ActiveRecord def test_lookup_non_strings mapping = HashLookupTypeMap.new - mapping.register_type(1, 'string') - mapping.register_type(2, 'int') + mapping.register_type(1, "string") + mapping.register_type(2, "int") mapping.alias_type(3, 1) - assert_equal mapping.lookup(1), 'string' - assert_equal mapping.lookup(2), 'int' - assert_equal mapping.lookup(3), 'string' + assert_equal mapping.lookup(1), "string" + assert_equal mapping.lookup(2), "int" + assert_equal mapping.lookup(3), "string" assert_kind_of Type::Value, mapping.lookup(4) end @@ -174,4 +174,3 @@ module ActiveRecord end end end - diff --git a/activerecord/test/cases/types_test.rb b/activerecord/test/cases/types_test.rb index 81fcf04a27..11476ea0ef 100644 --- a/activerecord/test/cases/types_test.rb +++ b/activerecord/test/cases/types_test.rb @@ -9,7 +9,7 @@ module ActiveRecord raise end klass = Class.new(ActiveRecord::Base) do - self.table_name = 'posts' + self.table_name = "posts" attribute :foo, type_which_cannot_go_to_the_database end model = klass.new diff --git a/activerecord/test/cases/validations/absence_validation_test.rb b/activerecord/test/cases/validations/absence_validation_test.rb index dd43ee358c..870619e4e7 100644 --- a/activerecord/test/cases/validations/absence_validation_test.rb +++ b/activerecord/test/cases/validations/absence_validation_test.rb @@ -1,8 +1,8 @@ require "cases/helper" -require 'models/face' -require 'models/interest' -require 'models/man' -require 'models/topic' +require "models/face" +require "models/interest" +require "models/man" +require "models/topic" class AbsenceValidationTest < ActiveRecord::TestCase def test_non_association @@ -52,24 +52,22 @@ class AbsenceValidationTest < ActiveRecord::TestCase end face_with_to_a = Face.new - def face_with_to_a.to_a; ['(/)', '(\)']; end + def face_with_to_a.to_a; ["(/)", '(\)']; end assert_nothing_raised { boy_klass.new(face: face_with_to_a).valid? } end - def test_does_not_validate_if_parent_record_is_validate_false + def test_validates_absence_of_virtual_attribute_on_model repair_validations(Interest) do - Interest.validates_absence_of(:topic) - interest = Interest.new(topic: Topic.new(title: "Math")) - interest.save!(validate: false) - assert interest.persisted? + Interest.send(:attr_accessor, :token) + Interest.validates_absence_of(:token) - man = Man.new(interest_ids: [interest.id]) - man.save! - - assert_equal man.interests.size, 1 + interest = Interest.create!(topic: "Thought Leadering") assert interest.valid? - assert man.valid? + + interest.token = "tl" + + assert interest.invalid? end end end diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb index 584a3dc0d8..66d6ecb928 100644 --- a/activerecord/test/cases/validations/association_validation_test.rb +++ b/activerecord/test/cases/validations/association_validation_test.rb @@ -1,8 +1,8 @@ require "cases/helper" -require 'models/topic' -require 'models/reply' -require 'models/man' -require 'models/interest' +require "models/topic" +require "models/reply" +require "models/man" +require "models/interest" class AssociationValidationTest < ActiveRecord::TestCase fixtures :topics @@ -25,7 +25,7 @@ class AssociationValidationTest < ActiveRecord::TestCase end def test_validates_associated_one - Reply.validates :topic, :associated => true + Reply.validates :topic, associated: true Topic.validates_presence_of( :content ) r = Reply.new("title" => "A reply", "content" => "with content!") r.topic = Topic.create("title" => "uhohuhoh") @@ -58,7 +58,7 @@ class AssociationValidationTest < ActiveRecord::TestCase end def test_validates_associated_with_custom_message_using_quotes - Reply.validates_associated :topic, :message=> "This string contains 'single' and \"double\" quotes" + Reply.validates_associated :topic, message: "This string contains 'single' and \"double\" quotes" Topic.validates_presence_of :content r = Reply.create("title" => "A reply", "content" => "with content!") r.topic = Topic.create("title" => "uhohuhoh") @@ -80,8 +80,8 @@ class AssociationValidationTest < ActiveRecord::TestCase repair_validations(Interest) do # Note that Interest and Man have the :inverse_of option set Interest.validates_presence_of(:man) - man = Man.new(:name => 'John') - interest = man.interests.build(:topic => 'Airplanes') + man = Man.new(name: "John") + interest = man.interests.build(topic: "Airplanes") assert interest.valid?, "Expected interest to be valid, but was not. Interest should have a man object associated" end end @@ -89,8 +89,8 @@ class AssociationValidationTest < ActiveRecord::TestCase def test_validates_presence_of_belongs_to_association__existing_parent repair_validations(Interest) do Interest.validates_presence_of(:man) - man = Man.create!(:name => 'John') - interest = man.interests.build(:topic => 'Airplanes') + man = Man.create!(name: "John") + interest = man.interests.build(topic: "Airplanes") assert interest.valid?, "Expected interest to be valid, but was not. Interest should have a man object associated" end end diff --git a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb index 13d4d85afa..a57065ba75 100644 --- a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'models/topic' +require "models/topic" class I18nGenerateMessageValidationTest < ActiveRecord::TestCase def setup @@ -20,20 +20,20 @@ class I18nGenerateMessageValidationTest < ActiveRecord::TestCase # validates_associated: generate_message(attr_name, :invalid, :message => custom_message, :value => value) def test_generate_message_invalid_with_default_message - assert_equal 'is invalid', @topic.errors.generate_message(:title, :invalid, :value => 'title') + assert_equal "is invalid", @topic.errors.generate_message(:title, :invalid, value: "title") end def test_generate_message_invalid_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :invalid, :message => 'custom message %{value}', :value => 'title') + assert_equal "custom message title", @topic.errors.generate_message(:title, :invalid, message: "custom message %{value}", value: "title") end # validates_uniqueness_of: generate_message(attr_name, :taken, :message => custom_message) def test_generate_message_taken_with_default_message - assert_equal "has already been taken", @topic.errors.generate_message(:title, :taken, :value => 'title') + assert_equal "has already been taken", @topic.errors.generate_message(:title, :taken, value: "title") end def test_generate_message_taken_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :taken, :message => 'custom message %{value}', :value => 'title') + assert_equal "custom message title", @topic.errors.generate_message(:title, :taken, message: "custom message %{value}", value: "title") end # ActiveRecord#RecordInvalid exception @@ -47,7 +47,7 @@ class I18nGenerateMessageValidationTest < ActiveRecord::TestCase test "RecordInvalid exception translation falls back to the :errors namespace" do reset_i18n_load_path do - I18n.backend.store_translations 'en', :errors => {:messages => {:record_invalid => 'fallback message'}} + I18n.backend.store_translations "en", errors: { messages: { record_invalid: "fallback message" } } topic = Topic.new topic.errors.add(:title, :blank) assert_equal "fallback message", ActiveRecord::RecordInvalid.new(topic).message @@ -56,29 +56,29 @@ class I18nGenerateMessageValidationTest < ActiveRecord::TestCase test "translation for 'taken' can be overridden" do reset_i18n_load_path do - I18n.backend.store_translations "en", {errors: {attributes: {title: {taken: "Custom taken message" }}}} - assert_equal "Custom taken message", @topic.errors.generate_message(:title, :taken, :value => 'title') + I18n.backend.store_translations "en", errors: { attributes: { title: { taken: "Custom taken message" } } } + assert_equal "Custom taken message", @topic.errors.generate_message(:title, :taken, value: "title") end end test "translation for 'taken' can be overridden in activerecord scope" do reset_i18n_load_path do - I18n.backend.store_translations "en", {activerecord: {errors: {messages: {taken: "Custom taken message" }}}} - assert_equal "Custom taken message", @topic.errors.generate_message(:title, :taken, :value => 'title') + I18n.backend.store_translations "en", activerecord: { errors: { messages: { taken: "Custom taken message" } } } + assert_equal "Custom taken message", @topic.errors.generate_message(:title, :taken, value: "title") end end test "translation for 'taken' can be overridden in activerecord model scope" do reset_i18n_load_path do - I18n.backend.store_translations "en", {activerecord: {errors: {models: {topic: {taken: "Custom taken message" }}}}} - assert_equal "Custom taken message", @topic.errors.generate_message(:title, :taken, :value => 'title') + I18n.backend.store_translations "en", activerecord: { errors: { models: { topic: { taken: "Custom taken message" } } } } + assert_equal "Custom taken message", @topic.errors.generate_message(:title, :taken, value: "title") end end test "translation for 'taken' can be overridden in activerecord attributes scope" do reset_i18n_load_path do - I18n.backend.store_translations "en", {activerecord: {errors: {models: {topic: {attributes: {title: {taken: "Custom taken message" }}}}}}} - assert_equal "Custom taken message", @topic.errors.generate_message(:title, :taken, :value => 'title') + I18n.backend.store_translations "en", activerecord: { errors: { models: { topic: { attributes: { title: { taken: "Custom taken message" } } } } } } + assert_equal "Custom taken message", @topic.errors.generate_message(:title, :taken, value: "title") end end end diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb index 981239c4d6..fd88a3ea67 100644 --- a/activerecord/test/cases/validations/i18n_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_validation_test.rb @@ -1,6 +1,6 @@ require "cases/helper" -require 'models/topic' -require 'models/reply' +require "models/topic" +require "models/reply" class I18nValidationTest < ActiveRecord::TestCase repair_validations(Topic, Reply) @@ -12,7 +12,7 @@ class I18nValidationTest < ActiveRecord::TestCase @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend I18n.load_path.clear I18n.backend = I18n::Backend::Simple.new - I18n.backend.store_translations('en', :errors => {:messages => {:custom => nil}}) + I18n.backend.store_translations("en", errors: { messages: { custom: nil } }) end teardown do @@ -21,12 +21,12 @@ class I18nValidationTest < ActiveRecord::TestCase end def unique_topic - @unique ||= Topic.create :title => 'unique!' + @unique ||= Topic.create title: "unique!" end def replied_topic @replied_topic ||= begin - topic = Topic.create(:title => "topic") + topic = Topic.create(title: "topic") topic.replies << Reply.new topic end @@ -36,57 +36,48 @@ class I18nValidationTest < ActiveRecord::TestCase # are used to generate tests to keep things DRY # COMMON_CASES = [ - # [ case, validation_options, generate_message_options] + # [ case, validation_options, generate_message_options] [ "given no options", {}, {}], - [ "given custom message", {:message => "custom"}, {:message => "custom"}], - [ "given if condition", {:if => lambda { true }}, {}], - [ "given unless condition", {:unless => lambda { false }}, {}], - [ "given option that is not reserved", {:format => "jpg"}, {:format => "jpg" }] - # TODO Add :on case, but below doesn't work, because then the validation isn't run for some reason - # even when using .save instead .valid? - # [ "given on condition", {on: :save}, {}] + [ "given custom message", { message: "custom" }, { message: "custom" }], + [ "given if condition", { if: lambda { true } }, {}], + [ "given unless condition", { unless: lambda { false } }, {}], + [ "given option that is not reserved", { format: "jpg" }, { format: "jpg" }], + [ "given on condition", { on: [:create, :update] }, {}] ] - # validates_uniqueness_of w/ mocha - COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_uniqueness_of on generated message #{name}" do Topic.validates_uniqueness_of :title, validation_options @topic.title = unique_topic.title - assert_called_with(@topic.errors, :generate_message, [:title, :taken, generate_message_options.merge(:value => 'unique!')]) do + assert_called_with(@topic.errors, :generate_message, [:title, :taken, generate_message_options.merge(value: "unique!")]) do @topic.valid? end end end - # validates_associated w/ mocha - COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_associated on generated message #{name}" do Topic.validates_associated :replies, validation_options - assert_called_with(replied_topic.errors, :generate_message, [:replies, :invalid, generate_message_options.merge(:value => replied_topic.replies)]) do + assert_called_with(replied_topic.errors, :generate_message, [:replies, :invalid, generate_message_options.merge(value: replied_topic.replies)]) do replied_topic.save end end end - # validates_associated w/o mocha - def test_validates_associated_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:replies => {:invalid => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}} + I18n.backend.store_translations "en", activerecord: { errors: { models: { topic: { attributes: { replies: { invalid: "custom message" } } } } } } + I18n.backend.store_translations "en", activerecord: { errors: { messages: { invalid: "global message" } } } Topic.validates_associated :replies replied_topic.valid? - assert_equal ['custom message'], replied_topic.errors[:replies].uniq + assert_equal ["custom message"], replied_topic.errors[:replies].uniq end def test_validates_associated_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}} + I18n.backend.store_translations "en", activerecord: { errors: { messages: { invalid: "global message" } } } Topic.validates_associated :replies replied_topic.valid? - assert_equal ['global message'], replied_topic.errors[:replies] + assert_equal ["global message"], replied_topic.errors[:replies] end - end diff --git a/activerecord/test/cases/validations/length_validation_test.rb b/activerecord/test/cases/validations/length_validation_test.rb index c5d8f8895c..ba45c6dcc1 100644 --- a/activerecord/test/cases/validations/length_validation_test.rb +++ b/activerecord/test/cases/validations/length_validation_test.rb @@ -1,47 +1,46 @@ require "cases/helper" -require 'models/owner' -require 'models/pet' -require 'models/person' +require "models/owner" +require "models/pet" +require "models/person" class LengthValidationTest < ActiveRecord::TestCase fixtures :owners setup do @owner = Class.new(Owner) do - def self.name; 'Owner'; end + def self.name; "Owner"; end end end - def test_validates_size_of_association assert_nothing_raised { @owner.validates_size_of :pets, minimum: 1 } - o = @owner.new('name' => 'nopets') + o = @owner.new("name" => "nopets") assert !o.save assert o.errors[:pets].any? - o.pets.build('name' => 'apet') + o.pets.build("name" => "apet") assert o.valid? end def test_validates_size_of_association_using_within assert_nothing_raised { @owner.validates_size_of :pets, within: 1..2 } - o = @owner.new('name' => 'nopets') + o = @owner.new("name" => "nopets") assert !o.save assert o.errors[:pets].any? - o.pets.build('name' => 'apet') + o.pets.build("name" => "apet") assert o.valid? - 2.times { o.pets.build('name' => 'apet') } + 2.times { o.pets.build("name" => "apet") } assert !o.save assert o.errors[:pets].any? end def test_validates_size_of_association_utf8 @owner.validates_size_of :pets, minimum: 1 - o = @owner.new('name' => 'ã‚ã„ã†ãˆãŠã‹ããã‘ã“') + o = @owner.new("name" => "ã‚ã„ã†ãˆãŠã‹ããã‘ã“") assert !o.save assert o.errors[:pets].any? - o.pets.build('name' => 'ã‚ã„ã†ãˆãŠã‹ããã‘ã“') + o.pets.build("name" => "ã‚ã„ã†ãˆãŠã‹ããã‘ã“") assert o.valid? end @@ -55,23 +54,25 @@ class LengthValidationTest < ActiveRecord::TestCase assert owner.save pet_count = Pet.count - assert_not owner.update_attributes pets_attributes: [ {_destroy: 1, id: pet.id} ] + assert_not owner.update_attributes pets_attributes: [ { _destroy: 1, id: pet.id } ] assert_not owner.valid? assert owner.errors[:pets].any? assert_equal pet_count, Pet.count end - def test_does_not_validate_length_of_if_parent_record_is_validate_false - @owner.validates_length_of :name, minimum: 1 - owner = @owner.new - owner.save!(validate: false) - assert owner.persisted? + def test_validates_length_of_virtual_attribute_on_model + repair_validations(Pet) do + Pet.send(:attr_accessor, :nickname) + Pet.validates_length_of(:name, minimum: 1) + Pet.validates_length_of(:nickname, minimum: 1) - pet = Pet.new(owner_id: owner.id) - pet.save! + pet = Pet.create!(name: "Fancy Pants", nickname: "Fancy") - assert_equal owner.pets.size, 1 - assert owner.valid? - assert pet.valid? + assert pet.valid? + + pet.nickname = "" + + assert pet.invalid? + end end end diff --git a/activerecord/test/cases/validations/presence_validation_test.rb b/activerecord/test/cases/validations/presence_validation_test.rb index 6f8ad06ab6..13956e26ec 100644 --- a/activerecord/test/cases/validations/presence_validation_test.rb +++ b/activerecord/test/cases/validations/presence_validation_test.rb @@ -1,9 +1,9 @@ require "cases/helper" -require 'models/man' -require 'models/face' -require 'models/interest' -require 'models/speedometer' -require 'models/dashboard' +require "models/man" +require "models/face" +require "models/interest" +require "models/speedometer" +require "models/dashboard" class PresenceValidationTest < ActiveRecord::TestCase class Boy < Man; end @@ -57,7 +57,7 @@ class PresenceValidationTest < ActiveRecord::TestCase dash = Dashboard.new # dashboard has to_a method - def dash.to_a; ['(/)', '(\)']; end + def dash.to_a; ["(/)", '(\)']; end s = speedometer.new s.dashboard = dash @@ -65,19 +65,39 @@ class PresenceValidationTest < ActiveRecord::TestCase assert_nothing_raised { s.valid? } end - def test_does_not_validate_presence_of_if_parent_record_is_validate_false + def test_validates_presence_of_virtual_attribute_on_model repair_validations(Interest) do + Interest.send(:attr_accessor, :abbreviation) Interest.validates_presence_of(:topic) + Interest.validates_presence_of(:abbreviation) + + interest = Interest.create!(topic: "Thought Leadering", abbreviation: "tl") + assert interest.valid? + + interest.abbreviation = "" + + assert interest.invalid? + end + end + + def test_validations_run_on_persisted_record + repair_validations(Interest) do interest = Interest.new - interest.save!(validate: false) - assert interest.persisted? + interest.save! + assert_predicate interest, :valid? - man = Man.new(interest_ids: [interest.id]) - man.save! + Interest.validates_presence_of(:topic) - assert_equal man.interests.size, 1 - assert interest.valid? - assert man.valid? + assert_not_predicate interest, :valid? + end + end + + def test_validates_presence_with_on_context + repair_validations(Interest) do + Interest.validates_presence_of(:topic, on: :required_name) + interest = Interest.new + interest.save! + assert_not interest.valid?(:required_name) end end end diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index e601c53dbf..44b4e28777 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -1,10 +1,11 @@ require "cases/helper" -require 'models/topic' -require 'models/reply' -require 'models/warehouse_thing' -require 'models/guid' -require 'models/event' -require 'models/dashboard' +require "models/topic" +require "models/reply" +require "models/warehouse_thing" +require "models/guid" +require "models/event" +require "models/dashboard" +require "models/uuid_item" class Wizard < ActiveRecord::Base self.abstract_class = true @@ -25,7 +26,7 @@ end class ReplyTitle; end class ReplyWithTitleObject < Reply - validates_uniqueness_of :content, :scope => :title + validates_uniqueness_of :content, scope: :title def title; ReplyTitle.new; end end @@ -37,21 +38,33 @@ end class BigIntTest < ActiveRecord::Base INT_MAX_VALUE = 2147483647 - self.table_name = 'cars' + self.table_name = "cars" validates :engines_count, uniqueness: true, inclusion: { in: 0..INT_MAX_VALUE } end class BigIntReverseTest < ActiveRecord::Base INT_MAX_VALUE = 2147483647 - self.table_name = 'cars' + self.table_name = "cars" validates :engines_count, inclusion: { in: 0..INT_MAX_VALUE } validates :engines_count, uniqueness: true end +class CoolTopic < Topic + validates_uniqueness_of :id +end + +class TopicWithAfterCreate < Topic + after_create :set_author + + def set_author + update_attributes!(author_name: "#{title} #{id}") + end +end + class UniquenessValidationTest < ActiveRecord::TestCase INT_MAX_VALUE = 2147483647 - fixtures :topics, 'warehouse-things' + fixtures :topics, "warehouse-things" repair_validations(Topic, Reply) @@ -77,7 +90,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase Topic.alias_attribute :new_title, :title Topic.validates_uniqueness_of(:new_title) - topic = Topic.new(new_title: 'abc') + topic = Topic.new(new_title: "abc") assert topic.valid? end @@ -94,33 +107,33 @@ class UniquenessValidationTest < ActiveRecord::TestCase end def test_validates_uniqueness_with_validates - Topic.validates :title, :uniqueness => true - Topic.create!('title' => 'abc') + Topic.validates :title, uniqueness: true + Topic.create!("title" => "abc") - t2 = Topic.new('title' => 'abc') + t2 = Topic.new("title" => "abc") assert !t2.valid? assert t2.errors[:title] end def test_validate_uniqueness_when_integer_out_of_range entry = BigIntTest.create(engines_count: INT_MAX_VALUE + 1) - assert_equal entry.errors[:engines_count], ['is not included in the list'] + assert_equal entry.errors[:engines_count], ["is not included in the list"] end def test_validate_uniqueness_when_integer_out_of_range_show_order_does_not_matter entry = BigIntReverseTest.create(engines_count: INT_MAX_VALUE + 1) - assert_equal entry.errors[:engines_count], ['is not included in the list'] + assert_equal entry.errors[:engines_count], ["is not included in the list"] end def test_validates_uniqueness_with_newline_chars - Topic.validates_uniqueness_of(:title, :case_sensitive => false) + Topic.validates_uniqueness_of(:title, case_sensitive: false) t = Topic.new("title" => "new\nline") assert t.save, "Should save t as unique" end def test_validate_uniqueness_with_scope - Reply.validates_uniqueness_of(:content, :scope => "parent_id") + Reply.validates_uniqueness_of(:content, scope: "parent_id") t = Topic.create("title" => "I'm unique!") @@ -139,7 +152,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase end def test_validate_uniqueness_with_object_scope - Reply.validates_uniqueness_of(:content, :scope => :topic) + Reply.validates_uniqueness_of(:content, scope: :topic) t = Topic.create("title" => "I'm unique!") @@ -186,7 +199,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase end def test_validate_uniqueness_with_scope_array - Reply.validates_uniqueness_of(:author_name, :scope => [:author_email_address, :parent_id]) + Reply.validates_uniqueness_of(:author_name, scope: [:author_email_address, :parent_id]) t = Topic.create("title" => "The earth is actually flat!") @@ -210,7 +223,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase end def test_validate_case_insensitive_uniqueness - Topic.validates_uniqueness_of(:title, :parent_id, :case_sensitive => false, :allow_nil => true) + Topic.validates_uniqueness_of(:title, :parent_id, case_sensitive: false, allow_nil: true) t = Topic.new("title" => "I'm unique!", :parent_id => 2) assert t.save, "Should save t as unique" @@ -243,7 +256,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase assert t_utf8.save, "Should save t_utf8 as unique" # If database hasn't UTF-8 character set, this test fails - if Topic.all.merge!(:select => 'LOWER(title) AS title').find(t_utf8.id).title == "Ñ Ñ‚Ð¾Ð¶Ðµ уникальный!" + if Topic.all.merge!(select: "LOWER(title) AS title").find(t_utf8.id).title == "Ñ Ñ‚Ð¾Ð¶Ðµ уникальный!" t2_utf8 = Topic.new("title" => "Ñ Ñ‚Ð¾Ð¶Ðµ УÐИКÐЛЬÐЫЙ!") assert !t2_utf8.valid?, "Shouldn't be valid" assert !t2_utf8.save, "Shouldn't save t2_utf8 as unique" @@ -251,7 +264,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase end def test_validate_case_sensitive_uniqueness_with_special_sql_like_chars - Topic.validates_uniqueness_of(:title, :case_sensitive => true) + Topic.validates_uniqueness_of(:title, case_sensitive: true) t = Topic.new("title" => "I'm unique!") assert t.save, "Should save t as unique" @@ -264,7 +277,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase end def test_validate_case_insensitive_uniqueness_with_special_sql_like_chars - Topic.validates_uniqueness_of(:title, :case_sensitive => false) + Topic.validates_uniqueness_of(:title, case_sensitive: false) t = Topic.new("title" => "I'm unique!") assert t.save, "Should save t as unique" @@ -277,7 +290,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase end def test_validate_case_sensitive_uniqueness - Topic.validates_uniqueness_of(:title, :case_sensitive => true, :allow_nil => true) + Topic.validates_uniqueness_of(:title, case_sensitive: true, allow_nil: true) t = Topic.new("title" => "I'm unique!") assert t.save, "Should save t as unique" @@ -301,16 +314,16 @@ class UniquenessValidationTest < ActiveRecord::TestCase end def test_validate_case_sensitive_uniqueness_with_attribute_passed_as_integer - Topic.validates_uniqueness_of(:title, :case_sensitive => true) - Topic.create!('title' => 101) + Topic.validates_uniqueness_of(:title, case_sensitive: true) + Topic.create!("title" => 101) - t2 = Topic.new('title' => 101) + t2 = Topic.new("title" => 101) assert !t2.valid? assert t2.errors[:title] end def test_validate_uniqueness_with_non_standard_table_names - i1 = WarehouseThing.create(:value => 1000) + i1 = WarehouseThing.create(value: 1000) assert !i1.valid?, "i1 should not be valid" assert i1.errors[:value].any?, "Should not be empty" end @@ -318,7 +331,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase def test_validates_uniqueness_inside_scoping Topic.validates_uniqueness_of(:title) - Topic.where(:author_name => "David").scoping do + Topic.where(author_name: "David").scoping do t1 = Topic.new("title" => "I'm unique!", "author_name" => "Mary") assert t1.save t2 = Topic.new("title" => "I'm unique!", "author_name" => "David") @@ -336,45 +349,67 @@ class UniquenessValidationTest < ActiveRecord::TestCase end def test_validate_uniqueness_with_limit - # Event.title is limited to 5 characters - e1 = Event.create(:title => "abcde") - assert e1.valid?, "Could not create an event with a unique, 5 character title" - e2 = Event.create(:title => "abcdefgh") - assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique" + if current_adapter?(:SQLite3Adapter) + # Event.title has limit 5, but SQLite doesn't truncate. + e1 = Event.create(title: "abcdefgh") + assert e1.valid?, "Could not create an event with a unique 8 characters title" + + e2 = Event.create(title: "abcdefgh") + assert_not e2.valid?, "Created an event whose title is not unique" + elsif current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter) + assert_raise(ActiveRecord::ValueTooLong) do + Event.create(title: "abcdefgh") + end + else + assert_raise(ActiveRecord::StatementInvalid) do + Event.create(title: "abcdefgh") + end + end end def test_validate_uniqueness_with_limit_and_utf8 - # Event.title is limited to 5 characters - e1 = Event.create(:title => "一二三四五") - assert e1.valid?, "Could not create an event with a unique, 5 character title" - e2 = Event.create(:title => "一二三四五å…七八") - assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique" + if current_adapter?(:SQLite3Adapter) + # Event.title has limit 5, but does SQLite doesn't truncate. + e1 = Event.create(title: "一二三四五å…七八") + assert e1.valid?, "Could not create an event with a unique 8 characters title" + + e2 = Event.create(title: "一二三四五å…七八") + assert_not e2.valid?, "Created an event whose title is not unique" + elsif current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter) + assert_raise(ActiveRecord::ValueTooLong) do + Event.create(title: "一二三四五å…七八") + end + else + assert_raise(ActiveRecord::StatementInvalid) do + Event.create(title: "一二三四五å…七八") + end + end end def test_validate_straight_inheritance_uniqueness - w1 = IneptWizard.create(:name => "Rincewind", :city => "Ankh-Morpork") + w1 = IneptWizard.create(name: "Rincewind", city: "Ankh-Morpork") assert w1.valid?, "Saving w1" # Should use validation from base class (which is abstract) - w2 = IneptWizard.new(:name => "Rincewind", :city => "Quirm") + w2 = IneptWizard.new(name: "Rincewind", city: "Quirm") assert !w2.valid?, "w2 shouldn't be valid" assert w2.errors[:name].any?, "Should have errors for name" assert_equal ["has already been taken"], w2.errors[:name], "Should have uniqueness message for name" - w3 = Conjurer.new(:name => "Rincewind", :city => "Quirm") + w3 = Conjurer.new(name: "Rincewind", city: "Quirm") assert !w3.valid?, "w3 shouldn't be valid" assert w3.errors[:name].any?, "Should have errors for name" assert_equal ["has already been taken"], w3.errors[:name], "Should have uniqueness message for name" - w4 = Conjurer.create(:name => "The Amazing Bonko", :city => "Quirm") + w4 = Conjurer.create(name: "The Amazing Bonko", city: "Quirm") assert w4.valid?, "Saving w4" - w5 = Thaumaturgist.new(:name => "The Amazing Bonko", :city => "Lancre") + w5 = Thaumaturgist.new(name: "The Amazing Bonko", city: "Lancre") assert !w5.valid?, "w5 shouldn't be valid" assert w5.errors[:name].any?, "Should have errors for name" assert_equal ["has already been taken"], w5.errors[:name], "Should have uniqueness message for name" - w6 = Thaumaturgist.new(:name => "Mustrum Ridcully", :city => "Quirm") + w6 = Thaumaturgist.new(name: "Mustrum Ridcully", city: "Quirm") assert !w6.valid?, "w6 shouldn't be valid" assert w6.errors[:city].any?, "Should have errors for city" assert_equal ["has already been taken"], w6.errors[:city], "Should have uniqueness message for city" @@ -404,7 +439,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase topic = TopicWithUniqEvent.new(event: event) assert_not topic.valid? - assert_equal ['has already been taken'], topic.errors[:event] + assert_equal ["has already been taken"], topic.errors[:event] end def test_validate_uniqueness_on_empty_relation @@ -412,23 +447,6 @@ class UniquenessValidationTest < ActiveRecord::TestCase assert topic.valid? end - def test_does_not_validate_uniqueness_of_if_parent_record_is_validate_false - Reply.validates_uniqueness_of(:content) - - Reply.create!(content: "Topic Title") - - reply = Reply.new(content: "Topic Title") - reply.save!(validate: false) - assert reply.persisted? - - topic = Topic.new(reply_ids: [reply.id]) - topic.save! - - assert_equal topic.replies.size, 1 - assert reply.valid? - assert topic.valid? - end - def test_validate_uniqueness_of_custom_primary_key klass = Class.new(ActiveRecord::Base) do self.table_name = "keyboards" @@ -480,4 +498,35 @@ class UniquenessValidationTest < ActiveRecord::TestCase assert t.valid?, "Should be valid" assert t.save, "Should still save t as unique" end + + def test_validate_uniqueness_with_after_create_performing_save + TopicWithAfterCreate.validates_uniqueness_of(:title) + topic = TopicWithAfterCreate.create!(title: "Title1") + assert topic.author_name.start_with?("Title1") + + topic2 = TopicWithAfterCreate.new(title: "Title1") + refute topic2.valid? + assert_equal(["has already been taken"], topic2.errors[:title]) + end + + def test_validate_uniqueness_uuid + skip unless current_adapter?(:PostgreSQLAdapter) + item = UuidItem.create!(uuid: SecureRandom.uuid, title: "item1") + item.update(title: "item1-title2") + assert_empty item.errors + + item2 = UuidValidatingItem.create!(uuid: SecureRandom.uuid, title: "item2") + item2.update(title: "item2-title2") + assert_empty item2.errors + end + + def test_validate_uniqueness_regular_id + item = CoolTopic.create!(title: "MyItem") + assert_empty item.errors + + item2 = CoolTopic.new(id: item.id, title: "MyItem2") + refute item2.valid? + + assert_equal(["has already been taken"], item2.errors[:id]) + end end diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index d04f4f7ce7..5d9aa99497 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -1,11 +1,11 @@ require "cases/helper" -require 'models/topic' -require 'models/reply' -require 'models/person' -require 'models/developer' -require 'models/computer' -require 'models/parrot' -require 'models/company' +require "models/topic" +require "models/reply" +require "models/person" +require "models/developer" +require "models/computer" +require "models/parrot" +require "models/company" class ValidationsTest < ActiveRecord::TestCase fixtures :topics, :developers @@ -36,7 +36,7 @@ class ValidationsTest < ActiveRecord::TestCase end def test_valid_using_special_context - r = WrongReply.new(:title => "Valid title") + r = WrongReply.new(title: "Valid title") assert !r.valid?(:special_case) assert_equal "Invalid", r.errors[:author_name].join @@ -53,7 +53,7 @@ class ValidationsTest < ActiveRecord::TestCase end def test_invalid_using_multiple_contexts - r = WrongReply.new(:title => 'Wrong Create') + r = WrongReply.new(title: "Wrong Create") assert r.invalid?([:special_case, :create]) assert_equal "Invalid", r.errors[:author_name].join assert_equal "is Wrong Create", r.errors[:title].join @@ -95,7 +95,7 @@ class ValidationsTest < ActiveRecord::TestCase assert_raise(ActiveRecord::RecordInvalid) do WrongReply.new.validate!(:special_case) end - r = WrongReply.new(:title => "Valid title", :author_name => "secret", :content => "Good") + r = WrongReply.new(title: "Valid title", author_name: "secret", content: "Good") assert r.validate!(:special_case) end @@ -107,7 +107,7 @@ class ValidationsTest < ActiveRecord::TestCase def test_exception_on_create_bang_with_block assert_raise(ActiveRecord::RecordInvalid) do - WrongReply.create!({ "title" => "OK" }) do |r| + WrongReply.create!("title" => "OK") do |r| r.content = nil end end @@ -124,13 +124,13 @@ class ValidationsTest < ActiveRecord::TestCase def test_save_without_validation reply = WrongReply.new assert !reply.save - assert reply.save(:validate => false) + assert reply.save(validate: false) end def test_validates_acceptance_of_with_non_existent_table Object.const_set :IncorporealModel, Class.new(ActiveRecord::Base) - assert_nothing_raised ActiveRecord::StatementInvalid do + assert_nothing_raised do IncorporealModel.validates_acceptance_of(:incorporeal_column) end end @@ -156,22 +156,20 @@ class ValidationsTest < ActiveRecord::TestCase end def test_numericality_validation_with_mutation - Topic.class_eval do + klass = Class.new(Topic) do attribute :wibble, :string validates_numericality_of :wibble, only_integer: true end - topic = Topic.new(wibble: '123-4567') - topic.wibble.gsub!('-', '') + topic = klass.new(wibble: "123-4567") + topic.wibble.gsub!("-", "") assert topic.valid? - ensure - Topic.reset_column_information end def test_acceptance_validator_doesnt_require_db_connection klass = Class.new(ActiveRecord::Base) do - self.table_name = 'posts' + self.table_name = "posts" end klass.reset_column_information diff --git a/activerecord/test/cases/view_test.rb b/activerecord/test/cases/view_test.rb index f3c2d2f30e..1f326d4b39 100644 --- a/activerecord/test/cases/view_test.rb +++ b/activerecord/test/cases/view_test.rb @@ -11,20 +11,21 @@ module ViewBehavior end class Ebook < ActiveRecord::Base + self.table_name = "ebooks'" self.primary_key = "id" end def setup super @connection = ActiveRecord::Base.connection - create_view "ebooks", <<-SQL + create_view "ebooks'", <<-SQL SELECT id, name, status FROM books WHERE format = 'ebook' SQL end def teardown super - drop_view "ebooks" + drop_view "ebooks'" end def test_reading @@ -60,157 +61,162 @@ module ViewBehavior end def test_attributes - assert_equal({"id" => 2, "name" => "Ruby for Rails", "status" => 0}, + assert_equal({ "id" => 2, "name" => "Ruby for Rails", "status" => 0 }, Ebook.first.attributes) end def test_does_not_assume_id_column_as_primary_key model = Class.new(ActiveRecord::Base) do - self.table_name = "ebooks" + self.table_name = "ebooks'" end assert_nil model.primary_key end def test_does_not_dump_view_as_table - schema = dump_table_schema "ebooks" - assert_no_match %r{create_table "ebooks"}, schema + schema = dump_table_schema "ebooks'" + assert_no_match %r{create_table "ebooks'"}, schema end -end - -if ActiveRecord::Base.connection.supports_views? -class ViewWithPrimaryKeyTest < ActiveRecord::TestCase - include ViewBehavior private - def create_view(name, query) - @connection.execute "CREATE VIEW #{name} AS #{query}" - end - - def drop_view(name) - @connection.execute "DROP VIEW #{name}" if @connection.view_exists? name - end + def quote_table_name(name) + @connection.quote_table_name(name) + end end -class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase - include SchemaDumpingHelper - fixtures :books - - class Paperback < ActiveRecord::Base; end - - setup do - @connection = ActiveRecord::Base.connection - @connection.execute <<-SQL - CREATE VIEW paperbacks - AS SELECT name, status FROM books WHERE format = 'paperback' - SQL - end - - teardown do - @connection.execute "DROP VIEW paperbacks" if @connection.view_exists? "paperbacks" - end +if ActiveRecord::Base.connection.supports_views? + class ViewWithPrimaryKeyTest < ActiveRecord::TestCase + include ViewBehavior - def test_reading - books = Paperback.all - assert_equal ["Agile Web Development with Rails"], books.map(&:name) - end + private + def create_view(name, query) + @connection.execute "CREATE VIEW #{quote_table_name(name)} AS #{query}" + end - def test_views - assert_equal [Paperback.table_name], @connection.views - end - - def test_view_exists - view_name = Paperback.table_name - assert @connection.view_exists?(view_name), "'#{view_name}' view should exist" + def drop_view(name) + @connection.execute "DROP VIEW #{quote_table_name(name)}" if @connection.view_exists? name + end end - 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" } - end + class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase + include SchemaDumpingHelper + fixtures :books - def test_column_definitions - assert_equal([["name", :string], - ["status", :integer]], Paperback.columns.map { |c| [c.name, c.type] }) - end + class Paperback < ActiveRecord::Base; end - def test_attributes - assert_equal({"name" => "Agile Web Development with Rails", "status" => 2}, - Paperback.first.attributes) - end + setup do + @connection = ActiveRecord::Base.connection + @connection.execute <<-SQL + CREATE VIEW paperbacks + AS SELECT name, status FROM books WHERE format = 'paperback' + SQL + end - def test_does_not_have_a_primary_key - assert_nil Paperback.primary_key - end + teardown do + @connection.execute "DROP VIEW paperbacks" if @connection.view_exists? "paperbacks" + end - def test_does_not_dump_view_as_table - schema = dump_table_schema "paperbacks" - assert_no_match %r{create_table "paperbacks"}, schema - end -end + def test_reading + books = Paperback.all + assert_equal ["Agile Web Development with Rails"], books.map(&:name) + end -# sqlite dose not support CREATE, INSERT, and DELETE for VIEW -if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter) -class UpdateableViewTest < ActiveRecord::TestCase - self.use_transactional_tests = false - fixtures :books + def test_views + assert_equal [Paperback.table_name], @connection.views + end - class PrintedBook < ActiveRecord::Base - self.primary_key = "id" - end + def test_view_exists + view_name = Paperback.table_name + assert @connection.view_exists?(view_name), "'#{view_name}' view should exist" + end - setup do - @connection = ActiveRecord::Base.connection - @connection.execute <<-SQL - CREATE VIEW printed_books - AS SELECT id, name, status, format FROM books WHERE format = 'paperback' - SQL - end + 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" } + end - teardown do - @connection.execute "DROP VIEW printed_books" if @connection.view_exists? "printed_books" - end + def test_column_definitions + assert_equal([["name", :string], + ["status", :integer]], Paperback.columns.map { |c| [c.name, c.type] }) + end - def test_update_record - book = PrintedBook.first - book.name = "AWDwR" - book.save! - book.reload - assert_equal "AWDwR", book.name - end + def test_attributes + assert_equal({ "name" => "Agile Web Development with Rails", "status" => 2 }, + Paperback.first.attributes) + end - def test_insert_record - PrintedBook.create! name: "Rails in Action", status: 0, format: "paperback" + def test_does_not_have_a_primary_key + assert_nil Paperback.primary_key + end - new_book = PrintedBook.last - assert_equal "Rails in Action", new_book.name + def test_does_not_dump_view_as_table + schema = dump_table_schema "paperbacks" + assert_no_match %r{create_table "paperbacks"}, schema + end end - def test_update_record_to_fail_view_conditions - book = PrintedBook.first - book.format = "ebook" - book.save! - - assert_raises ActiveRecord::RecordNotFound do - book.reload + # sqlite dose not support CREATE, INSERT, and DELETE for VIEW + if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter) + class UpdateableViewTest < ActiveRecord::TestCase + self.use_transactional_tests = false + fixtures :books + + class PrintedBook < ActiveRecord::Base + self.primary_key = "id" + end + + setup do + @connection = ActiveRecord::Base.connection + @connection.execute <<-SQL + CREATE VIEW printed_books + AS SELECT id, name, status, format FROM books WHERE format = 'paperback' + SQL + end + + teardown do + @connection.execute "DROP VIEW printed_books" if @connection.view_exists? "printed_books" + end + + def test_update_record + book = PrintedBook.first + book.name = "AWDwR" + book.save! + book.reload + assert_equal "AWDwR", book.name + end + + def test_insert_record + PrintedBook.create! name: "Rails in Action", status: 0, format: "paperback" + + new_book = PrintedBook.last + assert_equal "Rails in Action", new_book.name + end + + def test_update_record_to_fail_view_conditions + book = PrintedBook.first + book.format = "ebook" + book.save! + + assert_raises ActiveRecord::RecordNotFound do + book.reload + end + end end - end -end -end # end fo `if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)` + end # end fo `if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)` end # end fo `if ActiveRecord::Base.connection.supports_views?` if ActiveRecord::Base.connection.respond_to?(:supports_materialized_views?) && ActiveRecord::Base.connection.supports_materialized_views? -class MaterializedViewTest < ActiveRecord::PostgreSQLTestCase - include ViewBehavior + class MaterializedViewTest < ActiveRecord::PostgreSQLTestCase + include ViewBehavior - private - def create_view(name, query) - @connection.execute "CREATE MATERIALIZED VIEW #{name} AS #{query}" - end + private + def create_view(name, query) + @connection.execute "CREATE MATERIALIZED VIEW #{quote_table_name(name)} AS #{query}" + end - def drop_view(name) - @connection.execute "DROP MATERIALIZED VIEW #{name}" if @connection.view_exists? name + def drop_view(name) + @connection.execute "DROP MATERIALIZED VIEW #{quote_table_name(name)}" if @connection.view_exists? name + end end end -end diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb index 56909a8630..5192e5050a 100644 --- a/activerecord/test/cases/yaml_serialization_test.rb +++ b/activerecord/test/cases/yaml_serialization_test.rb @@ -1,15 +1,15 @@ -require 'cases/helper' -require 'models/topic' -require 'models/reply' -require 'models/post' -require 'models/author' +require "cases/helper" +require "models/topic" +require "models/reply" +require "models/post" +require "models/author" class YamlSerializationTest < ActiveRecord::TestCase fixtures :topics, :authors, :posts def test_to_yaml_with_time_with_zone_should_not_raise_exception with_timezone_config aware_attributes: true, zone: "Pacific Time (US & Canada)" do - topic = Topic.new(:written_on => DateTime.now) + topic = Topic.new(written_on: DateTime.now) assert_nothing_raised { topic.to_yaml } end end @@ -22,8 +22,8 @@ class YamlSerializationTest < ActiveRecord::TestCase end def test_roundtrip_serialized_column - topic = Topic.new(:content => {:omg=>:lol}) - assert_equal({:omg=>:lol}, YAML.load(YAML.dump(topic)).content) + topic = Topic.new(content: { omg: :lol }) + assert_equal({ omg: :lol }, YAML.load(YAML.dump(topic)).content) end def test_psych_roundtrip @@ -67,16 +67,16 @@ class YamlSerializationTest < ActiveRecord::TestCase assert_not topic.new_record?, "Saved records are not new" assert_not YAML.load(YAML.dump(topic)).new_record?, "Saved record should not be new after deserialization" - topic = Topic.select('title').last + topic = Topic.select("title").last assert_not topic.new_record?, "Loaded records without ID are not new" assert_not YAML.load(YAML.dump(topic)).new_record?, "Record should not be new after deserialization" end def test_types_of_virtual_columns_are_not_changed_on_round_trip - author = Author.select('authors.*, count(posts.id) as posts_count') + author = Author.select("authors.*, count(posts.id) as posts_count") .joins(:posts) - .group('authors.id') + .group("authors.id") .first dumped = YAML.load(YAML.dump(author)) @@ -88,7 +88,7 @@ class YamlSerializationTest < ActiveRecord::TestCase coder = {} Topic.first.encode_with(coder) - assert coder['active_record_yaml_version'] + assert coder["active_record_yaml_version"] end def test_deserializing_rails_41_yaml @@ -109,13 +109,23 @@ class YamlSerializationTest < ActiveRecord::TestCase assert_equal("Have a nice day", topic.content) end - private + def test_yaml_encoding_keeps_mutations + author = Author.first + author.name = "Sean" + dumped = YAML.load(YAML.dump(author)) - def yaml_fixture(file_name) - path = File.expand_path( - "../../support/yaml_compatibility_fixtures/#{file_name}.yml", - __FILE__ - ) - File.read(path) + assert_equal "Sean", dumped.name + assert_equal author.name_was, dumped.name_was + assert_equal author.changes, dumped.changes end + + private + + def yaml_fixture(file_name) + path = File.expand_path( + "../../support/yaml_compatibility_fixtures/#{file_name}.yml", + __FILE__ + ) + File.read(path) + end end diff --git a/activerecord/test/fixtures/all/namespaced/accounts.yml b/activerecord/test/fixtures/all/namespaced/accounts.yml new file mode 100644 index 0000000000..9e341a15af --- /dev/null +++ b/activerecord/test/fixtures/all/namespaced/accounts.yml @@ -0,0 +1,2 @@ +signals37: + name: 37signals diff --git a/activerecord/test/fixtures/books.yml b/activerecord/test/fixtures/books.yml index a304fba399..b3625ee72e 100644 --- a/activerecord/test/fixtures/books.yml +++ b/activerecord/test/fixtures/books.yml @@ -9,6 +9,7 @@ awdr: author_visibility: :visible illustrator_visibility: :visible font_size: :medium + difficulty: :medium rfr: author_id: 1 diff --git a/activerecord/test/fixtures/price_estimates.yml b/activerecord/test/fixtures/price_estimates.yml index 1149ab17a2..406d65a142 100644 --- a/activerecord/test/fixtures/price_estimates.yml +++ b/activerecord/test/fixtures/price_estimates.yml @@ -1,7 +1,16 @@ -saphire_1: +sapphire_1: price: 10 estimate_of: sapphire (Treasure) sapphire_2: price: 20 estimate_of: sapphire (Treasure) + +diamond: + price: 30 + estimate_of: diamond (Treasure) + +honda: + price: 40 + estimate_of_type: Car + estimate_of_id: 1 diff --git a/activerecord/test/migrations/decimal/1_give_me_big_numbers.rb b/activerecord/test/migrations/decimal/1_give_me_big_numbers.rb index 549647de86..43c79bc20b 100644 --- a/activerecord/test/migrations/decimal/1_give_me_big_numbers.rb +++ b/activerecord/test/migrations/decimal/1_give_me_big_numbers.rb @@ -1,10 +1,10 @@ class GiveMeBigNumbers < ActiveRecord::Migration::Current def self.up create_table :big_numbers do |table| - table.column :bank_balance, :decimal, :precision => 10, :scale => 2 - table.column :big_bank_balance, :decimal, :precision => 15, :scale => 2 - table.column :world_population, :decimal, :precision => 10 - table.column :my_house_population, :decimal, :precision => 2 + table.column :bank_balance, :decimal, precision: 10, scale: 2 + table.column :big_bank_balance, :decimal, precision: 15, scale: 2 + table.column :world_population, :decimal, precision: 10 + table.column :my_house_population, :decimal, precision: 2 table.column :value_of_e, :decimal end end diff --git a/activerecord/test/migrations/magic/1_currencies_have_symbols.rb b/activerecord/test/migrations/magic/1_currencies_have_symbols.rb index 53b263bf55..d4b0e6cd95 100644 --- a/activerecord/test/migrations/magic/1_currencies_have_symbols.rb +++ b/activerecord/test/migrations/magic/1_currencies_have_symbols.rb @@ -2,8 +2,8 @@ class CurrenciesHaveSymbols < ActiveRecord::Migration::Current def self.up - # We use ¤ for default currency symbol - add_column "currencies", "symbol", :string, :default => "¤" + # We use € for default currency symbol + add_column "currencies", "symbol", :string, default: "€" end def self.down diff --git a/activerecord/test/migrations/missing/4_innocent_jointable.rb b/activerecord/test/migrations/missing/4_innocent_jointable.rb index 20fe183777..bd3bf21576 100644 --- a/activerecord/test/migrations/missing/4_innocent_jointable.rb +++ b/activerecord/test/migrations/missing/4_innocent_jointable.rb @@ -1,6 +1,6 @@ class InnocentJointable < ActiveRecord::Migration::Current def self.up - create_table("people_reminders", :id => false) do |t| + create_table("people_reminders", id: false) do |t| t.column :reminder_id, :integer t.column :person_id, :integer end diff --git a/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb b/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb index 607113b091..76734bcd7d 100644 --- a/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb +++ b/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb @@ -1,4 +1,4 @@ -class PeopleHaveLastNames < ActiveRecord::Migration::Current +class PeopleHaveHobbies < ActiveRecord::Migration::Current def self.up add_column "people", "hobbies", :text end diff --git a/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb b/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb index d4cbddab50..7f883dbb45 100644 --- a/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb +++ b/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb @@ -1,4 +1,4 @@ -class PeopleHaveLastNames < ActiveRecord::Migration::Current +class PeopleHaveDescriptions < ActiveRecord::Migration::Current def self.up add_column "people", "description", :text end diff --git a/activerecord/test/migrations/to_copy2/2_create_comments.rb b/activerecord/test/migrations/to_copy2/2_create_comments.rb index 2e9f5ec6bc..d361847d4b 100644 --- a/activerecord/test/migrations/to_copy2/2_create_comments.rb +++ b/activerecord/test/migrations/to_copy2/2_create_comments.rb @@ -1,4 +1,4 @@ -class CreateArticles < ActiveRecord::Migration::Current +class CreateComments < ActiveRecord::Migration::Current def self.up end diff --git a/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb b/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb index 8f81805fe1..1a863367dd 100644 --- a/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb +++ b/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb @@ -1,4 +1,4 @@ -class PeopleHaveLastNames < ActiveRecord::Migration::Current +class PeopleHaveHobbies < ActiveRecord::Migration::Current def self.up add_column "people", "hobbies", :string end diff --git a/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb b/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb index 607113b091..76734bcd7d 100644 --- a/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb +++ b/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb @@ -1,4 +1,4 @@ -class PeopleHaveLastNames < ActiveRecord::Migration::Current +class PeopleHaveHobbies < ActiveRecord::Migration::Current def self.up add_column "people", "hobbies", :text end diff --git a/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb b/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb index d4cbddab50..7f883dbb45 100644 --- a/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb +++ b/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb @@ -1,4 +1,4 @@ -class PeopleHaveLastNames < ActiveRecord::Migration::Current +class PeopleHaveDescriptions < ActiveRecord::Migration::Current def self.up add_column "people", "description", :text end diff --git a/activerecord/test/migrations/valid/3_innocent_jointable.rb b/activerecord/test/migrations/valid/3_innocent_jointable.rb index 20fe183777..bd3bf21576 100644 --- a/activerecord/test/migrations/valid/3_innocent_jointable.rb +++ b/activerecord/test/migrations/valid/3_innocent_jointable.rb @@ -1,6 +1,6 @@ class InnocentJointable < ActiveRecord::Migration::Current def self.up - create_table("people_reminders", :id => false) do |t| + create_table("people_reminders", id: false) do |t| t.column :reminder_id, :integer t.column :person_id, :integer end diff --git a/activerecord/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb b/activerecord/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb index 20fe183777..bd3bf21576 100644 --- a/activerecord/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb +++ b/activerecord/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb @@ -1,6 +1,6 @@ class InnocentJointable < ActiveRecord::Migration::Current def self.up - create_table("people_reminders", :id => false) do |t| + create_table("people_reminders", id: false) do |t| t.column :reminder_id, :integer t.column :person_id, :integer end diff --git a/activerecord/test/migrations/valid_with_timestamps/20100301010101_valid_with_timestamps_innocent_jointable.rb b/activerecord/test/migrations/valid_with_timestamps/20100301010101_valid_with_timestamps_innocent_jointable.rb index bf934576c9..be24de6d70 100644 --- a/activerecord/test/migrations/valid_with_timestamps/20100301010101_valid_with_timestamps_innocent_jointable.rb +++ b/activerecord/test/migrations/valid_with_timestamps/20100301010101_valid_with_timestamps_innocent_jointable.rb @@ -1,6 +1,6 @@ class ValidWithTimestampsInnocentJointable < ActiveRecord::Migration::Current def self.up - create_table("people_reminders", :id => false) do |t| + create_table("people_reminders", id: false) do |t| t.column :reminder_id, :integer t.column :person_id, :integer end diff --git a/activerecord/test/models/admin.rb b/activerecord/test/models/admin.rb index a38e3f4846..bc3ce23447 100644 --- a/activerecord/test/models/admin.rb +++ b/activerecord/test/models/admin.rb @@ -1,5 +1,5 @@ module Admin def self.table_name_prefix - 'admin_' + "admin_" end end diff --git a/activerecord/test/models/admin/user.rb b/activerecord/test/models/admin/user.rb index 48a110bd23..2e703f6219 100644 --- a/activerecord/test/models/admin/user.rb +++ b/activerecord/test/models/admin/user.rb @@ -15,26 +15,26 @@ class Admin::User < ActiveRecord::Base belongs_to :account store :params, accessors: [ :token ], coder: YAML - store :settings, :accessors => [ :color, :homepage ] + store :settings, accessors: [ :color, :homepage ] store_accessor :settings, :favorite_food - store :preferences, :accessors => [ :remember_login ] - store :json_data, :accessors => [ :height, :weight ], :coder => Coder.new - store :json_data_empty, :accessors => [ :is_a_good_guy ], :coder => Coder.new + store :preferences, accessors: [ :remember_login ] + store :json_data, accessors: [ :height, :weight ], coder: Coder.new + store :json_data_empty, accessors: [ :is_a_good_guy ], coder: Coder.new def phone_number read_store_attribute(:settings, :phone_number).gsub(/(\d{3})(\d{3})(\d{4})/,'(\1) \2-\3') end def phone_number=(value) - write_store_attribute(:settings, :phone_number, value && value.gsub(/[^\d]/,'')) + write_store_attribute(:settings, :phone_number, value && value.gsub(/[^\d]/,"")) end def color - super || 'red' + super || "red" end def color=(value) - value = 'blue' unless %w(black red green blue).include?(value) + value = "blue" unless %w(black red green blue).include?(value) super end end diff --git a/activerecord/test/models/aircraft.rb b/activerecord/test/models/aircraft.rb index c4404a8094..ebd42ff824 100644 --- a/activerecord/test/models/aircraft.rb +++ b/activerecord/test/models/aircraft.rb @@ -1,5 +1,5 @@ class Aircraft < ActiveRecord::Base self.pluralize_table_names = false - has_many :engines, :foreign_key => "car_id" + has_many :engines, foreign_key: "car_id" has_many :wheels, as: :wheelable end diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index f25e31b13d..fab613afd1 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -2,148 +2,155 @@ class Author < ActiveRecord::Base has_many :posts has_many :serialized_posts has_one :post - has_many :very_special_comments, :through => :posts - has_many :posts_with_comments, -> { includes(:comments) }, :class_name => "Post" - has_many :popular_grouped_posts, -> { includes(:comments).group("type").having("SUM(comments_count) > 1").select("type") }, :class_name => "Post" - has_many :posts_with_comments_sorted_by_comment_id, -> { includes(:comments).order('comments.id') }, :class_name => "Post" - has_many :posts_sorted_by_id_limited, -> { order('posts.id').limit(1) }, :class_name => "Post" - has_many :posts_with_categories, -> { includes(:categories) }, :class_name => "Post" - has_many :posts_with_comments_and_categories, -> { includes(:comments, :categories).order("posts.id") }, :class_name => "Post" - has_many :posts_with_special_categorizations, :class_name => 'PostWithSpecialCategorization' - has_one :post_about_thinking, -> { where("posts.title like '%thinking%'") }, :class_name => 'Post' - has_one :post_about_thinking_with_last_comment, -> { where("posts.title like '%thinking%'").includes(:last_comment) }, :class_name => 'Post' + has_many :very_special_comments, through: :posts + has_many :posts_with_comments, -> { includes(:comments) }, class_name: "Post" + has_many :popular_grouped_posts, -> { includes(:comments).group("type").having("SUM(comments_count) > 1").select("type") }, class_name: "Post" + has_many :posts_with_comments_sorted_by_comment_id, -> { includes(:comments).order("comments.id") }, class_name: "Post" + has_many :posts_sorted_by_id_limited, -> { order("posts.id").limit(1) }, class_name: "Post" + has_many :posts_with_categories, -> { includes(:categories) }, class_name: "Post" + has_many :posts_with_comments_and_categories, -> { includes(:comments, :categories).order("posts.id") }, class_name: "Post" + has_many :posts_with_special_categorizations, class_name: "PostWithSpecialCategorization" + has_one :post_about_thinking, -> { where("posts.title like '%thinking%'") }, class_name: "Post" + has_one :post_about_thinking_with_last_comment, -> { where("posts.title like '%thinking%'").includes(:last_comment) }, class_name: "Post" has_many :comments, through: :posts do def ratings Rating.joins(:comment).merge(self) end end - has_many :comments_containing_the_letter_e, :through => :posts, :source => :comments - has_many :comments_with_order_and_conditions, -> { order('comments.body').where("comments.body like 'Thank%'") }, :through => :posts, :source => :comments - has_many :comments_with_include, -> { includes(:post) }, :through => :posts, :source => :comments + has_many :comments_containing_the_letter_e, through: :posts, source: :comments + has_many :comments_with_order_and_conditions, -> { order("comments.body").where("comments.body like 'Thank%'") }, through: :posts, source: :comments + has_many :comments_with_include, -> { includes(:post) }, through: :posts, source: :comments has_many :first_posts - has_many :comments_on_first_posts, -> { order('posts.id desc, comments.id asc') }, :through => :first_posts, :source => :comments + has_many :comments_on_first_posts, -> { order("posts.id desc, comments.id asc") }, through: :first_posts, source: :comments has_one :first_post - has_one :comment_on_first_post, -> { order('posts.id desc, comments.id asc') }, :through => :first_post, :source => :comments + has_one :comment_on_first_post, -> { order("posts.id desc, comments.id asc") }, through: :first_post, source: :comments - has_many :thinking_posts, -> { where(:title => 'So I was thinking') }, :dependent => :delete_all, :class_name => 'Post' - has_many :welcome_posts, -> { where(:title => 'Welcome to the weblog') }, :class_name => 'Post' + has_many :thinking_posts, -> { where(title: "So I was thinking") }, dependent: :delete_all, class_name: "Post" + has_many :welcome_posts, -> { where(title: "Welcome to the weblog") }, class_name: "Post" has_many :welcome_posts_with_one_comment, - -> { where(title: 'Welcome to the weblog').where('comments_count = ?', 1) }, - class_name: 'Post' + -> { where(title: "Welcome to the weblog").where("comments_count = ?", 1) }, + class_name: "Post" has_many :welcome_posts_with_comments, - -> { where(title: 'Welcome to the weblog').where(Post.arel_table[:comments_count].gt(0)) }, - class_name: 'Post' + -> { where(title: "Welcome to the weblog").where(Post.arel_table[:comments_count].gt(0)) }, + class_name: "Post" - has_many :comments_desc, -> { order('comments.id DESC') }, :through => :posts, :source => :comments - has_many :funky_comments, :through => :posts, :source => :comments - has_many :ordered_uniq_comments, -> { distinct.order('comments.id') }, :through => :posts, :source => :comments - has_many :ordered_uniq_comments_desc, -> { distinct.order('comments.id DESC') }, :through => :posts, :source => :comments - has_many :readonly_comments, -> { readonly }, :through => :posts, :source => :comments + has_many :comments_desc, -> { order("comments.id DESC") }, through: :posts, source: :comments + has_many :funky_comments, through: :posts, source: :comments + has_many :ordered_uniq_comments, -> { distinct.order("comments.id") }, through: :posts, source: :comments + has_many :ordered_uniq_comments_desc, -> { distinct.order("comments.id DESC") }, through: :posts, source: :comments + has_many :readonly_comments, -> { readonly }, through: :posts, source: :comments has_many :special_posts - has_many :special_post_comments, :through => :special_posts, :source => :comments - has_many :special_posts_with_default_scope, :class_name => 'SpecialPostWithDefaultScope' - - has_many :sti_posts, :class_name => 'StiPost' - has_many :sti_post_comments, :through => :sti_posts, :source => :comments - - has_many :special_nonexistent_posts, -> { where("posts.body = 'nonexistent'") }, :class_name => "SpecialPost" - has_many :special_nonexistent_post_comments, -> { where('comments.post_id' => 0) }, :through => :special_nonexistent_posts, :source => :comments - has_many :nonexistent_comments, :through => :posts - - has_many :hello_posts, -> { where "posts.body = 'hello'" }, :class_name => "Post" - has_many :hello_post_comments, :through => :hello_posts, :source => :comments - has_many :posts_with_no_comments, -> { where('comments.id' => nil).includes(:comments) }, :class_name => 'Post' - - has_many :hello_posts_with_hash_conditions, -> { where(:body => 'hello') }, :class_name => "Post" - has_many :hello_post_comments_with_hash_conditions, :through => -:hello_posts_with_hash_conditions, :source => :comments - - has_many :other_posts, :class_name => "Post" - has_many :posts_with_callbacks, :class_name => "Post", :before_add => :log_before_adding, - :after_add => :log_after_adding, - :before_remove => :log_before_removing, - :after_remove => :log_after_removing - has_many :posts_with_proc_callbacks, :class_name => "Post", - :before_add => Proc.new {|o, r| o.post_log << "before_adding#{r.id || '<new>'}"}, - :after_add => Proc.new {|o, r| o.post_log << "after_adding#{r.id || '<new>'}"}, - :before_remove => Proc.new {|o, r| o.post_log << "before_removing#{r.id}"}, - :after_remove => Proc.new {|o, r| o.post_log << "after_removing#{r.id}"} - has_many :posts_with_multiple_callbacks, :class_name => "Post", - :before_add => [:log_before_adding, Proc.new {|o, r| o.post_log << "before_adding_proc#{r.id || '<new>'}"}], - :after_add => [:log_after_adding, Proc.new {|o, r| o.post_log << "after_adding_proc#{r.id || '<new>'}"}] - has_many :unchangeable_posts, :class_name => "Post", :before_add => :raise_exception, :after_add => :log_after_adding + has_many :special_post_comments, through: :special_posts, source: :comments + has_many :special_posts_with_default_scope, class_name: "SpecialPostWithDefaultScope" + + has_many :sti_posts, class_name: "StiPost" + has_many :sti_post_comments, through: :sti_posts, source: :comments + + has_many :special_nonexistent_posts, -> { where("posts.body = 'nonexistent'") }, class_name: "SpecialPost" + has_many :special_nonexistent_post_comments, -> { where("comments.post_id" => 0) }, through: :special_nonexistent_posts, source: :comments + has_many :nonexistent_comments, through: :posts + + has_many :hello_posts, -> { where "posts.body = 'hello'" }, class_name: "Post" + has_many :hello_post_comments, through: :hello_posts, source: :comments + has_many :posts_with_no_comments, -> { where("comments.id" => nil).includes(:comments) }, class_name: "Post" + + has_many :hello_posts_with_hash_conditions, -> { where(body: "hello") }, class_name: "Post" + has_many :hello_post_comments_with_hash_conditions, through: :hello_posts_with_hash_conditions, source: :comments + + has_many :other_posts, class_name: "Post" + has_many :posts_with_callbacks, class_name: "Post", before_add: :log_before_adding, + after_add: :log_after_adding, + before_remove: :log_before_removing, + after_remove: :log_after_removing + has_many :posts_with_proc_callbacks, class_name: "Post", + before_add: Proc.new { |o, r| o.post_log << "before_adding#{r.id || '<new>'}" }, + after_add: Proc.new { |o, r| o.post_log << "after_adding#{r.id || '<new>'}" }, + before_remove: Proc.new { |o, r| o.post_log << "before_removing#{r.id}" }, + after_remove: Proc.new { |o, r| o.post_log << "after_removing#{r.id}" } + has_many :posts_with_multiple_callbacks, class_name: "Post", + before_add: [:log_before_adding, Proc.new { |o, r| o.post_log << "before_adding_proc#{r.id || '<new>'}" }], + after_add: [:log_after_adding, Proc.new { |o, r| o.post_log << "after_adding_proc#{r.id || '<new>'}" }] + has_many :unchangeable_posts, class_name: "Post", before_add: :raise_exception, after_add: :log_after_adding has_many :categorizations - has_many :categories, :through => :categorizations - has_many :named_categories, :through => :categorizations + has_many :categories, through: :categorizations + has_many :named_categories, through: :categorizations has_many :special_categorizations - has_many :special_categories, :through => :special_categorizations, :source => :category - has_one :special_category, :through => :special_categorizations, :source => :category + has_many :special_categories, through: :special_categorizations, source: :category + has_one :special_category, through: :special_categorizations, source: :category - has_many :categories_like_general, -> { where(:name => 'General') }, :through => :categorizations, :source => :category, :class_name => 'Category' + has_many :categories_like_general, -> { where(name: "General") }, through: :categorizations, source: :category, class_name: "Category" - has_many :categorized_posts, :through => :categorizations, :source => :post - has_many :unique_categorized_posts, -> { distinct }, :through => :categorizations, :source => :post + has_many :categorized_posts, through: :categorizations, source: :post + has_many :unique_categorized_posts, -> { distinct }, through: :categorizations, source: :post - has_many :nothings, :through => :kateggorisatons, :class_name => 'Category' + has_many :nothings, through: :kateggorisatons, class_name: "Category" has_many :author_favorites - has_many :favorite_authors, -> { order('name') }, :through => :author_favorites + has_many :favorite_authors, -> { order("name") }, through: :author_favorites - has_many :taggings, :through => :posts, :source => :taggings - has_many :taggings_2, :through => :posts, :source => :tagging - has_many :tags, :through => :posts - has_many :post_categories, :through => :posts, :source => :categories - has_many :tagging_tags, :through => :taggings, :source => :tag + has_many :taggings, through: :posts, source: :taggings + has_many :taggings_2, through: :posts, source: :tagging + has_many :tags, through: :posts + has_many :post_categories, through: :posts, source: :categories + has_many :tagging_tags, through: :taggings, source: :tag - has_many :similar_posts, -> { distinct }, :through => :tags, :source => :tagged_posts - has_many :distinct_tags, -> { select("DISTINCT tags.*").order("tags.name") }, :through => :posts, :source => :tags + has_many :similar_posts, -> { distinct }, through: :tags, source: :tagged_posts + has_many :distinct_tags, -> { select("DISTINCT tags.*").order("tags.name") }, through: :posts, source: :tags - has_many :tags_with_primary_key, :through => :posts + has_many :tags_with_primary_key, through: :posts has_many :books - has_many :subscriptions, :through => :books - has_many :subscribers, -> { order("subscribers.nick") }, :through => :subscriptions - has_many :distinct_subscribers, -> { select("DISTINCT subscribers.*").order("subscribers.nick") }, :through => :subscriptions, :source => :subscriber + has_many :subscriptions, through: :books + has_many :subscribers, -> { order("subscribers.nick") }, through: :subscriptions + has_many :distinct_subscribers, -> { select("DISTINCT subscribers.*").order("subscribers.nick") }, through: :subscriptions, source: :subscriber - has_one :essay, :primary_key => :name, :as => :writer - has_one :essay_category, :through => :essay, :source => :category - has_one :essay_owner, :through => :essay, :source => :owner + has_one :essay, primary_key: :name, as: :writer + has_one :essay_category, through: :essay, source: :category + has_one :essay_owner, through: :essay, source: :owner - has_one :essay_2, :primary_key => :name, :class_name => 'Essay', :foreign_key => :author_id - has_one :essay_category_2, :through => :essay_2, :source => :category + has_one :essay_2, primary_key: :name, class_name: "Essay", foreign_key: :author_id + has_one :essay_category_2, through: :essay_2, source: :category - has_many :essays, :primary_key => :name, :as => :writer - has_many :essay_categories, :through => :essays, :source => :category - has_many :essay_owners, :through => :essays, :source => :owner + has_many :essays, primary_key: :name, as: :writer + has_many :essay_categories, through: :essays, source: :category + has_many :essay_owners, through: :essays, source: :owner - has_many :essays_2, :primary_key => :name, :class_name => 'Essay', :foreign_key => :author_id - has_many :essay_categories_2, :through => :essays_2, :source => :category + has_many :essays_2, primary_key: :name, class_name: "Essay", foreign_key: :author_id + has_many :essay_categories_2, through: :essays_2, source: :category - belongs_to :owned_essay, :primary_key => :name, :class_name => 'Essay' - has_one :owned_essay_category, :through => :owned_essay, :source => :category + belongs_to :owned_essay, primary_key: :name, class_name: "Essay" + has_one :owned_essay_category, through: :owned_essay, source: :category - belongs_to :author_address, :dependent => :destroy - belongs_to :author_address_extra, :dependent => :delete, :class_name => "AuthorAddress" + belongs_to :author_address, dependent: :destroy + belongs_to :author_address_extra, dependent: :delete, class_name: "AuthorAddress" - has_many :category_post_comments, :through => :categories, :source => :post_comments + has_many :category_post_comments, through: :categories, source: :post_comments - has_many :misc_posts, -> { where(:posts => { :title => ['misc post by bob', 'misc post by mary'] }) }, :class_name => 'Post' - has_many :misc_post_first_blue_tags, :through => :misc_posts, :source => :first_blue_tags + has_many :misc_posts, -> { where(posts: { title: ["misc post by bob", "misc post by mary"] }) }, class_name: "Post" + has_many :misc_post_first_blue_tags, through: :misc_posts, source: :first_blue_tags - has_many :misc_post_first_blue_tags_2, -> { where(:posts => { :title => ['misc post by bob', 'misc post by mary'] }) }, - :through => :posts, :source => :first_blue_tags_2 + has_many :misc_post_first_blue_tags_2, -> { where(posts: { title: ["misc post by bob", "misc post by mary"] }) }, + through: :posts, source: :first_blue_tags_2 - has_many :posts_with_default_include, :class_name => 'PostWithDefaultInclude' - has_many :comments_on_posts_with_default_include, :through => :posts_with_default_include, :source => :comments + has_many :posts_with_default_include, class_name: "PostWithDefaultInclude" + has_many :comments_on_posts_with_default_include, through: :posts_with_default_include, source: :comments has_many :posts_with_signature, ->(record) { where("posts.title LIKE ?", "%by #{record.name.downcase}%") }, class_name: "Post" + has_many :posts_with_extension, -> { order(:title) }, class_name: "Post" do + def extension_method; end + end + + has_many :posts_with_extension_and_instance, ->(record) { order(:title) }, class_name: "Post" do + def extension_method; end + end + attr_accessor :post_log after_initialize :set_post_log @@ -197,5 +204,5 @@ end class AuthorFavorite < ActiveRecord::Base belongs_to :author - belongs_to :favorite_author, :class_name => "Author" + belongs_to :favorite_author, class_name: "Author" end diff --git a/activerecord/test/models/bird.rb b/activerecord/test/models/bird.rb index 2a51d903b8..24b839135d 100644 --- a/activerecord/test/models/bird.rb +++ b/activerecord/test/models/bird.rb @@ -5,7 +5,7 @@ class Bird < ActiveRecord::Base accepts_nested_attributes_for :pirate attr_accessor :cancel_save_from_callback - before_save :cancel_save_callback_method, :if => :cancel_save_from_callback + before_save :cancel_save_callback_method, if: :cancel_save_from_callback def cancel_save_callback_method throw(:abort) end diff --git a/activerecord/test/models/book.rb b/activerecord/test/models/book.rb index e43e5c3901..17bf3fbcb4 100644 --- a/activerecord/test/models/book.rb +++ b/activerecord/test/models/book.rb @@ -1,20 +1,21 @@ class Book < ActiveRecord::Base has_many :authors - has_many :citations, :foreign_key => 'book1_id' + has_many :citations, foreign_key: "book1_id" has_many :references, -> { distinct }, through: :citations, source: :reference_of has_many :subscriptions has_many :subscribers, through: :subscriptions enum status: [:proposed, :written, :published] - enum read_status: {unread: 0, reading: 2, read: 3} + enum read_status: { unread: 0, reading: 2, read: 3 } enum nullable_status: [:single, :married] enum language: [:english, :spanish, :french], _prefix: :in enum author_visibility: [:visible, :invisible], _prefix: true enum illustrator_visibility: [:visible, :invisible], _prefix: true enum font_size: [:small, :medium, :large], _prefix: :with, _suffix: true - enum cover: { hard: 'hard', soft: 'soft' } + enum difficulty: [:easy, :medium, :hard], _suffix: :to_read + enum cover: { hard: "hard", soft: "soft" } def published! super diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb index dc0296305a..113d21cb84 100644 --- a/activerecord/test/models/bulb.rb +++ b/activerecord/test/models/bulb.rb @@ -1,6 +1,6 @@ class Bulb < ActiveRecord::Base - default_scope { where(:name => 'defaulty') } - belongs_to :car, :touch => true + default_scope { where(name: "defaulty") } + belongs_to :car, touch: true scope :awesome, -> { where(frickinawesome: true) } attr_reader :scope_after_initialize, :attributes_after_initialize @@ -35,7 +35,7 @@ class CustomBulb < Bulb after_initialize :set_awesomeness def set_awesomeness - self.frickinawesome = true if name == 'Dude' + self.frickinawesome = true if name == "Dude" end end @@ -50,9 +50,3 @@ class FailedBulb < Bulb throw(:abort) end end - -class TrickyBulb < Bulb - after_create do |record| - record.car.bulbs.to_a - end -end diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb index 778c22b1f6..92bff7ff96 100644 --- a/activerecord/test/models/car.rb +++ b/activerecord/test/models/car.rb @@ -1,27 +1,29 @@ class Car < ActiveRecord::Base has_many :bulbs has_many :all_bulbs, -> { unscope where: :name }, class_name: "Bulb" - has_many :funky_bulbs, class_name: 'FunkyBulb', dependent: :destroy - has_many :failed_bulbs, class_name: 'FailedBulb', dependent: :destroy - has_many :foo_bulbs, -> { where(:name => 'foo') }, :class_name => "Bulb" + has_many :funky_bulbs, class_name: "FunkyBulb", dependent: :destroy + has_many :failed_bulbs, class_name: "FailedBulb", dependent: :destroy + has_many :foo_bulbs, -> { where(name: "foo") }, class_name: "Bulb" has_many :awesome_bulbs, -> { awesome }, class_name: "Bulb" has_one :bulb has_many :tyres - has_many :engines, :dependent => :destroy, inverse_of: :my_car - has_many :wheels, :as => :wheelable, :dependent => :destroy + has_many :engines, dependent: :destroy, inverse_of: :my_car + has_many :wheels, as: :wheelable, dependent: :destroy + + has_many :price_estimates, as: :estimate_of scope :incl_tyres, -> { includes(:tyres) } scope :incl_engines, -> { includes(:engines) } - scope :order_using_new_style, -> { order('name asc') } + scope :order_using_new_style, -> { order("name asc") } end class CoolCar < Car - default_scope { order('name desc') } + default_scope { order("name desc") } end class FastCar < Car - default_scope { order('name desc') } + default_scope { order("name desc") } end diff --git a/activerecord/test/models/cat.rb b/activerecord/test/models/cat.rb new file mode 100644 index 0000000000..dfdde18641 --- /dev/null +++ b/activerecord/test/models/cat.rb @@ -0,0 +1,10 @@ +class Cat < ActiveRecord::Base + self.abstract_class = true + + enum gender: [:female, :male] + + default_scope -> { where(is_vegetarian: false) } +end + +class Lion < Cat +end diff --git a/activerecord/test/models/categorization.rb b/activerecord/test/models/categorization.rb index 4cd67c970a..b99383d0b1 100644 --- a/activerecord/test/models/categorization.rb +++ b/activerecord/test/models/categorization.rb @@ -1,18 +1,18 @@ class Categorization < ActiveRecord::Base belongs_to :post belongs_to :category, counter_cache: true - belongs_to :named_category, :class_name => 'Category', :foreign_key => :named_category_name, :primary_key => :name + belongs_to :named_category, class_name: "Category", foreign_key: :named_category_name, primary_key: :name belongs_to :author - has_many :post_taggings, :through => :author, :source => :taggings + has_many :post_taggings, through: :author, source: :taggings - belongs_to :author_using_custom_pk, :class_name => 'Author', :foreign_key => :author_id, :primary_key => :author_address_extra_id - has_many :authors_using_custom_pk, :class_name => 'Author', :foreign_key => :id, :primary_key => :category_id + belongs_to :author_using_custom_pk, class_name: "Author", foreign_key: :author_id, primary_key: :author_address_extra_id + has_many :authors_using_custom_pk, class_name: "Author", foreign_key: :id, primary_key: :category_id end class SpecialCategorization < ActiveRecord::Base - self.table_name = 'categorizations' - default_scope { where(:special => true) } + self.table_name = "categorizations" + default_scope { where(special: true) } belongs_to :author belongs_to :category diff --git a/activerecord/test/models/category.rb b/activerecord/test/models/category.rb index 272223e1d8..e8654dca01 100644 --- a/activerecord/test/models/category.rb +++ b/activerecord/test/models/category.rb @@ -1,34 +1,34 @@ class Category < ActiveRecord::Base has_and_belongs_to_many :posts - has_and_belongs_to_many :special_posts, :class_name => "Post" - has_and_belongs_to_many :other_posts, :class_name => "Post" - has_and_belongs_to_many :posts_with_authors_sorted_by_author_id, -> { includes(:authors).order("authors.id") }, :class_name => "Post" + has_and_belongs_to_many :special_posts, class_name: "Post" + has_and_belongs_to_many :other_posts, class_name: "Post" + has_and_belongs_to_many :posts_with_authors_sorted_by_author_id, -> { includes(:authors).order("authors.id") }, class_name: "Post" has_and_belongs_to_many :select_testing_posts, - -> { select 'posts.*, 1 as correctness_marker' }, - :class_name => 'Post', - :foreign_key => 'category_id', - :association_foreign_key => 'post_id' + -> { select "posts.*, 1 as correctness_marker" }, + class_name: "Post", + foreign_key: "category_id", + association_foreign_key: "post_id" has_and_belongs_to_many :post_with_conditions, - -> { where :title => 'Yet Another Testing Title' }, - :class_name => 'Post' + -> { where title: "Yet Another Testing Title" }, + class_name: "Post" - has_and_belongs_to_many :popular_grouped_posts, -> { group("posts.type").having("sum(comments.post_id) > 2").includes(:comments) }, :class_name => "Post" - has_and_belongs_to_many :posts_grouped_by_title, -> { group("title").select("title") }, :class_name => "Post" + has_and_belongs_to_many :popular_grouped_posts, -> { group("posts.type").having("sum(comments.post_id) > 2").includes(:comments) }, class_name: "Post" + has_and_belongs_to_many :posts_grouped_by_title, -> { group("title").select("title") }, class_name: "Post" def self.what_are_you - 'a category...' + "a category..." end has_many :categorizations has_many :special_categorizations - has_many :post_comments, :through => :posts, :source => :comments + has_many :post_comments, through: :posts, source: :comments - has_many :authors, :through => :categorizations - has_many :authors_with_select, -> { select 'authors.*, categorizations.post_id' }, :through => :categorizations, :source => :author + has_many :authors, through: :categorizations + has_many :authors_with_select, -> { select "authors.*, categorizations.post_id" }, through: :categorizations, source: :author - scope :general, -> { where(:name => 'General') } + scope :general, -> { where(name: "General") } end class SpecialCategory < Category diff --git a/activerecord/test/models/citation.rb b/activerecord/test/models/citation.rb index 3d87eb795c..7d06387f56 100644 --- a/activerecord/test/models/citation.rb +++ b/activerecord/test/models/citation.rb @@ -1,3 +1,3 @@ class Citation < ActiveRecord::Base - belongs_to :reference_of, :class_name => "Book", :foreign_key => :book2_id + belongs_to :reference_of, class_name: "Book", foreign_key: :book2_id end diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb index 6ceafe5858..49d7b24a3b 100644 --- a/activerecord/test/models/club.rb +++ b/activerecord/test/models/club.rb @@ -1,23 +1,23 @@ class Club < ActiveRecord::Base has_one :membership - has_many :memberships, :inverse_of => false - has_many :members, :through => :memberships + has_many :memberships, inverse_of: false + has_many :members, through: :memberships has_one :sponsor - has_one :sponsored_member, :through => :sponsor, :source => :sponsorable, :source_type => "Member" + has_one :sponsored_member, through: :sponsor, source: :sponsorable, source_type: "Member" belongs_to :category has_many :favourites, -> { where(memberships: { favourite: true }) }, through: :memberships, source: :member private - def private_method - "I'm sorry sir, this is a *private* club, not a *pirate* club" - end + def private_method + "I'm sorry sir, this is a *private* club, not a *pirate* club" + end end class SuperClub < ActiveRecord::Base self.table_name = "clubs" - has_many :memberships, class_name: 'SuperMembership', foreign_key: 'club_id' + has_many :memberships, class_name: "SuperMembership", foreign_key: "club_id" has_many :members, through: :memberships end diff --git a/activerecord/test/models/college.rb b/activerecord/test/models/college.rb index 501af4a8dd..c9dbe1ecc2 100644 --- a/activerecord/test/models/college.rb +++ b/activerecord/test/models/college.rb @@ -1,5 +1,5 @@ -require_dependency 'models/arunit2_model' -require 'active_support/core_ext/object/with_options' +require_dependency "models/arunit2_model" +require "active_support/core_ext/object/with_options" class College < ARUnit2Model has_many :courses diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb index dcc5c5a310..a4b81d56e0 100644 --- a/activerecord/test/models/comment.rb +++ b/activerecord/test/models/comment.rb @@ -1,26 +1,26 @@ class Comment < ActiveRecord::Base - scope :limit_by, lambda {|l| limit(l) } + scope :limit_by, lambda { |l| limit(l) } scope :containing_the_letter_e, -> { where("comments.body LIKE '%e%'") } scope :not_again, -> { where("comments.body NOT LIKE '%again%'") } - scope :for_first_post, -> { where(:post_id => 1) } + scope :for_first_post, -> { where(post_id: 1) } scope :for_first_author, -> { joins(:post).where("posts.author_id" => 1) } scope :created, -> { all } - belongs_to :post, :counter_cache => true + belongs_to :post, counter_cache: true belongs_to :author, polymorphic: true belongs_to :resource, polymorphic: true belongs_to :developer has_many :ratings - belongs_to :first_post, :foreign_key => :post_id + belongs_to :first_post, foreign_key: :post_id belongs_to :special_post_with_default_scope, foreign_key: :post_id - has_many :children, :class_name => 'Comment', :foreign_key => :parent_id - belongs_to :parent, :class_name => 'Comment', :counter_cache => :children_count + has_many :children, class_name: "Comment", foreign_key: :parent_id + belongs_to :parent, class_name: "Comment", counter_cache: :children_count def self.what_are_you - 'a comment...' + "a comment..." end def self.search_by_type(q) @@ -55,6 +55,6 @@ class CommentThatAutomaticallyAltersPostBody < Comment end class CommentWithDefaultScopeReferencesAssociation < Comment - default_scope ->{ includes(:developer).order('developers.name').references(:developer) } + default_scope -> { includes(:developer).order("developers.name").references(:developer) } belongs_to :developer end diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 1dcd9fc21e..025630087c 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -7,13 +7,13 @@ class Company < AbstractCompany validates_presence_of :name - has_one :dummy_account, :foreign_key => "firm_id", :class_name => "Account" + has_one :dummy_account, foreign_key: "firm_id", class_name: "Account" has_many :contracts - has_many :developers, :through => :contracts + has_many :developers, through: :contracts scope :of_first_firm, lambda { - joins(:account => :firm). - where('firms.id' => 1) + joins(account: :firm). + where("firms.id" => 1) } def arbitrary_method @@ -22,12 +22,12 @@ class Company < AbstractCompany private - def private_method - "I am Jack's innermost fears and aspirations" - end + def private_method + "I am Jack's innermost fears and aspirations" + end - class SpecialCo < Company - end + class SpecialCo < Company + end end module Namespaced @@ -35,7 +35,7 @@ module Namespaced end class Firm < ::Company - has_many :clients, :class_name => 'Namespaced::Client' + has_many :clients, class_name: "Namespaced::Client" end class Client < ::Company @@ -45,45 +45,45 @@ end class Firm < Company to_param :name - has_many :clients, -> { order "id" }, :dependent => :destroy, :before_remove => :log_before_remove, :after_remove => :log_after_remove - has_many :unsorted_clients, :class_name => "Client" - has_many :unsorted_clients_with_symbol, :class_name => :Client - has_many :clients_sorted_desc, -> { order "id DESC" }, :class_name => "Client" - has_many :clients_of_firm, -> { order "id" }, :foreign_key => "client_of", :class_name => "Client", :inverse_of => :firm - has_many :clients_ordered_by_name, -> { order "name" }, :class_name => "Client" - has_many :unvalidated_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :validate => false - has_many :dependent_clients_of_firm, -> { order "id" }, :foreign_key => "client_of", :class_name => "Client", :dependent => :destroy - has_many :exclusively_dependent_clients_of_firm, -> { order "id" }, :foreign_key => "client_of", :class_name => "Client", :dependent => :delete_all - has_many :limited_clients, -> { limit 1 }, :class_name => "Client" - has_many :clients_with_interpolated_conditions, ->(firm) { where "rating > #{firm.rating}" }, :class_name => "Client" - has_many :clients_like_ms, -> { where("name = 'Microsoft'").order("id") }, :class_name => "Client" - has_many :clients_like_ms_with_hash_conditions, -> { where(:name => 'Microsoft').order("id") }, :class_name => "Client" - has_many :plain_clients, :class_name => 'Client' - has_many :clients_using_primary_key, :class_name => 'Client', - :primary_key => 'name', :foreign_key => 'firm_name' - has_many :clients_using_primary_key_with_delete_all, :class_name => 'Client', - :primary_key => 'name', :foreign_key => 'firm_name', :dependent => :delete_all - has_many :clients_grouped_by_firm_id, -> { group("firm_id").select("firm_id") }, :class_name => "Client" - has_many :clients_grouped_by_name, -> { group("name").select("name") }, :class_name => "Client" - - has_one :account, :foreign_key => "firm_id", :dependent => :destroy, :validate => true - has_one :unvalidated_account, :foreign_key => "firm_id", :class_name => 'Account', :validate => false - has_one :account_with_select, -> { select("id, firm_id") }, :foreign_key => "firm_id", :class_name=>'Account' - has_one :readonly_account, -> { readonly }, :foreign_key => "firm_id", :class_name => "Account" + has_many :clients, -> { order "id" }, dependent: :destroy, before_remove: :log_before_remove, after_remove: :log_after_remove + has_many :unsorted_clients, class_name: "Client" + has_many :unsorted_clients_with_symbol, class_name: :Client + has_many :clients_sorted_desc, -> { order "id DESC" }, class_name: "Client" + has_many :clients_of_firm, -> { order "id" }, foreign_key: "client_of", class_name: "Client", inverse_of: :firm + has_many :clients_ordered_by_name, -> { order "name" }, class_name: "Client" + has_many :unvalidated_clients_of_firm, foreign_key: "client_of", class_name: "Client", validate: false + has_many :dependent_clients_of_firm, -> { order "id" }, foreign_key: "client_of", class_name: "Client", dependent: :destroy + has_many :exclusively_dependent_clients_of_firm, -> { order "id" }, foreign_key: "client_of", class_name: "Client", dependent: :delete_all + has_many :limited_clients, -> { limit 1 }, class_name: "Client" + has_many :clients_with_interpolated_conditions, ->(firm) { where "rating > #{firm.rating}" }, class_name: "Client" + has_many :clients_like_ms, -> { where("name = 'Microsoft'").order("id") }, class_name: "Client" + has_many :clients_like_ms_with_hash_conditions, -> { where(name: "Microsoft").order("id") }, class_name: "Client" + has_many :plain_clients, class_name: "Client" + has_many :clients_using_primary_key, class_name: "Client", + primary_key: "name", foreign_key: "firm_name" + has_many :clients_using_primary_key_with_delete_all, class_name: "Client", + primary_key: "name", foreign_key: "firm_name", dependent: :delete_all + has_many :clients_grouped_by_firm_id, -> { group("firm_id").select("firm_id") }, class_name: "Client" + has_many :clients_grouped_by_name, -> { group("name").select("name") }, class_name: "Client" + + has_one :account, foreign_key: "firm_id", dependent: :destroy, validate: true + has_one :unvalidated_account, foreign_key: "firm_id", class_name: "Account", validate: false + has_one :account_with_select, -> { select("id, firm_id") }, foreign_key: "firm_id", class_name: "Account" + has_one :readonly_account, -> { readonly }, foreign_key: "firm_id", class_name: "Account" # added order by id as in fixtures there are two accounts for Rails Core # Oracle tests were failing because of that as the second fixture was selected - has_one :account_using_primary_key, -> { order('id') }, :primary_key => "firm_id", :class_name => "Account" - has_one :account_using_foreign_and_primary_keys, :foreign_key => "firm_name", :primary_key => "name", :class_name => "Account" - has_one :account_with_inexistent_foreign_key, class_name: 'Account', foreign_key: "inexistent" - has_one :deletable_account, :foreign_key => "firm_id", :class_name => "Account", :dependent => :delete + has_one :account_using_primary_key, -> { order("id") }, primary_key: "firm_id", class_name: "Account" + has_one :account_using_foreign_and_primary_keys, foreign_key: "firm_name", primary_key: "name", class_name: "Account" + has_one :account_with_inexistent_foreign_key, class_name: "Account", foreign_key: "inexistent" + has_one :deletable_account, foreign_key: "firm_id", class_name: "Account", dependent: :delete - has_one :account_limit_500_with_hash_conditions, -> { where :credit_limit => 500 }, :foreign_key => "firm_id", :class_name => "Account" + has_one :account_limit_500_with_hash_conditions, -> { where credit_limit: 500 }, foreign_key: "firm_id", class_name: "Account" - has_one :unautosaved_account, :foreign_key => "firm_id", :class_name => 'Account', :autosave => false + has_one :unautosaved_account, foreign_key: "firm_id", class_name: "Account", autosave: false has_many :accounts - has_many :unautosaved_accounts, :foreign_key => "firm_id", :class_name => 'Account', :autosave => false + has_many :unautosaved_accounts, foreign_key: "firm_id", class_name: "Account", autosave: false - has_many :association_with_references, -> { references(:foo) }, :class_name => 'Client' + has_many :association_with_references, -> { references(:foo) }, class_name: "Client" has_one :lead_developer, class_name: "Developer" has_many :projects @@ -103,32 +103,32 @@ class Firm < Company end class DependentFirm < Company - has_one :account, :foreign_key => "firm_id", :dependent => :nullify - has_many :companies, :foreign_key => 'client_of', :dependent => :nullify - has_one :company, :foreign_key => 'client_of', :dependent => :nullify + has_one :account, foreign_key: "firm_id", dependent: :nullify + has_many :companies, foreign_key: "client_of", dependent: :nullify + has_one :company, foreign_key: "client_of", dependent: :nullify end class RestrictedWithExceptionFirm < Company - has_one :account, -> { order("id") }, :foreign_key => "firm_id", :dependent => :restrict_with_exception - has_many :companies, -> { order("id") }, :foreign_key => 'client_of', :dependent => :restrict_with_exception + has_one :account, -> { order("id") }, foreign_key: "firm_id", dependent: :restrict_with_exception + has_many :companies, -> { order("id") }, foreign_key: "client_of", dependent: :restrict_with_exception end class RestrictedWithErrorFirm < Company - has_one :account, -> { order("id") }, :foreign_key => "firm_id", :dependent => :restrict_with_error - has_many :companies, -> { order("id") }, :foreign_key => 'client_of', :dependent => :restrict_with_error + has_one :account, -> { order("id") }, foreign_key: "firm_id", dependent: :restrict_with_error + has_many :companies, -> { order("id") }, foreign_key: "client_of", dependent: :restrict_with_error end class Client < Company - belongs_to :firm, :foreign_key => "client_of" - belongs_to :firm_with_basic_id, :class_name => "Firm", :foreign_key => "firm_id" - belongs_to :firm_with_select, -> { select("id") }, :class_name => "Firm", :foreign_key => "firm_id" - belongs_to :firm_with_other_name, :class_name => "Firm", :foreign_key => "client_of" - belongs_to :firm_with_condition, -> { where "1 = ?", 1 }, :class_name => "Firm", :foreign_key => "client_of" - belongs_to :firm_with_primary_key, :class_name => "Firm", :primary_key => "name", :foreign_key => "firm_name" - belongs_to :firm_with_primary_key_symbols, :class_name => "Firm", :primary_key => :name, :foreign_key => :firm_name - belongs_to :readonly_firm, -> { readonly }, :class_name => "Firm", :foreign_key => "firm_id" - belongs_to :bob_firm, -> { where :name => "Bob" }, :class_name => "Firm", :foreign_key => "client_of" - has_many :accounts, :through => :firm, :source => :accounts + belongs_to :firm, foreign_key: "client_of" + belongs_to :firm_with_basic_id, class_name: "Firm", foreign_key: "firm_id" + belongs_to :firm_with_select, -> { select("id") }, class_name: "Firm", foreign_key: "firm_id" + belongs_to :firm_with_other_name, class_name: "Firm", foreign_key: "client_of" + belongs_to :firm_with_condition, -> { where "1 = ?", 1 }, class_name: "Firm", foreign_key: "client_of" + belongs_to :firm_with_primary_key, class_name: "Firm", primary_key: "name", foreign_key: "firm_name" + belongs_to :firm_with_primary_key_symbols, class_name: "Firm", primary_key: :name, foreign_key: :firm_name + belongs_to :readonly_firm, -> { readonly }, class_name: "Firm", foreign_key: "firm_id" + belongs_to :bob_firm, -> { where name: "Bob" }, class_name: "Firm", foreign_key: "client_of" + has_many :accounts, through: :firm, source: :accounts belongs_to :account validate do @@ -181,9 +181,9 @@ class Client < Company end class ExclusivelyDependentFirm < Company - has_one :account, :foreign_key => "firm_id", :dependent => :delete - has_many :dependent_sanitized_conditional_clients_of_firm, -> { order("id").where("name = 'BigShot Inc.'") }, :foreign_key => "client_of", :class_name => "Client", :dependent => :delete_all - has_many :dependent_conditional_clients_of_firm, -> { order("id").where("name = ?", 'BigShot Inc.') }, :foreign_key => "client_of", :class_name => "Client", :dependent => :delete_all + has_one :account, foreign_key: "firm_id", dependent: :delete + has_many :dependent_sanitized_conditional_clients_of_firm, -> { order("id").where("name = 'BigShot Inc.'") }, foreign_key: "client_of", class_name: "Client", dependent: :delete_all + has_many :dependent_conditional_clients_of_firm, -> { order("id").where("name = ?", "BigShot Inc.") }, foreign_key: "client_of", class_name: "Client", dependent: :delete_all end class SpecialClient < Client @@ -193,8 +193,8 @@ class VerySpecialClient < SpecialClient end class Account < ActiveRecord::Base - belongs_to :firm, :class_name => 'Company' - belongs_to :unautosaved_firm, :foreign_key => "firm_id", :class_name => "Firm", :autosave => false + belongs_to :firm, class_name: "Company" + belongs_to :unautosaved_firm, foreign_key: "firm_id", class_name: "Firm", autosave: false alias_attribute :available_credit, :credit_limit @@ -204,7 +204,7 @@ class Account < ActiveRecord::Base # Test private kernel method through collection proxy using has_many. def self.open - where('firm_name = ?', '37signals') + where("firm_name = ?", "37signals") end before_destroy do |account| @@ -218,13 +218,13 @@ class Account < ActiveRecord::Base protected - def check_empty_credit_limit - errors.add("credit_limit", :blank) if credit_limit.blank? - end + def check_empty_credit_limit + errors.add("credit_limit", :blank) if credit_limit.blank? + end private - def private_method - "Sir, yes sir!" - end + def private_method + "Sir, yes sir!" + end end diff --git a/activerecord/test/models/company_in_module.rb b/activerecord/test/models/company_in_module.rb index bf0a0d1c3e..682f99e365 100644 --- a/activerecord/test/models/company_in_module.rb +++ b/activerecord/test/models/company_in_module.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/object/with_options' +require "active_support/core_ext/object/with_options" module MyApplication module Business @@ -6,23 +6,23 @@ module MyApplication end class Firm < Company - has_many :clients, -> { order("id") }, :dependent => :destroy - has_many :clients_sorted_desc, -> { order("id DESC") }, :class_name => "Client" - has_many :clients_of_firm, -> { order "id" }, :foreign_key => "client_of", :class_name => "Client" - has_many :clients_like_ms, -> { where("name = 'Microsoft'").order("id") }, :class_name => "Client" - has_one :account, :class_name => 'MyApplication::Billing::Account', :dependent => :destroy + has_many :clients, -> { order("id") }, dependent: :destroy + has_many :clients_sorted_desc, -> { order("id DESC") }, class_name: "Client" + has_many :clients_of_firm, -> { order "id" }, foreign_key: "client_of", class_name: "Client" + has_many :clients_like_ms, -> { where("name = 'Microsoft'").order("id") }, class_name: "Client" + has_one :account, class_name: "MyApplication::Billing::Account", dependent: :destroy end class Client < Company - belongs_to :firm, :foreign_key => "client_of" - belongs_to :firm_with_other_name, :class_name => "Firm", :foreign_key => "client_of" + belongs_to :firm, foreign_key: "client_of" + belongs_to :firm_with_other_name, class_name: "Firm", foreign_key: "client_of" class Contact < ActiveRecord::Base; end end class Developer < ActiveRecord::Base has_and_belongs_to_many :projects - validates_length_of :name, :within => (3..20) + validates_length_of :name, within: (3..20) end class Project < ActiveRecord::Base @@ -31,14 +31,14 @@ module MyApplication module Prefixed def self.table_name_prefix - 'prefixed_' + "prefixed_" end class Company < ActiveRecord::Base end class Firm < Company - self.table_name = 'companies' + self.table_name = "companies" end module Nested @@ -49,14 +49,14 @@ module MyApplication module Suffixed def self.table_name_suffix - '_suffixed' + "_suffixed" end class Company < ActiveRecord::Base end class Firm < Company - self.table_name = 'companies' + self.table_name = "companies" end module Nested @@ -68,31 +68,31 @@ module MyApplication module Billing class Firm < ActiveRecord::Base - self.table_name = 'companies' + self.table_name = "companies" end module Nested class Firm < ActiveRecord::Base - self.table_name = 'companies' + self.table_name = "companies" end end class Account < ActiveRecord::Base - with_options(:foreign_key => :firm_id) do |i| - i.belongs_to :firm, :class_name => 'MyApplication::Business::Firm' - i.belongs_to :qualified_billing_firm, :class_name => 'MyApplication::Billing::Firm' - i.belongs_to :unqualified_billing_firm, :class_name => 'Firm' - i.belongs_to :nested_qualified_billing_firm, :class_name => 'MyApplication::Billing::Nested::Firm' - i.belongs_to :nested_unqualified_billing_firm, :class_name => 'Nested::Firm' + with_options(foreign_key: :firm_id) do |i| + i.belongs_to :firm, class_name: "MyApplication::Business::Firm" + i.belongs_to :qualified_billing_firm, class_name: "MyApplication::Billing::Firm" + i.belongs_to :unqualified_billing_firm, class_name: "Firm" + i.belongs_to :nested_qualified_billing_firm, class_name: "MyApplication::Billing::Nested::Firm" + i.belongs_to :nested_unqualified_billing_firm, class_name: "Nested::Firm" end validate :check_empty_credit_limit protected - def check_empty_credit_limit - errors.add("credit_card", :blank) if credit_card.blank? - end + def check_empty_credit_limit + errors.add("credit_card", :blank) if credit_card.blank? + end end end end diff --git a/activerecord/test/models/computer.rb b/activerecord/test/models/computer.rb index cc8deb1b2b..1c9856e1af 100644 --- a/activerecord/test/models/computer.rb +++ b/activerecord/test/models/computer.rb @@ -1,3 +1,3 @@ class Computer < ActiveRecord::Base - belongs_to :developer, :foreign_key=>'developer' + belongs_to :developer, foreign_key: "developer" end diff --git a/activerecord/test/models/contact.rb b/activerecord/test/models/contact.rb index 9f2f69e1ee..47bbbbfd8b 100644 --- a/activerecord/test/models/contact.rb +++ b/activerecord/test/models/contact.rb @@ -1,11 +1,11 @@ module ContactFakeColumns def self.extended(base) base.class_eval do - establish_connection(:adapter => 'fake') + establish_connection(adapter: "fake") connection.data_sources = [table_name] connection.primary_keys = { - table_name => 'id' + table_name => "id" } column :id, :integer @@ -19,7 +19,7 @@ module ContactFakeColumns serialize :preferences - belongs_to :alternative, :class_name => 'Contact' + belongs_to :alternative, class_name: "Contact" end end @@ -37,5 +37,5 @@ class ContactSti < ActiveRecord::Base extend ContactFakeColumns column :type, :string - def type; 'ContactSti' end + def type; "ContactSti" end end diff --git a/activerecord/test/models/content.rb b/activerecord/test/models/content.rb index 140e1dfc78..68db2127d8 100644 --- a/activerecord/test/models/content.rb +++ b/activerecord/test/models/content.rb @@ -1,5 +1,5 @@ class Content < ActiveRecord::Base - self.table_name = 'content' + self.table_name = "content" has_one :content_position, dependent: :destroy def self.destroyed_ids @@ -12,8 +12,8 @@ class Content < ActiveRecord::Base end class ContentWhichRequiresTwoDestroyCalls < ActiveRecord::Base - self.table_name = 'content' - has_one :content_position, foreign_key: 'content_id', dependent: :destroy + self.table_name = "content" + has_one :content_position, foreign_key: "content_id", dependent: :destroy after_initialize do @destroy_count = 0 diff --git a/activerecord/test/models/contract.rb b/activerecord/test/models/contract.rb index cdf7b267b5..32bd581377 100644 --- a/activerecord/test/models/contract.rb +++ b/activerecord/test/models/contract.rb @@ -1,7 +1,7 @@ class Contract < ActiveRecord::Base belongs_to :company belongs_to :developer - belongs_to :firm, :foreign_key => 'company_id' + belongs_to :firm, foreign_key: "company_id" before_save :hi after_save :bye diff --git a/activerecord/test/models/country.rb b/activerecord/test/models/country.rb index 7db9a4e731..7912719ddd 100644 --- a/activerecord/test/models/country.rb +++ b/activerecord/test/models/country.rb @@ -1,7 +1,5 @@ class Country < ActiveRecord::Base - self.primary_key = :country_id has_and_belongs_to_many :treaties - end diff --git a/activerecord/test/models/course.rb b/activerecord/test/models/course.rb index f3d0e05ff7..348f2bf1e0 100644 --- a/activerecord/test/models/course.rb +++ b/activerecord/test/models/course.rb @@ -1,4 +1,4 @@ -require_dependency 'models/arunit2_model' +require_dependency "models/arunit2_model" class Course < ARUnit2Model belongs_to :college diff --git a/activerecord/test/models/customer.rb b/activerecord/test/models/customer.rb index afe4b3d707..60af7c2247 100644 --- a/activerecord/test/models/customer.rb +++ b/activerecord/test/models/customer.rb @@ -1,12 +1,13 @@ class Customer < ActiveRecord::Base cattr_accessor :gps_conversion_was_run - composed_of :address, :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ], :allow_nil => true - composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), :converter => Proc.new(&:to_money) - composed_of :gps_location, :allow_nil => true - composed_of :non_blank_gps_location, :class_name => "GpsLocation", :allow_nil => true, :mapping => %w(gps_location gps_location), - :converter => lambda { |gps| self.gps_conversion_was_run = true; gps.blank? ? nil : GpsLocation.new(gps)} - composed_of :fullname, :mapping => %w(name to_s), :constructor => Proc.new { |name| Fullname.parse(name) }, :converter => :parse + composed_of :address, mapping: [ %w(address_street street), %w(address_city city), %w(address_country country) ], allow_nil: true + composed_of :balance, class_name: "Money", mapping: %w(balance amount), converter: Proc.new(&:to_money) + composed_of :gps_location, allow_nil: true + composed_of :non_blank_gps_location, class_name: "GpsLocation", allow_nil: true, mapping: %w(gps_location gps_location), + converter: lambda { |gps| self.gps_conversion_was_run = true; gps.blank? ? nil : GpsLocation.new(gps) } + composed_of :fullname, mapping: %w(name to_s), constructor: Proc.new { |name| Fullname.parse(name) }, converter: :parse + composed_of :fullname_no_converter, mapping: %w(name to_s), class_name: "Fullname" end class Address @@ -64,7 +65,12 @@ class Fullname def self.parse(str) return nil unless str - new(*str.to_s.split) + + if str.is_a?(Hash) + new(str[:first], str[:last]) + else + new(*str.to_s.split) + end end def initialize(first, last = nil) diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 9a907273f8..5ca1d37f6d 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -1,4 +1,4 @@ -require 'ostruct' +require "ostruct" module DeveloperProjectsAssociationExtension2 def find_least_recent @@ -23,35 +23,35 @@ class Developer < ActiveRecord::Base has_and_belongs_to_many :projects_extended_by_name, -> { extending(DeveloperProjectsAssociationExtension) }, - :class_name => "Project", - :join_table => "developers_projects", - :association_foreign_key => "project_id" + class_name: "Project", + join_table: "developers_projects", + association_foreign_key: "project_id" has_and_belongs_to_many :projects_extended_by_name_twice, -> { extending(DeveloperProjectsAssociationExtension, DeveloperProjectsAssociationExtension2) }, - :class_name => "Project", - :join_table => "developers_projects", - :association_foreign_key => "project_id" + class_name: "Project", + join_table: "developers_projects", + association_foreign_key: "project_id" has_and_belongs_to_many :projects_extended_by_name_and_block, -> { extending(DeveloperProjectsAssociationExtension) }, - :class_name => "Project", - :join_table => "developers_projects", - :association_foreign_key => "project_id" do + class_name: "Project", + join_table: "developers_projects", + association_foreign_key: "project_id" do def find_least_recent order("id ASC").first end end - has_and_belongs_to_many :special_projects, :join_table => 'developers_projects', :association_foreign_key => 'project_id' + has_and_belongs_to_many :special_projects, join_table: "developers_projects", association_foreign_key: "project_id" has_and_belongs_to_many :sym_special_projects, - :join_table => :developers_projects, - :association_foreign_key => 'project_id', - :class_name => 'SpecialProject' + join_table: :developers_projects, + association_foreign_key: "project_id", + class_name: "SpecialProject" has_many :audit_logs has_many :contracts - has_many :firms, :through => :contracts, :source => :firm + has_many :firms, through: :contracts, source: :firm has_many :comments, ->(developer) { where(body: "I'm #{developer.name}") } has_many :ratings, through: :comments has_one :ship, dependent: :nullify @@ -59,20 +59,20 @@ class Developer < ActiveRecord::Base belongs_to :firm has_many :contracted_projects, class_name: "Project" - scope :jamises, -> { where(:name => 'Jamis') } + scope :jamises, -> { where(name: "Jamis") } - validates_inclusion_of :salary, :in => 50000..200000 - validates_length_of :name, :within => 3..20 + validates_inclusion_of :salary, in: 50000..200000 + validates_length_of :name, within: 3..20 before_create do |developer| - developer.audit_logs.build :message => "Computer created" + developer.audit_logs.build message: "Computer created" end attr_accessor :last_name - define_attribute_method 'last_name' + define_attribute_method "last_name" def log=(message) - audit_logs.build :message => message + audit_logs.build message: message end after_find :track_instance_count @@ -83,17 +83,16 @@ class Developer < ActiveRecord::Base self.class.instance_count += 1 end private :track_instance_count - end class AuditLog < ActiveRecord::Base - belongs_to :developer, :validate => true - belongs_to :unvalidated_developer, :class_name => 'Developer' + belongs_to :developer, validate: true + belongs_to :unvalidated_developer, class_name: "Developer" end class DeveloperWithBeforeDestroyRaise < ActiveRecord::Base - self.table_name = 'developers' - has_and_belongs_to_many :projects, :join_table => 'developers_projects', :foreign_key => 'developer_id' + self.table_name = "developers" + has_and_belongs_to_many :projects, join_table: "developers_projects", foreign_key: "developer_id" before_destroy :raise_if_projects_empty! def raise_if_projects_empty! @@ -102,63 +101,63 @@ class DeveloperWithBeforeDestroyRaise < ActiveRecord::Base end class DeveloperWithSelect < ActiveRecord::Base - self.table_name = 'developers' - default_scope { select('name') } + self.table_name = "developers" + default_scope { select("name") } end class DeveloperWithIncludes < ActiveRecord::Base - self.table_name = 'developers' - has_many :audit_logs, :foreign_key => :developer_id + self.table_name = "developers" + has_many :audit_logs, foreign_key: :developer_id default_scope { includes(:audit_logs) } end class DeveloperFilteredOnJoins < ActiveRecord::Base - self.table_name = 'developers' - has_and_belongs_to_many :projects, -> { order('projects.id') }, :foreign_key => 'developer_id', :join_table => 'developers_projects' + self.table_name = "developers" + has_and_belongs_to_many :projects, -> { order("projects.id") }, foreign_key: "developer_id", join_table: "developers_projects" def self.default_scope - joins(:projects).where(:projects => { :name => 'Active Controller' }) + joins(:projects).where(projects: { name: "Active Controller" }) end end class DeveloperOrderedBySalary < ActiveRecord::Base - self.table_name = 'developers' - default_scope { order('salary DESC') } + self.table_name = "developers" + default_scope { order("salary DESC") } - scope :by_name, -> { order('name DESC') } + scope :by_name, -> { order("name DESC") } end class DeveloperCalledDavid < ActiveRecord::Base - self.table_name = 'developers' + self.table_name = "developers" default_scope { where("name = 'David'") } end class LazyLambdaDeveloperCalledDavid < ActiveRecord::Base - self.table_name = 'developers' - default_scope lambda { where(:name => 'David') } + self.table_name = "developers" + default_scope lambda { where(name: "David") } end class LazyBlockDeveloperCalledDavid < ActiveRecord::Base - self.table_name = 'developers' - default_scope { where(:name => 'David') } + self.table_name = "developers" + default_scope { where(name: "David") } end class CallableDeveloperCalledDavid < ActiveRecord::Base - self.table_name = 'developers' - default_scope OpenStruct.new(:call => where(:name => 'David')) + self.table_name = "developers" + default_scope OpenStruct.new(call: where(name: "David")) end class ClassMethodDeveloperCalledDavid < ActiveRecord::Base - self.table_name = 'developers' + self.table_name = "developers" def self.default_scope - where(:name => 'David') + where(name: "David") end end class ClassMethodReferencingScopeDeveloperCalledDavid < ActiveRecord::Base - self.table_name = 'developers' - scope :david, -> { where(:name => 'David') } + self.table_name = "developers" + scope :david, -> { where(name: "David") } def self.default_scope david @@ -166,61 +165,61 @@ class ClassMethodReferencingScopeDeveloperCalledDavid < ActiveRecord::Base end class LazyBlockReferencingScopeDeveloperCalledDavid < ActiveRecord::Base - self.table_name = 'developers' - scope :david, -> { where(:name => 'David') } + self.table_name = "developers" + scope :david, -> { where(name: "David") } default_scope { david } end class DeveloperCalledJamis < ActiveRecord::Base - self.table_name = 'developers' + self.table_name = "developers" - default_scope { where(:name => 'Jamis') } - scope :poor, -> { where('salary < 150000') } + default_scope { where(name: "Jamis") } + scope :poor, -> { where("salary < 150000") } scope :david, -> { where name: "David" } scope :david2, -> { unscoped.where name: "David" } end class PoorDeveloperCalledJamis < ActiveRecord::Base - self.table_name = 'developers' + self.table_name = "developers" - default_scope -> { where(:name => 'Jamis', :salary => 50000) } + default_scope -> { where(name: "Jamis", salary: 50000) } end class InheritedPoorDeveloperCalledJamis < DeveloperCalledJamis - self.table_name = 'developers' + self.table_name = "developers" - default_scope -> { where(:salary => 50000) } + default_scope -> { where(salary: 50000) } end class MultiplePoorDeveloperCalledJamis < ActiveRecord::Base - self.table_name = 'developers' + self.table_name = "developers" - default_scope -> { where(:name => 'Jamis') } - default_scope -> { where(:salary => 50000) } + default_scope -> { where(name: "Jamis") } + default_scope -> { where(salary: 50000) } end module SalaryDefaultScope extend ActiveSupport::Concern - included { default_scope { where(:salary => 50000) } } + included { default_scope { where(salary: 50000) } } end class ModuleIncludedPoorDeveloperCalledJamis < DeveloperCalledJamis - self.table_name = 'developers' + self.table_name = "developers" include SalaryDefaultScope end class EagerDeveloperWithDefaultScope < ActiveRecord::Base - self.table_name = 'developers' - has_and_belongs_to_many :projects, -> { order('projects.id') }, :foreign_key => 'developer_id', :join_table => 'developers_projects' + self.table_name = "developers" + has_and_belongs_to_many :projects, -> { order("projects.id") }, foreign_key: "developer_id", join_table: "developers_projects" default_scope { includes(:projects) } end class EagerDeveloperWithClassMethodDefaultScope < ActiveRecord::Base - self.table_name = 'developers' - has_and_belongs_to_many :projects, -> { order('projects.id') }, :foreign_key => 'developer_id', :join_table => 'developers_projects' + self.table_name = "developers" + has_and_belongs_to_many :projects, -> { order("projects.id") }, foreign_key: "developer_id", join_table: "developers_projects" def self.default_scope includes(:projects) @@ -228,28 +227,28 @@ class EagerDeveloperWithClassMethodDefaultScope < ActiveRecord::Base end class EagerDeveloperWithLambdaDefaultScope < ActiveRecord::Base - self.table_name = 'developers' - has_and_belongs_to_many :projects, -> { order('projects.id') }, :foreign_key => 'developer_id', :join_table => 'developers_projects' + self.table_name = "developers" + has_and_belongs_to_many :projects, -> { order("projects.id") }, foreign_key: "developer_id", join_table: "developers_projects" default_scope lambda { includes(:projects) } end class EagerDeveloperWithBlockDefaultScope < ActiveRecord::Base - self.table_name = 'developers' - has_and_belongs_to_many :projects, -> { order('projects.id') }, :foreign_key => 'developer_id', :join_table => 'developers_projects' + self.table_name = "developers" + has_and_belongs_to_many :projects, -> { order("projects.id") }, foreign_key: "developer_id", join_table: "developers_projects" default_scope { includes(:projects) } end class EagerDeveloperWithCallableDefaultScope < ActiveRecord::Base - self.table_name = 'developers' - has_and_belongs_to_many :projects, -> { order('projects.id') }, :foreign_key => 'developer_id', :join_table => 'developers_projects' + self.table_name = "developers" + has_and_belongs_to_many :projects, -> { order("projects.id") }, foreign_key: "developer_id", join_table: "developers_projects" - default_scope OpenStruct.new(:call => includes(:projects)) + default_scope OpenStruct.new(call: includes(:projects)) end class ThreadsafeDeveloper < ActiveRecord::Base - self.table_name = 'developers' + self.table_name = "developers" def self.default_scope sleep 0.05 if Thread.current[:long_default_scope] diff --git a/activerecord/test/models/doubloon.rb b/activerecord/test/models/doubloon.rb index 2b11d128e2..7272504666 100644 --- a/activerecord/test/models/doubloon.rb +++ b/activerecord/test/models/doubloon.rb @@ -8,5 +8,5 @@ end class Doubloon < AbstractDoubloon # This uses an abstract class that defines attributes and associations. - self.table_name = 'doubloons' + self.table_name = "doubloons" end diff --git a/activerecord/test/models/edge.rb b/activerecord/test/models/edge.rb index 55e0c31fcb..e61d25c9bc 100644 --- a/activerecord/test/models/edge.rb +++ b/activerecord/test/models/edge.rb @@ -1,5 +1,5 @@ # This class models an edge in a directed graph. class Edge < ActiveRecord::Base - belongs_to :source, :class_name => 'Vertex', :foreign_key => 'source_id' - belongs_to :sink, :class_name => 'Vertex', :foreign_key => 'sink_id' + belongs_to :source, class_name: "Vertex", foreign_key: "source_id" + belongs_to :sink, class_name: "Vertex", foreign_key: "sink_id" end diff --git a/activerecord/test/models/engine.rb b/activerecord/test/models/engine.rb index 851ff8c22b..eada171f6a 100644 --- a/activerecord/test/models/engine.rb +++ b/activerecord/test/models/engine.rb @@ -1,4 +1,3 @@ class Engine < ActiveRecord::Base - belongs_to :my_car, :class_name => 'Car', :foreign_key => 'car_id', :counter_cache => :engines_count + belongs_to :my_car, class_name: "Car", foreign_key: "car_id", counter_cache: :engines_count end - diff --git a/activerecord/test/models/essay.rb b/activerecord/test/models/essay.rb index ec4b982b5b..13267fbc21 100644 --- a/activerecord/test/models/essay.rb +++ b/activerecord/test/models/essay.rb @@ -1,5 +1,5 @@ class Essay < ActiveRecord::Base - belongs_to :writer, :primary_key => :name, :polymorphic => true - belongs_to :category, :primary_key => :name - has_one :owner, :primary_key => :name + belongs_to :writer, primary_key: :name, polymorphic: true + belongs_to :category, primary_key: :name + has_one :owner, primary_key: :name end diff --git a/activerecord/test/models/eye.rb b/activerecord/test/models/eye.rb index dc8ae2b3f6..ab3b3eacf3 100644 --- a/activerecord/test/models/eye.rb +++ b/activerecord/test/models/eye.rb @@ -15,7 +15,7 @@ class Eye < ActiveRecord::Base after_create :trace_after_create2 after_update :trace_after_update2 after_save :trace_after_save2 - + def trace_after_create (@after_create_callbacks_stack ||= []) << !iris.persisted? end diff --git a/activerecord/test/models/face.rb b/activerecord/test/models/face.rb index af76fea52c..5913bfa969 100644 --- a/activerecord/test/models/face.rb +++ b/activerecord/test/models/face.rb @@ -1,9 +1,9 @@ class Face < ActiveRecord::Base - belongs_to :man, :inverse_of => :face - belongs_to :polymorphic_man, :polymorphic => true, :inverse_of => :polymorphic_face + belongs_to :man, inverse_of: :face + belongs_to :polymorphic_man, polymorphic: true, inverse_of: :polymorphic_face # Oracle identifier length is limited to 30 bytes or less, `polymorphic` renamed `poly` - belongs_to :poly_man_without_inverse, :polymorphic => true + belongs_to :poly_man_without_inverse, polymorphic: true # These is a "broken" inverse_of for the purposes of testing - belongs_to :horrible_man, :class_name => 'Man', :inverse_of => :horrible_face - belongs_to :horrible_polymorphic_man, :polymorphic => true, :inverse_of => :horrible_polymorphic_face + belongs_to :horrible_man, class_name: "Man", inverse_of: :horrible_face + belongs_to :horrible_polymorphic_man, polymorphic: true, inverse_of: :horrible_polymorphic_face end diff --git a/activerecord/test/models/friendship.rb b/activerecord/test/models/friendship.rb index 4b411ca8e0..578382b494 100644 --- a/activerecord/test/models/friendship.rb +++ b/activerecord/test/models/friendship.rb @@ -1,6 +1,6 @@ class Friendship < ActiveRecord::Base - belongs_to :friend, class_name: 'Person' + belongs_to :friend, class_name: "Person" # friend_too exists to test a bug, and probably shouldn't be used elsewhere - belongs_to :friend_too, foreign_key: 'friend_id', class_name: 'Person', counter_cache: :friends_too_count - belongs_to :follower, class_name: 'Person' + belongs_to :friend_too, foreign_key: "friend_id", class_name: "Person", counter_cache: :friends_too_count + belongs_to :follower, class_name: "Person" end diff --git a/activerecord/test/models/hotel.rb b/activerecord/test/models/hotel.rb index 9c90ffcff4..7bc717c891 100644 --- a/activerecord/test/models/hotel.rb +++ b/activerecord/test/models/hotel.rb @@ -1,11 +1,11 @@ class Hotel < ActiveRecord::Base has_many :departments has_many :chefs, through: :departments - has_many :cake_designers, source_type: 'CakeDesigner', source: :employable, through: :chefs - has_many :drink_designers, source_type: 'DrinkDesigner', source: :employable, through: :chefs + has_many :cake_designers, source_type: "CakeDesigner", source: :employable, through: :chefs + has_many :drink_designers, source_type: "DrinkDesigner", source: :employable, through: :chefs has_many :chef_lists, as: :employable_list - has_many :mocktail_designers, through: :chef_lists, source: :employable, :source_type => "MocktailDesigner" + has_many :mocktail_designers, through: :chef_lists, source: :employable, source_type: "MocktailDesigner" has_many :recipes, through: :chefs end diff --git a/activerecord/test/models/interest.rb b/activerecord/test/models/interest.rb index d5d9226204..ec79416ee7 100644 --- a/activerecord/test/models/interest.rb +++ b/activerecord/test/models/interest.rb @@ -1,5 +1,5 @@ class Interest < ActiveRecord::Base - belongs_to :man, :inverse_of => :interests - belongs_to :polymorphic_man, :polymorphic => true, :inverse_of => :polymorphic_interests - belongs_to :zine, :inverse_of => :interests + belongs_to :man, inverse_of: :interests + belongs_to :polymorphic_man, polymorphic: true, inverse_of: :polymorphic_interests + belongs_to :zine, inverse_of: :interests end diff --git a/activerecord/test/models/invoice.rb b/activerecord/test/models/invoice.rb index fc6ef0230e..4be5a00193 100644 --- a/activerecord/test/models/invoice.rb +++ b/activerecord/test/models/invoice.rb @@ -1,4 +1,4 @@ class Invoice < ActiveRecord::Base - has_many :line_items, :autosave => true - before_save {|record| record.balance = record.line_items.map(&:amount).sum } + has_many :line_items, autosave: true + before_save { |record| record.balance = record.line_items.map(&:amount).sum } end diff --git a/activerecord/test/models/item.rb b/activerecord/test/models/item.rb index c2571dd7fb..336fb1769a 100644 --- a/activerecord/test/models/item.rb +++ b/activerecord/test/models/item.rb @@ -1,6 +1,6 @@ class AbstractItem < ActiveRecord::Base self.abstract_class = true - has_one :tagging, :as => :taggable + has_one :tagging, as: :taggable end class Item < AbstractItem diff --git a/activerecord/test/models/job.rb b/activerecord/test/models/job.rb index f7b0e787b1..bbaef2792c 100644 --- a/activerecord/test/models/job.rb +++ b/activerecord/test/models/job.rb @@ -1,7 +1,7 @@ class Job < ActiveRecord::Base has_many :references - has_many :people, :through => :references - belongs_to :ideal_reference, :class_name => 'Reference' + has_many :people, through: :references + belongs_to :ideal_reference, class_name: "Reference" - has_many :agents, :through => :people + has_many :agents, through: :people end diff --git a/activerecord/test/models/joke.rb b/activerecord/test/models/joke.rb index edda4655dc..eeb5818a1f 100644 --- a/activerecord/test/models/joke.rb +++ b/activerecord/test/models/joke.rb @@ -1,7 +1,7 @@ class Joke < ActiveRecord::Base - self.table_name = 'funny_jokes' + self.table_name = "funny_jokes" end class GoodJoke < ActiveRecord::Base - self.table_name = 'funny_jokes' + self.table_name = "funny_jokes" end diff --git a/activerecord/test/models/keyboard.rb b/activerecord/test/models/keyboard.rb index 39347e274e..bcede53ec9 100644 --- a/activerecord/test/models/keyboard.rb +++ b/activerecord/test/models/keyboard.rb @@ -1,3 +1,3 @@ class Keyboard < ActiveRecord::Base - self.primary_key = 'key_number' + self.primary_key = "key_number" end diff --git a/activerecord/test/models/line_item.rb b/activerecord/test/models/line_item.rb index 0dd921a300..93f7cceb13 100644 --- a/activerecord/test/models/line_item.rb +++ b/activerecord/test/models/line_item.rb @@ -1,3 +1,3 @@ class LineItem < ActiveRecord::Base - belongs_to :invoice, :touch => true + belongs_to :invoice, touch: true end diff --git a/activerecord/test/models/man.rb b/activerecord/test/models/man.rb index 4fbb6b226b..d2436a735c 100644 --- a/activerecord/test/models/man.rb +++ b/activerecord/test/models/man.rb @@ -1,11 +1,11 @@ class Man < ActiveRecord::Base - has_one :face, :inverse_of => :man - has_one :polymorphic_face, :class_name => 'Face', :as => :polymorphic_man, :inverse_of => :polymorphic_man - has_one :polymorphic_face_without_inverse, :class_name => 'Face', :as => :poly_man_without_inverse - has_many :interests, :inverse_of => :man - has_many :polymorphic_interests, :class_name => 'Interest', :as => :polymorphic_man, :inverse_of => :polymorphic_man + has_one :face, inverse_of: :man + has_one :polymorphic_face, class_name: "Face", as: :polymorphic_man, inverse_of: :polymorphic_man + has_one :polymorphic_face_without_inverse, class_name: "Face", as: :poly_man_without_inverse + has_many :interests, inverse_of: :man + has_many :polymorphic_interests, class_name: "Interest", as: :polymorphic_man, inverse_of: :polymorphic_man # These are "broken" inverse_of associations for the purposes of testing - has_one :dirty_face, :class_name => 'Face', :inverse_of => :dirty_man - has_many :secret_interests, :class_name => 'Interest', :inverse_of => :secret_man + has_one :dirty_face, class_name: "Face", inverse_of: :dirty_man + has_many :secret_interests, class_name: "Interest", inverse_of: :secret_man has_one :mixed_case_monkey end diff --git a/activerecord/test/models/matey.rb b/activerecord/test/models/matey.rb index 47b0baa974..80ee5f47c5 100644 --- a/activerecord/test/models/matey.rb +++ b/activerecord/test/models/matey.rb @@ -1,4 +1,4 @@ class Matey < ActiveRecord::Base belongs_to :pirate - belongs_to :target, :class_name => 'Pirate' + belongs_to :target, class_name: "Pirate" end diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb index 7693c6e515..36f2461b84 100644 --- a/activerecord/test/models/member.rb +++ b/activerecord/test/models/member.rb @@ -2,34 +2,34 @@ class Member < ActiveRecord::Base has_one :current_membership has_one :selected_membership has_one :membership - has_one :club, :through => :current_membership - has_one :selected_club, :through => :selected_membership, :source => :club - has_one :favourite_club, -> { where "memberships.favourite = ?", true }, :through => :membership, :source => :club - has_one :hairy_club, -> { where :clubs => {:name => "Moustache and Eyebrow Fancier Club"} }, :through => :membership, :source => :club - has_one :sponsor, :as => :sponsorable - has_one :sponsor_club, :through => :sponsor - has_one :member_detail, :inverse_of => false - has_one :organization, :through => :member_detail + has_one :club, through: :current_membership + has_one :selected_club, through: :selected_membership, source: :club + has_one :favourite_club, -> { where "memberships.favourite = ?", true }, through: :membership, source: :club + has_one :hairy_club, -> { where clubs: { name: "Moustache and Eyebrow Fancier Club" } }, through: :membership, source: :club + has_one :sponsor, as: :sponsorable + has_one :sponsor_club, through: :sponsor + has_one :member_detail, inverse_of: false + has_one :organization, through: :member_detail belongs_to :member_type - has_many :nested_member_types, :through => :member_detail, :source => :member_type - has_one :nested_member_type, :through => :member_detail, :source => :member_type + has_many :nested_member_types, through: :member_detail, source: :member_type + has_one :nested_member_type, through: :member_detail, source: :member_type - has_many :nested_sponsors, :through => :sponsor_club, :source => :sponsor - has_one :nested_sponsor, :through => :sponsor_club, :source => :sponsor + has_many :nested_sponsors, through: :sponsor_club, source: :sponsor + has_one :nested_sponsor, through: :sponsor_club, source: :sponsor - has_many :organization_member_details, :through => :member_detail - has_many :organization_member_details_2, :through => :organization, :source => :member_details + has_many :organization_member_details, through: :member_detail + has_many :organization_member_details_2, through: :organization, source: :member_details - has_one :club_category, :through => :club, :source => :category + has_one :club_category, through: :club, source: :category - has_many :current_memberships, -> { where :favourite => true } - has_many :clubs, :through => :current_memberships + has_many :current_memberships, -> { where favourite: true } + has_many :clubs, through: :current_memberships has_many :tenant_memberships - has_many :tenant_clubs, through: :tenant_memberships, class_name: 'Club', source: :club + has_many :tenant_clubs, through: :tenant_memberships, class_name: "Club", source: :club - has_one :club_through_many, :through => :current_memberships, :source => :club + has_one :club_through_many, through: :current_memberships, source: :club belongs_to :admittable, polymorphic: true has_one :premium_club, through: :admittable @@ -37,5 +37,5 @@ end class SelfMember < ActiveRecord::Base self.table_name = "members" - has_and_belongs_to_many :friends, :class_name => "SelfMember", :join_table => "member_friends" + has_and_belongs_to_many :friends, class_name: "SelfMember", join_table: "member_friends" end diff --git a/activerecord/test/models/membership.rb b/activerecord/test/models/membership.rb index e181ba1f11..2c3ad230a7 100644 --- a/activerecord/test/models/membership.rb +++ b/activerecord/test/models/membership.rb @@ -9,7 +9,7 @@ class CurrentMembership < Membership end class SuperMembership < Membership - belongs_to :member, -> { order('members.id DESC') } + belongs_to :member, -> { order("members.id DESC") } belongs_to :club end diff --git a/activerecord/test/models/mentor.rb b/activerecord/test/models/mentor.rb index 11f1e4bff8..66504b4e91 100644 --- a/activerecord/test/models/mentor.rb +++ b/activerecord/test/models/mentor.rb @@ -1,3 +1,3 @@ class Mentor < ActiveRecord::Base has_many :developers -end
\ No newline at end of file +end diff --git a/activerecord/test/models/minivan.rb b/activerecord/test/models/minivan.rb index 4fe79720ad..e9b05dadf2 100644 --- a/activerecord/test/models/minivan.rb +++ b/activerecord/test/models/minivan.rb @@ -2,8 +2,7 @@ class Minivan < ActiveRecord::Base self.primary_key = :minivan_id belongs_to :speedometer - has_one :dashboard, :through => :speedometer + has_one :dashboard, through: :speedometer attr_readonly :color - end diff --git a/activerecord/test/models/node.rb b/activerecord/test/models/node.rb index 07dd2dbccb..459ea8cf95 100644 --- a/activerecord/test/models/node.rb +++ b/activerecord/test/models/node.rb @@ -1,5 +1,5 @@ class Node < ActiveRecord::Base belongs_to :tree, touch: true - belongs_to :parent, class_name: 'Node', touch: true, optional: true - has_many :children, class_name: 'Node', foreign_key: :parent_id, dependent: :destroy + belongs_to :parent, class_name: "Node", touch: true, optional: true + has_many :children, class_name: "Node", foreign_key: :parent_id, dependent: :destroy end diff --git a/activerecord/test/models/notification.rb b/activerecord/test/models/notification.rb index b4b4b8f1b6..82edc64b68 100644 --- a/activerecord/test/models/notification.rb +++ b/activerecord/test/models/notification.rb @@ -1,2 +1,3 @@ class Notification < ActiveRecord::Base + validates_presence_of :message end diff --git a/activerecord/test/models/order.rb b/activerecord/test/models/order.rb index e838c0b70d..699be53959 100644 --- a/activerecord/test/models/order.rb +++ b/activerecord/test/models/order.rb @@ -1,4 +1,4 @@ class Order < ActiveRecord::Base - belongs_to :billing, :class_name => 'Customer', :foreign_key => 'billing_customer_id' - belongs_to :shipping, :class_name => 'Customer', :foreign_key => 'shipping_customer_id' + belongs_to :billing, class_name: "Customer", foreign_key: "billing_customer_id" + belongs_to :shipping, class_name: "Customer", foreign_key: "shipping_customer_id" end diff --git a/activerecord/test/models/organization.rb b/activerecord/test/models/organization.rb index f3e92f3067..462830dadc 100644 --- a/activerecord/test/models/organization.rb +++ b/activerecord/test/models/organization.rb @@ -1,14 +1,14 @@ class Organization < ActiveRecord::Base has_many :member_details - has_many :members, :through => :member_details + has_many :members, through: :member_details - has_many :authors, :primary_key => :name - has_many :author_essay_categories, :through => :authors, :source => :essay_categories + has_many :authors, primary_key: :name + has_many :author_essay_categories, through: :authors, source: :essay_categories - has_one :author, :primary_key => :name - has_one :author_owned_essay_category, :through => :author, :source => :owned_essay_category + has_one :author, primary_key: :name + has_one :author_owned_essay_category, through: :author, source: :owned_essay_category - has_many :posts, :through => :author, :source => :posts + has_many :posts, through: :author, source: :posts - scope :clubs, -> { from('clubs') } + scope :clubs, -> { from("clubs") } end diff --git a/activerecord/test/models/owner.rb b/activerecord/test/models/owner.rb index cedb774b10..21fc9b6eb8 100644 --- a/activerecord/test/models/owner.rb +++ b/activerecord/test/models/owner.rb @@ -1,18 +1,19 @@ class Owner < ActiveRecord::Base self.primary_key = :owner_id - has_many :pets, -> { order 'pets.name desc' } - has_many :toys, :through => :pets + has_many :pets, -> { order "pets.name desc" } + has_many :toys, through: :pets + has_many :persons, through: :pets - belongs_to :last_pet, class_name: 'Pet' + belongs_to :last_pet, class_name: "Pet" scope :including_last_pet, -> { - select(%q[ + select(' owners.*, ( select p.pet_id from pets p where p.owner_id = owners.owner_id order by p.name desc limit 1 ) as last_pet_id - ]).includes(:last_pet) + ').includes(:last_pet) } after_commit :execute_blocks diff --git a/activerecord/test/models/parrot.rb b/activerecord/test/models/parrot.rb index ddc9dcaf29..5b693664d4 100644 --- a/activerecord/test/models/parrot.rb +++ b/activerecord/test/models/parrot.rb @@ -3,13 +3,13 @@ class Parrot < ActiveRecord::Base has_and_belongs_to_many :pirates has_and_belongs_to_many :treasures - has_many :loots, :as => :looter + has_many :loots, as: :looter alias_attribute :title, :name validates_presence_of :name attr_accessor :cancel_save_from_callback - before_save :cancel_save_callback_method, :if => :cancel_save_from_callback + before_save :cancel_save_callback_method, if: :cancel_save_from_callback def cancel_save_callback_method throw(:abort) end @@ -19,5 +19,5 @@ class LiveParrot < Parrot end class DeadParrot < Parrot - belongs_to :killer, :class_name => 'Pirate', foreign_key: :killer_id + belongs_to :killer, class_name: "Pirate", foreign_key: :killer_id end diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index a4a9c6b0d4..18994d6f18 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -3,71 +3,70 @@ class Person < ActiveRecord::Base has_many :secure_readers has_one :reader - has_many :posts, :through => :readers - has_many :secure_posts, :through => :secure_readers - has_many :posts_with_no_comments, -> { includes(:comments).where('comments.id is null').references(:comments) }, - :through => :readers, :source => :post + has_many :posts, through: :readers + has_many :secure_posts, through: :secure_readers + has_many :posts_with_no_comments, -> { includes(:comments).where("comments.id is null").references(:comments) }, + through: :readers, source: :post - has_many :friendships, foreign_key: 'friend_id' + has_many :friendships, foreign_key: "friend_id" # friends_too exists to test a bug, and probably shouldn't be used elsewhere - has_many :friends_too, foreign_key: 'friend_id', class_name: 'Friendship' + has_many :friends_too, foreign_key: "friend_id", class_name: "Friendship" has_many :followers, through: :friendships has_many :references has_many :bad_references - has_many :fixed_bad_references, -> { where :favourite => true }, :class_name => 'BadReference' - has_one :favourite_reference, -> { where 'favourite=?', true }, :class_name => 'Reference' - has_many :posts_with_comments_sorted_by_comment_id, -> { includes(:comments).order('comments.id') }, :through => :readers, :source => :post + has_many :fixed_bad_references, -> { where favourite: true }, class_name: "BadReference" + has_one :favourite_reference, -> { where "favourite=?", true }, class_name: "Reference" + has_many :posts_with_comments_sorted_by_comment_id, -> { includes(:comments).order("comments.id") }, through: :readers, source: :post has_many :first_posts, -> { where(id: [1, 2]) }, through: :readers - has_many :jobs, :through => :references - has_many :jobs_with_dependent_destroy, :source => :job, :through => :references, :dependent => :destroy - has_many :jobs_with_dependent_delete_all, :source => :job, :through => :references, :dependent => :delete_all - has_many :jobs_with_dependent_nullify, :source => :job, :through => :references, :dependent => :nullify + has_many :jobs, through: :references + has_many :jobs_with_dependent_destroy, source: :job, through: :references, dependent: :destroy + has_many :jobs_with_dependent_delete_all, source: :job, through: :references, dependent: :delete_all + has_many :jobs_with_dependent_nullify, source: :job, through: :references, dependent: :nullify - belongs_to :primary_contact, :class_name => 'Person' - has_many :agents, :class_name => 'Person', :foreign_key => 'primary_contact_id' - has_many :agents_of_agents, :through => :agents, :source => :agents - belongs_to :number1_fan, :class_name => 'Person' + belongs_to :primary_contact, class_name: "Person" + has_many :agents, class_name: "Person", foreign_key: "primary_contact_id" + has_many :agents_of_agents, through: :agents, source: :agents + belongs_to :number1_fan, class_name: "Person" - has_many :personal_legacy_things, :dependent => :destroy + has_many :personal_legacy_things, dependent: :destroy - has_many :agents_posts, :through => :agents, :source => :posts - has_many :agents_posts_authors, :through => :agents_posts, :source => :author + has_many :agents_posts, through: :agents, source: :posts + has_many :agents_posts_authors, through: :agents_posts, source: :author has_many :essays, primary_key: "first_name", foreign_key: "writer_id" - scope :males, -> { where(:gender => 'M') } + scope :males, -> { where(gender: "M") } end class PersonWithDependentDestroyJobs < ActiveRecord::Base - self.table_name = 'people' + self.table_name = "people" - has_many :references, :foreign_key => :person_id - has_many :jobs, :source => :job, :through => :references, :dependent => :destroy + has_many :references, foreign_key: :person_id + has_many :jobs, source: :job, through: :references, dependent: :destroy end class PersonWithDependentDeleteAllJobs < ActiveRecord::Base - self.table_name = 'people' + self.table_name = "people" - has_many :references, :foreign_key => :person_id - has_many :jobs, :source => :job, :through => :references, :dependent => :delete_all + has_many :references, foreign_key: :person_id + has_many :jobs, source: :job, through: :references, dependent: :delete_all end class PersonWithDependentNullifyJobs < ActiveRecord::Base - self.table_name = 'people' + self.table_name = "people" - has_many :references, :foreign_key => :person_id - has_many :jobs, :source => :job, :through => :references, :dependent => :nullify + has_many :references, foreign_key: :person_id + has_many :jobs, source: :job, through: :references, dependent: :nullify end - class LoosePerson < ActiveRecord::Base - self.table_name = 'people' + self.table_name = "people" self.abstract_class = true - has_one :best_friend, :class_name => 'LoosePerson', :foreign_key => :best_friend_id - belongs_to :best_friend_of, :class_name => 'LoosePerson', :foreign_key => :best_friend_of_id - has_many :best_friends, :class_name => 'LoosePerson', :foreign_key => :best_friend_id + has_one :best_friend, class_name: "LoosePerson", foreign_key: :best_friend_id + belongs_to :best_friend_of, class_name: "LoosePerson", foreign_key: :best_friend_of_id + has_many :best_friends, class_name: "LoosePerson", foreign_key: :best_friend_id accepts_nested_attributes_for :best_friend, :best_friend_of, :best_friends end @@ -75,11 +74,11 @@ end class LooseDescendant < LoosePerson; end class TightPerson < ActiveRecord::Base - self.table_name = 'people' + self.table_name = "people" - has_one :best_friend, :class_name => 'TightPerson', :foreign_key => :best_friend_id - belongs_to :best_friend_of, :class_name => 'TightPerson', :foreign_key => :best_friend_of_id - has_many :best_friends, :class_name => 'TightPerson', :foreign_key => :best_friend_id + has_one :best_friend, class_name: "TightPerson", foreign_key: :best_friend_id + belongs_to :best_friend_of, class_name: "TightPerson", foreign_key: :best_friend_of_id + has_many :best_friends, class_name: "TightPerson", foreign_key: :best_friend_id accepts_nested_attributes_for :best_friend, :best_friend_of, :best_friends end @@ -87,56 +86,56 @@ end class TightDescendant < TightPerson; end class RichPerson < ActiveRecord::Base - self.table_name = 'people' + self.table_name = "people" - has_and_belongs_to_many :treasures, :join_table => 'peoples_treasures' + has_and_belongs_to_many :treasures, join_table: "peoples_treasures" before_validation :run_before_create, on: :create before_validation :run_before_validation private - def run_before_create - self.first_name = first_name.to_s + 'run_before_create' - end + def run_before_create + self.first_name = first_name.to_s + "run_before_create" + end - def run_before_validation - self.first_name = first_name.to_s + 'run_before_validation' - end + def run_before_validation + self.first_name = first_name.to_s + "run_before_validation" + end end class NestedPerson < ActiveRecord::Base - self.table_name = 'people' + self.table_name = "people" - has_one :best_friend, :class_name => 'NestedPerson', :foreign_key => :best_friend_id - accepts_nested_attributes_for :best_friend, :update_only => true + has_one :best_friend, class_name: "NestedPerson", foreign_key: :best_friend_id + accepts_nested_attributes_for :best_friend, update_only: true def comments=(new_comments) raise RuntimeError end def best_friend_first_name=(new_name) - assign_attributes({ :best_friend_attributes => { :first_name => new_name } }) + assign_attributes(best_friend_attributes: { first_name: new_name }) end end class Insure INSURES = %W{life annuality} - def self.load mask + def self.load(mask) INSURES.select do |insure| (1 << INSURES.index(insure)) & mask.to_i > 0 end end - def self.dump insures + def self.dump(insures) numbers = insures.map { |insure| INSURES.index(insure) } numbers.inject(0) { |sum, n| sum + (1 << n) } end end class SerializedPerson < ActiveRecord::Base - self.table_name = 'people' + self.table_name = "people" serialize :insures, Insure end diff --git a/activerecord/test/models/personal_legacy_thing.rb b/activerecord/test/models/personal_legacy_thing.rb index a7ee3a0bca..adde7a504a 100644 --- a/activerecord/test/models/personal_legacy_thing.rb +++ b/activerecord/test/models/personal_legacy_thing.rb @@ -1,4 +1,4 @@ class PersonalLegacyThing < ActiveRecord::Base self.locking_column = :version - belongs_to :person, :counter_cache => true + belongs_to :person, counter_cache: true end diff --git a/activerecord/test/models/pet.rb b/activerecord/test/models/pet.rb index f7970d7aab..51a3e42815 100644 --- a/activerecord/test/models/pet.rb +++ b/activerecord/test/models/pet.rb @@ -2,8 +2,11 @@ class Pet < ActiveRecord::Base attr_accessor :current_user self.primary_key = :pet_id - belongs_to :owner, :touch => true + belongs_to :owner, touch: true has_many :toys + has_many :pet_treasures + has_many :treasures, through: :pet_treasures + has_many :persons, through: :treasures, source: :looter, source_type: "Person" class << self attr_accessor :after_destroy_output diff --git a/activerecord/test/models/pet_treasure.rb b/activerecord/test/models/pet_treasure.rb new file mode 100644 index 0000000000..1fe7807ffe --- /dev/null +++ b/activerecord/test/models/pet_treasure.rb @@ -0,0 +1,6 @@ +class PetTreasure < ActiveRecord::Base + self.table_name = "pets_treasures" + + belongs_to :pet + belongs_to :treasure +end diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb index 30545bdcd7..2dc8f9bd84 100644 --- a/activerecord/test/models/pirate.rb +++ b/activerecord/test/models/pirate.rb @@ -1,47 +1,47 @@ class Pirate < ActiveRecord::Base - belongs_to :parrot, :validate => true - belongs_to :non_validated_parrot, :class_name => 'Parrot' - has_and_belongs_to_many :parrots, -> { order('parrots.id ASC') }, :validate => true - has_and_belongs_to_many :non_validated_parrots, :class_name => 'Parrot' - has_and_belongs_to_many :parrots_with_method_callbacks, :class_name => "Parrot", - :before_add => :log_before_add, - :after_add => :log_after_add, - :before_remove => :log_before_remove, - :after_remove => :log_after_remove - has_and_belongs_to_many :parrots_with_proc_callbacks, :class_name => "Parrot", - :before_add => proc {|p,pa| p.ship_log << "before_adding_proc_parrot_#{pa.id || '<new>'}"}, - :after_add => proc {|p,pa| p.ship_log << "after_adding_proc_parrot_#{pa.id || '<new>'}"}, - :before_remove => proc {|p,pa| p.ship_log << "before_removing_proc_parrot_#{pa.id}"}, - :after_remove => proc {|p,pa| p.ship_log << "after_removing_proc_parrot_#{pa.id}"} + belongs_to :parrot, validate: true + belongs_to :non_validated_parrot, class_name: "Parrot" + has_and_belongs_to_many :parrots, -> { order("parrots.id ASC") }, validate: true + has_and_belongs_to_many :non_validated_parrots, class_name: "Parrot" + has_and_belongs_to_many :parrots_with_method_callbacks, class_name: "Parrot", + before_add: :log_before_add, + after_add: :log_after_add, + before_remove: :log_before_remove, + after_remove: :log_after_remove + has_and_belongs_to_many :parrots_with_proc_callbacks, class_name: "Parrot", + before_add: proc { |p,pa| p.ship_log << "before_adding_proc_parrot_#{pa.id || '<new>'}" }, + after_add: proc { |p,pa| p.ship_log << "after_adding_proc_parrot_#{pa.id || '<new>'}" }, + before_remove: proc { |p,pa| p.ship_log << "before_removing_proc_parrot_#{pa.id}" }, + after_remove: proc { |p,pa| p.ship_log << "after_removing_proc_parrot_#{pa.id}" } has_and_belongs_to_many :autosaved_parrots, class_name: "Parrot", autosave: true - has_many :treasures, :as => :looter - has_many :treasure_estimates, :through => :treasures, :source => :price_estimates + has_many :treasures, as: :looter + has_many :treasure_estimates, through: :treasures, source: :price_estimates has_one :ship - has_one :update_only_ship, :class_name => 'Ship' - has_one :non_validated_ship, :class_name => 'Ship' - has_many :birds, -> { order('birds.id ASC') } - has_many :birds_with_method_callbacks, :class_name => "Bird", - :before_add => :log_before_add, - :after_add => :log_after_add, - :before_remove => :log_before_remove, - :after_remove => :log_after_remove - has_many :birds_with_proc_callbacks, :class_name => "Bird", - :before_add => proc {|p,b| p.ship_log << "before_adding_proc_bird_#{b.id || '<new>'}"}, - :after_add => proc {|p,b| p.ship_log << "after_adding_proc_bird_#{b.id || '<new>'}"}, - :before_remove => proc {|p,b| p.ship_log << "before_removing_proc_bird_#{b.id}"}, - :after_remove => proc {|p,b| p.ship_log << "after_removing_proc_bird_#{b.id}"} - has_many :birds_with_reject_all_blank, :class_name => "Bird" + has_one :update_only_ship, class_name: "Ship" + has_one :non_validated_ship, class_name: "Ship" + has_many :birds, -> { order("birds.id ASC") } + has_many :birds_with_method_callbacks, class_name: "Bird", + before_add: :log_before_add, + after_add: :log_after_add, + before_remove: :log_before_remove, + after_remove: :log_after_remove + has_many :birds_with_proc_callbacks, class_name: "Bird", + before_add: proc { |p,b| p.ship_log << "before_adding_proc_bird_#{b.id || '<new>'}" }, + after_add: proc { |p,b| p.ship_log << "after_adding_proc_bird_#{b.id || '<new>'}" }, + before_remove: proc { |p,b| p.ship_log << "before_removing_proc_bird_#{b.id}" }, + after_remove: proc { |p,b| p.ship_log << "after_removing_proc_bird_#{b.id}" } + has_many :birds_with_reject_all_blank, class_name: "Bird" - has_one :foo_bulb, -> { where :name => 'foo' }, :foreign_key => :car_id, :class_name => "Bulb" + has_one :foo_bulb, -> { where name: "foo" }, foreign_key: :car_id, class_name: "Bulb" - accepts_nested_attributes_for :parrots, :birds, :allow_destroy => true, :reject_if => proc(&:empty?) - accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc(&:empty?) - accepts_nested_attributes_for :update_only_ship, :update_only => true + accepts_nested_attributes_for :parrots, :birds, allow_destroy: true, reject_if: proc(&:empty?) + accepts_nested_attributes_for :ship, allow_destroy: true, reject_if: proc(&:empty?) + accepts_nested_attributes_for :update_only_ship, update_only: true accepts_nested_attributes_for :parrots_with_method_callbacks, :parrots_with_proc_callbacks, - :birds_with_method_callbacks, :birds_with_proc_callbacks, :allow_destroy => true - accepts_nested_attributes_for :birds_with_reject_all_blank, :reject_if => :all_blank + :birds_with_method_callbacks, :birds_with_proc_callbacks, allow_destroy: true + accepts_nested_attributes_for :birds_with_reject_all_blank, reject_if: :all_blank validates_presence_of :catchphrase @@ -50,11 +50,11 @@ class Pirate < ActiveRecord::Base end def reject_empty_ships_on_create(attributes) - attributes.delete('_reject_me_if_new').present? && !persisted? + attributes.delete("_reject_me_if_new").present? && !persisted? end attr_accessor :cancel_save_from_callback, :parrots_limit - before_save :cancel_save_callback_method, :if => :cancel_save_from_callback + before_save :cancel_save_callback_method, if: :cancel_save_from_callback def cancel_save_callback_method throw(:abort) end @@ -82,11 +82,11 @@ class Pirate < ActiveRecord::Base end class DestructivePirate < Pirate - has_one :dependent_ship, :class_name => 'Ship', :foreign_key => :pirate_id, :dependent => :destroy + has_one :dependent_ship, class_name: "Ship", foreign_key: :pirate_id, dependent: :destroy end class FamousPirate < ActiveRecord::Base - self.table_name = 'pirates' + self.table_name = "pirates" has_many :famous_ships validates_presence_of :catchphrase, on: :conference end diff --git a/activerecord/test/models/possession.rb b/activerecord/test/models/possession.rb index ddf759113b..0226336c16 100644 --- a/activerecord/test/models/possession.rb +++ b/activerecord/test/models/possession.rb @@ -1,3 +1,3 @@ class Possession < ActiveRecord::Base - self.table_name = 'having' + self.table_name = "having" end diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index bf3079a1df..66a99cbcda 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -7,7 +7,7 @@ class Post < ActiveRecord::Base module NamedExtension def author - 'lifo' + "lifo" end end @@ -21,22 +21,23 @@ class Post < ActiveRecord::Base scope :titled_with_an_apostrophe, -> { where("title LIKE '%''%'") } scope :ranked_by_comments, -> { order("comments_count DESC") } - scope :limit_by, lambda {|l| limit(l) } + scope :limit_by, lambda { |l| limit(l) } belongs_to :author + belongs_to :readonly_author, -> { readonly }, class_name: "Author", foreign_key: :author_id - belongs_to :author_with_posts, -> { includes(:posts) }, :class_name => "Author", :foreign_key => :author_id - belongs_to :author_with_address, -> { includes(:author_address) }, :class_name => "Author", :foreign_key => :author_id + belongs_to :author_with_posts, -> { includes(:posts) }, class_name: "Author", foreign_key: :author_id + belongs_to :author_with_address, -> { includes(:author_address) }, class_name: "Author", foreign_key: :author_id def first_comment super.body end - has_one :first_comment, -> { order('id ASC') }, :class_name => 'Comment' - has_one :last_comment, -> { order('id desc') }, :class_name => 'Comment' + has_one :first_comment, -> { order("id ASC") }, class_name: "Comment" + has_one :last_comment, -> { order("id desc") }, class_name: "Comment" - scope :with_special_comments, -> { joins(:comments).where(:comments => {:type => 'SpecialComment'}) } - scope :with_very_special_comments, -> { joins(:comments).where(:comments => {:type => 'VerySpecialComment'}) } - scope :with_post, ->(post_id) { joins(:comments).where(:comments => { :post_id => post_id }) } + scope :with_special_comments, -> { joins(:comments).where(comments: { type: "SpecialComment" }) } + scope :with_very_special_comments, -> { joins(:comments).where(comments: { type: "VerySpecialComment" }) } + scope :with_post, ->(post_id) { joins(:comments).where(comments: { post_id: post_id }) } scope :with_comments, -> { preload(:comments) } scope :with_tags, -> { preload(:taggings) } @@ -68,92 +69,92 @@ class Post < ActiveRecord::Base has_many :comments_with_extend_2, extend: [NamedExtension, NamedExtension2], class_name: "Comment", foreign_key: "post_id" - has_many :author_favorites, :through => :author - has_many :author_categorizations, :through => :author, :source => :categorizations - has_many :author_addresses, :through => :author + has_many :author_favorites, through: :author + has_many :author_categorizations, through: :author, source: :categorizations + has_many :author_addresses, through: :author has_many :author_address_extra_with_address, through: :author_with_address, source: :author_address_extra has_one :very_special_comment - has_one :very_special_comment_with_post, -> { includes(:post) }, :class_name => "VerySpecialComment" - has_one :very_special_comment_with_post_with_joins, -> { joins(:post).order('posts.id') }, class_name: "VerySpecialComment" + has_one :very_special_comment_with_post, -> { includes(:post) }, class_name: "VerySpecialComment" + has_one :very_special_comment_with_post_with_joins, -> { joins(:post).order("posts.id") }, class_name: "VerySpecialComment" has_many :special_comments - has_many :nonexistent_comments, -> { where 'comments.id < 0' }, :class_name => 'Comment' + has_many :nonexistent_comments, -> { where "comments.id < 0" }, class_name: "Comment" - has_many :special_comments_ratings, :through => :special_comments, :source => :ratings - has_many :special_comments_ratings_taggings, :through => :special_comments_ratings, :source => :taggings + has_many :special_comments_ratings, through: :special_comments, source: :ratings + has_many :special_comments_ratings_taggings, through: :special_comments_ratings, source: :taggings - has_many :category_posts, :class_name => 'CategoryPost' + has_many :category_posts, class_name: "CategoryPost" has_many :scategories, through: :category_posts, source: :category has_and_belongs_to_many :categories - has_and_belongs_to_many :special_categories, :join_table => "categories_posts", :association_foreign_key => 'category_id' + has_and_belongs_to_many :special_categories, join_table: "categories_posts", association_foreign_key: "category_id" - has_many :taggings, :as => :taggable, :counter_cache => :tags_count - has_many :tags, :through => :taggings do + has_many :taggings, as: :taggable, counter_cache: :tags_count + has_many :tags, through: :taggings do def add_joins_and_select - select('tags.*, authors.id as author_id') - .joins('left outer join posts on taggings.taggable_id = posts.id left outer join authors on posts.author_id = authors.id') + select("tags.*, authors.id as author_id") + .joins("left outer join posts on taggings.taggable_id = posts.id left outer join authors on posts.author_id = authors.id") .to_a end end - has_many :taggings_with_delete_all, :class_name => 'Tagging', :as => :taggable, :dependent => :delete_all, counter_cache: :taggings_with_delete_all_count - has_many :taggings_with_destroy, :class_name => 'Tagging', :as => :taggable, :dependent => :destroy, counter_cache: :taggings_with_destroy_count + has_many :taggings_with_delete_all, class_name: "Tagging", as: :taggable, dependent: :delete_all, counter_cache: :taggings_with_delete_all_count + has_many :taggings_with_destroy, class_name: "Tagging", as: :taggable, dependent: :destroy, counter_cache: :taggings_with_destroy_count - has_many :tags_with_destroy, :through => :taggings, :source => :tag, :dependent => :destroy, counter_cache: :tags_with_destroy_count - has_many :tags_with_nullify, :through => :taggings, :source => :tag, :dependent => :nullify, counter_cache: :tags_with_nullify_count + has_many :tags_with_destroy, through: :taggings, source: :tag, dependent: :destroy, counter_cache: :tags_with_destroy_count + has_many :tags_with_nullify, through: :taggings, source: :tag, dependent: :nullify, counter_cache: :tags_with_nullify_count - has_many :misc_tags, -> { where :tags => { :name => 'Misc' } }, :through => :taggings, :source => :tag - has_many :funky_tags, :through => :taggings, :source => :tag - has_many :super_tags, :through => :taggings - has_many :tags_with_primary_key, :through => :taggings, :source => :tag_with_primary_key - has_one :tagging, :as => :taggable + has_many :misc_tags, -> { where tags: { name: "Misc" } }, through: :taggings, source: :tag + has_many :funky_tags, through: :taggings, source: :tag + has_many :super_tags, through: :taggings + has_many :tags_with_primary_key, through: :taggings, source: :tag_with_primary_key + has_one :tagging, as: :taggable - has_many :first_taggings, -> { where :taggings => { :comment => 'first' } }, :as => :taggable, :class_name => 'Tagging' - has_many :first_blue_tags, -> { where :tags => { :name => 'Blue' } }, :through => :first_taggings, :source => :tag + has_many :first_taggings, -> { where taggings: { comment: "first" } }, as: :taggable, class_name: "Tagging" + has_many :first_blue_tags, -> { where tags: { name: "Blue" } }, through: :first_taggings, source: :tag - has_many :first_blue_tags_2, -> { where :taggings => { :comment => 'first' } }, :through => :taggings, :source => :blue_tag + has_many :first_blue_tags_2, -> { where taggings: { comment: "first" } }, through: :taggings, source: :blue_tag - has_many :invalid_taggings, -> { where 'taggings.id < 0' }, :as => :taggable, :class_name => "Tagging" - has_many :invalid_tags, :through => :invalid_taggings, :source => :tag + has_many :invalid_taggings, -> { where "taggings.id < 0" }, as: :taggable, class_name: "Tagging" + has_many :invalid_tags, through: :invalid_taggings, source: :tag - has_many :categorizations, :foreign_key => :category_id - has_many :authors, :through => :categorizations + has_many :categorizations, foreign_key: :category_id + has_many :authors, through: :categorizations - has_many :categorizations_using_author_id, :primary_key => :author_id, :foreign_key => :post_id, :class_name => 'Categorization' - has_many :authors_using_author_id, :through => :categorizations_using_author_id, :source => :author + has_many :categorizations_using_author_id, primary_key: :author_id, foreign_key: :post_id, class_name: "Categorization" + has_many :authors_using_author_id, through: :categorizations_using_author_id, source: :author - has_many :taggings_using_author_id, :primary_key => :author_id, :as => :taggable, :class_name => 'Tagging' - has_many :tags_using_author_id, :through => :taggings_using_author_id, :source => :tag + has_many :taggings_using_author_id, primary_key: :author_id, as: :taggable, class_name: "Tagging" + has_many :tags_using_author_id, through: :taggings_using_author_id, source: :tag - has_many :images, :as => :imageable, :foreign_key => :imageable_identifier, :foreign_type => :imageable_class - has_one :main_image, :as => :imageable, :foreign_key => :imageable_identifier, :foreign_type => :imageable_class, :class_name => 'Image' + has_many :images, as: :imageable, foreign_key: :imageable_identifier, foreign_type: :imageable_class + has_one :main_image, as: :imageable, foreign_key: :imageable_identifier, foreign_type: :imageable_class, class_name: "Image" - has_many :standard_categorizations, :class_name => 'Categorization', :foreign_key => :post_id - has_many :author_using_custom_pk, :through => :standard_categorizations - has_many :authors_using_custom_pk, :through => :standard_categorizations - has_many :named_categories, :through => :standard_categorizations + has_many :standard_categorizations, class_name: "Categorization", foreign_key: :post_id + has_many :author_using_custom_pk, through: :standard_categorizations + has_many :authors_using_custom_pk, through: :standard_categorizations + has_many :named_categories, through: :standard_categorizations has_many :readers has_many :secure_readers - has_many :readers_with_person, -> { includes(:person) }, :class_name => "Reader" - has_many :people, :through => :readers - has_many :single_people, :through => :readers - has_many :people_with_callbacks, :source=>:person, :through => :readers, - :before_add => lambda {|owner, reader| log(:added, :before, reader.first_name) }, - :after_add => lambda {|owner, reader| log(:added, :after, reader.first_name) }, - :before_remove => lambda {|owner, reader| log(:removed, :before, reader.first_name) }, - :after_remove => lambda {|owner, reader| log(:removed, :after, reader.first_name) } - has_many :skimmers, -> { where :skimmer => true }, :class_name => 'Reader' - has_many :impatient_people, :through => :skimmers, :source => :person + has_many :readers_with_person, -> { includes(:person) }, class_name: "Reader" + has_many :people, through: :readers + has_many :single_people, through: :readers + has_many :people_with_callbacks, source: :person, through: :readers, + before_add: lambda { |owner, reader| log(:added, :before, reader.first_name) }, + after_add: lambda { |owner, reader| log(:added, :after, reader.first_name) }, + before_remove: lambda { |owner, reader| log(:removed, :before, reader.first_name) }, + after_remove: lambda { |owner, reader| log(:removed, :after, reader.first_name) } + has_many :skimmers, -> { where skimmer: true }, class_name: "Reader" + has_many :impatient_people, through: :skimmers, source: :person has_many :lazy_readers - has_many :lazy_readers_skimmers_or_not, -> { where(skimmer: [ true, false ]) }, :class_name => 'LazyReader' + has_many :lazy_readers_skimmers_or_not, -> { where(skimmer: [ true, false ]) }, class_name: "LazyReader" - has_many :lazy_people, :through => :lazy_readers, :source => :person - has_many :lazy_readers_unscope_skimmers, -> { skimmers_or_not }, :class_name => 'LazyReader' - has_many :lazy_people_unscope_skimmers, :through => :lazy_readers_unscope_skimmers, :source => :person + has_many :lazy_people, through: :lazy_readers, source: :person + has_many :lazy_readers_unscope_skimmers, -> { skimmers_or_not }, class_name: "LazyReader" + has_many :lazy_people_unscope_skimmers, through: :lazy_readers_unscope_skimmers, source: :person def self.top(limit) ranked_by_comments.limit_by(limit) @@ -177,7 +178,7 @@ class SpecialPost < Post; end class StiPost < Post self.abstract_class = true - has_one :special_comment, :class_name => "SpecialComment" + has_one :special_comment, class_name: "SpecialComment" end class SubStiPost < StiPost @@ -186,56 +187,56 @@ end class FirstPost < ActiveRecord::Base self.inheritance_column = :disabled - self.table_name = 'posts' - default_scope { where(:id => 1) } + self.table_name = "posts" + default_scope { where(id: 1) } - has_many :comments, :foreign_key => :post_id - has_one :comment, :foreign_key => :post_id + has_many :comments, foreign_key: :post_id + has_one :comment, foreign_key: :post_id end class PostWithDefaultInclude < ActiveRecord::Base self.inheritance_column = :disabled - self.table_name = 'posts' + self.table_name = "posts" default_scope { includes(:comments) } - has_many :comments, :foreign_key => :post_id + has_many :comments, foreign_key: :post_id end class PostWithSpecialCategorization < Post - has_many :categorizations, :foreign_key => :post_id - default_scope { where(:type => 'PostWithSpecialCategorization').joins(:categorizations).where(:categorizations => { :special => true }) } + has_many :categorizations, foreign_key: :post_id + default_scope { where(type: "PostWithSpecialCategorization").joins(:categorizations).where(categorizations: { special: true }) } end class PostWithDefaultScope < ActiveRecord::Base self.inheritance_column = :disabled - self.table_name = 'posts' + self.table_name = "posts" default_scope { order(:title) } end class PostWithPreloadDefaultScope < ActiveRecord::Base - self.table_name = 'posts' + self.table_name = "posts" - has_many :readers, foreign_key: 'post_id' + has_many :readers, foreign_key: "post_id" default_scope { preload(:readers) } end class PostWithIncludesDefaultScope < ActiveRecord::Base - self.table_name = 'posts' + self.table_name = "posts" - has_many :readers, foreign_key: 'post_id' + has_many :readers, foreign_key: "post_id" default_scope { includes(:readers) } end class SpecialPostWithDefaultScope < ActiveRecord::Base self.inheritance_column = :disabled - self.table_name = 'posts' - default_scope { where(:id => [1, 5,6]) } + self.table_name = "posts" + default_scope { where(id: [1, 5,6]) } end class PostThatLoadsCommentsInAnAfterSaveHook < ActiveRecord::Base self.inheritance_column = :disabled - self.table_name = 'posts' + self.table_name = "posts" has_many :comments, class_name: "CommentThatAutomaticallyAltersPostBody", foreign_key: :post_id after_save do |post| @@ -245,7 +246,7 @@ end class PostWithAfterCreateCallback < ActiveRecord::Base self.inheritance_column = :disabled - self.table_name = 'posts' + self.table_name = "posts" has_many :comments, foreign_key: :post_id after_create do |post| @@ -255,7 +256,7 @@ end class PostWithCommentWithDefaultScopeReferencesAssociation < ActiveRecord::Base self.inheritance_column = :disabled - self.table_name = 'posts' + self.table_name = "posts" has_many :comment_with_default_scope_references_associations, foreign_key: :post_id has_one :first_comment, class_name: "CommentWithDefaultScopeReferencesAssociation", foreign_key: :post_id end @@ -265,7 +266,7 @@ class SerializedPost < ActiveRecord::Base end class ConditionalStiPost < Post - default_scope { where(title: 'Untitled') } + default_scope { where(title: "Untitled") } end class SubConditionalStiPost < ConditionalStiPost diff --git a/activerecord/test/models/price_estimate.rb b/activerecord/test/models/price_estimate.rb index d09e2a88a3..ce086e40a3 100644 --- a/activerecord/test/models/price_estimate.rb +++ b/activerecord/test/models/price_estimate.rb @@ -1,4 +1,4 @@ class PriceEstimate < ActiveRecord::Base - belongs_to :estimate_of, :polymorphic => true + belongs_to :estimate_of, polymorphic: true belongs_to :thing, polymorphic: true end diff --git a/activerecord/test/models/professor.rb b/activerecord/test/models/professor.rb index 7654eda0ef..4dfb1a9602 100644 --- a/activerecord/test/models/professor.rb +++ b/activerecord/test/models/professor.rb @@ -1,4 +1,4 @@ -require_dependency 'models/arunit2_model' +require_dependency "models/arunit2_model" class Professor < ARUnit2Model has_and_belongs_to_many :courses diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb index efa8246f1e..5009f8f54b 100644 --- a/activerecord/test/models/project.rb +++ b/activerecord/test/models/project.rb @@ -1,17 +1,17 @@ class Project < ActiveRecord::Base belongs_to :mentor - has_and_belongs_to_many :developers, -> { distinct.order 'developers.name desc, developers.id desc' } - has_and_belongs_to_many :readonly_developers, -> { readonly }, :class_name => "Developer" - has_and_belongs_to_many :non_unique_developers, -> { order 'developers.name desc, developers.id desc' }, :class_name => 'Developer' - has_and_belongs_to_many :limited_developers, -> { limit 1 }, :class_name => "Developer" - has_and_belongs_to_many :developers_named_david, -> { where("name = 'David'").distinct }, :class_name => "Developer" - has_and_belongs_to_many :developers_named_david_with_hash_conditions, -> { where(:name => 'David').distinct }, :class_name => "Developer" - has_and_belongs_to_many :salaried_developers, -> { where "salary > 0" }, :class_name => "Developer" - has_and_belongs_to_many :developers_with_callbacks, :class_name => "Developer", :before_add => Proc.new {|o, r| o.developers_log << "before_adding#{r.id || '<new>'}"}, - :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 :developers, -> { distinct.order "developers.name desc, developers.id desc" } + has_and_belongs_to_many :readonly_developers, -> { readonly }, class_name: "Developer" + has_and_belongs_to_many :non_unique_developers, -> { order "developers.name desc, developers.id desc" }, class_name: "Developer" + has_and_belongs_to_many :limited_developers, -> { limit 1 }, class_name: "Developer" + has_and_belongs_to_many :developers_named_david, -> { where("name = 'David'").distinct }, class_name: "Developer" + has_and_belongs_to_many :developers_named_david_with_hash_conditions, -> { where(name: "David").distinct }, class_name: "Developer" + has_and_belongs_to_many :salaried_developers, -> { where "salary > 0" }, class_name: "Developer" + has_and_belongs_to_many :developers_with_callbacks, class_name: "Developer", before_add: Proc.new { |o, r| o.developers_log << "before_adding#{r.id || '<new>'}" }, + 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" belongs_to :firm has_one :lead_developer, through: :firm, inverse_of: :contracted_projects diff --git a/activerecord/test/models/rating.rb b/activerecord/test/models/rating.rb index 25a52c4ad7..7420821db0 100644 --- a/activerecord/test/models/rating.rb +++ b/activerecord/test/models/rating.rb @@ -1,4 +1,4 @@ class Rating < ActiveRecord::Base belongs_to :comment - has_many :taggings, :as => :taggable + has_many :taggings, as: :taggable end diff --git a/activerecord/test/models/reader.rb b/activerecord/test/models/reader.rb index 91afc1898c..7c5a159fe0 100644 --- a/activerecord/test/models/reader.rb +++ b/activerecord/test/models/reader.rb @@ -1,22 +1,22 @@ class Reader < ActiveRecord::Base belongs_to :post - belongs_to :person, :inverse_of => :readers - belongs_to :single_person, :class_name => 'Person', :foreign_key => :person_id, :inverse_of => :reader + belongs_to :person, inverse_of: :readers + belongs_to :single_person, class_name: "Person", foreign_key: :person_id, inverse_of: :reader belongs_to :first_post, -> { where(id: [2, 3]) } end class SecureReader < ActiveRecord::Base self.table_name = "readers" - belongs_to :secure_post, :class_name => "Post", :foreign_key => "post_id" - belongs_to :secure_person, :inverse_of => :secure_readers, :class_name => "Person", :foreign_key => "person_id" + belongs_to :secure_post, class_name: "Post", foreign_key: "post_id" + belongs_to :secure_person, inverse_of: :secure_readers, class_name: "Person", foreign_key: "person_id" end class LazyReader < ActiveRecord::Base self.table_name = "readers" default_scope -> { where(skimmer: true) } - scope :skimmers_or_not, -> { unscope(:where => :skimmer) } + scope :skimmers_or_not, -> { unscope(where: :skimmer) } belongs_to :post belongs_to :person diff --git a/activerecord/test/models/reference.rb b/activerecord/test/models/reference.rb index c2f9068f57..e2bb980fed 100644 --- a/activerecord/test/models/reference.rb +++ b/activerecord/test/models/reference.rb @@ -2,7 +2,7 @@ class Reference < ActiveRecord::Base belongs_to :person belongs_to :job - has_many :agents_posts_authors, :through => :person + has_many :agents_posts_authors, through: :person class << self; attr_accessor :make_comments; end self.make_comments = false @@ -17,6 +17,6 @@ class Reference < ActiveRecord::Base end class BadReference < ActiveRecord::Base - self.table_name = 'references' - default_scope { where(:favourite => false) } + self.table_name = "references" + default_scope { where(favourite: false) } end diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb index 3e82e55d89..a2d169292a 100644 --- a/activerecord/test/models/reply.rb +++ b/activerecord/test/models/reply.rb @@ -1,14 +1,14 @@ -require 'models/topic' +require "models/topic" class Reply < Topic - belongs_to :topic, :foreign_key => "parent_id", :counter_cache => true - belongs_to :topic_with_primary_key, :class_name => "Topic", :primary_key => "title", :foreign_key => "parent_title", :counter_cache => "replies_count" - has_many :replies, :class_name => "SillyReply", :dependent => :destroy, :foreign_key => "parent_id" + belongs_to :topic, foreign_key: "parent_id", counter_cache: true + belongs_to :topic_with_primary_key, class_name: "Topic", primary_key: "title", foreign_key: "parent_title", counter_cache: "replies_count" + has_many :replies, class_name: "SillyReply", dependent: :destroy, foreign_key: "parent_id" end class UniqueReply < Reply - belongs_to :topic, :foreign_key => 'parent_id', :counter_cache => true - validates_uniqueness_of :content, :scope => 'parent_id' + belongs_to :topic, foreign_key: "parent_id", counter_cache: true + validates_uniqueness_of :content, scope: "parent_id" end class SillyUniqueReply < UniqueReply @@ -16,12 +16,12 @@ end class WrongReply < Reply validate :errors_on_empty_content - validate :title_is_wrong_create, :on => :create + validate :title_is_wrong_create, on: :create validate :check_empty_title - validate :check_content_mismatch, :on => :create - validate :check_wrong_update, :on => :update - validate :check_author_name_is_secret, :on => :special_case + validate :check_content_mismatch, on: :create + validate :check_wrong_update, on: :update + validate :check_author_name_is_secret, on: :special_case def check_empty_title errors[:title] << "Empty" unless attribute_present?("title") @@ -51,11 +51,11 @@ class WrongReply < Reply end class SillyReply < Reply - belongs_to :reply, :foreign_key => "parent_id", :counter_cache => :replies_count + belongs_to :reply, foreign_key: "parent_id", counter_cache: :replies_count end module Web class Reply < Web::Topic - belongs_to :topic, :foreign_key => "parent_id", :counter_cache => true, :class_name => 'Web::Topic' + belongs_to :topic, foreign_key: "parent_id", counter_cache: true, class_name: "Web::Topic" end end diff --git a/activerecord/test/models/ship.rb b/activerecord/test/models/ship.rb index e333b964ab..77a7b22315 100644 --- a/activerecord/test/models/ship.rb +++ b/activerecord/test/models/ship.rb @@ -2,19 +2,19 @@ class Ship < ActiveRecord::Base self.record_timestamps = false belongs_to :pirate - belongs_to :update_only_pirate, :class_name => 'Pirate' + belongs_to :update_only_pirate, class_name: "Pirate" belongs_to :developer, dependent: :destroy - has_many :parts, :class_name => 'ShipPart' + has_many :parts, class_name: "ShipPart" has_many :treasures - accepts_nested_attributes_for :parts, :allow_destroy => true - accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc(&:empty?) - accepts_nested_attributes_for :update_only_pirate, :update_only => true + accepts_nested_attributes_for :parts, allow_destroy: true + accepts_nested_attributes_for :pirate, allow_destroy: true, reject_if: proc(&:empty?) + accepts_nested_attributes_for :update_only_pirate, update_only: true validates_presence_of :name attr_accessor :cancel_save_from_callback - before_save :cancel_save_callback_method, :if => :cancel_save_from_callback + before_save :cancel_save_callback_method, if: :cancel_save_from_callback def cancel_save_callback_method throw(:abort) end @@ -33,7 +33,7 @@ class Prisoner < ActiveRecord::Base end class FamousShip < ActiveRecord::Base - self.table_name = 'ships' + self.table_name = "ships" belongs_to :famous_pirate validates_presence_of :name, on: :conference end diff --git a/activerecord/test/models/ship_part.rb b/activerecord/test/models/ship_part.rb index 05c65f8a4a..1a633b8d77 100644 --- a/activerecord/test/models/ship_part.rb +++ b/activerecord/test/models/ship_part.rb @@ -1,7 +1,7 @@ class ShipPart < ActiveRecord::Base belongs_to :ship - has_many :trinkets, :class_name => "Treasure", :as => :looter - accepts_nested_attributes_for :trinkets, :allow_destroy => true + has_many :trinkets, class_name: "Treasure", as: :looter + accepts_nested_attributes_for :trinkets, allow_destroy: true accepts_nested_attributes_for :ship validates_presence_of :name diff --git a/activerecord/test/models/shop.rb b/activerecord/test/models/shop.rb index 607a0a5b41..f9d23d13b0 100644 --- a/activerecord/test/models/shop.rb +++ b/activerecord/test/models/shop.rb @@ -1,10 +1,10 @@ module Shop class Collection < ActiveRecord::Base - has_many :products, :dependent => :nullify + has_many :products, dependent: :nullify end class Product < ActiveRecord::Base - has_many :variants, :dependent => :delete_all + has_many :variants, dependent: :delete_all belongs_to :type class Type < ActiveRecord::Base diff --git a/activerecord/test/models/sponsor.rb b/activerecord/test/models/sponsor.rb index ec3dcf8a97..3f142b25fe 100644 --- a/activerecord/test/models/sponsor.rb +++ b/activerecord/test/models/sponsor.rb @@ -1,7 +1,7 @@ class Sponsor < ActiveRecord::Base - belongs_to :sponsor_club, :class_name => "Club", :foreign_key => "club_id" - belongs_to :sponsorable, :polymorphic => true - belongs_to :thing, :polymorphic => true, :foreign_type => :sponsorable_type, :foreign_key => :sponsorable_id - belongs_to :sponsorable_with_conditions, -> { where :name => 'Ernie'}, :polymorphic => true, - :foreign_type => 'sponsorable_type', :foreign_key => 'sponsorable_id' + belongs_to :sponsor_club, class_name: "Club", foreign_key: "club_id" + belongs_to :sponsorable, polymorphic: true + belongs_to :thing, polymorphic: true, foreign_type: :sponsorable_type, foreign_key: :sponsorable_id + belongs_to :sponsorable_with_conditions, -> { where name: "Ernie" }, polymorphic: true, + foreign_type: "sponsorable_type", foreign_key: "sponsorable_id" end diff --git a/activerecord/test/models/subject.rb b/activerecord/test/models/subject.rb index 8e28f8b86b..29e290825e 100644 --- a/activerecord/test/models/subject.rb +++ b/activerecord/test/models/subject.rb @@ -1,16 +1,14 @@ # used for OracleSynonymTest, see test/synonym_test_oracle.rb # class Subject < ActiveRecord::Base - # added initialization of author_email_address in the same way as in Topic class # as otherwise synonym test was failing after_initialize :set_email_address protected def set_email_address - unless self.persisted? - self.author_email_address = 'test@test.com' + unless persisted? + self.author_email_address = "test@test.com" end end - end diff --git a/activerecord/test/models/subscriber.rb b/activerecord/test/models/subscriber.rb index 76e85a0cd3..a820329003 100644 --- a/activerecord/test/models/subscriber.rb +++ b/activerecord/test/models/subscriber.rb @@ -1,7 +1,7 @@ class Subscriber < ActiveRecord::Base - self.primary_key = 'nick' + self.primary_key = "nick" has_many :subscriptions - has_many :books, :through => :subscriptions + has_many :books, through: :subscriptions end class SpecialSubscriber < Subscriber diff --git a/activerecord/test/models/subscription.rb b/activerecord/test/models/subscription.rb index bcac4738a3..1cedf6deae 100644 --- a/activerecord/test/models/subscription.rb +++ b/activerecord/test/models/subscription.rb @@ -1,4 +1,4 @@ class Subscription < ActiveRecord::Base - belongs_to :subscriber, :counter_cache => :books_count + belongs_to :subscriber, counter_cache: :books_count belongs_to :book end diff --git a/activerecord/test/models/tag.rb b/activerecord/test/models/tag.rb index 80d4725f7e..c907aea10f 100644 --- a/activerecord/test/models/tag.rb +++ b/activerecord/test/models/tag.rb @@ -1,7 +1,13 @@ class Tag < ActiveRecord::Base has_many :taggings - has_many :taggables, :through => :taggings + has_many :taggables, through: :taggings has_one :tagging - has_many :tagged_posts, :through => :taggings, :source => 'taggable', :source_type => 'Post' + has_many :tagged_posts, through: :taggings, source: "taggable", source_type: "Post" +end + +class OrderedTag < Tag + self.table_name = "tags" + + has_many :taggings, -> { order("taggings.id DESC") }, foreign_key: "tag_id" end diff --git a/activerecord/test/models/tagging.rb b/activerecord/test/models/tagging.rb index a6c05da26a..f739b4a197 100644 --- a/activerecord/test/models/tagging.rb +++ b/activerecord/test/models/tagging.rb @@ -4,10 +4,10 @@ end class Tagging < ActiveRecord::Base belongs_to :tag, -> { includes(:tagging) } - belongs_to :super_tag, :class_name => 'Tag', :foreign_key => 'super_tag_id' - belongs_to :invalid_tag, :class_name => 'Tag', :foreign_key => 'tag_id' - belongs_to :blue_tag, -> { where :tags => { :name => 'Blue' } }, :class_name => 'Tag', :foreign_key => :tag_id - belongs_to :tag_with_primary_key, :class_name => 'Tag', :foreign_key => :tag_id, :primary_key => :custom_primary_key - belongs_to :taggable, :polymorphic => true, :counter_cache => :tags_count - has_many :things, :through => :taggable + belongs_to :super_tag, class_name: "Tag", foreign_key: "super_tag_id" + belongs_to :invalid_tag, class_name: "Tag", foreign_key: "tag_id" + belongs_to :blue_tag, -> { where tags: { name: "Blue" } }, class_name: "Tag", foreign_key: :tag_id + belongs_to :tag_with_primary_key, class_name: "Tag", foreign_key: :tag_id, primary_key: :custom_primary_key + belongs_to :taggable, polymorphic: true, counter_cache: :tags_count + has_many :things, through: :taggable end diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index 176bc79dc7..db04735d01 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -2,18 +2,18 @@ class Topic < ActiveRecord::Base scope :base, -> { all } scope :written_before, lambda { |time| if time - where 'written_on < ?', time + where "written_on < ?", time end } - scope :approved, -> { where(:approved => true) } - scope :rejected, -> { where(:approved => false) } + scope :approved, -> { where(approved: true) } + scope :rejected, -> { where(approved: false) } scope :scope_with_lambda, lambda { all } - scope :by_lifo, -> { where(:author_name => 'lifo') } - scope :replied, -> { where 'replies_count > 0' } + scope :by_lifo, -> { where(author_name: "lifo") } + scope :replied, -> { where "replies_count > 0" } - scope 'approved_as_string', -> { where(:approved => true) } + scope "approved_as_string", -> { where(approved: true) } scope :anonymous_extension, -> { all } do def one 1 @@ -22,7 +22,7 @@ class Topic < ActiveRecord::Base scope :with_object, Class.new(Struct.new(:klass)) { def call - klass.where(:approved => true) + klass.where(approved: true) end }.new(self) @@ -33,22 +33,16 @@ class Topic < ActiveRecord::Base end has_many :replies, dependent: :destroy, foreign_key: "parent_id", autosave: true - has_many :approved_replies, -> { approved }, class_name: 'Reply', foreign_key: "parent_id", counter_cache: 'replies_count' + has_many :approved_replies, -> { approved }, class_name: "Reply", foreign_key: "parent_id", counter_cache: "replies_count" - has_many :unique_replies, :dependent => :destroy, :foreign_key => "parent_id" - has_many :silly_unique_replies, :dependent => :destroy, :foreign_key => "parent_id" + has_many :unique_replies, dependent: :destroy, foreign_key: "parent_id" + has_many :silly_unique_replies, dependent: :destroy, foreign_key: "parent_id" serialize :content before_create :default_written_on before_destroy :destroy_children - # Explicitly define as :date column so that returned Oracle DATE values would be typecasted to Date and not Time. - # Some tests depend on assumption that this attribute will have Date values. - if current_adapter?(:OracleEnhancedAdapter) - set_date_columns :last_read - end - def parent Topic.find(parent_id) end @@ -90,8 +84,8 @@ class Topic < ActiveRecord::Base end def set_email_address - unless self.persisted? - self.author_email_address = 'test@test.com' + unless persisted? + self.author_email_address = "test@test.com" end end @@ -119,6 +113,6 @@ end module Web class Topic < ActiveRecord::Base - has_many :replies, :dependent => :destroy, :foreign_key => "parent_id", :class_name => 'Web::Reply' + has_many :replies, dependent: :destroy, foreign_key: "parent_id", class_name: "Web::Reply" end end diff --git a/activerecord/test/models/treasure.rb b/activerecord/test/models/treasure.rb index 63ff0c23ec..fb2a5d44e2 100644 --- a/activerecord/test/models/treasure.rb +++ b/activerecord/test/models/treasure.rb @@ -1,11 +1,11 @@ class Treasure < ActiveRecord::Base has_and_belongs_to_many :parrots - belongs_to :looter, :polymorphic => true + belongs_to :looter, polymorphic: true # No counter_cache option given belongs_to :ship - has_many :price_estimates, :as => :estimate_of - has_and_belongs_to_many :rich_people, join_table: 'peoples_treasures', validate: false + has_many :price_estimates, as: :estimate_of + has_and_belongs_to_many :rich_people, join_table: "peoples_treasures", validate: false accepts_nested_attributes_for :looter end diff --git a/activerecord/test/models/treaty.rb b/activerecord/test/models/treaty.rb index 41fd1350f3..373cd48f71 100644 --- a/activerecord/test/models/treaty.rb +++ b/activerecord/test/models/treaty.rb @@ -1,7 +1,5 @@ class Treaty < ActiveRecord::Base - self.primary_key = :treaty_id has_and_belongs_to_many :countries - end diff --git a/activerecord/test/models/user.rb b/activerecord/test/models/user.rb index f5dc93e994..47649e0a77 100644 --- a/activerecord/test/models/user.rb +++ b/activerecord/test/models/user.rb @@ -1,6 +1,12 @@ +require "models/job" + class User < ActiveRecord::Base has_secure_token has_secure_token :auth_token + + has_and_belongs_to_many :jobs_pool, + class_name: Job, + join_table: "jobs_pool" end class UserWithNotification < User diff --git a/activerecord/test/models/uuid_item.rb b/activerecord/test/models/uuid_item.rb new file mode 100644 index 0000000000..2353e40213 --- /dev/null +++ b/activerecord/test/models/uuid_item.rb @@ -0,0 +1,6 @@ +class UuidItem < ActiveRecord::Base +end + +class UuidValidatingItem < UuidItem + validates_uniqueness_of :uuid +end diff --git a/activerecord/test/models/vegetables.rb b/activerecord/test/models/vegetables.rb index 1f41cde3a5..a4590d06e0 100644 --- a/activerecord/test/models/vegetables.rb +++ b/activerecord/test/models/vegetables.rb @@ -1,9 +1,8 @@ class Vegetable < ActiveRecord::Base - validates_presence_of :name def self.inheritance_column - 'custom_type' + "custom_type" end end @@ -20,5 +19,5 @@ class KingCole < GreenCabbage end class RedCabbage < Cabbage - belongs_to :seller, :class_name => 'Company' + belongs_to :seller, class_name: "Company" end diff --git a/activerecord/test/models/vehicle.rb b/activerecord/test/models/vehicle.rb index ef26170f1f..855bc4e325 100644 --- a/activerecord/test/models/vehicle.rb +++ b/activerecord/test/models/vehicle.rb @@ -4,4 +4,4 @@ class Vehicle < ActiveRecord::Base end class Bus < Vehicle -end
\ No newline at end of file +end diff --git a/activerecord/test/models/vertex.rb b/activerecord/test/models/vertex.rb index 48bb851e62..3d19433b6f 100644 --- a/activerecord/test/models/vertex.rb +++ b/activerecord/test/models/vertex.rb @@ -1,9 +1,9 @@ # This class models a vertex in a directed graph. class Vertex < ActiveRecord::Base - has_many :sink_edges, :class_name => 'Edge', :foreign_key => 'source_id' - has_many :sinks, :through => :sink_edges + has_many :sink_edges, class_name: "Edge", foreign_key: "source_id" + has_many :sinks, through: :sink_edges has_and_belongs_to_many :sources, - :class_name => 'Vertex', :join_table => 'edges', - :foreign_key => 'sink_id', :association_foreign_key => 'source_id' + class_name: "Vertex", join_table: "edges", + foreign_key: "sink_id", association_foreign_key: "source_id" end diff --git a/activerecord/test/models/wheel.rb b/activerecord/test/models/wheel.rb index 26868bce5e..cba2b3e518 100644 --- a/activerecord/test/models/wheel.rb +++ b/activerecord/test/models/wheel.rb @@ -1,3 +1,3 @@ class Wheel < ActiveRecord::Base - belongs_to :wheelable, :polymorphic => true, :counter_cache => true + belongs_to :wheelable, polymorphic: true, counter_cache: true end diff --git a/activerecord/test/models/without_table.rb b/activerecord/test/models/without_table.rb index 50c824e4ac..7c0fc286e1 100644 --- a/activerecord/test/models/without_table.rb +++ b/activerecord/test/models/without_table.rb @@ -1,3 +1,3 @@ class WithoutTable < ActiveRecord::Base - default_scope -> { where(:published => true) } + default_scope -> { where(published: true) } end diff --git a/activerecord/test/models/zine.rb b/activerecord/test/models/zine.rb index c2d0fdaf25..3f2b348b46 100644 --- a/activerecord/test/models/zine.rb +++ b/activerecord/test/models/zine.rb @@ -1,3 +1,3 @@ class Zine < ActiveRecord::Base - has_many :interests, :inverse_of => :zine + has_many :interests, inverse_of: :zine end diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb index 101e657982..9a203a7293 100644 --- a/activerecord/test/schema/mysql2_specific_schema.rb +++ b/activerecord/test/schema/mysql2_specific_schema.rb @@ -1,8 +1,8 @@ ActiveRecord::Schema.define do - if ActiveRecord::Base.connection.version >= '5.6.0' + if ActiveRecord::Base.connection.version >= "5.6.0" create_table :datetime_defaults, force: true do |t| - t.datetime :modified_datetime, default: -> { 'CURRENT_TIMESTAMP' } + t.datetime :modified_datetime, default: -> { "CURRENT_TIMESTAMP" } end end @@ -21,18 +21,19 @@ ActiveRecord::Schema.define do t.index :var_binary end - create_table :key_tests, force: true, options: 'ENGINE=MyISAM' do |t| + create_table :key_tests, force: true, options: "ENGINE=MyISAM" do |t| t.string :awesome t.string :pizza t.string :snacks - t.index :awesome, type: :fulltext, name: 'index_key_tests_on_awesome' - t.index :pizza, using: :btree, name: 'index_key_tests_on_pizza' - t.index :snacks, name: 'index_key_tests_on_snack' + t.index :awesome, type: :fulltext, name: "index_key_tests_on_awesome" + t.index :pizza, using: :btree, name: "index_key_tests_on_pizza" + t.index :snacks, name: "index_key_tests_on_snack" end create_table :collation_tests, id: false, force: true do |t| - t.string :string_cs_column, limit: 1, collation: 'utf8_bin' - t.string :string_ci_column, limit: 1, collation: 'utf8_general_ci' + t.string :string_cs_column, limit: 1, collation: "utf8_bin" + t.string :string_ci_column, limit: 1, collation: "utf8_general_ci" + t.binary :binary_column, limit: 1 end ActiveRecord::Base.connection.execute <<-SQL diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index 3a5d73a0ed..f00b858ea6 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -1,6 +1,6 @@ ActiveRecord::Schema.define do - enable_extension!('uuid-ossp', ActiveRecord::Base.connection) + enable_extension!("uuid-ossp", ActiveRecord::Base.connection) create_table :uuid_parents, id: :uuid, force: true do |t| t.string :name @@ -12,16 +12,16 @@ ActiveRecord::Schema.define do end create_table :defaults, force: true do |t| - t.date :modified_date, default: -> { 'CURRENT_DATE' } - t.date :modified_date_function, default: -> { 'now()' } - t.date :fixed_date, default: '2004-01-01' - t.datetime :modified_time, default: -> { 'CURRENT_TIMESTAMP' } - t.datetime :modified_time_function, default: -> { 'now()' } - t.datetime :fixed_time, default: '2004-01-01 00:00:00.000000-00' - t.column :char1, 'char(1)', default: 'Y' - t.string :char2, limit: 50, default: 'a varchar field' - t.text :char3, default: 'a text field' - t.bigint :bigint_default, default: -> { '0::bigint' } + t.date :modified_date, default: -> { "CURRENT_DATE" } + t.date :modified_date_function, default: -> { "now()" } + t.date :fixed_date, default: "2004-01-01" + t.datetime :modified_time, default: -> { "CURRENT_TIMESTAMP" } + t.datetime :modified_time_function, default: -> { "now()" } + t.datetime :fixed_time, default: "2004-01-01 00:00:00.000000-00" + t.column :char1, "char(1)", default: "Y" + 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: '--- [] ' @@ -32,12 +32,12 @@ ActiveRecord::Schema.define do drop_table table_name, if_exists: true end - execute 'DROP SEQUENCE IF EXISTS companies_nonstd_seq CASCADE' - execute 'CREATE SEQUENCE companies_nonstd_seq START 101 OWNED BY companies.id' + 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')" - execute 'DROP SEQUENCE IF EXISTS companies_id_seq' + execute "DROP SEQUENCE IF EXISTS companies_id_seq" - execute 'DROP FUNCTION IF EXISTS partitioned_insert_trigger()' + execute "DROP FUNCTION IF EXISTS partitioned_insert_trigger()" %w(accounts_id_seq developers_id_seq projects_id_seq topics_id_seq customers_id_seq orders_id_seq).each do |seq_name| execute "SELECT setval('#{seq_name}', 100)" @@ -88,7 +88,7 @@ _SQL FOR EACH ROW EXECUTE PROCEDURE partitioned_insert_trigger(); _SQL rescue ActiveRecord::StatementInvalid => e - if e.message =~ /language "plpgsql" does not exist/ + if e.message.include?('language "plpgsql" does not exist') execute "CREATE LANGUAGE 'plpgsql';" retry else @@ -106,4 +106,9 @@ _SQL t.integer :big_int_data_points, limit: 8, array: true t.decimal :decimal_array_default, array: true, default: [1.23, 3.45] end + + create_table :uuid_items, force: true, id: false do |t| + t.uuid :uuid, primary_key: true + t.string :title + end end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index b9e0706d60..983ac076a9 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -1,10 +1,4 @@ ActiveRecord::Schema.define do - def except(adapter_names_to_exclude) - unless [adapter_names_to_exclude].flatten.include?(adapter_name) - yield - end - end - # ------------------------------------------------------------------- # # # # Please keep these create table statements in alphabetical order # @@ -27,7 +21,7 @@ ActiveRecord::Schema.define do t.string :settings, null: true, limit: 1024 # MySQL does not allow default values for blobs. Fake it out with a # big varchar below. - t.string :preferences, null: true, default: '', limit: 1024 + t.string :preferences, null: true, default: "", limit: 1024 t.string :json_data, null: true, limit: 1024 t.string :json_data_empty, null: true, default: "", limit: 1024 t.text :params @@ -104,7 +98,8 @@ ActiveRecord::Schema.define do t.column :author_visibility, :integer, default: 0 t.column :illustrator_visibility, :integer, default: 0 t.column :font_size, :integer, default: 0 - t.column :cover, :string, default: 'hard' + t.column :difficulty, :integer, default: 0 + t.column :cover, :string, default: "hard" end create_table :booleans, force: true do |t| @@ -202,9 +197,10 @@ ActiveRecord::Schema.define do t.integer :rating, default: 1 t.integer :account_id t.string :description, default: "" - t.index [:firm_id, :type, :rating], name: "company_index" + t.index [:firm_id, :type, :rating], name: "company_index", length: { type: 10 }, order: { rating: :desc } t.index [:firm_id, :type], name: "company_partial_index", where: "rating > 10" - t.index :name, name: 'company_name_index', using: :btree + t.index :name, name: "company_name_index", using: :btree + t.index "lower(name)", name: "company_expression_index" if supports_expression_index? end create_table :content, force: true do |t| @@ -303,7 +299,7 @@ ActiveRecord::Schema.define do create_table :edges, force: true, id: false do |t| t.column :source_id, :integer, null: false t.column :sink_id, :integer, null: false - t.index [:source_id, :sink_id], unique: true, name: 'unique_edge_index' + t.index [:source_id, :sink_id], unique: true, name: "unique_edge_index" end create_table :engines, force: true do |t| @@ -395,6 +391,11 @@ ActiveRecord::Schema.define do t.integer :ideal_reference_id end + create_table :jobs_pool, force: true, id: false do |t| + t.references :job, null: false, index: true + t.references :user, null: false, index: true + end + create_table :keyboards, force: true, id: false do |t| t.primary_key :key_number t.string :name @@ -414,6 +415,14 @@ ActiveRecord::Schema.define do t.references :student end + create_table :students, force: true do |t| + t.string :name + t.boolean :active + t.integer :college_id + end + + add_foreign_key :lessons_students, :students, on_delete: :cascade + create_table :lint_models, force: true create_table :line_items, force: true do |t| @@ -421,11 +430,18 @@ ActiveRecord::Schema.define do t.integer :amount end + create_table :lions, force: true do |t| + t.integer :gender + t.boolean :is_vegetarian, default: false + end + create_table :lock_without_defaults, force: true do |t| + t.column :title, :string t.column :lock_version, :integer end create_table :lock_without_defaults_cust, force: true do |t| + t.column :title, :string t.column :custom_lock_version, :integer end @@ -615,6 +631,12 @@ ActiveRecord::Schema.define do end end + create_table :pets_treasures, force: true do |t| + t.column :treasure_id, :integer + t.column :pet_id, :integer + t.column :rainbow_color, :string + end + create_table :pirates, force: true do |t| t.column :catchphrase, :string t.column :parrot_id, :integer @@ -771,12 +793,6 @@ ActiveRecord::Schema.define do t.integer :lock_version, null: false, default: 0 end - create_table :students, force: true do |t| - t.string :name - t.boolean :active - t.integer :college_id - end - create_table :subscribers, force: true, id: false do |t| t.string :nick, null: false t.string :name @@ -881,12 +897,12 @@ ActiveRecord::Schema.define do t.column :label, :string end - create_table 'warehouse-things', force: true do |t| + create_table "warehouse-things", force: true do |t| t.integer :value end [:circles, :squares, :triangles, :non_poly_ones, :non_poly_twos].each do |t| - create_table(t, force: true) { } + create_table(t, force: true) {} end # NOTE - the following 4 tables are used by models that have :inverse_of options on the associations @@ -921,15 +937,15 @@ ActiveRecord::Schema.define do t.string :title end - create_table :countries, force: true, id: false, primary_key: 'country_id' do |t| + create_table :countries, force: true, id: false, primary_key: "country_id" do |t| t.string :country_id t.string :name end - create_table :treaties, force: true, id: false, primary_key: 'treaty_id' do |t| + create_table :treaties, force: true, id: false, primary_key: "treaty_id" do |t| t.string :treaty_id t.string :name end - create_table :countries_treaties, force: true, id: false do |t| + create_table :countries_treaties, force: true, primary_key: [:country_id, :treaty_id] do |t| t.string :country_id, null: false t.string :treaty_id, null: false end @@ -946,9 +962,9 @@ ActiveRecord::Schema.define do t.string :name end create_table :weirds, force: true do |t| - t.string 'a$b' - t.string 'ãªã¾ãˆ' - t.string 'from' + t.string "a$b" + t.string "ãªã¾ãˆ" + t.string "from" end create_table :nodes, force: true do |t| @@ -986,7 +1002,7 @@ ActiveRecord::Schema.define do create_table :records, force: true do |t| end - except 'SQLite' do + 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 @@ -996,14 +1012,13 @@ ActiveRecord::Schema.define do end add_foreign_key :fk_test_has_fk, :fk_test_has_pk, column: "fk_id", name: "fk_name", primary_key: "pk_id" - add_foreign_key :lessons_students, :students end create_table :overloaded_types, force: true do |t| t.float :overloaded_float, default: 500 t.float :unoverloaded_float t.string :overloaded_string_with_limit, limit: 255 - t.string :string_with_default, default: 'the original default' + t.string :string_with_default, default: "the original default" end create_table :users, force: true do |t| diff --git a/activerecord/test/schema/sqlite_specific_schema.rb b/activerecord/test/schema/sqlite_specific_schema.rb index b5552c2755..cc7c36fe2b 100644 --- a/activerecord/test/schema/sqlite_specific_schema.rb +++ b/activerecord/test/schema/sqlite_specific_schema.rb @@ -1,8 +1,4 @@ ActiveRecord::Schema.define do - create_table :table_with_autoincrement, :force => true do |t| - t.column :name, :string - end - execute "DROP TABLE fk_test_has_fk" rescue nil execute "DROP TABLE fk_test_has_pk" rescue nil execute <<_SQL diff --git a/activerecord/test/support/config.rb b/activerecord/test/support/config.rb index 6d123688a3..d0717f7b34 100644 --- a/activerecord/test/support/config.rb +++ b/activerecord/test/support/config.rb @@ -1,7 +1,7 @@ -require 'yaml' -require 'erubis' -require 'fileutils' -require 'pathname' +require "yaml" +require "erubis" +require "fileutils" +require "pathname" module ARTest class << self @@ -12,12 +12,12 @@ module ARTest private def config_file - Pathname.new(ENV['ARCONFIG'] || TEST_ROOT + '/config.yml') + Pathname.new(ENV["ARCONFIG"] || TEST_ROOT + "/config.yml") end def read_config unless config_file.exist? - FileUtils.cp TEST_ROOT + '/config.example.yml', config_file + FileUtils.cp TEST_ROOT + "/config.example.yml", config_file end erb = Erubis::Eruby.new(config_file.read) @@ -25,15 +25,15 @@ module ARTest end def expand_config(config) - config['connections'].each do |adapter, connection| - dbs = [['arunit', 'activerecord_unittest'], ['arunit2', 'activerecord_unittest2']] + config["connections"].each do |adapter, connection| + dbs = [["arunit", "activerecord_unittest"], ["arunit2", "activerecord_unittest2"]] dbs.each do |name, dbname| unless connection[name].is_a?(Hash) - connection[name] = { 'database' => connection[name] } + connection[name] = { "database" => connection[name] } end - connection[name]['database'] ||= dbname - connection[name]['adapter'] ||= adapter + connection[name]["database"] ||= dbname + connection[name]["adapter"] ||= adapter end end diff --git a/activerecord/test/support/connection.rb b/activerecord/test/support/connection.rb index c5334e8596..c9260398e2 100644 --- a/activerecord/test/support/connection.rb +++ b/activerecord/test/support/connection.rb @@ -1,15 +1,15 @@ -require 'active_support/logger' -require 'models/college' -require 'models/course' -require 'models/professor' +require "active_support/logger" +require "models/college" +require "models/course" +require "models/professor" module ARTest def self.connection_name - ENV['ARCONN'] || config['default_connection'] + ENV["ARCONN"] || config["default_connection"] end def self.connection_config - config['connections'][connection_name] + config["connections"][connection_name] end def self.connect diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 3d4cc8fae6..83ff80e31a 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,553 +1,245 @@ -* Add `Array#second_to_last` and `Array#third_to_last` methods. +* Use `Hash#compact` and `Hash#compact!` from Ruby 2.4. Old Ruby versions + will continue to get these methods from Active Support as before. - *Brian Christian* + *Prathamesh Sonpatki* -* Fix regression in `Hash#dig` for HashWithIndifferentAccess. - *Jon Moss* +* Fix `ActiveSupport::TimeZone#strptime`. + Support for timestamps in format of seconds (%s) and milliseconds (%Q). -## Rails 5.0.0.beta2 (February 01, 2016) ## + Fixes #26840. -* Change number_to_currency behavior for checking negativity. + *Lev Denisov* - Used `to_f.negative` instead of using `to_f.phase` for checking negativity - of a number in number_to_currency helper. - This change works same for all cases except when number is "-0.0". +* Fix `DateAndTime::Calculations#copy_time_to`. Copy `nsec` instead of `usec`. - -0.0.to_f.negative? => false - -0.0.to_f.phase? => 3.14 + Jumping forward or backward between weeks now preserves nanosecond digits. - This change reverts changes from https://github.com/rails/rails/pull/6512. - But it should be acceptable as we could not find any currency which - supports negative zeros. + *Josua Schmid* - *Prathamesh Sonpatki*, *Rafael Mendonça França* +* Fix `ActiveSupport::TimeWithZone#in` across DST boundaries. -* Match `HashWithIndifferentAccess#default`'s behaviour with `Hash#default`. + Previously calls to `in` were being sent to the non-DST aware + method `Time#since` via `method_missing`. It is now aliased to + the DST aware `ActiveSupport::TimeWithZone#+` which handles + transitions across DST boundaries, e.g: - *David Cornu* + Time.zone = "US/Eastern" -* Adds `:exception_object` key to `ActiveSupport::Notifications::Instrumenter` - payload when an exception is raised. + t = Time.zone.local(2016,11,6,1) + # => Sun, 06 Nov 2016 01:00:00 EDT -05:00 - Adds new key/value pair to payload when an exception is raised: - e.g. `:exception_object => #<RuntimeError: FAIL>`. + t.in(1.hour) + # => Sun, 06 Nov 2016 01:00:00 EST -05:00 - *Ryan T. Hosford* + Fixes #26580. -* Support extended grapheme clusters and UAX 29. + *Thomas Balthazar* - *Adam Roben* +* Remove unused parameter `options = nil` for `#clear` of + `ActiveSupport::Cache::Strategy::LocalCache::LocalStore` and + `ActiveSupport::Cache::Strategy::LocalCache`. -* Add petabyte and exabyte numeric conversion. + *Yosuke Kabuto* - *Akshay Vishnoi* +* Fix `thread_mattr_accessor` subclass no longer overwrites parent. -## Rails 5.0.0.beta1 (December 18, 2015) ## + Assigning a value to a subclass using `thread_mattr_accessor` no + longer changes the value of the parent class. This brings the + behavior inline with the documentation. -* Add thread_m/cattr_accessor/reader/writer suite of methods for declaring class and module variables that live per-thread. - This makes it easy to declare per-thread globals that are encapsulated. Note: This is a sharp edge. A wild proliferation - of globals is A Bad Thing. But like other sharp tools, when it's right, it's right. + Given: - Here's an example of a simple event tracking system where the object being tracked needs not pass a creator that it - doesn't need itself along: - - module Current - thread_mattr_accessor :account + class Account thread_mattr_accessor :user - - def self.reset() self.account = self.user = nil end - end - - class ApplicationController < ActionController::Base - before_action :set_current - after_action { Current.reset } - - private - def set_current - Current.account = Account.find(params[:account_id]) - Current.user = Current.account.users.find(params[:user_id]) - end - end - - class MessagesController < ApplicationController - def create - @message = Message.create!(message_params) - end end - class Message < ApplicationRecord - has_many :events - after_create :track_created - - private - def track_created - events.create! origin: self, action: :create - end - end - - class Event < ApplicationRecord - belongs_to :creator, class_name: 'User' - before_validation { self.creator ||= Current.user } - end - - *DHH* - - -* Deprecated `Module#qualified_const_` in favour of the builtin Module#const_ - methods. - - *Genadi Samokovarov* - -* Deprecate passing string to define callback. - - *Yuichiro Kaneko* - -* `ActiveSupport::Cache::Store#namespaced_key`, - `ActiveSupport::Cache::MemCachedStore#escape_key`, and - `ActiveSupport::Cache::FileStore#key_file_path` - are deprecated and replaced with `normalize_key` that now calls `super`. - - `ActiveSupport::Cache::LocaleCache#set_cache_value` is deprecated and replaced with `write_cache_value`. - - *Michael Grosser* - -* Implements an evented file watcher to asynchronously detect changes in the - application source code, routes, locales, etc. - - This watcher is disabled by default, applications my enable it in the configuration: - - # config/environments/development.rb - config.file_watcher = ActiveSupport::EventedFileUpdateChecker - - This feature depends on the [listen](https://github.com/guard/listen) gem: - - group :development do - gem 'listen', '~> 3.0.5' + class Customer < Account end - *Puneet Agarwal* and *Xavier Noria* - -* Added `Time.days_in_year` to return the number of days in the given year, or the - current year if no argument is provided. - - *Jon Pascoe* - -* Updated `parameterize` to preserve the case of a string, optionally. - - Example: - - parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth" - parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth" - - *Swaathi Kakarla* - -* `HashWithIndifferentAccess.new` respects the default value or proc on objects - that respond to `#to_hash`. `.new_from_hash_copying_default` simply invokes `.new`. - All calls to `.new_from_hash_copying_default` are replaced with `.new`. - - *Gordon Chan* - -* Change Integer#year to return a Fixnum instead of a Float to improve - consistency. - - Integer#years returned a Float while the rest of the accompanying methods - (days, weeks, months, etc.) return a Fixnum. + Account.user = "DHH" + Customer.user = "Rafael" Before: - 1.year # => 31557600.0 + Account.user # => "Rafael" After: - 1.year # => 31557600 - - *Konstantinos Rousis* - -* Handle invalid UTF-8 strings when HTML escaping. - - Use `ActiveSupport::Multibyte::Unicode.tidy_bytes` to handle invalid UTF-8 - strings in `ERB::Util.unwrapped_html_escape` and `ERB::Util.html_escape_once`. - Prevents user-entered input passed from a querystring into a form field from - causing invalid byte sequence errors. - - *Grey Baker* - -* Update `ActiveSupport::Multibyte::Chars#slice!` to return `nil` if the - arguments are out of bounds, to mirror the behavior of `String#slice!` - - *Gourav Tiwari* - -* Fix `number_to_human` so that 999999999 rounds to "1 Billion" instead of - "1000 Million". - - *Max Jacobson* - -* Fix `ActiveSupport::Deprecation#deprecate_methods` to report using the - current deprecator instance, where applicable. + Account.user # => "DHH" - *Brandon Dunne* + *Shinichi Maeshima* -* `Cache#fetch` instrumentation marks whether it was a `:hit`. +* Since weeks are no longer converted to days, add `:weeks` to the list of + parts that `ActiveSupport::TimeWithZone` will recognize as possibly being + of variable duration to take account of DST transitions. - *Robin Clowers* - -* `assert_difference` and `assert_no_difference` now returns the result of the - yielded block. - - Example: - - post = assert_difference -> { Post.count }, 1 do - Post.create - end - - *Lucas Mazza* - -* Short-circuit `blank?` on date and time values since they are never blank. - - Fixes #21657. + Fixes #26039. *Andrew White* -* Replaced deprecated `ThreadSafe::Cache` with its successor `Concurrent::Map` now that - the thread_safe gem has been merged into concurrent-ruby. - - *Jerry D'Antonio* - -* Updated Unicode version to 8.0.0 - - *Anshul Sharma* - -* `number_to_currency` and `number_with_delimiter` now accept custom `delimiter_pattern` option - to handle placement of delimiter, to support currency formats like INR +* Defines `Regexp.match?` for Ruby versions prior to 2.4. The predicate + has the same interface, but it does not have the performance boost. Its + purpose is to be able to write 2.4 compatible code. - Example: + *Xavier Noria* - number_to_currency(1230000, delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/, unit: '₹', format: "%u %n") - # => '₹ 12,30,000.00' +* Allow `MessageEncryptor` to take advantage of authenticated encryption modes. - *Vipul A M* + AEAD modes like `aes-256-gcm` provide both confidentiality and data + authenticity, eliminating the need to use `MessageVerifier` to check if the + encrypted data has been tampered with. This speeds up encryption/decryption + and results in shorter cipher text. -* Deprecate `:prefix` option of `number_to_human_size` with no replacement. + *Bart de Water* - *Jean Boussier* +* Introduce `assert_changes` and `assert_no_changes`. -* Fix `TimeWithZone#eql?` to properly handle `TimeWithZone` created from `DateTime`: - twz = DateTime.now.in_time_zone - twz.eql?(twz.dup) => true - - Fixes #14178. - - *Roque Pinel* - -* ActiveSupport::HashWithIndifferentAccess `select` and `reject` will now return - enumerator if called without block. - - Fixes #20095. - - *Bernard Potocki* - -* Removed `ActiveSupport::Concurrency::Latch`, superseded by `Concurrent::CountDownLatch` - from the concurrent-ruby gem. - - *Jerry D'Antonio* - -* Fix not calling `#default` on `HashWithIndifferentAccess#to_hash` when only - `default_proc` is set, which could raise. - - *Simon Eskildsen* - -* Fix setting `default_proc` on `HashWithIndifferentAccess#dup`. - - *Simon Eskildsen* - -* Fix a range of values for parameters of the Time#change. - - *Nikolay Kondratyev* - -* Add `Enumerable#pluck` to get the same values from arrays as from ActiveRecord - associations. - - Fixes #20339. - - *Kevin Deisz* - -* Add a bang version to `ActiveSupport::OrderedOptions` get methods which will raise - an `KeyError` if the value is `.blank?`. - - Before: + `assert_changes` is a more general `assert_difference` that works with any + value. - if (slack_url = Rails.application.secrets.slack_url).present? - # Do something worthwhile - else - # Raise as important secret password is not specified + assert_changes 'Error.current', from: nil, to: 'ERR' do + expected_bad_operation end - After: - - slack_url = Rails.application.secrets.slack_url! - - *Aditya Sanghi*, *Gaurish Sharma* - -* Remove deprecated `Class#superclass_delegating_accessor`. - Use `Class#class_attribute` instead. - - *Akshay Vishnoi* - -* Patch `Delegator` to work with `#try`. - - Fixes #5790. - - *Nate Smith* - -* Add `Integer#positive?` and `Integer#negative?` query methods - in the vein of `Fixnum#zero?`. - - This makes it nicer to do things like `bunch_of_numbers.select(&:positive?)`. - - *DHH* - -* Encoding `ActiveSupport::TimeWithZone` to YAML now preserves the timezone information. - - Fixes #9183. - - *Andrew White* - -* Added `ActiveSupport::TimeZone#strptime` to allow parsing times as if - from a given timezone. - - *Paul A Jungwirth* - -* `ActiveSupport::Callbacks#skip_callback` now raises an `ArgumentError` if - an unrecognized callback is removed. - - *Iain Beeston* - -* Added `ActiveSupport::ArrayInquirer` and `Array#inquiry`. - - Wrapping an array in an `ArrayInquirer` gives a friendlier way to check its - contents: - - variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet]) - - variants.phone? # => true - variants.tablet? # => true - variants.desktop? # => false - - variants.any?(:phone, :tablet) # => true - variants.any?(:phone, :desktop) # => true - variants.any?(:desktop, :watch) # => false - - `Array#inquiry` is a shortcut for wrapping the receiving array in an - `ArrayInquirer`. - - *George Claghorn* - -* Deprecate `alias_method_chain` in favour of `Module#prepend` introduced in - Ruby 2.0. - - *Kir Shatrov* - -* Added `#without` on `Enumerable` and `Array` to return a copy of an - enumerable without the specified elements. - - *Todd Bealmear* - -* Fixed a problem where `String#truncate_words` would get stuck with a complex - string. - - *Henrik Nygren* - -* Fixed a roundtrip problem with `AS::SafeBuffer` where primitive-like strings - will be dumped as primitives: - - Before: - - YAML.load ActiveSupport::SafeBuffer.new("Hello").to_yaml # => "Hello" - YAML.load ActiveSupport::SafeBuffer.new("true").to_yaml # => true - YAML.load ActiveSupport::SafeBuffer.new("false").to_yaml # => false - YAML.load ActiveSupport::SafeBuffer.new("1").to_yaml # => 1 - YAML.load ActiveSupport::SafeBuffer.new("1.1").to_yaml # => 1.1 - - After: - - YAML.load ActiveSupport::SafeBuffer.new("Hello").to_yaml # => "Hello" - YAML.load ActiveSupport::SafeBuffer.new("true").to_yaml # => "true" - YAML.load ActiveSupport::SafeBuffer.new("false").to_yaml # => "false" - YAML.load ActiveSupport::SafeBuffer.new("1").to_yaml # => "1" - YAML.load ActiveSupport::SafeBuffer.new("1.1").to_yaml # => "1.1" - - *Godfrey Chan* + Can be called with strings, to be evaluated in the binding (context) of + the block given to the assertion, or a lambda. -* Enable `number_to_percentage` to keep the number's precision by allowing - `:precision` to be `nil`. - - *Jack Xu* - -* `config_accessor` became a private method, as with Ruby's `attr_accessor`. - - *Akira Matsuda* - -* `AS::Testing::TimeHelpers#travel_to` now changes `DateTime.now` as well as - `Time.now` and `Date.today`. - - *Yuki Nishijima* - -* Add `file_fixture` to `ActiveSupport::TestCase`. - It provides a simple mechanism to access sample files in your test cases. - - By default file fixtures are stored in `test/fixtures/files`. This can be - configured per test-case using the `file_fixture_path` class attribute. - - *Yves Senn* - -* Return value of yielded block in `File.atomic_write`. - - *Ian Ker-Seymer* - -* Duplicate frozen array when assigning it to a `HashWithIndifferentAccess` so - that it doesn't raise a `RuntimeError` when calling `map!` on it in `convert_value`. - - Fixes #18550. - - *Aditya Kapoor* - -* Add missing time zone definitions for Russian Federation and sync them - with `zone.tab` file from tzdata version 2014j (latest). - - *Andrey Novikov* - -* Add `SecureRandom.base58` for generation of random base58 strings. - - *Matthew Draper*, *Guillermo Iguaran* - -* Add `#prev_day` and `#next_day` counterparts to `#yesterday` and - `#tomorrow` for `Date`, `Time`, and `DateTime`. - - *George Claghorn* - -* Add `same_time` option to `#next_week` and `#prev_week` for `Date`, `Time`, - and `DateTime`. - - *George Claghorn* - -* Add `#on_weekend?`, `#next_weekday`, `#prev_weekday` methods to `Date`, - `Time`, and `DateTime`. - - `#on_weekend?` returns `true` if the receiving date/time falls on a Saturday - or Sunday. + assert_changes -> { Error.current }, from: nil, to: 'ERR' do + expected_bad_operation + end - `#next_weekday` returns a new date/time representing the next day that does - not fall on a Saturday or Sunday. + The `from` and `to` arguments are compared with the case operator (`===`). - `#prev_weekday` returns a new date/time representing the previous day that - does not fall on a Saturday or Sunday. + assert_changes 'Error.current', from: nil, to: Error do + expected_bad_operation + end - *George Claghorn* + This is pretty useful, if you need to loosely compare a value. For example, + you need to test a token has been generated and it has that many random + characters. -* Change the default test order from `:sorted` to `:random`. + user = User.start_registration + assert_changes 'user.token', to: /\w{32}/ do + user.finish_registration + end - *Rafael Mendonça França* + *Genadi Samokovarov* -* Remove deprecated `ActiveSupport::JSON::Encoding::CircularReferenceError`. +* Add `:fallback_string` option to `Array#to_sentence`. If an empty array + calls the function and a fallback string option is set then it returns the + fallback string other than an empty string. - *Rafael Mendonça França* + *Mohamed Osama* -* Remove deprecated methods `ActiveSupport::JSON::Encoding.encode_big_decimal_as_string=` - and `ActiveSupport::JSON::Encoding.encode_big_decimal_as_string`. +* Fix `ActiveSupport::TimeZone#strptime`. Now raises `ArgumentError` when the + given time doesn't match the format. The error is the same as the one given + by Ruby's `Date.strptime`. Previously it raised + `NoMethodError: undefined method empty? for nil:NilClass.` due to a bug. - *Rafael Mendonça França* + Fixes #25701. -* Remove deprecated `ActiveSupport::SafeBuffer#prepend`. + *John Gesimondo* - *Rafael Mendonça França* +* `travel/travel_to` travel time helpers, now raise on nested calls, + as this can lead to confusing time stubbing. -* Remove deprecated methods at `Kernel`. + Instead of: - `silence_stderr`, `silence_stream`, `capture` and `quietly`. + travel_to 2.days.from_now do + # 2 days from today + travel_to 3.days.from_now do + # 5 days from today + end + end - *Rafael Mendonça França* + preferred way to achieve above is: -* Remove deprecated `active_support/core_ext/big_decimal/yaml_conversions` - file. + travel 2.days do + # 2 days from today + end - *Rafael Mendonça França* + travel 5.days do + # 5 days from today + end -* Remove deprecated methods `ActiveSupport::Cache::Store.instrument` and - `ActiveSupport::Cache::Store.instrument=`. + *Vipul A M* - *Rafael Mendonça França* +* Support parsing JSON time in ISO8601 local time strings in + `ActiveSupport::JSON.decode` when `parse_json_times` is enabled. + Strings in the format of `YYYY-MM-DD hh:mm:ss` (without a `Z` at + the end) will be parsed in the local timezone (`Time.zone`). In + addition, date strings (`YYYY-MM-DD`) are now parsed into `Date` + objects. -* Change the way in which callback chains can be halted. + *Grzegorz Witek* - The preferred method to halt a callback chain from now on is to explicitly - `throw(:abort)`. - In the past, callbacks could only be halted by explicitly providing a - terminator and by having a callback match the conditions of the terminator. +* Fixed `ActiveSupport::Logger.broadcast` so that calls to `#silence` now + properly delegate to all loggers. Silencing now properly suppresses logging + to both the log and the console. -* Add `ActiveSupport.halt_callback_chains_on_return_false` + *Kevin McPhillips* - Setting `ActiveSupport.halt_callback_chains_on_return_false` - to `true` will let an app support the deprecated way of halting Active Record, - and Active Model callback chains by returning `false`. +* Remove deprecated arguments in `assert_nothing_raised`. - Setting the value to `false` will tell the app to ignore any `false` value - returned by those callbacks, and only halt the chain upon `throw(:abort)`. + *Rafel Mendonça França* - When the configuration option is missing, its value is `true`, so older apps - ported to Rails 5.0 will not break (but display a deprecation warning). - For new Rails 5.0 apps, its value is set to `false` in an initializer, so - these apps will support the new behavior by default. +* `Date.to_s` doesn't produce too many spaces. For example, `to_s(:short)` + will now produce `01 Feb` instead of ` 1 Feb`. - *claudiob*, *Roque Pinel* + Fixes #25251. -* Changes arguments and default value of CallbackChain's `:terminator` option + *Sean Griffin* - Chains of callbacks defined without an explicit `:terminator` option will - now be halted as soon as a `before_` callback throws `:abort`. +* Introduce `Module#delegate_missing_to`. - Chains of callbacks defined with a `:terminator` option will maintain their - existing behavior of halting as soon as a `before_` callback matches the - terminator's expectation. + When building a decorator, a common pattern emerges: - *claudiob* + class Partition + def initialize(first_event) + @events = [ first_event ] + end -* Deprecate `MissingSourceFile` in favor of `LoadError`. + def people + if @events.first.detail.people.any? + @events.collect { |e| Array(e.detail.people) }.flatten.uniq + else + @events.collect(&:creator).uniq + end + end - `MissingSourceFile` was just an alias to `LoadError` and was not being - raised inside the framework. + private + def respond_to_missing?(name, include_private = false) + @events.respond_to?(name, include_private) + end - *Rafael Mendonça França* + def method_missing(method, *args, &block) + @events.send(method, *args, &block) + end + end -* Add support for error dispatcher classes in `ActiveSupport::Rescuable`. - Now it acts closer to Ruby's rescue. + With `Module#delegate_missing_to`, the above is condensed to: - Example: + class Partition + delegate_missing_to :@events - class BaseController < ApplicationController - module ErrorDispatcher - def self.===(other) - Exception === other && other.respond_to?(:status) - end + def initialize(first_event) + @events = [ first_event ] end - rescue_from ErrorDispatcher do |error| - render status: error.status, json: { error: error.to_s } + def people + if @events.first.detail.people.any? + @events.collect { |e| Array(e.detail.people) }.flatten.uniq + else + @events.collect(&:creator).uniq + end end end - *Genadi Samokovarov* - -* Add `#verified` and `#valid_message?` methods to `ActiveSupport::MessageVerifier` - - Previously, the only way to decode a message with `ActiveSupport::MessageVerifier` - was to use `#verify`, which would raise an exception on invalid messages. Now - `#verified` can also be used, which returns `nil` on messages that cannot be - decoded. + *Genadi Samokovarov*, *DHH* - Previously, there was no way to check if a message's format was valid without - attempting to decode it. `#valid_message?` is a boolean convenience method that - checks whether the message is valid without actually decoding it. +* Rescuable: If a handler doesn't match the exception, check for handlers + matching the exception's cause. - *Logan Leger* + *Jeremy Daer* -Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activesupport/CHANGELOG.md) for previous changes. +Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/activesupport/CHANGELOG.md) for previous changes. diff --git a/activesupport/Rakefile b/activesupport/Rakefile index 33ee62aa1b..2e1c50cc3d 100644 --- a/activesupport/Rakefile +++ b/activesupport/Rakefile @@ -1,13 +1,12 @@ -require 'rake/testtask' +require "rake/testtask" -task :default => :test +task default: :test task :package -task "package:clean" Rake::TestTask.new do |t| - t.libs << 'test' - t.pattern = 'test/**/*_test.rb' + t.libs << "test" + t.pattern = "test/**/*_test.rb" t.warning = true t.verbose = true t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) @@ -16,7 +15,7 @@ end namespace :test do task :isolated do Dir.glob("test/**/*_test.rb").all? do |file| - sh(Gem.ruby, '-w', '-Ilib:test', file) - end or raise "Failures" + sh(Gem.ruby, "-w", "-Ilib:test", file) + end || raise("Failures") end end diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index 68a80701ed..08370cba85 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -1,27 +1,27 @@ -version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip +version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY - s.name = 'activesupport' + s.name = "activesupport" s.version = version - s.summary = 'A toolkit of support libraries and Ruby core extensions extracted from the Rails framework.' - s.description = 'A toolkit of support libraries and Ruby core extensions extracted from the Rails framework. Rich support for multibyte strings, internationalization, time zones, and testing.' + s.summary = "A toolkit of support libraries and Ruby core extensions extracted from the Rails framework." + s.description = "A toolkit of support libraries and Ruby core extensions extracted from the Rails framework. Rich support for multibyte strings, internationalization, time zones, and testing." - s.required_ruby_version = '>= 2.2.2' + s.required_ruby_version = ">= 2.2.2" - s.license = 'MIT' + s.license = "MIT" - s.author = 'David Heinemeier Hansson' - s.email = 'david@loudthinking.com' - s.homepage = 'http://www.rubyonrails.org' + s.author = "David Heinemeier Hansson" + s.email = "david@loudthinking.com" + s.homepage = "http://rubyonrails.org" - s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*'] - s.require_path = 'lib' + s.files = Dir["CHANGELOG.md", "MIT-LICENSE", "README.rdoc", "lib/**/*"] + s.require_path = "lib" - s.rdoc_options.concat ['--encoding', 'UTF-8'] + s.rdoc_options.concat ["--encoding", "UTF-8"] - s.add_dependency 'i18n', '~> 0.7' - s.add_dependency 'tzinfo', '~> 1.1' - s.add_dependency 'minitest', '~> 5.1' - s.add_dependency 'concurrent-ruby', '~> 1.0' + s.add_dependency "i18n", "~> 0.7" + s.add_dependency "tzinfo", "~> 1.1" + s.add_dependency "minitest", "~> 5.1" + s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2" end diff --git a/activesupport/bin/generate_tables b/activesupport/bin/generate_tables index 2193533588..ef220e0329 100755 --- a/activesupport/bin/generate_tables +++ b/activesupport/bin/generate_tables @@ -1,18 +1,17 @@ #!/usr/bin/env ruby begin - $:.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib')) - require 'active_support' + $:.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) + require "active_support" rescue IOError end -require 'open-uri' -require 'tmpdir' +require "open-uri" +require "tmpdir" module ActiveSupport module Multibyte module Unicode - class UnicodeDatabase def load; end end @@ -20,10 +19,10 @@ module ActiveSupport class DatabaseGenerator BASE_URI = "http://www.unicode.org/Public/#{UNICODE_VERSION}/ucd/" SOURCES = { - :codepoints => BASE_URI + 'UnicodeData.txt', - :composition_exclusion => BASE_URI + 'CompositionExclusions.txt', - :grapheme_break_property => BASE_URI + 'auxiliary/GraphemeBreakProperty.txt', - :cp1252 => 'http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1252.TXT' + codepoints: BASE_URI + "UnicodeData.txt", + composition_exclusion: BASE_URI + "CompositionExclusions.txt", + grapheme_break_property: BASE_URI + "auxiliary/GraphemeBreakProperty.txt", + cp1252: "http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1252.TXT" } def initialize @@ -52,9 +51,9 @@ module ActiveSupport codepoint.code = $1.hex codepoint.combining_class = Integer($4) codepoint.decomp_type = $7 - codepoint.decomp_mapping = ($8=='') ? nil : $8.split.collect(&:hex) - codepoint.uppercase_mapping = ($16=='') ? 0 : $16.hex - codepoint.lowercase_mapping = ($17=='') ? 0 : $17.hex + codepoint.decomp_mapping = ($8=="") ? nil : $8.split.collect(&:hex) + codepoint.uppercase_mapping = ($16=="") ? 0 : $16.hex + codepoint.lowercase_mapping = ($17=="") ? 0 : $17.hex @ucd.codepoints[codepoint.code] = codepoint end @@ -62,8 +61,8 @@ module ActiveSupport if line =~ /^([0-9A-F.]+)\s*;\s*([\w]+)\s*#/ type = $2.downcase.intern @ucd.boundary[type] ||= [] - if $1.include? '..' - parts = $1.split '..' + if $1.include? ".." + parts = $1.split ".." @ucd.boundary[type] << (parts[0].hex..parts[1].hex) else @ucd.boundary[type] << $1.hex @@ -85,7 +84,7 @@ module ActiveSupport def create_composition_map @ucd.codepoints.each do |_, cp| - if !cp.nil? and cp.combining_class == 0 and cp.decomp_type.nil? and !cp.decomp_mapping.nil? and cp.decomp_mapping.length == 2 and @ucd.codepoints[cp.decomp_mapping[0]].combining_class == 0 and !@ucd.composition_exclusion.include?(cp.code) + if !cp.nil? && cp.combining_class == 0 && cp.decomp_type.nil? && !cp.decomp_mapping.nil? && cp.decomp_mapping.length == 2 && @ucd.codepoints[cp.decomp_mapping[0]].combining_class == 0 && !@ucd.composition_exclusion.include?(cp.code) @ucd.composition_map[cp.decomp_mapping[0]] ||= {} @ucd.composition_map[cp.decomp_mapping[0]][cp.decomp_mapping[1]] = cp.code end @@ -105,7 +104,7 @@ module ActiveSupport filename = File.join(Dir.tmpdir, "#{url.split('/').last}") unless File.exist?(filename) $stderr.puts "Downloading #{url.split('/').last}" - File.open(filename, 'wb') do |target| + File.open(filename, "wb") do |target| open(url) do |source| source.each_line { |line| target.write line } end @@ -120,7 +119,7 @@ module ActiveSupport end def dump_to(filename) - File.open(filename, 'wb') do |f| + File.open(filename, "wb") do |f| f.write Marshal.dump([@ucd.codepoints, @ucd.composition_exclusion, @ucd.composition_map, @ucd.boundary, @ucd.cp1252]) end end diff --git a/activesupport/bin/test b/activesupport/bin/test index 404cabba51..84a05bba08 100755 --- a/activesupport/bin/test +++ b/activesupport/bin/test @@ -1,4 +1,6 @@ #!/usr/bin/env ruby -COMPONENT_ROOT = File.expand_path("../../", __FILE__) + +COMPONENT_ROOT = File.expand_path("..", __dir__) require File.expand_path("../tools/test", COMPONENT_ROOT) + exit Minitest.run(ARGV) diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 94fe893149..2fc42e1975 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -21,11 +21,12 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ -require 'securerandom' +require "securerandom" require "active_support/dependencies/autoload" require "active_support/version" require "active_support/logger" require "active_support/lazy_load_hooks" +require "active_support/core_ext/date_and_time/compatibility" module ActiveSupport extend ActiveSupport::Autoload @@ -33,10 +34,13 @@ module ActiveSupport autoload :Concern autoload :Dependencies autoload :DescendantsTracker + autoload :ExecutionWrapper + autoload :Executor autoload :FileUpdateChecker autoload :EventedFileUpdateChecker autoload :LogSubscriber autoload :Notifications + autoload :Reloader eager_autoload do autoload :BacktraceCleaner @@ -82,6 +86,14 @@ module ActiveSupport def self.halt_callback_chains_on_return_false=(value) Callbacks.halt_and_display_warning_on_return_false = value end + + def self.to_time_preserves_timezone + DateAndTime::Compatibility.preserve_timezone + end + + def self.to_time_preserves_timezone=(value) + DateAndTime::Compatibility.preserve_timezone = value + end end autoload :I18n, "active_support/i18n" diff --git a/activesupport/lib/active_support/all.rb b/activesupport/lib/active_support/all.rb index f537818300..72a23075af 100644 --- a/activesupport/lib/active_support/all.rb +++ b/activesupport/lib/active_support/all.rb @@ -1,3 +1,3 @@ -require 'active_support' -require 'active_support/time' -require 'active_support/core_ext' +require "active_support" +require "active_support/time" +require "active_support/core_ext" diff --git a/activesupport/lib/active_support/array_inquirer.rb b/activesupport/lib/active_support/array_inquirer.rb index f59ddf5403..85122e39b2 100644 --- a/activesupport/lib/active_support/array_inquirer.rb +++ b/activesupport/lib/active_support/array_inquirer.rb @@ -9,8 +9,10 @@ module ActiveSupport # variants.desktop? # => false class ArrayInquirer < Array # Passes each element of +candidates+ collection to ArrayInquirer collection. - # The method returns true if at least one element is the same. If +candidates+ - # collection is not given, method returns true. + # The method returns true if any element from the ArrayInquirer collection + # is equal to the stringified or symbolized form of any element in the +candidates+ collection. + # + # If +candidates+ collection is not given, method returns true. # # variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet]) # @@ -30,11 +32,11 @@ module ActiveSupport private def respond_to_missing?(name, include_private = false) - name[-1] == '?' + name[-1] == "?" end def method_missing(name, *args) - if name[-1] == '?' + if name[-1] == "?" any?(name[0..-2]) else super diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb index e161ec4cca..169a58ecd1 100644 --- a/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/activesupport/lib/active_support/backtrace_cleaner.rb @@ -14,7 +14,7 @@ module ActiveSupport # # bc = BacktraceCleaner.new # bc.add_filter { |line| line.gsub(Rails.root.to_s, '') } # strip the Rails.root prefix - # bc.add_silencer { |line| line =~ /mongrel|rubygems/ } # skip any lines from mongrel or rubygems + # bc.add_silencer { |line| line =~ /puma|rubygems/ } # skip any lines from puma or rubygems # bc.clean(exception.backtrace) # perform the cleanup # # To reconfigure an existing BacktraceCleaner (like the default one in Rails) @@ -59,8 +59,8 @@ module ActiveSupport # Adds a silencer from the block provided. If the silencer returns +true+ # for a given line, it will be excluded from the clean backtrace. # - # # Will reject all lines that include the word "mongrel", like "/gems/mongrel/server.rb" or "/app/my_mongrel_server/rb" - # backtrace_cleaner.add_silencer { |line| line =~ /mongrel/ } + # # Will reject all lines that include the word "puma", like "/gems/puma/server.rb" or "/app/my_puma_server/rb" + # backtrace_cleaner.add_silencer { |line| line =~ /puma/ } def add_silencer(&block) @silencers << block end diff --git a/activesupport/lib/active_support/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb index 805b7a714f..70493c8da7 100644 --- a/activesupport/lib/active_support/benchmarkable.rb +++ b/activesupport/lib/active_support/benchmarkable.rb @@ -1,5 +1,5 @@ -require 'active_support/core_ext/benchmark' -require 'active_support/core_ext/hash/keys' +require "active_support/core_ext/benchmark" +require "active_support/core_ext/hash/keys" module ActiveSupport module Benchmarkable @@ -38,8 +38,8 @@ module ActiveSupport options[:level] ||= :info result = nil - ms = Benchmark.ms { result = options[:silence] ? silence { yield } : yield } - logger.send(options[:level], '%s (%.1fms)' % [ message, ms ]) + ms = Benchmark.ms { result = options[:silence] ? logger.silence { yield } : yield } + logger.send(options[:level], "%s (%.1fms)" % [ message, ms ]) result else yield diff --git a/activesupport/lib/active_support/builder.rb b/activesupport/lib/active_support/builder.rb index 321e462acd..0f010c5d96 100644 --- a/activesupport/lib/active_support/builder.rb +++ b/activesupport/lib/active_support/builder.rb @@ -1,5 +1,5 @@ begin - require 'builder' + require "builder" rescue LoadError => e $stderr.puts "You don't have builder installed in your application. Please add it to your Gemfile and run bundle install" raise e diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 610105f41c..ad02546755 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -1,29 +1,27 @@ -require 'benchmark' -require 'zlib' -require 'active_support/core_ext/array/extract_options' -require 'active_support/core_ext/array/wrap' -require 'active_support/core_ext/benchmark' -require 'active_support/core_ext/module/attribute_accessors' -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' +require "zlib" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/array/wrap" +require "active_support/core_ext/module/attribute_accessors" +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. module Cache - autoload :FileStore, 'active_support/cache/file_store' - autoload :MemoryStore, 'active_support/cache/memory_store' - autoload :MemCacheStore, 'active_support/cache/mem_cache_store' - autoload :NullStore, 'active_support/cache/null_store' + autoload :FileStore, "active_support/cache/file_store" + autoload :MemoryStore, "active_support/cache/memory_store" + autoload :MemCacheStore, "active_support/cache/mem_cache_store" + autoload :NullStore, "active_support/cache/null_store" # These options mean something to all cache implementations. Individual cache # implementations may support additional options. UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl] module Strategy - autoload :LocalCache, 'active_support/cache/strategy/local_cache' + autoload :LocalCache, "active_support/cache/strategy/local_cache" end class << self @@ -153,25 +151,25 @@ module ActiveSupport # or +write+. To specify the threshold at which to compress values, set the # <tt>:compress_threshold</tt> option. The default threshold is 16K. class Store - cattr_accessor :logger, :instance_writer => true + cattr_accessor :logger, instance_writer: true attr_reader :silence, :options alias :silence? :silence - # Create a new cache. The options will be passed to any write method calls + # Creates a new cache. The options will be passed to any write method calls # except for <tt>:namespace</tt> which can be used to set the global # namespace for the cache. def initialize(options = nil) @options = options ? options.dup : {} end - # Silence the logger. + # Silences the logger. def silence! @silence = true self end - # Silence the logger within a block. + # Silences the logger within a block. def mute previous_silence, @silence = defined?(@silence) && @silence, true yield @@ -198,10 +196,17 @@ module ActiveSupport # cache.fetch('city') # => "Duckburgh" # # You may also specify additional options via the +options+ argument. - # Setting <tt>force: true</tt> will force a cache miss: + # Setting <tt>force: true</tt> forces a cache "miss," meaning we treat + # the cache value as missing even if it's present. Passing a block is + # required when +force+ is true so this always results in a cache write. # # cache.write('today', 'Monday') - # cache.fetch('today', force: true) # => nil + # cache.fetch('today', force: true) { 'Tuesday' } # => 'Tuesday' + # cache.fetch('today', force: true) # => ArgumentError + # + # The +:force+ option is useful when you're calling some other method to + # ask whether you should force a cache write. Otherwise, it's clearer to + # just call <tt>Cache#write</tt>. # # Setting <tt>:compress</tt> will store a large cache entry set by the call # in a compressed format. @@ -243,14 +248,14 @@ module ActiveSupport # sleep 60 # # Thread.new do - # val_1 = cache.fetch('foo', race_condition_ttl: 10) do + # val_1 = cache.fetch('foo', race_condition_ttl: 10.seconds) do # sleep 1 # 'new value 1' # end # end # # Thread.new do - # val_2 = cache.fetch('foo', race_condition_ttl: 10) do + # val_2 = cache.fetch('foo', race_condition_ttl: 10.seconds) do # 'new value 2' # end # end @@ -292,6 +297,8 @@ module ActiveSupport else save_block_result_to_cache(name, options) { |_name| yield _name } end + elsif options && options[:force] + raise ArgumentError, "Missing block: Calling `Cache#fetch` with `force: true` requires a block." else read(name, options) end @@ -323,7 +330,7 @@ module ActiveSupport end end - # Read multiple values at once from the cache. Options can be passed + # Reads multiple values at once from the cache. Options can be passed # in the last argument. # # Some cache implementation may optimize this method. @@ -333,27 +340,28 @@ module ActiveSupport options = names.extract_options! options = merged_options(options) - instrument_multi(:read, names, options) do |payload| - results = {} - names.each do |name| - key = normalize_key(name, options) - entry = read_entry(key, options) - if entry - if entry.expired? - delete_entry(key, options) - else - results[name] = entry.value - end + results = {} + names.each do |name| + key = normalize_key(name, options) + entry = read_entry(key, options) + if entry + if entry.expired? + delete_entry(key, options) + else + results[name] = entry.value end end - results end + results end # Fetches data from the cache, using the given keys. If there is data in # the cache with the given keys, then that data is returned. Otherwise, # the supplied block is called for each key for which there was no data, # and the result will be written to the cache and returned. + # Therefore, you need to pass a block that returns the data to be written + # to the cache. If you do not want to write the cache when the cache is + # not found, use #read_multi. # # Options are passed to the underlying cache implementation. # @@ -367,6 +375,8 @@ module ActiveSupport # # "unknown_key" => "Fallback value for key: unknown_key" } # def fetch_multi(*names) + raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given? + options = names.extract_options! options = merged_options(options) results = read_multi(*names, options) @@ -415,7 +425,7 @@ module ActiveSupport end end - # Delete all entries with keys matching the pattern. + # Deletes all entries with keys matching the pattern. # # Options are passed to the underlying cache implementation. # @@ -424,7 +434,7 @@ module ActiveSupport raise NotImplementedError.new("#{self.class.name} does not support delete_matched") end - # Increment an integer value in the cache. + # Increments an integer value in the cache. # # Options are passed to the underlying cache implementation. # @@ -433,7 +443,7 @@ module ActiveSupport raise NotImplementedError.new("#{self.class.name} does not support increment") end - # Decrement an integer value in the cache. + # Decrements an integer value in the cache. # # Options are passed to the underlying cache implementation. # @@ -442,7 +452,7 @@ module ActiveSupport raise NotImplementedError.new("#{self.class.name} does not support decrement") end - # Cleanup the cache by removing expired entries. + # Cleanups the cache by removing expired entries. # # Options are passed to the underlying cache implementation. # @@ -451,18 +461,18 @@ module ActiveSupport raise NotImplementedError.new("#{self.class.name} does not support cleanup") end - # Clear the entire cache. Be careful with this method since it could + # Clears the entire cache. Be careful with this method since it could # affect other processes if shared cache is being used. # # The options hash is passed to the underlying cache implementation. # # All implementations may not support this method. - def clear(options = nil) + def clear raise NotImplementedError.new("#{self.class.name} does not support clear") end protected - # Add the namespace defined in the options to a pattern designed to + # 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. @@ -470,7 +480,7 @@ module ActiveSupport prefix = options[:namespace].is_a?(Proc) ? options[:namespace].call : options[:namespace] if prefix source = pattern.source - if source.start_with?('^') + if source.start_with?("^") source = source[1, source.length] else source = ".*#{source[0, source.length]}" @@ -481,26 +491,26 @@ module ActiveSupport end end - # Read an entry from the cache implementation. Subclasses must implement + # Reads an entry from the cache implementation. Subclasses must implement # this method. def read_entry(key, options) # :nodoc: raise NotImplementedError.new end - # Write an entry to the cache implementation. Subclasses must implement + # Writes an entry to the cache implementation. Subclasses must implement # this method. def write_entry(key, entry, options) # :nodoc: raise NotImplementedError.new end - # Delete an entry from the cache implementation. Subclasses must + # Deletes an entry from the cache implementation. Subclasses must # implement this method. def delete_entry(key, options) # :nodoc: raise NotImplementedError.new end private - # Merge the default options with ones specific to a method call. + # Merges the default options with ones specific to a method call. def merged_options(call_options) # :nodoc: if call_options options.merge(call_options) @@ -509,7 +519,7 @@ module ActiveSupport end end - # Expand key to be a consistent string value. Invoke +cache_key+ if + # 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: @@ -518,18 +528,18 @@ module ActiveSupport case key when Array if key.size > 1 - key = key.collect{|element| expanded_key(element)} + key = key.collect { |element| expanded_key(element) } else key = key.first end when Hash - key = key.sort_by { |k,_| k.to_s }.collect{|k,v| "#{k}=#{v}"} + key = key.sort_by { |k,_| k.to_s }.collect { |k,v| "#{k}=#{v}" } end key.to_param end - # Prefix a key with the namespace. Namespace and key will be delimited + # Prefixes a key with the namespace. Namespace and key will be delimited # with a colon. def normalize_key(key, options) key = expanded_key(key) @@ -550,20 +560,9 @@ module ActiveSupport def instrument(operation, key, options = nil) log { "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" } - payload = { :key => key } - payload.merge!(options) if options.is_a?(Hash) - ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield(payload) } - end - - def instrument_multi(operation, keys, options = nil) - log do - formatted_keys = keys.map { |k| "- #{k}" }.join("\n") - "Caches multi #{operation}:\n#{formatted_keys}#{options.blank? ? "" : " (#{options.inspect})"}" - end - - payload = { key: keys } + payload = { key: key } payload.merge!(options) if options.is_a?(Hash) - ActiveSupport::Notifications.instrument("cache_#{operation}_multi.active_support", payload) { yield(payload) } + ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) { yield(payload) } end def log @@ -578,7 +577,7 @@ module ActiveSupport # When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache # for a brief period while the entry is being recalculated. entry.expires_at = Time.now + race_ttl - write_entry(key, entry, :expires_in => race_ttl * 2) + write_entry(key, entry, expires_in: race_ttl * 2) else delete_entry(key, options) end @@ -588,12 +587,12 @@ module ActiveSupport end def get_entry_value(entry, name, options) - instrument(:fetch_hit, name, options) { |payload| } + instrument(:fetch_hit, name, options) {} entry.value end def save_block_result_to_cache(name, options) - result = instrument(:generate, name, options) do |payload| + result = instrument(:generate, name, options) do yield(name) end @@ -611,7 +610,7 @@ module ActiveSupport class Entry # :nodoc: DEFAULT_COMPRESS_LIMIT = 16.kilobytes - # Create a new cache entry for the specified value. Options supported are + # Creates a new cache entry for the specified value. Options supported are # +:compress+, +:compress_threshold+, and +:expires_in+. def initialize(value, options = {}) if should_compress?(value, options) @@ -630,7 +629,7 @@ module ActiveSupport compressed? ? uncompress(@value) : @value end - # Check if the entry is expired. The +expires_in+ parameter can override + # Checks if the entry is expired. The +expires_in+ parameter can override # the value set when the entry was created. def expired? @expires_in && @created_at + @expires_in <= Time.now.to_f @@ -665,7 +664,7 @@ module ActiveSupport end end - # Duplicate the value in a class. This is used by cache implementations that don't natively + # Duplicates the value in a class. This is used by cache implementations that don't natively # serialize entries to protect against accidental cache modifications. def dup_value! if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false) diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 99c55b1aa4..1971ff182e 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -1,7 +1,7 @@ -require 'active_support/core_ext/marshal' -require 'active_support/core_ext/file/atomic' -require 'active_support/core_ext/string/conversions' -require 'uri/common' +require "active_support/core_ext/marshal" +require "active_support/core_ext/file/atomic" +require "active_support/core_ext/string/conversions" +require "uri/common" module ActiveSupport module Cache @@ -16,8 +16,8 @@ module ActiveSupport DIR_FORMATTER = "%03X" FILENAME_MAX_SIZE = 228 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write) FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room - EXCLUDED_DIRS = ['.', '..'].freeze - GITKEEP_FILES = ['.gitkeep', '.keep'].freeze + EXCLUDED_DIRS = [".", ".."].freeze + GITKEEP_FILES = [".gitkeep", ".keep"].freeze def initialize(cache_path, options = nil) super(options) @@ -27,9 +27,9 @@ module ActiveSupport # Deletes all items from the cache. In this case it deletes all the entries in the specified # file store directory except for .keep or .gitkeep. Be careful which directory is specified in your # config file when using +FileStore+ because everything in that directory will be deleted. - def clear(options = nil) + def clear root_dirs = exclude_from(cache_path, EXCLUDED_DIRS + GITKEEP_FILES) - FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)}) + FileUtils.rm_r(root_dirs.collect { |f| File.join(cache_path, f) }) rescue Errno::ENOENT end @@ -80,7 +80,7 @@ module ActiveSupport def write_entry(key, entry, options) return false if options[:unless_exist] && File.exist?(key) ensure_cache_path(File.dirname(key)) - File.atomic_write(key, cache_path) {|f| Marshal.dump(entry, f)} + File.atomic_write(key, cache_path) { |f| Marshal.dump(entry, f) } true end @@ -102,7 +102,7 @@ module ActiveSupport # Lock a file for a block so only one process can modify it at a time. def lock_file(file_name, &block) # :nodoc: if File.exist?(file_name) - File.open(file_name, 'r+') do |f| + File.open(file_name, "r+") do |f| begin f.flock File::LOCK_EX yield diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index 174913365a..cfd5e39bc4 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -1,13 +1,13 @@ begin - require 'dalli' + require "dalli" rescue LoadError => e $stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install" raise e end -require 'digest/md5' -require 'active_support/core_ext/marshal' -require 'active_support/core_ext/array/extract_options' +require "digest/md5" +require "active_support/core_ext/marshal" +require "active_support/core_ext/array/extract_options" module ActiveSupport module Cache @@ -27,23 +27,23 @@ module ActiveSupport # Provide support for raw values in the local cache strategy. module LocalCacheWithRaw # :nodoc: protected - def read_entry(key, options) - entry = super - if options[:raw] && local_cache && entry - entry = deserialize_entry(entry.value) + def read_entry(key, options) + entry = super + if options[:raw] && local_cache && entry + entry = deserialize_entry(entry.value) + end + entry end - entry - end - def write_entry(key, entry, options) # :nodoc: - if options[:raw] && local_cache - raw_entry = Entry.new(entry.value.to_s) - raw_entry.expires_at = entry.expires_at - super(key, raw_entry, options) - else - super + def write_entry(key, entry, options) # :nodoc: + if options[:raw] && local_cache + raw_entry = Entry.new(entry.value.to_s) + raw_entry.expires_at = entry.expires_at + super(key, raw_entry, options) + else + super + end end - end end prepend Strategy::LocalCache @@ -85,7 +85,7 @@ module ActiveSupport @data = addresses.first else mem_cache_options = options.dup - UNIVERSAL_OPTIONS.each{|name| mem_cache_options.delete(name)} + UNIVERSAL_OPTIONS.each { |name| mem_cache_options.delete(name) } @data = self.class.build_mem_cache(*(addresses + [mem_cache_options])) end end @@ -96,16 +96,14 @@ module ActiveSupport options = names.extract_options! options = merged_options(options) - instrument_multi(:read, names, options) do - keys_to_names = Hash[names.map{|name| [normalize_key(name, options), name]}] - raw_values = @data.get_multi(keys_to_names.keys, :raw => true) - values = {} - raw_values.each do |key, value| - entry = deserialize_entry(value) - values[keys_to_names[key]] = entry.value unless entry.expired? - end - values + keys_to_names = Hash[names.map { |name| [normalize_key(name, options), name] }] + raw_values = @data.get_multi(keys_to_names.keys, raw: true) + values = {} + raw_values.each do |key, value| + entry = deserialize_entry(value) + values[keys_to_names[key]] = entry.value unless entry.expired? end + values end # Increment a cached value. This method uses the memcached incr atomic @@ -114,7 +112,7 @@ module ActiveSupport # to zero. def increment(name, amount = 1, options = nil) # :nodoc: options = merged_options(options) - instrument(:increment, name, :amount => amount) do + instrument(:increment, name, amount: amount) do rescue_error_with nil do @data.incr(normalize_key(name, options), amount) end @@ -127,7 +125,7 @@ module ActiveSupport # to zero. def decrement(name, amount = 1, options = nil) # :nodoc: options = merged_options(options) - instrument(:decrement, name, :amount => amount) do + instrument(:decrement, name, amount: amount) do rescue_error_with nil do @data.decr(normalize_key(name, options), amount) end @@ -178,7 +176,7 @@ module ActiveSupport def normalize_key(key, options) key = super.dup key = key.force_encoding(Encoding::ASCII_8BIT) - key = key.gsub(ESCAPE_KEY_CHARS){ |match| "%#{match.getbyte(0).to_s(16).upcase}" } + key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" } key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250 key end diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb index 896c28ad8b..1a8477f9fe 100644 --- a/activesupport/lib/active_support/cache/memory_store.rb +++ b/activesupport/lib/active_support/cache/memory_store.rb @@ -1,10 +1,10 @@ -require 'monitor' +require "monitor" module ActiveSupport module Cache # A cache store implementation which stores everything into memory in the # same process. If you're running multiple Ruby on Rails server processes - # (which is the case if you're using mongrel_cluster or Phusion Passenger), + # (which is the case if you're using Phusion Passenger or puma clustered mode), # then this means that Rails server process instances won't be able # to share cache data with each other and this may not be the most # appropriate cache in that scenario. @@ -39,8 +39,8 @@ module ActiveSupport # Preemptively iterates through all stored keys and removes the ones which have expired. def cleanup(options = nil) options = merged_options(options) - instrument(:cleanup, :size => @data.size) do - keys = synchronize{ @data.keys } + instrument(:cleanup, size: @data.size) do + keys = synchronize { @data.keys } keys.each do |key| entry = @data[key] delete_entry(key, options) if entry && entry.expired? @@ -56,8 +56,8 @@ module ActiveSupport begin start_time = Time.now cleanup - instrument(:prune, target_size, :from => @cache_size) do - keys = synchronize{ @key_access.keys.sort{|a,b| @key_access[a].to_f <=> @key_access[b].to_f} } + instrument(:prune, target_size, from: @cache_size) do + keys = synchronize { @key_access.keys.sort { |a,b| @key_access[a].to_f <=> @key_access[b].to_f } } keys.each do |key| delete_entry(key, options) return if @cache_size <= target_size || (max_time && Time.now - start_time > max_time) diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index df38dbcf11..f41cc23eb0 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -1,6 +1,6 @@ -require 'active_support/core_ext/object/duplicable' -require 'active_support/core_ext/string/inflections' -require 'active_support/per_thread_registry' +require "active_support/core_ext/object/duplicable" +require "active_support/core_ext/string/inflections" +require "active_support/per_thread_registry" module ActiveSupport module Cache @@ -9,7 +9,7 @@ module ActiveSupport # duration of a block. Repeated calls to the cache for the same key will hit the # in-memory cache for faster access. module LocalCache - autoload :Middleware, 'active_support/cache/strategy/local_cache_middleware' + autoload :Middleware, "active_support/cache/strategy/local_cache_middleware" # Class for storing and registering the local caches. class LocalCacheRegistry # :nodoc: @@ -44,7 +44,7 @@ module ActiveSupport yield end - def clear(options = nil) + def clear @data.clear end @@ -70,6 +70,7 @@ module ActiveSupport def with_local_cache use_temporary_local_cache(LocalStore.new) { yield } end + # Middleware class can be inserted as a Rack handler to be local cache for the # duration of request. def middleware @@ -78,9 +79,9 @@ module ActiveSupport local_cache_key) end - def clear(options = nil) # :nodoc: + def clear # :nodoc: return super unless cache = local_cache - cache.clear(options) + cache.clear super end @@ -92,14 +93,14 @@ module ActiveSupport def increment(name, amount = 1, options = nil) # :nodoc: return super unless local_cache - value = bypass_local_cache{super} + value = bypass_local_cache { super } write_cache_value(name, value, options) value end def decrement(name, amount = 1, options = nil) # :nodoc: return super unless local_cache - value = bypass_local_cache{super} + value = bypass_local_cache { super } write_cache_value(name, value, options) value end @@ -126,7 +127,7 @@ module ActiveSupport def set_cache_value(value, name, amount, options) # :nodoc: ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc) `set_cache_value` is deprecated and will be removed from Rails 5.1. - Please use `write_cache_value` + Please use `write_cache_value` instead. MESSAGE write_cache_value name, value, options end @@ -146,7 +147,7 @@ module ActiveSupport private def local_cache_key - @local_cache_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, '_').to_sym + @local_cache_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, "_").to_sym end def local_cache diff --git a/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb b/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb index a6f24b1a3c..af51f66dda 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb @@ -1,11 +1,10 @@ -require 'rack/body_proxy' -require 'rack/utils' +require "rack/body_proxy" +require "rack/utils" module ActiveSupport module Cache module Strategy module LocalCache - #-- # This class wraps up local storage for middlewares. Only the middleware method should # construct them. diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index e6baddf5db..2ad1145fae 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -1,13 +1,13 @@ -require 'active_support/concern' -require 'active_support/descendants_tracker' -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' +require "active_support/concern" +require "active_support/descendants_tracker" +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" module ActiveSupport # Callbacks are code hooks that are run at key points in an object's life cycle. @@ -63,6 +63,8 @@ module ActiveSupport included do extend ActiveSupport::DescendantsTracker + class_attribute :__callbacks, instance_writer: false + self.__callbacks ||= {} end CALLBACK_FILTER_TYPES = [:before, :after, :around] @@ -86,706 +88,777 @@ module ActiveSupport # run_callbacks :save do # save # end - def run_callbacks(kind, &block) - send "_run_#{kind}_callbacks", &block - end - - private + # + #-- + # + # As this method is used in many places, and often wraps large portions of + # user code, it has an additional design goal of minimizing its impact on + # the visible call stack. An exception from inside a :before or :after + # callback can be as noisy as it likes -- but when control has passed + # smoothly through and into the supplied block, we want as little evidence + # as possible that we were here. + def run_callbacks(kind) + callbacks = __callbacks[kind.to_sym] - def __run_callbacks__(callbacks, &block) if callbacks.empty? yield if block_given? else - runner = callbacks.compile - e = Filters::Environment.new(self, false, nil, block) - runner.call(e).value - end - end - - # A hook invoked every time a before callback is halted. - # This can be overridden in AS::Callback implementors in order - # to provide better debugging/logging. - def halted_callback_hook(filter) - end + env = Filters::Environment.new(self, false, nil) + next_sequence = callbacks.compile + + invoke_sequence = Proc.new do + skipped = nil + while true + current, next_sequence = next_sequence, next_sequence.nested + current.invoke_before(env) + if current.final? + env.value = !env.halted && (!block_given? || yield) + elsif current.skip?(env) + (skipped ||= []) << current + next + else + target, block, method, *arguments = current.expand_call_template(env, invoke_sequence) + target.send(method, *arguments, &block) + end + current.invoke_after(env) + skipped.pop.invoke_after(env) while skipped && skipped.first + break env.value + end + end - module Conditionals # :nodoc: - class Value - def initialize(&block) - @block = block + # Common case: no 'around' callbacks defined + if next_sequence.final? + next_sequence.invoke_before(env) + env.value = !env.halted && (!block_given? || yield) + next_sequence.invoke_after(env) + env.value + else + invoke_sequence.call end - def call(target, value); @block.call(value); end end end - module Filters - Environment = Struct.new(:target, :halted, :value, :run_block) + private - class End - def call(env) - block = env.run_block - env.value = !env.halted && (!block || block.call) - env - end + # A hook invoked every time a before callback is halted. + # This can be overridden in ActiveSupport::Callbacks implementors in order + # to provide better debugging/logging. + def halted_callback_hook(filter) end - ENDING = End.new - - class Before - def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter) - halted_lambda = chain_config[:terminator] - if user_conditions.any? - halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter) - else - halting(callback_sequence, user_callback, halted_lambda, filter) + module Conditionals # :nodoc: + class Value + def initialize(&block) + @block = block end + def call(target, value); @block.call(value); end end + end - def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter) - callback_sequence.before do |env| - target = env.target - value = env.value - halted = env.halted + module Filters + Environment = Struct.new(:target, :halted, :value) - if !halted && user_conditions.all? { |c| c.call(target, value) } - result_lambda = -> { user_callback.call target, value } - env.halted = halted_lambda.call(target, result_lambda) - if env.halted - target.send :halted_callback_hook, filter - end + class Before + def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter) + halted_lambda = chain_config[:terminator] + + if user_conditions.any? + halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter) + else + halting(callback_sequence, user_callback, halted_lambda, filter) end + end + + def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter) + callback_sequence.before do |env| + target = env.target + value = env.value + halted = env.halted + + if !halted && user_conditions.all? { |c| c.call(target, value) } + result_lambda = -> { user_callback.call target, value } + env.halted = halted_lambda.call(target, result_lambda) + if env.halted + target.send :halted_callback_hook, filter + end + end - env + env + end end - end - private_class_method :halting_and_conditional + private_class_method :halting_and_conditional - def self.halting(callback_sequence, user_callback, halted_lambda, filter) - callback_sequence.before do |env| - target = env.target - value = env.value - halted = env.halted + def self.halting(callback_sequence, user_callback, halted_lambda, filter) + callback_sequence.before do |env| + target = env.target + value = env.value + halted = env.halted - unless halted - result_lambda = -> { user_callback.call target, value } - env.halted = halted_lambda.call(target, result_lambda) + unless halted + result_lambda = -> { user_callback.call target, value } + env.halted = halted_lambda.call(target, result_lambda) - if env.halted - target.send :halted_callback_hook, filter + if env.halted + target.send :halted_callback_hook, filter + end end - end - env + env + end end + private_class_method :halting end - private_class_method :halting - end - class After - def self.build(callback_sequence, user_callback, user_conditions, chain_config) - if chain_config[:skip_after_callbacks_if_terminated] - if user_conditions.any? - halting_and_conditional(callback_sequence, user_callback, user_conditions) + class After + def self.build(callback_sequence, user_callback, user_conditions, chain_config) + if chain_config[:skip_after_callbacks_if_terminated] + if user_conditions.any? + halting_and_conditional(callback_sequence, user_callback, user_conditions) + else + halting(callback_sequence, user_callback) + end else - halting(callback_sequence, user_callback) + if user_conditions.any? + conditional callback_sequence, user_callback, user_conditions + else + simple callback_sequence, user_callback + end end - else - if user_conditions.any? - conditional callback_sequence, user_callback, user_conditions - else - simple callback_sequence, user_callback + end + + def self.halting_and_conditional(callback_sequence, user_callback, user_conditions) + callback_sequence.after do |env| + target = env.target + value = env.value + halted = env.halted + + if !halted && user_conditions.all? { |c| c.call(target, value) } + user_callback.call target, value + end + + env end end - end + private_class_method :halting_and_conditional - def self.halting_and_conditional(callback_sequence, user_callback, user_conditions) - callback_sequence.after do |env| - target = env.target - value = env.value - halted = env.halted + def self.halting(callback_sequence, user_callback) + callback_sequence.after do |env| + unless env.halted + user_callback.call env.target, env.value + end - if !halted && user_conditions.all? { |c| c.call(target, value) } - user_callback.call target, value + env end + end + private_class_method :halting + + def self.conditional(callback_sequence, user_callback, user_conditions) + callback_sequence.after do |env| + target = env.target + value = env.value + + if user_conditions.all? { |c| c.call(target, value) } + user_callback.call target, value + end - env + env + end end - end - private_class_method :halting_and_conditional + private_class_method :conditional - def self.halting(callback_sequence, user_callback) - callback_sequence.after do |env| - unless env.halted + def self.simple(callback_sequence, user_callback) + callback_sequence.after do |env| user_callback.call env.target, env.value - end - env + env + end end + private_class_method :simple end - private_class_method :halting + end - def self.conditional(callback_sequence, user_callback, user_conditions) - callback_sequence.after do |env| - target = env.target - value = env.value + 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. + MSG + end - if user_conditions.all? { |c| c.call(target, value) } - user_callback.call target, value - end + new chain.name, filter, kind, options, chain.config + end - env - end + attr_accessor :kind, :name + attr_reader :chain_config + + def initialize(name, filter, kind, options, chain_config) + @chain_config = chain_config + @name = name + @kind = kind + @filter = filter + @key = compute_identifier filter + @if = Array(options[:if]) + @unless = Array(options[:unless]) end - private_class_method :conditional - def self.simple(callback_sequence, user_callback) - callback_sequence.after do |env| - user_callback.call env.target, env.value + def filter; @key; end + def raw_filter; @filter; end - env - end + def merge_conditional_options(chain, if_option:, unless_option:) + options = { + if: @if.dup, + unless: @unless.dup + } + + options[:if].concat Array(unless_option) + options[:unless].concat Array(if_option) + + self.class.build chain, @filter, @kind, options + end + + def matches?(_kind, _filter) + @kind == _kind && filter == _filter end - private_class_method :simple - end - class Around - def self.build(callback_sequence, user_callback, user_conditions, chain_config) - if user_conditions.any? - halting_and_conditional(callback_sequence, user_callback, user_conditions) + def duplicates?(other) + case @filter + when Symbol, String + matches?(other.kind, other.filter) else - halting(callback_sequence, user_callback) + false end end - def self.halting_and_conditional(callback_sequence, user_callback, user_conditions) - callback_sequence.around do |env, &run| - target = env.target - value = env.value - halted = env.halted - - if !halted && user_conditions.all? { |c| c.call(target, value) } - user_callback.call(target, value) { - run.call.value - } - env - else - run.call - end + # Wraps code with filter + def apply(callback_sequence) + user_conditions = conditions_lambdas + user_callback = CallTemplate.build(@filter, self) + + case kind + when :before + Filters::Before.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config, @filter) + when :after + Filters::After.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config) + when :around + callback_sequence.around(user_callback, user_conditions) end end - private_class_method :halting_and_conditional - def self.halting(callback_sequence, user_callback) - callback_sequence.around do |env, &run| - target = env.target - value = env.value + def current_scopes + Array(chain_config[:scope]).map { |s| public_send(s) } + end - if env.halted - run.call + private + def compute_identifier(filter) + case filter + when String, ::Proc + filter.object_id else - user_callback.call(target, value) { - run.call.value - } - env + filter end end - end - private_class_method :halting + + def conditions_lambdas + @if.map { |c| CallTemplate.build(c, self).make_lambda } + + @unless.map { |c| CallTemplate.build(c, self).inverted_lambda } + end end - end - 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. - MSG + # A future invocation of user-supplied code (either as a callback, + # or a condition filter). + class CallTemplate # :nodoc: + def initialize(target, method, arguments, block) + @override_target = target + @method_name = method + @arguments = arguments + @override_block = block end - new chain.name, filter, kind, options, chain.config - end + # Return the parts needed to make this call, with the given + # input values. + # + # Returns an array of the form: + # + # [target, block, method, *arguments] + # + # This array can be used as such: + # + # target.send(method, *arguments, &block) + # + # The actual invocation is left up to the caller to minimize + # call stack pollution. + def expand(target, value, block) + result = @arguments.map { |arg| + case arg + when :value; value + when :target; target + when :block; block || raise(ArgumentError) + end + } - attr_accessor :kind, :name - attr_reader :chain_config - - def initialize(name, filter, kind, options, chain_config) - @chain_config = chain_config - @name = name - @kind = kind - @filter = filter - @key = compute_identifier filter - @if = Array(options[:if]) - @unless = Array(options[:unless]) - end + result.unshift @method_name + result.unshift @override_block || block + result.unshift @override_target || target - def filter; @key; end - def raw_filter; @filter; end + # target, block, method, *arguments = result + # target.send(method, *arguments, &block) + result + end - def merge_conditional_options(chain, if_option:, unless_option:) - options = { - :if => @if.dup, - :unless => @unless.dup - } + # Return a lambda that will make this call when given the input + # values. + def make_lambda + lambda do |target, value, &block| + target, block, method, *arguments = expand(target, value, block) + target.send(method, *arguments, &block) + end + end - options[:if].concat Array(unless_option) - options[:unless].concat Array(if_option) + # Return a lambda that will make this call when given the input + # values, but then return the boolean inverse of that result. + def inverted_lambda + lambda do |target, value, &block| + target, block, method, *arguments = expand(target, value, block) + ! target.send(method, *arguments, &block) + end + end - self.class.build chain, @filter, @kind, options - end + # Filters support: + # + # Symbols:: A method to call. + # Strings:: Some content to evaluate. + # Procs:: A proc to call with the object. + # Objects:: An object with a <tt>before_foo</tt> method on it to call. + # + # All of these objects are converted into a CallTemplate and handled + # the same after this point. + def self.build(filter, callback) + case filter + when Symbol + new(nil, filter, [], nil) + when String + new(nil, :instance_exec, [:value], compile_lambda(filter)) + when Conditionals::Value + new(filter, :call, [:target, :value], nil) + when ::Proc + if filter.arity > 1 + new(nil, :instance_exec, [:target, :block], filter) + elsif filter.arity > 0 + new(nil, :instance_exec, [:target], filter) + else + new(nil, :instance_exec, [], filter) + end + else + method_to_call = callback.current_scopes.join("_") - def matches?(_kind, _filter) - @kind == _kind && filter == _filter - end + new(filter, method_to_call, [:target], nil) + end + end - def duplicates?(other) - case @filter - when Symbol, String - matches?(other.kind, other.filter) - else - false + def self.compile_lambda(filter) + eval("lambda { |value| #{filter} }") end end - # Wraps code with filter - def apply(callback_sequence) - user_conditions = conditions_lambdas - user_callback = make_lambda @filter + # Execute before and after filters in a sequence instead of + # chaining them with nested lambda calls, see: + # https://github.com/rails/rails/issues/18011 + class CallbackSequence # :nodoc: + def initialize(nested = nil, call_template = nil, user_conditions = nil) + @nested = nested + @call_template = call_template + @user_conditions = user_conditions - case kind - when :before - Filters::Before.build(callback_sequence, user_callback, user_conditions, chain_config, @filter) - when :after - Filters::After.build(callback_sequence, user_callback, user_conditions, chain_config) - when :around - Filters::Around.build(callback_sequence, user_callback, user_conditions, chain_config) + @before = [] + @after = [] end - end - private - - def invert_lambda(l) - lambda { |*args, &blk| !l.call(*args, &blk) } - end + def before(&before) + @before.unshift(before) + self + end - # Filters support: - # - # Symbols:: A method to call. - # Strings:: Some content to evaluate. - # Procs:: A proc to call with the object. - # Objects:: An object with a <tt>before_foo</tt> method on it to call. - # - # All of these objects are converted into a lambda and handled - # the same after this point. - def make_lambda(filter) - case filter - when Symbol - lambda { |target, _, &blk| target.send filter, &blk } - when String - l = eval "lambda { |value| #{filter} }" - lambda { |target, value| target.instance_exec(value, &l) } - when Conditionals::Value then filter - when ::Proc - if filter.arity > 1 - return lambda { |target, _, &block| - raise ArgumentError unless block - target.instance_exec(target, block, &filter) - } - end - - if filter.arity <= 0 - lambda { |target, _| target.instance_exec(&filter) } - else - lambda { |target, _| target.instance_exec(target, &filter) } - end - else - scopes = Array(chain_config[:scope]) - method_to_call = scopes.map{ |s| public_send(s) }.join("_") + def after(&after) + @after.push(after) + self + end - lambda { |target, _, &blk| - filter.public_send method_to_call, target, &blk - } + def around(call_template, user_conditions) + CallbackSequence.new(self, call_template, user_conditions) end - end - def compute_identifier(filter) - case filter - when String, ::Proc - filter.object_id - else - filter + def skip?(arg) + arg.halted || !@user_conditions.all? { |c| c.call(arg.target, arg.value) } end - end - def conditions_lambdas - @if.map { |c| make_lambda c } + - @unless.map { |c| invert_lambda make_lambda c } - end - end + def nested + @nested + end - # Execute before and after filters in a sequence instead of - # chaining them with nested lambda calls, see: - # https://github.com/rails/rails/issues/18011 - class CallbackSequence - def initialize(&call) - @call = call - @before = [] - @after = [] - end + def final? + !@call_template + end - def before(&before) - @before.unshift(before) - self - end + def expand_call_template(arg, block) + @call_template.expand(arg.target, arg.value, block) + end - def after(&after) - @after.push(after) - self - end + def invoke_before(arg) + @before.each { |b| b.call(arg) } + end - def around(&around) - CallbackSequence.new do |arg| - around.call(arg) { - self.call(arg) - } + def invoke_after(arg) + @after.each { |a| a.call(arg) } end end - def call(arg) - @before.each { |b| b.call(arg) } - value = @call.call(arg) - @after.each { |a| a.call(arg) } - value - end - end + # An Array with a compile method. + class CallbackChain #:nodoc:# + include Enumerable - # An Array with a compile method. - class CallbackChain #:nodoc:# - include Enumerable - - attr_reader :name, :config - - def initialize(name, config) - @name = name - @config = { - scope: [:kind], - terminator: default_terminator - }.merge!(config) - @chain = [] - @callbacks = nil - @mutex = Mutex.new - end + attr_reader :name, :config - def each(&block); @chain.each(&block); end - def index(o); @chain.index(o); end - def empty?; @chain.empty?; end + def initialize(name, config) + @name = name + @config = { + scope: [:kind], + terminator: default_terminator + }.merge!(config) + @chain = [] + @callbacks = nil + @mutex = Mutex.new + end - def insert(index, o) - @callbacks = nil - @chain.insert(index, o) - end + def each(&block); @chain.each(&block); end + def index(o); @chain.index(o); end + def empty?; @chain.empty?; end - def delete(o) - @callbacks = nil - @chain.delete(o) - end + def insert(index, o) + @callbacks = nil + @chain.insert(index, o) + end - def clear - @callbacks = nil - @chain.clear - self - end + def delete(o) + @callbacks = nil + @chain.delete(o) + end - def initialize_copy(other) - @callbacks = nil - @chain = other.chain.dup - @mutex = Mutex.new - end + def clear + @callbacks = nil + @chain.clear + self + end - def compile - @callbacks || @mutex.synchronize do - final_sequence = CallbackSequence.new { |env| Filters::ENDING.call(env) } - @callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback| - callback.apply callback_sequence + def initialize_copy(other) + @callbacks = nil + @chain = other.chain.dup + @mutex = Mutex.new + end + + def compile + @callbacks || @mutex.synchronize do + final_sequence = CallbackSequence.new + @callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback| + callback.apply callback_sequence + end end end - end - def append(*callbacks) - callbacks.each { |c| append_one(c) } - end + def append(*callbacks) + callbacks.each { |c| append_one(c) } + end - def prepend(*callbacks) - callbacks.each { |c| prepend_one(c) } - end + def prepend(*callbacks) + callbacks.each { |c| prepend_one(c) } + end - protected - def chain; @chain; end + protected + def chain; @chain; end - private + private - def append_one(callback) - @callbacks = nil - remove_duplicates(callback) - @chain.push(callback) - end + def append_one(callback) + @callbacks = nil + remove_duplicates(callback) + @chain.push(callback) + end - def prepend_one(callback) - @callbacks = nil - remove_duplicates(callback) - @chain.unshift(callback) - end + def prepend_one(callback) + @callbacks = nil + remove_duplicates(callback) + @chain.unshift(callback) + end - def remove_duplicates(callback) - @callbacks = nil - @chain.delete_if { |c| callback.duplicates?(c) } - end + def remove_duplicates(callback) + @callbacks = nil + @chain.delete_if { |c| callback.duplicates?(c) } + end - def default_terminator - Proc.new do |target, result_lambda| - terminate = true - catch(:abort) do - result_lambda.call if result_lambda.is_a?(Proc) - terminate = false + def default_terminator + Proc.new do |target, result_lambda| + terminate = true + catch(:abort) do + result_lambda.call if result_lambda.is_a?(Proc) + terminate = false + end + terminate + end end - terminate - end end - end - module ClassMethods - def normalize_callback_params(filters, block) # :nodoc: - type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before - options = filters.extract_options! - filters.unshift(block) if block - [type, filters, options.dup] - end + module ClassMethods + def normalize_callback_params(filters, block) # :nodoc: + type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before + options = filters.extract_options! + filters.unshift(block) if block + [type, filters, options.dup] + end - # This is used internally to append, prepend and skip callbacks to the - # CallbackChain. - def __update_callbacks(name) #:nodoc: - ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse_each do |target| - chain = target.get_callbacks name - yield target, chain.dup + # This is used internally to append, prepend and skip callbacks to the + # CallbackChain. + def __update_callbacks(name) #:nodoc: + ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse_each do |target| + chain = target.get_callbacks name + yield target, chain.dup + end end - end - # Install a callback for the given event. - # - # set_callback :save, :before, :before_meth - # set_callback :save, :after, :after_meth, if: :condition - # set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff } - # - # The second argument indicates whether the callback is to be run +:before+, - # +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This - # means the first example above can also be written as: - # - # set_callback :save, :before_meth - # - # 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+. - # - # 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 - # an argument. - # - # Before and around callbacks are called in the order that they are set; - # after callbacks are called in the reverse order. - # - # Around callbacks can access the return value from the event, if it - # wasn't halted, from the +yield+ call. - # - # ===== Options - # - # * <tt>:if</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 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>: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) - self_chain = get_callbacks name - mapped = filters.map do |filter| - Callback.build(self_chain, filter, type, options) - end - - __update_callbacks(name) do |target, chain| - options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped) - target.set_callbacks name, chain + # Install a callback for the given event. + # + # set_callback :save, :before, :before_method + # set_callback :save, :after, :after_method, if: :condition + # set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff } + # + # The second argument indicates whether the callback is to be run +:before+, + # +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This + # means the first example above can also be written as: + # + # 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+. + # + # 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 + # an argument. + # + # Before and around callbacks are called in the order that they are set; + # after callbacks are called in the reverse order. + # + # Around callbacks can access the return value from the event, if it + # wasn't halted, from the +yield+ call. + # + # ===== Options + # + # * <tt>:if</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 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>: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) + self_chain = get_callbacks name + mapped = filters.map do |filter| + Callback.build(self_chain, filter, type, options) + end + + __update_callbacks(name) do |target, chain| + options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped) + target.set_callbacks name, chain + end end - end - # Skip a previously set callback. Like +set_callback+, <tt>:if</tt> or - # <tt>:unless</tt> options may be passed in order to control when the - # callback is skipped. - # - # class Writer < Person - # skip_callback :validate, :before, :check_membership, if: -> { self.age > 18 } - # end - # - # An <tt>ArgumentError</tt> will be raised if the callback has not - # 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) - options[:raise] = true unless options.key?(:raise) - - __update_callbacks(name) do |target, chain| - filters.each do |filter| - callback = chain.find {|c| c.matches?(type, filter) } - - if !callback && options[:raise] - raise ArgumentError, "#{type.to_s.capitalize} #{name} callback #{filter.inspect} has not been defined" - end + # Skip a previously set callback. Like +set_callback+, <tt>:if</tt> or + # <tt>:unless</tt> options may be passed in order to control when the + # callback is skipped. + # + # class Writer < Person + # skip_callback :validate, :before, :check_membership, if: -> { age > 18 } + # end + # + # An <tt>ArgumentError</tt> will be raised if the callback has not + # 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) + options[:raise] = true unless options.key?(:raise) + + __update_callbacks(name) do |target, chain| + filters.each do |filter| + callback = chain.find { |c| c.matches?(type, filter) } + + if !callback && options[:raise] + raise ArgumentError, "#{type.to_s.capitalize} #{name} callback #{filter.inspect} has not been defined" + end - if callback && (options.key?(:if) || options.key?(:unless)) - new_callback = callback.merge_conditional_options(chain, if_option: options[:if], unless_option: options[:unless]) - chain.insert(chain.index(callback), new_callback) - end + if callback && (options.key?(:if) || options.key?(:unless)) + new_callback = callback.merge_conditional_options(chain, if_option: options[:if], unless_option: options[:unless]) + chain.insert(chain.index(callback), new_callback) + end - chain.delete(callback) + chain.delete(callback) + end + target.set_callbacks name, chain end - target.set_callbacks name, chain end - end - # Remove all set callbacks for the given event. - def reset_callbacks(name) - callbacks = get_callbacks name + # Remove all set callbacks for the given event. + def reset_callbacks(name) + callbacks = get_callbacks name + + ActiveSupport::DescendantsTracker.descendants(self).each do |target| + chain = target.get_callbacks(name).dup + callbacks.each { |c| chain.delete(c) } + target.set_callbacks name, chain + end - ActiveSupport::DescendantsTracker.descendants(self).each do |target| - chain = target.get_callbacks(name).dup - callbacks.each { |c| chain.delete(c) } - target.set_callbacks name, chain + set_callbacks(name, callbacks.dup.clear) end - self.set_callbacks name, callbacks.dup.clear - end + # Define sets of events in the object life cycle that support callbacks. + # + # define_callbacks :validate + # define_callbacks :initialize, :save, :destroy + # + # ===== Options + # + # * <tt>:terminator</tt> - Determines when a before filter will halt the + # callback chain, preventing following before and around callbacks from + # being called and the event from being triggered. + # This should be a lambda to be executed. + # The current object and the result lambda of the callback will be provided + # to the terminator lambda. + # + # define_callbacks :validate, terminator: ->(target, result_lambda) { result_lambda.call == false } + # + # In this example, if any before validate callbacks returns +false+, + # any successive before and around callback is not executed. + # + # The default terminator halts the chain when a callback throws +:abort+. + # + # * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after + # callbacks should be terminated by the <tt>:terminator</tt> option. By + # default after callbacks are executed no matter if callback chain was + # terminated or not. This option makes sense only when <tt>:terminator</tt> + # option is specified. + # + # * <tt>:scope</tt> - Indicates which methods should be executed when an + # object is used as a callback. + # + # class Audit + # def before(caller) + # puts 'Audit: before is called' + # end + # + # def before_save(caller) + # puts 'Audit: before_save is called' + # end + # end + # + # class Account + # include ActiveSupport::Callbacks + # + # define_callbacks :save + # set_callback :save, :before, Audit.new + # + # def save + # run_callbacks :save do + # puts 'save in main' + # end + # end + # end + # + # In the above case whenever you save an account the method + # <tt>Audit#before</tt> will be called. On the other hand + # + # define_callbacks :save, scope: [:kind, :name] + # + # would trigger <tt>Audit#before_save</tt> instead. That's constructed + # by calling <tt>#{kind}_#{name}</tt> on the given instance. In this + # case "kind" is "before" and "name" is "save". In this context +:kind+ + # and +:name+ have special meanings: +:kind+ refers to the kind of + # callback (before/after/around) and +:name+ refers to the method on + # which callbacks are being defined. + # + # A declaration like + # + # define_callbacks :save, scope: [:name] + # + # would call <tt>Audit#save</tt>. + # + # ===== Notes + # + # +names+ passed to +define_callbacks+ must not end with + # <tt>!</tt>, <tt>?</tt> or <tt>=</tt>. + # + # Calling +define_callbacks+ multiple times with the same +names+ will + # overwrite previous callbacks registered with +set_callback+. + def define_callbacks(*names) + options = names.extract_options! + + names.each do |name| + name = name.to_sym + + set_callbacks name, CallbackChain.new(name, options) + + module_eval <<-RUBY, __FILE__, __LINE__ + 1 + def _run_#{name}_callbacks(&block) + run_callbacks #{name.inspect}, &block + end - # Define sets of events in the object life cycle that support callbacks. - # - # define_callbacks :validate - # define_callbacks :initialize, :save, :destroy - # - # ===== Options - # - # * <tt>:terminator</tt> - Determines when a before filter will halt the - # callback chain, preventing following before and around callbacks from - # being called and the event from being triggered. - # This should be a lambda to be executed. - # The current object and the result lambda of the callback will be provided - # to the terminator lambda. - # - # define_callbacks :validate, terminator: ->(target, result_lambda) { result_lambda.call == false } - # - # In this example, if any before validate callbacks returns +false+, - # any successive before and around callback is not executed. - # - # The default terminator halts the chain when a callback throws +:abort+. - # - # * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after - # callbacks should be terminated by the <tt>:terminator</tt> option. By - # default after callbacks are executed no matter if callback chain was - # terminated or not. This option makes sense only when <tt>:terminator</tt> - # option is specified. - # - # * <tt>:scope</tt> - Indicates which methods should be executed when an - # object is used as a callback. - # - # class Audit - # def before(caller) - # puts 'Audit: before is called' - # end - # - # def before_save(caller) - # puts 'Audit: before_save is called' - # end - # end - # - # class Account - # include ActiveSupport::Callbacks - # - # define_callbacks :save - # set_callback :save, :before, Audit.new - # - # def save - # run_callbacks :save do - # puts 'save in main' - # end - # end - # end - # - # In the above case whenever you save an account the method - # <tt>Audit#before</tt> will be called. On the other hand - # - # define_callbacks :save, scope: [:kind, :name] - # - # would trigger <tt>Audit#before_save</tt> instead. That's constructed - # by calling <tt>#{kind}_#{name}</tt> on the given instance. In this - # case "kind" is "before" and "name" is "save". In this context +:kind+ - # and +:name+ have special meanings: +:kind+ refers to the kind of - # callback (before/after/around) and +:name+ refers to the method on - # which callbacks are being defined. - # - # A declaration like - # - # define_callbacks :save, scope: [:name] - # - # would call <tt>Audit#save</tt>. - # - # NOTE: +method_name+ passed to `define_model_callbacks` must not end with - # `!`, `?` or `=`. - def define_callbacks(*names) - options = names.extract_options! - - names.each do |name| - class_attribute "_#{name}_callbacks", instance_writer: false - set_callbacks name, CallbackChain.new(name, options) - - module_eval <<-RUBY, __FILE__, __LINE__ + 1 - def _run_#{name}_callbacks(&block) - __run_callbacks__(_#{name}_callbacks, &block) - end - RUBY + def self._#{name}_callbacks + get_callbacks(#{name.inspect}) + end + + def self._#{name}_callbacks=(value) + set_callbacks(#{name.inspect}, value) + end + + def _#{name}_callbacks + __callbacks[#{name.inspect}] + end + RUBY + end end - end - protected + protected - def get_callbacks(name) # :nodoc: - send "_#{name}_callbacks" - end + def get_callbacks(name) # :nodoc: + __callbacks[name.to_sym] + end - def set_callbacks(name, callbacks) # :nodoc: - send "_#{name}_callbacks=", callbacks - end + 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 + 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 - terminate - end - end - private + 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 the next release of Rails. - To explicitly halt the callback chain, please use `throw :abort` instead. - MSG + 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 end diff --git a/activesupport/lib/active_support/concurrency/latch.rb b/activesupport/lib/active_support/concurrency/latch.rb index 4abe5ece6f..53e09b685c 100644 --- a/activesupport/lib/active_support/concurrency/latch.rb +++ b/activesupport/lib/active_support/concurrency/latch.rb @@ -1,18 +1,24 @@ -require 'concurrent/atomic/count_down_latch' +require "concurrent/atomic/count_down_latch" module ActiveSupport module Concurrency - class Latch < Concurrent::CountDownLatch - + class Latch def initialize(count = 1) - ActiveSupport::Deprecation.warn("ActiveSupport::Concurrency::Latch is deprecated. Please use Concurrent::CountDownLatch instead.") - super(count) + if count == 1 + ActiveSupport::Deprecation.warn("ActiveSupport::Concurrency::Latch is deprecated. Please use Concurrent::Event instead.") + else + ActiveSupport::Deprecation.warn("ActiveSupport::Concurrency::Latch is deprecated. Please use Concurrent::CountDownLatch instead.") + end + + @inner = Concurrent::CountDownLatch.new(count) end - alias_method :release, :count_down + def release + @inner.count_down + end def await - wait(nil) + @inner.wait(nil) end end end diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb index 54244317e4..4318523b07 100644 --- a/activesupport/lib/active_support/concurrency/share_lock.rb +++ b/activesupport/lib/active_support/concurrency/share_lock.rb @@ -1,5 +1,5 @@ -require 'thread' -require 'monitor' +require "thread" +require "monitor" module ActiveSupport module Concurrency @@ -13,6 +13,37 @@ module ActiveSupport # we need exclusive locks to be reentrant, and we need to be able # to upgrade share locks to exclusive. + def raw_state # :nodoc: + synchronize do + threads = @sleeping.keys | @sharing.keys | @waiting.keys + threads |= [@exclusive_thread] if @exclusive_thread + + data = {} + + threads.each do |thread| + purpose, compatible = @waiting[thread] + + data[thread] = { + thread: thread, + sharing: @sharing[thread], + exclusive: @exclusive_thread == thread, + purpose: purpose, + compatible: compatible, + waiting: !!@waiting[thread], + sleeper: @sleeping[thread], + } + end + + # NB: Yields while holding our *internal* synchronize lock, + # which is supposed to be used only for a few instructions at + # a time. This allows the caller to inspect additional state + # without things changing out from underneath, but would have + # disastrous effects upon normal operation. Fortunately, this + # method is only intended to be called when things have + # already gone wrong. + yield data + end + end def initialize super() @@ -21,6 +52,7 @@ module ActiveSupport @sharing = Hash.new(0) @waiting = {} + @sleeping = {} @exclusive_thread = nil @exclusive_depth = 0 end @@ -46,7 +78,7 @@ module ActiveSupport return false if no_wait yield_shares(purpose: purpose, compatible: compatible, block_share: true) do - @cv.wait_while { busy_for_exclusive?(purpose) } + wait_for(:start_exclusive) { busy_for_exclusive?(purpose) } end end @exclusive_thread = Thread.current @@ -69,7 +101,7 @@ module ActiveSupport if eligible_waiters?(compatible) yield_shares(compatible: compatible, block_share: true) do - @cv.wait_while { @exclusive_thread || eligible_waiters?(compatible) } + wait_for(:stop_exclusive) { @exclusive_thread || eligible_waiters?(compatible) } end end @cv.broadcast @@ -84,11 +116,11 @@ module ActiveSupport elsif @waiting[Thread.current] # We're nested inside a +yield_shares+ call: we'll resume as # soon as there isn't an exclusive lock in our way - @cv.wait_while { @exclusive_thread } + wait_for(:start_sharing) { @exclusive_thread } else # This is an initial / outermost share call: any outstanding # requests for an exclusive lock get to go first - @cv.wait_while { busy_for_sharing?(false) } + wait_for(:start_sharing) { busy_for_sharing?(false) } end @sharing[Thread.current] += 1 end @@ -144,16 +176,16 @@ module ActiveSupport end compatible |= [false] unless block_share @waiting[Thread.current] = [purpose, compatible] - - @cv.broadcast end + + @cv.broadcast end begin yield ensure synchronize do - @cv.wait_while { @exclusive_thread && @exclusive_thread != Thread.current } + wait_for(:yield_shares) { @exclusive_thread && @exclusive_thread != Thread.current } if previous_wait @waiting[Thread.current] = previous_wait @@ -167,20 +199,27 @@ module ActiveSupport private - # Must be called within synchronize - def busy_for_exclusive?(purpose) - busy_for_sharing?(purpose) || - @sharing.size > (@sharing[Thread.current] > 0 ? 1 : 0) - end + # Must be called within synchronize + def busy_for_exclusive?(purpose) + busy_for_sharing?(purpose) || + @sharing.size > (@sharing[Thread.current] > 0 ? 1 : 0) + end - def busy_for_sharing?(purpose) - (@exclusive_thread && @exclusive_thread != Thread.current) || - @waiting.any? { |t, (_, c)| t != Thread.current && !c.include?(purpose) } - end + def busy_for_sharing?(purpose) + (@exclusive_thread && @exclusive_thread != Thread.current) || + @waiting.any? { |t, (_, c)| t != Thread.current && !c.include?(purpose) } + end - def eligible_waiters?(compatible) - @waiting.any? { |t, (p, _)| compatible.include?(p) && @waiting.all? { |t2, (_, c2)| t == t2 || c2.include?(p) } } - end + def eligible_waiters?(compatible) + @waiting.any? { |t, (p, _)| compatible.include?(p) && @waiting.all? { |t2, (_, c2)| t == t2 || c2.include?(p) } } + end + + def wait_for(method) + @sleeping[Thread.current] = method + @cv.wait_while { yield } + ensure + @sleeping.delete Thread.current + end end end end diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb index 8256c325af..f72a893bcc 100644 --- a/activesupport/lib/active_support/configurable.rb +++ b/activesupport/lib/active_support/configurable.rb @@ -1,6 +1,7 @@ -require 'active_support/concern' -require 'active_support/ordered_options' -require 'active_support/core_ext/array/extract_options' +require "active_support/concern" +require "active_support/ordered_options" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/regexp" module ActiveSupport # Configurable provides a <tt>config</tt> method to store and retrieve @@ -107,7 +108,7 @@ module ActiveSupport options = names.extract_options! names.each do |name| - raise NameError.new('invalid config attribute name') unless name =~ /\A[_A-Za-z]\w*\z/ + raise NameError.new("invalid config attribute name") unless /\A[_A-Za-z]\w*\z/.match?(name) reader, reader_line = "def #{name}; config.#{name}; end", __LINE__ writer, writer_line = "def #{name}=(value); config.#{name} = value; end", __LINE__ @@ -145,4 +146,3 @@ module ActiveSupport end end end - diff --git a/activesupport/lib/active_support/core_ext/array.rb b/activesupport/lib/active_support/core_ext/array.rb index 7551551bd7..e908386a1c 100644 --- a/activesupport/lib/active_support/core_ext/array.rb +++ b/activesupport/lib/active_support/core_ext/array.rb @@ -1,7 +1,7 @@ -require 'active_support/core_ext/array/wrap' -require 'active_support/core_ext/array/access' -require 'active_support/core_ext/array/conversions' -require 'active_support/core_ext/array/extract_options' -require 'active_support/core_ext/array/grouping' -require 'active_support/core_ext/array/prepend_and_append' -require 'active_support/core_ext/array/inquiry' +require "active_support/core_ext/array/wrap" +require "active_support/core_ext/array/access" +require "active_support/core_ext/array/conversions" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/array/grouping" +require "active_support/core_ext/array/prepend_and_append" +require "active_support/core_ext/array/inquiry" diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index 8718b7e1e5..1d4c83ae52 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -1,8 +1,8 @@ -require 'active_support/xml_mini' -require 'active_support/core_ext/hash/keys' -require 'active_support/core_ext/string/inflections' -require 'active_support/core_ext/object/to_param' -require 'active_support/core_ext/object/to_query' +require "active_support/xml_mini" +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/string/inflections" +require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/to_query" class Array # Converts the array to a comma-separated sentence where the last element is @@ -40,6 +40,12 @@ class Array # ['one', 'two', 'three'].to_sentence(words_connector: ' or ', last_word_connector: ' or at least ') # # => "one or two or at least three" # + # [].to_sentence(fallback_string: 'none') + # # => "none" + # + # ['one', 'two'].to_sentence(fallback_string: 'none') + # # => "one and two" + # # Using <tt>:locale</tt> option: # # # Given this locale dictionary: @@ -57,12 +63,12 @@ class Array # ['uno', 'dos', 'tres'].to_sentence(locale: :es) # # => "uno o dos o al menos tres" def to_sentence(options = {}) - options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale) + options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale, :fallback_string) default_connectors = { - :words_connector => ', ', - :two_words_connector => ' and ', - :last_word_connector => ', and ' + words_connector: ", ", + two_words_connector: " and ", + last_word_connector: ", and " } if defined?(I18n) i18n_connectors = I18n.translate(:'support.array', locale: options[:locale], default: {}) @@ -72,7 +78,7 @@ class Array case length when 0 - '' + "#{options[:fallback_string] || ''}" when 1 "#{self[0]}" when 2 @@ -92,9 +98,9 @@ class Array case format when :db if empty? - 'null' + "null" else - collect(&:id).join(',') + collect(&:id).join(",") end else to_default_s @@ -179,7 +185,7 @@ class Array # </messages> # def to_xml(options = {}) - require 'active_support/builder' unless defined?(Builder) + require "active_support/builder" unless defined?(Builder) options = options.dup options[:indent] ||= 2 @@ -187,9 +193,9 @@ class Array options[:root] ||= \ if first.class != Hash && all? { |e| e.is_a?(first.class) } underscored = ActiveSupport::Inflector.underscore(first.class.name) - ActiveSupport::Inflector.pluralize(underscored).tr('/', '_') + ActiveSupport::Inflector.pluralize(underscored).tr("/", "_") else - 'objects' + "objects" end builder = options[:builder] @@ -197,7 +203,7 @@ class Array root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options) children = options.delete(:children) || root.singularize - attributes = options[:skip_types] ? {} : { type: 'array' } + attributes = options[:skip_types] ? {} : { type: "array" } if empty? builder.tag!(root, attributes) diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb index 87ae052eb0..ea9d85f6e3 100644 --- a/activesupport/lib/active_support/core_ext/array/grouping.rb +++ b/activesupport/lib/active_support/core_ext/array/grouping.rb @@ -89,28 +89,19 @@ 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 + result = [] if block_given? - inject([[]]) do |results, element| - if yield(element) - results << [] - else - results.last << element - end - - results + while (idx = arr.index { |i| yield i }) + result << arr.shift(idx) + arr.shift end else - results, arr = [[]], self.dup - until arr.empty? - if (idx = arr.index(value)) - results.last.concat(arr.shift(idx)) - arr.shift - results << [] - else - results.last.concat(arr.shift(arr.size)) - end + while (idx = arr.index(value)) + result << arr.shift(idx) + arr.shift end - results end + result << arr end end diff --git a/activesupport/lib/active_support/core_ext/array/inquiry.rb b/activesupport/lib/active_support/core_ext/array/inquiry.rb index e8f44cc378..66fa5fcd0c 100644 --- a/activesupport/lib/active_support/core_ext/array/inquiry.rb +++ b/activesupport/lib/active_support/core_ext/array/inquiry.rb @@ -1,4 +1,4 @@ -require 'active_support/array_inquirer' +require "active_support/array_inquirer" class Array # Wraps the array in an +ArrayInquirer+ object, which gives a friendlier way diff --git a/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb b/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb index f8d48b69df..16a6789f8d 100644 --- a/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb +++ b/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb @@ -4,4 +4,4 @@ class Array # The human way of thinking about adding stuff to the beginning of a list is with prepend. alias_method :prepend, :unshift -end
\ No newline at end of file +end diff --git a/activesupport/lib/active_support/core_ext/benchmark.rb b/activesupport/lib/active_support/core_ext/benchmark.rb index eb25b2bc44..2300953860 100644 --- a/activesupport/lib/active_support/core_ext/benchmark.rb +++ b/activesupport/lib/active_support/core_ext/benchmark.rb @@ -1,4 +1,4 @@ -require 'benchmark' +require "benchmark" class << Benchmark # Benchmark realtime in milliseconds. diff --git a/activesupport/lib/active_support/core_ext/big_decimal.rb b/activesupport/lib/active_support/core_ext/big_decimal.rb index 8143113cfa..7b4f87f10e 100644 --- a/activesupport/lib/active_support/core_ext/big_decimal.rb +++ b/activesupport/lib/active_support/core_ext/big_decimal.rb @@ -1 +1 @@ -require 'active_support/core_ext/big_decimal/conversions' +require "active_support/core_ext/big_decimal/conversions" diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb index 22fc7ecf92..decd4e1699 100644 --- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb +++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb @@ -1,12 +1,10 @@ -require 'bigdecimal' -require 'bigdecimal/util' +require "bigdecimal" +require "bigdecimal/util" module ActiveSupport module BigDecimalWithDefaultFormat #:nodoc: - DEFAULT_STRING_FORMAT = 'F' - - def to_s(format = nil) - super(format || DEFAULT_STRING_FORMAT) + def to_s(format = "F") + super(format) end end end diff --git a/activesupport/lib/active_support/core_ext/class.rb b/activesupport/lib/active_support/core_ext/class.rb index ef903d59b5..6a19e862d0 100644 --- a/activesupport/lib/active_support/core_ext/class.rb +++ b/activesupport/lib/active_support/core_ext/class.rb @@ -1,2 +1,2 @@ -require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/class/subclasses' +require "active_support/core_ext/class/attribute" +require "active_support/core_ext/class/subclasses" diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index 802d988af2..ba422f9071 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -1,6 +1,6 @@ -require 'active_support/core_ext/kernel/singleton_class' -require 'active_support/core_ext/module/remove_method' -require 'active_support/core_ext/array/extract_options' +require "active_support/core_ext/kernel/singleton_class" +require "active_support/core_ext/module/remove_method" +require "active_support/core_ext/array/extract_options" class Class # Declare a class-level attribute whose value is inheritable by subclasses. @@ -20,14 +20,14 @@ class Class # Base.setting # => true # # In the above case as long as Subclass does not assign a value to setting - # by performing <tt>Subclass.setting = _something_ </tt>, <tt>Subclass.setting</tt> + # by performing <tt>Subclass.setting = _something_</tt>, <tt>Subclass.setting</tt> # would read value assigned to parent class. Once Subclass assigns a value then # the value assigned by Subclass would be returned. # # This matches normal Ruby method inheritance: think of writing an attribute # on a subclass as overriding the reader method. However, you need to be aware # when using +class_attribute+ with mutable structures as +Array+ or +Hash+. - # In such cases, you don't want to do changes in places but use setters: + # In such cases, you don't want to do changes in place. Instead use setters: # # Base.setting = [] # Base.setting # => [] diff --git a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb index 84d5e95e7a..0f767925ed 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb @@ -1,4 +1,4 @@ # cattr_* became mattr_* aliases in 7dfbd91b0780fbd6a1dd9bfbc176e10894871d2d, # but we keep this around for libraries that directly require it knowing they # want cattr_*. No need to deprecate. -require 'active_support/core_ext/module/attribute_accessors' +require "active_support/core_ext/module/attribute_accessors" diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb index b0f9a8be34..10a7c787f6 100644 --- a/activesupport/lib/active_support/core_ext/class/subclasses.rb +++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb @@ -1,12 +1,25 @@ -require 'active_support/core_ext/module/anonymous' -require 'active_support/core_ext/module/reachable' +require "active_support/core_ext/module/anonymous" +require "active_support/core_ext/module/reachable" class Class begin # Test if this Ruby supports each_object against singleton_class ObjectSpace.each_object(Numeric.singleton_class) {} - def descendants # :nodoc: + # Returns an array with all classes that are < than its receiver. + # + # class C; end + # C.descendants # => [] + # + # class B < C; end + # C.descendants # => [B] + # + # class A < B; end + # C.descendants # => [B, A] + # + # class D < C; end + # C.descendants # => [B, A, D] + def descendants descendants = [] ObjectSpace.each_object(singleton_class) do |k| descendants.unshift k unless k == self @@ -14,7 +27,7 @@ class Class descendants end rescue StandardError # JRuby 9.0.4.0 and earlier - def descendants # :nodoc: + def descendants descendants = [] ObjectSpace.each_object(Class) do |k| descendants.unshift k if k < self @@ -26,8 +39,6 @@ class Class # Returns an array with the direct children of +self+. # - # Integer.subclasses # => [Fixnum, Bignum] - # # class Foo; end # class Bar < Foo; end # class Baz < Bar; end diff --git a/activesupport/lib/active_support/core_ext/date.rb b/activesupport/lib/active_support/core_ext/date.rb index 7f0f4639a2..4f66804da2 100644 --- a/activesupport/lib/active_support/core_ext/date.rb +++ b/activesupport/lib/active_support/core_ext/date.rb @@ -1,5 +1,5 @@ -require 'active_support/core_ext/date/acts_like' -require 'active_support/core_ext/date/blank' -require 'active_support/core_ext/date/calculations' -require 'active_support/core_ext/date/conversions' -require 'active_support/core_ext/date/zones' +require "active_support/core_ext/date/acts_like" +require "active_support/core_ext/date/blank" +require "active_support/core_ext/date/calculations" +require "active_support/core_ext/date/conversions" +require "active_support/core_ext/date/zones" diff --git a/activesupport/lib/active_support/core_ext/date/acts_like.rb b/activesupport/lib/active_support/core_ext/date/acts_like.rb index cd90cee236..46fe99ad47 100644 --- a/activesupport/lib/active_support/core_ext/date/acts_like.rb +++ b/activesupport/lib/active_support/core_ext/date/acts_like.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/object/acts_like' +require "active_support/core_ext/object/acts_like" class Date # Duck-types as a Date-like class. See Object#acts_like?. diff --git a/activesupport/lib/active_support/core_ext/date/blank.rb b/activesupport/lib/active_support/core_ext/date/blank.rb index 71627b6a6f..edd2847126 100644 --- a/activesupport/lib/active_support/core_ext/date/blank.rb +++ b/activesupport/lib/active_support/core_ext/date/blank.rb @@ -1,4 +1,4 @@ -require 'date' +require "date" class Date #:nodoc: # No Date is blank: diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index d589b67bf7..2e64097933 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -1,9 +1,9 @@ -require 'date' -require 'active_support/duration' -require 'active_support/core_ext/object/acts_like' -require 'active_support/core_ext/date/zones' -require 'active_support/core_ext/time/zones' -require 'active_support/core_ext/date_and_time/calculations' +require "date" +require "active_support/duration" +require "active_support/core_ext/object/acts_like" +require "active_support/core_ext/date/zones" +require "active_support/core_ext/time/zones" +require "active_support/core_ext/date_and_time/calculations" class Date include DateAndTime::Calculations @@ -129,7 +129,7 @@ class Date options.fetch(:day, day) ) end - + # 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) diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb index ed8bca77ac..d553406dff 100644 --- a/activesupport/lib/active_support/core_ext/date/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date/conversions.rb @@ -1,20 +1,20 @@ -require 'date' -require 'active_support/inflector/methods' -require 'active_support/core_ext/date/zones' -require 'active_support/core_ext/module/remove_method' +require "date" +require "active_support/inflector/methods" +require "active_support/core_ext/date/zones" +require "active_support/core_ext/module/remove_method" class Date DATE_FORMATS = { - :short => '%e %b', - :long => '%B %e, %Y', - :db => '%Y-%m-%d', - :number => '%Y%m%d', - :long_ordinal => lambda { |date| + short: "%d %b", + long: "%B %d, %Y", + db: "%Y-%m-%d", + number: "%Y%m%d", + long_ordinal: lambda { |date| day_format = ActiveSupport::Inflector.ordinalize(date.day) date.strftime("%B #{day_format}, %Y") # => "April 25th, 2007" }, - :rfc822 => '%e %b %Y', - :iso8601 => lambda { |date| date.iso8601 } + rfc822: "%d %b %Y", + iso8601: lambda { |date| date.iso8601 } } # Ruby 1.9 has Date#to_time which converts to localtime only. @@ -65,7 +65,7 @@ class Date # Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005" def readable_inspect - strftime('%a, %d %b %Y') + strftime("%a, %d %b %Y") end alias_method :default_inspect, :inspect alias_method :inspect, :readable_inspect @@ -80,6 +80,7 @@ class Date # # date.to_time(:utc) # => 2007-11-10 00:00:00 UTC def to_time(form = :local) + raise ArgumentError, "Expected :local or :utc, got #{form.inspect}." unless [:local, :utc].include?(form) ::Time.send(form, year, month, day) end diff --git a/activesupport/lib/active_support/core_ext/date/zones.rb b/activesupport/lib/active_support/core_ext/date/zones.rb index d109b430db..da23fe4892 100644 --- a/activesupport/lib/active_support/core_ext/date/zones.rb +++ b/activesupport/lib/active_support/core_ext/date/zones.rb @@ -1,5 +1,5 @@ -require 'date' -require 'active_support/core_ext/date_and_time/zones' +require "date" +require "active_support/core_ext/date_and_time/zones" class Date include DateAndTime::Zones diff --git a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb index e079af594d..f2ba7fdda5 100644 --- a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb @@ -1,13 +1,15 @@ +require "active_support/core_ext/object/try" + module DateAndTime module Calculations DAYS_INTO_WEEK = { - :monday => 0, - :tuesday => 1, - :wednesday => 2, - :thursday => 3, - :friday => 4, - :saturday => 5, - :sunday => 6 + monday: 0, + tuesday: 1, + wednesday: 2, + thursday: 3, + friday: 4, + saturday: 5, + sunday: 6 } WEEKEND_DAYS = [ 6, 0 ] @@ -51,44 +53,49 @@ module DateAndTime WEEKEND_DAYS.include?(wday) end + # Returns true if the date/time does not fall on a Saturday or Sunday. + def on_weekday? + !WEEKEND_DAYS.include?(wday) + end + # Returns a new date/time the specified number of days ago. def days_ago(days) - advance(:days => -days) + advance(days: -days) end # Returns a new date/time the specified number of days in the future. def days_since(days) - advance(:days => days) + advance(days: days) end # Returns a new date/time the specified number of weeks ago. def weeks_ago(weeks) - advance(:weeks => -weeks) + advance(weeks: -weeks) end # Returns a new date/time the specified number of weeks in the future. def weeks_since(weeks) - advance(:weeks => weeks) + advance(weeks: weeks) end # Returns a new date/time the specified number of months ago. def months_ago(months) - advance(:months => -months) + advance(months: -months) end # Returns a new date/time the specified number of months in the future. def months_since(months) - advance(:months => months) + advance(months: months) end # Returns a new date/time the specified number of years ago. def years_ago(years) - advance(:years => -years) + advance(years: -years) end # Returns a new date/time the specified number of years in the future. def years_since(years) - advance(:years => years) + advance(years: years) end # Returns a new date/time at the start of the month. @@ -101,7 +108,7 @@ module DateAndTime # now = DateTime.current # => Thu, 18 Jun 2015 15:23:13 +0000 # now.beginning_of_month # => Mon, 01 Jun 2015 00:00:00 +0000 def beginning_of_month - first_hour(change(:day => 1)) + first_hour(change(day: 1)) end alias :at_beginning_of_month :beginning_of_month @@ -116,7 +123,7 @@ module DateAndTime # now.beginning_of_quarter # => Wed, 01 Jul 2015 00:00:00 +0000 def beginning_of_quarter first_quarter_month = [10, 7, 4, 1].detect { |m| m <= month } - beginning_of_month.change(:month => first_quarter_month) + beginning_of_month.change(month: first_quarter_month) end alias :at_beginning_of_quarter :beginning_of_quarter @@ -131,7 +138,7 @@ module DateAndTime # now.end_of_quarter # => Wed, 30 Sep 2015 23:59:59 +0000 def end_of_quarter last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month } - beginning_of_month.change(:month => last_quarter_month).end_of_month + beginning_of_month.change(month: last_quarter_month).end_of_month end alias :at_end_of_quarter :end_of_quarter @@ -145,7 +152,7 @@ module DateAndTime # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000 # now.beginning_of_year # => Thu, 01 Jan 2015 00:00:00 +0000 def beginning_of_year - change(:month => 1).beginning_of_month + change(month: 1).beginning_of_month end alias :at_beginning_of_year :beginning_of_year @@ -283,12 +290,17 @@ module DateAndTime # Returns a new date/time representing the end of the year. # DateTime objects will have a time set to 23:59:59. def end_of_year - change(:month => 12).end_of_month + change(month: 12).end_of_month end alias :at_end_of_year :end_of_year + # Returns a Range representing the whole day of the current date/time. + def all_day + beginning_of_day..end_of_day + end + # Returns a Range representing the whole week of the current date/time. - # Week starts on start_day, default is <tt>Date.week_start</tt> or <tt>config.week_start</tt> when set. + # Week starts on start_day, default is <tt>Date.beginning_of_week</tt> or <tt>config.beginning_of_week</tt> when set. def all_week(start_day = Date.beginning_of_week) beginning_of_week(start_day)..end_of_week(start_day) end @@ -322,7 +334,7 @@ module DateAndTime end def copy_time_to(other) - other.change(hour: hour, min: min, sec: sec, usec: try(:usec)) + other.change(hour: hour, min: min, sec: sec, nsec: try(:nsec)) end end end diff --git a/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb new file mode 100644 index 0000000000..db95ae0db5 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb @@ -0,0 +1,22 @@ +require "active_support/core_ext/module/attribute_accessors" + +module DateAndTime + module Compatibility + # If true, +to_time+ preserves the timezone offset of receiver. + # + # NOTE: With Ruby 2.4+ the default for +to_time+ changed from + # converting to the local system time, to preserving the offset + # of the receiver. For backwards compatibility we're overriding + # this behavior, but new apps will have an initializer that sets + # this to true, because the new behavior is preferred. + mattr_accessor(:preserve_timezone, instance_writer: false) { false } + + def to_time + if preserve_timezone + @_to_time_with_instance_offset ||= getlocal(utc_offset) + else + @_to_time_with_system_offset ||= getlocal + end + end + end +end diff --git a/activesupport/lib/active_support/core_ext/date_and_time/zones.rb b/activesupport/lib/active_support/core_ext/date_and_time/zones.rb index d29a8db5cf..edd724f1d0 100644 --- a/activesupport/lib/active_support/core_ext/date_and_time/zones.rb +++ b/activesupport/lib/active_support/core_ext/date_and_time/zones.rb @@ -5,7 +5,7 @@ module DateAndTime # # Time.zone = 'Hawaii' # => 'Hawaii' # Time.utc(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00 - # Date.new(2000).in_time_zone # => Sat, 01 Jan 2000 00:00:00 HST -10:00 + # Date.new(2000).in_time_zone # => Sat, 01 Jan 2000 00:00:00 HST -10:00 # # This method is similar to Time#localtime, except that it uses <tt>Time.zone</tt> as the local zone # instead of the operating system's time zone. @@ -14,7 +14,7 @@ module DateAndTime # and the conversion will be based on that zone instead of <tt>Time.zone</tt>. # # Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00 - # Date.new(2000).in_time_zone('Alaska') # => Sat, 01 Jan 2000 00:00:00 AKST -09:00 + # Date.new(2000).in_time_zone('Alaska') # => Sat, 01 Jan 2000 00:00:00 AKST -09:00 def in_time_zone(zone = ::Time.zone) time_zone = ::Time.find_zone! zone time = acts_like?(:time) ? self : nil @@ -22,19 +22,18 @@ module DateAndTime if time_zone time_with_zone(time, time_zone) else - time || self.to_time + time || to_time end end private - def time_with_zone(time, zone) - if time - ActiveSupport::TimeWithZone.new(time.utc? ? time : time.getutc, zone) - else - ActiveSupport::TimeWithZone.new(nil, zone, to_time(:utc)) + def time_with_zone(time, zone) + if time + ActiveSupport::TimeWithZone.new(time.utc? ? time : time.getutc, zone) + else + ActiveSupport::TimeWithZone.new(nil, zone, to_time(:utc)) + end end - end end end - diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb index bcb228b09a..6fd498f864 100644 --- a/activesupport/lib/active_support/core_ext/date_time.rb +++ b/activesupport/lib/active_support/core_ext/date_time.rb @@ -1,5 +1,5 @@ -require 'active_support/core_ext/date_time/acts_like' -require 'active_support/core_ext/date_time/blank' -require 'active_support/core_ext/date_time/calculations' -require 'active_support/core_ext/date_time/conversions' -require 'active_support/core_ext/date_time/zones' +require "active_support/core_ext/date_time/acts_like" +require "active_support/core_ext/date_time/blank" +require "active_support/core_ext/date_time/calculations" +require "active_support/core_ext/date_time/compatibility" +require "active_support/core_ext/date_time/conversions" diff --git a/activesupport/lib/active_support/core_ext/date_time/acts_like.rb b/activesupport/lib/active_support/core_ext/date_time/acts_like.rb index 8fbbe0d3e9..6f50f55a53 100644 --- a/activesupport/lib/active_support/core_ext/date_time/acts_like.rb +++ b/activesupport/lib/active_support/core_ext/date_time/acts_like.rb @@ -1,5 +1,5 @@ -require 'date' -require 'active_support/core_ext/object/acts_like' +require "date" +require "active_support/core_ext/object/acts_like" class DateTime # Duck-types as a Date-like class. See Object#acts_like?. diff --git a/activesupport/lib/active_support/core_ext/date_time/blank.rb b/activesupport/lib/active_support/core_ext/date_time/blank.rb index 56981b75fb..b475fd926d 100644 --- a/activesupport/lib/active_support/core_ext/date_time/blank.rb +++ b/activesupport/lib/active_support/core_ext/date_time/blank.rb @@ -1,4 +1,4 @@ -require 'date' +require "date" class DateTime #:nodoc: # No DateTime is ever blank: diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb index 95617fb8c2..70d5c9af8e 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -1,4 +1,4 @@ -require 'date' +require "date" class DateTime class << self @@ -28,6 +28,13 @@ class DateTime end_of_day.to_i - to_i end + # Returns the fraction of a second as a +Rational+ + # + # DateTime.new(2012, 8, 29, 0, 0, 0.5).subsec # => (1/2) + def subsec + sec_fraction + end + # Returns a new DateTime where one or more of the elements have been changed # according to the +options+ parameter. The time options (<tt>:hour</tt>, # <tt>:min</tt>, <tt>:sec</tt>) reset cascadingly, so if only the hour is @@ -68,7 +75,7 @@ class DateTime end d = to_date.advance(options) - datetime_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day) + datetime_advanced_by_date = change(year: d.year, month: d.month, day: d.day) seconds_to_advance = \ options.fetch(:seconds, 0) + options.fetch(:minutes, 0) * 60 + @@ -97,7 +104,7 @@ class DateTime # Returns a new DateTime representing the start of the day (0:00). def beginning_of_day - change(:hour => 0) + change(hour: 0) end alias :midnight :beginning_of_day alias :at_midnight :beginning_of_day @@ -105,7 +112,7 @@ class DateTime # Returns a new DateTime representing the middle of the day (12:00) def middle_of_day - change(:hour => 12) + change(hour: 12) end alias :midday :middle_of_day alias :noon :middle_of_day @@ -115,42 +122,60 @@ class DateTime # Returns a new DateTime representing the end of the day (23:59:59). def end_of_day - change(:hour => 23, :min => 59, :sec => 59) + change(hour: 23, min: 59, sec: 59) end alias :at_end_of_day :end_of_day # Returns a new DateTime representing the start of the hour (hh:00:00). def beginning_of_hour - change(:min => 0) + change(min: 0) end alias :at_beginning_of_hour :beginning_of_hour # Returns a new DateTime representing the end of the hour (hh:59:59). def end_of_hour - change(:min => 59, :sec => 59) + change(min: 59, sec: 59) end alias :at_end_of_hour :end_of_hour # Returns a new DateTime representing the start of the minute (hh:mm:00). def beginning_of_minute - change(:sec => 0) + change(sec: 0) end alias :at_beginning_of_minute :beginning_of_minute # Returns a new DateTime representing the end of the minute (hh:mm:59). def end_of_minute - change(:sec => 59) + change(sec: 59) end alias :at_end_of_minute :end_of_minute - # Adjusts DateTime to UTC by adding its offset value; offset is set to 0. + # Returns a <tt>Time</tt> instance of the simultaneous time in the system timezone. + def localtime(utc_offset = nil) + utc = new_offset(0) + + Time.utc( + utc.year, utc.month, utc.day, + utc.hour, utc.min, utc.sec + utc.sec_fraction + ).getlocal(utc_offset) + end + alias_method :getlocal, :localtime + + # Returns a <tt>Time</tt> instance of the simultaneous time in the UTC timezone. # # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)) # => Mon, 21 Feb 2005 10:11:12 -0600 - # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 +0000 + # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 UTC def utc - new_offset(0) + utc = new_offset(0) + + Time.utc( + utc.year, utc.month, utc.day, + utc.hour, utc.min, utc.sec + utc.sec_fraction + ) end + alias_method :getgm, :utc alias_method :getutc, :utc + alias_method :gmtime, :utc # Returns +true+ if <tt>offset == 0</tt>. def utc? @@ -165,13 +190,10 @@ class DateTime # Layers additional behavior on DateTime#<=> so that Time and # ActiveSupport::TimeWithZone instances can be compared with a DateTime. def <=>(other) - if other.kind_of?(Infinity) - super - elsif other.respond_to? :to_datetime + if other.respond_to? :to_datetime super other.to_datetime rescue nil else - nil + super end end - end diff --git a/activesupport/lib/active_support/core_ext/date_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb new file mode 100644 index 0000000000..30bb7f4a60 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb @@ -0,0 +1,5 @@ +require "active_support/core_ext/date_and_time/compatibility" + +class DateTime + prepend DateAndTime::Compatibility +end diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb index f59d05b214..44ae96dbe8 100644 --- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb @@ -1,8 +1,8 @@ -require 'date' -require 'active_support/inflector/methods' -require 'active_support/core_ext/time/conversions' -require 'active_support/core_ext/date_time/calculations' -require 'active_support/values/time_zone' +require "date" +require "active_support/inflector/methods" +require "active_support/core_ext/time/conversions" +require "active_support/core_ext/date_time/calculations" +require "active_support/values/time_zone" class DateTime # Convert to a formatted string. See Time::DATE_FORMATS for predefined formats. @@ -95,11 +95,11 @@ class DateTime private - def offset_in_seconds - (offset * 86400).to_i - end + def offset_in_seconds + (offset * 86400).to_i + end - def seconds_since_unix_epoch - (jd - 2440588) * 86400 - offset_in_seconds + seconds_since_midnight - end + def seconds_since_unix_epoch + (jd - 2440588) * 86400 - offset_in_seconds + seconds_since_midnight + end end diff --git a/activesupport/lib/active_support/core_ext/date_time/zones.rb b/activesupport/lib/active_support/core_ext/date_time/zones.rb deleted file mode 100644 index c39f358395..0000000000 --- a/activesupport/lib/active_support/core_ext/date_time/zones.rb +++ /dev/null @@ -1,6 +0,0 @@ -require 'date' -require 'active_support/core_ext/date_and_time/zones' - -class DateTime - include DateAndTime::Zones -end diff --git a/activesupport/lib/active_support/core_ext/digest/uuid.rb b/activesupport/lib/active_support/core_ext/digest/uuid.rb index 593c51bba2..e6d60e3267 100644 --- a/activesupport/lib/active_support/core_ext/digest/uuid.rb +++ b/activesupport/lib/active_support/core_ext/digest/uuid.rb @@ -1,4 +1,4 @@ -require 'securerandom' +require "securerandom" module Digest module UUID @@ -26,7 +26,7 @@ module Digest hash.update(uuid_namespace) hash.update(name) - ary = hash.digest.unpack('NnnnnN') + ary = hash.digest.unpack("NnnnnN") ary[2] = (ary[2] & 0x0FFF) | (version << 12) ary[3] = (ary[3] & 0x3FFF) | 0x8000 @@ -35,12 +35,12 @@ module Digest # Convenience method for uuid_from_hash using Digest::MD5. def self.uuid_v3(uuid_namespace, name) - self.uuid_from_hash(Digest::MD5, uuid_namespace, name) + uuid_from_hash(Digest::MD5, uuid_namespace, name) end # Convenience method for uuid_from_hash using Digest::SHA1. def self.uuid_v5(uuid_namespace, name) - self.uuid_from_hash(Digest::SHA1, uuid_namespace, name) + uuid_from_hash(Digest::SHA1, uuid_namespace, name) end # Convenience method for SecureRandom.uuid. diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index 8a74ad4d66..8ebe758078 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -17,11 +17,12 @@ module Enumerable # The default sum of an empty list is zero. You can override this default: # # [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0) - def sum(identity = 0, &block) + def sum(identity = nil, &block) if block_given? map(&block).sum(identity) else - inject(:+) || identity + sum = identity ? inject(identity, :+) : inject(:+) + sum || identity || 0 end end @@ -33,7 +34,9 @@ module Enumerable # => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...} def index_by if block_given? - Hash[map { |elem| [yield(elem), elem] }] + result = {} + each { |elem| result[yield(elem)] = elem } + result else to_enum(:index_by) { size if respond_to?(:size) } end @@ -91,15 +94,36 @@ end class Range #:nodoc: # Optimize range sum to use arithmetic progression if a block is not given and # we have a range of numeric values. - def sum(identity = 0) + def sum(identity = nil) if block_given? || !(first.is_a?(Integer) && last.is_a?(Integer)) super else actual_last = exclude_end? ? (last - 1) : last if actual_last >= first - (actual_last - first + 1) * (actual_last + first) / 2 + sum = identity || 0 + sum + (actual_last - first + 1) * (actual_last + first) / 2 else - identity + identity || 0 + end + end + end +end + +# Array#sum was added in Ruby 2.4 but it only works with Numeric elements. +# +# We tried shimming it to attempt the fast native method, rescue TypeError, +# 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 + + def sum(init = nil, &block) #:nodoc: + if init.is_a?(Numeric) || first.is_a?(Numeric) + init ||= 0 + orig_sum(init, &block) + else + super end end end diff --git a/activesupport/lib/active_support/core_ext/file.rb b/activesupport/lib/active_support/core_ext/file.rb index dc24afbe7f..6d99bad2af 100644 --- a/activesupport/lib/active_support/core_ext/file.rb +++ b/activesupport/lib/active_support/core_ext/file.rb @@ -1 +1 @@ -require 'active_support/core_ext/file/atomic' +require "active_support/core_ext/file/atomic" diff --git a/activesupport/lib/active_support/core_ext/file/atomic.rb b/activesupport/lib/active_support/core_ext/file/atomic.rb index 463fd78412..8d6c0d3685 100644 --- a/activesupport/lib/active_support/core_ext/file/atomic.rb +++ b/activesupport/lib/active_support/core_ext/file/atomic.rb @@ -1,4 +1,4 @@ -require 'fileutils' +require "fileutils" class File # Write to a file atomically. Useful for situations where you don't @@ -17,7 +17,7 @@ class File # file.write('hello') # end def self.atomic_write(file_name, temp_dir = dirname(file_name)) - require 'tempfile' unless defined?(Tempfile) + require "tempfile" unless defined?(Tempfile) Tempfile.open(".#{basename(file_name)}", temp_dir) do |temp_file| temp_file.binmode @@ -53,11 +53,11 @@ class File # Private utility method. def self.probe_stat_in(dir) #:nodoc: basename = [ - '.permissions_check', + ".permissions_check", Thread.current.object_id, Process.pid, rand(1000000) - ].join('.') + ].join(".") file_name = join(dir, basename) FileUtils.touch(file_name) diff --git a/activesupport/lib/active_support/core_ext/hash.rb b/activesupport/lib/active_support/core_ext/hash.rb index af4d1da0eb..c819307e8a 100644 --- a/activesupport/lib/active_support/core_ext/hash.rb +++ b/activesupport/lib/active_support/core_ext/hash.rb @@ -1,9 +1,9 @@ -require 'active_support/core_ext/hash/compact' -require 'active_support/core_ext/hash/conversions' -require 'active_support/core_ext/hash/deep_merge' -require 'active_support/core_ext/hash/except' -require 'active_support/core_ext/hash/indifferent_access' -require 'active_support/core_ext/hash/keys' -require 'active_support/core_ext/hash/reverse_merge' -require 'active_support/core_ext/hash/slice' -require 'active_support/core_ext/hash/transform_values' +require "active_support/core_ext/hash/compact" +require "active_support/core_ext/hash/conversions" +require "active_support/core_ext/hash/deep_merge" +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/indifferent_access" +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/hash/reverse_merge" +require "active_support/core_ext/hash/slice" +require "active_support/core_ext/hash/transform_values" diff --git a/activesupport/lib/active_support/core_ext/hash/compact.rb b/activesupport/lib/active_support/core_ext/hash/compact.rb index 5dc9a05ec7..5cae495bda 100644 --- a/activesupport/lib/active_support/core_ext/hash/compact.rb +++ b/activesupport/lib/active_support/core_ext/hash/compact.rb @@ -1,20 +1,27 @@ class Hash - # Returns a hash with non +nil+ values. - # - # hash = { a: true, b: false, c: nil} - # hash.compact # => { a: true, b: false} - # hash # => { a: true, b: false, c: nil} - # { c: nil }.compact # => {} - def compact - self.select { |_, value| !value.nil? } + unless Hash.instance_methods(false).include?(:compact) + # Returns a hash with non +nil+ values. + # + # hash = { a: true, b: false, c: nil } + # hash.compact # => { a: true, b: false } + # hash # => { a: true, b: false, c: nil } + # { c: nil }.compact # => {} + # { c: true }.compact # => { c: true } + def compact + select { |_, value| !value.nil? } + end end - # Replaces current hash with non +nil+ values. - # - # hash = { a: true, b: false, c: nil} - # hash.compact! # => { a: true, b: false} - # hash # => { a: true, b: false} - def compact! - self.reject! { |_, value| value.nil? } + unless Hash.instance_methods(false).include?(:compact!) + # Replaces current hash with non +nil+ values. + # Returns nil if no changes were made, otherwise returns the hash. + # + # hash = { a: true, b: false, c: nil } + # hash.compact! # => { a: true, b: false } + # hash # => { a: true, b: false } + # { c: true }.compact! # => nil + def compact! + reject! { |_, value| value.nil? } + end end end diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 6741e732f0..3cd96ccb9a 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -1,11 +1,11 @@ -require 'active_support/xml_mini' -require 'active_support/time' -require 'active_support/core_ext/object/blank' -require 'active_support/core_ext/object/to_param' -require 'active_support/core_ext/object/to_query' -require 'active_support/core_ext/array/wrap' -require 'active_support/core_ext/hash/reverse_merge' -require 'active_support/core_ext/string/inflections' +require "active_support/xml_mini" +require "active_support/time" +require "active_support/core_ext/object/blank" +require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/to_query" +require "active_support/core_ext/array/wrap" +require "active_support/core_ext/hash/reverse_merge" +require "active_support/core_ext/string/inflections" class Hash # Returns a string containing an XML representation of its receiver: @@ -31,7 +31,7 @@ class Hash # with +key+ as <tt>:root</tt>, and +key+ singularized as second argument. The # callable can add nodes by using <tt>options[:builder]</tt>. # - # 'foo'.to_xml(lambda { |options, key| options[:builder].b(key) }) + # {foo: lambda { |options, key| options[:builder].b(key) }}.to_xml # # => "<b>foo</b>" # # * If +value+ responds to +to_xml+ the method is invoked with +key+ as <tt>:root</tt>. @@ -55,8 +55,7 @@ class Hash # # XML_TYPE_NAMES = { # "Symbol" => "symbol", - # "Fixnum" => "integer", - # "Bignum" => "integer", + # "Integer" => "integer", # "BigDecimal" => "decimal", # "Float" => "float", # "TrueClass" => "boolean", @@ -72,11 +71,11 @@ class Hash # configure your own builder with the <tt>:builder</tt> option. The method also accepts # options like <tt>:dasherize</tt> and friends, they are forwarded to the builder. def to_xml(options = {}) - require 'active_support/builder' unless defined?(Builder) + require "active_support/builder" unless defined?(Builder) options = options.dup options[:indent] ||= 2 - options[:root] ||= 'hash' + options[:root] ||= "hash" options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent]) builder = options[:builder] @@ -160,36 +159,36 @@ module ActiveSupport private def normalize_keys(params) case params - when Hash - Hash[params.map { |k,v| [k.to_s.tr('-', '_'), normalize_keys(v)] } ] - when Array - params.map { |v| normalize_keys(v) } + when Hash + Hash[params.map { |k,v| [k.to_s.tr("-", "_"), normalize_keys(v)] } ] + when Array + params.map { |v| normalize_keys(v) } else - params + params end end def deep_to_h(value) case value - when Hash - process_hash(value) - when Array - process_array(value) - when String - value + when Hash + process_hash(value) + when Array + process_array(value) + when String + value else - raise "can't typecast #{value.class.name} - #{value.inspect}" + raise "can't typecast #{value.class.name} - #{value.inspect}" end end def process_hash(value) - if value.include?('type') && !value['type'].is_a?(Hash) && @disallowed_types.include?(value['type']) - raise DisallowedType, value['type'] + if value.include?("type") && !value["type"].is_a?(Hash) && @disallowed_types.include?(value["type"]) + raise DisallowedType, value["type"] end if become_array?(value) _, entries = Array.wrap(value.detect { |k,v| not v.is_a?(String) }) - if entries.nil? || value['__content__'].try(:empty?) + if entries.nil? || value["__content__"].try(:empty?) [] else case entries @@ -205,28 +204,28 @@ module ActiveSupport process_content(value) elsif become_empty_string?(value) - '' + "" elsif become_hash?(value) xml_value = Hash[value.map { |k,v| [k, deep_to_h(v)] }] # Turn { files: { file: #<StringIO> } } into { files: #<StringIO> } so it is compatible with # how multipart uploaded files from HTML appear - xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value + xml_value["file"].is_a?(StringIO) ? xml_value["file"] : xml_value end end def become_content?(value) - value['type'] == 'file' || (value['__content__'] && (value.keys.size == 1 || value['__content__'].present?)) + value["type"] == "file" || (value["__content__"] && (value.keys.size == 1 || value["__content__"].present?)) end def become_array?(value) - value['type'] == 'array' + value["type"] == "array" end def become_empty_string?(value) # { "string" => true } # No tests fail when the second term is removed. - value['type'] == 'string' && value['nil'] != 'true' + value["type"] == "string" && value["nil"] != "true" end def become_hash?(value) @@ -235,19 +234,19 @@ module ActiveSupport def nothing?(value) # blank or nil parsed values are represented by nil - value.blank? || value['nil'] == 'true' + value.blank? || value["nil"] == "true" end def garbage?(value) # If the type is the only element which makes it then # this still makes the value nil, except if type is # an XML node(where type['value'] is a Hash) - value['type'] && !value['type'].is_a?(::Hash) && value.size == 1 + value["type"] && !value["type"].is_a?(::Hash) && value.size == 1 end def process_content(value) - content = value['__content__'] - if parser = ActiveSupport::XmlMini::PARSING[value['type']] + content = value["__content__"] + if parser = ActiveSupport::XmlMini::PARSING[value["type"]] parser.arity == 1 ? parser.call(content) : parser.call(content, value) else content @@ -258,6 +257,5 @@ module ActiveSupport value.map! { |i| deep_to_h(i) } value.length > 1 ? value : value.first end - end end diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb index 6df7b4121b..3e1ccecb6c 100644 --- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb +++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb @@ -1,7 +1,6 @@ -require 'active_support/hash_with_indifferent_access' +require "active_support/hash_with_indifferent_access" class Hash - # Returns an <tt>ActiveSupport::HashWithIndifferentAccess</tt> out of its receiver: # # { a: 1 }.with_indifferent_access['a'] # => 1 diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb index 8b2366c4b3..b7089357a8 100644 --- a/activesupport/lib/active_support/core_ext/hash/keys.rb +++ b/activesupport/lib/active_support/core_ext/hash/keys.rb @@ -11,7 +11,7 @@ class Hash # hash.transform_keys.with_index { |k, i| [k, i].join } # => {"name0"=>"Rob", "age1"=>"28"} def transform_keys return enum_for(:transform_keys) { size } unless block_given? - result = self.class.new + result = {} each_key do |key| result[yield(key)] = self[key] end @@ -52,14 +52,14 @@ class Hash # hash.symbolize_keys # # => {:name=>"Rob", :age=>"28"} def symbolize_keys - transform_keys{ |key| key.to_sym rescue key } + transform_keys { |key| key.to_sym rescue key } end alias_method :to_options, :symbolize_keys # Destructively converts all keys to symbols, as long as they respond # to +to_sym+. Same as +symbolize_keys+, but modifies +self+. def symbolize_keys! - transform_keys!{ |key| key.to_sym rescue key } + transform_keys! { |key| key.to_sym rescue key } end alias_method :to_options!, :symbolize_keys! @@ -128,14 +128,14 @@ class Hash # hash.deep_symbolize_keys # # => {:person=>{:name=>"Rob", :age=>"28"}} def deep_symbolize_keys - deep_transform_keys{ |key| key.to_sym rescue key } + deep_transform_keys { |key| key.to_sym rescue key } end # Destructively converts all keys to symbols, as long as they respond # to +to_sym+. This includes the keys from the root hash and from all # nested hashes and arrays. def deep_symbolize_keys! - deep_transform_keys!{ |key| key.to_sym rescue key } + deep_transform_keys! { |key| key.to_sym rescue key } end private @@ -147,7 +147,7 @@ class Hash result[yield(key)] = _deep_transform_keys_in_object(value, &block) end when Array - object.map {|e| _deep_transform_keys_in_object(e, &block) } + object.map { |e| _deep_transform_keys_in_object(e, &block) } else object end @@ -162,7 +162,7 @@ class Hash end object when Array - object.map! {|e| _deep_transform_keys_in_object!(e, &block)} + object.map! { |e| _deep_transform_keys_in_object!(e, &block) } else object end diff --git a/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb b/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb index fbb482435d..db3e7508e7 100644 --- a/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb +++ b/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb @@ -16,7 +16,7 @@ class Hash # Destructive +reverse_merge+. def reverse_merge!(other_hash) # right wins if there is no left - merge!( other_hash ){|key,left,right| left } + merge!( other_hash ) { |key,left,right| left } end alias_method :reverse_update, :reverse_merge! end diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb index 1d5f38231a..161b00dfb3 100644 --- a/activesupport/lib/active_support/core_ext/hash/slice.rb +++ b/activesupport/lib/active_support/core_ext/hash/slice.rb @@ -1,11 +1,11 @@ class Hash - # Slices a hash to include only the given keys. Returns a hash containing + # Slices a hash to include only the given keys. Returns a hash containing # the given keys. - # + # # { a: 1, b: 2, c: 3, d: 4 }.slice(:a, :b) # # => {:a=>1, :b=>2} - # - # This is useful for limiting an options hash to valid keys before + # + # This is useful for limiting an options hash to valid keys before # passing to a method: # # def search(criteria = {}) diff --git a/activesupport/lib/active_support/core_ext/hash/transform_values.rb b/activesupport/lib/active_support/core_ext/hash/transform_values.rb index 7d507ac998..2f693bff0c 100644 --- a/activesupport/lib/active_support/core_ext/hash/transform_values.rb +++ b/activesupport/lib/active_support/core_ext/hash/transform_values.rb @@ -16,7 +16,7 @@ class Hash result[key] = yield(value) end result - end + end unless method_defined? :transform_values # Destructively converts all values using the +block+ operations. # Same as +transform_values+ but modifies +self+. @@ -25,5 +25,6 @@ class Hash each do |key, value| self[key] = yield(value) end - end + end unless method_defined? :transform_values! + # TODO: Remove this file when supporting only Ruby 2.4+. end diff --git a/activesupport/lib/active_support/core_ext/integer.rb b/activesupport/lib/active_support/core_ext/integer.rb index a44a1b4c74..8f0c55f9d3 100644 --- a/activesupport/lib/active_support/core_ext/integer.rb +++ b/activesupport/lib/active_support/core_ext/integer.rb @@ -1,3 +1,3 @@ -require 'active_support/core_ext/integer/multiple' -require 'active_support/core_ext/integer/inflections' -require 'active_support/core_ext/integer/time' +require "active_support/core_ext/integer/multiple" +require "active_support/core_ext/integer/inflections" +require "active_support/core_ext/integer/time" diff --git a/activesupport/lib/active_support/core_ext/integer/inflections.rb b/activesupport/lib/active_support/core_ext/integer/inflections.rb index 56f2ed5985..bc21b65533 100644 --- a/activesupport/lib/active_support/core_ext/integer/inflections.rb +++ b/activesupport/lib/active_support/core_ext/integer/inflections.rb @@ -1,4 +1,4 @@ -require 'active_support/inflector' +require "active_support/inflector" class Integer # Ordinalize turns a number into an ordinal string used to denote the diff --git a/activesupport/lib/active_support/core_ext/integer/time.rb b/activesupport/lib/active_support/core_ext/integer/time.rb index 87185b024f..4a64872392 100644 --- a/activesupport/lib/active_support/core_ext/integer/time.rb +++ b/activesupport/lib/active_support/core_ext/integer/time.rb @@ -1,5 +1,5 @@ -require 'active_support/duration' -require 'active_support/core_ext/numeric/time' +require "active_support/duration" +require "active_support/core_ext/numeric/time" class Integer # Enables the use of time calculations and declarations, like <tt>45.minutes + diff --git a/activesupport/lib/active_support/core_ext/kernel.rb b/activesupport/lib/active_support/core_ext/kernel.rb index 364ed9d65f..3d41ff7876 100644 --- a/activesupport/lib/active_support/core_ext/kernel.rb +++ b/activesupport/lib/active_support/core_ext/kernel.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/kernel/agnostics' -require 'active_support/core_ext/kernel/concern' -require 'active_support/core_ext/kernel/reporting' -require 'active_support/core_ext/kernel/singleton_class' +require "active_support/core_ext/kernel/agnostics" +require "active_support/core_ext/kernel/concern" +require "active_support/core_ext/kernel/reporting" +require "active_support/core_ext/kernel/singleton_class" diff --git a/activesupport/lib/active_support/core_ext/kernel/concern.rb b/activesupport/lib/active_support/core_ext/kernel/concern.rb index 18bcc01fa4..307a7f7a63 100644 --- a/activesupport/lib/active_support/core_ext/kernel/concern.rb +++ b/activesupport/lib/active_support/core_ext/kernel/concern.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/module/concerning' +require "active_support/core_ext/module/concerning" module Kernel module_function diff --git a/activesupport/lib/active_support/core_ext/kernel/debugger.rb b/activesupport/lib/active_support/core_ext/kernel/debugger.rb index 1fde3db070..dccfa3e9bb 100644 --- a/activesupport/lib/active_support/core_ext/kernel/debugger.rb +++ b/activesupport/lib/active_support/core_ext/kernel/debugger.rb @@ -1,3 +1,3 @@ -require 'active_support/deprecation' +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/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb index 60732eb41a..cd00d1b662 100644 --- a/activesupport/lib/active_support/core_ext/load_error.rb +++ b/activesupport/lib/active_support/core_ext/load_error.rb @@ -1,4 +1,5 @@ -require 'active_support/deprecation/proxy_wrappers' +require "active_support/deprecation" +require "active_support/deprecation/proxy_wrappers" class LoadError REGEXPS = [ @@ -23,8 +24,8 @@ class LoadError # 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) - location.sub(/\.rb$/, ''.freeze) == path.sub(/\.rb$/, ''.freeze) + location.sub(/\.rb$/, "".freeze) == path.sub(/\.rb$/, "".freeze) end end -MissingSourceFile = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('MissingSourceFile', 'LoadError') +MissingSourceFile = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("MissingSourceFile", "LoadError") diff --git a/activesupport/lib/active_support/core_ext/marshal.rb b/activesupport/lib/active_support/core_ext/marshal.rb index e333b26133..edfc8296fe 100644 --- a/activesupport/lib/active_support/core_ext/marshal.rb +++ b/activesupport/lib/active_support/core_ext/marshal.rb @@ -3,9 +3,12 @@ module ActiveSupport def load(source) super(source) rescue ArgumentError, NameError => exc - if exc.message.match(%r|undefined class/module (.+)|) + if exc.message.match(%r|undefined class/module (.+?)(?:::)?\z|) # try loading the class/module - $1.constantize + loaded = $1.constantize + + raise unless $1 == loaded.name + # if it is an IO we need to go back to read the object source.rewind if source.respond_to?(:rewind) retry diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb index ef038331c2..57feea69a5 100644 --- a/activesupport/lib/active_support/core_ext/module.rb +++ b/activesupport/lib/active_support/core_ext/module.rb @@ -1,12 +1,12 @@ -require 'active_support/core_ext/module/aliasing' -require 'active_support/core_ext/module/introspection' -require 'active_support/core_ext/module/anonymous' -require 'active_support/core_ext/module/reachable' -require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/core_ext/module/attribute_accessors_per_thread' -require 'active_support/core_ext/module/attr_internal' -require 'active_support/core_ext/module/concerning' -require 'active_support/core_ext/module/delegation' -require 'active_support/core_ext/module/deprecation' -require 'active_support/core_ext/module/remove_method' -require 'active_support/core_ext/module/qualified_const' +require "active_support/core_ext/module/aliasing" +require "active_support/core_ext/module/introspection" +require "active_support/core_ext/module/anonymous" +require "active_support/core_ext/module/reachable" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/module/attribute_accessors_per_thread" +require "active_support/core_ext/module/attr_internal" +require "active_support/core_ext/module/concerning" +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/module/deprecation" +require "active_support/core_ext/module/remove_method" +require "active_support/core_ext/module/qualified_const" diff --git a/activesupport/lib/active_support/core_ext/module/aliasing.rb b/activesupport/lib/active_support/core_ext/module/aliasing.rb index b6934b9c54..4a04bdd446 100644 --- a/activesupport/lib/active_support/core_ext/module/aliasing.rb +++ b/activesupport/lib/active_support/core_ext/module/aliasing.rb @@ -28,7 +28,7 @@ class Module # Strip out punctuation on predicates, bang or writer methods since # e.g. target?_without_feature is not a valid method name. - aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1 + aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ""), $1 yield(aliased_target, punctuation) if block_given? with_method = "#{aliased_target}_with_#{feature}#{punctuation}" @@ -65,6 +65,9 @@ class Module # e.subject = "Megastars" # e.title # => "Megastars" def alias_attribute(new_name, old_name) + # The following reader methods use an explicit `self` receiver in order to + # support aliases that start with an uppercase letter. Otherwise, they would + # be resolved as constants instead. module_eval <<-STR, __FILE__, __LINE__ + 1 def #{new_name}; self.#{old_name}; end # def subject; self.title; end def #{new_name}?; self.#{old_name}?; end # def subject?; self.title?; end diff --git a/activesupport/lib/active_support/core_ext/module/attr_internal.rb b/activesupport/lib/active_support/core_ext/module/attr_internal.rb index 93fb598650..5081d5f7a3 100644 --- a/activesupport/lib/active_support/core_ext/module/attr_internal.rb +++ b/activesupport/lib/active_support/core_ext/module/attr_internal.rb @@ -1,12 +1,12 @@ class Module # Declares an attribute reader backed by an internally-named instance variable. def attr_internal_reader(*attrs) - attrs.each {|attr_name| attr_internal_define(attr_name, :reader)} + attrs.each { |attr_name| attr_internal_define(attr_name, :reader) } end # Declares an attribute writer backed by an internally-named instance variable. def attr_internal_writer(*attrs) - attrs.each {|attr_name| attr_internal_define(attr_name, :writer)} + attrs.each { |attr_name| attr_internal_define(attr_name, :writer) } end # Declares an attribute reader and writer backed by an internally-named instance @@ -18,7 +18,7 @@ class Module alias_method :attr_internal, :attr_internal_accessor class << self; attr_accessor :attr_internal_naming_format end - self.attr_internal_naming_format = '@_%s' + self.attr_internal_naming_format = "@_%s" private def attr_internal_ivar_name(attr) @@ -26,7 +26,7 @@ class Module end def attr_internal_define(attr_name, type) - internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, '') + internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, "") # use native attr_* methods as they are faster on some Ruby implementations send("attr_#{type}", internal_name) attr_name, internal_name = "#{attr_name}=", "#{internal_name}=" if type == :writer 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 76825862d7..90989f4f3d 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -1,4 +1,5 @@ -require 'active_support/core_ext/array/extract_options' +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/regexp" # Extends the module object with class/module and instance accessors for # class/module attributes, just like the native attr* accessors for instance @@ -27,7 +28,7 @@ class Module # <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>. # # module HairColors - # mattr_writer :hair_colors, instance_reader: false + # mattr_reader :hair_colors, instance_reader: false # end # # class Person @@ -40,7 +41,7 @@ class Module # Also, you can pass a block to set up the attribute with a default value. # # module HairColors - # cattr_reader :hair_colors do + # mattr_reader :hair_colors do # [:brown, :black, :blonde, :red] # end # end @@ -53,7 +54,7 @@ class Module def mattr_reader(*syms) options = syms.extract_options! syms.each do |sym| - raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /\A[_A-Za-z]\w*\z/ + raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym) class_eval(<<-EOS, __FILE__, __LINE__ + 1) @@#{sym} = nil unless defined? @@#{sym} @@ -119,7 +120,7 @@ class Module def mattr_writer(*syms) options = syms.extract_options! syms.each do |sym| - raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /\A[_A-Za-z]\w*\z/ + raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym) class_eval(<<-EOS, __FILE__, __LINE__ + 1) @@#{sym} = nil unless defined? @@#{sym} diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb index 8a7e6776da..1e82b4acc2 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb @@ -1,9 +1,10 @@ -require 'active_support/core_ext/array/extract_options' +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/regexp" # Extends the module object with class/module and instance accessors for # class/module attributes, just like the native attr* accessors for instance # attributes, but does so on a per-thread basis. -# +# # So the values are scoped within the Thread.current space under the class name # of the module. class Module @@ -25,7 +26,7 @@ class Module # end # # => NameError: invalid attribute name: 1_Badname # - # If you want to opt out the creation on the instance reader method, pass + # If you want to opt out of the creation of the instance reader method, pass # <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>. # # class Current @@ -33,21 +34,24 @@ class Module # end # # Current.new.user # => NoMethodError - def thread_mattr_reader(*syms) + def thread_mattr_reader(*syms) # :nodoc: options = syms.extract_options! syms.each do |sym| - raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/ + raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym) + + # The following generated method concatenates `name` because we want it + # to work with inheritance via polymorphism. class_eval(<<-EOS, __FILE__, __LINE__ + 1) def self.#{sym} - Thread.current[:"attr_#{name}_#{sym}"] + Thread.current["attr_" + name + "_#{sym}"] end EOS unless options[:instance_reader] == false || options[:instance_accessor] == false class_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{sym} - Thread.current[:"attr_#{self.class.name}_#{sym}"] + self.class.#{sym} end EOS end @@ -65,7 +69,7 @@ class Module # Current.user = "DHH" # Thread.current[:attr_Current_user] # => "DHH" # - # If you want to opt out the instance writer method, pass + # If you want to opt out of the creation of the instance writer method, pass # <tt>instance_writer: false</tt> or <tt>instance_accessor: false</tt>. # # class Current @@ -73,20 +77,23 @@ class Module # end # # Current.new.user = "DHH" # => NoMethodError - def thread_mattr_writer(*syms) + def thread_mattr_writer(*syms) # :nodoc: options = syms.extract_options! syms.each do |sym| - raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/ + raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym) + + # The following generated method concatenates `name` because we want it + # to work with inheritance via polymorphism. class_eval(<<-EOS, __FILE__, __LINE__ + 1) def self.#{sym}=(obj) - Thread.current[:"attr_#{name}_#{sym}"] = obj + Thread.current["attr_" + name + "_#{sym}"] = obj end EOS unless options[:instance_writer] == false || options[:instance_accessor] == false class_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{sym}=(obj) - Thread.current[:"attr_#{self.class.name}_#{sym}"] = obj + self.class.#{sym} = obj end EOS end @@ -133,9 +140,9 @@ class Module # # Current.new.user = "DHH" # => NoMethodError # Current.new.user # => NoMethodError - def thread_mattr_accessor(*syms, &blk) - thread_mattr_reader(*syms, &blk) - thread_mattr_writer(*syms, &blk) + def thread_mattr_accessor(*syms) + thread_mattr_reader(*syms) + thread_mattr_writer(*syms) end alias :thread_cattr_accessor :thread_mattr_accessor end diff --git a/activesupport/lib/active_support/core_ext/module/concerning.rb b/activesupport/lib/active_support/core_ext/module/concerning.rb index 65b88b9bbd..97b0a382ce 100644 --- a/activesupport/lib/active_support/core_ext/module/concerning.rb +++ b/activesupport/lib/active_support/core_ext/module/concerning.rb @@ -1,4 +1,4 @@ -require 'active_support/concern' +require "active_support/concern" class Module # = Bite-sized separation of concerns diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 0d46248582..f5f4ba61b7 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -1,4 +1,5 @@ -require 'set' +require "set" +require "active_support/core_ext/regexp" class Module # Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+ @@ -148,28 +149,24 @@ class Module # Foo.new("Bar").name # raises NoMethodError: undefined method `name' # # The target method must be public, otherwise it will raise +NoMethodError+. - # - def delegate(*methods) - options = methods.pop - unless options.is_a?(Hash) && to = options[:to] - raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).' + def delegate(*methods, to: nil, prefix: nil, allow_nil: nil) + unless to + raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter)." end - prefix, allow_nil = options.values_at(:prefix, :allow_nil) - - if prefix == true && to =~ /^[^a-z_]/ - raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.' + if prefix == true && /^[^a-z_]/.match?(to) + raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method." end method_prefix = \ if prefix "#{prefix == true ? to : prefix}_" else - '' + "" end - file, line = caller(1, 1).first.split(':'.freeze, 2) - line = line.to_i + location = caller_locations(1, 1).first + file, line = location.path, location.lineno to = to.to_s to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to) @@ -177,7 +174,7 @@ class Module methods.each do |method| # Attribute writer methods only accept one argument. Makes sure []= # methods still accept two arguments. - definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block' + definition = /[^\]]=$/.match?(method) ? "arg" : "*args, &block" # The following generated method calls the target exactly once, storing # the returned value in a dummy variable. @@ -194,7 +191,7 @@ class Module " _.#{method}(#{definition})", "end", "end" - ].join ';' + ].join ";" else exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") @@ -209,10 +206,74 @@ class Module " raise", " end", "end" - ].join ';' + ].join ";" end module_eval(method_def, file, line) end end + + # When building decorators, a common pattern may emerge: + # + # class Partition + # def initialize(first_event) + # @events = [ first_event ] + # end + # + # def people + # if @events.first.detail.people.any? + # @events.collect { |e| Array(e.detail.people) }.flatten.uniq + # else + # @events.collect(&:creator).uniq + # end + # end + # + # private + # def respond_to_missing?(name, include_private = false) + # @events.respond_to?(name, include_private) + # end + # + # def method_missing(method, *args, &block) + # @events.send(method, *args, &block) + # end + # end + # + # With `Module#delegate_missing_to`, the above is condensed to: + # + # class Partition + # delegate_missing_to :@events + # + # def initialize(first_event) + # @events = [ first_event ] + # end + # + # def people + # if @events.first.detail.people.any? + # @events.collect { |e| Array(e.detail.people) }.flatten.uniq + # else + # @events.collect(&:creator).uniq + # end + # end + # end + # + # The target can be anything callable within the object. E.g. instance + # variables, methods, constants ant the likes. + def delegate_missing_to(target) + target = target.to_s + target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target) + + module_eval <<-RUBY, __FILE__, __LINE__ + 1 + def respond_to_missing?(name, include_private = false) + #{target}.respond_to?(name, include_private) + end + + def method_missing(method, *args, &block) + if #{target}.respond_to?(method) + #{target}.public_send(method, *args, &block) + else + super + end + end + RUBY + end end diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb index f1d26ef28f..4f854a718b 100644 --- a/activesupport/lib/active_support/core_ext/module/introspection.rb +++ b/activesupport/lib/active_support/core_ext/module/introspection.rb @@ -1,4 +1,4 @@ -require 'active_support/inflector' +require "active_support/inflector" class Module # Returns the name of the module containing this one. @@ -46,9 +46,9 @@ class Module def parents parents = [] if parent_name - parts = parent_name.split('::') + parts = parent_name.split("::") until parts.empty? - parents << ActiveSupport::Inflector.constantize(parts * '::') + parents << ActiveSupport::Inflector.constantize(parts * "::") parts.pop end end @@ -57,6 +57,10 @@ class Module end def local_constants #:nodoc: + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Module#local_constants is deprecated and will be removed in Rails 5.1. + Use Module#constants(false) instead. + MSG constants(false) end end diff --git a/activesupport/lib/active_support/core_ext/module/method_transplanting.rb b/activesupport/lib/active_support/core_ext/module/method_transplanting.rb index 1fde3db070..dccfa3e9bb 100644 --- a/activesupport/lib/active_support/core_ext/module/method_transplanting.rb +++ b/activesupport/lib/active_support/core_ext/module/method_transplanting.rb @@ -1,3 +1,3 @@ -require 'active_support/deprecation' +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/activesupport/lib/active_support/core_ext/module/qualified_const.rb b/activesupport/lib/active_support/core_ext/module/qualified_const.rb index 3ea39d4267..62f0687ae9 100644 --- a/activesupport/lib/active_support/core_ext/module/qualified_const.rb +++ b/activesupport/lib/active_support/core_ext/module/qualified_const.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/string/inflections' +require "active_support/core_ext/string/inflections" #-- # Allows code reuse in the methods below without polluting Module. @@ -11,7 +11,7 @@ module ActiveSupport end def self.names(path) - path.split('::') + path.split("::") end end end diff --git a/activesupport/lib/active_support/core_ext/module/reachable.rb b/activesupport/lib/active_support/core_ext/module/reachable.rb index 5d3d0e9851..b89a38f26c 100644 --- a/activesupport/lib/active_support/core_ext/module/reachable.rb +++ b/activesupport/lib/active_support/core_ext/module/reachable.rb @@ -1,5 +1,5 @@ -require 'active_support/core_ext/module/anonymous' -require 'active_support/core_ext/string/inflections' +require "active_support/core_ext/module/anonymous" +require "active_support/core_ext/string/inflections" class Module def reachable? #:nodoc: diff --git a/activesupport/lib/active_support/core_ext/numeric.rb b/activesupport/lib/active_support/core_ext/numeric.rb index bcdc3eace2..6062f9e3a8 100644 --- a/activesupport/lib/active_support/core_ext/numeric.rb +++ b/activesupport/lib/active_support/core_ext/numeric.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/numeric/bytes' -require 'active_support/core_ext/numeric/time' -require 'active_support/core_ext/numeric/inquiry' -require 'active_support/core_ext/numeric/conversions' +require "active_support/core_ext/numeric/bytes" +require "active_support/core_ext/numeric/time" +require "active_support/core_ext/numeric/inquiry" +require "active_support/core_ext/numeric/conversions" diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb index 9d832897ed..cebfda8d31 100644 --- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb +++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb @@ -1,9 +1,8 @@ -require 'active_support/core_ext/big_decimal/conversions' -require 'active_support/number_helper' -require 'active_support/core_ext/module/deprecation' +require "active_support/core_ext/big_decimal/conversions" +require "active_support/number_helper" +require "active_support/core_ext/module/deprecation" module ActiveSupport::NumericWithFormat - # Provides options for converting numbers into formatted strings. # Options are provided for phone numbers, currency, percentage, # precision, positional notation, file size and pretty printing. @@ -15,72 +14,72 @@ module ActiveSupport::NumericWithFormat # ==== Examples # # Phone Numbers: - # 5551234.to_s(:phone) # => 555-1234 - # 1235551234.to_s(:phone) # => 123-555-1234 - # 1235551234.to_s(:phone, area_code: true) # => (123) 555-1234 - # 1235551234.to_s(:phone, delimiter: ' ') # => 123 555 1234 - # 1235551234.to_s(:phone, area_code: true, extension: 555) # => (123) 555-1234 x 555 - # 1235551234.to_s(:phone, country_code: 1) # => +1-123-555-1234 + # 5551234.to_s(:phone) # => "555-1234" + # 1235551234.to_s(:phone) # => "123-555-1234" + # 1235551234.to_s(:phone, area_code: true) # => "(123) 555-1234" + # 1235551234.to_s(:phone, delimiter: ' ') # => "123 555 1234" + # 1235551234.to_s(:phone, area_code: true, extension: 555) # => "(123) 555-1234 x 555" + # 1235551234.to_s(:phone, country_code: 1) # => "+1-123-555-1234" # 1235551234.to_s(:phone, country_code: 1, extension: 1343, delimiter: '.') - # # => +1.123.555.1234 x 1343 + # # => "+1.123.555.1234 x 1343" # # Currency: - # 1234567890.50.to_s(:currency) # => $1,234,567,890.50 - # 1234567890.506.to_s(:currency) # => $1,234,567,890.51 - # 1234567890.506.to_s(:currency, precision: 3) # => $1,234,567,890.506 - # 1234567890.506.to_s(:currency, locale: :fr) # => 1 234 567 890,51 € + # 1234567890.50.to_s(:currency) # => "$1,234,567,890.50" + # 1234567890.506.to_s(:currency) # => "$1,234,567,890.51" + # 1234567890.506.to_s(:currency, precision: 3) # => "$1,234,567,890.506" + # 1234567890.506.to_s(:currency, locale: :fr) # => "1 234 567 890,51 €" # -1234567890.50.to_s(:currency, negative_format: '(%u%n)') - # # => ($1,234,567,890.50) + # # => "($1,234,567,890.50)" # 1234567890.50.to_s(:currency, unit: '£', separator: ',', delimiter: '') - # # => £1234567890,50 + # # => "£1234567890,50" # 1234567890.50.to_s(:currency, unit: '£', separator: ',', delimiter: '', format: '%n %u') - # # => 1234567890,50 £ + # # => "1234567890,50 £" # # Percentage: - # 100.to_s(:percentage) # => 100.000% - # 100.to_s(:percentage, precision: 0) # => 100% - # 1000.to_s(:percentage, delimiter: '.', separator: ',') # => 1.000,000% - # 302.24398923423.to_s(:percentage, precision: 5) # => 302.24399% - # 1000.to_s(:percentage, locale: :fr) # => 1 000,000% - # 100.to_s(:percentage, format: '%n %') # => 100.000 % + # 100.to_s(:percentage) # => "100.000%" + # 100.to_s(:percentage, precision: 0) # => "100%" + # 1000.to_s(:percentage, delimiter: '.', separator: ',') # => "1.000,000%" + # 302.24398923423.to_s(:percentage, precision: 5) # => "302.24399%" + # 1000.to_s(:percentage, locale: :fr) # => "1 000,000%" + # 100.to_s(:percentage, format: '%n %') # => "100.000 %" # # Delimited: - # 12345678.to_s(:delimited) # => 12,345,678 - # 12345678.05.to_s(:delimited) # => 12,345,678.05 - # 12345678.to_s(:delimited, delimiter: '.') # => 12.345.678 - # 12345678.to_s(:delimited, delimiter: ',') # => 12,345,678 - # 12345678.05.to_s(:delimited, separator: ' ') # => 12,345,678 05 - # 12345678.05.to_s(:delimited, locale: :fr) # => 12 345 678,05 + # 12345678.to_s(:delimited) # => "12,345,678" + # 12345678.05.to_s(:delimited) # => "12,345,678.05" + # 12345678.to_s(:delimited, delimiter: '.') # => "12.345.678" + # 12345678.to_s(:delimited, delimiter: ',') # => "12,345,678" + # 12345678.05.to_s(:delimited, separator: ' ') # => "12,345,678 05" + # 12345678.05.to_s(:delimited, locale: :fr) # => "12 345 678,05" # 98765432.98.to_s(:delimited, delimiter: ' ', separator: ',') - # # => 98 765 432,98 + # # => "98 765 432,98" # # Rounded: - # 111.2345.to_s(:rounded) # => 111.235 - # 111.2345.to_s(:rounded, precision: 2) # => 111.23 - # 13.to_s(:rounded, precision: 5) # => 13.00000 - # 389.32314.to_s(:rounded, precision: 0) # => 389 - # 111.2345.to_s(:rounded, significant: true) # => 111 - # 111.2345.to_s(:rounded, precision: 1, significant: true) # => 100 - # 13.to_s(:rounded, precision: 5, significant: true) # => 13.000 - # 111.234.to_s(:rounded, locale: :fr) # => 111,234 + # 111.2345.to_s(:rounded) # => "111.235" + # 111.2345.to_s(:rounded, precision: 2) # => "111.23" + # 13.to_s(:rounded, precision: 5) # => "13.00000" + # 389.32314.to_s(:rounded, precision: 0) # => "389" + # 111.2345.to_s(:rounded, significant: true) # => "111" + # 111.2345.to_s(:rounded, precision: 1, significant: true) # => "100" + # 13.to_s(:rounded, precision: 5, significant: true) # => "13.000" + # 111.234.to_s(:rounded, locale: :fr) # => "111,234" # 13.to_s(:rounded, precision: 5, significant: true, strip_insignificant_zeros: true) - # # => 13 - # 389.32314.to_s(:rounded, precision: 4, significant: true) # => 389.3 + # # => "13" + # 389.32314.to_s(:rounded, precision: 4, significant: true) # => "389.3" # 1111.2345.to_s(:rounded, precision: 2, separator: ',', delimiter: '.') - # # => 1.111,23 + # # => "1.111,23" # # Human-friendly size in Bytes: - # 123.to_s(:human_size) # => 123 Bytes - # 1234.to_s(:human_size) # => 1.21 KB - # 12345.to_s(:human_size) # => 12.1 KB - # 1234567.to_s(:human_size) # => 1.18 MB - # 1234567890.to_s(:human_size) # => 1.15 GB - # 1234567890123.to_s(:human_size) # => 1.12 TB - # 1234567890123456.to_s(:human_size) # => 1.1 PB - # 1234567890123456789.to_s(:human_size) # => 1.07 EB - # 1234567.to_s(:human_size, precision: 2) # => 1.2 MB - # 483989.to_s(:human_size, precision: 2) # => 470 KB - # 1234567.to_s(:human_size, precision: 2, separator: ',') # => 1,2 MB + # 123.to_s(:human_size) # => "123 Bytes" + # 1234.to_s(:human_size) # => "1.21 KB" + # 12345.to_s(:human_size) # => "12.1 KB" + # 1234567.to_s(:human_size) # => "1.18 MB" + # 1234567890.to_s(:human_size) # => "1.15 GB" + # 1234567890123.to_s(:human_size) # => "1.12 TB" + # 1234567890123456.to_s(:human_size) # => "1.1 PB" + # 1234567890123456789.to_s(:human_size) # => "1.07 EB" + # 1234567.to_s(:human_size, precision: 2) # => "1.2 MB" + # 483989.to_s(:human_size, precision: 2) # => "470 KB" + # 1234567.to_s(:human_size, precision: 2, separator: ',') # => "1,2 MB" # 1234567890123.to_s(:human_size, precision: 5) # => "1.1228 TB" # 524288000.to_s(:human_size, precision: 5) # => "500 MB" # @@ -134,6 +133,12 @@ module ActiveSupport::NumericWithFormat deprecate to_formatted_s: :to_s end -[Fixnum, Bignum, Float, BigDecimal].each do |klass| - klass.prepend(ActiveSupport::NumericWithFormat) +# Ruby 2.4+ unifies Fixnum & Bignum into Integer. +if 0.class == Integer + Integer.prepend ActiveSupport::NumericWithFormat +else + Fixnum.prepend ActiveSupport::NumericWithFormat + Bignum.prepend ActiveSupport::NumericWithFormat end +Float.prepend ActiveSupport::NumericWithFormat +BigDecimal.prepend ActiveSupport::NumericWithFormat diff --git a/activesupport/lib/active_support/core_ext/numeric/inquiry.rb b/activesupport/lib/active_support/core_ext/numeric/inquiry.rb index 7e7ac1b0b2..ec79701189 100644 --- a/activesupport/lib/active_support/core_ext/numeric/inquiry.rb +++ b/activesupport/lib/active_support/core_ext/numeric/inquiry.rb @@ -1,26 +1,26 @@ unless 1.respond_to?(:positive?) # TODO: Remove this file when we drop support to ruby < 2.3 -class Numeric - # Returns true if the number is positive. - # - # 1.positive? # => true - # 0.positive? # => false - # -1.positive? # => false - def positive? - self > 0 - end + class Numeric + # Returns true if the number is positive. + # + # 1.positive? # => true + # 0.positive? # => false + # -1.positive? # => false + def positive? + self > 0 + end - # Returns true if the number is negative. - # - # -1.negative? # => true - # 0.negative? # => false - # 1.negative? # => false - def negative? - self < 0 + # Returns true if the number is negative. + # + # -1.negative? # => true + # 0.negative? # => false + # 1.negative? # => false + def negative? + self < 0 + end end -end -class Complex - undef :positive? - undef :negative? -end + class Complex + undef :positive? + undef :negative? + 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 6c4a975495..809dfd4e07 100644 --- a/activesupport/lib/active_support/core_ext/numeric/time.rb +++ b/activesupport/lib/active_support/core_ext/numeric/time.rb @@ -1,8 +1,8 @@ -require 'active_support/duration' -require 'active_support/core_ext/time/calculations' -require 'active_support/core_ext/time/acts_like' -require 'active_support/core_ext/date/calculations' -require 'active_support/core_ext/date/acts_like' +require "active_support/duration" +require "active_support/core_ext/time/calculations" +require "active_support/core_ext/time/acts_like" +require "active_support/core_ext/date/calculations" +require "active_support/core_ext/date/acts_like" class Numeric # Enables the use of time calculations and declarations, like 45.minutes + 2.hours + 4.years. @@ -25,17 +25,17 @@ class Numeric # Returns a Duration instance matching the number of minutes provided. # - # 2.minutes # => 120 seconds + # 2.minutes # => 2 minutes def minutes - ActiveSupport::Duration.new(self * 60, [[:seconds, self * 60]]) + ActiveSupport::Duration.new(self * 60, [[:minutes, self]]) end alias :minute :minutes # Returns a Duration instance matching the number of hours provided. # - # 2.hours # => 7_200 seconds + # 2.hours # => 2 hours def hours - ActiveSupport::Duration.new(self * 3600, [[:seconds, self * 3600]]) + ActiveSupport::Duration.new(self * 3600, [[:hours, self]]) end alias :hour :hours @@ -49,17 +49,17 @@ class Numeric # Returns a Duration instance matching the number of weeks provided. # - # 2.weeks # => 14 days + # 2.weeks # => 2 weeks def weeks - ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]]) + ActiveSupport::Duration.new(self * 7.days, [[:weeks, self]]) end alias :week :weeks # Returns a Duration instance matching the number of fortnights provided. # - # 2.fortnights # => 28 days + # 2.fortnights # => 4 weeks def fortnights - ActiveSupport::Duration.new(self * 2.weeks, [[:days, self * 14]]) + ActiveSupport::Duration.new(self * 2.weeks, [[:weeks, self * 2]]) end alias :fortnight :fortnights diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb index f4f9152d6a..58bbf78601 100644 --- a/activesupport/lib/active_support/core_ext/object.rb +++ b/activesupport/lib/active_support/core_ext/object.rb @@ -1,14 +1,14 @@ -require 'active_support/core_ext/object/acts_like' -require 'active_support/core_ext/object/blank' -require 'active_support/core_ext/object/duplicable' -require 'active_support/core_ext/object/deep_dup' -require 'active_support/core_ext/object/try' -require 'active_support/core_ext/object/inclusion' +require "active_support/core_ext/object/acts_like" +require "active_support/core_ext/object/blank" +require "active_support/core_ext/object/duplicable" +require "active_support/core_ext/object/deep_dup" +require "active_support/core_ext/object/try" +require "active_support/core_ext/object/inclusion" -require 'active_support/core_ext/object/conversions' -require 'active_support/core_ext/object/instance_variables' +require "active_support/core_ext/object/conversions" +require "active_support/core_ext/object/instance_variables" -require 'active_support/core_ext/object/json' -require 'active_support/core_ext/object/to_param' -require 'active_support/core_ext/object/to_query' -require 'active_support/core_ext/object/with_options' +require "active_support/core_ext/object/json" +require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/to_query" +require "active_support/core_ext/object/with_options" diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index 039c50a4a2..bdb50ee291 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -1,3 +1,5 @@ +require "active_support/core_ext/regexp" + class Object # An object is blank if it's false, empty, or a whitespace string. # For example, +false+, '', ' ', +nil+, [], and {} are all blank. @@ -112,7 +114,10 @@ class String # # @return [true, false] def blank? - BLANK_RE === self + # The regexp that matches blank strings is expensive. For the case of empty + # strings we can speed up this method (~3.5x) with an empty? call. The + # penalty for the rest of strings is marginal. + empty? || BLANK_RE.match?(self) end end diff --git a/activesupport/lib/active_support/core_ext/object/conversions.rb b/activesupport/lib/active_support/core_ext/object/conversions.rb index 540f7aadb0..918ebcdc9f 100644 --- a/activesupport/lib/active_support/core_ext/object/conversions.rb +++ b/activesupport/lib/active_support/core_ext/object/conversions.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/object/to_param' -require 'active_support/core_ext/object/to_query' -require 'active_support/core_ext/array/conversions' -require 'active_support/core_ext/hash/conversions' +require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/to_query" +require "active_support/core_ext/array/conversions" +require "active_support/core_ext/hash/conversions" diff --git a/activesupport/lib/active_support/core_ext/object/deep_dup.rb b/activesupport/lib/active_support/core_ext/object/deep_dup.rb index 8dfeed0066..5ac649e552 100644 --- a/activesupport/lib/active_support/core_ext/object/deep_dup.rb +++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/object/duplicable' +require "active_support/core_ext/object/duplicable" class Object # Returns a deep copy of object if it's duplicable. If it's diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index befa5aee21..aa2282cb7e 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -70,13 +70,13 @@ class Numeric # Numbers are not duplicable: # # 3.duplicable? # => false - # 3.dup # => TypeError: can't dup Fixnum + # 3.dup # => TypeError: can't dup Integer def duplicable? false end end -require 'bigdecimal' +require "bigdecimal" class BigDecimal # BigDecimals are duplicable: # diff --git a/activesupport/lib/active_support/core_ext/object/inclusion.rb b/activesupport/lib/active_support/core_ext/object/inclusion.rb index d4c17dfb07..98bf820d36 100644 --- a/activesupport/lib/active_support/core_ext/object/inclusion.rb +++ b/activesupport/lib/active_support/core_ext/object/inclusion.rb @@ -22,6 +22,6 @@ class Object # # @return [Object] def presence_in(another_object) - self.in?(another_object) ? self : nil + in?(another_object) ? self : nil end end diff --git a/activesupport/lib/active_support/core_ext/object/json.rb b/activesupport/lib/active_support/core_ext/object/json.rb index 0db787010c..1c4d181443 100644 --- a/activesupport/lib/active_support/core_ext/object/json.rb +++ b/activesupport/lib/active_support/core_ext/object/json.rb @@ -1,14 +1,16 @@ # Hack to load json gem first so we can overwrite its to_json. -require 'json' -require 'bigdecimal' -require 'active_support/core_ext/big_decimal/conversions' # for #to_s -require 'active_support/core_ext/hash/except' -require 'active_support/core_ext/hash/slice' -require 'active_support/core_ext/object/instance_variables' -require 'time' -require 'active_support/core_ext/time/conversions' -require 'active_support/core_ext/date_time/conversions' -require 'active_support/core_ext/date/conversions' +require "json" +require "bigdecimal" +require "uri/generic" +require "pathname" +require "active_support/core_ext/big_decimal/conversions" # for #to_s +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/slice" +require "active_support/core_ext/object/instance_variables" +require "time" +require "active_support/core_ext/time/conversions" +require "active_support/core_ext/date_time/conversions" +require "active_support/core_ext/date/conversions" # The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting # their default behavior. That said, we need to define the basic to_json method in all of them, @@ -187,13 +189,31 @@ class DateTime if ActiveSupport::JSON::Encoding.use_standard_json_time_format xmlschema(ActiveSupport::JSON::Encoding.time_precision) else - strftime('%Y/%m/%d %H:%M:%S %z') + strftime("%Y/%m/%d %H:%M:%S %z") end end end +class URI::Generic #:nodoc: + def as_json(options = nil) + to_s + end +end + +class Pathname #:nodoc: + def as_json(options = nil) + to_s + end +end + class Process::Status #:nodoc: def as_json(options = nil) - { :exitstatus => exitstatus, :pid => pid } + { exitstatus: exitstatus, pid: pid } + end +end + +class Exception + def as_json(options = nil) + to_s end end diff --git a/activesupport/lib/active_support/core_ext/object/to_param.rb b/activesupport/lib/active_support/core_ext/object/to_param.rb index 684d4ef57e..5eeaf03163 100644 --- a/activesupport/lib/active_support/core_ext/object/to_param.rb +++ b/activesupport/lib/active_support/core_ext/object/to_param.rb @@ -1 +1 @@ -require 'active_support/core_ext/object/to_query' +require "active_support/core_ext/object/to_query" diff --git a/activesupport/lib/active_support/core_ext/object/to_query.rb b/activesupport/lib/active_support/core_ext/object/to_query.rb index ec5ace4e16..a3a3abacbb 100644 --- a/activesupport/lib/active_support/core_ext/object/to_query.rb +++ b/activesupport/lib/active_support/core_ext/object/to_query.rb @@ -1,4 +1,4 @@ -require 'cgi' +require "cgi" class Object # Alias of <tt>to_s</tt>. @@ -38,7 +38,7 @@ class Array # Calls <tt>to_param</tt> on all its elements and joins the result with # slashes. This is used by <tt>url_for</tt> in Action Pack. def to_param - collect(&:to_param).join '/' + collect(&:to_param).join "/" end # Converts an array into a string suitable for use as a URL query string, @@ -51,7 +51,7 @@ class Array if empty? nil.to_query(prefix) else - collect { |value| value.to_query(prefix) }.join '&' + collect { |value| value.to_query(prefix) }.join "&" end end end @@ -77,7 +77,7 @@ class Hash unless (value.is_a?(Hash) || value.is_a?(Array)) && value.empty? value.to_query(namespace ? "#{namespace}[#{key}]" : key) end - end.compact.sort! * '&' + end.compact.sort! * "&" end alias_method :to_param, :to_query diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index 8c16d95b62..b2be619b2d 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -1,4 +1,4 @@ -require 'delegate' +require "delegate" module ActiveSupport module Tryable #:nodoc: @@ -99,7 +99,7 @@ class Object # # "a".try!(:upcase) # => "A" # nil.try!(:upcase) # => nil - # 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Fixnum + # 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Integer end class Delegator diff --git a/activesupport/lib/active_support/core_ext/object/with_options.rb b/activesupport/lib/active_support/core_ext/object/with_options.rb index 513c8b1d55..cf39b1d312 100644 --- a/activesupport/lib/active_support/core_ext/object/with_options.rb +++ b/activesupport/lib/active_support/core_ext/object/with_options.rb @@ -1,4 +1,4 @@ -require 'active_support/option_merger' +require "active_support/option_merger" class Object # An elegant way to factor duplication out of options passed to a series of diff --git a/activesupport/lib/active_support/core_ext/range.rb b/activesupport/lib/active_support/core_ext/range.rb index 9368e81235..3190e3ff76 100644 --- a/activesupport/lib/active_support/core_ext/range.rb +++ b/activesupport/lib/active_support/core_ext/range.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/range/conversions' -require 'active_support/core_ext/range/include_range' -require 'active_support/core_ext/range/overlaps' -require 'active_support/core_ext/range/each' +require "active_support/core_ext/range/conversions" +require "active_support/core_ext/range/include_range" +require "active_support/core_ext/range/overlaps" +require "active_support/core_ext/range/each" diff --git a/activesupport/lib/active_support/core_ext/range/conversions.rb b/activesupport/lib/active_support/core_ext/range/conversions.rb index 965436c23a..69ea046cb6 100644 --- a/activesupport/lib/active_support/core_ext/range/conversions.rb +++ b/activesupport/lib/active_support/core_ext/range/conversions.rb @@ -1,6 +1,6 @@ module ActiveSupport::RangeWithFormat RANGE_FORMATS = { - :db => Proc.new { |start, stop| "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'" } + db: Proc.new { |start, stop| "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'" } } # Convert range to a formatted string. See RANGE_FORMATS for predefined formats. diff --git a/activesupport/lib/active_support/core_ext/regexp.rb b/activesupport/lib/active_support/core_ext/regexp.rb index 784145f5fb..062d568228 100644 --- a/activesupport/lib/active_support/core_ext/regexp.rb +++ b/activesupport/lib/active_support/core_ext/regexp.rb @@ -2,4 +2,8 @@ class Regexp #:nodoc: def multiline? options & MULTILINE == MULTILINE end + + def match?(string, pos=0) + !!match(string, pos) + end unless //.respond_to?(:match?) end diff --git a/activesupport/lib/active_support/core_ext/securerandom.rb b/activesupport/lib/active_support/core_ext/securerandom.rb index 98cf7430f7..92392d1a92 100644 --- a/activesupport/lib/active_support/core_ext/securerandom.rb +++ b/activesupport/lib/active_support/core_ext/securerandom.rb @@ -1,7 +1,7 @@ -require 'securerandom' +require "securerandom" module SecureRandom - BASE58_ALPHABET = ('0'..'9').to_a + ('A'..'Z').to_a + ('a'..'z').to_a - ['0', 'O', 'I', 'l'] + BASE58_ALPHABET = ("0".."9").to_a + ("A".."Z").to_a + ("a".."z").to_a - ["0", "O", "I", "l"] # SecureRandom.base58 generates a random base58 string. # # The argument _n_ specifies the length, of the random string to be generated. diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb index c656db2c6c..4cb3200875 100644 --- a/activesupport/lib/active_support/core_ext/string.rb +++ b/activesupport/lib/active_support/core_ext/string.rb @@ -1,13 +1,13 @@ -require 'active_support/core_ext/string/conversions' -require 'active_support/core_ext/string/filters' -require 'active_support/core_ext/string/multibyte' -require 'active_support/core_ext/string/starts_ends_with' -require 'active_support/core_ext/string/inflections' -require 'active_support/core_ext/string/access' -require 'active_support/core_ext/string/behavior' -require 'active_support/core_ext/string/output_safety' -require 'active_support/core_ext/string/exclude' -require 'active_support/core_ext/string/strip' -require 'active_support/core_ext/string/inquiry' -require 'active_support/core_ext/string/indent' -require 'active_support/core_ext/string/zones' +require "active_support/core_ext/string/conversions" +require "active_support/core_ext/string/filters" +require "active_support/core_ext/string/multibyte" +require "active_support/core_ext/string/starts_ends_with" +require "active_support/core_ext/string/inflections" +require "active_support/core_ext/string/access" +require "active_support/core_ext/string/behavior" +require "active_support/core_ext/string/output_safety" +require "active_support/core_ext/string/exclude" +require "active_support/core_ext/string/strip" +require "active_support/core_ext/string/inquiry" +require "active_support/core_ext/string/indent" +require "active_support/core_ext/string/zones" diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb index ebd0dd3fc7..caa48e34c5 100644 --- a/activesupport/lib/active_support/core_ext/string/access.rb +++ b/activesupport/lib/active_support/core_ext/string/access.rb @@ -1,5 +1,5 @@ class String - # If you pass a single Fixnum, returns a substring of one character at that + # If you pass a single integer, returns a substring of one character at that # position. The first character of the string is at position 0, the next at # position 1, and so on. If a range is supplied, a substring containing # characters at offsets given by the range is returned. In both cases, if an @@ -74,9 +74,9 @@ class String # str.first(6) # => "hello" def first(limit = 1) if limit == 0 - '' + "" elsif limit >= size - self.dup + dup else to(limit - 1) end @@ -94,9 +94,9 @@ class String # str.last(6) # => "hello" def last(limit = 1) if limit == 0 - '' + "" elsif limit >= size - self.dup + dup else from(-limit) end diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb index fd79a40e31..221b4969cc 100644 --- a/activesupport/lib/active_support/core_ext/string/conversions.rb +++ b/activesupport/lib/active_support/core_ext/string/conversions.rb @@ -1,5 +1,5 @@ -require 'date' -require 'active_support/core_ext/time/calculations' +require "date" +require "active_support/core_ext/time/calculations" class String # Converts a string to a Time value. @@ -18,7 +18,8 @@ class String # "12/13/2012".to_time # => ArgumentError: argument out of range def to_time(form = :local) parts = Date._parse(self, false) - return if parts.empty? + used_keys = %i(year mon mday hour min sec sec_fraction offset) + return if (parts.keys & used_keys).empty? now = Time.now time = Time.new( @@ -31,7 +32,7 @@ class String parts.fetch(:offset, form == :utc ? 0 : nil) ) - form == :utc ? time.utc : time.getlocal + form == :utc ? time.utc : time.to_time end # Converts a string to a Date value. diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb index 375ec1aef8..a9ec2eb842 100644 --- a/activesupport/lib/active_support/core_ext/string/filters.rb +++ b/activesupport/lib/active_support/core_ext/string/filters.rb @@ -17,7 +17,7 @@ class String # str.squish! # => "foo bar boo" # str # => "foo bar boo" def squish! - gsub!(/[[:space:]]+/, ' ') + gsub!(/[[:space:]]+/, " ") strip! self end @@ -64,7 +64,7 @@ class String def truncate(truncate_at, options = {}) return dup unless length > truncate_at - omission = options[:omission] || '...' + omission = options[:omission] || "..." length_with_room_for_omission = truncate_at - omission.length stop = \ if options[:separator] @@ -94,7 +94,7 @@ class String sep = options[:separator] || /\s+/ sep = Regexp.escape(sep.to_s) unless Regexp === sep if self =~ /\A((?>.+?#{sep}){#{words_count - 1}}.+?)#{sep}.*/m - $1 + (options[:omission] || '...') + $1 + (options[:omission] || "...") else dup end diff --git a/activesupport/lib/active_support/core_ext/string/indent.rb b/activesupport/lib/active_support/core_ext/string/indent.rb index ce3a69cf5f..e87f72fdbd 100644 --- a/activesupport/lib/active_support/core_ext/string/indent.rb +++ b/activesupport/lib/active_support/core_ext/string/indent.rb @@ -3,7 +3,7 @@ class String # # Returns the indented string, or +nil+ if there was nothing to indent. def indent!(amount, indent_string=nil, indent_empty_lines=false) - indent_string = indent_string || self[/^[ \t]/] || ' ' + indent_string = indent_string || self[/^[ \t]/] || " " re = indent_empty_lines ? /^/ : /^(?!$)/ gsub!(re, indent_string * amount) end @@ -38,6 +38,6 @@ class String # "foo\n\nbar".indent(2, nil, true) # => " foo\n \n bar" # def indent(amount, indent_string=nil, indent_empty_lines=false) - dup.tap {|_| _.indent!(amount, indent_string, indent_empty_lines)} + dup.tap { |_| _.indent!(amount, indent_string, indent_empty_lines) } 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 cc71b8155d..7e12700c8c 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -1,5 +1,5 @@ -require 'active_support/inflector/methods' -require 'active_support/inflector/transliterate' +require "active_support/inflector/methods" +require "active_support/inflector/transliterate" # String inflections define new methods on the String class to transform names for different purposes. # For instance, you can figure out the name of a table from the name of a class. @@ -31,7 +31,7 @@ class String def pluralize(count = nil, locale = :en) locale = count if count.is_a?(Symbol) if count == 1 - self.dup + dup else ActiveSupport::Inflector.pluralize(self, locale) end @@ -164,7 +164,7 @@ class String # # <%= link_to(@person.name, person_path) %> # # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a> - # + # # To preserve the case of the characters in a string, use the `preserve_case` argument. # # class Person @@ -178,7 +178,7 @@ class String # # <%= link_to(@person.name, person_path) %> # # => <a href="/person/1-Donald-E-Knuth">Donald E. Knuth</a> - def parameterize(sep = :unused, separator: '-', preserve_case: false) + def parameterize(sep = :unused, separator: "-", preserve_case: false) unless sep == :unused ActiveSupport::Deprecation.warn("Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '#{sep}'` instead.") separator = sep @@ -222,6 +222,15 @@ class String ActiveSupport::Inflector.humanize(self, options) end + # Converts just the first character to uppercase. + # + # 'what a Lovely Day'.upcase_first # => "What a Lovely Day" + # 'w'.upcase_first # => "W" + # ''.upcase_first # => "" + def upcase_first + ActiveSupport::Inflector.upcase_first(self) + end + # Creates a foreign key name from a class name. # +separate_class_name_and_id_with_underscore+ sets whether # the method should put '_' between the name and 'id'. diff --git a/activesupport/lib/active_support/core_ext/string/inquiry.rb b/activesupport/lib/active_support/core_ext/string/inquiry.rb index 1dcd949536..c95d83beae 100644 --- a/activesupport/lib/active_support/core_ext/string/inquiry.rb +++ b/activesupport/lib/active_support/core_ext/string/inquiry.rb @@ -1,4 +1,4 @@ -require 'active_support/string_inquirer' +require "active_support/string_inquirer" class String # Wraps the current string in the <tt>ActiveSupport::StringInquirer</tt> class, diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb index cc6f2158e7..1c73182259 100644 --- a/activesupport/lib/active_support/core_ext/string/multibyte.rb +++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb @@ -1,4 +1,4 @@ -require 'active_support/multibyte' +require "active_support/multibyte" class String # == Multibyte proxy 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 43b9fd4bf7..2b7e556ced 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -1,10 +1,11 @@ -require 'erb' -require 'active_support/core_ext/kernel/singleton_class' +require "erb" +require "active_support/core_ext/kernel/singleton_class" +require "active_support/multibyte/unicode" class ERB module Util - HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' } - JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003e', '<' => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' } + HTML_ESCAPE = { "&" => "&", ">" => ">", "<" => "<", '"' => """, "'" => "'" } + JSON_ESCAPE = { "&" => '\u0026', ">" => '\u003e', "<" => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' } HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+)|(#[xX][\dA-Fa-f]+));)/ JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u @@ -144,7 +145,7 @@ module ActiveSupport #:nodoc: # Raised when <tt>ActiveSupport::SafeBuffer#safe_concat</tt> is called on unsafe buffers. class SafeConcatError < StandardError def initialize - super 'Could not concatenate to the buffer because it is not html safe.' + super "Could not concatenate to the buffer because it is not html safe." end end @@ -171,7 +172,7 @@ module ActiveSupport #:nodoc: original_concat(value) end - def initialize(str = '') + def initialize(str = "") @html_safe = true super end @@ -242,15 +243,15 @@ module ActiveSupport #:nodoc: private - def html_escape_interpolated_argument(arg) - (!html_safe? || arg.html_safe?) ? arg : CGI.escapeHTML(arg.to_s) - end + def html_escape_interpolated_argument(arg) + (!html_safe? || arg.html_safe?) ? arg : CGI.escapeHTML(arg.to_s) + end end end class String # Marks a string as trusted safe. It will be inserted into HTML with no - # additional escaping performed. It is your responsibilty to ensure that the + # additional escaping performed. It is your responsibility to ensure that the # string contains no malicious content. This method is equivalent to the # `raw` helper in views. It is recommended that you use `sanitize` instead of # this method. It should never be called on user input. diff --git a/activesupport/lib/active_support/core_ext/string/strip.rb b/activesupport/lib/active_support/core_ext/string/strip.rb index 55b9b87352..bb62e6c0ba 100644 --- a/activesupport/lib/active_support/core_ext/string/strip.rb +++ b/activesupport/lib/active_support/core_ext/string/strip.rb @@ -18,6 +18,6 @@ class String # Technically, it looks for the least indented non-empty line # in the whole string, and removes that amount of leading whitespace. def strip_heredoc - gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, ''.freeze) + gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, "".freeze) end end diff --git a/activesupport/lib/active_support/core_ext/string/zones.rb b/activesupport/lib/active_support/core_ext/string/zones.rb index 510c884c18..de5a28e4f7 100644 --- a/activesupport/lib/active_support/core_ext/string/zones.rb +++ b/activesupport/lib/active_support/core_ext/string/zones.rb @@ -1,5 +1,5 @@ -require 'active_support/core_ext/string/conversions' -require 'active_support/core_ext/time/zones' +require "active_support/core_ext/string/conversions" +require "active_support/core_ext/time/zones" class String # Converts String to a TimeWithZone in the current zone if Time.zone or Time.zone_default diff --git a/activesupport/lib/active_support/core_ext/struct.rb b/activesupport/lib/active_support/core_ext/struct.rb index 1fde3db070..dccfa3e9bb 100644 --- a/activesupport/lib/active_support/core_ext/struct.rb +++ b/activesupport/lib/active_support/core_ext/struct.rb @@ -1,3 +1,3 @@ -require 'active_support/deprecation' +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/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb index 72c3234630..b1ae4a45d9 100644 --- a/activesupport/lib/active_support/core_ext/time.rb +++ b/activesupport/lib/active_support/core_ext/time.rb @@ -1,4 +1,5 @@ -require 'active_support/core_ext/time/acts_like' -require 'active_support/core_ext/time/calculations' -require 'active_support/core_ext/time/conversions' -require 'active_support/core_ext/time/zones' +require "active_support/core_ext/time/acts_like" +require "active_support/core_ext/time/calculations" +require "active_support/core_ext/time/compatibility" +require "active_support/core_ext/time/conversions" +require "active_support/core_ext/time/zones" diff --git a/activesupport/lib/active_support/core_ext/time/acts_like.rb b/activesupport/lib/active_support/core_ext/time/acts_like.rb index 3f853b7893..cf4b2539c5 100644 --- a/activesupport/lib/active_support/core_ext/time/acts_like.rb +++ b/activesupport/lib/active_support/core_ext/time/acts_like.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/object/acts_like' +require "active_support/core_ext/object/acts_like" class Time # Duck-types as a Time-like class. See Object#acts_like?. diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 768c9a1b2c..2cbfa14772 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -1,9 +1,9 @@ -require 'active_support/duration' -require 'active_support/core_ext/time/conversions' -require 'active_support/time_with_zone' -require 'active_support/core_ext/time/zones' -require 'active_support/core_ext/date_and_time/calculations' -require 'active_support/core_ext/date/calculations' +require "active_support/duration" +require "active_support/core_ext/time/conversions" +require "active_support/time_with_zone" +require "active_support/core_ext/time/zones" +require "active_support/core_ext/date_and_time/calculations" +require "active_support/core_ext/date/calculations" class Time include DateAndTime::Calculations @@ -61,7 +61,7 @@ class Time # Time.new(2012, 8, 29, 12, 34, 56).seconds_since_midnight # => 45296.0 # Time.new(2012, 8, 29, 23, 59, 59).seconds_since_midnight # => 86399.0 def seconds_since_midnight - to_i - change(:hour => 0).to_i + (usec / 1.0e+6) + to_i - change(hour: 0).to_i + (usec / 1.0e+6) end # Returns the number of seconds until 23:59:59. @@ -73,6 +73,13 @@ class Time end_of_day.to_i - to_i end + # Returns the fraction of a second as a +Rational+ + # + # Time.new(2012, 8, 29, 0, 0, 0.5).sec_fraction # => (1/2) + def sec_fraction + subsec + end + # Returns a new Time where one or more of the elements have been changed according # to the +options+ parameter. The time options (<tt>:hour</tt>, <tt>:min</tt>, # <tt>:sec</tt>, <tt>:usec</tt>, <tt>:nsec</tt>) reset cascadingly, so if only @@ -105,7 +112,7 @@ class Time elsif zone ::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec) else - raise ArgumentError, 'argument out of range' if new_usec >= 1000000 + raise ArgumentError, "argument out of range" if new_usec >= 1000000 ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec + (new_usec.to_r / 1000000), utc_offset) end end @@ -134,7 +141,7 @@ class Time d = to_date.advance(options) d = d.gregorian if d.julian? - time_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day) + time_advanced_by_date = change(year: d.year, month: d.month, day: d.day) seconds_to_advance = \ options.fetch(:seconds, 0) + options.fetch(:minutes, 0) * 60 + @@ -162,7 +169,7 @@ class Time # Returns a new Time representing the start of the day (0:00) def beginning_of_day - change(:hour => 0) + change(hour: 0) end alias :midnight :beginning_of_day alias :at_midnight :beginning_of_day @@ -170,7 +177,7 @@ class Time # Returns a new Time representing the middle of the day (12:00) def middle_of_day - change(:hour => 12) + change(hour: 12) end alias :midday :middle_of_day alias :noon :middle_of_day @@ -181,50 +188,45 @@ class Time # Returns a new Time representing the end of the day, 23:59:59.999999 def end_of_day change( - :hour => 23, - :min => 59, - :sec => 59, - :usec => Rational(999999999, 1000) + hour: 23, + min: 59, + sec: 59, + usec: Rational(999999999, 1000) ) end alias :at_end_of_day :end_of_day # Returns a new Time representing the start of the hour (x:00) def beginning_of_hour - change(:min => 0) + change(min: 0) end alias :at_beginning_of_hour :beginning_of_hour # Returns a new Time representing the end of the hour, x:59:59.999999 def end_of_hour change( - :min => 59, - :sec => 59, - :usec => Rational(999999999, 1000) + min: 59, + sec: 59, + usec: Rational(999999999, 1000) ) end alias :at_end_of_hour :end_of_hour # Returns a new Time representing the start of the minute (x:xx:00) def beginning_of_minute - change(:sec => 0) + change(sec: 0) end alias :at_beginning_of_minute :beginning_of_minute # Returns a new Time representing the end of the minute, x:xx:59.999999 def end_of_minute change( - :sec => 59, - :usec => Rational(999999999, 1000) + sec: 59, + usec: Rational(999999999, 1000) ) end alias :at_end_of_minute :end_of_minute - # Returns a Range representing the whole day of the current time. - def all_day - beginning_of_day..end_of_day - end - def plus_with_duration(other) #:nodoc: if ActiveSupport::Duration === other other.since(self) @@ -279,5 +281,4 @@ class Time end alias_method :eql_without_coercion, :eql? alias_method :eql?, :eql_with_coercion - end diff --git a/activesupport/lib/active_support/core_ext/time/compatibility.rb b/activesupport/lib/active_support/core_ext/time/compatibility.rb new file mode 100644 index 0000000000..ca4b9574d5 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/time/compatibility.rb @@ -0,0 +1,5 @@ +require "active_support/core_ext/date_and_time/compatibility" + +class Time + prepend DateAndTime::Compatibility +end diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb index 536c4bf525..f2bbe55aa6 100644 --- a/activesupport/lib/active_support/core_ext/time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/time/conversions.rb @@ -1,24 +1,24 @@ -require 'active_support/inflector/methods' -require 'active_support/values/time_zone' +require "active_support/inflector/methods" +require "active_support/values/time_zone" class Time DATE_FORMATS = { - :db => '%Y-%m-%d %H:%M:%S', - :number => '%Y%m%d%H%M%S', - :nsec => '%Y%m%d%H%M%S%9N', - :usec => '%Y%m%d%H%M%S%6N', - :time => '%H:%M', - :short => '%d %b %H:%M', - :long => '%B %d, %Y %H:%M', - :long_ordinal => lambda { |time| + db: "%Y-%m-%d %H:%M:%S", + number: "%Y%m%d%H%M%S", + nsec: "%Y%m%d%H%M%S%9N", + usec: "%Y%m%d%H%M%S%6N", + time: "%H:%M", + short: "%d %b %H:%M", + long: "%B %d, %Y %H:%M", + long_ordinal: lambda { |time| day_format = ActiveSupport::Inflector.ordinalize(time.day) time.strftime("%B #{day_format}, %Y %H:%M") }, - :rfc822 => lambda { |time| + rfc822: lambda { |time| offset_format = time.formatted_offset(false) time.strftime("%a, %d %b %Y %H:%M:%S #{offset_format}") }, - :iso8601 => lambda { |time| time.iso8601 } + iso8601: lambda { |time| time.iso8601 } } # Converts to a formatted string. See DATE_FORMATS for built-in formats. diff --git a/activesupport/lib/active_support/core_ext/time/marshal.rb b/activesupport/lib/active_support/core_ext/time/marshal.rb index 467bad1726..d095d76ebb 100644 --- a/activesupport/lib/active_support/core_ext/time/marshal.rb +++ b/activesupport/lib/active_support/core_ext/time/marshal.rb @@ -1,3 +1,3 @@ -require 'active_support/deprecation' +require "active_support/deprecation" ActiveSupport::Deprecation.warn("This is deprecated and will be removed in Rails 5.1 with no replacement.") diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb index 7a60f94996..87b5ad903a 100644 --- a/activesupport/lib/active_support/core_ext/time/zones.rb +++ b/activesupport/lib/active_support/core_ext/time/zones.rb @@ -1,6 +1,6 @@ -require 'active_support/time_with_zone' -require 'active_support/core_ext/time/acts_like' -require 'active_support/core_ext/date_and_time/zones' +require "active_support/time_with_zone" +require "active_support/core_ext/time/acts_like" +require "active_support/core_ext/date_and_time/zones" class Time include DateAndTime::Zones diff --git a/activesupport/lib/active_support/core_ext/uri.rb b/activesupport/lib/active_support/core_ext/uri.rb index c6c183edd9..342a5fcd52 100644 --- a/activesupport/lib/active_support/core_ext/uri.rb +++ b/activesupport/lib/active_support/core_ext/uri.rb @@ -1,4 +1,4 @@ -require 'uri' +require "uri" str = "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" # Ni-ho-nn-go in UTF-8, means Japanese. parser = URI::Parser.new @@ -10,7 +10,7 @@ unless str == parser.unescape(parser.escape(str)) # YK: My initial experiments say yes, but let's be sure please enc = str.encoding enc = Encoding::UTF_8 if enc == Encoding::US_ASCII - str.gsub(escaped) { |match| [match[1, 2].hex].pack('C') }.force_encoding(enc) + str.gsub(escaped) { |match| [match[1, 2].hex].pack("C") }.force_encoding(enc) end end end diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index af18ff746f..216d52164e 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -1,19 +1,19 @@ -require 'set' -require 'thread' -require 'concurrent/map' -require 'pathname' -require 'active_support/core_ext/module/aliasing' -require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/core_ext/module/introspection' -require 'active_support/core_ext/module/anonymous' -require 'active_support/core_ext/module/qualified_const' -require 'active_support/core_ext/object/blank' -require 'active_support/core_ext/kernel/reporting' -require 'active_support/core_ext/load_error' -require 'active_support/core_ext/name_error' -require 'active_support/core_ext/string/starts_ends_with' +require "set" +require "thread" +require "concurrent/map" +require "pathname" +require "active_support/core_ext/module/aliasing" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/module/introspection" +require "active_support/core_ext/module/anonymous" +require "active_support/core_ext/module/qualified_const" +require "active_support/core_ext/object/blank" +require "active_support/core_ext/kernel/reporting" +require "active_support/core_ext/load_error" +require "active_support/core_ext/name_error" +require "active_support/core_ext/string/starts_ends_with" require "active_support/dependencies/interlock" -require 'active_support/inflector' +require "active_support/inflector" module ActiveSupport #:nodoc: module Dependencies #:nodoc: @@ -64,7 +64,7 @@ module ActiveSupport #:nodoc: # Should we load files or require them? mattr_accessor :mechanism - self.mechanism = ENV['NO_RELOAD'] ? :require : :load + self.mechanism = ENV["NO_RELOAD"] ? :require : :load # The set of directories from which we may automatically load files. Files # under these directories will be reloaded on each request in development mode, @@ -88,15 +88,6 @@ module ActiveSupport #:nodoc: mattr_accessor :explicitly_unloadable_constants self.explicitly_unloadable_constants = [] - # The logger is used for generating information on the action run-time - # (including benchmarking) if available. Can be set to nil for no logging. - # Compatible with both Ruby's own Logger and Log4r loggers. - mattr_accessor :logger - - # Set to +true+ to enable logging of const_missing and file loads. - mattr_accessor :log_activity - self.log_activity = false - # The WatchStack keeps a stack of the modules being watched as files are # loaded. If a file in the process of being loaded (parent.rb) triggers the # load of another file (child.rb) the stack will ensure that child.rb @@ -143,11 +134,11 @@ module ActiveSupport #:nodoc: next unless mod.is_a?(Module) # Get a list of the constants that were added - new_constants = mod.local_constants - original_constants + new_constants = mod.constants(false) - original_constants - # self[namespace] returns an Array of the constants that are being evaluated + # @stack[namespace] returns an Array of the constants that are being evaluated # for that namespace. For instance, if parent.rb requires child.rb, the first - # element of self[Object] will be an Array of the constants that were present + # element of @stack[Object] will be an Array of the constants that were present # before parent.rb was required. The second element will be an Array of the # constants that were present before child.rb was required. @stack[namespace].each do |namespace_constants| @@ -171,7 +162,7 @@ module ActiveSupport #:nodoc: @watching << namespaces.map do |namespace| module_name = Dependencies.to_constant_name(namespace) original_constants = Dependencies.qualified_const_defined?(module_name) ? - Inflector.constantize(module_name).local_constants : [] + Inflector.constantize(module_name).constants(false) : [] @stack[module_name] << original_constants module_name @@ -179,9 +170,9 @@ module ActiveSupport #:nodoc: end private - def pop_modules(modules) - modules.each { |mod| @stack[mod].pop } - end + def pop_modules(modules) + modules.each { |mod| @stack[mod].pop } + end end # An internal stack used to record which constants are loaded by any block. @@ -262,7 +253,7 @@ module ActiveSupport #:nodoc: end def load_dependency(file) - if Dependencies.load? && ActiveSupport::Dependencies.constant_watch_stack.watching? + if Dependencies.load? && Dependencies.constant_watch_stack.watching? Dependencies.new_constants_in(Object) { yield } else yield @@ -291,17 +282,17 @@ module ActiveSupport #:nodoc: private - def load(file, wrap = false) - result = false - load_dependency(file) { result = super } - result - end + def load(file, wrap = false) + result = false + load_dependency(file) { result = super } + result + end - def require(file) - result = false - load_dependency(file) { result = super } - result - end + def require(file) + result = false + load_dependency(file) { result = super } + result + end end # Exception file-blaming. @@ -352,7 +343,6 @@ module ActiveSupport #:nodoc: end def clear - log_call Dependencies.unload_interlock do loaded.clear loading.clear @@ -361,7 +351,6 @@ module ActiveSupport #:nodoc: end def require_or_load(file_name, const_path = nil) - log_call file_name, const_path file_name = $` if file_name =~ /\.rb\z/ expanded = File.expand_path(file_name) return if loaded.include?(expanded) @@ -377,20 +366,17 @@ module ActiveSupport #:nodoc: begin if load? - log "loading #{file_name}" - # Enable warnings if this file has not been loaded before and # warnings_on_first_load is set. load_args = ["#{file_name}.rb"] load_args << const_path unless const_path.nil? - if !warnings_on_first_load or history.include?(expanded) + if !warnings_on_first_load || history.include?(expanded) result = load_file(*load_args) else enable_warnings { result = load_file(*load_args) } end else - log "requiring #{file_name}" result = require file_name end rescue Exception @@ -483,7 +469,6 @@ module ActiveSupport #:nodoc: # set of names that the file at +path+ may define. See # +loadable_constants_for_path+ for more details. def load_file(path, const_paths = loadable_constants_for_path(path)) - log_call path, const_paths const_paths = [const_paths].compact unless const_paths.is_a? Array parent_paths = const_paths.collect { |const_path| const_path[/.*(?=::)/] || ::Object } @@ -494,7 +479,6 @@ module ActiveSupport #:nodoc: autoloaded_constants.concat newly_defined_paths unless load_once_path?(path) autoloaded_constants.uniq! - log "loading #{path} defined #{newly_defined_paths * ', '}" unless newly_defined_paths.empty? result end @@ -508,8 +492,6 @@ module ActiveSupport #:nodoc: # it is not possible to load the constant into from_mod, try its parent # module using +const_missing+. def load_missing_constant(from_mod, const_name) - log_call from_mod, const_name - unless qualified_const_defined?(from_mod.name) && Inflector.constantize(from_mod.name).equal?(from_mod) raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!" end @@ -521,7 +503,7 @@ module ActiveSupport #:nodoc: if file_path expanded = File.expand_path(file_path) - expanded.sub!(/\.rb\z/, ''.freeze) + expanded.sub!(/\.rb\z/, "".freeze) if loading.include?(expanded) raise "Circular dependency detected while autoloading constant #{qualified_name}" @@ -565,7 +547,7 @@ module ActiveSupport #:nodoc: end name_error = NameError.new("uninitialized constant #{qualified_name}", const_name) - name_error.set_backtrace(caller.reject {|l| l.starts_with? __FILE__ }) + name_error.set_backtrace(caller.reject { |l| l.starts_with? __FILE__ }) raise name_error end @@ -609,7 +591,7 @@ module ActiveSupport #:nodoc: def store(klass) return self unless klass.respond_to?(:name) - raise(ArgumentError, 'anonymous classes cannot be cached') if klass.name.empty? + raise(ArgumentError, "anonymous classes cannot be cached") if klass.name.empty? @store[klass.name] = klass self end @@ -673,54 +655,49 @@ module ActiveSupport #:nodoc: # exception, any new constants are regarded as being only partially defined # and will be removed immediately. def new_constants_in(*descs) - log_call(*descs) - constant_watch_stack.watch_namespaces(descs) - aborting = true + success = false begin yield # Now yield to the code that is to define new constants. - aborting = false + success = true ensure new_constants = constant_watch_stack.new_constants - log "New constants: #{new_constants * ', '}" - return new_constants unless aborting + return new_constants if success - log "Error during loading, removing partially loaded constants " - new_constants.each { |c| remove_constant(c) }.clear + # Remove partially loaded constants. + new_constants.each { |c| remove_constant(c) } end - - [] end # Convert the provided const desc to a qualified constant name (as a string). # A module, class, symbol, or string may be provided. def to_constant_name(desc) #:nodoc: case desc - when String then desc.sub(/^::/, '') - when Symbol then desc.to_s - when Module - desc.name || - raise(ArgumentError, "Anonymous modules have no name to be referenced by") + when String then desc.sub(/^::/, "") + when Symbol then desc.to_s + when Module + desc.name || + raise(ArgumentError, "Anonymous modules have no name to be referenced by") else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}" end end def remove_constant(const) #:nodoc: # Normalize ::Foo, ::Object::Foo, Object::Foo, Object::Object::Foo, etc. as Foo. - normalized = const.to_s.sub(/\A::/, '') - normalized.sub!(/\A(Object::)+/, '') + normalized = const.to_s.sub(/\A::/, "") + normalized.sub!(/\A(Object::)+/, "") - constants = normalized.split('::') + constants = normalized.split("::") to_remove = constants.pop # Remove the file path from the loaded list. file_path = search_for_file(const.underscore) if file_path expanded = File.expand_path(file_path) - expanded.sub!(/\.rb\z/, '') - self.loaded.delete(expanded) + expanded.sub!(/\.rb\z/, "") + loaded.delete(expanded) end if constants.empty? @@ -733,13 +710,11 @@ module ActiveSupport #:nodoc: # here than require the caller to be clever. We check the parent # rather than the very const argument because we do not want to # trigger Kernel#autoloads, see the comment below. - parent_name = constants.join('::') + parent_name = constants.join("::") return unless qualified_const_defined?(parent_name) parent = constantize(parent_name) end - log "removing constant #{const}" - # In an autoloaded user.rb like this # # autoload :Foo, 'foo' @@ -760,7 +735,7 @@ module ActiveSupport #:nodoc: begin constantized = parent.const_get(to_remove, false) rescue NameError - log "the constant #{const} is not reachable anymore, skipping" + # The constant is no longer reachable, just skip it. return else constantized.before_remove_const if constantized.respond_to?(:before_remove_const) @@ -770,27 +745,9 @@ module ActiveSupport #:nodoc: begin parent.instance_eval { remove_const to_remove } rescue NameError - log "the constant #{const} is not reachable anymore, skipping" + # The constant is no longer reachable, just skip it. end end - - protected - def log_call(*args) - if log_activity? - arg_str = args.collect(&:inspect) * ', ' - /in `([a-z_\?\!]+)'/ =~ caller(1).first - selector = $1 || '<unknown>' - log "called #{selector}(#{arg_str})" - end - end - - def log(msg) - logger.debug "Dependencies: #{msg}" if log_activity? - end - - def log_activity? - logger && log_activity - end end end diff --git a/activesupport/lib/active_support/dependencies/interlock.rb b/activesupport/lib/active_support/dependencies/interlock.rb index 47bcecbb35..e4e18439c5 100644 --- a/activesupport/lib/active_support/dependencies/interlock.rb +++ b/activesupport/lib/active_support/dependencies/interlock.rb @@ -1,4 +1,4 @@ -require 'active_support/concurrency/share_lock' +require "active_support/concurrency/share_lock" module ActiveSupport #:nodoc: module Dependencies #:nodoc: @@ -19,14 +19,12 @@ module ActiveSupport #:nodoc: end end - # Attempt to obtain an "unloading" (exclusive) lock. If possible, - # execute the supplied block while holding the lock. If there is - # concurrent activity, return immediately (without executing the - # block) instead of waiting. - def attempt_unloading - @lock.exclusive(purpose: :unload, compatible: [:load, :unload], after_compatible: [:load, :unload], no_wait: true) do - yield - end + def start_unloading + @lock.start_exclusive(purpose: :unload, compatible: [:load, :unload]) + end + + def done_unloading + @lock.stop_exclusive(compatible: [:load, :unload]) end def start_running @@ -48,6 +46,10 @@ module ActiveSupport #:nodoc: yield end end + + def raw_state(&block) # :nodoc: + @lock.raw_state(&block) + end end end end diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb index 24545d766c..191e582de8 100644 --- a/activesupport/lib/active_support/deprecation.rb +++ b/activesupport/lib/active_support/deprecation.rb @@ -1,4 +1,4 @@ -require 'singleton' +require "singleton" module ActiveSupport # \Deprecation specifies the API used by Rails to deprecate methods, instance @@ -12,12 +12,12 @@ module ActiveSupport # a circular require warning for active_support/deprecation.rb. # # So, we define the constant first, and load dependencies later. - require 'active_support/deprecation/instance_delegator' - require 'active_support/deprecation/behaviors' - require 'active_support/deprecation/reporting' - require 'active_support/deprecation/method_wrappers' - require 'active_support/deprecation/proxy_wrappers' - require 'active_support/core_ext/module/deprecation' + require "active_support/deprecation/instance_delegator" + require "active_support/deprecation/behaviors" + require "active_support/deprecation/reporting" + require "active_support/deprecation/method_wrappers" + require "active_support/deprecation/proxy_wrappers" + require "active_support/core_ext/module/deprecation" include Singleton include InstanceDelegator @@ -32,7 +32,7 @@ module ActiveSupport # and the second is a library name # # ActiveSupport::Deprecation.new('2.0', 'MyLibrary') - def initialize(deprecation_horizon = '5.1', gem_name = 'Rails') + def initialize(deprecation_horizon = "5.2", gem_name = "Rails") self.gem_name = gem_name self.deprecation_horizon = deprecation_horizon # By default, warnings are not silenced and debugging is off. diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb index dc24e2d0e1..1d1354c23e 100644 --- a/activesupport/lib/active_support/deprecation/behaviors.rb +++ b/activesupport/lib/active_support/deprecation/behaviors.rb @@ -2,7 +2,7 @@ require "active_support/notifications" module ActiveSupport # Raised when <tt>ActiveSupport::Deprecation::Behavior#behavior</tt> is set with <tt>:raise</tt>. - # You would set <tt>:raise</tt>, as a behaviour to raise errors and proactively report exceptions from deprecations. + # You would set <tt>:raise</tt>, as a behavior to raise errors and proactively report exceptions from deprecations. class DeprecationException < StandardError end @@ -25,7 +25,7 @@ module ActiveSupport if defined?(Rails.logger) && Rails.logger Rails.logger else - require 'active_support/logger' + require "active_support/logger" ActiveSupport::Logger.new($stderr) end logger.warn message @@ -34,7 +34,7 @@ module ActiveSupport notify: ->(message, callstack) { ActiveSupport::Notifications.instrument("deprecation.rails", - :message => message, :callstack => callstack) + message: message, callstack: callstack) }, silence: ->(message, callstack) {}, diff --git a/activesupport/lib/active_support/deprecation/instance_delegator.rb b/activesupport/lib/active_support/deprecation/instance_delegator.rb index 8472a58add..6d390f3b37 100644 --- a/activesupport/lib/active_support/deprecation/instance_delegator.rb +++ b/activesupport/lib/active_support/deprecation/instance_delegator.rb @@ -1,11 +1,12 @@ -require 'active_support/core_ext/kernel/singleton_class' -require 'active_support/core_ext/module/delegation' +require "active_support/core_ext/kernel/singleton_class" +require "active_support/core_ext/module/delegation" module ActiveSupport class Deprecation module InstanceDelegator # :nodoc: def self.included(base) base.extend(ClassMethods) + base.singleton_class.prepend(OverrideDelegators) base.public_class_method :new end @@ -19,6 +20,18 @@ module ActiveSupport singleton_class.delegate(method_name, to: :instance) end end + + module OverrideDelegators # :nodoc: + def warn(message = nil, callstack = nil) + callstack ||= caller_locations(2) + super + end + + def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil) + caller_backtrace ||= caller_locations(2) + super + end + end end end end diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb index f5ea6669ce..7655fa4f99 100644 --- a/activesupport/lib/active_support/deprecation/method_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb @@ -1,5 +1,5 @@ -require 'active_support/core_ext/module/aliasing' -require 'active_support/core_ext/array/extract_options' +require "active_support/core_ext/module/aliasing" +require "active_support/core_ext/array/extract_options" module ActiveSupport class Deprecation diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb index 0cb2d4d22e..1c6618b19a 100644 --- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb @@ -1,4 +1,5 @@ -require 'active_support/inflector/methods' +require "active_support/inflector/methods" +require "active_support/core_ext/regexp" module ActiveSupport class Deprecation @@ -10,7 +11,7 @@ module ActiveSupport super end - instance_methods.each { |m| undef_method m unless m =~ /^__|^object_id$/ } + instance_methods.each { |m| undef_method m unless /^__|^object_id$/.match?(m) } # Don't give a deprecation warning on inspect since test/unit and error # logs rely on it for diagnostics. diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb index f89fc0fe14..b8d200ba94 100644 --- a/activesupport/lib/active_support/deprecation/reporting.rb +++ b/activesupport/lib/active_support/deprecation/reporting.rb @@ -1,3 +1,5 @@ +require "rbconfig" + module ActiveSupport class Deprecation module Reporting @@ -55,15 +57,14 @@ module ActiveSupport def deprecated_method_warning(method_name, message = nil) warning = "#{method_name} is deprecated and will be removed from #{gem_name} #{deprecation_horizon}" case message - when Symbol then "#{warning} (use #{message} instead)" - when String then "#{warning} (#{message})" + when Symbol then "#{warning} (use #{message} instead)" + when String then "#{warning} (#{message})" else warning end end def deprecation_message(callstack, message = nil) message ||= "You are using deprecated behavior which will be removed from the next major or minor release." - message += '.' unless message =~ /\.$/ "DEPRECATION WARNING: #{message} #{deprecation_caller_message(callstack)}" end @@ -81,17 +82,17 @@ module ActiveSupport def extract_callstack(callstack) return _extract_callstack(callstack) if callstack.first.is_a? String - rails_gem_root = File.expand_path("../../../../..", __FILE__) + "/" offending_line = callstack.find { |frame| - frame.absolute_path && !frame.absolute_path.start_with?(rails_gem_root) + frame.absolute_path && !ignored_callstack(frame.absolute_path) } || callstack.first + [offending_line.path, offending_line.lineno, offending_line.label] end def _extract_callstack(callstack) warn "Please pass `caller_locations` to the deprecation API" if $VERBOSE - rails_gem_root = File.expand_path("../../../../..", __FILE__) + "/" - offending_line = callstack.find { |line| !line.start_with?(rails_gem_root) } || callstack.first + offending_line = callstack.find { |line| !ignored_callstack(line) } || callstack.first + if offending_line if md = offending_line.match(/^(.+?):(\d+)(?::in `(.*?)')?/) md.captures @@ -100,6 +101,12 @@ module ActiveSupport end end end + + RAILS_GEM_ROOT = File.expand_path("../../../../..", __FILE__) + "/" + + def ignored_callstack(path) + path.start_with?(RAILS_GEM_ROOT) || path.start_with?(RbConfig::CONFIG["rubylibdir"]) + end end end end diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index c63b61e97a..6ec0c3a70a 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -1,5 +1,5 @@ -require 'active_support/core_ext/array/conversions' -require 'active_support/core_ext/object/acts_like' +require "active_support/core_ext/array/conversions" +require "active_support/core_ext/object/acts_like" module ActiveSupport # Provides accurate date and time measurements using Date#advance and @@ -9,6 +9,9 @@ module ActiveSupport class Duration attr_accessor :value, :parts + autoload :ISO8601Parser, "active_support/duration/iso8601_parser" + autoload :ISO8601Serializer, "active_support/duration/iso8601_serializer" + def initialize(value, parts) #:nodoc: @value, @parts = value, parts end @@ -117,8 +120,8 @@ module ActiveSupport def inspect #:nodoc: parts. reduce(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }. - sort_by {|unit, _ | [:years, :months, :days, :minutes, :seconds].index(unit)}. - map {|unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}"}. + sort_by { |unit, _ | [:years, :months, :weeks, :days, :hours, :minutes, :seconds].index(unit) }. + map { |unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}" }. to_sentence(locale: ::I18n.default_locale) end @@ -130,6 +133,23 @@ 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! + time = ::Time.current + new(time.advance(parts) - time, 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) + ISO8601Serializer.new(self, precision: precision).serialize + end + delegate :<=>, to: :value protected @@ -139,6 +159,10 @@ module ActiveSupport if t.acts_like?(:time) || t.acts_like?(:date) if type == :seconds t.since(sign * number) + elsif type == :minutes + t.since(sign * number * 60) + elsif type == :hours + t.since(sign * number * 3600) else t.advance(type => sign * number) end diff --git a/activesupport/lib/active_support/duration/iso8601_parser.rb b/activesupport/lib/active_support/duration/iso8601_parser.rb new file mode 100644 index 0000000000..e96cb8e883 --- /dev/null +++ b/activesupport/lib/active_support/duration/iso8601_parser.rb @@ -0,0 +1,123 @@ +require "strscan" +require "active_support/core_ext/regexp" + +module ActiveSupport + class Duration + # Parses a string formatted according to ISO 8601 Duration into the hash. + # + # See {ISO 8601}[http://en.wikipedia.org/wiki/ISO_8601#Durations] for more information. + # + # This parser allows negative parts to be present in pattern. + class ISO8601Parser # :nodoc: + class ParsingError < ::ArgumentError; end + + PERIOD_OR_COMMA = /\.|,/ + PERIOD = ".".freeze + COMMA = ",".freeze + + SIGN_MARKER = /\A\-|\+|/ + DATE_MARKER = /P/ + TIME_MARKER = /T/ + DATE_COMPONENT = /(\-?\d+(?:[.,]\d+)?)(Y|M|D|W)/ + TIME_COMPONENT = /(\-?\d+(?:[.,]\d+)?)(H|M|S)/ + + DATE_TO_PART = { "Y" => :years, "M" => :months, "W" => :weeks, "D" => :days } + TIME_TO_PART = { "H" => :hours, "M" => :minutes, "S" => :seconds } + + DATE_COMPONENTS = [:years, :months, :days] + TIME_COMPONENTS = [:hours, :minutes, :seconds] + + attr_reader :parts, :scanner + attr_accessor :mode, :sign + + def initialize(string) + @scanner = StringScanner.new(string) + @parts = {} + @mode = :start + @sign = 1 + end + + def parse! + while !finished? + case mode + when :start + if scan(SIGN_MARKER) + self.sign = (scanner.matched == "-") ? -1 : 1 + self.mode = :sign + else + raise_parsing_error + end + + when :sign + if scan(DATE_MARKER) + self.mode = :date + else + raise_parsing_error + end + + when :date + if scan(TIME_MARKER) + self.mode = :time + elsif scan(DATE_COMPONENT) + parts[DATE_TO_PART[scanner[2]]] = number * sign + else + raise_parsing_error + end + + when :time + if scan(TIME_COMPONENT) + parts[TIME_TO_PART[scanner[2]]] = number * sign + else + raise_parsing_error + end + + end + end + + validate! + parts + end + + private + + def finished? + scanner.eos? + end + + # Parses number which can be a float with either comma or period. + def number + PERIOD_OR_COMMA.match?(scanner[1]) ? scanner[1].tr(COMMA, PERIOD).to_f : scanner[1].to_i + end + + def scan(pattern) + scanner.scan(pattern) + end + + def raise_parsing_error(reason = nil) + raise ParsingError, "Invalid ISO 8601 duration: #{scanner.string.inspect} #{reason}".strip + end + + # Checks for various semantic errors as stated in ISO 8601 standard. + def validate! + raise_parsing_error("is empty duration") if parts.empty? + + # Mixing any of Y, M, D with W is invalid. + if parts.key?(:weeks) && (parts.keys & DATE_COMPONENTS).any? + raise_parsing_error("mixing weeks with other date parts not allowed") + end + + # Specifying an empty T part is invalid. + if mode == :time && (parts.keys & TIME_COMPONENTS).empty? + raise_parsing_error("time part marker is present but time part is empty") + end + + fractions = parts.values.reject(&:zero?).select { |a| (a % 1) != 0 } + unless fractions.empty? || (fractions.size == 1 && fractions.last == @parts.values.reject(&:zero?).last) + raise_parsing_error "(only last part can be fractional)" + end + + return true + end + end + end +end diff --git a/activesupport/lib/active_support/duration/iso8601_serializer.rb b/activesupport/lib/active_support/duration/iso8601_serializer.rb new file mode 100644 index 0000000000..542630b950 --- /dev/null +++ b/activesupport/lib/active_support/duration/iso8601_serializer.rb @@ -0,0 +1,53 @@ +require "active_support/core_ext/object/blank" +require "active_support/core_ext/hash/transform_values" + +module ActiveSupport + class Duration + # Serializes duration to string according to ISO 8601 Duration format. + class ISO8601Serializer + def initialize(duration, precision: nil) + @duration = duration + @precision = precision + end + + # Builds and returns output string. + def serialize + parts, sign = normalize + return "PT0S".freeze if parts.empty? + + output = "P" + output << "#{parts[:years]}Y" if parts.key?(:years) + output << "#{parts[:months]}M" if parts.key?(:months) + output << "#{parts[:weeks]}W" if parts.key?(:weeks) + output << "#{parts[:days]}D" if parts.key?(:days) + time = "" + time << "#{parts[:hours]}H" if parts.key?(:hours) + time << "#{parts[:minutes]}M" if parts.key?(:minutes) + if parts.key?(:seconds) + time << "#{sprintf(@precision ? "%0.0#{@precision}f" : '%g', parts[:seconds])}S" + end + output << "T#{time}" if time.present? + "#{sign}#{output}" + end + + private + + # Return pair of duration's parts and whole duration sign. + # Parts are summarized (as they can become repetitive due to addition, etc). + # Zero parts are removed as not significant. + # If all parts are negative it will negate all of them and return minus as a sign. + def normalize + parts = @duration.parts.each_with_object(Hash.new(0)) do |(k,v),p| + p[k] += v unless v.zero? + end + # If all parts are negative - let's make a negative duration + sign = "" + if parts.values.all? { |v| v < 0 } + sign = "-" + parts.transform_values!(&:-@) + end + [parts, sign] + end + end + 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 315be85fb3..f54f88eb0a 100644 --- a/activesupport/lib/active_support/evented_file_update_checker.rb +++ b/activesupport/lib/active_support/evented_file_update_checker.rb @@ -1,8 +1,35 @@ -require 'set' -require 'pathname' -require 'concurrent/atomic/atomic_boolean' +require "set" +require "pathname" +require "concurrent/atomic/atomic_boolean" module ActiveSupport + # Allows you to "listen" to changes in a file system. + # The evented file updater does not hit disk when checking for updates + # instead it uses platform specific file system events to trigger a change + # in state. + # + # The file checker takes an array of files to watch or a hash specifying directories + # and file extensions to watch. It also takes a block that is called when + # EventedFileUpdateChecker#execute is run or when EventedFileUpdateChecker#execute_if_updated + # is run and there have been changes to the file system. + # + # Note: Forking will cause the first call to `updated?` to return `true`. + # + # Example: + # + # checker = EventedFileUpdateChecker.new(["/tmp/foo"], -> { puts "changed" }) + # checker.updated? + # # => false + # checker.execute_if_updated + # # => nil + # + # FileUtils.touch("/tmp/foo") + # + # checker.updated? + # # => true + # checker.execute_if_updated + # # => "changed" + # class EventedFileUpdateChecker #:nodoc: all def initialize(files, dirs = {}, &block) @ph = PathHelper.new @@ -13,20 +40,35 @@ module ActiveSupport @dirs[@ph.xpath(dir)] = Array(exts).map { |ext| @ph.normalize_extension(ext) } end - @block = block - @updated = Concurrent::AtomicBoolean.new(false) - @lcsp = @ph.longest_common_subpath(@dirs.keys) + @block = block + @updated = Concurrent::AtomicBoolean.new(false) + @lcsp = @ph.longest_common_subpath(@dirs.keys) + @pid = Process.pid + @boot_mutex = Mutex.new - if (dtw = directories_to_watch).any? + if (@dtw = directories_to_watch).any? # Loading listen triggers warnings. These are originated by a legit # usage of attr_* macros for private attributes, but adds a lot of noise # to our test suite. Thus, we lazy load it and disable warnings locally. - silence_warnings { require 'listen' } - Listen.to(*dtw, &method(:changed)).start + silence_warnings do + begin + require "listen" + rescue LoadError => e + raise LoadError, "Could not load the 'listen' gem. Add `gem 'listen'` to the development group of your Gemfile", e.backtrace + end + end end + boot! end def updated? + @boot_mutex.synchronize do + if @pid != Process.pid + boot! + @pid = Process.pid + @updated.make_true + end + end @updated.true? end @@ -37,12 +79,16 @@ module ActiveSupport def execute_if_updated if updated? + yield if block_given? execute true end end private + def boot! + Listen.to(*@dtw, &method(:changed)).start + end def changed(modified, added, removed) unless updated? @@ -78,73 +124,71 @@ module ActiveSupport @ph.filter_out_descendants(dtw) end - class PathHelper - using Module.new { - refine Pathname do - def ascendant_of?(other) - self != other && other.ascend do |ascendant| - break true if self == ascendant - end - end + class PathHelper + def xpath(path) + Pathname.new(path).expand_path end - } - - def xpath(path) - Pathname.new(path).expand_path - end - def normalize_extension(ext) - ext.to_s.sub(/\A\./, '') - end + def normalize_extension(ext) + ext.to_s.sub(/\A\./, "") + end - # Given a collection of Pathname objects returns the longest subpath - # common to all of them, or +nil+ if there is none. - def longest_common_subpath(paths) - return if paths.empty? - - lcsp = Pathname.new(paths[0]) - - paths[1..-1].each do |path| - until lcsp.ascendant_of?(path) - if lcsp.root? - # If we get here a root directory is not an ascendant of path. - # This may happen if there are paths in different drives on - # Windows. - return - else - lcsp = lcsp.parent + # Given a collection of Pathname objects returns the longest subpath + # common to all of them, or +nil+ if there is none. + def longest_common_subpath(paths) + return if paths.empty? + + lcsp = Pathname.new(paths[0]) + + paths[1..-1].each do |path| + until ascendant_of?(lcsp, path) + if lcsp.root? + # If we get here a root directory is not an ascendant of path. + # This may happen if there are paths in different drives on + # Windows. + return + else + lcsp = lcsp.parent + end end end - end - lcsp - end + lcsp + end - # Returns the deepest existing ascendant, which could be the argument itself. - def existing_parent(dir) - dir.ascend do |ascendant| - break ascendant if ascendant.directory? + # Returns the deepest existing ascendant, which could be the argument itself. + def existing_parent(dir) + dir.ascend do |ascendant| + break ascendant if ascendant.directory? + end end - end - # Filters out directories which are descendants of others in the collection (stable). - def filter_out_descendants(dirs) - return dirs if dirs.length < 2 + # Filters out directories which are descendants of others in the collection (stable). + def filter_out_descendants(dirs) + return dirs if dirs.length < 2 - dirs_sorted_by_nparts = dirs.sort_by { |dir| dir.each_filename.to_a.length } - descendants = [] + dirs_sorted_by_nparts = dirs.sort_by { |dir| dir.each_filename.to_a.length } + descendants = [] - until dirs_sorted_by_nparts.empty? - dir = dirs_sorted_by_nparts.shift + until dirs_sorted_by_nparts.empty? + dir = dirs_sorted_by_nparts.shift - dirs_sorted_by_nparts.reject! do |possible_descendant| - dir.ascendant_of?(possible_descendant) && descendants << possible_descendant + dirs_sorted_by_nparts.reject! do |possible_descendant| + ascendant_of?(dir, possible_descendant) && descendants << possible_descendant + end end + + # Array#- preserves order. + dirs - descendants end - # Array#- preserves order. - dirs - descendants + private + + def ascendant_of?(base, other) + base != other && other.ascend do |ascendant| + break true if base == ascendant + end + end end - end end end diff --git a/activesupport/lib/active_support/execution_wrapper.rb b/activesupport/lib/active_support/execution_wrapper.rb new file mode 100644 index 0000000000..3384d12d5b --- /dev/null +++ b/activesupport/lib/active_support/execution_wrapper.rb @@ -0,0 +1,126 @@ +require "active_support/callbacks" + +module ActiveSupport + class ExecutionWrapper + include ActiveSupport::Callbacks + + Null = Object.new # :nodoc: + def Null.complete! # :nodoc: + end + + define_callbacks :run + define_callbacks :complete + + def self.to_run(*args, &block) + set_callback(:run, *args, &block) + end + + def self.to_complete(*args, &block) + set_callback(:complete, *args, &block) + end + + class RunHook < Struct.new(:hook) # :nodoc: + def before(target) + hook_state = target.send(:hook_state) + hook_state[hook] = hook.run + end + end + + class CompleteHook < Struct.new(:hook) # :nodoc: + def before(target) + hook_state = target.send(:hook_state) + if hook_state.key?(hook) + hook.complete hook_state[hook] + end + end + alias after before + end + + # Register an object to be invoked during both the +run+ and + # +complete+ steps. + # + # +hook.complete+ will be passed the value returned from +hook.run+, + # and will only be invoked if +run+ has previously been called. + # (Mostly, this means it won't be invoked if an exception occurs in + # a preceding +to_run+ block; all ordinary +to_complete+ blocks are + # invoked in that situation.) + def self.register_hook(hook, outer: false) + if outer + to_run RunHook.new(hook), prepend: true + to_complete :after, CompleteHook.new(hook) + else + to_run RunHook.new(hook) + to_complete CompleteHook.new(hook) + end + end + + # Run this execution. + # + # Returns an instance, whose +complete!+ method *must* be invoked + # after the work has been performed. + # + # Where possible, prefer +wrap+. + def self.run! + if active? + Null + else + new.tap do |instance| + success = nil + begin + instance.run! + success = true + ensure + instance.complete! unless success + end + end + end + end + + # Perform the work in the supplied block as an execution. + def self.wrap + return yield if active? + + instance = run! + begin + yield + ensure + instance.complete! + end + end + + class << self # :nodoc: + attr_accessor :active + end + + def self.inherited(other) # :nodoc: + super + other.active = Concurrent::Hash.new + end + + self.active = Concurrent::Hash.new + + def self.active? # :nodoc: + @active[Thread.current] + end + + def run! # :nodoc: + self.class.active[Thread.current] = true + run_callbacks(:run) + end + + # Complete this in-flight execution. This method *must* be called + # exactly once on the result of any call to +run!+. + # + # Where possible, prefer +wrap+. + def complete! + run_callbacks(:complete) + ensure + self.class.active.delete Thread.current + end + + private + def hook_state + @_hook_state ||= {} + end + end +end diff --git a/activesupport/lib/active_support/executor.rb b/activesupport/lib/active_support/executor.rb new file mode 100644 index 0000000000..a6400cae0a --- /dev/null +++ b/activesupport/lib/active_support/executor.rb @@ -0,0 +1,6 @@ +require "active_support/execution_wrapper" + +module ActiveSupport + class Executor < ExecutionWrapper + end +end diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb index 1fa9335080..98aceabe21 100644 --- a/activesupport/lib/active_support/file_update_checker.rb +++ b/activesupport/lib/active_support/file_update_checker.rb @@ -1,3 +1,5 @@ +require "active_support/core_ext/time/calculations" + module ActiveSupport # FileUpdateChecker specifies the API used by Rails to watch files # and control reloading. The API depends on four methods: @@ -23,7 +25,7 @@ module ActiveSupport # I18n.reload! # end # - # ActionDispatch::Reloader.to_prepare do + # ActiveSupport::Reloader.to_prepare do # i18n_reloader.execute_if_updated # end class FileUpdateChecker @@ -81,6 +83,7 @@ module ActiveSupport # Execute the block given if updated. def execute_if_updated if updated? + yield if block_given? execute true else @@ -90,48 +93,65 @@ module ActiveSupport private - def watched - @watched || begin - all = @files.select { |f| File.exist?(f) } - all.concat(Dir[@glob]) if @glob - all + def watched + @watched || begin + all = @files.select { |f| File.exist?(f) } + all.concat(Dir[@glob]) if @glob + all + end end - end - def updated_at(paths) - @updated_at || max_mtime(paths) || Time.at(0) - end + def updated_at(paths) + @updated_at || max_mtime(paths) || Time.at(0) + end - # This method returns the maximum mtime of the files in +paths+, or +nil+ - # if the array is empty. - # - # Files with a mtime in the future are ignored. Such abnormal situation - # can happen for example if the user changes the clock by hand. It is - # healthy to consider this edge case because with mtimes in the future - # reloading is not triggered. - def max_mtime(paths) - time_now = Time.now - paths.map {|path| File.mtime(path)}.reject {|mtime| time_now < mtime}.max - end + # This method returns the maximum mtime of the files in +paths+, or +nil+ + # if the array is empty. + # + # Files with a mtime in the future are ignored. Such abnormal situation + # can happen for example if the user changes the clock by hand. It is + # healthy to consider this edge case because with mtimes in the future + # reloading is not triggered. + def max_mtime(paths) + time_now = Time.now + max_mtime = nil + + # Time comparisons are performed with #compare_without_coercion because + # AS redefines these operators in a way that is much slower and does not + # bring any benefit in this particular code. + # + # Read t1.compare_without_coercion(t2) < 0 as t1 < t2. + paths.each do |path| + mtime = File.mtime(path) - def compile_glob(hash) - hash.freeze # Freeze so changes aren't accidentally pushed - return if hash.empty? + next if time_now.compare_without_coercion(mtime) < 0 + + if max_mtime.nil? || max_mtime.compare_without_coercion(mtime) < 0 + max_mtime = mtime + end + end - globs = hash.map do |key, value| - "#{escape(key)}/**/*#{compile_ext(value)}" + max_mtime end - "{#{globs.join(",")}}" - end - def escape(key) - key.gsub(',','\,') - end + def compile_glob(hash) + hash.freeze # Freeze so changes aren't accidentally pushed + return if hash.empty? - def compile_ext(array) - array = Array(array) - return if array.empty? - ".{#{array.join(",")}}" - end + globs = hash.map do |key, value| + "#{escape(key)}/**/*#{compile_ext(value)}" + end + "{#{globs.join(",")}}" + end + + def escape(key) + key.gsub(",",'\,') + end + + def compile_ext(array) + array = Array(array) + return if array.empty? + ".{#{array.join(",")}}" + end end end diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb index fc08273b6d..74f2d8dd4b 100644 --- a/activesupport/lib/active_support/gem_version.rb +++ b/activesupport/lib/active_support/gem_version.rb @@ -6,9 +6,9 @@ module ActiveSupport module VERSION MAJOR = 5 - MINOR = 0 + MINOR = 1 TINY = 0 - PRE = "beta2" + PRE = "alpha" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activesupport/lib/active_support/gzip.rb b/activesupport/lib/active_support/gzip.rb index b837c879bb..4aee8dddba 100644 --- a/activesupport/lib/active_support/gzip.rb +++ b/activesupport/lib/active_support/gzip.rb @@ -1,5 +1,5 @@ -require 'zlib' -require 'stringio' +require "zlib" +require "stringio" module ActiveSupport # A convenient wrapper for the zlib standard library that allows @@ -9,7 +9,7 @@ module ActiveSupport # # => "\x1F\x8B\b\x00o\x8D\xCDO\x00\x03K\xCE\xCF-(J-.V\xC8MU\x04\x00R>n\x83\f\x00\x00\x00" # # ActiveSupport::Gzip.decompress(gzip) - # # => "compress me!" + # # => "compress me!" module Gzip class Stream < StringIO def initialize(*) diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 03770a197c..d3bb5496bf 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -1,5 +1,5 @@ -require 'active_support/core_ext/hash/keys' -require 'active_support/core_ext/hash/reverse_merge' +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/hash/reverse_merge" module ActiveSupport # Implements a hash where keys <tt>:foo</tt> and <tt>"foo"</tt> are considered @@ -40,6 +40,12 @@ module ActiveSupport # rgb = { black: '#000000', white: '#FFFFFF' }.with_indifferent_access # # which may be handy. + # + # To access this class outside of Rails, require the core extension with: + # + # require "active_support/core_ext/hash/indifferent_access" + # + # which will, in turn, require this file. class HashWithIndifferentAccess < Hash # Returns +true+ so that <tt>Array#extract_options!</tt> finds members of # this class. @@ -161,7 +167,6 @@ module ActiveSupport alias_method :has_key?, :key? alias_method :member?, :key? - # Same as <tt>Hash#[]</tt> where the key passed as argument can be # either a string or a symbol: # @@ -217,7 +222,7 @@ module ActiveSupport # modify the receiver but rather returns a new hash with indifferent # access with the result of the merge. def merge(hash, &block) - self.dup.update(hash, &block) + dup.update(hash, &block) end # Like +merge+ but the other way around: Merges the receiver into the @@ -268,6 +273,11 @@ module ActiveSupport dup.tap { |hash| hash.reject!(*args, &block) } end + def transform_values(*args, &block) + return to_enum(:transform_values) unless block_given? + dup.tap { |hash| hash.transform_values!(*args, &block) } + end + # Convert to a regular hash with string keys. def to_hash _new_hash = Hash.new diff --git a/activesupport/lib/active_support/i18n.rb b/activesupport/lib/active_support/i18n.rb index 6cc98191d4..f0408f429c 100644 --- a/activesupport/lib/active_support/i18n.rb +++ b/activesupport/lib/active_support/i18n.rb @@ -1,13 +1,13 @@ -require 'active_support/core_ext/hash/deep_merge' -require 'active_support/core_ext/hash/except' -require 'active_support/core_ext/hash/slice' +require "active_support/core_ext/hash/deep_merge" +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/slice" begin - require 'i18n' + require "i18n" rescue LoadError => e $stderr.puts "The i18n gem is not available. Please add it to your Gemfile and run bundle install" raise e end -require 'active_support/lazy_load_hooks' +require "active_support/lazy_load_hooks" ActiveSupport.run_load_hooks(:i18n) I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml" diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb index 82aacf3b24..08c9ef8f02 100644 --- a/activesupport/lib/active_support/i18n_railtie.rb +++ b/activesupport/lib/active_support/i18n_railtie.rb @@ -64,8 +64,8 @@ module I18n end app.reloaders << reloader - ActionDispatch::Reloader.to_prepare do - reloader.execute_if_updated + app.reloader.to_run do + reloader.execute_if_updated { require_unload_lock! } # TODO: remove the following line as soon as the return value of # callbacks is ignored, that is, returning `false` does not # display a deprecation warning or halts the callback chain. @@ -83,14 +83,15 @@ module I18n def self.init_fallbacks(fallbacks) include_fallbacks_module - args = case fallbacks - when ActiveSupport::OrderedOptions - [*(fallbacks[:defaults] || []) << fallbacks[:map]].compact - when Hash, Array - Array.wrap(fallbacks) - else # TrueClass - [] - end + args = \ + case fallbacks + when ActiveSupport::OrderedOptions + [*(fallbacks[:defaults] || []) << fallbacks[:map]].compact + when Hash, Array + Array.wrap(fallbacks) + else # TrueClass + [] + end I18n.fallbacks = I18n::Locale::Fallbacks.new(*args) end diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb index 2ca1124e76..afa7d1f325 100644 --- a/activesupport/lib/active_support/inflections.rb +++ b/activesupport/lib/active_support/inflections.rb @@ -1,4 +1,4 @@ -require 'active_support/inflector/inflections' +require "active_support/inflector/inflections" #-- # Defines the standard inflection rules. These are the starting point for @@ -8,8 +8,8 @@ require 'active_support/inflector/inflections' #++ module ActiveSupport Inflector.inflections(:en) do |inflect| - inflect.plural(/$/, 's') - inflect.plural(/s$/i, 's') + inflect.plural(/$/, "s") + inflect.plural(/s$/i, "s") inflect.plural(/^(ax|test)is$/i, '\1es') inflect.plural(/(octop|vir)us$/i, '\1i') inflect.plural(/(octop|vir)i$/i, '\1i') @@ -18,7 +18,7 @@ module ActiveSupport inflect.plural(/(buffal|tomat)o$/i, '\1oes') inflect.plural(/([ti])um$/i, '\1a') inflect.plural(/([ti])a$/i, '\1a') - inflect.plural(/sis$/i, 'ses') + inflect.plural(/sis$/i, "ses") inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves') inflect.plural(/(hive)$/i, '\1s') inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies') @@ -30,7 +30,7 @@ module ActiveSupport inflect.plural(/^(oxen)$/i, '\1') inflect.plural(/(quiz)$/i, '\1zes') - inflect.singular(/s$/i, '') + inflect.singular(/s$/i, "") inflect.singular(/(ss)$/i, '\1') inflect.singular(/(n)ews$/i, '\1ews') inflect.singular(/([ti])a$/i, '\1um') @@ -58,12 +58,12 @@ module ActiveSupport inflect.singular(/(quiz)zes$/i, '\1') inflect.singular(/(database)s$/i, '\1') - inflect.irregular('person', 'people') - inflect.irregular('man', 'men') - inflect.irregular('child', 'children') - inflect.irregular('sex', 'sexes') - inflect.irregular('move', 'moves') - inflect.irregular('zombie', 'zombies') + inflect.irregular("person", "people") + inflect.irregular("man", "men") + inflect.irregular("child", "children") + inflect.irregular("sex", "sexes") + inflect.irregular("move", "moves") + inflect.irregular("zombie", "zombies") inflect.uncountable(%w(equipment information rice money species series fish sheep jeans police)) end diff --git a/activesupport/lib/active_support/inflector.rb b/activesupport/lib/active_support/inflector.rb index 215a60eba7..48631b16a8 100644 --- a/activesupport/lib/active_support/inflector.rb +++ b/activesupport/lib/active_support/inflector.rb @@ -1,7 +1,7 @@ # in case active_support/inflector is required without the rest of active_support -require 'active_support/inflector/inflections' -require 'active_support/inflector/transliterate' -require 'active_support/inflector/methods' +require "active_support/inflector/inflections" +require "active_support/inflector/transliterate" +require "active_support/inflector/methods" -require 'active_support/inflections' -require 'active_support/core_ext/string/inflections' +require "active_support/inflections" +require "active_support/core_ext/string/inflections" diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index f3e52b48ac..aa68f9ec9e 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -1,6 +1,6 @@ -require 'concurrent/map' -require 'active_support/core_ext/array/prepend_and_append' -require 'active_support/i18n' +require "concurrent/map" +require "active_support/core_ext/array/prepend_and_append" +require "active_support/i18n" module ActiveSupport module Inflector @@ -43,8 +43,8 @@ module ActiveSupport end def add(words) - self.concat(words.flatten.map(&:downcase)) - @regex_array += self.map {|word| to_regex(word) } + concat(words.flatten.map(&:downcase)) + @regex_array += map { |word| to_regex(word) } self end @@ -215,10 +215,10 @@ module ActiveSupport # clear :plurals def clear(scope = :all) case scope - when :all - @plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, [] + when :all + @plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, [] else - instance_variable_set "@#{scope}", [] + instance_variable_set "@#{scope}", [] end end end diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index f741c0bfb8..c80243c40a 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -1,4 +1,5 @@ -require 'active_support/inflections' +require "active_support/inflections" +require "active_support/core_ext/regexp" module ActiveSupport # The Inflector transforms words from singular to plural, class names to table @@ -71,7 +72,7 @@ module ActiveSupport string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { |match| match.downcase } end string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" } - string.gsub!('/'.freeze, '::'.freeze) + string.gsub!("/".freeze, "::".freeze) string end @@ -87,8 +88,8 @@ module ActiveSupport # # camelize(underscore('SSLError')) # => "SslError" def underscore(camel_cased_word) - return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/ - word = camel_cased_word.to_s.gsub('::'.freeze, '/'.freeze) + return camel_cased_word unless /[A-Z-]|::/.match?(camel_cased_word) + word = camel_cased_word.to_s.gsub("::".freeze, "/".freeze) word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'.freeze }#{$2.downcase}" } word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze) word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze) @@ -125,9 +126,9 @@ module ActiveSupport inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) } - result.sub!(/\A_+/, ''.freeze) - result.sub!(/_id\z/, ''.freeze) - result.tr!('_'.freeze, ' '.freeze) + result.sub!(/\A_+/, "".freeze) + result.sub!(/_id\z/, "".freeze) + result.tr!("_".freeze, " ".freeze) result.gsub!(/([a-z\d]*)/i) do |match| "#{inflections.acronyms[match] || match.downcase}" @@ -140,6 +141,15 @@ module ActiveSupport result end + # Converts just the first character to uppercase. + # + # upcase_first('what a Lovely Day') # => "What a Lovely Day" + # upcase_first('w') # => "W" + # upcase_first('') # => "" + def upcase_first(string) + string.length > 0 ? string[0].upcase.concat(string[1..-1]) : "" + end + # Capitalizes all the words and replaces some characters in the string to # create a nicer looking title. +titleize+ is meant for creating pretty # output. It is not used in the Rails internals. @@ -176,14 +186,14 @@ module ActiveSupport # classify('calculus') # => "Calculus" def classify(table_name) # strip out any leading schema name - camelize(singularize(table_name.to_s.sub(/.*\./, ''.freeze))) + camelize(singularize(table_name.to_s.sub(/.*\./, "".freeze))) end # Replaces underscores with dashes in the string. # # dasherize('puni_puni') # => "puni-puni" def dasherize(underscored_word) - underscored_word.tr('_'.freeze, '-'.freeze) + underscored_word.tr("_".freeze, "-".freeze) end # Removes the module part from the expression in the string. @@ -196,7 +206,7 @@ module ActiveSupport # See also #deconstantize. def demodulize(path) path = path.to_s - if i = path.rindex('::') + if i = path.rindex("::") path[(i+2)..-1] else path @@ -213,7 +223,7 @@ module ActiveSupport # # See also #demodulize. def deconstantize(path) - path.to_s[0, path.rindex('::') || 0] # implementation based on the one in facets' Module#spacename + path.to_s[0, path.rindex("::") || 0] # implementation based on the one in facets' Module#spacename end # Creates a foreign key name from a class name. @@ -229,8 +239,8 @@ module ActiveSupport # Tries to find a constant with the name specified in the argument string. # - # 'Module'.constantize # => Module - # 'Foo::Bar'.constantize # => Foo::Bar + # constantize('Module') # => Module + # constantize('Foo::Bar') # => Foo::Bar # # The name is assumed to be the one of a top-level constant, no matter # whether it starts with "::" or not. No lexical context is taken into @@ -239,14 +249,14 @@ module ActiveSupport # C = 'outside' # module M # C = 'inside' - # C # => 'inside' - # 'C'.constantize # => 'outside', same as ::C + # C # => 'inside' + # constantize('C') # => 'outside', same as ::C # end # # NameError is raised when the name is not in CamelCase or the constant is # unknown. def constantize(camel_cased_word) - names = camel_cased_word.split('::'.freeze) + names = camel_cased_word.split("::".freeze) # Trigger a built-in NameError exception including the ill-formed constant in the message. Object.const_get(camel_cased_word) if names.empty? @@ -304,7 +314,7 @@ module ActiveSupport raise if e.name && !(camel_cased_word.to_s.split("::").include?(e.name.to_s) || e.name.to_s == camel_cased_word.to_s) rescue ArgumentError => e - raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/ + raise unless /not missing constant #{const_regexp(camel_cased_word)}!$/.match?(e.message) end # Returns the suffix that should be added to a number to denote the position @@ -323,9 +333,9 @@ module ActiveSupport "th" else case abs_number % 10 - when 1; "st" - when 2; "nd" - when 3; "rd" + when 1; "st" + when 2; "nd" + when 3; "rd" else "th" end end @@ -346,36 +356,36 @@ module ActiveSupport private - # Mounts a regular expression, returned as a string to ease interpolation, - # that will match part by part the given constant. - # - # const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?" - # const_regexp("::") # => "::" - def const_regexp(camel_cased_word) #:nodoc: - parts = camel_cased_word.split("::".freeze) + # Mounts a regular expression, returned as a string to ease interpolation, + # that will match part by part the given constant. + # + # const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?" + # const_regexp("::") # => "::" + def const_regexp(camel_cased_word) #:nodoc: + parts = camel_cased_word.split("::".freeze) - return Regexp.escape(camel_cased_word) if parts.blank? + return Regexp.escape(camel_cased_word) if parts.blank? - last = parts.pop + last = parts.pop - parts.reverse.inject(last) do |acc, part| - part.empty? ? acc : "#{part}(::#{acc})?" + parts.reverse.inject(last) do |acc, part| + part.empty? ? acc : "#{part}(::#{acc})?" + end end - end - # Applies inflection rules for +singularize+ and +pluralize+. - # - # apply_inflections('post', inflections.plurals) # => "posts" - # apply_inflections('posts', inflections.singulars) # => "post" - def apply_inflections(word, rules) - result = word.to_s.dup + # Applies inflection rules for +singularize+ and +pluralize+. + # + # apply_inflections('post', inflections.plurals) # => "posts" + # apply_inflections('posts', inflections.singulars) # => "post" + def apply_inflections(word, rules) + result = word.to_s.dup - if word.empty? || inflections.uncountables.uncountable?(result) - result - else - rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) } - result + if word.empty? || inflections.uncountables.uncountable?(result) + result + else + rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) } + result + end end - end end end diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb index 871cfb8a72..85fa83c803 100644 --- a/activesupport/lib/active_support/inflector/transliterate.rb +++ b/activesupport/lib/active_support/inflector/transliterate.rb @@ -1,9 +1,8 @@ -require 'active_support/core_ext/string/multibyte' -require 'active_support/i18n' +require "active_support/core_ext/string/multibyte" +require "active_support/i18n" module ActiveSupport module Inflector - # Replaces non-ASCII characters with an ASCII approximation, or if none # exists, a replacement character which defaults to "?". # @@ -60,7 +59,7 @@ module ActiveSupport def transliterate(string, replacement = "?".freeze) I18n.transliterate(ActiveSupport::Multibyte::Unicode.normalize( ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c), - :replacement => replacement) + replacement: replacement) end # Replaces special characters in a string so that it may be used as part of @@ -79,7 +78,7 @@ module ActiveSupport # parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth" # parameterize("^trés|Jolie-- ", preserve_case: true) # => "tres-Jolie" # - def parameterize(string, sep = :unused, separator: '-', preserve_case: false) + def parameterize(string, sep = :unused, separator: "-", preserve_case: false) unless sep == :unused ActiveSupport::Deprecation.warn("Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '#{sep}'` instead.") separator = sep @@ -102,9 +101,9 @@ module ActiveSupport # No more than one of the separator in a row. parameterized_string.gsub!(re_duplicate_separator, separator) # Remove leading/trailing separator. - parameterized_string.gsub!(re_leading_trailing_separator, ''.freeze) + parameterized_string.gsub!(re_leading_trailing_separator, "".freeze) end - + parameterized_string.downcase! unless preserve_case parameterized_string end diff --git a/activesupport/lib/active_support/json.rb b/activesupport/lib/active_support/json.rb index 3e1d9b1d33..da938d1555 100644 --- a/activesupport/lib/active_support/json.rb +++ b/activesupport/lib/active_support/json.rb @@ -1,2 +1,2 @@ -require 'active_support/json/decoding' -require 'active_support/json/encoding' +require "active_support/json/decoding" +require "active_support/json/encoding" diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb index 2932954f03..f487fa0c65 100644 --- a/activesupport/lib/active_support/json/decoding.rb +++ b/activesupport/lib/active_support/json/decoding.rb @@ -1,6 +1,6 @@ -require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/core_ext/module/delegation' -require 'json' +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/module/delegation" +require "json" module ActiveSupport # Look for and parse json strings that look like ISO 8601 times. @@ -8,7 +8,8 @@ module ActiveSupport module JSON # matches YAML-formatted dates - DATE_REGEX = /^(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?))$/ + DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/ + DATETIME_REGEX = /^(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?)?)$/ class << self # Parses a JSON string (JavaScript Object Notation) into a hash. @@ -48,7 +49,13 @@ module ActiveSupport nil when DATE_REGEX begin - DateTime.parse(data) + Date.parse(data) + rescue ArgumentError + data + end + when DATETIME_REGEX + begin + Time.zone.parse(data) rescue ArgumentError data end diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index 031c5e9339..cee731417f 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -1,5 +1,5 @@ -require 'active_support/core_ext/object/json' -require 'active_support/core_ext/module/delegation' +require "active_support/core_ext/object/json" +require "active_support/core_ext/module/delegation" module ActiveSupport class << self @@ -7,7 +7,7 @@ module ActiveSupport :time_precision, :time_precision=, :escape_html_entities_in_json, :escape_html_entities_in_json=, :json_encoder, :json_encoder=, - :to => :'ActiveSupport::JSON::Encoding' + to: :'ActiveSupport::JSON::Encoding' end module JSON @@ -40,9 +40,9 @@ module ActiveSupport ESCAPED_CHARS = { "\u2028" => '\u2028', "\u2029" => '\u2029', - '>' => '\u003e', - '<' => '\u003c', - '&' => '\u0026', + ">" => '\u003e', + "<" => '\u003c', + "&" => '\u0026', } ESCAPE_REGEX_WITH_HTML_ENTITIES = /[\u2028\u2029><&]/u diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb index 7f73f9ddfc..1064398d8c 100644 --- a/activesupport/lib/active_support/key_generator.rb +++ b/activesupport/lib/active_support/key_generator.rb @@ -1,5 +1,5 @@ -require 'concurrent/map' -require 'openssl' +require "concurrent/map" +require "openssl" module ActiveSupport # KeyGenerator is a simple wrapper around OpenSSL's implementation of PBKDF2. @@ -31,11 +31,9 @@ module ActiveSupport @cache_keys = Concurrent::Map.new end - # Returns a derived key suitable for use. The default key_size is chosen - # to be compatible with the default settings of ActiveSupport::MessageVerifier. - # i.e. OpenSSL::Digest::SHA1#block_length - def generate_key(salt, key_size=64) - @cache_keys["#{salt}#{key_size}"] ||= @key_generator.generate_key(salt, key_size) + # Returns a derived key suitable for use. + def generate_key(*args) + @cache_keys[args.join] ||= @key_generator.generate_key(*args) end end @@ -53,21 +51,21 @@ module ActiveSupport private - # To prevent users from using something insecure like "Password" we make sure that the - # secret they've provided is at least 30 characters in length. - def ensure_secret_secure(secret) - if secret.blank? - raise ArgumentError, "A secret is required to generate an integrity hash " \ - "for cookie session data. Set a secret_key_base of at least " \ - "#{SECRET_MIN_LENGTH} characters in config/secrets.yml." - end + # To prevent users from using something insecure like "Password" we make sure that the + # secret they've provided is at least 30 characters in length. + def ensure_secret_secure(secret) + if secret.blank? + raise ArgumentError, "A secret is required to generate an integrity hash " \ + "for cookie session data. Set a secret_key_base of at least " \ + "#{SECRET_MIN_LENGTH} characters in config/secrets.yml." + end - if secret.length < SECRET_MIN_LENGTH - raise ArgumentError, "Secret should be something secure, " \ - "like \"#{SecureRandom.hex(16)}\". The value you " \ - "provided, \"#{secret}\", is shorter than the minimum length " \ - "of #{SECRET_MIN_LENGTH} characters." + if secret.length < SECRET_MIN_LENGTH + raise ArgumentError, "Secret should be something secure, " \ + "like \"#{SecureRandom.hex(16)}\". The value you " \ + "provided, \"#{secret}\", is shorter than the minimum length " \ + "of #{SECRET_MIN_LENGTH} characters." + end end - end end end diff --git a/activesupport/lib/active_support/lazy_load_hooks.rb b/activesupport/lib/active_support/lazy_load_hooks.rb index e2b8f0f648..ae1897b886 100644 --- a/activesupport/lib/active_support/lazy_load_hooks.rb +++ b/activesupport/lib/active_support/lazy_load_hooks.rb @@ -15,34 +15,44 @@ module ActiveSupport # end # end # - # When the entirety of +activerecord/lib/active_record/base.rb+ has been + # When the entirety of +ActiveRecord::Base+ has been # evaluated then +run_load_hooks+ is invoked. The very last line of - # +activerecord/lib/active_record/base.rb+ is: + # +ActiveRecord::Base+ is: # # ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base) - @load_hooks = Hash.new { |h,k| h[k] = [] } - @loaded = Hash.new { |h,k| h[k] = [] } - - def self.on_load(name, options = {}, &block) - @loaded[name].each do |base| - execute_hook(base, options, block) + module LazyLoadHooks + def self.extended(base) # :nodoc: + base.class_eval do + @load_hooks = Hash.new { |h,k| h[k] = [] } + @loaded = Hash.new { |h,k| h[k] = [] } + end end - @load_hooks[name] << [block, options] - end + # Declares a block that will be executed when a Rails component is fully + # loaded. + def on_load(name, options = {}, &block) + @loaded[name].each do |base| + execute_hook(base, options, block) + end - def self.execute_hook(base, options, block) - if options[:yield] - block.call(base) - else - base.instance_eval(&block) + @load_hooks[name] << [block, options] end - end - def self.run_load_hooks(name, base = Object) - @loaded[name] << base - @load_hooks[name].each do |hook, options| - execute_hook(base, options, hook) + def execute_hook(base, options, block) + if options[:yield] + block.call(base) + else + base.instance_eval(&block) + end + end + + def run_load_hooks(name, base = Object) + @loaded[name] << base + @load_hooks[name].each do |hook, options| + execute_hook(base, options, hook) + end end end + + extend LazyLoadHooks end diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb index e782cd2d4b..e5812d75d0 100644 --- a/activesupport/lib/active_support/log_subscriber.rb +++ b/activesupport/lib/active_support/log_subscriber.rb @@ -1,6 +1,6 @@ -require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/core_ext/class/attribute' -require 'active_support/subscriber' +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/class/attribute" +require "active_support/subscriber" module ActiveSupport # ActiveSupport::LogSubscriber is an object set to consume diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb index 588ed67c81..a7e30632f6 100644 --- a/activesupport/lib/active_support/log_subscriber/test_helper.rb +++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb @@ -1,6 +1,6 @@ -require 'active_support/log_subscriber' -require 'active_support/logger' -require 'active_support/notifications' +require "active_support/log_subscriber" +require "active_support/logger" +require "active_support/notifications" module ActiveSupport class LogSubscriber @@ -62,11 +62,11 @@ module ActiveSupport end def method_missing(level, message = nil) - if block_given? - @logged[level] << yield - else - @logged[level] << message - end + if block_given? + @logged[level] << yield + else + @logged[level] << message + end end def logged(level) diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb index 7626b28108..3ba6461b57 100644 --- a/activesupport/lib/active_support/logger.rb +++ b/activesupport/lib/active_support/logger.rb @@ -1,8 +1,10 @@ -require 'active_support/logger_silence' -require 'logger' +require "active_support/logger_silence" +require "active_support/logger_thread_safe_level" +require "logger" module ActiveSupport class Logger < ::Logger + include ActiveSupport::LoggerThreadSafeLevel include LoggerSilence # Returns true if the logger destination matches one of the sources @@ -48,6 +50,29 @@ module ActiveSupport logger.level = level super(level) end + + define_method(:local_level=) do |level| + logger.local_level = level if logger.respond_to?(:local_level=) + super(level) if respond_to?(:local_level=) + end + + define_method(:silence) do |level = Logger::ERROR, &block| + if logger.respond_to?(:silence) + logger.silence(level) do + if respond_to?(:silence) + super(level, &block) + else + block.call(self) + end + end + else + if respond_to?(:silence) + super(level, &block) + else + block.call(self) + end + end + end end end diff --git a/activesupport/lib/active_support/logger_silence.rb b/activesupport/lib/active_support/logger_silence.rb index 125d81d973..632994cf50 100644 --- a/activesupport/lib/active_support/logger_silence.rb +++ b/activesupport/lib/active_support/logger_silence.rb @@ -1,45 +1,28 @@ -require 'active_support/concern' -require 'active_support/core_ext/module/attribute_accessors' -require 'concurrent' +require "active_support/concern" +require "active_support/core_ext/module/attribute_accessors" +require "concurrent" module LoggerSilence extend ActiveSupport::Concern included do cattr_accessor :silencer - attr_reader :local_levels self.silencer = true end - def after_initialize - @local_levels = Concurrent::Map.new(:initial_capacity => 2) - end - - def local_log_id - Thread.current.__id__ - end - - def level - local_levels[local_log_id] || super - end - # Silences the logger for the duration of the block. def silence(temporary_level = Logger::ERROR) if silencer begin - old_local_level = local_levels[local_log_id] - local_levels[local_log_id] = temporary_level + old_local_level = local_level + self.local_level = temporary_level yield self ensure - if old_local_level - local_levels[local_log_id] = old_local_level - else - local_levels.delete(local_log_id) - end + self.local_level = old_local_level end else yield self end end -end
\ No newline at end of file +end diff --git a/activesupport/lib/active_support/logger_thread_safe_level.rb b/activesupport/lib/active_support/logger_thread_safe_level.rb new file mode 100644 index 0000000000..7fb175dea6 --- /dev/null +++ b/activesupport/lib/active_support/logger_thread_safe_level.rb @@ -0,0 +1,31 @@ +require "active_support/concern" + +module ActiveSupport + module LoggerThreadSafeLevel # :nodoc: + extend ActiveSupport::Concern + + def after_initialize + @local_levels = Concurrent::Map.new(initial_capacity: 2) + end + + def local_log_id + Thread.current.__id__ + end + + def local_level + @local_levels[local_log_id] + end + + def local_level=(level) + if level + @local_levels[local_log_id] = level + else + @local_levels.delete(local_log_id) + end + end + + def level + local_level || super + end + end +end diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index 2dde01c844..7b33dc3481 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -1,6 +1,7 @@ -require 'openssl' -require 'base64' -require 'active_support/core_ext/array/extract_options' +require "openssl" +require "base64" +require "active_support/core_ext/array/extract_options" +require "active_support/message_verifier" module ActiveSupport # MessageEncryptor is a simple way to encrypt values which get stored @@ -18,6 +19,8 @@ module ActiveSupport # 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" + module NullSerializer #:nodoc: def self.load(value) value @@ -28,6 +31,16 @@ module ActiveSupport end end + module NullVerifier #:nodoc: + def self.verify(value) + value + end + + def self.generate(value) + value + end + end + class InvalidMessage < StandardError; end OpenSSLCipherError = OpenSSL::Cipher::CipherError @@ -40,15 +53,17 @@ module ActiveSupport # Options: # * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by # <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-cbc'. - # * <tt>:digest</tt> - String of digest to use for signing. Default is +SHA1+. + # * <tt>:digest</tt> - String of digest to use for signing. Default is + # +SHA1+. Ignored when using an AEAD cipher like 'aes-256-gcm'. # * <tt>:serializer</tt> - Object serializer to use. Default is +Marshal+. def initialize(secret, *signature_key_or_options) options = signature_key_or_options.extract_options! sign_secret = signature_key_or_options.first @secret = secret @sign_secret = sign_secret - @cipher = options[:cipher] || 'aes-256-cbc' - @verifier = MessageVerifier.new(@sign_secret || @secret, digest: options[:digest] || 'SHA1', serializer: NullSerializer) + @cipher = options[:cipher] || "aes-256-cbc" + @digest = options[:digest] || "SHA1" unless aead_mode? + @verifier = resolve_verifier @serializer = options[:serializer] || Marshal end @@ -64,44 +79,73 @@ module ActiveSupport _decrypt(verifier.verify(value)) end + # Given a cipher, returns the key length of the cipher to help generate the key of desired size + def self.key_len(cipher = DEFAULT_CIPHER) + OpenSSL::Cipher.new(cipher).key_len + end + private - def _encrypt(value) - cipher = new_cipher - cipher.encrypt - cipher.key = @secret + def _encrypt(value) + cipher = new_cipher + cipher.encrypt + cipher.key = @secret - # Rely on OpenSSL for the initialization vector - iv = cipher.random_iv + # Rely on OpenSSL for the initialization vector + iv = cipher.random_iv + cipher.auth_data = "" if aead_mode? - encrypted_data = cipher.update(@serializer.dump(value)) - encrypted_data << cipher.final + encrypted_data = cipher.update(@serializer.dump(value)) + encrypted_data << cipher.final - "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}" - end - - def _decrypt(encrypted_message) - cipher = new_cipher - encrypted_data, iv = encrypted_message.split("--".freeze).map {|v| ::Base64.strict_decode64(v)} + blob = "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}" + blob << "--#{::Base64.strict_encode64 cipher.auth_tag}" if aead_mode? + blob + end - cipher.decrypt - cipher.key = @secret - cipher.iv = iv + def _decrypt(encrypted_message) + cipher = new_cipher + encrypted_data, iv, auth_tag = encrypted_message.split("--".freeze).map { |v| ::Base64.strict_decode64(v) } + + # Currently the OpenSSL bindings do not raise an error if auth_tag is + # truncated, which would allow an attacker to easily forge it. See + # https://github.com/ruby/openssl/issues/63 + raise InvalidMessage if aead_mode? && auth_tag.bytes.length != 16 + + cipher.decrypt + cipher.key = @secret + cipher.iv = iv + if aead_mode? + cipher.auth_tag = auth_tag + cipher.auth_data = "" + end + + decrypted_data = cipher.update(encrypted_data) + decrypted_data << cipher.final + + @serializer.load(decrypted_data) + rescue OpenSSLCipherError, TypeError, ArgumentError + raise InvalidMessage + end - decrypted_data = cipher.update(encrypted_data) - decrypted_data << cipher.final + def new_cipher + OpenSSL::Cipher.new(@cipher) + end - @serializer.load(decrypted_data) - rescue OpenSSLCipherError, TypeError, ArgumentError - raise InvalidMessage - end + def verifier + @verifier + end - def new_cipher - OpenSSL::Cipher::Cipher.new(@cipher) - end + def aead_mode? + @aead_mode ||= new_cipher.authenticated? + end - def verifier - @verifier - end + def resolve_verifier + if aead_mode? + NullVerifier + else + MessageVerifier.new(@sign_secret || @secret, digest: @digest, serializer: NullSerializer) + end + end end end diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index 854029bf83..8419e858c6 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -1,6 +1,6 @@ -require 'base64' -require 'active_support/core_ext/object/blank' -require 'active_support/security_utils' +require "base64" +require "active_support/core_ext/object/blank" +require "active_support/security_utils" module ActiveSupport # +MessageVerifier+ makes it easy to generate and verify messages which are @@ -24,13 +24,19 @@ module ActiveSupport # hash upon initialization: # # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', serializer: YAML) + # + # +MessageVerifier+ creates HMAC signatures using SHA1 hash algorithm by default. + # If you want to use a different hash algorithm, you can change it by providing + # `:digest` key as an option while initializing the verifier: + # + # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', digest: 'SHA256') class MessageVerifier class InvalidSignature < StandardError; end def initialize(secret, options = {}) - raise ArgumentError, 'Secret should not be nil.' unless secret + raise ArgumentError, "Secret should not be nil." unless secret @secret = secret - @digest = options[:digest] || 'SHA1' + @digest = options[:digest] || "SHA1" @serializer = options[:serializer] || Marshal end @@ -77,7 +83,7 @@ module ActiveSupport data = signed_message.split("--".freeze)[0] @serializer.load(decode(data)) rescue ArgumentError => argument_error - return if argument_error.message =~ %r{invalid base64} + return if argument_error.message.include?("invalid base64") raise end end @@ -121,7 +127,7 @@ module ActiveSupport end def generate_digest(data) - require 'openssl' unless defined?(OpenSSL) + require "openssl" unless defined?(OpenSSL) OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data) end end diff --git a/activesupport/lib/active_support/multibyte.rb b/activesupport/lib/active_support/multibyte.rb index ffebd9a60b..f7c7befee0 100644 --- a/activesupport/lib/active_support/multibyte.rb +++ b/activesupport/lib/active_support/multibyte.rb @@ -1,7 +1,7 @@ module ActiveSupport #:nodoc: module Multibyte - autoload :Chars, 'active_support/multibyte/chars' - autoload :Unicode, 'active_support/multibyte/unicode' + autoload :Chars, "active_support/multibyte/chars" + autoload :Unicode, "active_support/multibyte/unicode" # The proxy class returned when calling mb_chars. You can use this accessor # to configure your own proxy class so you can support other encodings. See diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index 707cf200b5..938e4ebb72 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -1,7 +1,8 @@ -require 'active_support/json' -require 'active_support/core_ext/string/access' -require 'active_support/core_ext/string/behavior' -require 'active_support/core_ext/module/delegation' +require "active_support/json" +require "active_support/core_ext/string/access" +require "active_support/core_ext/string/behavior" +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/regexp" module ActiveSupport #:nodoc: module Multibyte #:nodoc: @@ -45,7 +46,7 @@ module ActiveSupport #:nodoc: alias to_s wrapped_string alias to_str wrapped_string - delegate :<=>, :=~, :acts_like_string?, :to => :wrapped_string + delegate :<=>, :=~, :acts_like_string?, to: :wrapped_string # Creates a new Chars instance by wrapping _string_. def initialize(string) @@ -56,7 +57,7 @@ module ActiveSupport #:nodoc: # Forward all undefined methods to the wrapped string. def method_missing(method, *args, &block) result = @wrapped_string.__send__(method, *args, &block) - if method.to_s =~ /!$/ + if /!$/.match?(method) self if result else result.kind_of?(String) ? chars(result) : result @@ -105,7 +106,7 @@ module ActiveSupport #:nodoc: # # 'Café'.mb_chars.reverse.to_s # => 'éfaC' def reverse - chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack('U*')) + chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack("U*")) end # Limits the byte size of the string to a number of bytes without breaking @@ -142,7 +143,7 @@ module ActiveSupport #:nodoc: # # 'über'.mb_chars.capitalize.to_s # => "Über" def capitalize - (slice(0) || chars('')).upcase + (slice(1..-1) || chars('')).downcase + (slice(0) || chars("")).upcase + (slice(1..-1) || chars("")).downcase end # Capitalizes the first letter of every word, when possible. @@ -150,7 +151,7 @@ module ActiveSupport #:nodoc: # "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró" # "日本語".mb_chars.titleize # => "日本語" def titleize - chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1)}) + chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1) }) end alias_method :titlecase, :titleize @@ -170,7 +171,7 @@ module ActiveSupport #:nodoc: # 'é'.length # => 2 # 'é'.mb_chars.decompose.to_s.length # => 3 def decompose - chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack('U*')) + chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack("U*")) end # Performs composition on all the characters. @@ -178,7 +179,7 @@ module ActiveSupport #:nodoc: # 'é'.length # => 3 # 'é'.mb_chars.compose.to_s.length # => 2 def compose - chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack('U*')) + chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack("U*")) end # Returns the number of grapheme clusters in the string. @@ -213,10 +214,10 @@ module ActiveSupport #:nodoc: def translate_offset(byte_offset) #:nodoc: return nil if byte_offset.nil? - return 0 if @wrapped_string == '' + return 0 if @wrapped_string == "" begin - @wrapped_string.byteslice(0...byte_offset).unpack('U*').length + @wrapped_string.byteslice(0...byte_offset).unpack("U*").length rescue ArgumentError byte_offset -= 1 retry diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index 72b20fff06..217919ccb8 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -1,7 +1,6 @@ module ActiveSupport module Multibyte module Unicode - extend self # A list of all available normalization forms. @@ -10,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 = "8.0.0" # The default normalization used for operations that require # normalization. It can be set to any of the normalizations @@ -31,36 +30,6 @@ module ActiveSupport HANGUL_NCOUNT = HANGUL_VCOUNT * HANGUL_TCOUNT HANGUL_SCOUNT = 11172 HANGUL_SLAST = HANGUL_SBASE + HANGUL_SCOUNT - HANGUL_JAMO_FIRST = 0x1100 - HANGUL_JAMO_LAST = 0x11FF - - # All the unicode whitespace - WHITESPACE = [ - (0x0009..0x000D).to_a, # White_Space # Cc [5] <control-0009>..<control-000D> - 0x0020, # White_Space # Zs SPACE - 0x0085, # White_Space # Cc <control-0085> - 0x00A0, # White_Space # Zs NO-BREAK SPACE - 0x1680, # White_Space # Zs OGHAM SPACE MARK - (0x2000..0x200A).to_a, # White_Space # Zs [11] EN QUAD..HAIR SPACE - 0x2028, # White_Space # Zl LINE SEPARATOR - 0x2029, # White_Space # Zp PARAGRAPH SEPARATOR - 0x202F, # White_Space # Zs NARROW NO-BREAK SPACE - 0x205F, # White_Space # Zs MEDIUM MATHEMATICAL SPACE - 0x3000, # White_Space # Zs IDEOGRAPHIC SPACE - ].flatten.freeze - - # BOM (byte order mark) can also be seen as whitespace, it's a - # non-rendering character used to distinguish between little and big - # endian. This is not an issue in utf-8, so it must be ignored. - LEADERS_AND_TRAILERS = WHITESPACE + [65279] # ZERO-WIDTH NO-BREAK SPACE aka BOM - - # Returns a regular expression pattern that matches the passed Unicode - # codepoints. - def self.codepoints_to_pattern(array_of_codepoints) #:nodoc: - array_of_codepoints.collect{ |e| [e].pack 'U*'.freeze }.join('|'.freeze) - end - TRAILERS_PAT = /(#{codepoints_to_pattern(LEADERS_AND_TRAILERS)})+\Z/u - LEADERS_PAT = /\A(#{codepoints_to_pattern(LEADERS_AND_TRAILERS)})+/u # Detect whether the codepoint is in a certain character class. Returns # +true+ when it's in the specified character class and +false+ otherwise. @@ -90,25 +59,25 @@ module ActiveSupport should_break = # GB3. CR X LF - if previous == database.boundary[:cr] and current == database.boundary[:lf] + if previous == database.boundary[:cr] && current == database.boundary[:lf] false # GB4. (Control|CR|LF) ÷ - elsif previous and in_char_class?(previous, [:control,:cr,:lf]) + elsif previous && in_char_class?(previous, [:control,:cr,:lf]) true # GB5. ÷ (Control|CR|LF) elsif in_char_class?(current, [:control,:cr,:lf]) true # GB6. L X (L|V|LV|LVT) - elsif database.boundary[:l] === previous and in_char_class?(current, [:l,:v,:lv,:lvt]) + elsif database.boundary[:l] === previous && in_char_class?(current, [:l,:v,:lv,:lvt]) false # GB7. (LV|V) X (V|T) - elsif in_char_class?(previous, [:lv,:v]) and in_char_class?(current, [:v,:t]) + elsif in_char_class?(previous, [:lv,:v]) && in_char_class?(current, [:v,:t]) false # GB8. (LVT|T) X (T) - elsif in_char_class?(previous, [:lvt,:t]) and database.boundary[:t] === current + elsif in_char_class?(previous, [:lvt,:t]) && database.boundary[:t] === current false # GB8a. Regional_Indicator X Regional_Indicator - elsif database.boundary[:regional_indicator] === previous and database.boundary[:regional_indicator] === current + elsif database.boundary[:regional_indicator] === previous && database.boundary[:regional_indicator] === current false # GB9. X Extend elsif database.boundary[:extend] === current @@ -136,7 +105,7 @@ module ActiveSupport # # Unicode.pack_graphemes(Unicode.unpack_graphemes('कà¥à¤·à¤¿')) # => 'कà¥à¤·à¤¿' def pack_graphemes(unpacked) - unpacked.flatten.pack('U*') + unpacked.flatten.pack("U*") end # Re-order codepoints so the string becomes canonical. @@ -159,7 +128,7 @@ module ActiveSupport def decompose(type, codepoints) codepoints.inject([]) do |decomposed, cp| # if it's a hangul syllable starter character - if HANGUL_SBASE <= cp and cp < HANGUL_SLAST + if HANGUL_SBASE <= cp && cp < HANGUL_SLAST sindex = cp - HANGUL_SBASE ncp = [] # new codepoints ncp << HANGUL_LBASE + sindex / HANGUL_NCOUNT @@ -168,7 +137,7 @@ module ActiveSupport ncp << (HANGUL_TBASE + tindex) unless tindex == 0 decomposed.concat ncp # if the codepoint is decomposable in with the current decomposition type - elsif (ncp = database.codepoints[cp].decomp_mapping) and (!database.codepoints[cp].decomp_type || type == :compatibility) + elsif (ncp = database.codepoints[cp].decomp_mapping) && (!database.codepoints[cp].decomp_type || type == :compatibility) decomposed.concat decompose(type, ncp.dup) else decomposed << cp @@ -187,11 +156,11 @@ module ActiveSupport pos += 1 lindex = starter_char - HANGUL_LBASE # -- Hangul - if 0 <= lindex and lindex < HANGUL_LCOUNT + if 0 <= lindex && lindex < HANGUL_LCOUNT vindex = codepoints[starter_pos+1] - HANGUL_VBASE rescue vindex = -1 - if 0 <= vindex and vindex < HANGUL_VCOUNT + if 0 <= vindex && vindex < HANGUL_VCOUNT tindex = codepoints[starter_pos+2] - HANGUL_TBASE rescue tindex = -1 - if 0 <= tindex and tindex < HANGUL_TCOUNT + if 0 <= tindex && tindex < HANGUL_TCOUNT j = starter_pos + 2 eoa -= 2 else @@ -259,7 +228,7 @@ module ActiveSupport reader = Encoding::Converter.new(Encoding::UTF_8, Encoding::UTF_16LE) source = string.dup - out = ''.force_encoding(Encoding::UTF_16LE) + out = "".force_encoding(Encoding::UTF_16LE) loop do reader.primitive_convert(source, out) @@ -287,17 +256,17 @@ module ActiveSupport # See http://www.unicode.org/reports/tr15, Table 1 codepoints = string.codepoints.to_a case form - when :d - reorder_characters(decompose(:canonical, codepoints)) - when :c - compose(reorder_characters(decompose(:canonical, codepoints))) - when :kd - reorder_characters(decompose(:compatibility, codepoints)) - when :kc - compose(reorder_characters(decompose(:compatibility, codepoints))) + when :d + reorder_characters(decompose(:canonical, codepoints)) + when :c + compose(reorder_characters(decompose(:canonical, codepoints))) + when :kd + reorder_characters(decompose(:compatibility, codepoints)) + when :kc + compose(reorder_characters(decompose(:compatibility, codepoints))) else - raise ArgumentError, "#{form} is not a valid normalization variant", caller - end.pack('U*'.freeze) + raise ArgumentError, "#{form} is not a valid normalization variant", caller + end.pack("U*".freeze) end def downcase(string) @@ -356,7 +325,7 @@ module ActiveSupport # UnicodeDatabase. def load begin - @codepoints, @composition_exclusion, @composition_map, @boundary, @cp1252 = File.open(self.class.filename, 'rb') { |f| Marshal.load f.read } + @codepoints, @composition_exclusion, @composition_map, @boundary, @cp1252 = File.open(self.class.filename, "rb") { |f| Marshal.load f.read } rescue => e raise IOError.new("Couldn't load the Unicode tables for UTF8Handler (#{e.message}), ActiveSupport::Multibyte is unusable") end @@ -378,7 +347,7 @@ module ActiveSupport # Returns the directory in which the data files are stored. def self.dirname - File.dirname(__FILE__) + '/../values/' + File.dirname(__FILE__) + "/../values/" end # Returns the filename for the data file for this version. @@ -389,25 +358,25 @@ module ActiveSupport private - def apply_mapping(string, mapping) #:nodoc: - database.codepoints - string.each_codepoint.map do |codepoint| - cp = database.codepoints[codepoint] - if cp and (ncp = cp.send(mapping)) and ncp > 0 - ncp - else - codepoint - end - end.pack('U*') - end + def apply_mapping(string, mapping) #:nodoc: + database.codepoints + string.each_codepoint.map do |codepoint| + cp = database.codepoints[codepoint] + if cp && (ncp = cp.send(mapping)) && ncp > 0 + ncp + else + codepoint + end + end.pack("U*") + end - def recode_windows1252_chars(string) - string.encode(Encoding::UTF_8, Encoding::Windows_1252, invalid: :replace, undef: :replace) - end + def recode_windows1252_chars(string) + string.encode(Encoding::UTF_8, Encoding::Windows_1252, invalid: :replace, undef: :replace) + end - def database - @database ||= UnicodeDatabase.new - end + def database + @database ||= UnicodeDatabase.new + end end end end diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index 823d68e507..bae5f067ae 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -1,6 +1,6 @@ -require 'active_support/notifications/instrumenter' -require 'active_support/notifications/fanout' -require 'active_support/per_thread_registry' +require "active_support/notifications/instrumenter" +require "active_support/notifications/fanout" +require "active_support/per_thread_registry" module ActiveSupport # = Notifications diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index c53f9c1039..9da115f552 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -1,5 +1,5 @@ -require 'mutex_m' -require 'concurrent/map' +require "mutex_m" +require "concurrent/map" module ActiveSupport module Notifications @@ -68,7 +68,7 @@ module ActiveSupport module Subscribers # :nodoc: def self.new(pattern, listener) - if listener.respond_to?(:start) and listener.respond_to?(:finish) + if listener.respond_to?(:start) && listener.respond_to?(:finish) subscriber = Evented.new pattern, listener else subscriber = Timed.new pattern, listener diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb index 91f94cb2d7..23262d5398 100644 --- a/activesupport/lib/active_support/notifications/instrumenter.rb +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -1,4 +1,4 @@ -require 'securerandom' +require "securerandom" module ActiveSupport module Notifications @@ -44,9 +44,9 @@ module ActiveSupport private - def unique_id - SecureRandom.hex(10) - end + def unique_id + SecureRandom.hex(10) + end end class Event diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb index 64d9e71f37..7a49bbb960 100644 --- a/activesupport/lib/active_support/number_helper.rb +++ b/activesupport/lib/active_support/number_helper.rb @@ -15,7 +15,7 @@ module ActiveSupport extend self - # Formats a +number+ into a US phone number (e.g., (555) + # Formats a +number+ into a phone number (US by default e.g., (555) # 123-9876). You can customize the format in the +options+ hash. # # ==== Options @@ -27,19 +27,26 @@ module ActiveSupport # end of the generated number. # * <tt>:country_code</tt> - Sets the country code for the phone # number. + # * <tt>:pattern</tt> - Specifies how the number is divided into three + # groups with the custom regexp to override the default format. # ==== Examples # - # number_to_phone(5551234) # => 555-1234 - # number_to_phone('5551234') # => 555-1234 - # number_to_phone(1235551234) # => 123-555-1234 - # number_to_phone(1235551234, area_code: true) # => (123) 555-1234 - # number_to_phone(1235551234, delimiter: ' ') # => 123 555 1234 - # number_to_phone(1235551234, area_code: true, extension: 555) # => (123) 555-1234 x 555 - # number_to_phone(1235551234, country_code: 1) # => +1-123-555-1234 - # number_to_phone('123a456') # => 123a456 + # number_to_phone(5551234) # => "555-1234" + # number_to_phone('5551234') # => "555-1234" + # number_to_phone(1235551234) # => "123-555-1234" + # number_to_phone(1235551234, area_code: true) # => "(123) 555-1234" + # number_to_phone(1235551234, delimiter: ' ') # => "123 555 1234" + # number_to_phone(1235551234, area_code: true, extension: 555) # => "(123) 555-1234 x 555" + # number_to_phone(1235551234, country_code: 1) # => "+1-123-555-1234" + # number_to_phone('123a456') # => "123a456" # # number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: '.') - # # => +1.123.555.1234 x 1343 + # # => "+1.123.555.1234 x 1343" + # + # 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})$/)) + # # => "133-1234-5678" def number_to_phone(number, options = {}) NumberToPhoneConverter.convert(number, options) end @@ -78,18 +85,18 @@ module ActiveSupport # # ==== Examples # - # number_to_currency(1234567890.50) # => $1,234,567,890.50 - # number_to_currency(1234567890.506) # => $1,234,567,890.51 - # number_to_currency(1234567890.506, precision: 3) # => $1,234,567,890.506 - # number_to_currency(1234567890.506, locale: :fr) # => 1 234 567 890,51 € - # number_to_currency('123a456') # => $123a456 + # number_to_currency(1234567890.50) # => "$1,234,567,890.50" + # number_to_currency(1234567890.506) # => "$1,234,567,890.51" + # number_to_currency(1234567890.506, precision: 3) # => "$1,234,567,890.506" + # number_to_currency(1234567890.506, locale: :fr) # => "1 234 567 890,51 €" + # number_to_currency('123a456') # => "$123a456" # # number_to_currency(-1234567890.50, negative_format: '(%u%n)') - # # => ($1,234,567,890.50) + # # => "($1,234,567,890.50)" # number_to_currency(1234567890.50, unit: '£', separator: ',', delimiter: '') - # # => £1234567890,50 + # # => "£1234567890,50" # number_to_currency(1234567890.50, unit: '£', separator: ',', delimiter: '', format: '%n %u') - # # => 1234567890,50 £ + # # => "1234567890,50 £" def number_to_currency(number, options = {}) NumberToCurrencyConverter.convert(number, options) end @@ -118,15 +125,15 @@ module ActiveSupport # # ==== Examples # - # number_to_percentage(100) # => 100.000% - # number_to_percentage('98') # => 98.000% - # number_to_percentage(100, precision: 0) # => 100% - # number_to_percentage(1000, delimiter: '.', separator: ',') # => 1.000,000% - # number_to_percentage(302.24398923423, precision: 5) # => 302.24399% - # number_to_percentage(1000, locale: :fr) # => 1000,000% - # number_to_percentage(1000, precision: nil) # => 1000% - # number_to_percentage('98a') # => 98a% - # number_to_percentage(100, format: '%n %') # => 100.000 % + # number_to_percentage(100) # => "100.000%" + # number_to_percentage('98') # => "98.000%" + # number_to_percentage(100, precision: 0) # => "100%" + # number_to_percentage(1000, delimiter: '.', separator: ',') # => "1.000,000%" + # number_to_percentage(302.24398923423, precision: 5) # => "302.24399%" + # number_to_percentage(1000, locale: :fr) # => "1000,000%" + # number_to_percentage(1000, precision: nil) # => "1000%" + # number_to_percentage('98a') # => "98a%" + # number_to_percentage(100, format: '%n %') # => "100.000 %" def number_to_percentage(number, options = {}) NumberToPercentageConverter.convert(number, options) end @@ -149,19 +156,19 @@ module ActiveSupport # # ==== Examples # - # number_to_delimited(12345678) # => 12,345,678 - # number_to_delimited('123456') # => 123,456 - # number_to_delimited(12345678.05) # => 12,345,678.05 - # number_to_delimited(12345678, delimiter: '.') # => 12.345.678 - # number_to_delimited(12345678, delimiter: ',') # => 12,345,678 - # number_to_delimited(12345678.05, separator: ' ') # => 12,345,678 05 - # number_to_delimited(12345678.05, locale: :fr) # => 12 345 678,05 - # number_to_delimited('112a') # => 112a + # number_to_delimited(12345678) # => "12,345,678" + # number_to_delimited('123456') # => "123,456" + # number_to_delimited(12345678.05) # => "12,345,678.05" + # number_to_delimited(12345678, delimiter: '.') # => "12.345.678" + # number_to_delimited(12345678, delimiter: ',') # => "12,345,678" + # number_to_delimited(12345678.05, separator: ' ') # => "12,345,678 05" + # number_to_delimited(12345678.05, locale: :fr) # => "12 345 678,05" + # number_to_delimited('112a') # => "112a" # number_to_delimited(98765432.98, delimiter: ' ', separator: ',') - # # => 98 765 432,98 + # # => "98 765 432,98" # number_to_delimited("123456.78", # delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/) - # # => 1,23,456.78 + # # => "1,23,456.78" def number_to_delimited(number, options = {}) NumberToDelimitedConverter.convert(number, options) end @@ -190,22 +197,22 @@ module ActiveSupport # # ==== Examples # - # number_to_rounded(111.2345) # => 111.235 - # number_to_rounded(111.2345, precision: 2) # => 111.23 - # number_to_rounded(13, precision: 5) # => 13.00000 - # number_to_rounded(389.32314, precision: 0) # => 389 - # number_to_rounded(111.2345, significant: true) # => 111 - # number_to_rounded(111.2345, precision: 1, significant: true) # => 100 - # number_to_rounded(13, precision: 5, significant: true) # => 13.000 - # number_to_rounded(13, precision: nil) # => 13 - # number_to_rounded(111.234, locale: :fr) # => 111,234 + # number_to_rounded(111.2345) # => "111.235" + # number_to_rounded(111.2345, precision: 2) # => "111.23" + # number_to_rounded(13, precision: 5) # => "13.00000" + # number_to_rounded(389.32314, precision: 0) # => "389" + # number_to_rounded(111.2345, significant: true) # => "111" + # number_to_rounded(111.2345, precision: 1, significant: true) # => "100" + # number_to_rounded(13, precision: 5, significant: true) # => "13.000" + # number_to_rounded(13, precision: nil) # => "13" + # number_to_rounded(111.234, locale: :fr) # => "111,234" # # number_to_rounded(13, precision: 5, significant: true, strip_insignificant_zeros: true) - # # => 13 + # # => "13" # - # number_to_rounded(389.32314, precision: 4, significant: true) # => 389.3 + # number_to_rounded(389.32314, precision: 4, significant: true) # => "389.3" # number_to_rounded(1111.2345, precision: 2, separator: ',', delimiter: '.') - # # => 1.111,23 + # # => "1.111,23" def number_to_rounded(number, options = {}) NumberToRoundedConverter.convert(number, options) end @@ -237,17 +244,17 @@ module ActiveSupport # # ==== Examples # - # number_to_human_size(123) # => 123 Bytes - # number_to_human_size(1234) # => 1.21 KB - # number_to_human_size(12345) # => 12.1 KB - # number_to_human_size(1234567) # => 1.18 MB - # number_to_human_size(1234567890) # => 1.15 GB - # number_to_human_size(1234567890123) # => 1.12 TB - # number_to_human_size(1234567890123456) # => 1.1 PB - # number_to_human_size(1234567890123456789) # => 1.07 EB - # number_to_human_size(1234567, precision: 2) # => 1.2 MB - # number_to_human_size(483989, precision: 2) # => 470 KB - # number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB + # number_to_human_size(123) # => "123 Bytes" + # number_to_human_size(1234) # => "1.21 KB" + # number_to_human_size(12345) # => "12.1 KB" + # number_to_human_size(1234567) # => "1.18 MB" + # number_to_human_size(1234567890) # => "1.15 GB" + # number_to_human_size(1234567890123) # => "1.12 TB" + # number_to_human_size(1234567890123456) # => "1.1 PB" + # number_to_human_size(1234567890123456789) # => "1.07 EB" + # number_to_human_size(1234567, precision: 2) # => "1.2 MB" + # number_to_human_size(483989, precision: 2) # => "470 KB" + # number_to_human_size(1234567, precision: 2, separator: ',') # => "1,2 MB" # number_to_human_size(1234567890123, precision: 5) # => "1.1228 TB" # number_to_human_size(524288000, precision: 5) # => "500 MB" def number_to_human_size(number, options = {}) diff --git a/activesupport/lib/active_support/number_helper/number_converter.rb b/activesupport/lib/active_support/number_helper/number_converter.rb index 9d976f1831..c485a6af63 100644 --- a/activesupport/lib/active_support/number_helper/number_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_converter.rb @@ -1,8 +1,8 @@ -require 'active_support/core_ext/big_decimal/conversions' -require 'active_support/core_ext/object/blank' -require 'active_support/core_ext/hash/keys' -require 'active_support/i18n' -require 'active_support/core_ext/class/attribute' +require "active_support/core_ext/big_decimal/conversions" +require "active_support/core_ext/object/blank" +require "active_support/core_ext/hash/keys" +require "active_support/i18n" +require "active_support/core_ext/class/attribute" module ActiveSupport module NumberHelper @@ -169,7 +169,7 @@ module ActiveSupport end def default_value(key) - key.split('.').reduce(DEFAULTS) { |defaults, k| defaults[k.to_sym] } + key.split(".").reduce(DEFAULTS) { |defaults, k| defaults[k.to_sym] } end def valid_float? #:nodoc: diff --git a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb index 57f40f33bf..0f9dce722f 100644 --- a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/numeric/inquiry' +require "active_support/core_ext/numeric/inquiry" module ActiveSupport module NumberHelper @@ -15,13 +15,13 @@ module ActiveSupport end rounded_number = NumberToRoundedConverter.convert(number, options) - format.gsub('%n'.freeze, rounded_number).gsub('%u'.freeze, options[:unit]) + format.gsub("%n".freeze, rounded_number).gsub("%u".freeze, options[:unit]) end private def absolute_value(number) - number.respond_to?(:abs) ? number.abs : number.sub(/\A-/, '') + number.respond_to?(:abs) ? number.abs : number.sub(/\A-/, "") end def options diff --git a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb index 45ae8f1a93..e3b35531b1 100644 --- a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb @@ -12,7 +12,7 @@ module ActiveSupport private def parts - left, right = number.to_s.split('.') + left, right = number.to_s.split(".".freeze) left.gsub!(delimiter_pattern) do |digit_to_delimit| "#{digit_to_delimit}#{options[:delimiter]}" end @@ -22,7 +22,6 @@ module ActiveSupport def delimiter_pattern options.fetch(:delimiter_pattern, DEFAULT_DELIMITER_REGEX) end - end end end diff --git a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb index 7a1f8171c0..695ee1dad3 100644 --- a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb @@ -25,22 +25,22 @@ module ActiveSupport exponent += 3 end unit = determine_unit(units, exponent) - format.gsub('%n'.freeze, rounded_number).gsub('%u'.freeze, unit).strip + format.gsub("%n".freeze, rounded_number).gsub("%u".freeze, unit).strip end private def format - options[:format] || translate_in_locale('human.decimal_units.format') + options[:format] || translate_in_locale("human.decimal_units.format") end def determine_unit(units, exponent) exp = DECIMAL_UNITS[exponent] case units when Hash - units[exp] || '' + units[exp] || "" when String, Symbol - I18n.translate("#{units}.#{exp}", :locale => options[:locale], :count => number.to_i) + I18n.translate("#{units}.#{exp}", locale: options[:locale], count: number.to_i) else translate_in_locale("human.decimal_units.units.#{exp}", count: number.to_i) end @@ -56,7 +56,7 @@ module ActiveSupport when Hash units when String, Symbol - I18n.translate(units.to_s, :locale => options[:locale], :raise => true) + I18n.translate(units.to_s, locale: options[:locale], raise: true) when nil translate_in_locale("human.decimal_units.units", raise: true) else diff --git a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb index a83b368b7f..78cb33aa62 100644 --- a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb @@ -8,7 +8,7 @@ module ActiveSupport def convert if opts.key?(:prefix) - ActiveSupport::Deprecation.warn('The :prefix option of `number_to_human_size` is deprecated and will be removed in Rails 5.1 with no replacement.') + ActiveSupport::Deprecation.warn("The :prefix option of `number_to_human_size` is deprecated and will be removed in Rails 5.1 with no replacement.") end @number = Float(number) @@ -24,21 +24,21 @@ module ActiveSupport human_size = number / (base ** exponent) number_to_format = NumberToRoundedConverter.convert(human_size, options) end - conversion_format.gsub('%n'.freeze, number_to_format).gsub('%u'.freeze, unit) + conversion_format.gsub("%n".freeze, number_to_format).gsub("%u".freeze, unit) end private def conversion_format - translate_number_value_with_default('human.storage_units.format', :locale => options[:locale], :raise => true) + translate_number_value_with_default("human.storage_units.format", locale: options[:locale], raise: true) end def unit - translate_number_value_with_default(storage_unit_key, :locale => options[:locale], :count => number.to_i, :raise => true) + translate_number_value_with_default(storage_unit_key, locale: options[:locale], count: number.to_i, raise: true) end def storage_unit_key - key_end = smaller_than_base? ? 'byte' : STORAGE_UNITS[exponent] + key_end = smaller_than_base? ? "byte" : STORAGE_UNITS[exponent] "human.storage_units.units.#{key_end}" end @@ -59,4 +59,3 @@ module ActiveSupport end end end - diff --git a/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb b/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb index 4c04d40c19..ac647ca9b7 100644 --- a/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb @@ -5,7 +5,7 @@ module ActiveSupport def convert rounded_number = NumberToRoundedConverter.convert(number, options) - options[:format].gsub('%n'.freeze, rounded_number) + options[:format].gsub("%n".freeze, rounded_number) end end end diff --git a/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb index af2ee56d91..c2612f9a9b 100644 --- a/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb @@ -18,12 +18,16 @@ module ActiveSupport end def convert_with_area_code(number) - number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3") + default_pattern = /(\d{1,3})(\d{3})(\d{4}$)/ + number.gsub!(regexp_pattern(default_pattern), + "(\\1) \\2#{delimiter}\\3") number end def convert_without_area_code(number) - number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3") + default_pattern = /(\d{0,3})(\d{3})(\d{4})$/ + number.gsub!(regexp_pattern(default_pattern), + "\\1#{delimiter}\\2#{delimiter}\\3") number.slice!(0, 1) if start_with_delimiter?(number) number end @@ -43,7 +47,10 @@ module ActiveSupport def phone_ext(ext) ext.blank? ? "" : " x #{ext}" end + + def regexp_pattern(default_pattern) + opts.fetch :pattern, default_pattern + end end end end - diff --git a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb index 981c562551..cfcb0045fb 100644 --- a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb @@ -29,9 +29,11 @@ module ActiveSupport formatted_string = if BigDecimal === rounded_number && rounded_number.finite? - s = rounded_number.to_s('F') + '0'*precision - a, b = s.split('.', 2) - a + '.' + b[0, precision] + s = rounded_number.to_s("F") + s << "0".freeze * precision + a, b = s.split(".".freeze, 2) + a << ".".freeze + a << b[0, precision] else "%00.#{precision}f" % rounded_number end @@ -72,7 +74,7 @@ module ActiveSupport def format_number(number) if strip_insignificant_zeros escaped_separator = Regexp.escape(options[:separator]) - number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '') + number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, "") else number end diff --git a/activesupport/lib/active_support/option_merger.rb b/activesupport/lib/active_support/option_merger.rb index dea84e437f..0f2caa98f2 100644 --- a/activesupport/lib/active_support/option_merger.rb +++ b/activesupport/lib/active_support/option_merger.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/hash/deep_merge' +require "active_support/core_ext/hash/deep_merge" module ActiveSupport class OptionMerger #:nodoc: diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb index b1658f0f27..793bc9e22d 100644 --- a/activesupport/lib/active_support/ordered_hash.rb +++ b/activesupport/lib/active_support/ordered_hash.rb @@ -1,7 +1,7 @@ -require 'yaml' +require "yaml" YAML.add_builtin_type("omap") do |type, val| - ActiveSupport::OrderedHash[val.map{ |v| v.to_a.first }] + ActiveSupport::OrderedHash[val.map { |v| v.to_a.first }] end module ActiveSupport @@ -25,7 +25,7 @@ module ActiveSupport end def encode_with(coder) - coder.represent_seq '!omap', map { |k,v| { k => v } } + coder.represent_seq "!omap", map { |k,v| { k => v } } end def select(*args, &block) diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb index 53a55bd986..14ed9049be 100644 --- a/activesupport/lib/active_support/ordered_options.rb +++ b/activesupport/lib/active_support/ordered_options.rb @@ -1,3 +1,5 @@ +require "active_support/core_ext/object/blank" + module ActiveSupport # Usually key value pairs are handled something like this: # @@ -36,10 +38,10 @@ module ActiveSupport def method_missing(name, *args) name_string = name.to_s - if name_string.chomp!('=') + if name_string.chomp!("=") self[name_string] = args.first else - bangs = name_string.chomp!('!') + bangs = name_string.chomp!("!") if bangs fetch(name_string.to_sym).presence || raise(KeyError.new("#{name_string} is blank.")) diff --git a/activesupport/lib/active_support/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb index 88e2b12cc7..9e6d8d4fd8 100644 --- a/activesupport/lib/active_support/per_thread_registry.rb +++ b/activesupport/lib/active_support/per_thread_registry.rb @@ -1,7 +1,7 @@ -require 'active_support/core_ext/module/delegation' +require "active_support/core_ext/module/delegation" module ActiveSupport - # NOTE: This approach has been deprecated for end-user code in favor of thread_mattr_accessor and friends. + # NOTE: This approach has been deprecated for end-user code in favor of {thread_mattr_accessor}[rdoc-ref:Module#thread_mattr_accessor] and friends. # Please use that approach instead. # # This module is used to encapsulate access to thread local variables. @@ -38,7 +38,7 @@ module ActiveSupport # If the class has an initializer, it must accept no arguments. module PerThreadRegistry def self.extended(object) - object.instance_variable_set '@per_thread_registry_key', object.name.freeze + object.instance_variable_set "@per_thread_registry_key", object.name.freeze end def instance diff --git a/activesupport/lib/active_support/rails.rb b/activesupport/lib/active_support/rails.rb index c8e3a4bf53..f6b018f0d3 100644 --- a/activesupport/lib/active_support/rails.rb +++ b/activesupport/lib/active_support/rails.rb @@ -9,19 +9,25 @@ # Rails and can change anytime. # Defines Object#blank? and Object#present?. -require 'active_support/core_ext/object/blank' +require "active_support/core_ext/object/blank" # Rails own autoload, eager_load, etc. -require 'active_support/dependencies/autoload' +require "active_support/dependencies/autoload" # Support for ClassMethods and the included macro. -require 'active_support/concern' +require "active_support/concern" # Defines Class#class_attribute. -require 'active_support/core_ext/class/attribute' +require "active_support/core_ext/class/attribute" # Defines Module#delegate. -require 'active_support/core_ext/module/delegation' +require "active_support/core_ext/module/delegation" # Defines ActiveSupport::Deprecation. -require 'active_support/deprecation' +require "active_support/deprecation" + +# Defines Regexp#match?. +# +# This should be removed when Rails needs Ruby 2.4 or later, and the require +# added where other Regexp extensions are being used (easy to grep). +require "active_support/core_ext/regexp" diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb index 845788b669..b875875afe 100644 --- a/activesupport/lib/active_support/railtie.rb +++ b/activesupport/lib/active_support/railtie.rb @@ -21,11 +21,11 @@ module ActiveSupport rescue TZInfo::DataSourceNotFound => e raise e.exception "tzinfo-data is not present. Please add gem 'tzinfo-data' to your Gemfile and run bundle install" end - require 'active_support/core_ext/time/zones' + require "active_support/core_ext/time/zones" zone_default = Time.find_zone!(app.config.time_zone) unless zone_default - raise 'Value assigned to config.time_zone not recognized. ' \ + raise "Value assigned to config.time_zone not recognized. " \ 'Run "rake time:zones:all" for a time zone names list.' end @@ -35,7 +35,7 @@ module ActiveSupport # Sets the default week start # If assigned value is not a valid day symbol (e.g. :sunday, :monday, ...), an exception will be raised. initializer "active_support.initialize_beginning_of_week" do |app| - require 'active_support/core_ext/date/calculations' + require "active_support/core_ext/date/calculations" beginning_of_week_default = Date.find_beginning_of_week!(app.config.beginning_of_week) Date.beginning_of_week_default = beginning_of_week_default diff --git a/activesupport/lib/active_support/reloader.rb b/activesupport/lib/active_support/reloader.rb new file mode 100644 index 0000000000..121c621751 --- /dev/null +++ b/activesupport/lib/active_support/reloader.rb @@ -0,0 +1,129 @@ +require "active_support/execution_wrapper" + +module ActiveSupport + #-- + # This class defines several callbacks: + # + # to_prepare -- Run once at application startup, and also from + # +to_run+. + # + # to_run -- Run before a work run that is reloading. If + # +reload_classes_only_on_change+ is true (the default), the class + # unload will have already occurred. + # + # to_complete -- Run after a work run that has reloaded. If + # +reload_classes_only_on_change+ is false, the class unload will + # have occurred after the work run, but before this callback. + # + # before_class_unload -- Run immediately before the classes are + # unloaded. + # + # after_class_unload -- Run immediately after the classes are + # unloaded. + # + class Reloader < ExecutionWrapper + define_callbacks :prepare + + define_callbacks :class_unload + + def self.to_prepare(*args, &block) + set_callback(:prepare, *args, &block) + end + + def self.before_class_unload(*args, &block) + set_callback(:class_unload, *args, &block) + end + + def self.after_class_unload(*args, &block) + set_callback(:class_unload, :after, *args, &block) + end + + to_run(:after) { self.class.prepare! } + + # Initiate a manual reload + def self.reload! + executor.wrap do + new.tap do |instance| + begin + instance.run! + ensure + instance.complete! + end + end + end + prepare! + end + + def self.run! # :nodoc: + if check! + super + else + Null + end + end + + # Run the supplied block as a work unit, reloading code as needed + def self.wrap + executor.wrap do + super + end + end + + class_attribute :executor + class_attribute :check + + self.executor = Executor + self.check = lambda { false } + + def self.check! # :nodoc: + @should_reload ||= check.call + end + + def self.reloaded! # :nodoc: + @should_reload = false + end + + def self.prepare! # :nodoc: + new.run_callbacks(:prepare) + end + + def initialize + super + @locked = false + end + + # Acquire the ActiveSupport::Dependencies::Interlock unload lock, + # ensuring it will be released automatically + def require_unload_lock! + unless @locked + ActiveSupport::Dependencies.interlock.start_unloading + @locked = true + end + end + + # Release the unload lock if it has been previously obtained + def release_unload_lock! + if @locked + @locked = false + ActiveSupport::Dependencies.interlock.done_unloading + end + end + + def run! # :nodoc: + super + release_unload_lock! + end + + def class_unload!(&block) # :nodoc: + require_unload_lock! + run_callbacks(:class_unload, &block) + end + + def complete! # :nodoc: + super + self.class.reloaded! + ensure + release_unload_lock! + end + end +end diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb index fcf5553061..dc3f27a16d 100644 --- a/activesupport/lib/active_support/rescuable.rb +++ b/activesupport/lib/active_support/rescuable.rb @@ -1,7 +1,6 @@ -require 'active_support/concern' -require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/string/inflections' -require 'active_support/core_ext/array/extract_options' +require "active_support/concern" +require "active_support/core_ext/class/attribute" +require "active_support/core_ext/string/inflections" module ActiveSupport # Rescuable module adds support for easier exception handling. @@ -48,14 +47,12 @@ module ActiveSupport # end # # Exceptions raised inside exception handlers are not propagated up. - def rescue_from(*klasses, &block) - options = klasses.extract_options! - - unless options.has_key?(:with) + def rescue_from(*klasses, with: nil, &block) + unless with if block_given? - options[:with] = block + with = block else - raise ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument." + raise ArgumentError, "Need a handler. Pass the with: keyword argument or provide a block." end end @@ -65,55 +62,104 @@ module ActiveSupport elsif klass.is_a?(String) klass else - raise ArgumentError, "#{klass} is neither an Exception nor a String" + raise ArgumentError, "#{klass.inspect} must be an Exception class or a String referencing an Exception class" end # Put the new handler at the end because the list is read in reverse. - self.rescue_handlers += [[key, options[:with]]] + self.rescue_handlers += [[key, with]] end end - end - # Tries to rescue the exception by looking up and calling a registered handler. - def rescue_with_handler(exception) - if handler = handler_for_rescue(exception) - handler.arity != 0 ? handler.call(exception) : handler.call - true # don't rely on the return value of the handler + # Matches an exception to a handler based on the exception class. + # + # If no handler matches the exception, check for a handler matching the + # (optional) exception.cause. If no handler matches the exception or its + # cause, this returns nil so you can deal with unhandled exceptions. + # Be sure to re-raise unhandled exceptions if this is what you expect. + # + # begin + # … + # rescue => exception + # rescue_with_handler(exception) || raise + # end + # + # Returns the exception if it was handled and nil if it was not. + def rescue_with_handler(exception, object: self) + if handler = handler_for_rescue(exception, object: object) + handler.call exception + exception + end end - end - def handler_for_rescue(exception) - # We go from right to left because pairs are pushed onto rescue_handlers - # as rescue_from declarations are found. - _, rescuer = self.class.rescue_handlers.reverse.detect do |klass_name, handler| - # The purpose of allowing strings in rescue_from is to support the - # declaration of handler associations for exception classes whose - # definition is yet unknown. - # - # Since this loop needs the constants it would be inconsistent to - # assume they should exist at this point. An early raised exception - # could trigger some other handler and the array could include - # precisely a string whose corresponding constant has not yet been - # seen. This is why we are tolerant to unknown constants. - # - # Note that this tolerance only matters if the exception was given as - # a string, otherwise a NameError will be raised by the interpreter - # itself when rescue_from CONSTANT is executed. - klass = self.class.const_get(klass_name) rescue nil - klass ||= (klass_name.constantize rescue nil) - klass === exception if klass + def handler_for_rescue(exception, object: self) #:nodoc: + case rescuer = find_rescue_handler(exception) + when Symbol + method = object.method(rescuer) + if method.arity == 0 + -> e { method.call } + else + method + end + when Proc + if rescuer.arity == 0 + -> e { object.instance_exec(&rescuer) } + else + -> e { object.instance_exec(e, &rescuer) } + end + end end - case rescuer - when Symbol - method(rescuer) - when Proc - if rescuer.arity == 0 - Proc.new { instance_exec(&rescuer) } - else - Proc.new { |_exception| instance_exec(_exception, &rescuer) } + private + def find_rescue_handler(exception) + if exception + # Handlers are in order of declaration but the most recently declared + # is the highest priority match, so we search for matching handlers + # in reverse. + _, handler = rescue_handlers.reverse_each.detect do |class_or_name, _| + if klass = constantize_rescue_handler_class(class_or_name) + klass === exception + end + end + + handler || find_rescue_handler(exception.cause) + end end - end + + def constantize_rescue_handler_class(class_or_name) + case class_or_name + when String, Symbol + begin + # Try a lexical lookup first since we support + # + # class Super + # rescue_from 'Error', with: … + # end + # + # class Sub + # class Error < StandardError; end + # end + # + # so an Error raised in Sub will hit the 'Error' handler. + const_get class_or_name + rescue NameError + class_or_name.safe_constantize + end + else + class_or_name + end + end + end + + # Delegates to the class method, but uses the instance as the subject for + # rescue_from handlers (method calls, instance_exec blocks). + def rescue_with_handler(exception) + self.class.rescue_with_handler exception, object: self + end + + # Internal handler lookup. Delegates to class method. Some libraries call + # this directly, so keeping it around for compatibility. + def handler_for_rescue(exception) #:nodoc: + self.class.handler_for_rescue exception, object: self end end end diff --git a/activesupport/lib/active_support/security_utils.rb b/activesupport/lib/active_support/security_utils.rb index 9be8613ada..b655d64449 100644 --- a/activesupport/lib/active_support/security_utils.rb +++ b/activesupport/lib/active_support/security_utils.rb @@ -1,4 +1,4 @@ -require 'digest' +require "digest" module ActiveSupport module SecurityUtils diff --git a/activesupport/lib/active_support/string_inquirer.rb b/activesupport/lib/active_support/string_inquirer.rb index bc673150d0..09e1cbb28d 100644 --- a/activesupport/lib/active_support/string_inquirer.rb +++ b/activesupport/lib/active_support/string_inquirer.rb @@ -8,15 +8,21 @@ module ActiveSupport # you can call this: # # Rails.env.production? + # + # == Instantiating a new StringInquirer + # + # vehicle = ActiveSupport::StringInquirer.new('car') + # vehicle.car? # => true + # vehicle.bike? # => false class StringInquirer < String private def respond_to_missing?(method_name, include_private = false) - method_name[-1] == '?' + method_name[-1] == "?" end def method_missing(method_name, *arguments) - if method_name[-1] == '?' + if method_name[-1] == "?" self == method_name[0..-2] else super diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb index 1cd4b807ad..0f09f1eb63 100644 --- a/activesupport/lib/active_support/subscriber.rb +++ b/activesupport/lib/active_support/subscriber.rb @@ -1,4 +1,4 @@ -require 'active_support/per_thread_registry' +require "active_support/per_thread_registry" module ActiveSupport # ActiveSupport::Subscriber is an object set to consume @@ -23,7 +23,6 @@ module ActiveSupport # the +sql+ method. class Subscriber class << self - # Attach the subscriber to a namespace. def attach_to(namespace, subscriber=new, notifier=ActiveSupport::Notifications) @namespace = namespace @@ -91,7 +90,7 @@ module ActiveSupport event.end = finished event.payload.merge!(payload) - method = name.split('.'.freeze).first + method = name.split(".".freeze).first send(method, event) end diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb index bcd7bf74c0..6836378943 100644 --- a/activesupport/lib/active_support/tagged_logging.rb +++ b/activesupport/lib/active_support/tagged_logging.rb @@ -1,7 +1,7 @@ -require 'active_support/core_ext/module/delegation' -require 'active_support/core_ext/object/blank' -require 'logger' -require 'active_support/logger' +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/object/blank" +require "logger" +require "active_support/logger" module ActiveSupport # Wraps any standard Logger object to provide tagging capabilities. diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index d9a668c0ea..3de4ccc1da 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -1,15 +1,15 @@ -gem 'minitest' # make sure we get the gem, not stdlib -require 'minitest' -require 'active_support/testing/tagged_logging' -require 'active_support/testing/setup_and_teardown' -require 'active_support/testing/assertions' -require 'active_support/testing/deprecation' -require 'active_support/testing/declarative' -require 'active_support/testing/isolation' -require 'active_support/testing/constant_lookup' -require 'active_support/testing/time_helpers' -require 'active_support/testing/file_fixtures' -require 'active_support/core_ext/kernel/reporting' +gem "minitest" # make sure we get the gem, not stdlib +require "minitest" +require "active_support/testing/tagged_logging" +require "active_support/testing/setup_and_teardown" +require "active_support/testing/assertions" +require "active_support/testing/deprecation" +require "active_support/testing/declarative" +require "active_support/testing/isolation" +require "active_support/testing/constant_lookup" +require "active_support/testing/time_helpers" +require "active_support/testing/file_fixtures" +require "active_support/core_ext/kernel/reporting" module ActiveSupport class TestCase < ::Minitest::Test @@ -66,13 +66,6 @@ module ActiveSupport alias :assert_not_respond_to :refute_respond_to alias :assert_not_same :refute_same - # Reveals the intention that the block should not raise any exception. - # - # assert_nothing_raised do - # ... - # end - def assert_nothing_raised(*args) - yield - end + ActiveSupport.run_load_hooks(:active_support_test_case, self) end end diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb index 29305e0082..28cf2953bf 100644 --- a/activesupport/lib/active_support/testing/assertions.rb +++ b/activesupport/lib/active_support/testing/assertions.rb @@ -1,8 +1,8 @@ -require 'active_support/core_ext/object/blank' - module ActiveSupport module Testing module Assertions + UNTRACKED = Object.new # :nodoc: + # Asserts that an expression is not truthy. Passes if <tt>object</tt> is # +nil+ or +false+. "Truthy" means "considered true in a conditional" # like <tt>if foo</tt>. @@ -19,6 +19,17 @@ module ActiveSupport assert !object, message end + # Assertion that the block should not raise an exception. + # + # Passes if evaluated code in the yielded block raises no exception. + # + # assert_nothing_raised do + # perform_service(param: 'no_exception') + # end + def assert_nothing_raised + yield + end + # Test numeric difference between the return value of an expression as a # result of what is evaluated in the yielded block. # @@ -94,6 +105,93 @@ module ActiveSupport def assert_no_difference(expression, message = nil, &block) assert_difference expression, 0, message, &block end + + # Assertion that the result of evaluating an expression is changed before + # and after invoking the passed in block. + # + # assert_changes 'Status.all_good?' do + # post :create, params: { status: { ok: false } } + # end + # + # You can pass the block as a string to be evaluated in the context of + # the block. A lambda can be passed for the block as well. + # + # assert_changes -> { Status.all_good? } do + # post :create, params: { status: { ok: false } } + # end + # + # The assertion is useful to test side effects. The passed block can be + # anything that can be converted to string with #to_s. + # + # assert_changes :@object do + # @object = 42 + # end + # + # The keyword arguments :from and :to can be given to specify the + # expected initial value and the expected value after the block was + # executed. + # + # assert_changes :@object, from: nil, to: :foo do + # @object = :foo + # end + # + # An error message can be specified. + # + # assert_changes -> { Status.all_good? }, 'Expected the status to be bad' do + # post :create, params: { status: { incident: true } } + # end + def assert_changes(expression, message = nil, from: UNTRACKED, to: UNTRACKED, &block) + exp = expression.respond_to?(:call) ? expression : -> { eval(expression.to_s, block.binding) } + + before = exp.call + retval = yield + + unless from == UNTRACKED + error = "#{expression.inspect} isn't #{from.inspect}" + error = "#{message}.\n#{error}" if message + assert from === before, error + end + + after = exp.call + + if to == UNTRACKED + error = "#{expression.inspect} didn't changed" + error = "#{message}.\n#{error}" if message + assert_not_equal before, after, error + else + error = "#{expression.inspect} didn't change to #{to}" + error = "#{message}.\n#{error}" if message + assert to === after, error + end + + retval + end + + # Assertion that the result of evaluating an expression is changed before + # and after invoking the passed in block. + # + # assert_no_changes 'Status.all_good?' do + # post :create, params: { status: { ok: true } } + # end + # + # An error message can be specified. + # + # assert_no_changes -> { Status.all_good? }, 'Expected the status to be good' do + # post :create, params: { status: { ok: false } } + # end + def assert_no_changes(expression, message = nil, &block) + exp = expression.respond_to?(:call) ? expression : -> { eval(expression.to_s, block.binding) } + + before = exp.call + retval = yield + after = exp.call + + error = "#{expression.inspect} did change to #{after}" + error = "#{message}.\n#{error}" if message + assert_equal before, after, error + + retval + end end end end diff --git a/activesupport/lib/active_support/testing/autorun.rb b/activesupport/lib/active_support/testing/autorun.rb index 84c6b89340..3108e3e549 100644 --- a/activesupport/lib/active_support/testing/autorun.rb +++ b/activesupport/lib/active_support/testing/autorun.rb @@ -1,12 +1,9 @@ -gem 'minitest' +gem "minitest" -require 'minitest' +require "minitest" -if Minitest.respond_to?(:run_with_rails_extension) - unless Minitest.run_with_rails_extension - Minitest.run_with_autorun = true - Minitest.autorun - end -else - Minitest.autorun +if Minitest.respond_to?(:run_via) && !Minitest.run_via[:rails] + Minitest.run_via[:ruby] = true end + +Minitest.autorun diff --git a/activesupport/lib/active_support/testing/constant_lookup.rb b/activesupport/lib/active_support/testing/constant_lookup.rb index 07d477c0db..647395d2b3 100644 --- a/activesupport/lib/active_support/testing/constant_lookup.rb +++ b/activesupport/lib/active_support/testing/constant_lookup.rb @@ -44,7 +44,6 @@ module ActiveSupport end end end - end end end diff --git a/activesupport/lib/active_support/testing/deprecation.rb b/activesupport/lib/active_support/testing/deprecation.rb index 5dfa14eeba..58911570e8 100644 --- a/activesupport/lib/active_support/testing/deprecation.rb +++ b/activesupport/lib/active_support/testing/deprecation.rb @@ -1,4 +1,5 @@ -require 'active_support/deprecation' +require "active_support/deprecation" +require "active_support/core_ext/regexp" module ActiveSupport module Testing @@ -8,7 +9,7 @@ module ActiveSupport assert !warnings.empty?, "Expected a deprecation warning within the block but received none" if match match = Regexp.new(Regexp.escape(match)) unless match.is_a?(Regexp) - assert warnings.any? { |w| w =~ match }, "No deprecation warning matched #{match}: #{warnings.join(', ')}" + assert warnings.any? { |w| match.match?(w) }, "No deprecation warning matched #{match}: #{warnings.join(', ')}" end result end diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index edf8b30a0a..d30b34ecd6 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -1,7 +1,7 @@ module ActiveSupport module Testing module Isolation - require 'thread' + require "thread" def self.included(klass) #:nodoc: klass.class_eval do @@ -13,17 +13,6 @@ module ActiveSupport !ENV["NO_FORK"] && Process.respond_to?(:fork) end - @@class_setup_mutex = Mutex.new - - def _run_class_setup # class setup method should only happen in parent - @@class_setup_mutex.synchronize do - unless defined?(@@ran_class_setup) || ENV['ISOLATION_TEST'] - self.class.setup if self.class.respond_to?(:setup) - @@ran_class_setup = true - end - end - end - def run serialized = run_in_isolation do super @@ -79,17 +68,17 @@ module ActiveSupport if ENV["ISOLATION_TEST"] yield File.open(ENV["ISOLATION_OUTPUT"], "w") do |file| - file.puts [Marshal.dump(self.dup)].pack("m") + file.puts [Marshal.dump(dup)].pack("m") end exit! else Tempfile.open("isolation") do |tmpfile| env = { - 'ISOLATION_TEST' => self.class.name, - 'ISOLATION_OUTPUT' => tmpfile.path + "ISOLATION_TEST" => self.class.name, + "ISOLATION_OUTPUT" => tmpfile.path } - load_paths = $-I.map {|p| "-I\"#{File.expand_path(p)}\"" }.join(" ") + load_paths = $-I.map { |p| "-I\"#{File.expand_path(p)}\"" }.join(" ") orig_args = ORIG_ARGV.join(" ") test_opts = "-n#{self.class.name}##{self.name}" command = "#{Gem.ruby} #{load_paths} #{$0} '#{orig_args}' #{test_opts}" diff --git a/activesupport/lib/active_support/testing/method_call_assertions.rb b/activesupport/lib/active_support/testing/method_call_assertions.rb index fccaa54f40..6b07416fdc 100644 --- a/activesupport/lib/active_support/testing/method_call_assertions.rb +++ b/activesupport/lib/active_support/testing/method_call_assertions.rb @@ -1,4 +1,4 @@ -require 'minitest/mock' +require "minitest/mock" module ActiveSupport module Testing diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb index 33f2b8dc9b..358c79c321 100644 --- a/activesupport/lib/active_support/testing/setup_and_teardown.rb +++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb @@ -1,5 +1,5 @@ -require 'active_support/concern' -require 'active_support/callbacks' +require "active_support/concern" +require "active_support/callbacks" module ActiveSupport module Testing diff --git a/activesupport/lib/active_support/testing/stream.rb b/activesupport/lib/active_support/testing/stream.rb index 895192ad05..1d06b94559 100644 --- a/activesupport/lib/active_support/testing/stream.rb +++ b/activesupport/lib/active_support/testing/stream.rb @@ -3,40 +3,40 @@ module ActiveSupport module Stream #:nodoc: private - def silence_stream(stream) - old_stream = stream.dup - stream.reopen(IO::NULL) - stream.sync = true - yield - ensure - stream.reopen(old_stream) - old_stream.close - end + def silence_stream(stream) + old_stream = stream.dup + stream.reopen(IO::NULL) + stream.sync = true + yield + ensure + stream.reopen(old_stream) + old_stream.close + end - def quietly - silence_stream(STDOUT) do - silence_stream(STDERR) do - yield + def quietly + silence_stream(STDOUT) do + silence_stream(STDERR) do + yield + end end end - end - def capture(stream) - stream = stream.to_s - captured_stream = Tempfile.new(stream) - stream_io = eval("$#{stream}") - origin_stream = stream_io.dup - stream_io.reopen(captured_stream) + def capture(stream) + stream = stream.to_s + captured_stream = Tempfile.new(stream) + stream_io = eval("$#{stream}") + origin_stream = stream_io.dup + stream_io.reopen(captured_stream) - yield + yield - stream_io.rewind - return captured_stream.read - ensure - captured_stream.close - captured_stream.unlink - stream_io.reopen(origin_stream) - end + stream_io.rewind + return captured_stream.read + ensure + captured_stream.close + captured_stream.unlink + stream_io.reopen(origin_stream) + end end end end diff --git a/activesupport/lib/active_support/testing/tagged_logging.rb b/activesupport/lib/active_support/testing/tagged_logging.rb index 843ce4a867..afdff87b45 100644 --- a/activesupport/lib/active_support/testing/tagged_logging.rb +++ b/activesupport/lib/active_support/testing/tagged_logging.rb @@ -8,7 +8,7 @@ module ActiveSupport def before_setup if tagged_logger && tagged_logger.info? heading = "#{self.class}: #{name}" - divider = '-' * heading.size + divider = "-" * heading.size tagged_logger.info divider tagged_logger.info heading tagged_logger.info divider diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb index fca0947c5b..e2f008b4b7 100644 --- a/activesupport/lib/active_support/testing/time_helpers.rb +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -1,32 +1,39 @@ +require "active_support/core_ext/string/strip" # for strip_heredoc +require "concurrent/map" + module ActiveSupport module Testing class SimpleStubs # :nodoc: Stub = Struct.new(:object, :method_name, :original_method) def initialize - @stubs = {} + @stubs = Concurrent::Map.new { |h, k| h[k] = {} } end def stub_object(object, method_name, return_value) - key = [object.object_id, method_name] - - if stub = @stubs[key] + if stub = stubbing(object, method_name) unstub_object(stub) end new_name = "__simple_stub__#{method_name}" - @stubs[key] = Stub.new(object, method_name, new_name) + @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 } end def unstub_all! - @stubs.each_value do |stub| - unstub_object(stub) + @stubs.each_value do |object_stubs| + object_stubs.each_value do |stub| + unstub_object(stub) + end end - @stubs = {} + @stubs.clear + end + + def stubbing(object, method_name) + @stubs[object.object_id][method_name] end private @@ -66,7 +73,7 @@ module ActiveSupport # +Date.today+, and +DateTime.now+ to return the time or date passed into this method. # # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 - # travel_to Time.new(2004, 11, 24, 01, 04, 44) + # travel_to Time.zone.local(2004, 11, 24, 01, 04, 44) # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 # Date.current # => Wed, 24 Nov 2004 # DateTime.current # => Wed, 24 Nov 2004 01:04:44 -0500 @@ -88,11 +95,39 @@ module ActiveSupport # state at the end of the block: # # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 - # travel_to Time.new(2004, 11, 24, 01, 04, 44) do + # travel_to Time.zone.local(2004, 11, 24, 01, 04, 44) do # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 # end # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 def travel_to(date_or_time) + if block_given? && simple_stubs.stubbing(Time, :now) + travel_to_nested_block_call = <<-MSG.strip_heredoc + + Calling `travel_to` with a block, when we have previously already made a call to `travel_to`, can lead to confusing time stubbing. + + Instead of: + + travel_to 2.days.from_now do + # 2 days from today + travel_to 3.days.from_now do + # 5 days from today + end + end + + preferred way to achieve above is: + + travel 2.days do + # 2 days from today + end + + travel 5.days do + # 5 days from today + end + + MSG + raise travel_to_nested_block_call + end + if date_or_time.is_a?(Date) && !date_or_time.is_a?(DateTime) now = date_or_time.midnight.to_time else @@ -116,7 +151,7 @@ module ActiveSupport # `travel` and `travel_to`. # # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 - # travel_to Time.new(2004, 11, 24, 01, 04, 44) + # travel_to Time.zone.local(2004, 11, 24, 01, 04, 44) # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 # travel_back # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 diff --git a/activesupport/lib/active_support/time.rb b/activesupport/lib/active_support/time.rb index ea2d3391bd..7658228ca6 100644 --- a/activesupport/lib/active_support/time.rb +++ b/activesupport/lib/active_support/time.rb @@ -1,18 +1,18 @@ module ActiveSupport - autoload :Duration, 'active_support/duration' - autoload :TimeWithZone, 'active_support/time_with_zone' - autoload :TimeZone, 'active_support/values/time_zone' + autoload :Duration, "active_support/duration" + autoload :TimeWithZone, "active_support/time_with_zone" + autoload :TimeZone, "active_support/values/time_zone" end -require 'date' -require 'time' +require "date" +require "time" -require 'active_support/core_ext/time' -require 'active_support/core_ext/date' -require 'active_support/core_ext/date_time' +require "active_support/core_ext/time" +require "active_support/core_ext/date" +require "active_support/core_ext/date_time" -require 'active_support/core_ext/integer/time' -require 'active_support/core_ext/numeric/time' +require "active_support/core_ext/integer/time" +require "active_support/core_ext/numeric/time" -require 'active_support/core_ext/string/conversions' -require 'active_support/core_ext/string/zones' +require "active_support/core_ext/string/conversions" +require "active_support/core_ext/string/zones" diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 79cc748cf5..889f71c4f3 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -1,6 +1,7 @@ -require 'active_support/duration' -require 'active_support/values/time_zone' -require 'active_support/core_ext/object/acts_like' +require "active_support/duration" +require "active_support/values/time_zone" +require "active_support/core_ext/object/acts_like" +require "active_support/core_ext/date_and_time/compatibility" module ActiveSupport # A Time-like class that can represent a time in any time zone. Necessary @@ -35,29 +36,29 @@ module ActiveSupport # t.is_a?(Time) # => true # t.is_a?(ActiveSupport::TimeWithZone) # => true class TimeWithZone - # Report class name as 'Time' to thwart type checking. def self.name - 'Time' + "Time" end PRECISIONS = Hash.new { |h, n| h[n] = "%FT%T.%#{n}N".freeze } - PRECISIONS[0] = '%FT%T'.freeze + PRECISIONS[0] = "%FT%T".freeze - include Comparable + include Comparable, DateAndTime::Compatibility attr_reader :time_zone def initialize(utc_time, time_zone, local_time = nil, period = nil) - @utc, @time_zone, @time = utc_time, time_zone, local_time + @utc = utc_time ? transfer_time_values_to_utc_constructor(utc_time) : nil + @time_zone, @time = time_zone, local_time @period = @utc ? period : get_period_and_ensure_valid_local_time(period) end - # Returns a Time or DateTime instance that represents the time in +time_zone+. + # Returns a <tt>Time</tt> instance that represents the time in +time_zone+. def time @time ||= period.to_local(@utc) end - # Returns a Time or DateTime instance that represents the time in UTC. + # Returns a <tt>Time</tt> instance of the simultaneous time in the UTC timezone. def utc @utc ||= period.to_utc(@time) end @@ -77,10 +78,9 @@ module ActiveSupport utc.in_time_zone(new_zone) end - # Returns a <tt>Time.local()</tt> instance of the simultaneous time in your - # system's <tt>ENV['TZ']</tt> zone. + # Returns a <tt>Time</tt> instance of the simultaneous time in the system timezone. def localtime(utc_offset = nil) - utc.respond_to?(:getlocal) ? utc.getlocal(utc_offset) : utc.to_time.getlocal(utc_offset) + utc.getlocal(utc_offset) end alias_method :getlocal, :localtime @@ -133,7 +133,7 @@ module ActiveSupport period.zone_identifier.to_s end - # Returns a string of the object's date, time, zone and offset from UTC. + # Returns a string of the object's date, time, zone, and offset from UTC. # # Time.zone.now.inspect # => "Thu, 04 Dec 2014 11:00:25 EST -05:00" def inspect @@ -170,12 +170,12 @@ module ActiveSupport end def init_with(coder) #:nodoc: - initialize(coder['utc'], coder['zone'], coder['time']) + initialize(coder["utc"], coder["zone"], coder["time"]) end def encode_with(coder) #:nodoc: - coder.tag = '!ruby/object:ActiveSupport::TimeWithZone' - coder.map = { 'utc' => utc, 'zone' => time_zone, 'time' => time } + coder.tag = "!ruby/object:ActiveSupport::TimeWithZone" + coder.map = { "utc" => utc, "zone" => time_zone, "time" => time } end # Returns a string of the object's date and time in the format used by @@ -279,6 +279,7 @@ module ActiveSupport end end alias_method :since, :+ + alias_method :in, :+ # Returns a new TimeWithZone object that represents the difference between # the current object's time and the +other+ time. @@ -401,17 +402,12 @@ module ActiveSupport utc.to_r end - # Returns an instance of Time in the system timezone. - def to_time - utc.to_time - end - # Returns an instance of DateTime with the timezone's UTC offset # # Time.zone.now.to_datetime # => Tue, 18 Aug 2015 02:32:20 +0000 # Time.current.in_time_zone('Hawaii').to_datetime # => Mon, 17 Aug 2015 16:32:20 -1000 def to_datetime - utc.to_datetime.new_offset(Rational(utc_offset, 86_400)) + @to_datetime ||= utc.to_datetime.new_offset(Rational(utc_offset, 86_400)) end # So that +self+ <tt>acts_like?(:time)</tt>. @@ -454,7 +450,6 @@ module ActiveSupport # Ensure proxy class responds to all methods that underlying time instance # responds to. def respond_to_missing?(sym, include_priv) - # consistently respond false to acts_like?(:date), regardless of whether #time is a Time or DateTime return false if sym.to_sym == :acts_like_date? time.respond_to?(sym, include_priv) end @@ -464,7 +459,7 @@ module ActiveSupport def method_missing(sym, *args, &block) wrap_with_time_zone time.__send__(sym, *args, &block) rescue NoMethodError => e - raise e, e.message.sub(time.inspect, self.inspect), e.backtrace + raise e, e.message.sub(time.inspect, inspect), e.backtrace end private @@ -482,11 +477,13 @@ module ActiveSupport end def transfer_time_values_to_utc_constructor(time) - ::Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec, Rational(time.nsec, 1000)) + # avoid creating another Time object if possible + return time if time.instance_of?(::Time) && time.utc? + ::Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec + time.subsec) end def duration_of_variable_length?(obj) - ActiveSupport::Duration === obj && obj.parts.any? {|p| [:years, :months, :days].include?(p[0]) } + ActiveSupport::Duration === obj && obj.parts.any? { |p| [:years, :months, :weeks, :days].include?(p[0]) } end def wrap_with_time_zone(time) diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 7ca3592520..a562f33420 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -1,7 +1,6 @@ -require 'tzinfo' -require 'concurrent/map' -require 'active_support/core_ext/object/blank' -require 'active_support/core_ext/object/try' +require "tzinfo" +require "concurrent/map" +require "active_support/core_ext/object/blank" module ActiveSupport # The TimeZone class serves as a wrapper around TZInfo::Timezone instances. @@ -181,10 +180,11 @@ module ActiveSupport "Samoa" => "Pacific/Apia" } - UTC_OFFSET_WITH_COLON = '%s%02d:%02d' - UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.tr(':', '') + UTC_OFFSET_WITH_COLON = "%s%02d:%02d" + UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.tr(":", "") @lazy_zones_map = Concurrent::Map.new + @country_zones = Concurrent::Map.new class << self # Assumes self represents an offset from UTC in seconds (as returned from @@ -193,7 +193,7 @@ module ActiveSupport # ActiveSupport::TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00" def seconds_to_utc_offset(seconds, colon = true) format = colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON - sign = (seconds < 0 ? '-' : '+') + sign = (seconds < 0 ? "-" : "+") hours = seconds.abs / 3600 minutes = (seconds.abs % 3600) / 60 format % [sign, hours, minutes] @@ -226,30 +226,41 @@ module ActiveSupport # Returns +nil+ if no such time zone is known to the system. def [](arg) case arg - when String + when String begin @lazy_zones_map[arg] ||= create(arg) rescue TZInfo::InvalidTimezoneIdentifier nil end - when Numeric, ActiveSupport::Duration - arg *= 3600 if arg.abs <= 13 - all.find { |z| z.utc_offset == arg.to_i } + when Numeric, ActiveSupport::Duration + arg *= 3600 if arg.abs <= 13 + all.find { |z| z.utc_offset == arg.to_i } else - raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}" + raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}" end end # A convenience method for returning a collection of TimeZone objects # for time zones in the USA. def us_zones - @us_zones ||= all.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ } + country_zones(:us) + end + + # A convenience method for returning a collection of TimeZone objects + # for time zones in the country specified by its ISO 3166-1 Alpha2 code. + def country_zones(country_code) + code = country_code.to_s.upcase + @country_zones[code] ||= + TZInfo::Country.get(code).zone_identifiers.map do |tz_id| + name = MAPPING.key(tz_id) + name && self[name] + end.compact.sort! end private def zones_map @zones_map ||= begin - MAPPING.each_key {|place| self[place]} # load all the zones + MAPPING.each_key { |place| self[place] } # load all the zones @lazy_zones_map end end @@ -267,7 +278,6 @@ module ActiveSupport @name = name @utc_offset = utc_offset @tzinfo = tzinfo || TimeZone.find_tzinfo(name) - @current_period = nil end # Returns the offset of this time zone from UTC in seconds. @@ -275,8 +285,7 @@ module ActiveSupport if @utc_offset @utc_offset else - @current_period ||= tzinfo.current_period if tzinfo - @current_period.utc_offset if @current_period + tzinfo.current_period.utc_offset if tzinfo && tzinfo.current_period end end @@ -428,29 +437,34 @@ module ActiveSupport end def init_with(coder) #:nodoc: - initialize(coder['name']) + initialize(coder["name"]) end def encode_with(coder) #:nodoc: coder.tag ="!ruby/object:#{self.class}" - coder.map = { 'name' => tzinfo.name } + coder.map = { "name" => tzinfo.name } end private def parts_to_time(parts, now) + raise ArgumentError, "invalid date" if parts.nil? return if parts.empty? - time = Time.new( - parts.fetch(:year, now.year), - parts.fetch(:mon, now.month), - parts.fetch(:mday, parts[:year] || parts[:mon] ? 1 : now.day), - parts.fetch(:hour, 0), - parts.fetch(:min, 0), - parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0), - parts.fetch(:offset, 0) - ) - - if parts[:offset] + if parts[:seconds] + time = Time.at(parts[:seconds]) + else + time = Time.new( + parts.fetch(:year, now.year), + parts.fetch(:mon, now.month), + parts.fetch(:mday, parts[:year] || parts[:mon] ? 1 : now.day), + parts.fetch(:hour, 0), + parts.fetch(:min, 0), + parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0), + parts.fetch(:offset, 0) + ) + end + + if parts[:offset] || parts[:seconds] TimeWithZone.new(time.utc, self) else TimeWithZone.new(nil, self, time) diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb index fe03984546..20b91ac911 100644 --- a/activesupport/lib/active_support/version.rb +++ b/activesupport/lib/active_support/version.rb @@ -1,4 +1,4 @@ -require_relative 'gem_version' +require_relative "gem_version" module ActiveSupport # Returns the version of the currently loaded ActiveSupport as a <tt>Gem::Version</tt> diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb index df7b081993..921a3447d0 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -1,9 +1,9 @@ -require 'time' -require 'base64' -require 'bigdecimal' -require 'active_support/core_ext/module/delegation' -require 'active_support/core_ext/string/inflections' -require 'active_support/core_ext/date_time/calculations' +require "time" +require "base64" +require "bigdecimal" +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/string/inflections" +require "active_support/core_ext/date_time/calculations" module ActiveSupport # = XmlMini @@ -20,11 +20,11 @@ module ActiveSupport attr_writer :original_filename, :content_type def original_filename - @original_filename || 'untitled' + @original_filename || "untitled" end def content_type - @content_type || 'application/octet-stream' + @content_type || "application/octet-stream" end end @@ -32,20 +32,25 @@ module ActiveSupport "binary" => "base64" } unless defined?(DEFAULT_ENCODINGS) - TYPE_NAMES = { - "Symbol" => "symbol", - "Fixnum" => "integer", - "Bignum" => "integer", - "BigDecimal" => "decimal", - "Float" => "float", - "TrueClass" => "boolean", - "FalseClass" => "boolean", - "Date" => "date", - "DateTime" => "dateTime", - "Time" => "dateTime", - "Array" => "array", - "Hash" => "hash" - } unless defined?(TYPE_NAMES) + unless defined?(TYPE_NAMES) + TYPE_NAMES = { + "Symbol" => "symbol", + "Integer" => "integer", + "BigDecimal" => "decimal", + "Float" => "float", + "TrueClass" => "boolean", + "FalseClass" => "boolean", + "Date" => "date", + "DateTime" => "dateTime", + "Time" => "dateTime", + "Array" => "array", + "Hash" => "hash" + } + + # No need to map these on Ruby 2.4+ + TYPE_NAMES["Fixnum"] = "integer" unless 0.class == Integer + TYPE_NAMES["Bignum"] = "integer" unless 0.class == Integer + end FORMATTING = { "symbol" => Proc.new { |symbol| symbol.to_s }, @@ -81,7 +86,7 @@ module ActiveSupport attr_accessor :depth self.depth = 100 - delegate :parse, :to => :backend + delegate :parse, to: :backend def backend current_thread_backend || @backend @@ -103,7 +108,7 @@ module ActiveSupport def to_tag(key, value, options) type_name = options.delete(:type) - merged_options = options.merge(:root => key, :skip_instruct => true) + merged_options = options.merge(root: key, skip_instruct: true) if value.is_a?(::Method) || value.is_a?(::Proc) if value.arity == 1 @@ -121,7 +126,7 @@ module ActiveSupport key = rename_key(key.to_s, options) - attributes = options[:skip_types] || type_name.nil? ? { } : { :type => type_name } + attributes = options[:skip_types] || type_name.nil? ? {} : { type: type_name } attributes[:nil] = true if value.nil? encoding = options[:encoding] || DEFAULT_ENCODINGS[type_name] @@ -146,29 +151,29 @@ module ActiveSupport protected - def _dasherize(key) - # $2 must be a non-greedy regex for this to work - left, middle, right = /\A(_*)(.*?)(_*)\Z/.match(key.strip)[1,3] - "#{left}#{middle.tr('_ ', '--')}#{right}" - end + def _dasherize(key) + # $2 must be a non-greedy regex for this to work + left, middle, right = /\A(_*)(.*?)(_*)\Z/.match(key.strip)[1,3] + "#{left}#{middle.tr('_ ', '--')}#{right}" + end - # TODO: Add support for other encodings - def _parse_binary(bin, entity) #:nodoc: - case entity['encoding'] - when 'base64' - ::Base64.decode64(bin) - else - bin + # TODO: Add support for other encodings + def _parse_binary(bin, entity) #:nodoc: + case entity["encoding"] + when "base64" + ::Base64.decode64(bin) + else + bin + end end - end - def _parse_file(file, entity) - f = StringIO.new(::Base64.decode64(file)) - f.extend(FileLike) - f.original_filename = entity['name'] - f.content_type = entity['content_type'] - f - end + def _parse_file(file, entity) + f = StringIO.new(::Base64.decode64(file)) + f.extend(FileLike) + f.original_filename = entity["name"] + f.content_type = entity["content_type"] + f + end private @@ -190,5 +195,5 @@ module ActiveSupport end end - XmlMini.backend = 'REXML' + XmlMini.backend = "REXML" end diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb index 94751bbc04..10498c9be0 100644 --- a/activesupport/lib/active_support/xml_mini/jdom.rb +++ b/activesupport/lib/active_support/xml_mini/jdom.rb @@ -1,9 +1,9 @@ -raise "JRuby is required to use the JDOM backend for XmlMini" unless RUBY_PLATFORM =~ /java/ +raise "JRuby is required to use the JDOM backend for XmlMini" unless RUBY_PLATFORM.include?("java") -require 'jruby' +require "jruby" include Java -require 'active_support/core_ext/object/blank' +require "active_support/core_ext/object/blank" java_import javax.xml.parsers.DocumentBuilder unless defined? DocumentBuilder java_import javax.xml.parsers.DocumentBuilderFactory unless defined? DocumentBuilderFactory @@ -16,7 +16,7 @@ module ActiveSupport module XmlMini_JDOM #:nodoc: extend self - CONTENT_KEY = '__content__'.freeze + CONTENT_KEY = "__content__".freeze NODE_TYPE_NAMES = %w{ATTRIBUTE_NODE CDATA_SECTION_NODE COMMENT_NODE DOCUMENT_FRAGMENT_NODE DOCUMENT_NODE DOCUMENT_TYPE_NODE ELEMENT_NODE ENTITY_NODE ENTITY_REFERENCE_NODE NOTATION_NODE @@ -46,136 +46,136 @@ module ActiveSupport xml_string_reader = StringReader.new(data) xml_input_source = InputSource.new(xml_string_reader) doc = @dbf.new_document_builder.parse(xml_input_source) - merge_element!({CONTENT_KEY => ''}, doc.document_element, XmlMini.depth) + merge_element!({ CONTENT_KEY => "" }, doc.document_element, XmlMini.depth) end end private - # Convert an XML element and merge into the hash - # - # hash:: - # Hash to merge the converted element into. - # element:: - # XML element to merge into hash - def merge_element!(hash, element, depth) - raise 'Document too deep!' if depth == 0 - delete_empty(hash) - merge!(hash, element.tag_name, collapse(element, depth)) - end - - def delete_empty(hash) - hash.delete(CONTENT_KEY) if hash[CONTENT_KEY] == '' - end + # Convert an XML element and merge into the hash + # + # hash:: + # Hash to merge the converted element into. + # element:: + # XML element to merge into hash + def merge_element!(hash, element, depth) + raise "Document too deep!" if depth == 0 + delete_empty(hash) + merge!(hash, element.tag_name, collapse(element, depth)) + end - # Actually converts an XML document element into a data structure. - # - # element:: - # The document element to be collapsed. - def collapse(element, depth) - hash = get_attributes(element) + def delete_empty(hash) + hash.delete(CONTENT_KEY) if hash[CONTENT_KEY] == "" + end - child_nodes = element.child_nodes - if child_nodes.length > 0 - (0...child_nodes.length).each do |i| - child = child_nodes.item(i) - merge_element!(hash, child, depth - 1) unless child.node_type == Node.TEXT_NODE + # Actually converts an XML document element into a data structure. + # + # element:: + # The document element to be collapsed. + def collapse(element, depth) + hash = get_attributes(element) + + child_nodes = element.child_nodes + if child_nodes.length > 0 + (0...child_nodes.length).each do |i| + child = child_nodes.item(i) + merge_element!(hash, child, depth - 1) unless child.node_type == Node.TEXT_NODE + end + merge_texts!(hash, element) unless empty_content?(element) + hash + else + merge_texts!(hash, element) end - merge_texts!(hash, element) unless empty_content?(element) - hash - else - merge_texts!(hash, element) end - end - # Merge all the texts of an element into the hash - # - # hash:: - # Hash to add the converted element to. - # element:: - # XML element whose texts are to me merged into the hash - def merge_texts!(hash, element) - delete_empty(hash) - text_children = texts(element) - if text_children.join.empty? - hash - else - # must use value to prevent double-escaping - merge!(hash, CONTENT_KEY, text_children.join) + # Merge all the texts of an element into the hash + # + # hash:: + # Hash to add the converted element to. + # element:: + # XML element whose texts are to me merged into the hash + def merge_texts!(hash, element) + delete_empty(hash) + text_children = texts(element) + if text_children.join.empty? + hash + else + # must use value to prevent double-escaping + merge!(hash, CONTENT_KEY, text_children.join) + end end - end - # Adds a new key/value pair to an existing Hash. If the key to be added - # already exists and the existing value associated with key is not - # an Array, it will be wrapped in an Array. Then the new value is - # appended to that Array. - # - # hash:: - # Hash to add key/value pair to. - # key:: - # Key to be added. - # value:: - # Value to be associated with key. - def merge!(hash, key, value) - if hash.has_key?(key) - if hash[key].instance_of?(Array) - hash[key] << value + # Adds a new key/value pair to an existing Hash. If the key to be added + # already exists and the existing value associated with key is not + # an Array, it will be wrapped in an Array. Then the new value is + # appended to that Array. + # + # hash:: + # Hash to add key/value pair to. + # key:: + # Key to be added. + # value:: + # Value to be associated with key. + def merge!(hash, key, value) + if hash.has_key?(key) + if hash[key].instance_of?(Array) + hash[key] << value + else + hash[key] = [hash[key], value] + end + elsif value.instance_of?(Array) + hash[key] = [value] else - hash[key] = [hash[key], value] + hash[key] = value end - elsif value.instance_of?(Array) - hash[key] = [value] - else - hash[key] = value + hash end - hash - end - # Converts the attributes array of an XML element into a hash. - # Returns an empty Hash if node has no attributes. - # - # element:: - # XML element to extract attributes from. - def get_attributes(element) - attribute_hash = {} - attributes = element.attributes - (0...attributes.length).each do |i| - attribute_hash[CONTENT_KEY] ||= '' - attribute_hash[attributes.item(i).name] = attributes.item(i).value + # Converts the attributes array of an XML element into a hash. + # Returns an empty Hash if node has no attributes. + # + # element:: + # XML element to extract attributes from. + def get_attributes(element) + attribute_hash = {} + attributes = element.attributes + (0...attributes.length).each do |i| + attribute_hash[CONTENT_KEY] ||= "" + attribute_hash[attributes.item(i).name] = attributes.item(i).value + end + attribute_hash end - attribute_hash - end - # Determines if a document element has text content - # - # element:: - # XML element to be checked. - def texts(element) - texts = [] - child_nodes = element.child_nodes - (0...child_nodes.length).each do |i| - item = child_nodes.item(i) - if item.node_type == Node.TEXT_NODE - texts << item.get_data + # Determines if a document element has text content + # + # element:: + # XML element to be checked. + def texts(element) + texts = [] + child_nodes = element.child_nodes + (0...child_nodes.length).each do |i| + item = child_nodes.item(i) + if item.node_type == Node.TEXT_NODE + texts << item.get_data + end end + texts end - texts - end - # Determines if a document element has text content - # - # element:: - # XML element to be checked. - def empty_content?(element) - text = '' - child_nodes = element.child_nodes - (0...child_nodes.length).each do |i| - item = child_nodes.item(i) - if item.node_type == Node.TEXT_NODE - text << item.get_data.strip + # Determines if a document element has text content + # + # element:: + # XML element to be checked. + def empty_content?(element) + text = "" + child_nodes = element.child_nodes + (0...child_nodes.length).each do |i| + item = child_nodes.item(i) + if item.node_type == Node.TEXT_NODE + text << item.get_data.strip + end end + text.strip.length == 0 end - text.strip.length == 0 - end end end diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb index bb0ea9c582..8a4aa03963 100644 --- a/activesupport/lib/active_support/xml_mini/libxml.rb +++ b/activesupport/lib/active_support/xml_mini/libxml.rb @@ -1,6 +1,6 @@ -require 'libxml' -require 'active_support/core_ext/object/blank' -require 'stringio' +require "libxml" +require "active_support/core_ext/object/blank" +require "stringio" module ActiveSupport module XmlMini_LibXML #:nodoc: @@ -11,7 +11,7 @@ module ActiveSupport # XML Document string or IO to parse def parse(data) if !data.respond_to?(:read) - data = StringIO.new(data || '') + data = StringIO.new(data || "") end char = data.getc @@ -22,7 +22,6 @@ module ActiveSupport LibXML::XML::Parser.io(data).parse.to_hash end end - end end @@ -35,7 +34,7 @@ module LibXML #:nodoc: end module Node #:nodoc: - CONTENT_ROOT = '__content__'.freeze + CONTENT_ROOT = "__content__".freeze # Convert XML document to hash. # @@ -46,9 +45,9 @@ module LibXML #:nodoc: # Insert node hash into parent hash correctly. case hash[name] - when Array then hash[name] << node_hash - when Hash then hash[name] = [hash[name], node_hash] - when nil then hash[name] = node_hash + when Array then hash[name] << node_hash + when Hash then hash[name] = [hash[name], node_hash] + when nil then hash[name] = node_hash end # Handle child elements @@ -56,7 +55,7 @@ module LibXML #:nodoc: if c.element? c.to_hash(node_hash) elsif c.text? || c.cdata? - node_hash[CONTENT_ROOT] ||= '' + node_hash[CONTENT_ROOT] ||= "" node_hash[CONTENT_ROOT] << c.content end end diff --git a/activesupport/lib/active_support/xml_mini/libxmlsax.rb b/activesupport/lib/active_support/xml_mini/libxmlsax.rb index 70a95299ec..4edb0bd2b1 100644 --- a/activesupport/lib/active_support/xml_mini/libxmlsax.rb +++ b/activesupport/lib/active_support/xml_mini/libxmlsax.rb @@ -1,6 +1,6 @@ -require 'libxml' -require 'active_support/core_ext/object/blank' -require 'stringio' +require "libxml" +require "active_support/core_ext/object/blank" +require "stringio" module ActiveSupport module XmlMini_LibXMLSAX #:nodoc: @@ -9,11 +9,10 @@ module ActiveSupport # Class that will build the hash while the XML document # is being parsed using SAX events. class HashBuilder - include LibXML::XML::SaxParser::Callbacks - CONTENT_KEY = '__content__'.freeze - HASH_SIZE_KEY = '__hash_size__'.freeze + CONTENT_KEY = "__content__".freeze + HASH_SIZE_KEY = "__hash_size__".freeze attr_reader :hash @@ -22,7 +21,7 @@ module ActiveSupport end def on_start_document - @hash = { CONTENT_KEY => '' } + @hash = { CONTENT_KEY => "" } @hash_stack = [@hash] end @@ -32,20 +31,20 @@ module ActiveSupport end def on_start_element(name, attrs = {}) - new_hash = { CONTENT_KEY => '' }.merge!(attrs) + new_hash = { CONTENT_KEY => "" }.merge!(attrs) new_hash[HASH_SIZE_KEY] = new_hash.size + 1 case current_hash[name] - when Array then current_hash[name] << new_hash - when Hash then current_hash[name] = [current_hash[name], new_hash] - when nil then current_hash[name] = new_hash + when Array then current_hash[name] << new_hash + when Hash then current_hash[name] = [current_hash[name], new_hash] + when nil then current_hash[name] = new_hash end @hash_stack.push(new_hash) end def on_end_element(name) - if current_hash.length > current_hash.delete(HASH_SIZE_KEY) && current_hash[CONTENT_KEY].blank? || current_hash[CONTENT_KEY] == '' + if current_hash.length > current_hash.delete(HASH_SIZE_KEY) && current_hash[CONTENT_KEY].blank? || current_hash[CONTENT_KEY] == "" current_hash.delete(CONTENT_KEY) end @hash_stack.pop @@ -63,7 +62,7 @@ module ActiveSupport def parse(data) if !data.respond_to?(:read) - data = StringIO.new(data || '') + data = StringIO.new(data || "") end char = data.getc diff --git a/activesupport/lib/active_support/xml_mini/nokogiri.rb b/activesupport/lib/active_support/xml_mini/nokogiri.rb index 619cc7522d..b92fa7ea7c 100644 --- a/activesupport/lib/active_support/xml_mini/nokogiri.rb +++ b/activesupport/lib/active_support/xml_mini/nokogiri.rb @@ -1,11 +1,11 @@ begin - require 'nokogiri' + require "nokogiri" rescue LoadError => e $stderr.puts "You don't have nokogiri installed in your application. Please add it to your Gemfile and run bundle install" raise e end -require 'active_support/core_ext/object/blank' -require 'stringio' +require "active_support/core_ext/object/blank" +require "stringio" module ActiveSupport module XmlMini_Nokogiri #:nodoc: @@ -16,7 +16,7 @@ module ActiveSupport # XML Document string or IO to parse def parse(data) if !data.respond_to?(:read) - data = StringIO.new(data || '') + data = StringIO.new(data || "") end char = data.getc @@ -38,7 +38,7 @@ module ActiveSupport end module Node #:nodoc: - CONTENT_ROOT = '__content__'.freeze + CONTENT_ROOT = "__content__".freeze # Convert XML document to hash. # @@ -49,9 +49,9 @@ module ActiveSupport # Insert node hash into parent hash correctly. case hash[name] - when Array then hash[name] << node_hash - when Hash then hash[name] = [hash[name], node_hash] - when nil then hash[name] = node_hash + when Array then hash[name] << node_hash + when Hash then hash[name] = [hash[name], node_hash] + when nil then hash[name] = node_hash end # Handle child elements @@ -59,7 +59,7 @@ module ActiveSupport if c.element? c.to_hash(node_hash) elsif c.text? || c.cdata? - node_hash[CONTENT_ROOT] ||= '' + node_hash[CONTENT_ROOT] ||= "" node_hash[CONTENT_ROOT] << c.content end end diff --git a/activesupport/lib/active_support/xml_mini/nokogirisax.rb b/activesupport/lib/active_support/xml_mini/nokogirisax.rb index be2d6a4cb1..b8b85146c5 100644 --- a/activesupport/lib/active_support/xml_mini/nokogirisax.rb +++ b/activesupport/lib/active_support/xml_mini/nokogirisax.rb @@ -1,11 +1,11 @@ begin - require 'nokogiri' + require "nokogiri" rescue LoadError => e $stderr.puts "You don't have nokogiri installed in your application. Please add it to your Gemfile and run bundle install" raise e end -require 'active_support/core_ext/object/blank' -require 'stringio' +require "active_support/core_ext/object/blank" +require "stringio" module ActiveSupport module XmlMini_NokogiriSAX #:nodoc: @@ -14,9 +14,8 @@ module ActiveSupport # Class that will build the hash while the XML document # is being parsed using SAX events. class HashBuilder < Nokogiri::XML::SAX::Document - - CONTENT_KEY = '__content__'.freeze - HASH_SIZE_KEY = '__hash_size__'.freeze + CONTENT_KEY = "__content__".freeze + HASH_SIZE_KEY = "__hash_size__".freeze attr_reader :hash @@ -38,20 +37,20 @@ module ActiveSupport end def start_element(name, attrs = []) - new_hash = { CONTENT_KEY => '' }.merge!(Hash[attrs]) + new_hash = { CONTENT_KEY => "" }.merge!(Hash[attrs]) new_hash[HASH_SIZE_KEY] = new_hash.size + 1 case current_hash[name] - when Array then current_hash[name] << new_hash - when Hash then current_hash[name] = [current_hash[name], new_hash] - when nil then current_hash[name] = new_hash + when Array then current_hash[name] << new_hash + when Hash then current_hash[name] = [current_hash[name], new_hash] + when nil then current_hash[name] = new_hash end @hash_stack.push(new_hash) end def end_element(name) - if current_hash.length > current_hash.delete(HASH_SIZE_KEY) && current_hash[CONTENT_KEY].blank? || current_hash[CONTENT_KEY] == '' + if current_hash.length > current_hash.delete(HASH_SIZE_KEY) && current_hash[CONTENT_KEY].blank? || current_hash[CONTENT_KEY] == "" current_hash.delete(CONTENT_KEY) end @hash_stack.pop @@ -69,7 +68,7 @@ module ActiveSupport def parse(data) if !data.respond_to?(:read) - data = StringIO.new(data || '') + data = StringIO.new(data || "") end char = data.getc diff --git a/activesupport/lib/active_support/xml_mini/rexml.rb b/activesupport/lib/active_support/xml_mini/rexml.rb index 924ed72345..091a15294c 100644 --- a/activesupport/lib/active_support/xml_mini/rexml.rb +++ b/activesupport/lib/active_support/xml_mini/rexml.rb @@ -1,12 +1,12 @@ -require 'active_support/core_ext/kernel/reporting' -require 'active_support/core_ext/object/blank' -require 'stringio' +require "active_support/core_ext/kernel/reporting" +require "active_support/core_ext/object/blank" +require "stringio" module ActiveSupport module XmlMini_REXML #:nodoc: extend self - CONTENT_KEY = '__content__'.freeze + CONTENT_KEY = "__content__".freeze # Parse an XML Document string or IO into a simple hash. # @@ -17,15 +17,13 @@ module ActiveSupport # XML Document string or IO to parse def parse(data) if !data.respond_to?(:read) - data = StringIO.new(data || '') + data = StringIO.new(data || "") end - char = data.getc - if char.nil? + if data.eof? {} else - data.ungetc(char) - silence_warnings { require 'rexml/document' } unless defined?(REXML::Document) + silence_warnings { require "rexml/document" } unless defined?(REXML::Document) doc = REXML::Document.new(data) if doc.root @@ -57,7 +55,7 @@ module ActiveSupport hash = get_attributes(element) if element.has_elements? - element.each_element {|child| merge_element!(hash, child, depth - 1) } + element.each_element { |child| merge_element!(hash, child, depth - 1) } merge_texts!(hash, element) unless empty_content?(element) hash else @@ -76,7 +74,7 @@ module ActiveSupport hash else # must use value to prevent double-escaping - texts = '' + texts = "" element.texts.each { |t| texts << t.value } merge!(hash, CONTENT_KEY, texts) end diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb index c0e23e89f7..4e564591b4 100644 --- a/activesupport/test/abstract_unit.rb +++ b/activesupport/test/abstract_unit.rb @@ -1,40 +1,36 @@ ORIG_ARGV = ARGV.dup -begin - old, $VERBOSE = $VERBOSE, nil - require File.expand_path('../../../load_paths', __FILE__) -ensure - $VERBOSE = old -end - -require 'active_support/core_ext/kernel/reporting' +require "active_support/core_ext/kernel/reporting" silence_warnings do Encoding.default_internal = "UTF-8" Encoding.default_external = "UTF-8" end -require 'active_support/testing/autorun' -require 'active_support/testing/method_call_assertions' +require "active_support/testing/autorun" +require "active_support/testing/method_call_assertions" -ENV['NO_RELOAD'] = '1' -require 'active_support' +ENV["NO_RELOAD"] = "1" +require "active_support" Thread.abort_on_exception = true # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true +# Default to old to_time behavior but allow running tests with new behavior +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' +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 = '') +def jruby_skip(message = "") skip message if defined?(JRUBY_VERSION) end diff --git a/activesupport/test/array_inquirer_test.rb b/activesupport/test/array_inquirer_test.rb index 263ab3802b..4d3f5b001c 100644 --- a/activesupport/test/array_inquirer_test.rb +++ b/activesupport/test/array_inquirer_test.rb @@ -1,9 +1,9 @@ -require 'abstract_unit' -require 'active_support/core_ext/array' +require "abstract_unit" +require "active_support/core_ext/array" class ArrayInquirerTest < ActiveSupport::TestCase def setup - @array_inquirer = ActiveSupport::ArrayInquirer.new([:mobile, :tablet, 'api']) + @array_inquirer = ActiveSupport::ArrayInquirer.new([:mobile, :tablet, "api"]) end def test_individual @@ -19,7 +19,7 @@ class ArrayInquirerTest < ActiveSupport::TestCase end def test_any_string_symbol_mismatch - assert @array_inquirer.any?('mobile') + assert @array_inquirer.any?("mobile") assert @array_inquirer.any?(:api) end @@ -33,7 +33,7 @@ class ArrayInquirerTest < ActiveSupport::TestCase end def test_inquiry - result = [:mobile, :tablet, 'api'].inquiry + result = [:mobile, :tablet, "api"].inquiry assert_instance_of ActiveSupport::ArrayInquirer, result assert_equal @array_inquirer, result diff --git a/activesupport/test/autoload_test.rb b/activesupport/test/autoload_test.rb index c18b007612..6c8aa3e055 100644 --- a/activesupport/test/autoload_test.rb +++ b/activesupport/test/autoload_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class TestAutoloadModule < ActiveSupport::TestCase include ActiveSupport::Testing::Isolation @@ -31,7 +31,7 @@ class TestAutoloadModule < ActiveSupport::TestCase end end - assert !$LOADED_FEATURES.include?(@some_class_path) + assert_not_includes $LOADED_FEATURES, @some_class_path assert_nothing_raised { ::Fixtures::Autoload::SomeClass } end @@ -40,7 +40,7 @@ class TestAutoloadModule < ActiveSupport::TestCase autoload :SomeClass end - assert !$LOADED_FEATURES.include?(@some_class_path) + assert_not_includes $LOADED_FEATURES, @some_class_path assert_nothing_raised { ::Fixtures::Autoload::SomeClass } end @@ -51,9 +51,9 @@ class TestAutoloadModule < ActiveSupport::TestCase end end - assert !$LOADED_FEATURES.include?(@some_class_path) + assert_not_includes $LOADED_FEATURES, @some_class_path ::Fixtures::Autoload.eager_load! - assert $LOADED_FEATURES.include?(@some_class_path) + assert_includes $LOADED_FEATURES, @some_class_path assert_nothing_raised { ::Fixtures::Autoload::SomeClass } end @@ -64,7 +64,7 @@ class TestAutoloadModule < ActiveSupport::TestCase end end - assert !$LOADED_FEATURES.include?(@another_class_path) + assert_not_includes $LOADED_FEATURES, @another_class_path assert_nothing_raised { ::Fixtures::AnotherClass } end @@ -75,7 +75,7 @@ class TestAutoloadModule < ActiveSupport::TestCase end end - assert !$LOADED_FEATURES.include?(@another_class_path) + assert_not_includes $LOADED_FEATURES, @another_class_path assert_nothing_raised { ::Fixtures::AnotherClass } end -end
\ No newline at end of file +end diff --git a/activesupport/test/autoloading_fixtures/a/b.rb b/activesupport/test/autoloading_fixtures/a/b.rb index 9c9e6454cf..0dbbbbd181 100644 --- a/activesupport/test/autoloading_fixtures/a/b.rb +++ b/activesupport/test/autoloading_fixtures/a/b.rb @@ -1,2 +1,2 @@ class A::B -end
\ No newline at end of file +end diff --git a/activesupport/test/autoloading_fixtures/a/c/d.rb b/activesupport/test/autoloading_fixtures/a/c/d.rb index 0f40d6fbc4..2c0ec5f182 100644 --- a/activesupport/test/autoloading_fixtures/a/c/d.rb +++ b/activesupport/test/autoloading_fixtures/a/c/d.rb @@ -1,2 +1,2 @@ class A::C::D -end
\ No newline at end of file +end diff --git a/activesupport/test/autoloading_fixtures/a/c/em/f.rb b/activesupport/test/autoloading_fixtures/a/c/em/f.rb index 8b28e19148..3ff1b7efa0 100644 --- a/activesupport/test/autoloading_fixtures/a/c/em/f.rb +++ b/activesupport/test/autoloading_fixtures/a/c/em/f.rb @@ -1,2 +1,2 @@ class A::C::EM::F -end
\ No newline at end of file +end diff --git a/activesupport/test/autoloading_fixtures/class_folder.rb b/activesupport/test/autoloading_fixtures/class_folder.rb index ad2b27be61..6ee8182214 100644 --- a/activesupport/test/autoloading_fixtures/class_folder.rb +++ b/activesupport/test/autoloading_fixtures/class_folder.rb @@ -1,3 +1,3 @@ class ClassFolder - ConstantInClassFolder = 'indeed' + ConstantInClassFolder = "indeed" end diff --git a/activesupport/test/autoloading_fixtures/class_folder/class_folder_subclass.rb b/activesupport/test/autoloading_fixtures/class_folder/class_folder_subclass.rb index 402609c583..4df069cab6 100644 --- a/activesupport/test/autoloading_fixtures/class_folder/class_folder_subclass.rb +++ b/activesupport/test/autoloading_fixtures/class_folder/class_folder_subclass.rb @@ -1,3 +1,3 @@ class ClassFolder::ClassFolderSubclass < ClassFolder - ConstantInClassFolder = 'indeed' + ConstantInClassFolder = "indeed" end diff --git a/activesupport/test/autoloading_fixtures/conflict.rb b/activesupport/test/autoloading_fixtures/conflict.rb index 4ac6201902..d7f42b5d5f 100644 --- a/activesupport/test/autoloading_fixtures/conflict.rb +++ b/activesupport/test/autoloading_fixtures/conflict.rb @@ -1 +1 @@ -Conflict = 2
\ No newline at end of file +Conflict = 2 diff --git a/activesupport/test/autoloading_fixtures/cross_site_dependency.rb b/activesupport/test/autoloading_fixtures/cross_site_dependency.rb index 21ee554e92..de941bf271 100644 --- a/activesupport/test/autoloading_fixtures/cross_site_dependency.rb +++ b/activesupport/test/autoloading_fixtures/cross_site_dependency.rb @@ -1,2 +1,2 @@ class CrossSiteDependency -end
\ No newline at end of file +end diff --git a/activesupport/test/autoloading_fixtures/d.rb b/activesupport/test/autoloading_fixtures/d.rb index 45c794d4ca..52850e1e1a 100644 --- a/activesupport/test/autoloading_fixtures/d.rb +++ b/activesupport/test/autoloading_fixtures/d.rb @@ -1,2 +1,2 @@ class D -end
\ No newline at end of file +end diff --git a/activesupport/test/autoloading_fixtures/em.rb b/activesupport/test/autoloading_fixtures/em.rb index 16a1838667..e47024999e 100644 --- a/activesupport/test/autoloading_fixtures/em.rb +++ b/activesupport/test/autoloading_fixtures/em.rb @@ -1,2 +1,2 @@ class EM -end
\ No newline at end of file +end diff --git a/activesupport/test/autoloading_fixtures/load_path/loaded_constant.rb b/activesupport/test/autoloading_fixtures/load_path/loaded_constant.rb index e3d1218c96..d2c4f6b0c5 100644 --- a/activesupport/test/autoloading_fixtures/load_path/loaded_constant.rb +++ b/activesupport/test/autoloading_fixtures/load_path/loaded_constant.rb @@ -1,3 +1,2 @@ module LoadedConstant end - diff --git a/activesupport/test/autoloading_fixtures/module_folder/nested_sibling.rb b/activesupport/test/autoloading_fixtures/module_folder/nested_sibling.rb index 80244b8bab..04c426833e 100644 --- a/activesupport/test/autoloading_fixtures/module_folder/nested_sibling.rb +++ b/activesupport/test/autoloading_fixtures/module_folder/nested_sibling.rb @@ -1,2 +1,2 @@ class ModuleFolder::NestedSibling -end
\ No newline at end of file +end diff --git a/activesupport/test/autoloading_fixtures/module_with_custom_const_missing/a/b.rb b/activesupport/test/autoloading_fixtures/module_with_custom_const_missing/a/b.rb index d12d02f3aa..4f2020c503 100644 --- a/activesupport/test/autoloading_fixtures/module_with_custom_const_missing/a/b.rb +++ b/activesupport/test/autoloading_fixtures/module_with_custom_const_missing/a/b.rb @@ -1 +1 @@ -ModuleWithCustomConstMissing::A::B = "10"
\ No newline at end of file +ModuleWithCustomConstMissing::A::B = "10" diff --git a/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb b/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb new file mode 100644 index 0000000000..829ee917d2 --- /dev/null +++ b/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb @@ -0,0 +1,4 @@ +RaisesArbitraryException = 1 +_ = A::B # Autoloading recursion, also expected to be watched and discarded. + +raise Exception, "arbitray exception message" diff --git a/activesupport/test/autoloading_fixtures/requires_constant.rb b/activesupport/test/autoloading_fixtures/requires_constant.rb index 14804a0de0..f04dcc4091 100644 --- a/activesupport/test/autoloading_fixtures/requires_constant.rb +++ b/activesupport/test/autoloading_fixtures/requires_constant.rb @@ -2,4 +2,3 @@ require "loaded_constant" module RequiresConstant end - diff --git a/activesupport/test/autoloading_fixtures/throws.rb b/activesupport/test/autoloading_fixtures/throws.rb new file mode 100644 index 0000000000..e1d96cc512 --- /dev/null +++ b/activesupport/test/autoloading_fixtures/throws.rb @@ -0,0 +1,4 @@ +Throws = 1 +_ = A::B # Autoloading recursion, expected to be discarded. + +throw :t diff --git a/activesupport/test/autoloading_fixtures/typo.rb b/activesupport/test/autoloading_fixtures/typo.rb index 8e047f5fd4..0ffe07c2b3 100644 --- a/activesupport/test/autoloading_fixtures/typo.rb +++ b/activesupport/test/autoloading_fixtures/typo.rb @@ -1,2 +1 @@ TypO = 1 - diff --git a/activesupport/test/benchmarkable_test.rb b/activesupport/test/benchmarkable_test.rb index 04d4f5e503..210b9cb9fd 100644 --- a/activesupport/test/benchmarkable_test.rb +++ b/activesupport/test/benchmarkable_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class BenchmarkableTest < ActiveSupport::TestCase include ActiveSupport::Benchmarkable @@ -36,27 +36,41 @@ class BenchmarkableTest < ActiveSupport::TestCase def test_with_message i_was_run = false - benchmark('test_run') { i_was_run = true } + benchmark("test_run") { i_was_run = true } assert i_was_run - assert_last_logged 'test_run' + assert_last_logged "test_run" + end + + def test_with_silence + assert_difference "buffer.count", +2 do + benchmark("test_run") do + logger.info "SOMETHING" + end + end + + assert_difference "buffer.count", +1 do + benchmark("test_run", silence: true) do + logger.info "NOTHING" + end + end end def test_within_level logger.level = ActiveSupport::Logger::DEBUG - benchmark('included_debug_run', :level => :debug) { } - assert_last_logged 'included_debug_run' + benchmark("included_debug_run", level: :debug) {} + assert_last_logged "included_debug_run" end def test_outside_level logger.level = ActiveSupport::Logger::ERROR - benchmark('skipped_debug_run', :level => :debug) { } + benchmark("skipped_debug_run", level: :debug) {} assert_no_match(/skipped_debug_run/, buffer.last) ensure logger.level = ActiveSupport::Logger::DEBUG end private - def assert_last_logged(message = 'Benchmarking') + def assert_last_logged(message = "Benchmarking") assert_match(/^#{message} \(.*\)$/, buffer.last) end end diff --git a/activesupport/test/broadcast_logger_test.rb b/activesupport/test/broadcast_logger_test.rb index 6d4e3b74f7..4b74f1313e 100644 --- a/activesupport/test/broadcast_logger_test.rb +++ b/activesupport/test/broadcast_logger_test.rb @@ -1,77 +1,149 @@ -require 'abstract_unit' +require "abstract_unit" module ActiveSupport class BroadcastLoggerTest < TestCase attr_reader :logger, :log1, :log2 - def setup + + setup do @log1 = FakeLogger.new @log2 = FakeLogger.new @log1.extend Logger.broadcast @log2 @logger = @log1 end - def test_debug - logger.debug "foo" - assert_equal 'foo', log1.adds.first[2] - assert_equal 'foo', log2.adds.first[2] + Logger::Severity.constants.each do |level_name| + method = level_name.downcase + level = Logger::Severity.const_get(level_name) + + test "##{method} adds the message to all loggers" do + logger.send(method, "msg") + + assert_equal [level, "msg", nil], log1.adds.first + assert_equal [level, "msg", nil], log2.adds.first + end end - def test_close + test "#close broadcasts to all loggers" do logger.close - assert log1.closed, 'should be closed' - assert log2.closed, 'should be closed' + + assert log1.closed, "should be closed" + assert log2.closed, "should be closed" end - def test_chevrons + test "#<< shovels the value into all loggers" do logger << "foo" + assert_equal %w{ foo }, log1.chevrons assert_equal %w{ foo }, log2.chevrons end - def test_level - assert_nil logger.level - logger.level = 10 - assert_equal 10, log1.level - assert_equal 10, log2.level + test "#level= assigns the level to all loggers" do + assert_equal ::Logger::DEBUG, logger.level + logger.level = ::Logger::FATAL + + assert_equal ::Logger::FATAL, log1.level + assert_equal ::Logger::FATAL, log2.level end - def test_progname + test "#progname= assigns to all the loggers" do assert_nil logger.progname - logger.progname = 10 - assert_equal 10, log1.progname - assert_equal 10, log2.progname + logger.progname = ::Logger::FATAL + + assert_equal ::Logger::FATAL, log1.progname + assert_equal ::Logger::FATAL, log2.progname end - def test_formatter + test "#formatter= assigns to all the loggers" do assert_nil logger.formatter - logger.formatter = 10 - assert_equal 10, log1.formatter - assert_equal 10, log2.formatter + logger.formatter = ::Logger::FATAL + + assert_equal ::Logger::FATAL, log1.formatter + assert_equal ::Logger::FATAL, log2.formatter + end + + test "#local_level= assigns the local_level to all loggers" do + assert_equal ::Logger::DEBUG, logger.local_level + logger.local_level = ::Logger::FATAL + + assert_equal ::Logger::FATAL, log1.local_level + assert_equal ::Logger::FATAL, log2.local_level + end + + test "#silence silences all loggers below the default level of ERROR" do + logger.silence do + logger.debug "test" + end + + assert_equal [], log1.adds + assert_equal [], log2.adds + end + + test "#silence does not silence at or above ERROR" do + logger.silence do + logger.error "from error" + logger.unknown "from unknown" + end + + assert_equal [[::Logger::ERROR, "from error", nil], [::Logger::UNKNOWN, "from unknown", nil]], log1.adds + assert_equal [[::Logger::ERROR, "from error", nil], [::Logger::UNKNOWN, "from unknown", nil]], log2.adds + end + + test "#silence allows you to override the silence level" do + logger.silence(::Logger::FATAL) do + logger.error "unseen" + logger.fatal "seen" + end + + assert_equal [[::Logger::FATAL, "seen", nil]], log1.adds + assert_equal [[::Logger::FATAL, "seen", nil]], log2.adds end class FakeLogger + include LoggerSilence + attr_reader :adds, :closed, :chevrons - attr_accessor :level, :progname, :formatter + attr_accessor :level, :progname, :formatter, :local_level def initialize - @adds = [] - @closed = false - @chevrons = [] - @level = nil - @progname = nil - @formatter = nil + @adds = [] + @closed = false + @chevrons = [] + @level = ::Logger::DEBUG + @local_level = ::Logger::DEBUG + @progname = nil + @formatter = nil + end + + def debug(message, &block) + add(::Logger::DEBUG, message, &block) + end + + def info(message, &block) + add(::Logger::INFO, message, &block) + end + + def warn(message, &block) + add(::Logger::WARN, message, &block) + end + + def error(message, &block) + add(::Logger::ERROR, message, &block) + end + + def fatal(message, &block) + add(::Logger::FATAL, message, &block) end - def debug msg, &block - add(:omg, nil, msg, &block) + def unknown(message, &block) + add(::Logger::UNKNOWN, message, &block) end - def << x + def <<(x) @chevrons << x end - def add(*args) - @adds << args + def add(message_level, message=nil, progname=nil, &block) + @adds << [message_level, message, progname] if message_level >= local_level end def close diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index 4a299429f3..0df4173a1a 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -1,7 +1,9 @@ -require 'logger' -require 'abstract_unit' -require 'active_support/cache' -require 'dependencies_test_helpers' +require "logger" +require "abstract_unit" +require "active_support/cache" +require "dependencies_test_helpers" + +require "pathname" module ActiveSupport module Cache @@ -11,23 +13,35 @@ module ActiveSupport def test_local_cache_cleared_on_close key = "super awesome key" assert_nil LocalCacheRegistry.cache_for key - middleware = Middleware.new('<3', key).new(->(env) { - assert LocalCacheRegistry.cache_for(key), 'should have a cache' + middleware = Middleware.new("<3", key).new(->(env) { + assert LocalCacheRegistry.cache_for(key), "should have a cache" [200, {}, []] }) _, _, body = middleware.call({}) - assert LocalCacheRegistry.cache_for(key), 'should still have a cache' - body.each { } - assert LocalCacheRegistry.cache_for(key), 'should still have a cache' + assert LocalCacheRegistry.cache_for(key), "should still have a cache" + body.each {} + assert LocalCacheRegistry.cache_for(key), "should still have a cache" body.close assert_nil LocalCacheRegistry.cache_for(key) end + def test_local_cache_cleared_and_response_should_be_present_on_invalid_parameters_error + key = "super awesome key" + assert_nil LocalCacheRegistry.cache_for key + middleware = Middleware.new("<3", key).new(->(env) { + assert LocalCacheRegistry.cache_for(key), "should have a cache" + raise Rack::Utils::InvalidParameterError + }) + response = middleware.call({}) + assert response, "response should exist" + assert_nil LocalCacheRegistry.cache_for(key) + end + def test_local_cache_cleared_on_exception key = "super awesome key" assert_nil LocalCacheRegistry.cache_for key - middleware = Middleware.new('<3', key).new(->(env) { - assert LocalCacheRegistry.cache_for(key), 'should have a cache' + middleware = Middleware.new("<3", key).new(->(env) { + assert LocalCacheRegistry.cache_for(key), "should have a cache" raise }) assert_raises(RuntimeError) { middleware.call({}) } @@ -50,82 +64,92 @@ class CacheKeyTest < ActiveSupport::TestCase end end - entry = legacy.new 'foo' - assert_equal 'foo', entry.value + entry = legacy.new "foo" + assert_equal "foo", entry.value end def test_expand_cache_key - assert_equal '1/2/true', ActiveSupport::Cache.expand_cache_key([1, '2', true]) - assert_equal 'name/1/2/true', ActiveSupport::Cache.expand_cache_key([1, '2', true], :name) + assert_equal "1/2/true", ActiveSupport::Cache.expand_cache_key([1, "2", true]) + assert_equal "name/1/2/true", ActiveSupport::Cache.expand_cache_key([1, "2", true], :name) end def test_expand_cache_key_with_rails_cache_id - with_env('RAILS_CACHE_ID' => 'c99') do - assert_equal 'c99/foo', ActiveSupport::Cache.expand_cache_key(:foo) - assert_equal 'c99/foo', ActiveSupport::Cache.expand_cache_key([:foo]) - assert_equal 'c99/foo/bar', ActiveSupport::Cache.expand_cache_key([:foo, :bar]) - assert_equal 'nm/c99/foo', ActiveSupport::Cache.expand_cache_key(:foo, :nm) - assert_equal 'nm/c99/foo', ActiveSupport::Cache.expand_cache_key([:foo], :nm) - assert_equal 'nm/c99/foo/bar', ActiveSupport::Cache.expand_cache_key([:foo, :bar], :nm) + with_env("RAILS_CACHE_ID" => "c99") do + assert_equal "c99/foo", ActiveSupport::Cache.expand_cache_key(:foo) + assert_equal "c99/foo", ActiveSupport::Cache.expand_cache_key([:foo]) + assert_equal "c99/foo/bar", ActiveSupport::Cache.expand_cache_key([:foo, :bar]) + assert_equal "nm/c99/foo", ActiveSupport::Cache.expand_cache_key(:foo, :nm) + assert_equal "nm/c99/foo", ActiveSupport::Cache.expand_cache_key([:foo], :nm) + assert_equal "nm/c99/foo/bar", ActiveSupport::Cache.expand_cache_key([:foo, :bar], :nm) end end def test_expand_cache_key_with_rails_app_version - with_env('RAILS_APP_VERSION' => 'rails3') do - assert_equal 'rails3/foo', ActiveSupport::Cache.expand_cache_key(:foo) + with_env("RAILS_APP_VERSION" => "rails3") do + assert_equal "rails3/foo", ActiveSupport::Cache.expand_cache_key(:foo) end end def test_expand_cache_key_rails_cache_id_should_win_over_rails_app_version - with_env('RAILS_CACHE_ID' => 'c99', 'RAILS_APP_VERSION' => 'rails3') do - assert_equal 'c99/foo', ActiveSupport::Cache.expand_cache_key(:foo) + with_env("RAILS_CACHE_ID" => "c99", "RAILS_APP_VERSION" => "rails3") do + assert_equal "c99/foo", ActiveSupport::Cache.expand_cache_key(:foo) end end def test_expand_cache_key_respond_to_cache_key - key = 'foo' + key = "foo" def key.cache_key :foo_key end - assert_equal 'foo_key', ActiveSupport::Cache.expand_cache_key(key) + assert_equal "foo_key", ActiveSupport::Cache.expand_cache_key(key) end def test_expand_cache_key_array_with_something_that_responds_to_cache_key - key = 'foo' + key = "foo" def key.cache_key :foo_key end - assert_equal 'foo_key', ActiveSupport::Cache.expand_cache_key([key]) + assert_equal "foo_key", ActiveSupport::Cache.expand_cache_key([key]) end def test_expand_cache_key_of_nil - assert_equal '', ActiveSupport::Cache.expand_cache_key(nil) + assert_equal "", ActiveSupport::Cache.expand_cache_key(nil) end def test_expand_cache_key_of_false - assert_equal 'false', ActiveSupport::Cache.expand_cache_key(false) + assert_equal "false", ActiveSupport::Cache.expand_cache_key(false) end def test_expand_cache_key_of_true - assert_equal 'true', ActiveSupport::Cache.expand_cache_key(true) + assert_equal "true", ActiveSupport::Cache.expand_cache_key(true) end def test_expand_cache_key_of_array_like_object - assert_equal 'foo/bar/baz', ActiveSupport::Cache.expand_cache_key(%w{foo bar baz}.to_enum) + assert_equal "foo/bar/baz", ActiveSupport::Cache.expand_cache_key(%w{foo bar baz}.to_enum) end private - def with_env(kv) - old_values = {} - kv.each { |key, value| old_values[key], ENV[key] = ENV[key], value } - yield - ensure - old_values.each { |key, value| ENV[key] = value} - end + def with_env(kv) + old_values = {} + kv.each { |key, value| old_values[key], ENV[key] = ENV[key], value } + yield + ensure + old_values.each { |key, value| ENV[key] = value } + end end class CacheStoreSettingTest < ActiveSupport::TestCase + def test_memory_store_gets_created_if_no_arguments_passed_to_lookup_store_method + store = ActiveSupport::Cache.lookup_store + assert_kind_of(ActiveSupport::Cache::MemoryStore, store) + end + + def test_memory_store + store = ActiveSupport::Cache.lookup_store :memory_store + assert_kind_of(ActiveSupport::Cache::MemoryStore, store) + end + def test_file_fragment_cache_store store = ActiveSupport::Cache.lookup_store :file_store, "/path/to/cache/directory" assert_kind_of(ActiveSupport::Cache::FileStore, store) @@ -158,16 +182,16 @@ class CacheStoreSettingTest < ActiveSupport::TestCase def test_mem_cache_fragment_cache_store_with_multiple_servers assert_called_with(Dalli::Client, :new, [%w[localhost 192.168.1.1], {}]) do - store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", '192.168.1.1' + store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", "192.168.1.1" assert_kind_of(ActiveSupport::Cache::MemCacheStore, store) end end def test_mem_cache_fragment_cache_store_with_options - assert_called_with(Dalli::Client, :new, [%w[localhost 192.168.1.1], { :timeout => 10 }]) do - store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", '192.168.1.1', :namespace => 'foo', :timeout => 10 + assert_called_with(Dalli::Client, :new, [%w[localhost 192.168.1.1], { timeout: 10 }]) do + store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", "192.168.1.1", namespace: "foo", timeout: 10 assert_kind_of(ActiveSupport::Cache::MemCacheStore, store) - assert_equal 'foo', store.options[:namespace] + assert_equal "foo", store.options[:namespace] end end @@ -180,7 +204,7 @@ end class CacheStoreNamespaceTest < ActiveSupport::TestCase def test_static_namespace - cache = ActiveSupport::Cache.lookup_store(:memory_store, :namespace => "tester") + cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: "tester") cache.write("foo", "bar") assert_equal "bar", cache.read("foo") assert_equal "bar", cache.instance_variable_get(:@data)["tester:foo"].value @@ -188,15 +212,15 @@ class CacheStoreNamespaceTest < ActiveSupport::TestCase def test_proc_namespace test_val = "tester" - proc = lambda{test_val} - cache = ActiveSupport::Cache.lookup_store(:memory_store, :namespace => proc) + proc = lambda { test_val } + cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: proc) cache.write("foo", "bar") assert_equal "bar", cache.read("foo") assert_equal "bar", cache.instance_variable_get(:@data)["tester:foo"].value end def test_delete_matched_key_start - cache = ActiveSupport::Cache.lookup_store(:memory_store, :namespace => "tester") + cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: "tester") cache.write("foo", "bar") cache.write("fu", "baz") cache.delete_matched(/^fo/) @@ -205,7 +229,7 @@ class CacheStoreNamespaceTest < ActiveSupport::TestCase end def test_delete_matched_key - cache = ActiveSupport::Cache.lookup_store(:memory_store, :namespace => "foo") + cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: "foo") cache.write("foo", "bar") cache.write("fu", "baz") cache.delete_matched(/OO/i) @@ -217,127 +241,147 @@ end # Tests the base functionality that should be identical across all cache stores. module CacheStoreBehavior def test_should_read_and_write_strings - assert @cache.write('foo', 'bar') - assert_equal 'bar', @cache.read('foo') + assert @cache.write("foo", "bar") + assert_equal "bar", @cache.read("foo") end def test_should_overwrite - @cache.write('foo', 'bar') - @cache.write('foo', 'baz') - assert_equal 'baz', @cache.read('foo') + @cache.write("foo", "bar") + @cache.write("foo", "baz") + assert_equal "baz", @cache.read("foo") end def test_fetch_without_cache_miss - @cache.write('foo', 'bar') + @cache.write("foo", "bar") assert_not_called(@cache, :write) do - assert_equal 'bar', @cache.fetch('foo') { 'baz' } + assert_equal "bar", @cache.fetch("foo") { "baz" } end end def test_fetch_with_cache_miss - assert_called_with(@cache, :write, ['foo', 'baz', @cache.options]) do - assert_equal 'baz', @cache.fetch('foo') { 'baz' } + assert_called_with(@cache, :write, ["foo", "baz", @cache.options]) do + assert_equal "baz", @cache.fetch("foo") { "baz" } end end def test_fetch_with_cache_miss_passes_key_to_block cache_miss = false - assert_equal 3, @cache.fetch('foo') { |key| cache_miss = true; key.length } + assert_equal 3, @cache.fetch("foo") { |key| cache_miss = true; key.length } assert cache_miss cache_miss = false - assert_equal 3, @cache.fetch('foo') { |key| cache_miss = true; key.length } + assert_equal 3, @cache.fetch("foo") { |key| cache_miss = true; key.length } assert !cache_miss end def test_fetch_with_forced_cache_miss - @cache.write('foo', 'bar') + @cache.write("foo", "bar") assert_not_called(@cache, :read) do - assert_called_with(@cache, :write, ['foo', 'bar', @cache.options.merge(:force => true)]) do - @cache.fetch('foo', :force => true) { 'bar' } + assert_called_with(@cache, :write, ["foo", "bar", @cache.options.merge(force: true)]) do + @cache.fetch("foo", force: true) { "bar" } end end end def test_fetch_with_cached_nil - @cache.write('foo', nil) + @cache.write("foo", nil) assert_not_called(@cache, :write) do - assert_nil @cache.fetch('foo') { 'baz' } + assert_nil @cache.fetch("foo") { "baz" } end end + def test_fetch_with_forced_cache_miss_with_block + @cache.write("foo", "bar") + assert_equal "foo_bar", @cache.fetch("foo", force: true) { "foo_bar" } + end + + def test_fetch_with_forced_cache_miss_without_block + @cache.write("foo", "bar") + assert_raises(ArgumentError) do + @cache.fetch("foo", force: true) + end + + assert_equal "bar", @cache.read("foo") + end + def test_should_read_and_write_hash - assert @cache.write('foo', {:a => "b"}) - assert_equal({:a => "b"}, @cache.read('foo')) + assert @cache.write("foo", a: "b") + assert_equal({ a: "b" }, @cache.read("foo")) end def test_should_read_and_write_integer - assert @cache.write('foo', 1) - assert_equal 1, @cache.read('foo') + assert @cache.write("foo", 1) + assert_equal 1, @cache.read("foo") end def test_should_read_and_write_nil - assert @cache.write('foo', nil) - assert_equal nil, @cache.read('foo') + assert @cache.write("foo", nil) + assert_equal nil, @cache.read("foo") end def test_should_read_and_write_false - assert @cache.write('foo', false) - assert_equal false, @cache.read('foo') + assert @cache.write("foo", false) + assert_equal false, @cache.read("foo") end def test_read_multi - @cache.write('foo', 'bar') - @cache.write('fu', 'baz') - @cache.write('fud', 'biz') - assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi('foo', 'fu')) + @cache.write("foo", "bar") + @cache.write("fu", "baz") + @cache.write("fud", "biz") + assert_equal({ "foo" => "bar", "fu" => "baz" }, @cache.read_multi("foo", "fu")) end def test_read_multi_with_expires time = Time.now - @cache.write('foo', 'bar', :expires_in => 10) - @cache.write('fu', 'baz') - @cache.write('fud', 'biz') + @cache.write("foo", "bar", expires_in: 10) + @cache.write("fu", "baz") + @cache.write("fud", "biz") Time.stub(:now, time + 11) do - assert_equal({"fu" => "baz"}, @cache.read_multi('foo', 'fu')) + assert_equal({ "fu" => "baz" }, @cache.read_multi("foo", "fu")) end end def test_fetch_multi - @cache.write('foo', 'bar') - @cache.write('fud', 'biz') + @cache.write("foo", "bar") + @cache.write("fud", "biz") - values = @cache.fetch_multi('foo', 'fu', 'fud') { |value| value * 2 } + values = @cache.fetch_multi("foo", "fu", "fud") { |value| value * 2 } - assert_equal({ 'foo' => 'bar', 'fu' => 'fufu', 'fud' => 'biz' }, values) - assert_equal('fufu', @cache.read('fu')) + assert_equal({ "foo" => "bar", "fu" => "fufu", "fud" => "biz" }, values) + assert_equal("fufu", @cache.read("fu")) end def test_multi_with_objects cache_struct = Struct.new(:cache_key, :title) - foo = cache_struct.new('foo', 'FOO!') - bar = cache_struct.new('bar') + foo = cache_struct.new("foo", "FOO!") + bar = cache_struct.new("bar") - @cache.write('bar', 'BAM!') + @cache.write("bar", "BAM!") values = @cache.fetch_multi(foo, bar) { |object| object.title } - assert_equal({ foo => 'FOO!', bar => 'BAM!' }, values) + assert_equal({ foo => "FOO!", bar => "BAM!" }, values) + end + + def test_fetch_multi_without_block + assert_raises(ArgumentError) do + @cache.fetch_multi("foo") + end end def test_read_and_write_compressed_small_data - @cache.write('foo', 'bar', :compress => true) - assert_equal 'bar', @cache.read('foo') + @cache.write("foo", "bar", compress: true) + assert_equal "bar", @cache.read("foo") end def test_read_and_write_compressed_large_data - @cache.write('foo', 'bar', :compress => true, :compress_threshold => 2) - assert_equal 'bar', @cache.read('foo') + @cache.write("foo", "bar", compress: true, compress_threshold: 2) + assert_equal "bar", @cache.read("foo") end def test_read_and_write_compressed_nil - @cache.write('foo', nil, :compress => true) - assert_nil @cache.read('foo') + @cache.write("foo", nil, compress: true) + assert_nil @cache.read("foo") end def test_cache_key @@ -364,7 +408,7 @@ module CacheStoreBehavior end def test_hash_as_cache_key - @cache.write({:foo => 1, :fu => 2}, "bar") + @cache.write({ foo: 1, fu: 2 }, "bar") assert_equal "bar", @cache.read("foo=1/fu=2") end @@ -374,65 +418,65 @@ module CacheStoreBehavior end def test_exist - @cache.write('foo', 'bar') - assert_equal true, @cache.exist?('foo') - assert_equal false, @cache.exist?('bar') + @cache.write("foo", "bar") + assert_equal true, @cache.exist?("foo") + assert_equal false, @cache.exist?("bar") end def test_nil_exist - @cache.write('foo', nil) - assert @cache.exist?('foo') + @cache.write("foo", nil) + assert @cache.exist?("foo") end def test_delete - @cache.write('foo', 'bar') - assert @cache.exist?('foo') - assert @cache.delete('foo') - assert !@cache.exist?('foo') + @cache.write("foo", "bar") + assert @cache.exist?("foo") + assert @cache.delete("foo") + assert !@cache.exist?("foo") end def test_original_store_objects_should_not_be_immutable - bar = 'bar' - @cache.write('foo', bar) - assert_nothing_raised { bar.gsub!(/.*/, 'baz') } + bar = "bar" + @cache.write("foo", bar) + assert_nothing_raised { bar.gsub!(/.*/, "baz") } end def test_expires_in time = Time.local(2008, 4, 24) Time.stub(:now, time) do - @cache.write('foo', 'bar') - assert_equal 'bar', @cache.read('foo') + @cache.write("foo", "bar") + assert_equal "bar", @cache.read("foo") end Time.stub(:now, time + 30) do - assert_equal 'bar', @cache.read('foo') + assert_equal "bar", @cache.read("foo") end Time.stub(:now, time + 61) do - assert_nil @cache.read('foo') + assert_nil @cache.read("foo") end end def test_race_condition_protection_skipped_if_not_defined - @cache.write('foo', 'bar') - time = @cache.send(:read_entry, @cache.send(:normalize_key, 'foo', {}), {}).expires_at + @cache.write("foo", "bar") + time = @cache.send(:read_entry, @cache.send(:normalize_key, "foo", {}), {}).expires_at Time.stub(:now, Time.at(time)) do - result = @cache.fetch('foo') do - assert_equal nil, @cache.read('foo') - 'baz' + result = @cache.fetch("foo") do + assert_equal nil, @cache.read("foo") + "baz" end - assert_equal 'baz', result + assert_equal "baz", result end end def test_race_condition_protection_is_limited time = Time.now - @cache.write('foo', 'bar', :expires_in => 60) + @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') + result = @cache.fetch("foo", race_condition_ttl: 10) do + assert_equal nil, @cache.read("foo") "baz" end assert_equal "baz", result @@ -441,28 +485,28 @@ module CacheStoreBehavior def test_race_condition_protection_is_safe time = Time.now - @cache.write('foo', 'bar', :expires_in => 60) + @cache.write("foo", "bar", expires_in: 60) Time.stub(:now, time + 61) do begin - @cache.fetch('foo', :race_condition_ttl => 10) do - assert_equal 'bar', @cache.read('foo') + @cache.fetch("foo", race_condition_ttl: 10) do + assert_equal "bar", @cache.read("foo") raise ArgumentError.new end rescue ArgumentError end - assert_equal "bar", @cache.read('foo') + assert_equal "bar", @cache.read("foo") end Time.stub(:now, time + 91) do - assert_nil @cache.read('foo') + assert_nil @cache.read("foo") end end def test_race_condition_protection time = Time.now - @cache.write('foo', 'bar', :expires_in => 60) + @cache.write("foo", "bar", expires_in: 60) Time.stub(:now, time + 61) do - result = @cache.fetch('foo', :race_condition_ttl => 10) do - assert_equal 'bar', @cache.read('foo') + result = @cache.fetch("foo", race_condition_ttl: 10) do + assert_equal "bar", @cache.read("foo") "baz" end assert_equal "baz", result @@ -471,23 +515,23 @@ module CacheStoreBehavior def test_crazy_key_characters crazy_key = "#/:*(<+=> )&$%@?;'\"\'`~-" - assert @cache.write(crazy_key, "1", :raw => true) + assert @cache.write(crazy_key, "1", raw: true) assert_equal "1", @cache.read(crazy_key) assert_equal "1", @cache.fetch(crazy_key) assert @cache.delete(crazy_key) - assert_equal "2", @cache.fetch(crazy_key, :raw => true) { "2" } + assert_equal "2", @cache.fetch(crazy_key, raw: true) { "2" } assert_equal 3, @cache.increment(crazy_key) assert_equal 2, @cache.decrement(crazy_key) end def test_really_long_keys key = "" - 900.times{key << "x"} + 900.times { key << "x" } assert @cache.write(key, "bar") assert_equal "bar", @cache.read(key) assert_equal "bar", @cache.fetch(key) assert_nil @cache.read("#{key}x") - assert_equal({key => "bar"}, @cache.read_multi(key)) + assert_equal({ key => "bar" }, @cache.read_multi(key)) assert @cache.delete(key) end @@ -497,10 +541,10 @@ module CacheStoreBehavior ActiveSupport::Notifications.subscribe "cache_read.active_support" do |*args| @events << ActiveSupport::Notifications::Event.new(*args) end - assert @cache.write(key, "1", :raw => true) + assert @cache.write(key, "1", raw: true) assert @cache.fetch(key) {} assert_equal 1, @events.length - assert_equal 'cache_read.active_support', @events[0].name + assert_equal "cache_read.active_support", @events[0].name assert_equal :fetch, @events[0].payload[:super_operation] assert @events[0].payload[:hit] ensure @@ -514,9 +558,9 @@ module CacheStoreBehavior end assert_not @cache.fetch("bad_key") {} assert_equal 3, @events.length - assert_equal 'cache_read.active_support', @events[0].name - assert_equal 'cache_generate.active_support', @events[1].name - assert_equal 'cache_write.active_support', @events[2].name + assert_equal "cache_read.active_support", @events[0].name + assert_equal "cache_generate.active_support", @events[1].name + assert_equal "cache_write.active_support", @events[2].name assert_equal :fetch, @events[0].payload[:super_operation] assert_not @events[0].payload[:hit] ensure @@ -537,11 +581,11 @@ module EncodedKeyCacheBehavior Encoding.list.each do |encoding| define_method "test_#{encoding.name.underscore}_encoded_values" do key = "foo".force_encoding(encoding) - assert @cache.write(key, "1", :raw => true) + assert @cache.write(key, "1", raw: true) assert_equal "1", @cache.read(key) assert_equal "1", @cache.fetch(key) assert @cache.delete(key) - assert_equal "2", @cache.fetch(key, :raw => true) { "2" } + assert_equal "2", @cache.fetch(key, raw: true) { "2" } assert_equal 3, @cache.increment(key) assert_equal 2, @cache.decrement(key) end @@ -549,18 +593,18 @@ module EncodedKeyCacheBehavior def test_common_utf8_values key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8) - assert @cache.write(key, "1", :raw => true) + assert @cache.write(key, "1", raw: true) assert_equal "1", @cache.read(key) assert_equal "1", @cache.fetch(key) assert @cache.delete(key) - assert_equal "2", @cache.fetch(key, :raw => true) { "2" } + assert_equal "2", @cache.fetch(key, raw: true) { "2" } assert_equal 3, @cache.increment(key) assert_equal 2, @cache.decrement(key) end def test_retains_encoding key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8) - assert @cache.write(key, "1", :raw => true) + assert @cache.write(key, "1", raw: true) assert_equal Encoding::UTF_8, key.encoding end end @@ -581,122 +625,122 @@ end module CacheIncrementDecrementBehavior def test_increment - @cache.write('foo', 1, :raw => true) - assert_equal 1, @cache.read('foo').to_i - assert_equal 2, @cache.increment('foo') - assert_equal 2, @cache.read('foo').to_i - assert_equal 3, @cache.increment('foo') - assert_equal 3, @cache.read('foo').to_i - assert_nil @cache.increment('bar') + @cache.write("foo", 1, raw: true) + assert_equal 1, @cache.read("foo").to_i + assert_equal 2, @cache.increment("foo") + assert_equal 2, @cache.read("foo").to_i + assert_equal 3, @cache.increment("foo") + assert_equal 3, @cache.read("foo").to_i + assert_nil @cache.increment("bar") end def test_decrement - @cache.write('foo', 3, :raw => true) - assert_equal 3, @cache.read('foo').to_i - assert_equal 2, @cache.decrement('foo') - assert_equal 2, @cache.read('foo').to_i - assert_equal 1, @cache.decrement('foo') - assert_equal 1, @cache.read('foo').to_i - assert_nil @cache.decrement('bar') + @cache.write("foo", 3, raw: true) + assert_equal 3, @cache.read("foo").to_i + assert_equal 2, @cache.decrement("foo") + assert_equal 2, @cache.read("foo").to_i + assert_equal 1, @cache.decrement("foo") + assert_equal 1, @cache.read("foo").to_i + assert_nil @cache.decrement("bar") end end module LocalCacheBehavior def test_local_writes_are_persistent_on_the_remote_cache retval = @cache.with_local_cache do - @cache.write('foo', 'bar') + @cache.write("foo", "bar") end assert retval - assert_equal 'bar', @cache.read('foo') + assert_equal "bar", @cache.read("foo") end def test_clear_also_clears_local_cache @cache.with_local_cache do - @cache.write('foo', 'bar') + @cache.write("foo", "bar") @cache.clear - assert_nil @cache.read('foo') + assert_nil @cache.read("foo") end - assert_nil @cache.read('foo') + assert_nil @cache.read("foo") end def test_local_cache_of_write @cache.with_local_cache do - @cache.write('foo', 'bar') - @peek.delete('foo') - assert_equal 'bar', @cache.read('foo') + @cache.write("foo", "bar") + @peek.delete("foo") + assert_equal "bar", @cache.read("foo") end end def test_local_cache_of_read - @cache.write('foo', 'bar') + @cache.write("foo", "bar") @cache.with_local_cache do - assert_equal 'bar', @cache.read('foo') + assert_equal "bar", @cache.read("foo") end end def test_local_cache_of_read_nil @cache.with_local_cache do - assert_equal nil, @cache.read('foo') - @cache.send(:bypass_local_cache) { @cache.write 'foo', 'bar' } - assert_equal nil, @cache.read('foo') + assert_equal nil, @cache.read("foo") + @cache.send(:bypass_local_cache) { @cache.write "foo", "bar" } + assert_equal nil, @cache.read("foo") end end def test_local_cache_fetch @cache.with_local_cache do - @cache.send(:local_cache).write 'foo', 'bar' - assert_equal 'bar', @cache.send(:local_cache).fetch('foo') + @cache.send(:local_cache).write "foo", "bar" + assert_equal "bar", @cache.send(:local_cache).fetch("foo") end end def test_local_cache_of_write_nil @cache.with_local_cache do - assert @cache.write('foo', nil) - assert_nil @cache.read('foo') - @peek.write('foo', 'bar') - assert_nil @cache.read('foo') + assert @cache.write("foo", nil) + assert_nil @cache.read("foo") + @peek.write("foo", "bar") + assert_nil @cache.read("foo") end end def test_local_cache_of_delete @cache.with_local_cache do - @cache.write('foo', 'bar') - @cache.delete('foo') - assert_nil @cache.read('foo') + @cache.write("foo", "bar") + @cache.delete("foo") + assert_nil @cache.read("foo") end end def test_local_cache_of_exist @cache.with_local_cache do - @cache.write('foo', 'bar') - @peek.delete('foo') - assert @cache.exist?('foo') + @cache.write("foo", "bar") + @peek.delete("foo") + assert @cache.exist?("foo") end end def test_local_cache_of_increment @cache.with_local_cache do - @cache.write('foo', 1, :raw => true) - @peek.write('foo', 2, :raw => true) - @cache.increment('foo') - assert_equal 3, @cache.read('foo') + @cache.write("foo", 1, raw: true) + @peek.write("foo", 2, raw: true) + @cache.increment("foo") + assert_equal 3, @cache.read("foo") end end def test_local_cache_of_decrement @cache.with_local_cache do - @cache.write('foo', 1, :raw => true) - @peek.write('foo', 3, :raw => true) - @cache.decrement('foo') - assert_equal 2, @cache.read('foo') + @cache.write("foo", 1, raw: true) + @peek.write("foo", 3, raw: true) + @cache.decrement("foo") + assert_equal 2, @cache.read("foo") end end def test_middleware app = lambda { |env| - result = @cache.write('foo', 'bar') - assert_equal 'bar', @cache.read('foo') # make sure 'foo' was written + result = @cache.write("foo", "bar") + assert_equal "bar", @cache.read("foo") # make sure 'foo' was written assert result [200, {}, []] } @@ -707,9 +751,9 @@ module LocalCacheBehavior def test_can_call_deprecated_set_cache_value @cache.with_local_cache do assert_deprecated "`set_cache_value` is deprecated" do - @cache.send(:set_cache_value, 1, 'foo', :ignored, {}) + @cache.send(:set_cache_value, 1, "foo", :ignored, {}) end - assert_equal 1, @cache.read('foo') + assert_equal 1, @cache.read("foo") end end end @@ -718,14 +762,14 @@ module AutoloadingCacheBehavior include DependenciesTestHelpers def test_simple_autoloading with_autoloading_fixtures do - @cache.write('foo', EM.new) + @cache.write("foo", EM.new) end remove_constants(:EM) ActiveSupport::Dependencies.clear with_autoloading_fixtures do - assert_kind_of EM, @cache.read('foo') + assert_kind_of EM, @cache.read("foo") end remove_constants(:EM) @@ -734,14 +778,14 @@ module AutoloadingCacheBehavior def test_two_classes_autoloading with_autoloading_fixtures do - @cache.write('foo', [EM.new, ClassFolder.new]) + @cache.write("foo", [EM.new, ClassFolder.new]) end remove_constants(:EM, :ClassFolder) ActiveSupport::Dependencies.clear with_autoloading_fixtures do - loaded = @cache.read('foo') + loaded = @cache.read("foo") assert_kind_of Array, loaded assert_equal 2, loaded.size assert_kind_of EM, loaded[0] @@ -756,9 +800,9 @@ end class FileStoreTest < ActiveSupport::TestCase def setup Dir.mkdir(cache_dir) unless File.exist?(cache_dir) - @cache = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, :expires_in => 60) - @peek = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, :expires_in => 60) - @cache_with_pathname = ActiveSupport::Cache.lookup_store(:file_store, Pathname.new(cache_dir), :expires_in => 60) + @cache = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, expires_in: 60) + @peek = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, expires_in: 60) + @cache_with_pathname = ActiveSupport::Cache.lookup_store(:file_store, Pathname.new(cache_dir), expires_in: 60) @buffer = StringIO.new @cache.logger = ActiveSupport::Logger.new(@buffer) @@ -770,7 +814,7 @@ class FileStoreTest < ActiveSupport::TestCase end def cache_dir - File.join(Dir.pwd, 'tmp_cache') + File.join(Dir.pwd, "tmp_cache") end include CacheStoreBehavior @@ -793,11 +837,6 @@ class FileStoreTest < ActiveSupport::TestCase @cache.clear end - def test_long_keys - @cache.write("a"*10000, 1) - assert_equal 1, @cache.read("a"*10000) - end - def test_long_uri_encoded_keys @cache.write("%"*870, 1) assert_equal 1, @cache.read("%"*870) @@ -820,7 +859,7 @@ class FileStoreTest < ActiveSupport::TestCase key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}" path = @cache.send(:normalize_key, key, {}) Dir::Tmpname.create(path) do |tmpname, n, opts| - assert File.basename(tmpname+'.lock').length <= 255, "Temp filename too long: #{File.basename(tmpname+'.lock').length}" + assert File.basename(tmpname+".lock").length <= 255, "Temp filename too long: #{File.basename(tmpname+'.lock').length}" end end @@ -829,28 +868,28 @@ class FileStoreTest < ActiveSupport::TestCase def test_key_transformation_max_filename_size key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}B" path = @cache.send(:normalize_key, key, {}) - assert path.split('/').all? { |dir_name| dir_name.size <= ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE} - assert_equal 'B', File.basename(path) + assert path.split("/").all? { |dir_name| dir_name.size <= ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE } + assert_equal "B", File.basename(path) end # If nothing has been stored in the cache, there is a chance the cache directory does not yet exist # Ensure delete_matched gracefully handles this case def test_delete_matched_when_cache_directory_does_not_exist - assert_nothing_raised(Exception) do - ActiveSupport::Cache::FileStore.new('/test/cache/directory').delete_matched(/does_not_exist/) + assert_nothing_raised do + ActiveSupport::Cache::FileStore.new("/test/cache/directory").delete_matched(/does_not_exist/) end end def test_delete_does_not_delete_empty_parent_dir - sub_cache_dir = File.join(cache_dir, 'subdir/') + sub_cache_dir = File.join(cache_dir, "subdir/") sub_cache_store = ActiveSupport::Cache::FileStore.new(sub_cache_dir) - assert_nothing_raised(Exception) do - assert sub_cache_store.write('foo', 'bar') - assert sub_cache_store.delete('foo') + assert_nothing_raised do + assert sub_cache_store.write("foo", "bar") + assert sub_cache_store.delete("foo") end assert File.exist?(cache_dir), "Parent of top level cache dir was deleted!" assert File.exist?(sub_cache_dir), "Top level cache dir was deleted!" - assert Dir.entries(sub_cache_dir).reject {|f| ActiveSupport::Cache::FileStore::EXCLUDED_DIRS.include?(f)}.empty? + assert Dir.entries(sub_cache_dir).reject { |f| ActiveSupport::Cache::FileStore::EXCLUDED_DIRS.include?(f) }.empty? end def test_log_exception_when_cache_read_fails @@ -862,14 +901,14 @@ class FileStoreTest < ActiveSupport::TestCase def test_cleanup_removes_all_expired_entries time = Time.now - @cache.write('foo', 'bar', expires_in: 10) - @cache.write('baz', 'qux') - @cache.write('quux', 'corge', expires_in: 20) + @cache.write("foo", "bar", expires_in: 10) + @cache.write("baz", "qux") + @cache.write("quux", "corge", expires_in: 20) Time.stub(:now, time + 15) do @cache.cleanup - assert_not @cache.exist?('foo') - assert @cache.exist?('baz') - assert @cache.exist?('quux') + assert_not @cache.exist?("foo") + assert @cache.exist?("baz") + assert @cache.exist?("quux") end end @@ -890,7 +929,7 @@ end class MemoryStoreTest < ActiveSupport::TestCase def setup @record_size = ActiveSupport::Cache.lookup_store(:memory_store).send(:cached_size, 1, ActiveSupport::Cache::Entry.new("aaaaaaaaaa")) - @cache = ActiveSupport::Cache.lookup_store(:memory_store, :expires_in => 60, :size => @record_size * 10 + 1) + @cache = ActiveSupport::Cache.lookup_store(:memory_store, expires_in: 60, size: @record_size * 10 + 1) end include CacheStoreBehavior @@ -950,7 +989,7 @@ class MemoryStoreTest < ActiveSupport::TestCase @cache.write(7, "gggggggggg") && sleep(0.001) @cache.write(8, "hhhhhhhhhh") && sleep(0.001) @cache.write(9, "iiiiiiiiii") && sleep(0.001) - long_key = '*' * 2 * @record_size + long_key = "*" * 2 * @record_size @cache.write(long_key, "llllllllll") assert @cache.exist?(long_key) assert @cache.exist?(9) @@ -984,18 +1023,18 @@ class MemoryStoreTest < ActiveSupport::TestCase def test_write_with_unless_exist assert_equal true, @cache.write(1, "aaaaaaaaaa") - assert_equal false, @cache.write(1, "aaaaaaaaaa", :unless_exist => true) + assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true) @cache.write(1, nil) - assert_equal false, @cache.write(1, "aaaaaaaaaa", :unless_exist => true) + assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true) end end class MemCacheStoreTest < ActiveSupport::TestCase - require 'dalli' + require "dalli" begin - ss = Dalli::Client.new('localhost:11211').stats - raise Dalli::DalliError unless ss['localhost:11211'] + ss = Dalli::Client.new("localhost:11211").stats + raise Dalli::DalliError unless ss["localhost:11211"] MEMCACHE_UP = true rescue Dalli::DalliError @@ -1006,7 +1045,7 @@ class MemCacheStoreTest < ActiveSupport::TestCase def setup skip "memcache server is not up" unless MEMCACHE_UP - @cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :expires_in => 60) + @cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, expires_in: 60) @peek = ActiveSupport::Cache.lookup_store(:mem_cache_store) @data = @cache.instance_variable_get(:@data) @cache.clear @@ -1021,21 +1060,21 @@ class MemCacheStoreTest < ActiveSupport::TestCase include AutoloadingCacheBehavior def test_raw_values - cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true) + cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true) cache.clear cache.write("foo", 2) assert_equal "2", cache.read("foo") end def test_raw_values_with_marshal - cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true) + cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true) cache.clear cache.write("foo", Marshal.dump([])) assert_equal [], cache.read("foo") end def test_local_cache_raw_values - cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true) + cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true) cache.clear cache.with_local_cache do cache.write("foo", 2) @@ -1044,7 +1083,7 @@ class MemCacheStoreTest < ActiveSupport::TestCase end def test_local_cache_raw_values_with_marshal - cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true) + cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true) cache.clear cache.with_local_cache do cache.write("foo", Marshal.dump([])) @@ -1053,11 +1092,11 @@ class MemCacheStoreTest < ActiveSupport::TestCase end def test_read_should_return_a_different_object_id_each_time_it_is_called - @cache.write('foo', 'bar') - value = @cache.read('foo') - assert_not_equal value.object_id, @cache.read('foo').object_id - value << 'bingo' - assert_not_equal value, @cache.read('foo') + @cache.write("foo", "bar") + value = @cache.read("foo") + assert_not_equal value.object_id, @cache.read("foo").object_id + value << "bingo" + assert_not_equal value, @cache.read("foo") end def test_can_call_deprecated_escape_key @@ -1095,12 +1134,12 @@ class NullStoreTest < ActiveSupport::TestCase end def test_increment - @cache.write("name", 1, :raw => true) + @cache.write("name", 1, raw: true) assert_nil @cache.increment("name") end def test_decrement - @cache.write("name", 1, :raw => true) + @cache.write("name", 1, raw: true) assert_nil @cache.increment("name") end @@ -1130,12 +1169,12 @@ class CacheStoreLoggerTest < ActiveSupport::TestCase end def test_logging - @cache.fetch('foo') { 'bar' } + @cache.fetch("foo") { "bar" } assert @buffer.string.present? end def test_log_with_string_namespace - @cache.fetch('foo', {namespace: 'string_namespace'}) { 'bar' } + @cache.fetch("foo", namespace: "string_namespace") { "bar" } assert_match %r{string_namespace:foo}, @buffer.string end @@ -1143,39 +1182,30 @@ class CacheStoreLoggerTest < ActiveSupport::TestCase proc = Proc.new do "proc_namespace" end - @cache.fetch('foo', {:namespace => proc}) { 'bar' } + @cache.fetch("foo", namespace: proc) { "bar" } assert_match %r{proc_namespace:foo}, @buffer.string end def test_mute_logging - @cache.mute { @cache.fetch('foo') { 'bar' } } + @cache.mute { @cache.fetch("foo") { "bar" } } assert @buffer.string.blank? end - - def test_multi_read_loggin - @cache.write 'hello', 'goodbye' - @cache.write 'world', 'earth' - - @cache.read_multi('hello', 'world') - - assert_match "Caches multi read:\n- hello\n- world", @buffer.string - end end class CacheEntryTest < ActiveSupport::TestCase def test_expired entry = ActiveSupport::Cache::Entry.new("value") - assert !entry.expired?, 'entry not expired' - entry = ActiveSupport::Cache::Entry.new("value", :expires_in => 60) - assert !entry.expired?, 'entry not expired' + assert !entry.expired?, "entry not expired" + entry = ActiveSupport::Cache::Entry.new("value", expires_in: 60) + assert !entry.expired?, "entry not expired" Time.stub(:now, Time.now + 61) do - assert entry.expired?, 'entry is expired' + assert entry.expired?, "entry is expired" end end def test_compress_values value = "value" * 100 - entry = ActiveSupport::Cache::Entry.new(value, :compress => true, :compress_threshold => 1) + entry = ActiveSupport::Cache::Entry.new(value, compress: true, compress_threshold: 1) assert_equal value, entry.value assert(value.bytesize > entry.size, "value is compressed") end diff --git a/activesupport/test/callback_inheritance_test.rb b/activesupport/test/callback_inheritance_test.rb index 1adfe4edf4..9e2f7527e0 100644 --- a/activesupport/test/callback_inheritance_test.rb +++ b/activesupport/test/callback_inheritance_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class GrandParent include ActiveSupport::Callbacks @@ -9,8 +9,8 @@ class GrandParent end define_callbacks :dispatch - set_callback :dispatch, :before, :before1, :before2, :if => proc {|c| c.action_name == "index" || c.action_name == "update" } - set_callback :dispatch, :after, :after1, :after2, :if => proc {|c| c.action_name == "update" || c.action_name == "delete" } + set_callback :dispatch, :before, :before1, :before2, if: proc { |c| c.action_name == "index" || c.action_name == "update" } + set_callback :dispatch, :after, :after1, :after2, if: proc { |c| c.action_name == "update" || c.action_name == "delete" } def before1 @log << "before1" @@ -37,12 +37,12 @@ class GrandParent end class Parent < GrandParent - skip_callback :dispatch, :before, :before2, :unless => proc {|c| c.action_name == "update" } - skip_callback :dispatch, :after, :after2, :unless => proc {|c| c.action_name == "delete" } + skip_callback :dispatch, :before, :before2, unless: proc { |c| c.action_name == "update" } + skip_callback :dispatch, :after, :after2, unless: proc { |c| c.action_name == "delete" } end class Child < GrandParent - skip_callback :dispatch, :before, :before2, :unless => proc {|c| c.action_name == "update" }, :if => :state_open? + skip_callback :dispatch, :before, :before2, unless: proc { |c| c.action_name == "update" }, if: :state_open? def state_open? @state == :open diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb index a624473f46..783952c8c7 100644 --- a/activesupport/test/callbacks_test.rb +++ b/activesupport/test/callbacks_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module CallbacksTest class Record @@ -56,27 +56,31 @@ module CallbacksTest end class Person < Record + attr_accessor :save_fails + [: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, callback_object(callback_method_sym.to_s.gsub(/_save/, ""))) send(callback_method, CallbackClass) send(callback_method) { |model| model.history << [callback_method_sym, :block] } end def save - run_callbacks :save + run_callbacks :save do + raise "inside save" if save_fails + end end end class PersonSkipper < Person - skip_callback :save, :before, :before_save_method, :if => :yes - skip_callback :save, :after, :after_save_method, :unless => :yes - skip_callback :save, :after, :after_save_method, :if => :no - skip_callback :save, :before, :before_save_method, :unless => :no - skip_callback :save, :before, CallbackClass , :if => :yes + skip_callback :save, :before, :before_save_method, if: :yes + skip_callback :save, :after, :after_save_method, unless: :yes + skip_callback :save, :after, :after_save_method, if: :no + skip_callback :save, :before, :before_save_method, unless: :no + skip_callback :save, :before, CallbackClass , if: :yes def yes; true; end def no; false; end end @@ -89,7 +93,7 @@ module CallbacksTest define_callbacks :dispatch - set_callback :dispatch, :before, :log, :unless => proc {|c| c.action_name == :index || c.action_name == :show } + set_callback :dispatch, :before, :log, unless: proc { |c| c.action_name == :index || c.action_name == :show } set_callback :dispatch, :after, :log2 attr_reader :action_name, :logger @@ -114,7 +118,7 @@ module CallbacksTest end class Child < ParentController - skip_callback :dispatch, :before, :log, :if => proc {|c| c.action_name == :update} + skip_callback :dispatch, :before, :log, if: proc { |c| c.action_name == :update } skip_callback :dispatch, :after, :log2 end @@ -125,10 +129,10 @@ module CallbacksTest super end - before_save Proc.new {|r| r.history << [:before_save, :starts_true, :if] }, :if => :starts_true - before_save Proc.new {|r| r.history << [:before_save, :starts_false, :if] }, :if => :starts_false - before_save Proc.new {|r| r.history << [:before_save, :starts_true, :unless] }, :unless => :starts_true - before_save Proc.new {|r| r.history << [:before_save, :starts_false, :unless] }, :unless => :starts_false + before_save Proc.new { |r| r.history << [:before_save, :starts_true, :if] }, if: :starts_true + before_save Proc.new { |r| r.history << [:before_save, :starts_false, :if] }, if: :starts_false + before_save Proc.new { |r| r.history << [:before_save, :starts_true, :unless] }, unless: :starts_true + before_save Proc.new { |r| r.history << [:before_save, :starts_false, :unless] }, unless: :starts_false def starts_true if @@starts_true @@ -181,27 +185,25 @@ module CallbacksTest end end - - class ConditionalPerson < Record # proc - before_save Proc.new { |r| r.history << [:before_save, :proc] }, :if => Proc.new { |r| true } - before_save Proc.new { |r| r.history << "b00m" }, :if => Proc.new { |r| false } - before_save Proc.new { |r| r.history << [:before_save, :proc] }, :unless => Proc.new { |r| false } - before_save Proc.new { |r| r.history << "b00m" }, :unless => Proc.new { |r| true } + before_save Proc.new { |r| r.history << [:before_save, :proc] }, if: Proc.new { |r| true } + before_save Proc.new { |r| r.history << "b00m" }, if: Proc.new { |r| false } + before_save Proc.new { |r| r.history << [:before_save, :proc] }, unless: Proc.new { |r| false } + before_save Proc.new { |r| r.history << "b00m" }, unless: Proc.new { |r| true } # symbol - before_save Proc.new { |r| r.history << [:before_save, :symbol] }, :if => :yes - before_save Proc.new { |r| r.history << "b00m" }, :if => :no - before_save Proc.new { |r| r.history << [:before_save, :symbol] }, :unless => :no - before_save Proc.new { |r| r.history << "b00m" }, :unless => :yes + before_save Proc.new { |r| r.history << [:before_save, :symbol] }, if: :yes + before_save Proc.new { |r| r.history << "b00m" }, if: :no + 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' + 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" # 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 + 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 def yes; true; end def other_yes; true; end @@ -224,19 +226,20 @@ module CallbacksTest class AroundPerson < MySuper attr_reader :history + attr_accessor :save_fails - set_callback :save, :before, :nope, :if => :no - set_callback :save, :before, :nope, :unless => :yes + 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 } - set_callback :save, :before, :yup, :if => proc { true } - set_callback :save, :before, :yup, :unless => proc { false } + 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 } + set_callback :save, :before, :yup, if: proc { true } + set_callback :save, :before, :yup, unless: proc { false } set_callback :save, :around, :tweedle_dum - set_callback :save, :around, :w0tyes, :if => :yes - set_callback :save, :around, :w0tno, :if => :no + set_callback :save, :around, :w0tyes, if: :yes + set_callback :save, :around, :w0tno, if: :no set_callback :save, :around, :tweedle_deedle def no; false; end @@ -287,6 +290,7 @@ module CallbacksTest def save run_callbacks :save do + raise "inside save" if save_fails @history << "running" end end @@ -323,7 +327,7 @@ module CallbacksTest define_callbacks :save attr_reader :stuff - set_callback :save, :before, :action, :if => :yes + set_callback :save, :before, :action, if: :yes def yes() true end @@ -361,7 +365,6 @@ module CallbacksTest end class ExtendCallbacks - include ActiveSupport::Callbacks define_callbacks :save @@ -405,6 +408,71 @@ module CallbacksTest end end + class CallStackTest < ActiveSupport::TestCase + def test_tidy_call_stack + around = AroundPerson.new + around.save_fails = true + + exception = (around.save rescue $!) + + # Make sure we have the exception we're expecting + assert_equal "inside save", exception.message + + call_stack = exception.backtrace_locations + call_stack.pop caller_locations(0).size + + # Yes, this looks like an implementation test, but it's the least + # obtuse way of asserting that there aren't a load of entries in + # the call stack for each callback. + # + # If you've renamed a method, or squeezed more lines out, go ahead + # and update this assertion. But if you're here because a + # refactoring added new lines, please reconsider. + + # As shown here, our current budget is one line for run_callbacks + # itself, plus N+1 lines where N is the number of :around + # callbacks that have been invoked, if there are any (plus + # whatever the callbacks do themselves, of course). + + assert_equal [ + "block in save", + "block in run_callbacks", + "tweedle_deedle", + "block in run_callbacks", + "w0tyes", + "block in run_callbacks", + "tweedle_dum", + "block in run_callbacks", + ("call" if RUBY_VERSION < "2.3"), + "run_callbacks", + "save" + ].compact, call_stack.map(&:label) + end + + def test_short_call_stack + person = Person.new + person.save_fails = true + + exception = (person.save rescue $!) + + # Make sure we have the exception we're expecting + assert_equal "inside save", exception.message + + call_stack = exception.backtrace_locations + call_stack.pop caller_locations(0).size + + # This budget much simpler: with no :around callbacks invoked, + # there should be just one line. run_callbacks yields directly + # back to its caller. + + assert_equal [ + "block in save", + "run_callbacks", + "save" + ], call_stack.map(&:label) + end + end + class AroundCallbackResultTest < ActiveSupport::TestCase def test_save_around around = AroundPersonResult.new @@ -453,7 +521,6 @@ module CallbacksTest end class CallbacksTest < ActiveSupport::TestCase - def test_save_person person = Person.new assert_equal [], person.history @@ -491,8 +558,6 @@ module CallbacksTest end end - - class ResetCallbackTest < ActiveSupport::TestCase def test_save_conditional_person person = CleanPerson.new @@ -637,7 +702,7 @@ module CallbacksTest class CustomScopeObject include ActiveSupport::Callbacks - define_callbacks :save, :scope => [:kind, :name] + define_callbacks :save, scope: [:kind, :name] set_callback :save, :before, CallbackObject.new attr_accessor :record @@ -810,7 +875,7 @@ module CallbacksTest class WriterSkipper < Person attr_accessor :age - skip_callback :save, :before, :before_save_method, :if => lambda {self.age > 21} + skip_callback :save, :before, :before_save_method, if: -> { age > 21 } end class WriterCallbacksTest < ActiveSupport::TestCase @@ -885,7 +950,7 @@ module CallbacksTest def test_proc_arity_2 assert_raises(ArgumentError) do - klass = build_class(->(x,y) { }) + klass = build_class(->(x,y) {}) klass.new.run end end @@ -903,7 +968,7 @@ module CallbacksTest Class.new { include ActiveSupport::Callbacks define_callbacks :foo - set_callback :foo, :before, :foo, :if => callback + set_callback :foo, :before, :foo, if: callback def foo; end def run; run_callbacks :foo; end } @@ -918,8 +983,8 @@ module CallbacksTest } klass = Class.new { include ActiveSupport::Callbacks - define_callbacks :foo, :scope => [:name] - set_callback :foo, :before, :foo, :if => callback + define_callbacks :foo, scope: [:name] + set_callback :foo, :before, :foo, if: callback def run; run_callbacks :foo; end private def foo; end @@ -964,7 +1029,7 @@ module CallbacksTest def test_proc_arity2 assert_raises(ArgumentError) do - object = build_class(->(a,b) { }).new + object = build_class(->(a,b) {}).new object.run end end diff --git a/activesupport/test/class_cache_test.rb b/activesupport/test/class_cache_test.rb index b96f476ce6..c618fea81a 100644 --- a/activesupport/test/class_cache_test.rb +++ b/activesupport/test/class_cache_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/dependencies' +require "abstract_unit" +require "active_support/dependencies" module ActiveSupport module Dependencies diff --git a/activesupport/test/clean_backtrace_test.rb b/activesupport/test/clean_backtrace_test.rb index 05580352a9..5ed518cdb0 100644 --- a/activesupport/test/clean_backtrace_test.rb +++ b/activesupport/test/clean_backtrace_test.rb @@ -1,9 +1,9 @@ -require 'abstract_unit' +require "abstract_unit" class BacktraceCleanerFilterTest < ActiveSupport::TestCase def setup @bc = ActiveSupport::BacktraceCleaner.new - @bc.add_filter { |line| line.gsub("/my/prefix", '') } + @bc.add_filter { |line| line.gsub("/my/prefix", "") } end test "backtrace should filter all lines in a backtrace, removing prefixes" do @@ -20,13 +20,12 @@ class BacktraceCleanerFilterTest < ActiveSupport::TestCase test "backtrace should contain unaltered lines if they dont match a filter" do assert_equal "/my/other_prefix/my/class.rb", @bc.clean([ "/my/other_prefix/my/class.rb" ]).first end - end class BacktraceCleanerSilencerTest < ActiveSupport::TestCase def setup @bc = ActiveSupport::BacktraceCleaner.new - @bc.add_silencer { |line| line =~ /mongrel/ } + @bc.add_silencer { |line| line.include?("mongrel") } end test "backtrace should not contain lines that match the silencer" do @@ -44,8 +43,8 @@ end class BacktraceCleanerMultipleSilencersTest < ActiveSupport::TestCase def setup @bc = ActiveSupport::BacktraceCleaner.new - @bc.add_silencer { |line| line =~ /mongrel/ } - @bc.add_silencer { |line| line =~ /yolo/ } + @bc.add_silencer { |line| line.include?("mongrel") } + @bc.add_silencer { |line| line.include?("yolo") } end test "backtrace should not contain lines that match the silencers" do @@ -66,7 +65,7 @@ class BacktraceCleanerFilterAndSilencerTest < ActiveSupport::TestCase def setup @bc = ActiveSupport::BacktraceCleaner.new @bc.add_filter { |line| line.gsub("/mongrel", "") } - @bc.add_silencer { |line| line =~ /mongrel/ } + @bc.add_silencer { |line| line.include?("mongrel") } end test "backtrace should not silence lines that has first had their silence hook filtered out" do diff --git a/activesupport/test/clean_logger_test.rb b/activesupport/test/clean_logger_test.rb index 02693a97dc..cf37fc5639 100644 --- a/activesupport/test/clean_logger_test.rb +++ b/activesupport/test/clean_logger_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'stringio' -require 'active_support/logger' +require "abstract_unit" +require "stringio" +require "active_support/logger" class CleanLoggerTest < ActiveSupport::TestCase def setup @@ -9,14 +9,14 @@ class CleanLoggerTest < ActiveSupport::TestCase end def test_format_message - @logger.error 'error' + @logger.error "error" assert_equal "error\n", @out.string end def test_datetime_format @logger.formatter = Logger::Formatter.new @logger.formatter.datetime_format = "%Y-%m-%d" - @logger.debug 'debug' + @logger.debug "debug" assert_equal "%Y-%m-%d", @logger.formatter.datetime_format assert_match(/D, \[\d\d\d\d-\d\d-\d\d#\d+\] DEBUG -- : debug/, @out.string) end diff --git a/activesupport/test/concern_test.rb b/activesupport/test/concern_test.rb index 8ea701cfb7..95507c815d 100644 --- a/activesupport/test/concern_test.rb +++ b/activesupport/test/concern_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/concern' +require "abstract_unit" +require "active_support/concern" class ConcernTest < ActiveSupport::TestCase module Baz @@ -66,13 +66,13 @@ class ConcernTest < ActiveSupport::TestCase def test_module_is_included_normally @klass.include(Baz) assert_equal "baz", @klass.new.baz - assert @klass.included_modules.include?(ConcernTest::Baz) + assert_includes @klass.included_modules, ConcernTest::Baz end def test_class_methods_are_extended @klass.include(Baz) assert_equal "baz", @klass.baz - assert_equal ConcernTest::Baz::ClassMethods, (class << @klass; self.included_modules; end)[0] + assert_equal ConcernTest::Baz::ClassMethods, (class << @klass; included_modules; end)[0] end def test_class_methods_are_extended_only_on_expected_objects @@ -105,7 +105,7 @@ class ConcernTest < ActiveSupport::TestCase assert_equal "bar", @klass.new.bar assert_equal "bar+baz", @klass.new.baz assert_equal "bar's baz + baz", @klass.baz - assert @klass.included_modules.include?(ConcernTest::Bar) + assert_includes @klass.included_modules, ConcernTest::Bar end def test_dependencies_with_multiple_modules diff --git a/activesupport/test/configurable_test.rb b/activesupport/test/configurable_test.rb index 5d22ded2de..3cd6d2d4d0 100644 --- a/activesupport/test/configurable_test.rb +++ b/activesupport/test/configurable_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/configurable' +require "abstract_unit" +require "active_support/configurable" class ConfigurableActiveSupport < ActiveSupport::TestCase class Parent @@ -111,7 +111,7 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase end end - test 'the config_accessor method should not be publicly callable' do + test "the config_accessor method should not be publicly callable" do assert_raises NoMethodError do Class.new { include ActiveSupport::Configurable @@ -121,11 +121,11 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase def assert_method_defined(object, method) methods = object.public_methods.map(&:to_s) - assert methods.include?(method.to_s), "Expected #{methods.inspect} to include #{method.to_s.inspect}" + assert_includes methods, method.to_s, "Expected #{methods.inspect} to include #{method.to_s.inspect}" end def assert_method_not_defined(object, method) methods = object.public_methods.map(&:to_s) - assert !methods.include?(method.to_s), "Expected #{methods.inspect} to not include #{method.to_s.inspect}" + assert_not_includes methods, method.to_s, "Expected #{methods.inspect} to not include #{method.to_s.inspect}" end end diff --git a/activesupport/test/constantize_test_cases.rb b/activesupport/test/constantize_test_cases.rb index 1115bc0fd8..af2db8c991 100644 --- a/activesupport/test/constantize_test_cases.rb +++ b/activesupport/test/constantize_test_cases.rb @@ -1,4 +1,4 @@ -require 'dependencies_test_helpers' +require "dependencies_test_helpers" module Ace module Base @@ -100,10 +100,10 @@ module ConstantizeTestCases assert_nil yield("Ace::Gas::ConstantizeTestCases") assert_nil yield("#<Class:0x7b8b718b>::Nested_1") assert_nil yield("Ace::gas") - assert_nil yield('Object::ABC') - assert_nil yield('Object::Object::Object::ABC') - assert_nil yield('A::Object::B') - assert_nil yield('A::Object::Object::Object::B') + assert_nil yield("Object::ABC") + assert_nil yield("Object::Object::Object::ABC") + assert_nil yield("A::Object::B") + assert_nil yield("A::Object::Object::Object::B") assert_raises(NameError) do with_autoloading_fixtures do diff --git a/activesupport/test/core_ext/array/access_test.rb b/activesupport/test/core_ext/array/access_test.rb index 1d834667f0..a38ea36d00 100644 --- a/activesupport/test/core_ext/array/access_test.rb +++ b/activesupport/test/core_ext/array/access_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/array' +require "abstract_unit" +require "active_support/core_ext/array" class AccessTest < ActiveSupport::TestCase def test_from diff --git a/activesupport/test/core_ext/array/conversions_test.rb b/activesupport/test/core_ext/array/conversions_test.rb index 507e13f968..469efcd934 100644 --- a/activesupport/test/core_ext/array/conversions_test.rb +++ b/activesupport/test/core_ext/array/conversions_test.rb @@ -1,37 +1,42 @@ -require 'abstract_unit' -require 'active_support/core_ext/array' -require 'active_support/core_ext/big_decimal' -require 'active_support/core_ext/hash' -require 'active_support/core_ext/string' +require "abstract_unit" +require "active_support/core_ext/array" +require "active_support/core_ext/big_decimal" +require "active_support/core_ext/hash" +require "active_support/core_ext/string" class ToSentenceTest < ActiveSupport::TestCase def test_plain_array_to_sentence assert_equal "", [].to_sentence - assert_equal "one", ['one'].to_sentence - assert_equal "one and two", ['one', 'two'].to_sentence - assert_equal "one, two, and three", ['one', 'two', 'three'].to_sentence + assert_equal "one", ["one"].to_sentence + assert_equal "one and two", ["one", "two"].to_sentence + assert_equal "one, two, and three", ["one", "two", "three"].to_sentence end def test_to_sentence_with_words_connector - assert_equal "one two, and three", ['one', 'two', 'three'].to_sentence(words_connector: ' ') - assert_equal "one & two, and three", ['one', 'two', 'three'].to_sentence(words_connector: ' & ') - assert_equal "onetwo, and three", ['one', 'two', 'three'].to_sentence(words_connector: nil) + assert_equal "one two, and three", ["one", "two", "three"].to_sentence(words_connector: " ") + assert_equal "one & two, and three", ["one", "two", "three"].to_sentence(words_connector: " & ") + assert_equal "onetwo, and three", ["one", "two", "three"].to_sentence(words_connector: nil) end def test_to_sentence_with_last_word_connector - assert_equal "one, two, and also three", ['one', 'two', 'three'].to_sentence(last_word_connector: ', and also ') - assert_equal "one, twothree", ['one', 'two', 'three'].to_sentence(last_word_connector: nil) - assert_equal "one, two three", ['one', 'two', 'three'].to_sentence(last_word_connector: ' ') - assert_equal "one, two and three", ['one', 'two', 'three'].to_sentence(last_word_connector: ' and ') + assert_equal "one, two, and also three", ["one", "two", "three"].to_sentence(last_word_connector: ", and also ") + assert_equal "one, twothree", ["one", "two", "three"].to_sentence(last_word_connector: nil) + assert_equal "one, two three", ["one", "two", "three"].to_sentence(last_word_connector: " ") + assert_equal "one, two and three", ["one", "two", "three"].to_sentence(last_word_connector: " and ") + end + + def test_to_sentence_with_fallback_string + assert_equal "none", [].to_sentence(fallback_string: "none") + assert_equal "one, two, and three", ["one", "two", "three"].to_sentence(fallback_string: "none") end def test_two_elements - assert_equal "one and two", ['one', 'two'].to_sentence - assert_equal "one two", ['one', 'two'].to_sentence(two_words_connector: ' ') + assert_equal "one and two", ["one", "two"].to_sentence + assert_equal "one two", ["one", "two"].to_sentence(two_words_connector: " ") end def test_one_element - assert_equal "one", ['one'].to_sentence + assert_equal "one", ["one"].to_sentence end def test_one_element_not_same_object @@ -40,31 +45,31 @@ class ToSentenceTest < ActiveSupport::TestCase end def test_one_non_string_element - assert_equal '1', [1].to_sentence + assert_equal "1", [1].to_sentence end def test_does_not_modify_given_hash - options = { words_connector: ' ' } - assert_equal "one two, and three", ['one', 'two', 'three'].to_sentence(options) - assert_equal({ words_connector: ' ' }, options) + options = { words_connector: " " } + assert_equal "one two, and three", ["one", "two", "three"].to_sentence(options) + assert_equal({ words_connector: " " }, options) end def test_with_blank_elements - assert_equal ", one, , two, and three", [nil, 'one', '', 'two', 'three'].to_sentence + assert_equal ", one, , two, and three", [nil, "one", "", "two", "three"].to_sentence end def test_with_invalid_options exception = assert_raise ArgumentError do - ['one', 'two'].to_sentence(passing: 'invalid option') + ["one", "two"].to_sentence(passing: "invalid option") end - assert_equal exception.message, "Unknown key: :passing. Valid keys are: :words_connector, :two_words_connector, :last_word_connector, :locale" + assert_equal exception.message, "Unknown key: :passing. Valid keys are: :words_connector, :two_words_connector, :last_word_connector, :locale, :fallback_string" end def test_always_returns_string - assert_instance_of String, [ActiveSupport::SafeBuffer.new('one')].to_sentence - assert_instance_of String, [ActiveSupport::SafeBuffer.new('one'), 'two'].to_sentence - assert_instance_of String, [ActiveSupport::SafeBuffer.new('one'), 'two', 'three'].to_sentence + assert_instance_of String, [ActiveSupport::SafeBuffer.new("one")].to_sentence + assert_instance_of String, [ActiveSupport::SafeBuffer.new("one"), "two"].to_sentence + assert_instance_of String, [ActiveSupport::SafeBuffer.new("one"), "two", "three"].to_sentence end end @@ -88,32 +93,32 @@ class ToXmlTest < ActiveSupport::TestCase def test_to_xml_with_hash_elements xml = [ { name: "David", age: 26, age_in_millis: 820497600000 }, - { name: "Jason", age: 31, age_in_millis: BigDecimal.new('1.0') } + { name: "Jason", age: 31, age_in_millis: BigDecimal.new("1.0") } ].to_xml(skip_instruct: true, indent: 0) assert_equal '<objects type="array"><object>', xml.first(30) - assert xml.include?(%(<age type="integer">26</age>)), xml - assert xml.include?(%(<age-in-millis type="integer">820497600000</age-in-millis>)), xml - assert xml.include?(%(<name>David</name>)), xml - assert xml.include?(%(<age type="integer">31</age>)), xml - assert xml.include?(%(<age-in-millis type="decimal">1.0</age-in-millis>)), xml - assert xml.include?(%(<name>Jason</name>)), xml + assert_includes xml, %(<age type="integer">26</age>), xml + assert_includes xml, %(<age-in-millis type="integer">820497600000</age-in-millis>), xml + assert_includes xml, %(<name>David</name>), xml + assert_includes xml, %(<age type="integer">31</age>), xml + assert_includes xml, %(<age-in-millis type="decimal">1.0</age-in-millis>), xml + assert_includes xml, %(<name>Jason</name>), xml end def test_to_xml_with_non_hash_elements - xml = [1, 2, 3].to_xml(skip_instruct: true, indent: 0) + xml = %w[1 2 3].to_xml(skip_instruct: true, indent: 0) - assert_equal '<fixnums type="array"><fixnum', xml.first(29) - assert xml.include?(%(<fixnum type="integer">2</fixnum>)), xml + assert_equal '<strings type="array"><string', xml.first(29) + assert_includes xml, %(<string>2</string>), xml end def test_to_xml_with_non_hash_different_type_elements - xml = [1, 2.0, '3'].to_xml(skip_instruct: true, indent: 0) + xml = [1, 2.0, "3"].to_xml(skip_instruct: true, indent: 0) assert_equal '<objects type="array"><object', xml.first(29) - assert xml.include?(%(<object type="integer">1</object>)), xml - assert xml.include?(%(<object type="float">2.0</object>)), xml - assert xml.include?(%(object>3</object>)), xml + assert_includes xml, %(<object type="integer">1</object>), xml + assert_includes xml, %(<object type="float">2.0</object>), xml + assert_includes xml, %(object>3</object>), xml end def test_to_xml_with_dedicated_name @@ -130,10 +135,10 @@ class ToXmlTest < ActiveSupport::TestCase ].to_xml(skip_instruct: true, skip_types: true, indent: 0) assert_equal "<objects><object>", xml.first(17) - assert xml.include?(%(<street-address>Paulina</street-address>)) - assert xml.include?(%(<name>David</name>)) - assert xml.include?(%(<street-address>Evergreen</street-address>)) - assert xml.include?(%(<name>Jason</name>)) + assert_includes xml, %(<street-address>Paulina</street-address>) + assert_includes xml, %(<name>David</name>) + assert_includes xml, %(<street-address>Evergreen</street-address>) + assert_includes xml, %(<name>Jason</name>) end def test_to_xml_with_indent_set @@ -142,10 +147,10 @@ class ToXmlTest < ActiveSupport::TestCase ].to_xml(skip_instruct: true, skip_types: true, indent: 4) assert_equal "<objects>\n <object>", xml.first(22) - assert xml.include?(%(\n <street-address>Paulina</street-address>)) - assert xml.include?(%(\n <name>David</name>)) - assert xml.include?(%(\n <street-address>Evergreen</street-address>)) - assert xml.include?(%(\n <name>Jason</name>)) + assert_includes xml, %(\n <street-address>Paulina</street-address>) + assert_includes xml, %(\n <name>David</name>) + assert_includes xml, %(\n <street-address>Evergreen</street-address>) + assert_includes xml, %(\n <name>Jason</name>) end def test_to_xml_with_dasherize_false @@ -154,8 +159,8 @@ class ToXmlTest < ActiveSupport::TestCase ].to_xml(skip_instruct: true, skip_types: true, indent: 0, dasherize: false) assert_equal "<objects><object>", xml.first(17) - assert xml.include?(%(<street_address>Paulina</street_address>)) - assert xml.include?(%(<street_address>Evergreen</street_address>)) + assert_includes xml, %(<street_address>Paulina</street_address>) + assert_includes xml, %(<street_address>Evergreen</street_address>) end def test_to_xml_with_dasherize_true @@ -164,14 +169,14 @@ class ToXmlTest < ActiveSupport::TestCase ].to_xml(skip_instruct: true, skip_types: true, indent: 0, dasherize: true) assert_equal "<objects><object>", xml.first(17) - assert xml.include?(%(<street-address>Paulina</street-address>)) - assert xml.include?(%(<street-address>Evergreen</street-address>)) + assert_includes xml, %(<street-address>Paulina</street-address>) + assert_includes xml, %(<street-address>Evergreen</street-address>) end def test_to_xml_with_instruct xml = [ { name: "David", age: 26, age_in_millis: 820497600000 }, - { name: "Jason", age: 31, age_in_millis: BigDecimal.new('1.0') } + { name: "Jason", age: 31, age_in_millis: BigDecimal.new("1.0") } ].to_xml(skip_instruct: false, indent: 0) assert_match(/^<\?xml [^>]*/, xml) @@ -181,12 +186,12 @@ class ToXmlTest < ActiveSupport::TestCase def test_to_xml_with_block xml = [ { name: "David", age: 26, age_in_millis: 820497600000 }, - { name: "Jason", age: 31, age_in_millis: BigDecimal.new('1.0') } + { name: "Jason", age: 31, age_in_millis: BigDecimal.new("1.0") } ].to_xml(skip_instruct: true, indent: 0) do |builder| builder.count 2 end - assert xml.include?(%(<count>2</count>)), xml + assert_includes xml, %(<count>2</count>), xml end def test_to_xml_with_empty diff --git a/activesupport/test/core_ext/array/extract_options_test.rb b/activesupport/test/core_ext/array/extract_options_test.rb index 0481a507cf..1651bee0f6 100644 --- a/activesupport/test/core_ext/array/extract_options_test.rb +++ b/activesupport/test/core_ext/array/extract_options_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'active_support/core_ext/array' -require 'active_support/core_ext/hash' +require "abstract_unit" +require "active_support/core_ext/array" +require "active_support/core_ext/hash" class ExtractOptionsTest < ActiveSupport::TestCase class HashSubclass < Hash diff --git a/activesupport/test/core_ext/array/grouping_test.rb b/activesupport/test/core_ext/array/grouping_test.rb index 2eb0f05141..b06f87c008 100644 --- a/activesupport/test/core_ext/array/grouping_test.rb +++ b/activesupport/test/core_ext/array/grouping_test.rb @@ -1,38 +1,39 @@ -require 'abstract_unit' -require 'active_support/core_ext/array' +require "abstract_unit" +require "active_support/core_ext/array" class GroupingTest < ActiveSupport::TestCase def setup - Fixnum.send :private, :/ # test we avoid Integer#/ (redefined by mathn) + # In Ruby < 2.4, test we avoid Integer#/ (redefined by mathn) + Fixnum.send :private, :/ unless 0.class == Integer end def teardown - Fixnum.send :public, :/ + Fixnum.send :public, :/ unless 0.class == Integer end def test_in_groups_of_with_perfect_fit groups = [] - ('a'..'i').to_a.in_groups_of(3) do |group| + ("a".."i").to_a.in_groups_of(3) do |group| groups << group end assert_equal [%w(a b c), %w(d e f), %w(g h i)], groups - assert_equal [%w(a b c), %w(d e f), %w(g h i)], ('a'..'i').to_a.in_groups_of(3) + assert_equal [%w(a b c), %w(d e f), %w(g h i)], ("a".."i").to_a.in_groups_of(3) end def test_in_groups_of_with_padding groups = [] - ('a'..'g').to_a.in_groups_of(3) do |group| + ("a".."g").to_a.in_groups_of(3) do |group| groups << group end - assert_equal [%w(a b c), %w(d e f), ['g', nil, nil]], groups + assert_equal [%w(a b c), %w(d e f), ["g", nil, nil]], groups end def test_in_groups_of_pads_with_specified_values groups = [] - ('a'..'g').to_a.in_groups_of(3, 'foo') do |group| + ("a".."g").to_a.in_groups_of(3, "foo") do |group| groups << group end @@ -42,7 +43,7 @@ class GroupingTest < ActiveSupport::TestCase def test_in_groups_of_without_padding groups = [] - ('a'..'g').to_a.in_groups_of(3, false) do |group| + ("a".."g").to_a.in_groups_of(3, false) do |group| groups << group end @@ -82,8 +83,8 @@ class GroupingTest < ActiveSupport::TestCase assert_equal [[1, 2, 3], [4, 5, nil], [6, 7, nil]], array.in_groups(3) - assert_equal [[1, 2, 3], [4, 5, 'foo'], [6, 7, 'foo']], - array.in_groups(3, 'foo') + assert_equal [[1, 2, 3], [4, 5, "foo"], [6, 7, "foo"]], + array.in_groups(3, "foo") end def test_in_groups_without_padding @@ -123,4 +124,12 @@ class SplitTest < ActiveSupport::TestCase assert_equal [[], [2, 3, 4], []], a.split { |i| i == 1 || i == 5 } assert_equal [1, 2, 3, 4, 5], a end + + def test_split_with_repeated_values + a = [1, 2, 3, 5, 5, 3, 4, 6, 2, 1, 3] + assert_equal [[1, 2], [5, 5], [4, 6, 2, 1], []], a.split(3) + assert_equal [[1, 2, 3], [], [3, 4, 6, 2, 1, 3]], a.split(5) + assert_equal [[1, 2], [], [], [], [4, 6, 2, 1], []], a.split { |i| i == 3 || i == 5 } + assert_equal [1, 2, 3, 5, 5, 3, 4, 6, 2, 1, 3], a + end end diff --git a/activesupport/test/core_ext/array/prepend_append_test.rb b/activesupport/test/core_ext/array/prepend_append_test.rb index 762aa69b2b..763e26191d 100644 --- a/activesupport/test/core_ext/array/prepend_append_test.rb +++ b/activesupport/test/core_ext/array/prepend_append_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/array' +require "abstract_unit" +require "active_support/core_ext/array" class PrependAppendTest < ActiveSupport::TestCase def test_append diff --git a/activesupport/test/core_ext/array/wrap_test.rb b/activesupport/test/core_ext/array/wrap_test.rb index baf426506f..ae846cb3f2 100644 --- a/activesupport/test/core_ext/array/wrap_test.rb +++ b/activesupport/test/core_ext/array/wrap_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/array' +require "abstract_unit" +require "active_support/core_ext/array" class WrapTest < ActiveSupport::TestCase class FakeCollection diff --git a/activesupport/test/core_ext/bigdecimal_test.rb b/activesupport/test/core_ext/bigdecimal_test.rb index 423a3f2e9d..43b659546f 100644 --- a/activesupport/test/core_ext/bigdecimal_test.rb +++ b/activesupport/test/core_ext/bigdecimal_test.rb @@ -1,9 +1,11 @@ -require 'abstract_unit' -require 'active_support/core_ext/big_decimal' +require "abstract_unit" +require "active_support/core_ext/big_decimal" class BigDecimalTest < ActiveSupport::TestCase def test_to_s - bd = BigDecimal.new '0.01' - assert_equal '0.01', bd.to_s + bd = BigDecimal.new "0.01" + assert_equal "0.01", bd.to_s + assert_equal "+0.01", bd.to_s("+F") + assert_equal "+0.0 1", bd.to_s("+1F") end end diff --git a/activesupport/test/core_ext/class/attribute_test.rb b/activesupport/test/core_ext/class/attribute_test.rb index e7a1334db3..5a9ec78cc1 100644 --- a/activesupport/test/core_ext/class/attribute_test.rb +++ b/activesupport/test/core_ext/class/attribute_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/class/attribute' +require "abstract_unit" +require "active_support/core_ext/class/attribute" class ClassAttributeTest < ActiveSupport::TestCase def setup @@ -7,17 +7,17 @@ class ClassAttributeTest < ActiveSupport::TestCase @sub = Class.new(@klass) end - test 'defaults to nil' do + test "defaults to nil" do assert_nil @klass.setting assert_nil @sub.setting end - test 'inheritable' do + test "inheritable" do @klass.setting = 1 assert_equal 1, @sub.setting end - test 'overridable' do + test "overridable" do @sub.setting = 1 assert_nil @klass.setting @@ -27,20 +27,20 @@ class ClassAttributeTest < ActiveSupport::TestCase assert_equal 1, Class.new(@sub).setting end - test 'predicate method' do + test "predicate method" do assert_equal false, @klass.setting? @klass.setting = 1 assert_equal true, @klass.setting? end - test 'instance reader delegates to class' do + test "instance reader delegates to class" do assert_nil @klass.new.setting @klass.setting = 1 assert_equal 1, @klass.new.setting end - test 'instance override' do + test "instance override" do object = @klass.new object.setting = 1 assert_nil @klass.setting @@ -48,43 +48,43 @@ class ClassAttributeTest < ActiveSupport::TestCase assert_equal 1, object.setting end - test 'instance predicate' do + test "instance predicate" do object = @klass.new assert_equal false, object.setting? object.setting = 1 assert_equal true, object.setting? end - test 'disabling instance writer' do - object = Class.new { class_attribute :setting, :instance_writer => false }.new - assert_raise(NoMethodError) { object.setting = 'boom' } + test "disabling instance writer" do + object = Class.new { class_attribute :setting, instance_writer: false }.new + assert_raise(NoMethodError) { object.setting = "boom" } end - test 'disabling instance reader' do - object = Class.new { class_attribute :setting, :instance_reader => false }.new + test "disabling instance reader" do + object = Class.new { class_attribute :setting, instance_reader: false }.new assert_raise(NoMethodError) { object.setting } assert_raise(NoMethodError) { object.setting? } end - test 'disabling both instance writer and reader' do - object = Class.new { class_attribute :setting, :instance_accessor => false }.new + test "disabling both instance writer and reader" do + object = Class.new { class_attribute :setting, instance_accessor: false }.new assert_raise(NoMethodError) { object.setting } assert_raise(NoMethodError) { object.setting? } - assert_raise(NoMethodError) { object.setting = 'boom' } + assert_raise(NoMethodError) { object.setting = "boom" } end - test 'disabling instance predicate' do + test "disabling instance predicate" do object = Class.new { class_attribute :setting, instance_predicate: false }.new assert_raise(NoMethodError) { object.setting? } end - test 'works well with singleton classes' do + test "works well with singleton classes" do object = @klass.new - object.singleton_class.setting = 'foo' - assert_equal 'foo', object.setting + object.singleton_class.setting = "foo" + assert_equal "foo", object.setting end - test 'setter returns set value' do + test "setter returns set value" do val = @klass.send(:setting=, 1) assert_equal 1, val end diff --git a/activesupport/test/core_ext/class_test.rb b/activesupport/test/core_ext/class_test.rb index 9c6c579ef7..a9c44907cc 100644 --- a/activesupport/test/core_ext/class_test.rb +++ b/activesupport/test/core_ext/class_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'active_support/core_ext/class' -require 'set' +require "abstract_unit" +require "active_support/core_ext/class" +require "set" class ClassTest < ActiveSupport::TestCase class Parent; end diff --git a/activesupport/test/core_ext/date_and_time_behavior.rb b/activesupport/test/core_ext/date_and_time_behavior.rb index 784547bdf8..6cc9781e02 100644 --- a/activesupport/test/core_ext/date_and_time_behavior.rb +++ b/activesupport/test/core_ext/date_and_time_behavior.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module DateAndTimeBehavior def test_yesterday @@ -126,7 +126,9 @@ module DateAndTimeBehavior end def test_next_week_at_same_time - assert_equal date_time_init(2005,2,28,15,15,10), date_time_init(2005,2,22,15,15,10).next_week(:monday, same_time: true) + assert_equal date_time_init(2005,2,28,15,15,10), date_time_init(2005,2,22,15,15,10).next_week(:monday, same_time: true) + assert_equal date_time_init(2005,2,28,15,15,10,999999), date_time_init(2005,2,22,15,15,10,999999).next_week(:monday, same_time: true) + assert_equal date_time_init(2005,2,28,15,15,10,Rational(999999999, 1000)), date_time_init(2005,2,22,15,15,10,Rational(999999999, 1000)).next_week(:monday, same_time: true) assert_equal date_time_init(2005,3,4,15,15,10), date_time_init(2005,2,22,15,15,10).next_week(:friday, same_time: true) assert_equal date_time_init(2006,10,30,0,0,0), date_time_init(2006,10,23,0,0,0).next_week(:monday, same_time: true) assert_equal date_time_init(2006,11,1,0,0,0), date_time_init(2006,10,23,0,0,0).next_week(:wednesday, same_time: true) @@ -301,6 +303,16 @@ module DateAndTimeBehavior assert_not date_time_init(2015,1,5,15,15,10).on_weekend? end + def test_on_weekday_on_sunday + assert_not date_time_init(2015,1,4,0,0,0).on_weekday? + assert_not date_time_init(2015,1,4,15,15,10).on_weekday? + end + + def test_on_weekday_on_monday + assert date_time_init(2015,1,5,0,0,0).on_weekday? + assert date_time_init(2015,1,5,15,15,10).on_weekday? + end + def with_bw_default(bw = :monday) old_bw = Date.beginning_of_week Date.beginning_of_week = bw diff --git a/activesupport/test/core_ext/date_and_time_compatibility_test.rb b/activesupport/test/core_ext/date_and_time_compatibility_test.rb new file mode 100644 index 0000000000..180b3e12aa --- /dev/null +++ b/activesupport/test/core_ext/date_and_time_compatibility_test.rb @@ -0,0 +1,127 @@ +require "abstract_unit" +require "active_support/time" +require "time_zone_test_helpers" + +class DateAndTimeCompatibilityTest < ActiveSupport::TestCase + include TimeZoneTestHelpers + + def setup + @utc_time = Time.utc(2016, 4, 23, 14, 11, 12) + @date_time = DateTime.new(2016, 4, 23, 14, 11, 12, 0) + @utc_offset = 3600 + @system_offset = -14400 + @zone = ActiveSupport::TimeZone["London"] + end + + def test_time_to_time_preserves_timezone + with_preserve_timezone(true) do + with_env_tz "US/Eastern" do + time = Time.new(2016, 4, 23, 15, 11, 12, 3600).to_time + + assert_instance_of Time, time + assert_equal @utc_time, time.getutc + assert_equal @utc_offset, time.utc_offset + end + end + end + + def test_time_to_time_does_not_preserve_time_zone + with_preserve_timezone(false) do + with_env_tz "US/Eastern" do + time = Time.new(2016, 4, 23, 15, 11, 12, 3600).to_time + + assert_instance_of Time, time + assert_equal @utc_time, time.getutc + assert_equal @system_offset, time.utc_offset + end + end + end + + def test_datetime_to_time_preserves_timezone + with_preserve_timezone(true) do + with_env_tz "US/Eastern" do + time = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1,24)).to_time + + assert_instance_of Time, time + assert_equal @utc_time, time.getutc + assert_equal @utc_offset, time.utc_offset + end + end + end + + def test_datetime_to_time_does_not_preserve_time_zone + with_preserve_timezone(false) do + with_env_tz "US/Eastern" do + time = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1,24)).to_time + + assert_instance_of Time, time + assert_equal @utc_time, time.getutc + assert_equal @system_offset, time.utc_offset + end + end + end + + def test_twz_to_time_preserves_timezone + with_preserve_timezone(true) do + with_env_tz "US/Eastern" do + time = ActiveSupport::TimeWithZone.new(@utc_time, @zone).to_time + + assert_instance_of Time, time + assert_equal @utc_time, time.getutc + assert_instance_of Time, time.getutc + assert_equal @utc_offset, time.utc_offset + + time = ActiveSupport::TimeWithZone.new(@date_time, @zone).to_time + + assert_instance_of Time, time + assert_equal @date_time, time.getutc + assert_instance_of Time, time.getutc + assert_equal @utc_offset, time.utc_offset + end + end + end + + def test_twz_to_time_does_not_preserve_time_zone + with_preserve_timezone(false) do + with_env_tz "US/Eastern" do + time = ActiveSupport::TimeWithZone.new(@utc_time, @zone).to_time + + assert_instance_of Time, time + assert_equal @utc_time, time.getutc + assert_instance_of Time, time.getutc + assert_equal @system_offset, time.utc_offset + + time = ActiveSupport::TimeWithZone.new(@date_time, @zone).to_time + + assert_instance_of Time, time + assert_equal @date_time, time.getutc + assert_instance_of Time, time.getutc + assert_equal @system_offset, time.utc_offset + end + end + end + + def test_string_to_time_preserves_timezone + with_preserve_timezone(true) do + with_env_tz "US/Eastern" do + time = "2016-04-23T15:11:12+01:00".to_time + + assert_instance_of Time, time + assert_equal @utc_time, time.getutc + assert_equal @utc_offset, time.utc_offset + end + end + end + + def test_string_to_time_does_not_preserve_time_zone + with_preserve_timezone(false) do + with_env_tz "US/Eastern" do + time = "2016-04-23T15:11:12+01:00".to_time + + assert_instance_of Time, time + assert_equal @utc_time, time.getutc + assert_equal @system_offset, time.utc_offset + end + end + end +end diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index 0fc3f765f5..0655197335 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -1,7 +1,7 @@ -require 'abstract_unit' -require 'active_support/time' -require 'core_ext/date_and_time_behavior' -require 'time_zone_test_helpers' +require "abstract_unit" +require "active_support/time" +require "core_ext/date_and_time_behavior" +require "time_zone_test_helpers" class DateExtCalculationsTest < ActiveSupport::TestCase def date_time_init(year,month,day,*args) @@ -30,13 +30,24 @@ class DateExtCalculationsTest < ActiveSupport::TestCase assert_equal "2005-02-21", date.to_s(:iso8601) end + def test_to_s_with_single_digit_day + date = Date.new(2005, 2, 1) + assert_equal "2005-02-01", date.to_s + assert_equal "01 Feb", date.to_s(:short) + assert_equal "February 01, 2005", date.to_s(:long) + assert_equal "February 1st, 2005", date.to_s(:long_ordinal) + assert_equal "2005-02-01", date.to_s(:db) + assert_equal "01 Feb 2005", date.to_s(:rfc822) + assert_equal "2005-02-01", date.to_s(:iso8601) + end + def test_readable_inspect assert_equal "Mon, 21 Feb 2005", Date.new(2005, 2, 21).readable_inspect assert_equal Date.new(2005, 2, 21).readable_inspect, Date.new(2005, 2, 21).inspect end def test_to_time - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do assert_equal Time, Date.new(2005, 2, 21).to_time.class assert_equal Time.local(2005, 2, 21), Date.new(2005, 2, 21).to_time assert_equal Time.local(2005, 2, 21).utc_offset, Date.new(2005, 2, 21).to_time.utc_offset @@ -49,6 +60,10 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end end end + + assert_raise(ArgumentError) do + Date.new(2005, 2, 21).to_time(:tokyo) + end end def test_compare_to_time @@ -66,10 +81,10 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end def test_change - assert_equal Date.new(2005, 2, 21), Date.new(2005, 2, 11).change(:day => 21) - assert_equal Date.new(2007, 5, 11), Date.new(2005, 2, 11).change(:year => 2007, :month => 5) - assert_equal Date.new(2006,2,22), Date.new(2005,2,22).change(:year => 2006) - assert_equal Date.new(2005,6,22), Date.new(2005,2,22).change(:month => 6) + assert_equal Date.new(2005, 2, 21), Date.new(2005, 2, 11).change(day: 21) + assert_equal Date.new(2007, 5, 11), Date.new(2005, 2, 11).change(year: 2007, month: 5) + assert_equal Date.new(2006,2,22), Date.new(2005,2,22).change(year: 2006) + assert_equal Date.new(2005,6,22), Date.new(2005,2,22).change(month: 6) end def test_sunday @@ -124,34 +139,34 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end def test_advance - assert_equal Date.new(2006,2,28), Date.new(2005,2,28).advance(:years => 1) - assert_equal Date.new(2005,6,28), Date.new(2005,2,28).advance(:months => 4) - assert_equal Date.new(2005,3,21), Date.new(2005,2,28).advance(:weeks => 3) - assert_equal Date.new(2005,3,5), Date.new(2005,2,28).advance(:days => 5) - assert_equal Date.new(2012,9,28), Date.new(2005,2,28).advance(:years => 7, :months => 7) - assert_equal Date.new(2013,10,3), Date.new(2005,2,28).advance(:years => 7, :months => 19, :days => 5) - assert_equal Date.new(2013,10,17), Date.new(2005,2,28).advance(:years => 7, :months => 19, :weeks => 2, :days => 5) - assert_equal Date.new(2005,2,28), Date.new(2004,2,29).advance(:years => 1) #leap day plus one year + assert_equal Date.new(2006,2,28), Date.new(2005,2,28).advance(years: 1) + assert_equal Date.new(2005,6,28), Date.new(2005,2,28).advance(months: 4) + assert_equal Date.new(2005,3,21), Date.new(2005,2,28).advance(weeks: 3) + assert_equal Date.new(2005,3,5), Date.new(2005,2,28).advance(days: 5) + assert_equal Date.new(2012,9,28), Date.new(2005,2,28).advance(years: 7, months: 7) + assert_equal Date.new(2013,10,3), Date.new(2005,2,28).advance(years: 7, months: 19, days: 5) + assert_equal Date.new(2013,10,17), Date.new(2005,2,28).advance(years: 7, months: 19, weeks: 2, days: 5) + assert_equal Date.new(2005,2,28), Date.new(2004,2,29).advance(years: 1) #leap day plus one year end def test_advance_does_first_years_and_then_days - assert_equal Date.new(2012, 2, 29), Date.new(2011, 2, 28).advance(:years => 1, :days => 1) + assert_equal Date.new(2012, 2, 29), Date.new(2011, 2, 28).advance(years: 1, days: 1) # If day was done first we would jump to 2012-03-01 instead. end def test_advance_does_first_months_and_then_days - assert_equal Date.new(2010, 3, 29), Date.new(2010, 2, 28).advance(:months => 1, :days => 1) + assert_equal Date.new(2010, 3, 29), Date.new(2010, 2, 28).advance(months: 1, days: 1) # If day was done first we would jump to 2010-04-01 instead. end def test_advance_in_calendar_reform - assert_equal Date.new(1582,10,15), Date.new(1582,10,4).advance(:days => 1) - assert_equal Date.new(1582,10,4), Date.new(1582,10,15).advance(:days => -1) + assert_equal Date.new(1582,10,15), Date.new(1582,10,4).advance(days: 1) + assert_equal Date.new(1582,10,4), Date.new(1582,10,15).advance(days: -1) 5.upto(14) do |day| - assert_equal Date.new(1582,10,4), Date.new(1582,9,day).advance(:months => 1) - assert_equal Date.new(1582,10,4), Date.new(1582,11,day).advance(:months => -1) - assert_equal Date.new(1582,10,4), Date.new(1581,10,day).advance(:years => 1) - assert_equal Date.new(1582,10,4), Date.new(1583,10,day).advance(:years => -1) + assert_equal Date.new(1582,10,4), Date.new(1582,9,day).advance(months: 1) + assert_equal Date.new(1582,10,4), Date.new(1582,11,day).advance(months: -1) + assert_equal Date.new(1582,10,4), Date.new(1581,10,day).advance(years: 1) + assert_equal Date.new(1582,10,4), Date.new(1583,10,day).advance(years: -1) end end @@ -181,7 +196,7 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end def test_yesterday_constructor_when_zone_is_not_set - with_env_tz 'UTC' do + with_env_tz "UTC" do with_tz_default do assert_equal(Date.today - 1, Date.yesterday) end @@ -189,8 +204,8 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end def test_yesterday_constructor_when_zone_is_set - with_env_tz 'UTC' do - with_tz_default ActiveSupport::TimeZone['Eastern Time (US & Canada)'] do # UTC -5 + with_env_tz "UTC" do + with_tz_default ActiveSupport::TimeZone["Eastern Time (US & Canada)"] do # UTC -5 Time.stub(:now, Time.local(2000, 1, 1)) do assert_equal Date.new(1999, 12, 30), Date.yesterday end @@ -203,7 +218,7 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end def test_tomorrow_constructor_when_zone_is_not_set - with_env_tz 'UTC' do + with_env_tz "UTC" do with_tz_default do assert_equal(Date.today + 1, Date.tomorrow) end @@ -211,8 +226,8 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end def test_tomorrow_constructor_when_zone_is_set - with_env_tz 'UTC' do - with_tz_default ActiveSupport::TimeZone['Europe/Paris'] do # UTC +1 + with_env_tz "UTC" do + with_tz_default ActiveSupport::TimeZone["Europe/Paris"] do # UTC +1 Time.stub(:now, Time.local(1999, 12, 31, 23)) do assert_equal Date.new(2000, 1, 2), Date.tomorrow end @@ -225,8 +240,8 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end def test_since_when_zone_is_set - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - with_env_tz 'UTC' do + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + with_env_tz "UTC" do with_tz_default zone do assert_equal zone.local(2005,2,21,0,0,45), Date.new(2005,2,21).since(45) assert_equal zone, Date.new(2005,2,21).since(45).time_zone @@ -239,8 +254,8 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end def test_ago_when_zone_is_set - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - with_env_tz 'UTC' do + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + with_env_tz "UTC" do with_tz_default zone do assert_equal zone.local(2005,2,20,23,59,15), Date.new(2005,2,21).ago(45) assert_equal zone, Date.new(2005,2,21).ago(45).time_zone @@ -257,8 +272,8 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end def test_beginning_of_day_when_zone_is_set - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - with_env_tz 'UTC' do + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + with_env_tz "UTC" do with_tz_default zone do assert_equal zone.local(2005,2,21,0,0,0), Date.new(2005,2,21).beginning_of_day assert_equal zone, Date.new(2005,2,21).beginning_of_day.time_zone @@ -271,8 +286,8 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end def test_end_of_day_when_zone_is_set - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - with_env_tz 'UTC' do + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + with_env_tz "UTC" do with_tz_default zone do assert_equal zone.local(2005,2,21,23,59,59,Rational(999999999, 1000)), Date.new(2005,2,21).end_of_day assert_equal zone, Date.new(2005,2,21).end_of_day.time_zone @@ -280,6 +295,23 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end end + def test_all_day + beginning_of_day = Time.local(2011,6,7,0,0,0) + end_of_day = Time.local(2011,6,7,23,59,59,Rational(999999999, 1000)) + assert_equal beginning_of_day..end_of_day, Date.new(2011,6,7).all_day + end + + def test_all_day_when_zone_is_set + zone = ActiveSupport::TimeZone["Hawaii"] + with_env_tz "UTC" do + with_tz_default zone do + beginning_of_day = zone.local(2011,6,7,0,0,0) + end_of_day = zone.local(2011,6,7,23,59,59,Rational(999999999, 1000)) + assert_equal beginning_of_day..end_of_day, Date.new(2011,6,7).all_day + end + end + end + def test_all_week assert_equal Date.new(2011,6,6)..Date.new(2011,6,12), Date.new(2011,6,7).all_week assert_equal Date.new(2011,6,5)..Date.new(2011,6,11), Date.new(2011,6,7).all_week(:sunday) @@ -298,7 +330,7 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end def test_xmlschema - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do assert_match(/^1980-02-28T00:00:00-05:?00$/, Date.new(1980, 2, 28).xmlschema) assert_match(/^1980-06-28T00:00:00-04:?00$/, Date.new(1980, 6, 28).xmlschema) # these tests are only of interest on platforms where older dates #to_time fail over to DateTime @@ -310,8 +342,8 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end def test_xmlschema_when_zone_is_set - with_env_tz 'UTC' do - with_tz_default ActiveSupport::TimeZone['Eastern Time (US & Canada)'] do # UTC -5 + with_env_tz "UTC" do + with_tz_default ActiveSupport::TimeZone["Eastern Time (US & Canada)"] do # UTC -5 assert_match(/^1980-02-28T00:00:00-05:?00$/, Date.new(1980, 2, 28).xmlschema) assert_match(/^1980-06-28T00:00:00-04:?00$/, Date.new(1980, 6, 28).xmlschema) end @@ -335,7 +367,7 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end def test_current_returns_date_today_when_zone_not_set - with_env_tz 'US/Central' do + with_env_tz "US/Central" do Time.stub(:now, Time.local(1999, 12, 31, 23)) do assert_equal Date.today, Date.current end @@ -343,8 +375,8 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end def test_current_returns_time_zone_today_when_zone_is_set - Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - with_env_tz 'US/Central' do + Time.zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + with_env_tz "US/Central" do assert_equal ::Time.zone.today, Date.current end ensure @@ -352,9 +384,9 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end def test_date_advance_should_not_change_passed_options_hash - options = { :years => 3, :months => 11, :days => 2 } + options = { years: 3, months: 11, days: 2 } Date.new(2005,2,28).advance(options) - assert_equal({ :years => 3, :months => 11, :days => 2 }, options) + assert_equal({ years: 3, months: 11, days: 2 }, options) end end @@ -375,4 +407,3 @@ class DateExtBehaviorTest < ActiveSupport::TestCase end end end - diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index 6fe38c45ec..f2a50f4693 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -1,7 +1,7 @@ -require 'abstract_unit' -require 'active_support/time' -require 'core_ext/date_and_time_behavior' -require 'time_zone_test_helpers' +require "abstract_unit" +require "active_support/time" +require "core_ext/date_and_time_behavior" +require "time_zone_test_helpers" class DateTimeExtCalculationsTest < ActiveSupport::TestCase def date_time_init(year,month,day,hour,minute,second,*args) @@ -35,11 +35,29 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase end def test_custom_date_format - Time::DATE_FORMATS[:custom] = '%Y%m%d%H%M%S' - assert_equal '20050221143000', DateTime.new(2005, 2, 21, 14, 30, 0).to_s(:custom) + Time::DATE_FORMATS[:custom] = "%Y%m%d%H%M%S" + assert_equal "20050221143000", DateTime.new(2005, 2, 21, 14, 30, 0).to_s(:custom) Time::DATE_FORMATS.delete(:custom) end + def test_localtime + with_env_tz "US/Eastern" do + assert_instance_of Time, DateTime.new(2016, 3, 11, 15, 11, 12, 0).localtime + assert_equal Time.local(2016, 3, 11, 10, 11, 12), DateTime.new(2016, 3, 11, 15, 11, 12, 0).localtime + assert_equal Time.local(2016, 3, 21, 11, 11, 12), DateTime.new(2016, 3, 21, 15, 11, 12, 0).localtime + assert_equal Time.local(2016, 4, 1, 11, 11, 12), DateTime.new(2016, 4, 1, 16, 11, 12, Rational(1,24)).localtime + end + end + + def test_getlocal + with_env_tz "US/Eastern" do + assert_instance_of Time, DateTime.new(2016, 3, 11, 15, 11, 12, 0).getlocal + assert_equal Time.local(2016, 3, 11, 10, 11, 12), DateTime.new(2016, 3, 11, 15, 11, 12, 0).getlocal + assert_equal Time.local(2016, 3, 21, 11, 11, 12), DateTime.new(2016, 3, 21, 15, 11, 12, 0).getlocal + assert_equal Time.local(2016, 4, 1, 11, 11, 12), DateTime.new(2016, 4, 1, 16, 11, 12, Rational(1,24)).getlocal + end + end + def test_to_date assert_equal Date.new(2005, 2, 21), DateTime.new(2005, 2, 21, 14, 30, 0).to_date end @@ -49,10 +67,16 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase end def test_to_time - with_env_tz 'US/Eastern' do - assert_equal Time, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time.class - assert_equal Time.local(2005, 2, 21, 5, 11, 12), DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time - assert_equal Time.local(2005, 2, 21, 5, 11, 12).utc_offset, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time.utc_offset + with_env_tz "US/Eastern" do + assert_instance_of Time, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time + + if ActiveSupport.to_time_preserves_timezone + assert_equal Time.local(2005, 2, 21, 5, 11, 12).getlocal(0), DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time + assert_equal Time.local(2005, 2, 21, 5, 11, 12).getlocal(0).utc_offset, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time.utc_offset + else + assert_equal Time.local(2005, 2, 21, 5, 11, 12), DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time + assert_equal Time.local(2005, 2, 21, 5, 11, 12).utc_offset, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time.utc_offset + end end end @@ -135,47 +159,47 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase end def test_change - assert_equal DateTime.civil(2006,2,22,15,15,10), DateTime.civil(2005,2,22,15,15,10).change(:year => 2006) - assert_equal DateTime.civil(2005,6,22,15,15,10), DateTime.civil(2005,2,22,15,15,10).change(:month => 6) - assert_equal DateTime.civil(2012,9,22,15,15,10), DateTime.civil(2005,2,22,15,15,10).change(:year => 2012, :month => 9) - assert_equal DateTime.civil(2005,2,22,16), DateTime.civil(2005,2,22,15,15,10).change(:hour => 16) - assert_equal DateTime.civil(2005,2,22,16,45), DateTime.civil(2005,2,22,15,15,10).change(:hour => 16, :min => 45) - assert_equal DateTime.civil(2005,2,22,15,45), DateTime.civil(2005,2,22,15,15,10).change(:min => 45) + assert_equal DateTime.civil(2006,2,22,15,15,10), DateTime.civil(2005,2,22,15,15,10).change(year: 2006) + assert_equal DateTime.civil(2005,6,22,15,15,10), DateTime.civil(2005,2,22,15,15,10).change(month: 6) + assert_equal DateTime.civil(2012,9,22,15,15,10), DateTime.civil(2005,2,22,15,15,10).change(year: 2012, month: 9) + assert_equal DateTime.civil(2005,2,22,16), DateTime.civil(2005,2,22,15,15,10).change(hour: 16) + assert_equal DateTime.civil(2005,2,22,16,45), DateTime.civil(2005,2,22,15,15,10).change(hour: 16, min: 45) + assert_equal DateTime.civil(2005,2,22,15,45), DateTime.civil(2005,2,22,15,15,10).change(min: 45) # datetime with fractions of a second - assert_equal DateTime.civil(2005,2,1,15,15,10.7), DateTime.civil(2005,2,22,15,15,10.7).change(:day => 1) + assert_equal DateTime.civil(2005,2,1,15,15,10.7), DateTime.civil(2005,2,22,15,15,10.7).change(day: 1) end def test_advance - assert_equal DateTime.civil(2006,2,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 1) - assert_equal DateTime.civil(2005,6,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:months => 4) - assert_equal DateTime.civil(2005,3,21,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:weeks => 3) - assert_equal DateTime.civil(2005,3,5,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:days => 5) - assert_equal DateTime.civil(2012,9,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 7) - assert_equal DateTime.civil(2013,10,3,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :days => 5) - assert_equal DateTime.civil(2013,10,17,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5) - assert_equal DateTime.civil(2001,12,27,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => -3, :months => -2, :days => -1) - assert_equal DateTime.civil(2005,2,28,15,15,10), DateTime.civil(2004,2,29,15,15,10).advance(:years => 1) #leap day plus one year - assert_equal DateTime.civil(2005,2,28,20,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:hours => 5) - assert_equal DateTime.civil(2005,2,28,15,22,10), DateTime.civil(2005,2,28,15,15,10).advance(:minutes => 7) - assert_equal DateTime.civil(2005,2,28,15,15,19), DateTime.civil(2005,2,28,15,15,10).advance(:seconds => 9) - assert_equal DateTime.civil(2005,2,28,20,22,19), DateTime.civil(2005,2,28,15,15,10).advance(:hours => 5, :minutes => 7, :seconds => 9) - assert_equal DateTime.civil(2005,2,28,10,8,1), DateTime.civil(2005,2,28,15,15,10).advance(:hours => -5, :minutes => -7, :seconds => -9) - assert_equal DateTime.civil(2013,10,17,20,22,19), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5, :hours => 5, :minutes => 7, :seconds => 9) + assert_equal DateTime.civil(2006,2,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(years: 1) + assert_equal DateTime.civil(2005,6,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(months: 4) + assert_equal DateTime.civil(2005,3,21,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(weeks: 3) + assert_equal DateTime.civil(2005,3,5,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(days: 5) + assert_equal DateTime.civil(2012,9,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(years: 7, months: 7) + assert_equal DateTime.civil(2013,10,3,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(years: 7, months: 19, days: 5) + assert_equal DateTime.civil(2013,10,17,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(years: 7, months: 19, weeks: 2, days: 5) + assert_equal DateTime.civil(2001,12,27,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(years: -3, months: -2, days: -1) + assert_equal DateTime.civil(2005,2,28,15,15,10), DateTime.civil(2004,2,29,15,15,10).advance(years: 1) #leap day plus one year + assert_equal DateTime.civil(2005,2,28,20,15,10), DateTime.civil(2005,2,28,15,15,10).advance(hours: 5) + assert_equal DateTime.civil(2005,2,28,15,22,10), DateTime.civil(2005,2,28,15,15,10).advance(minutes: 7) + assert_equal DateTime.civil(2005,2,28,15,15,19), DateTime.civil(2005,2,28,15,15,10).advance(seconds: 9) + assert_equal DateTime.civil(2005,2,28,20,22,19), DateTime.civil(2005,2,28,15,15,10).advance(hours: 5, minutes: 7, seconds: 9) + assert_equal DateTime.civil(2005,2,28,10,8,1), DateTime.civil(2005,2,28,15,15,10).advance(hours: -5, minutes: -7, seconds: -9) + assert_equal DateTime.civil(2013,10,17,20,22,19), DateTime.civil(2005,2,28,15,15,10).advance(years: 7, months: 19, weeks: 2, days: 5, hours: 5, minutes: 7, seconds: 9) end def test_advance_partial_days - assert_equal DateTime.civil(2012,9,29,13,15,10), DateTime.civil(2012,9,28,1,15,10).advance(:days => 1.5) - assert_equal DateTime.civil(2012,9,28,13,15,10), DateTime.civil(2012,9,28,1,15,10).advance(:days => 0.5) - assert_equal DateTime.civil(2012,10,29,13,15,10), DateTime.civil(2012,9,28,1,15,10).advance(:days => 1.5, :months => 1) + assert_equal DateTime.civil(2012,9,29,13,15,10), DateTime.civil(2012,9,28,1,15,10).advance(days: 1.5) + assert_equal DateTime.civil(2012,9,28,13,15,10), DateTime.civil(2012,9,28,1,15,10).advance(days: 0.5) + assert_equal DateTime.civil(2012,10,29,13,15,10), DateTime.civil(2012,9,28,1,15,10).advance(days: 1.5, months: 1) end def test_advanced_processes_first_the_date_deltas_and_then_the_time_deltas # If the time deltas were processed first, the following datetimes would be advanced to 2010/04/01 instead. - assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 23, 59, 59).advance(:months => 1, :seconds => 1) - assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 23, 59).advance(:months => 1, :minutes => 1) - assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 23).advance(:months => 1, :hours => 1) - assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 22, 58, 59).advance(:months => 1, :hours => 1, :minutes => 1, :seconds => 1) + assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 23, 59, 59).advance(months: 1, seconds: 1) + assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 23, 59).advance(months: 1, minutes: 1) + assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 23).advance(months: 1, hours: 1) + assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 22, 58, 59).advance(months: 1, hours: 1, minutes: 1, seconds: 1) end def test_last_week @@ -186,6 +210,10 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal DateTime.civil(2006,11,15), DateTime.civil(2006,11,23,0,0,0).last_week(:wednesday) end + def test_date_time_should_have_correct_last_week_for_leap_year + assert_equal DateTime.civil(2016, 2, 29), DateTime.civil(2016, 3, 7).last_week + end + def test_last_month_on_31st assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 3, 31).last_month end @@ -254,7 +282,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase end def test_current_returns_date_today_when_zone_is_not_set - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do Time.stub(:now, Time.local(1999, 12, 31, 23, 59, 59)) do assert_equal DateTime.new(1999, 12, 31, 23, 59, 59, Rational(-18000, 86400)), DateTime.current end @@ -262,8 +290,8 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase end def test_current_returns_time_zone_today_when_zone_is_set - Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - with_env_tz 'US/Eastern' do + Time.zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + with_env_tz "US/Eastern" do Time.stub(:now, Time.local(1999, 12, 31, 23, 59, 59)) do assert_equal DateTime.new(1999, 12, 31, 23, 59, 59, Rational(-18000, 86400)), DateTime.current end @@ -277,7 +305,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase end def test_current_with_time_zone - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do assert_kind_of DateTime, DateTime.current end end @@ -306,6 +334,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase end def test_utc + assert_instance_of Time, DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc assert_equal DateTime.civil(2005, 2, 21, 16, 11, 12, 0), DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc assert_equal DateTime.civil(2005, 2, 21, 15, 11, 12, 0), DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-5, 24)).utc assert_equal DateTime.civil(2005, 2, 21, 10, 11, 12, 0), DateTime.civil(2005, 2, 21, 10, 11, 12, 0).utc @@ -314,15 +343,15 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase end def test_formatted_offset_with_utc - assert_equal '+00:00', DateTime.civil(2000).formatted_offset - assert_equal '+0000', DateTime.civil(2000).formatted_offset(false) - assert_equal 'UTC', DateTime.civil(2000).formatted_offset(true, 'UTC') + assert_equal "+00:00", DateTime.civil(2000).formatted_offset + assert_equal "+0000", DateTime.civil(2000).formatted_offset(false) + assert_equal "UTC", DateTime.civil(2000).formatted_offset(true, "UTC") end def test_formatted_offset_with_local dt = DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-5, 24)) - assert_equal '-05:00', dt.formatted_offset - assert_equal '-0500', dt.formatted_offset(false) + assert_equal "-05:00", dt.formatted_offset + assert_equal "-0500", dt.formatted_offset(false) end def test_compare_with_time @@ -338,9 +367,9 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase end def test_compare_with_time_with_zone - assert_equal 1, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone['UTC'] ) - assert_equal 0, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['UTC'] ) - assert_equal(-1, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone['UTC'] )) + assert_equal 1, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone["UTC"] ) + assert_equal 0, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone["UTC"] ) + assert_equal(-1, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone["UTC"] )) end def test_compare_with_string @@ -350,6 +379,24 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal nil, DateTime.civil(2000) <=> "Invalid as Time" end + def test_compare_with_integer + assert_equal 1, DateTime.civil(1970, 1, 1, 12, 0, 0) <=> 2440587 + assert_equal 0, DateTime.civil(1970, 1, 1, 12, 0, 0) <=> 2440588 + assert_equal(-1, DateTime.civil(1970, 1, 1, 12, 0, 0) <=> 2440589) + end + + def test_compare_with_float + assert_equal 1, DateTime.civil(1970) <=> 2440586.5 + assert_equal 0, DateTime.civil(1970) <=> 2440587.5 + assert_equal(-1, DateTime.civil(1970) <=> 2440588.5) + end + + def test_compare_with_rational + assert_equal 1, DateTime.civil(1970) <=> Rational(4881173, 2) + assert_equal 0, DateTime.civil(1970) <=> Rational(4881175, 2) + assert_equal(-1, DateTime.civil(1970) <=> Rational(4881177, 2)) + end + def test_to_f assert_equal 946684800.0, DateTime.civil(2000).to_f assert_equal 946684800.0, DateTime.civil(1999,12,31,19,0,0,Rational(-5,24)).to_f @@ -370,4 +417,9 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal 0, DateTime.civil(2000).nsec assert_equal 500000000, DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)).nsec end + + def test_subsec + assert_equal 0, DateTime.civil(2000).subsec + assert_equal Rational(1,2), DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)).subsec + end end diff --git a/activesupport/test/core_ext/digest/uuid_test.rb b/activesupport/test/core_ext/digest/uuid_test.rb index 08e0a1d6e1..866a03259a 100644 --- a/activesupport/test/core_ext/digest/uuid_test.rb +++ b/activesupport/test/core_ext/digest/uuid_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/digest/uuid' +require "abstract_unit" +require "active_support/core_ext/digest/uuid" class DigestUUIDExt < ActiveSupport::TestCase def test_v3_uuids @@ -18,7 +18,7 @@ class DigestUUIDExt < ActiveSupport::TestCase def test_invalid_hash_class assert_raise ArgumentError do - Digest::UUID.uuid_from_hash(Digest::SHA2, Digest::UUID::OID_NAMESPACE, '1.2.3') + Digest::UUID.uuid_from_hash(Digest::SHA2, Digest::UUID::OID_NAMESPACE, "1.2.3") end end end diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index 9e97acaffb..a881768470 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -1,8 +1,8 @@ -require 'abstract_unit' -require 'active_support/inflector' -require 'active_support/time' -require 'active_support/json' -require 'time_zone_test_helpers' +require "abstract_unit" +require "active_support/inflector" +require "active_support/time" +require "active_support/json" +require "time_zone_test_helpers" class DurationTest < ActiveSupport::TestCase include TimeZoneTestHelpers @@ -12,7 +12,7 @@ class DurationTest < ActiveSupport::TestCase assert d.is_a?(ActiveSupport::Duration) assert_kind_of ActiveSupport::Duration, d assert_kind_of Numeric, d - assert_kind_of Fixnum, d + assert_kind_of Integer, d assert !d.is_a?(Hash) k = Class.new @@ -29,14 +29,14 @@ class DurationTest < ActiveSupport::TestCase def test_threequals assert ActiveSupport::Duration === 1.day assert !(ActiveSupport::Duration === 1.day.to_i) - assert !(ActiveSupport::Duration === 'foo') + assert !(ActiveSupport::Duration === "foo") end def test_equals assert 1.day == 1.day assert 1.day == 1.day.to_i assert 1.day.to_i == 1.day - assert !(1.day == 'foo') + assert !(1.day == "foo") end def test_to_s @@ -54,27 +54,28 @@ class DurationTest < ActiveSupport::TestCase assert !1.eql?(1.second) assert 1.minute.eql?(180.seconds - 2.minutes) assert !1.minute.eql?(60) - assert !1.minute.eql?('foo') + assert !1.minute.eql?("foo") end def test_inspect - assert_equal '0 seconds', 0.seconds.inspect - assert_equal '1 month', 1.month.inspect - assert_equal '1 month and 1 day', (1.month + 1.day).inspect - assert_equal '6 months and -2 days', (6.months - 2.days).inspect - assert_equal '10 seconds', 10.seconds.inspect - assert_equal '10 years, 2 months, and 1 day', (10.years + 2.months + 1.day).inspect - assert_equal '10 years, 2 months, and 1 day', (10.years + 1.month + 1.day + 1.month).inspect - assert_equal '10 years, 2 months, and 1 day', (1.day + 10.years + 2.months).inspect - assert_equal '7 days', 1.week.inspect - assert_equal '14 days', 1.fortnight.inspect + assert_equal "0 seconds", 0.seconds.inspect + assert_equal "1 month", 1.month.inspect + assert_equal "1 month and 1 day", (1.month + 1.day).inspect + assert_equal "6 months and -2 days", (6.months - 2.days).inspect + assert_equal "10 seconds", 10.seconds.inspect + assert_equal "10 years, 2 months, and 1 day", (10.years + 2.months + 1.day).inspect + assert_equal "10 years, 2 months, and 1 day", (10.years + 1.month + 1.day + 1.month).inspect + assert_equal "10 years, 2 months, and 1 day", (1.day + 10.years + 2.months).inspect + assert_equal "7 days", 7.days.inspect + assert_equal "1 week", 1.week.inspect + assert_equal "2 weeks", 1.fortnight.inspect end def test_inspect_locale current_locale = I18n.default_locale I18n.default_locale = :de - I18n.backend.store_translations(:de, { support: { array: { last_word_connector: ' und ' } } }) - assert_equal '10 years, 1 month und 1 day', (10.years + 1.month + 1.day).inspect + I18n.backend.store_translations(:de, support: { array: { last_word_connector: " und " } }) + assert_equal "10 years, 1 month und 1 day", (10.years + 1.month + 1.day).inspect ensure I18n.default_locale = current_locale end @@ -87,9 +88,18 @@ class DurationTest < ActiveSupport::TestCase assert_equal 1 + 1.second, 1.second + 1, "Duration + Numeric should == Numeric + Duration" end + def test_time_plus_duration_returns_same_time_datatype + twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Moscow"] , Time.utc(2016,4,28,00,45)) + now = Time.now.utc + %w( second minute hour day week month year ).each do |unit| + assert_equal((now + 1.send(unit)).class, Time, "Time + 1.#{unit} must be Time") + assert_equal((twz + 1.send(unit)).class, ActiveSupport::TimeWithZone, "TimeWithZone + 1.#{unit} must be TimeWithZone") + end + end + def test_argument_error e = assert_raise ArgumentError do - 1.second.ago('') + 1.second.ago("") end assert_equal 'expected a time or date, got ""', e.message, "ensure ArgumentError is not being raised by dependencies.rb" end @@ -139,7 +149,7 @@ class DurationTest < ActiveSupport::TestCase def test_since_and_ago_anchored_to_time_now_when_time_zone_is_not_set Time.zone = nil - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do Time.stub(:now, Time.local(2000)) do # since assert_not_instance_of ActiveSupport::TimeWithZone, 5.seconds.since @@ -152,17 +162,17 @@ class DurationTest < ActiveSupport::TestCase end def test_since_and_ago_anchored_to_time_zone_now_when_time_zone_is_set - Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - with_env_tz 'US/Eastern' do + Time.zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + with_env_tz "US/Eastern" do Time.stub(:now, Time.local(2000)) do # since assert_instance_of ActiveSupport::TimeWithZone, 5.seconds.since assert_equal Time.utc(2000,1,1,0,0,5), 5.seconds.since.time - assert_equal 'Eastern Time (US & Canada)', 5.seconds.since.time_zone.name + assert_equal "Eastern Time (US & Canada)", 5.seconds.since.time_zone.name # ago assert_instance_of ActiveSupport::TimeWithZone, 5.seconds.ago assert_equal Time.utc(1999,12,31,23,59,55), 5.seconds.ago.time - assert_equal 'Eastern Time (US & Canada)', 5.seconds.ago.time_zone.name + assert_equal "Eastern Time (US & Canada)", 5.seconds.ago.time_zone.name end end ensure @@ -170,13 +180,13 @@ class DurationTest < ActiveSupport::TestCase end def test_adding_hours_across_dst_boundary - with_env_tz 'CET' do + with_env_tz "CET" do assert_equal Time.local(2009,3,29,0,0,0) + 24.hours, Time.local(2009,3,30,1,0,0) end end def test_adding_day_across_dst_boundary - with_env_tz 'CET' do + with_env_tz "CET" do assert_equal Time.local(2009,3,29,0,0,0) + 1.day, Time.local(2009,3,30,0,0,0) end end @@ -184,7 +194,7 @@ class DurationTest < ActiveSupport::TestCase def test_delegation_with_block_works counter = 0 assert_nothing_raised do - 1.minute.times {counter += 1} + 1.minute.times { counter += 1 } end assert_equal counter, 60 end @@ -194,11 +204,15 @@ class DurationTest < ActiveSupport::TestCase end def test_to_json - assert_equal '172800', 2.days.to_json + assert_equal "172800", 2.days.to_json end def test_case_when - cased = case 1.day when 1.day then "ok" end + cased = \ + case 1.day + when 1.day + "ok" + end assert_equal cased, "ok" end @@ -222,4 +236,90 @@ class DurationTest < ActiveSupport::TestCase assert_equal(1, (1.minute <=> 1.second)) assert_equal(1, (61 <=> 1.minute)) 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 + # + # Copyright (c) 2012-2014 Arnau Siches + # + # MIT License + # + # Permission is hereby granted, free of charge, to any person obtaining + # a copy of this software and associated documentation files (the + # "Software"), to deal in the Software without restriction, including + # without limitation the rights to use, copy, modify, merge, publish, + # distribute, sublicense, and/or sell copies of the Software, and to + # permit persons to whom the Software is furnished to do so, subject to + # the following conditions: + # + # The above copyright notice and this permission notice shall be + # included in all copies or substantial portions of the Software. + # + # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + def test_iso8601_parsing_wrong_patterns_with_raise + invalid_patterns = ["", "P", "PT", "P1YT", "T", "PW", "P1Y1W", "~P1Y", ".P1Y", "P1.5Y0.5M", "P1.5Y1M", "P1.5MT10.5S"] + invalid_patterns.each do |pattern| + assert_raise ActiveSupport::Duration::ISO8601Parser::ParsingError, pattern.inspect do + ActiveSupport::Duration.parse(pattern) + end + end + end + + def test_iso8601_output + expectations = [ + ["P1Y", 1.year ], + ["P1W", 1.week ], + ["P1Y1M", 1.year + 1.month ], + ["P1Y1M1D", 1.year + 1.month + 1.day ], + ["-P1Y1D", -1.year - 1.day ], + ["P1Y-1DT-1S", 1.year - 1.day - 1.second ], # Parts with different signs are exists in PostgreSQL interval datatype. + ["PT1S", 1.second ], + ["PT1.4S", (1.4).seconds ], + ["P1Y1M1DT1H", 1.year + 1.month + 1.day + 1.hour], + ["PT0S", 0.minutes ], + ] + expectations.each do |expected_output, duration| + assert_equal expected_output, duration.iso8601, expected_output.inspect + end + end + + def test_iso8601_output_precision + expectations = [ + [nil, "P1Y1MT5.55S", 1.year + 1.month + (5.55).seconds ], + [0, "P1Y1MT6S", 1.year + 1.month + (5.55).seconds ], + [1, "P1Y1MT5.5S", 1.year + 1.month + (5.55).seconds ], + [2, "P1Y1MT5.55S", 1.year + 1.month + (5.55).seconds ], + [3, "P1Y1MT5.550S", 1.year + 1.month + (5.55).seconds ], + [nil, "PT1S", 1.second ], + [2, "PT1.00S", 1.second ], + [nil, "PT1.4S", (1.4).seconds ], + [0, "PT1S", (1.4).seconds ], + [1, "PT1.4S", (1.4).seconds ], + [5, "PT1.40000S", (1.4).seconds ], + ] + expectations.each do |precision, expected_output, duration| + assert_equal expected_output, duration.iso8601(precision: precision), expected_output.inspect + end + end + + def test_iso8601_output_and_reparsing + patterns = %w[ + P1Y P0.5Y P0,5Y P1Y1M P1Y0.5M P1Y0,5M P1Y1M1D P1Y1M0.5D P1Y1M0,5D P1Y1M1DT1H P1Y1M1DT0.5H P1Y1M1DT0,5H P1W +P1Y -P1Y + P1Y1M1DT1H1M P1Y1M1DT1H0.5M P1Y1M1DT1H0,5M P1Y1M1DT1H1M1S P1Y1M1DT1H1M1.0S P1Y1M1DT1H1M1,0S P-1Y-2M3DT-4H-5M-6S + ] + # That could be weird, but if we parse P1Y1M0.5D and output it to ISO 8601, we'll get P1Y1MT12.0H. + # So we check that initially parsed and reparsed duration added to time will result in the same time. + time = Time.current + patterns.each do |pattern| + duration = ActiveSupport::Duration.parse(pattern) + assert_equal time+duration, time+ActiveSupport::Duration.parse(duration.iso8601), pattern.inspect + end + end end diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index f09b7d8850..9072957e0e 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'active_support/core_ext/array' -require 'active_support/core_ext/enumerable' +require "abstract_unit" +require "active_support/core_ext/array" +require "active_support/core_ext/enumerable" Payment = Struct.new(:price) ExpandedPayment = Struct.new(:dollars, :cents) @@ -10,26 +10,31 @@ class SummablePayment < Payment end class EnumerableTests < ActiveSupport::TestCase - class GenericEnumerable include Enumerable + def initialize(values = [1, 2, 3]) @values = values end def each - @values.each{|v| yield v} + @values.each { |v| yield v } end end + def assert_typed_equal(e, v, cls, msg=nil) + assert_kind_of(cls, v, msg) + assert_equal(e, v, msg) + end + def test_sums enum = GenericEnumerable.new([5, 15, 10]) assert_equal 30, enum.sum - assert_equal 60, enum.sum { |i| i * 2} + assert_equal 60, enum.sum { |i| i * 2 } enum = GenericEnumerable.new(%w(a b c)) - assert_equal 'abc', enum.sum - assert_equal 'aabbcc', enum.sum { |i| i * 2 } + assert_equal "abc", enum.sum + assert_equal "aabbcc", enum.sum { |i| i * 2 } payments = GenericEnumerable.new([ Payment.new(5), Payment.new(15), Payment.new(10) ]) assert_equal 30, payments.sum(&:price) @@ -38,6 +43,40 @@ class EnumerableTests < ActiveSupport::TestCase payments = GenericEnumerable.new([ SummablePayment.new(5), SummablePayment.new(15) ]) assert_equal SummablePayment.new(20), payments.sum assert_equal SummablePayment.new(20), payments.sum { |p| p } + + sum = GenericEnumerable.new([3, 5.quo(1)]).sum + assert_typed_equal(8, sum, Rational) + + sum = GenericEnumerable.new([3, 5.quo(1)]).sum(0.0) + assert_typed_equal(8.0, sum, Float) + + sum = GenericEnumerable.new([3, 5.quo(1), 7.0]).sum + assert_typed_equal(15.0, sum, Float) + + sum = GenericEnumerable.new([3, 5.quo(1), Complex(7)]).sum + assert_typed_equal(Complex(15), sum, Complex) + assert_typed_equal(15, sum.real, Rational) + assert_typed_equal(0, sum.imag, Integer) + + sum = GenericEnumerable.new([3.5, 5]).sum + assert_typed_equal(8.5, sum, Float) + + sum = GenericEnumerable.new([2, 8.5]).sum + assert_typed_equal(10.5, sum, Float) + + sum = GenericEnumerable.new([1.quo(2), 1]).sum + assert_typed_equal(3.quo(2), sum, Rational) + + sum = GenericEnumerable.new([1.quo(2), 1.quo(3)]).sum + assert_typed_equal(5.quo(6), sum, Rational) + + sum = GenericEnumerable.new([2.0, 3.0*Complex::I]).sum + assert_typed_equal(Complex(2.0, 3.0), sum, Complex) + assert_typed_equal(2.0, sum.real, Float) + assert_typed_equal(3.0, sum.imag, Float) + + sum = GenericEnumerable.new([1, 2]).sum(10) { |v| v * 2 } + assert_typed_equal(16, sum, Integer) end def test_nil_sums @@ -55,6 +94,7 @@ class EnumerableTests < ActiveSupport::TestCase assert_equal 0, GenericEnumerable.new([]).sum assert_equal 0, GenericEnumerable.new([]).sum { |i| i + 10 } assert_equal Payment.new(0), GenericEnumerable.new([]).sum(Payment.new(0)) + assert_typed_equal 0.0, GenericEnumerable.new([]).sum(0.0), Float end def test_range_sums @@ -62,12 +102,68 @@ class EnumerableTests < ActiveSupport::TestCase assert_equal 10, (1..4).sum assert_equal 10, (1..4.5).sum assert_equal 6, (1...4).sum - assert_equal 'abc', ('a'..'c').sum + assert_equal "abc", ("a".."c").sum assert_equal 50_000_005_000_000, (0..10_000_000).sum assert_equal 0, (10..0).sum assert_equal 5, (10..0).sum(5) assert_equal 10, (10..10).sum assert_equal 42, (10...10).sum(42) + assert_typed_equal 20.0, (1..4).sum(0.0) { |i| i * 2 }, Float + assert_typed_equal 10.0, (1..4).sum(0.0), Float + assert_typed_equal 20.0, (1..4).sum(10.0), Float + assert_typed_equal 5.0, (10..0).sum(5.0), Float + end + + def test_array_sums + enum = [5, 15, 10] + assert_equal 30, enum.sum + assert_equal 60, enum.sum { |i| i * 2 } + + enum = %w(a b c) + assert_equal "abc", enum.sum + assert_equal "aabbcc", enum.sum { |i| i * 2 } + + payments = [ Payment.new(5), Payment.new(15), Payment.new(10) ] + assert_equal 30, payments.sum(&:price) + assert_equal 60, payments.sum { |p| p.price * 2 } + + payments = [ SummablePayment.new(5), SummablePayment.new(15) ] + assert_equal SummablePayment.new(20), payments.sum + assert_equal SummablePayment.new(20), payments.sum { |p| p } + + sum = [3, 5.quo(1)].sum + assert_typed_equal(8, sum, Rational) + + sum = [3, 5.quo(1)].sum(0.0) + assert_typed_equal(8.0, sum, Float) + + sum = [3, 5.quo(1), 7.0].sum + assert_typed_equal(15.0, sum, Float) + + sum = [3, 5.quo(1), Complex(7)].sum + assert_typed_equal(Complex(15), sum, Complex) + assert_typed_equal(15, sum.real, Rational) + assert_typed_equal(0, sum.imag, Integer) + + sum = [3.5, 5].sum + assert_typed_equal(8.5, sum, Float) + + sum = [2, 8.5].sum + assert_typed_equal(10.5, sum, Float) + + sum = [1.quo(2), 1].sum + assert_typed_equal(3.quo(2), sum, Rational) + + sum = [1.quo(2), 1.quo(3)].sum + assert_typed_equal(5.quo(6), sum, Rational) + + sum = [2.0, 3.0*Complex::I].sum + assert_typed_equal(Complex(2.0, 3.0), sum, Complex) + assert_typed_equal(2.0, sum.real, Float) + assert_typed_equal(3.0, sum.imag, Float) + + sum = [1, 2].sum(10) { |v| v * 2 } + assert_typed_equal(16, sum, Integer) end def test_index_by @@ -88,17 +184,17 @@ class EnumerableTests < ActiveSupport::TestCase assert_equal false, GenericEnumerable.new([ 1 ] ).many? assert_equal true, GenericEnumerable.new([ 1, 2 ] ).many? - assert_equal false, GenericEnumerable.new([] ).many? {|x| x > 1 } - assert_equal false, GenericEnumerable.new([ 2 ] ).many? {|x| x > 1 } - assert_equal false, GenericEnumerable.new([ 1, 2 ] ).many? {|x| x > 1 } - assert_equal true, GenericEnumerable.new([ 1, 2, 2 ]).many? {|x| x > 1 } + assert_equal false, GenericEnumerable.new([] ).many? { |x| x > 1 } + assert_equal false, GenericEnumerable.new([ 2 ] ).many? { |x| x > 1 } + assert_equal false, GenericEnumerable.new([ 1, 2 ] ).many? { |x| x > 1 } + assert_equal true, GenericEnumerable.new([ 1, 2, 2 ]).many? { |x| x > 1 } end def test_many_iterates_only_on_what_is_needed infinity = 1.0/0.0 very_long_enum = 0..infinity assert_equal true, very_long_enum.many? - assert_equal true, very_long_enum.many?{|x| x > 100} + assert_equal true, very_long_enum.many? { |x| x > 100 } end def test_exclude? @@ -110,7 +206,7 @@ class EnumerableTests < ActiveSupport::TestCase assert_equal [1, 2, 4], GenericEnumerable.new((1..5).to_a).without(3, 5) assert_equal [1, 2, 4], (1..5).to_a.without(3, 5) assert_equal [1, 2, 4], (1..5).to_set.without(3, 5) - assert_equal({foo: 1, baz: 3}, {foo: 1, bar: 2, baz: 3}.without(:bar)) + assert_equal({ foo: 1, baz: 3 }, { foo: 1, bar: 2, baz: 3 }.without(:bar)) end def test_pluck diff --git a/activesupport/test/core_ext/file_test.rb b/activesupport/test/core_ext/file_test.rb index cde0132b97..df5d09acd0 100644 --- a/activesupport/test/core_ext/file_test.rb +++ b/activesupport/test/core_ext/file_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/file' +require "abstract_unit" +require "active_support/core_ext/file" class AtomicWriteTest < ActiveSupport::TestCase def test_atomic_write_without_errors diff --git a/activesupport/test/core_ext/hash/transform_keys_test.rb b/activesupport/test/core_ext/hash/transform_keys_test.rb index 99af274614..7a11d827f8 100644 --- a/activesupport/test/core_ext/hash/transform_keys_test.rb +++ b/activesupport/test/core_ext/hash/transform_keys_test.rb @@ -1,46 +1,62 @@ -require 'abstract_unit' -require 'active_support/core_ext/hash/keys' +require "abstract_unit" +require "active_support/core_ext/hash/keys" class TransformKeysTest < ActiveSupport::TestCase test "transform_keys returns a new hash with the keys computed from the block" do - original = { a: 'a', b: 'b' } + original = { a: "a", b: "b" } mapped = original.transform_keys { |k| "#{k}!".to_sym } - assert_equal({ a: 'a', b: 'b' }, original) - assert_equal({ a!: 'a', b!: 'b' }, mapped) + assert_equal({ a: "a", b: "b" }, original) + assert_equal({ a!: "a", b!: "b" }, mapped) end test "transform_keys! modifies the keys of the original" do - original = { a: 'a', b: 'b' } + original = { a: "a", b: "b" } mapped = original.transform_keys! { |k| "#{k}!".to_sym } - assert_equal({ a!: 'a', b!: 'b' }, original) + assert_equal({ a!: "a", b!: "b" }, original) assert_same original, mapped end test "transform_keys returns a sized Enumerator if no block is given" do - original = { a: 'a', b: 'b' } + original = { a: "a", b: "b" } enumerator = original.transform_keys assert_equal original.size, enumerator.size assert_equal Enumerator, enumerator.class end test "transform_keys! returns a sized Enumerator if no block is given" do - original = { a: 'a', b: 'b' } + original = { a: "a", b: "b" } enumerator = original.transform_keys! assert_equal original.size, enumerator.size assert_equal Enumerator, enumerator.class end test "transform_keys is chainable with Enumerable methods" do - original = { a: 'a', b: 'b' } + original = { a: "a", b: "b" } mapped = original.transform_keys.with_index { |k, i| [k, i].join.to_sym } - assert_equal({ a0: 'a', b1: 'b' }, mapped) + assert_equal({ a0: "a", b1: "b" }, mapped) end test "transform_keys! is chainable with Enumerable methods" do - original = { a: 'a', b: 'b' } + original = { a: "a", b: "b" } original.transform_keys!.with_index { |k, i| [k, i].join.to_sym } - assert_equal({ a0: 'a', b1: 'b' }, original) + assert_equal({ a0: "a", b1: "b" }, original) + end + + test "transform_keys returns a Hash instance when self is inherited from Hash" do + class HashDescendant < ::Hash + def initialize(elements = nil) + super(elements) + (elements || {}).each_pair { |key, value| self[key] = value } + end + end + + original = HashDescendant.new(a: "a", b: "b") + mapped = original.transform_keys { |k| "#{k}!".to_sym } + + assert_equal({ a: "a", b: "b" }, original) + assert_equal({ a!: "a", b!: "b" }, mapped) + assert_equal(::Hash, mapped.class) end end diff --git a/activesupport/test/core_ext/hash/transform_values_test.rb b/activesupport/test/core_ext/hash/transform_values_test.rb index 114022fbaf..f2ac4ce6ce 100644 --- a/activesupport/test/core_ext/hash/transform_values_test.rb +++ b/activesupport/test/core_ext/hash/transform_values_test.rb @@ -1,75 +1,75 @@ -require 'abstract_unit' -require 'active_support/core_ext/hash/indifferent_access' -require 'active_support/core_ext/hash/transform_values' +require "abstract_unit" +require "active_support/core_ext/hash/indifferent_access" +require "active_support/core_ext/hash/transform_values" class TransformValuesTest < ActiveSupport::TestCase test "transform_values returns a new hash with the values computed from the block" do - original = { a: 'a', b: 'b' } - mapped = original.transform_values { |v| v + '!' } + original = { a: "a", b: "b" } + mapped = original.transform_values { |v| v + "!" } - assert_equal({ a: 'a', b: 'b' }, original) - assert_equal({ a: 'a!', b: 'b!' }, mapped) + assert_equal({ a: "a", b: "b" }, original) + assert_equal({ a: "a!", b: "b!" }, mapped) end test "transform_values! modifies the values of the original" do - original = { a: 'a', b: 'b' } - mapped = original.transform_values! { |v| v + '!' } + original = { a: "a", b: "b" } + mapped = original.transform_values! { |v| v + "!" } - assert_equal({ a: 'a!', b: 'b!' }, original) + assert_equal({ a: "a!", b: "b!" }, original) assert_same original, mapped end test "indifferent access is still indifferent after mapping values" do - original = { a: 'a', b: 'b' }.with_indifferent_access - mapped = original.transform_values { |v| v + '!' } + original = { a: "a", b: "b" }.with_indifferent_access + mapped = original.transform_values { |v| v + "!" } - assert_equal 'a!', mapped[:a] - assert_equal 'a!', mapped['a'] + assert_equal "a!", mapped[:a] + assert_equal "a!", mapped["a"] end # This is to be consistent with the behavior of Ruby's built in methods # (e.g. #select, #reject) as of 2.2 test "default values do not persist during mapping" do - original = Hash.new('foo') - original[:a] = 'a' - mapped = original.transform_values { |v| v + '!' } + original = Hash.new("foo") + original[:a] = "a" + mapped = original.transform_values { |v| v + "!" } - assert_equal 'a!', mapped[:a] + assert_equal "a!", mapped[:a] assert_nil mapped[:b] end test "default procs do not persist after mapping" do - original = Hash.new { 'foo' } - original[:a] = 'a' - mapped = original.transform_values { |v| v + '!' } + original = Hash.new { "foo" } + original[:a] = "a" + mapped = original.transform_values { |v| v + "!" } - assert_equal 'a!', mapped[:a] + assert_equal "a!", mapped[:a] assert_nil mapped[:b] end test "transform_values returns a sized Enumerator if no block is given" do - original = { a: 'a', b: 'b' } + original = { a: "a", b: "b" } enumerator = original.transform_values assert_equal original.size, enumerator.size assert_equal Enumerator, enumerator.class end test "transform_values! returns a sized Enumerator if no block is given" do - original = { a: 'a', b: 'b' } + original = { a: "a", b: "b" } enumerator = original.transform_values! assert_equal original.size, enumerator.size assert_equal Enumerator, enumerator.class end test "transform_values is chainable with Enumerable methods" do - original = { a: 'a', b: 'b' } + original = { a: "a", b: "b" } mapped = original.transform_values.with_index { |v, i| [v, i].join } - assert_equal({ a: 'a0', b: 'b1' }, mapped) + assert_equal({ a: "a0", b: "b1" }, mapped) end test "transform_values! is chainable with Enumerable methods" do - original = { a: 'a', b: 'b' } + original = { a: "a", b: "b" } original.transform_values!.with_index { |v, i| [v, i].join } - assert_equal({ a: 'a0', b: 'b1' }, original) + assert_equal({ a: "a0", b: "b1" }, original) end end diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index be8583e704..ae35adb19b 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -1,11 +1,11 @@ -require 'abstract_unit' -require 'active_support/core_ext/hash' -require 'bigdecimal' -require 'active_support/core_ext/string/access' -require 'active_support/ordered_hash' -require 'active_support/core_ext/object/conversions' -require 'active_support/core_ext/object/deep_dup' -require 'active_support/inflections' +require "abstract_unit" +require "active_support/core_ext/hash" +require "bigdecimal" +require "active_support/core_ext/string/access" +require "active_support/ordered_hash" +require "active_support/core_ext/object/conversions" +require "active_support/core_ext/object/deep_dup" +require "active_support/inflections" class HashExtTest < ActiveSupport::TestCase class IndifferentHash < ActiveSupport::HashWithIndifferentAccess @@ -34,22 +34,22 @@ class HashExtTest < ActiveSupport::TestCase end def setup - @strings = { 'a' => 1, 'b' => 2 } - @nested_strings = { 'a' => { 'b' => { 'c' => 3 } } } - @symbols = { :a => 1, :b => 2 } - @nested_symbols = { :a => { :b => { :c => 3 } } } - @mixed = { :a => 1, 'b' => 2 } - @nested_mixed = { 'a' => { :b => { 'c' => 3 } } } - @fixnums = { 0 => 1, 1 => 2 } - @nested_fixnums = { 0 => { 1 => { 2 => 3} } } + @strings = { "a" => 1, "b" => 2 } + @nested_strings = { "a" => { "b" => { "c" => 3 } } } + @symbols = { a: 1, b: 2 } + @nested_symbols = { a: { b: { c: 3 } } } + @mixed = { :a => 1, "b" => 2 } + @nested_mixed = { "a" => { b: { "c" => 3 } } } + @integers = { 0 => 1, 1 => 2 } + @nested_integers = { 0 => { 1 => { 2 => 3 } } } @illegal_symbols = { [] => 3 } - @nested_illegal_symbols = { [] => { [] => 3} } - @upcase_strings = { 'A' => 1, 'B' => 2 } - @nested_upcase_strings = { 'A' => { 'B' => { 'C' => 3 } } } - @string_array_of_hashes = { 'a' => [ { 'b' => 2 }, { 'c' => 3 }, 4 ] } - @symbol_array_of_hashes = { :a => [ { :b => 2 }, { :c => 3 }, 4 ] } - @mixed_array_of_hashes = { :a => [ { :b => 2 }, { 'c' => 3 }, 4 ] } - @upcase_array_of_hashes = { 'A' => [ { 'B' => 2 }, { 'C' => 3 }, 4 ] } + @nested_illegal_symbols = { [] => { [] => 3 } } + @upcase_strings = { "A" => 1, "B" => 2 } + @nested_upcase_strings = { "A" => { "B" => { "C" => 3 } } } + @string_array_of_hashes = { "a" => [ { "b" => 2 }, { "c" => 3 }, 4 ] } + @symbol_array_of_hashes = { a: [ { b: 2 }, { c: 3 }, 4 ] } + @mixed_array_of_hashes = { a: [ { b: 2 }, { "c" => 3 }, 4 ] } + @upcase_array_of_hashes = { "A" => [ { "B" => 2 }, { "C" => 3 }, 4 ] } end def test_methods @@ -75,59 +75,59 @@ class HashExtTest < ActiveSupport::TestCase end def test_transform_keys - assert_equal @upcase_strings, @strings.transform_keys{ |key| key.to_s.upcase } - assert_equal @upcase_strings, @symbols.transform_keys{ |key| key.to_s.upcase } - assert_equal @upcase_strings, @mixed.transform_keys{ |key| key.to_s.upcase } + assert_equal @upcase_strings, @strings.transform_keys { |key| key.to_s.upcase } + assert_equal @upcase_strings, @symbols.transform_keys { |key| key.to_s.upcase } + assert_equal @upcase_strings, @mixed.transform_keys { |key| key.to_s.upcase } end def test_transform_keys_not_mutates transformed_hash = @mixed.dup - transformed_hash.transform_keys{ |key| key.to_s.upcase } + transformed_hash.transform_keys { |key| key.to_s.upcase } assert_equal @mixed, transformed_hash end def test_deep_transform_keys - assert_equal @nested_upcase_strings, @nested_symbols.deep_transform_keys{ |key| key.to_s.upcase } - assert_equal @nested_upcase_strings, @nested_strings.deep_transform_keys{ |key| key.to_s.upcase } - assert_equal @nested_upcase_strings, @nested_mixed.deep_transform_keys{ |key| key.to_s.upcase } - assert_equal @upcase_array_of_hashes, @string_array_of_hashes.deep_transform_keys{ |key| key.to_s.upcase } - assert_equal @upcase_array_of_hashes, @symbol_array_of_hashes.deep_transform_keys{ |key| key.to_s.upcase } - assert_equal @upcase_array_of_hashes, @mixed_array_of_hashes.deep_transform_keys{ |key| key.to_s.upcase } + assert_equal @nested_upcase_strings, @nested_symbols.deep_transform_keys { |key| key.to_s.upcase } + assert_equal @nested_upcase_strings, @nested_strings.deep_transform_keys { |key| key.to_s.upcase } + assert_equal @nested_upcase_strings, @nested_mixed.deep_transform_keys { |key| key.to_s.upcase } + assert_equal @upcase_array_of_hashes, @string_array_of_hashes.deep_transform_keys { |key| key.to_s.upcase } + assert_equal @upcase_array_of_hashes, @symbol_array_of_hashes.deep_transform_keys { |key| key.to_s.upcase } + assert_equal @upcase_array_of_hashes, @mixed_array_of_hashes.deep_transform_keys { |key| key.to_s.upcase } end def test_deep_transform_keys_not_mutates transformed_hash = @nested_mixed.deep_dup - transformed_hash.deep_transform_keys{ |key| key.to_s.upcase } + transformed_hash.deep_transform_keys { |key| key.to_s.upcase } assert_equal @nested_mixed, transformed_hash end def test_transform_keys! - assert_equal @upcase_strings, @symbols.dup.transform_keys!{ |key| key.to_s.upcase } - assert_equal @upcase_strings, @strings.dup.transform_keys!{ |key| key.to_s.upcase } - assert_equal @upcase_strings, @mixed.dup.transform_keys!{ |key| key.to_s.upcase } + assert_equal @upcase_strings, @symbols.dup.transform_keys! { |key| key.to_s.upcase } + assert_equal @upcase_strings, @strings.dup.transform_keys! { |key| key.to_s.upcase } + assert_equal @upcase_strings, @mixed.dup.transform_keys! { |key| key.to_s.upcase } end def test_transform_keys_with_bang_mutates transformed_hash = @mixed.dup - transformed_hash.transform_keys!{ |key| key.to_s.upcase } + transformed_hash.transform_keys! { |key| key.to_s.upcase } assert_equal @upcase_strings, transformed_hash - assert_equal @mixed, { :a => 1, "b" => 2 } + assert_equal @mixed, :a => 1, "b" => 2 end def test_deep_transform_keys! - assert_equal @nested_upcase_strings, @nested_symbols.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase } - assert_equal @nested_upcase_strings, @nested_strings.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase } - assert_equal @nested_upcase_strings, @nested_mixed.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase } - assert_equal @upcase_array_of_hashes, @string_array_of_hashes.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase } - assert_equal @upcase_array_of_hashes, @symbol_array_of_hashes.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase } - assert_equal @upcase_array_of_hashes, @mixed_array_of_hashes.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase } + assert_equal @nested_upcase_strings, @nested_symbols.deep_dup.deep_transform_keys! { |key| key.to_s.upcase } + assert_equal @nested_upcase_strings, @nested_strings.deep_dup.deep_transform_keys! { |key| key.to_s.upcase } + assert_equal @nested_upcase_strings, @nested_mixed.deep_dup.deep_transform_keys! { |key| key.to_s.upcase } + assert_equal @upcase_array_of_hashes, @string_array_of_hashes.deep_dup.deep_transform_keys! { |key| key.to_s.upcase } + assert_equal @upcase_array_of_hashes, @symbol_array_of_hashes.deep_dup.deep_transform_keys! { |key| key.to_s.upcase } + assert_equal @upcase_array_of_hashes, @mixed_array_of_hashes.deep_dup.deep_transform_keys! { |key| key.to_s.upcase } end def test_deep_transform_keys_with_bang_mutates transformed_hash = @nested_mixed.deep_dup - transformed_hash.deep_transform_keys!{ |key| key.to_s.upcase } + 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 @nested_mixed, "a" => { b: { "c" => 3 } } 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 @mixed, :a => 1, "b" => 2 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 @nested_mixed, "a" => { b: { "c" => 3 } } end def test_symbolize_keys_preserves_keys_that_cant_be_symbolized @@ -196,14 +196,14 @@ class HashExtTest < ActiveSupport::TestCase assert_equal @nested_illegal_symbols, @nested_illegal_symbols.deep_dup.deep_symbolize_keys! end - def test_symbolize_keys_preserves_fixnum_keys - assert_equal @fixnums, @fixnums.symbolize_keys - assert_equal @fixnums, @fixnums.dup.symbolize_keys! + def test_symbolize_keys_preserves_integer_keys + assert_equal @integers, @integers.symbolize_keys + assert_equal @integers, @integers.dup.symbolize_keys! end - def test_deep_symbolize_keys_preserves_fixnum_keys - assert_equal @nested_fixnums, @nested_fixnums.deep_symbolize_keys - assert_equal @nested_fixnums, @nested_fixnums.deep_dup.deep_symbolize_keys! + def test_deep_symbolize_keys_preserves_integer_keys + assert_equal @nested_integers, @nested_integers.deep_symbolize_keys + assert_equal @nested_integers, @nested_integers.deep_dup.deep_symbolize_keys! end def test_stringify_keys @@ -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 @mixed, :a => 1, "b" => 2 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 @nested_mixed, "a" => { b: { "c" => 3 } } end def test_symbolize_keys_for_hash_with_indifferent_access @@ -276,7 +276,6 @@ class HashExtTest < ActiveSupport::TestCase assert_equal @nested_symbols, @nested_mixed.with_indifferent_access.deep_symbolize_keys end - def test_symbolize_keys_bang_for_hash_with_indifferent_access assert_raise(NoMethodError) { @symbols.with_indifferent_access.dup.symbolize_keys! } assert_raise(NoMethodError) { @strings.with_indifferent_access.dup.symbolize_keys! } @@ -299,14 +298,14 @@ class HashExtTest < ActiveSupport::TestCase assert_raise(NoMethodError) { @nested_illegal_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! } end - def test_symbolize_keys_preserves_fixnum_keys_for_hash_with_indifferent_access - assert_equal @fixnums, @fixnums.with_indifferent_access.symbolize_keys - assert_raise(NoMethodError) { @fixnums.with_indifferent_access.dup.symbolize_keys! } + def test_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access + assert_equal @integers, @integers.with_indifferent_access.symbolize_keys + assert_raise(NoMethodError) { @integers.with_indifferent_access.dup.symbolize_keys! } end - def test_deep_symbolize_keys_preserves_fixnum_keys_for_hash_with_indifferent_access - assert_equal @nested_fixnums, @nested_fixnums.with_indifferent_access.deep_symbolize_keys - assert_raise(NoMethodError) { @nested_fixnums.with_indifferent_access.deep_dup.deep_symbolize_keys! } + def test_deep_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access + assert_equal @nested_integers, @nested_integers.with_indifferent_access.deep_symbolize_keys + assert_raise(NoMethodError) { @nested_integers.with_indifferent_access.deep_dup.deep_symbolize_keys! } end def test_stringify_keys_for_hash_with_indifferent_access @@ -353,31 +352,31 @@ class HashExtTest < ActiveSupport::TestCase @symbols = @symbols.with_indifferent_access @mixed = @mixed.with_indifferent_access - assert_equal 'a', @strings.__send__(:convert_key, :a) + assert_equal "a", @strings.__send__(:convert_key, :a) - assert_equal 1, @strings.fetch('a') + assert_equal 1, @strings.fetch("a") assert_equal 1, @strings.fetch(:a.to_s) assert_equal 1, @strings.fetch(:a) hashes = { :@strings => @strings, :@symbols => @symbols, :@mixed => @mixed } - method_map = { :'[]' => 1, :fetch => 1, :values_at => [1], - :has_key? => true, :include? => true, :key? => true, - :member? => true } + method_map = { '[]': 1, fetch: 1, values_at: [1], + has_key?: true, include?: true, key?: true, + member?: true } hashes.each do |name, hash| method_map.sort_by(&:to_s).each do |meth, expected| - assert_equal(expected, hash.__send__(meth, 'a'), + assert_equal(expected, hash.__send__(meth, "a"), "Calling #{name}.#{meth} 'a'") assert_equal(expected, hash.__send__(meth, :a), "Calling #{name}.#{meth} :a") end end - assert_equal [1, 2], @strings.values_at('a', 'b') + assert_equal [1, 2], @strings.values_at("a", "b") assert_equal [1, 2], @strings.values_at(:a, :b) - assert_equal [1, 2], @symbols.values_at('a', 'b') + assert_equal [1, 2], @symbols.values_at("a", "b") assert_equal [1, 2], @symbols.values_at(:a, :b) - assert_equal [1, 2], @mixed.values_at('a', 'b') + assert_equal [1, 2], @mixed.values_at("a", "b") assert_equal [1, 2], @mixed.values_at(:a, :b) end @@ -412,11 +411,11 @@ class HashExtTest < ActiveSupport::TestCase def test_indifferent_writing hash = HashWithIndifferentAccess.new hash[:a] = 1 - hash['b'] = 2 + 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[:a], 1 assert_equal hash[:b], 2 assert_equal hash[3], 3 @@ -424,56 +423,56 @@ class HashExtTest < ActiveSupport::TestCase def test_indifferent_update hash = HashWithIndifferentAccess.new - hash[:a] = 'a' - hash['b'] = 'b' + hash[:a] = "a" + hash["b"] = "b" updated_with_strings = hash.update(@strings) 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 updated_with_strings["a"], 1 + assert_equal updated_with_strings["b"], 2 assert_equal updated_with_symbols[:a], 1 - assert_equal updated_with_symbols['b'], 2 + assert_equal updated_with_symbols["b"], 2 assert_equal updated_with_symbols[:b], 2 assert_equal updated_with_mixed[:a], 1 - assert_equal updated_with_mixed['b'], 2 + assert_equal updated_with_mixed["b"], 2 assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 } end def test_update_with_to_hash_conversion hash = HashWithIndifferentAccess.new - hash.update HashByConversion.new({ :a => 1 }) - assert_equal hash['a'], 1 + hash.update HashByConversion.new(a: 1) + assert_equal hash["a"], 1 end def test_indifferent_merging hash = HashWithIndifferentAccess.new - hash[:a] = 'failure' - hash['b'] = 'failure' + hash[:a] = "failure" + hash["b"] = "failure" - other = { 'a' => 1, :b => 2 } + other = { "a" => 1, :b => 2 } merged = hash.merge(other) assert_equal HashWithIndifferentAccess, merged.class assert_equal 1, merged[:a] - assert_equal 2, merged['b'] + assert_equal 2, merged["b"] hash.update(other) assert_equal 1, hash[:a] - assert_equal 2, hash['b'] + assert_equal 2, hash["b"] end def test_merge_with_to_hash_conversion hash = HashWithIndifferentAccess.new - merged = hash.merge HashByConversion.new({ :a => 1 }) - assert_equal merged['a'], 1 + merged = hash.merge HashByConversion.new(a: 1) + assert_equal merged["a"], 1 end def test_indifferent_replace @@ -482,7 +481,7 @@ class HashExtTest < ActiveSupport::TestCase replaced = hash.replace(b: 12) - assert hash.key?('b') + assert hash.key?("b") assert !hash.key?(:a) assert_equal 12, hash[:b] assert_same hash, replaced @@ -494,7 +493,7 @@ class HashExtTest < ActiveSupport::TestCase replaced = hash.replace(HashByConversion.new(b: 12)) - assert hash.key?('b') + assert hash.key?("b") assert !hash.key?(:a) assert_equal 12, hash[:b] assert_same hash, replaced @@ -503,18 +502,18 @@ class HashExtTest < ActiveSupport::TestCase def test_indifferent_merging_with_block hash = HashWithIndifferentAccess.new hash[:a] = 1 - hash['b'] = 3 + hash["b"] = 3 - other = { 'a' => 4, :b => 2, 'c' => 10 } + other = { "a" => 4, :b => 2, "c" => 10 } merged = hash.merge(other) { |key, old, new| old > new ? old : new } assert_equal HashWithIndifferentAccess, merged.class assert_equal 4, merged[:a] - assert_equal 3, merged['b'] + assert_equal 3, merged["b"] assert_equal 10, merged[:c] - other_indifferent = HashWithIndifferentAccess.new('a' => 9, :b => 2) + other_indifferent = HashWithIndifferentAccess.new("a" => 9, :b => 2) merged = hash.merge(other_indifferent) { |key, old, new| old + new } @@ -528,26 +527,26 @@ class HashExtTest < ActiveSupport::TestCase hash.reverse_merge! key: :new_value assert_equal :old_value, hash[:key] - hash = HashWithIndifferentAccess.new('some' => 'value', 'other' => 'value') - hash.reverse_merge!(:some => 'noclobber', :another => 'clobber') - assert_equal 'value', hash[:some] - assert_equal 'clobber', hash[:another] + hash = HashWithIndifferentAccess.new("some" => "value", "other" => "value") + hash.reverse_merge!(some: "noclobber", another: "clobber") + assert_equal "value", hash[:some] + assert_equal "clobber", hash[:another] end def test_indifferent_deleting - get_hash = proc{ { :a => 'foo' }.with_indifferent_access } + get_hash = proc { { a: "foo" }.with_indifferent_access } hash = get_hash.call - assert_equal hash.delete(:a), 'foo' + assert_equal hash.delete(:a), "foo" assert_equal hash.delete(:a), nil hash = get_hash.call - assert_equal hash.delete('a'), 'foo' - assert_equal hash.delete('a'), nil + assert_equal hash.delete("a"), "foo" + assert_equal hash.delete("a"), nil end def test_indifferent_select - hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select {|k,v| v == 1} + hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select { |k,v| v == 1 } - assert_equal({ 'a' => 1 }, hash) + assert_equal({ "a" => 1 }, hash) assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash end @@ -557,23 +556,23 @@ class HashExtTest < ActiveSupport::TestCase end def test_indifferent_select_returns_a_hash_when_unchanged - hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select {|k,v| true} + hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select { |k,v| true } assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash end def test_indifferent_select_bang indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings) - indifferent_strings.select! {|k,v| v == 1} + indifferent_strings.select! { |k,v| v == 1 } - assert_equal({ 'a' => 1 }, indifferent_strings) + assert_equal({ "a" => 1 }, indifferent_strings) assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings end def test_indifferent_reject - hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject {|k,v| v != 1} + hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject { |k,v| v != 1 } - assert_equal({ 'a' => 1 }, hash) + assert_equal({ "a" => 1 }, hash) assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash end @@ -584,9 +583,9 @@ class HashExtTest < ActiveSupport::TestCase def test_indifferent_reject_bang indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings) - indifferent_strings.reject! {|k,v| v != 1} + indifferent_strings.reject! { |k,v| v != 1 } - assert_equal({ 'a' => 1 }, indifferent_strings) + assert_equal({ "a" => 1 }, indifferent_strings) assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings end @@ -596,10 +595,10 @@ class HashExtTest < ActiveSupport::TestCase # Should preserve the default value. mixed_with_default = @mixed.dup - mixed_with_default.default = '1234' + mixed_with_default.default = "1234" roundtrip = mixed_with_default.with_indifferent_access.to_hash assert_equal @strings, roundtrip - assert_equal '1234', roundtrip.default + assert_equal "1234", roundtrip.default # Ensure nested hashes are not HashWithIndiffereneAccess new_to_hash = @nested_mixed.with_indifferent_access.to_hash @@ -609,21 +608,21 @@ class HashExtTest < ActiveSupport::TestCase end def test_lookup_returns_the_same_object_that_is_stored_in_hash_indifferent_access - hash = HashWithIndifferentAccess.new {|h, k| h[k] = []} + hash = HashWithIndifferentAccess.new { |h, k| h[k] = [] } hash[:a] << 1 assert_equal [1], hash[:a] end def test_with_indifferent_access_has_no_side_effects_on_existing_hash - hash = {content: [{:foo => :bar, 'bar' => 'baz'}]} + hash = { content: [{ :foo => :bar, "bar" => "baz" }] } hash.with_indifferent_access assert_equal [:foo, "bar"], hash[:content].first.keys end def test_indifferent_hash_with_array_of_hashes - hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] }}.with_indifferent_access + hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] } }.with_indifferent_access assert_equal "1", hash[:urls][:url].first[:address] hash = hash.to_hash @@ -635,14 +634,14 @@ class HashExtTest < ActiveSupport::TestCase def test_should_preserve_array_subclass_when_value_is_array array = SubclassingArray.new array << { "address" => "1" } - hash = { "urls" => { "url" => array }}.with_indifferent_access + hash = { "urls" => { "url" => array } }.with_indifferent_access assert_equal SubclassingArray, hash[:urls][:url].class end def test_should_preserve_array_class_when_hash_value_is_frozen_array array = SubclassingArray.new array << { "address" => "1" } - hash = { "urls" => { "url" => array.freeze }}.with_indifferent_access + hash = { "urls" => { "url" => array.freeze } }.with_indifferent_access assert_equal SubclassingArray, hash[:urls][:url].class end @@ -650,9 +649,9 @@ class HashExtTest < ActiveSupport::TestCase h = HashWithIndifferentAccess.new h[:first] = 1 h = h.stringify_keys - assert_equal 1, h['first'] + assert_equal 1, h["first"] h = HashWithIndifferentAccess.new - h['first'] = 1 + h["first"] = 1 h = h.symbolize_keys assert_equal 1, h[:first] end @@ -661,40 +660,40 @@ class HashExtTest < ActiveSupport::TestCase h = HashWithIndifferentAccess.new h[:first] = 1 h = h.deep_stringify_keys - assert_equal 1, h['first'] + assert_equal 1, h["first"] h = HashWithIndifferentAccess.new - h['first'] = 1 + h["first"] = 1 h = h.deep_symbolize_keys assert_equal 1, h[:first] end def test_to_options_on_indifferent_preserves_hash h = HashWithIndifferentAccess.new - h['first'] = 1 + h["first"] = 1 h.to_options! - assert_equal 1, h['first'] + assert_equal 1, h["first"] end def test_to_options_on_indifferent_preserves_works_as_hash_with_dup - h = HashWithIndifferentAccess.new({ a: { b: 'b' } }) + h = HashWithIndifferentAccess.new(a: { b: "b" }) dup = h.dup - dup[:a][:c] = 'c' - assert_equal 'c', h[:a][:c] + dup[:a][:c] = "c" + assert_equal "c", h[:a][:c] end def test_indifferent_sub_hashes - h = {'user' => {'id' => 5}}.with_indifferent_access - ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}} + h = { "user" => { "id" => 5 } }.with_indifferent_access + ["user", :user].each { |user| [:id, "id"].each { |id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5" } } - h = {:user => {:id => 5}}.with_indifferent_access - ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}} + h = { user: { id: 5 } }.with_indifferent_access + ["user", :user].each { |user| [:id, "id"].each { |id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5" } } end def test_indifferent_duplication # Should preserve default value h = HashWithIndifferentAccess.new - h.default = '1234' + h.default = "1234" assert_equal h.default, h.dup.default # Should preserve class for subclasses @@ -704,52 +703,52 @@ class HashExtTest < ActiveSupport::TestCase def test_nested_dig_indifferent_access skip if RUBY_VERSION < "2.3.0" - data = {"this" => {"views" => 1234}}.with_indifferent_access + data = { "this" => { "views" => 1234 } }.with_indifferent_access assert_equal 1234, data.dig(:this, :views) end def test_assert_valid_keys assert_nothing_raised do - { :failure => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ]) - { :failure => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny) + { failure: "stuff", funny: "business" }.assert_valid_keys([ :failure, :funny ]) + { failure: "stuff", funny: "business" }.assert_valid_keys(:failure, :funny) end # not all valid keys are required to be present assert_nothing_raised do - { :failure => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny, :sunny ]) - { :failure => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny, :sunny) + { failure: "stuff", funny: "business" }.assert_valid_keys([ :failure, :funny, :sunny ]) + { failure: "stuff", funny: "business" }.assert_valid_keys(:failure, :funny, :sunny) end exception = assert_raise ArgumentError do - { :failore => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ]) + { failore: "stuff", funny: "business" }.assert_valid_keys([ :failure, :funny ]) end assert_equal "Unknown key: :failore. Valid keys are: :failure, :funny", exception.message exception = assert_raise ArgumentError do - { :failore => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny) + { failore: "stuff", funny: "business" }.assert_valid_keys(:failure, :funny) end assert_equal "Unknown key: :failore. Valid keys are: :failure, :funny", exception.message exception = assert_raise ArgumentError do - { :failore => "stuff", :funny => "business" }.assert_valid_keys([ :failure ]) + { failore: "stuff", funny: "business" }.assert_valid_keys([ :failure ]) end assert_equal "Unknown key: :failore. Valid keys are: :failure", exception.message exception = assert_raise ArgumentError do - { :failore => "stuff", :funny => "business" }.assert_valid_keys(:failure) + { failore: "stuff", funny: "business" }.assert_valid_keys(:failure) end assert_equal "Unknown key: :failore. Valid keys are: :failure", exception.message end def test_assorted_keys_not_stringified - original = {Object.new => 2, 1 => 2, [] => true} + original = { Object.new => 2, 1 => 2, [] => true } indiff = original.with_indifferent_access - assert(!indiff.keys.any? {|k| k.kind_of? String}, "A key was converted to a string!") + assert(!indiff.keys.any? { |k| k.kind_of? String }, "A key was converted to a string!") end def test_deep_merge - hash_1 = { :a => "a", :b => "b", :c => { :c1 => "c1", :c2 => "c2", :c3 => { :d1 => "d1" } } } - hash_2 = { :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } } - expected = { :a => 1, :b => "b", :c => { :c1 => 2, :c2 => "c2", :c3 => { :d1 => "d1", :d2 => "d2" } } } + hash_1 = { a: "a", b: "b", c: { c1: "c1", c2: "c2", c3: { d1: "d1" } } } + hash_2 = { a: 1, c: { c1: 2, c3: { d2: "d2" } } } + expected = { a: 1, b: "b", c: { c1: 2, c2: "c2", c3: { d1: "d1", d2: "d2" } } } assert_equal expected, hash_1.deep_merge(hash_2) hash_1.deep_merge!(hash_2) @@ -757,9 +756,9 @@ class HashExtTest < ActiveSupport::TestCase end def test_deep_merge_with_block - hash_1 = { :a => "a", :b => "b", :c => { :c1 => "c1", :c2 => "c2", :c3 => { :d1 => "d1" } } } - hash_2 = { :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } } - expected = { :a => [:a, "a", 1], :b => "b", :c => { :c1 => [:c1, "c1", 2], :c2 => "c2", :c3 => { :d1 => "d1", :d2 => "d2" } } } + hash_1 = { a: "a", b: "b", c: { c1: "c1", c2: "c2", c3: { d1: "d1" } } } + hash_2 = { a: 1, c: { c1: 2, c3: { d2: "d2" } } } + expected = { a: [:a, "a", 1], b: "b", c: { c1: [:c1, "c1", 2], c2: "c2", c3: { d1: "d1", d2: "d2" } } } assert_equal(expected, hash_1.deep_merge(hash_2) { |k,o,n| [k, o, n] }) hash_1.deep_merge!(hash_2) { |k,o,n| [k, o, n] } @@ -768,8 +767,8 @@ class HashExtTest < ActiveSupport::TestCase def test_deep_merge_with_falsey_values hash_1 = { e: false } - hash_2 = { e: 'e' } - expected = { e: [:e, false, 'e'] } + hash_2 = { e: "e" } + expected = { e: [:e, false, "e"] } assert_equal(expected, hash_1.deep_merge(hash_2) { |k, o, n| [k, o, n] }) hash_1.deep_merge!(hash_2) { |k, o, n| [k, o, n] } @@ -777,9 +776,9 @@ class HashExtTest < ActiveSupport::TestCase end def test_deep_merge_on_indifferent_access - hash_1 = HashWithIndifferentAccess.new({ :a => "a", :b => "b", :c => { :c1 => "c1", :c2 => "c2", :c3 => { :d1 => "d1" } } }) - hash_2 = HashWithIndifferentAccess.new({ :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } }) - hash_3 = { :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } } + hash_1 = HashWithIndifferentAccess.new(a: "a", b: "b", c: { c1: "c1", c2: "c2", c3: { d1: "d1" } }) + hash_2 = HashWithIndifferentAccess.new(a: 1, c: { c1: 2, c3: { d2: "d2" } }) + hash_3 = { a: 1, c: { c1: 2, c3: { d2: "d2" } } } expected = { "a" => 1, "b" => "b", "c" => { "c1" => 2, "c2" => "c2", "c3" => { "d1" => "d1", "d2" => "d2" } } } assert_equal expected, hash_1.deep_merge(hash_2) assert_equal expected, hash_1.deep_merge(hash_3) @@ -791,9 +790,9 @@ class HashExtTest < ActiveSupport::TestCase def test_store_on_indifferent_access hash = HashWithIndifferentAccess.new hash.store(:test1, 1) - hash.store('test1', 11) + hash.store("test1", 11) hash[:test2] = 2 - hash['test2'] = 22 + hash["test2"] = 22 expected = { "test1" => 11, "test2" => 22 } assert_equal expected, hash end @@ -801,16 +800,16 @@ class HashExtTest < ActiveSupport::TestCase def test_constructor_on_indifferent_access hash = HashWithIndifferentAccess[:foo, 1] assert_equal 1, hash[:foo] - assert_equal 1, hash['foo'] + assert_equal 1, hash["foo"] hash[:foo] = 3 assert_equal 3, hash[:foo] - assert_equal 3, hash['foo'] + assert_equal 3, hash["foo"] end def test_reverse_merge - defaults = { :a => "x", :b => "y", :c => 10 }.freeze - options = { :a => 1, :b => 2 } - expected = { :a => 1, :b => 2, :c => 10 } + defaults = { a: "x", b: "y", c: 10 }.freeze + options = { a: 1, b: 2 } + expected = { a: 1, b: 2, c: 10 } # Should merge defaults into options, creating a new hash. assert_equal expected, options.reverse_merge(defaults) @@ -828,8 +827,8 @@ class HashExtTest < ActiveSupport::TestCase end def test_slice - original = { :a => 'x', :b => 'y', :c => 10 } - expected = { :a => 'x', :b => 'y' } + original = { a: "x", b: "y", c: 10 } + expected = { a: "x", b: "y" } # Should return a new hash with only the given keys. assert_equal expected, original.slice(:a, :b) @@ -837,15 +836,15 @@ class HashExtTest < ActiveSupport::TestCase end def test_slice_inplace - original = { :a => 'x', :b => 'y', :c => 10 } - expected = { :c => 10 } + original = { a: "x", b: "y", c: 10 } + expected = { c: 10 } # Should replace the hash with only the given keys. assert_equal expected, original.slice!(:a, :b) end def test_slice_with_an_array_key - original = { :a => 'x', :b => 'y', :c => 10, [:a, :b] => "an array key" } + original = { :a => "x", :b => "y", :c => 10, [:a, :b] => "an array key" } expected = { [:a, :b] => "an array key", :c => 10 } # Should return a new hash with only the given keys when given an array key. @@ -854,26 +853,26 @@ class HashExtTest < ActiveSupport::TestCase end def test_slice_inplace_with_an_array_key - original = { :a => 'x', :b => 'y', :c => 10, [:a, :b] => "an array key" } - expected = { :a => 'x', :b => 'y' } + original = { :a => "x", :b => "y", :c => 10, [:a, :b] => "an array key" } + expected = { a: "x", b: "y" } # Should replace the hash with only the given keys when given an array key. assert_equal expected, original.slice!([:a, :b], :c) end def test_slice_with_splatted_keys - original = { :a => 'x', :b => 'y', :c => 10, [:a, :b] => "an array key" } - expected = { :a => 'x', :b => "y" } + original = { :a => "x", :b => "y", :c => 10, [:a, :b] => "an array key" } + expected = { a: "x", b: "y" } # Should grab each of the splatted keys. assert_equal expected, original.slice(*[:a, :b]) end def test_indifferent_slice - original = { :a => 'x', :b => 'y', :c => 10 }.with_indifferent_access - expected = { :a => 'x', :b => 'y' }.with_indifferent_access + original = { a: "x", b: "y", c: 10 }.with_indifferent_access + expected = { a: "x", b: "y" }.with_indifferent_access - [['a', 'b'], [:a, :b]].each do |keys| + [["a", "b"], [:a, :b]].each do |keys| # Should return a new hash with only the given keys. assert_equal expected, original.slice(*keys), keys.inspect assert_not_equal expected, original @@ -881,10 +880,10 @@ class HashExtTest < ActiveSupport::TestCase end def test_indifferent_slice_inplace - original = { :a => 'x', :b => 'y', :c => 10 }.with_indifferent_access - expected = { :c => 10 }.with_indifferent_access + original = { a: "x", b: "y", c: 10 }.with_indifferent_access + expected = { c: 10 }.with_indifferent_access - [['a', 'b'], [:a, :b]].each do |keys| + [["a", "b"], [:a, :b]].each do |keys| # Should replace the hash with only the given keys. copy = original.dup assert_equal expected, copy.slice!(*keys) @@ -892,13 +891,13 @@ class HashExtTest < ActiveSupport::TestCase end def test_indifferent_slice_access_with_symbols - original = {'login' => 'bender', 'password' => 'shiny', 'stuff' => 'foo'} + original = { "login" => "bender", "password" => "shiny", "stuff" => "foo" } original = original.with_indifferent_access slice = original.slice(:login, :password) - assert_equal 'bender', slice[:login] - assert_equal 'bender', slice['login'] + assert_equal "bender", slice[:login] + assert_equal "bender", slice["login"] end def test_slice_bang_does_not_override_default @@ -920,17 +919,17 @@ class HashExtTest < ActiveSupport::TestCase end def test_extract - original = {:a => 1, :b => 2, :c => 3, :d => 4} - expected = {:a => 1, :b => 2} - remaining = {:c => 3, :d => 4} + original = { a: 1, b: 2, c: 3, d: 4 } + expected = { a: 1, b: 2 } + remaining = { c: 3, d: 4 } assert_equal expected, original.extract!(:a, :b, :x) assert_equal remaining, original end def test_extract_nils - original = {:a => nil, :b => nil} - expected = {:a => nil} + original = { a: nil, b: nil } + expected = { a: nil } extracted = original.extract!(:a, :x) assert_equal expected, extracted @@ -939,11 +938,11 @@ class HashExtTest < ActiveSupport::TestCase end def test_indifferent_extract - original = {:a => 1, 'b' => 2, :c => 3, 'd' => 4}.with_indifferent_access - expected = {:a => 1, :b => 2}.with_indifferent_access - remaining = {:c => 3, :d => 4}.with_indifferent_access + original = { :a => 1, "b" => 2, :c => 3, "d" => 4 }.with_indifferent_access + expected = { a: 1, b: 2 }.with_indifferent_access + remaining = { c: 3, d: 4 }.with_indifferent_access - [['a', 'b'], [:a, :b]].each do |keys| + [["a", "b"], [:a, :b]].each do |keys| copy = original.dup assert_equal expected, copy.extract!(*keys) assert_equal remaining, copy @@ -951,8 +950,8 @@ class HashExtTest < ActiveSupport::TestCase end def test_except - original = { :a => 'x', :b => 'y', :c => 10 } - expected = { :a => 'x', :b => 'y' } + original = { a: "x", b: "y", c: 10 } + expected = { a: "x", b: "y" } # Should return a new hash without the given keys. assert_equal expected, original.except(:c) @@ -964,8 +963,8 @@ class HashExtTest < ActiveSupport::TestCase end def test_except_with_more_than_one_argument - original = { :a => 'x', :b => 'y', :c => 10 } - expected = { :a => 'x' } + original = { a: "x", b: "y", c: 10 } + expected = { a: "x" } assert_equal expected, original.except(:b, :c) @@ -974,7 +973,7 @@ class HashExtTest < ActiveSupport::TestCase end def test_except_with_original_frozen - original = { :a => 'x', :b => 'y' } + original = { a: "x", b: "y" } original.freeze assert_nothing_raised { original.except(:a) } @@ -982,7 +981,7 @@ class HashExtTest < ActiveSupport::TestCase end def test_except_does_not_delete_values_in_original - original = { :a => 'x', :b => 'y' } + original = { a: "x", b: "y" } assert_not_called(original, :delete) do original.except(:a) end @@ -999,6 +998,10 @@ class HashExtTest < ActiveSupport::TestCase h = hash_with_only_nil_values.dup assert_equal({}, h.compact) assert_equal(hash_with_only_nil_values, h) + + h = @symbols.dup + assert_equal(@symbols, h.compact) + assert_equal(@symbols, h) end def test_compact! @@ -1012,11 +1015,15 @@ class HashExtTest < ActiveSupport::TestCase h = hash_with_only_nil_values.dup assert_equal({}, h.compact!) assert_equal({}, h) + + h = @symbols.dup + assert_equal(nil, h.compact!) + assert_equal(@symbols, h) end def test_new_with_to_hash_conversion hash = HashWithIndifferentAccess.new(HashByConversion.new(a: 1)) - assert hash.key?('a') + assert hash.key?("a") assert_equal 1, hash[:a] end @@ -1034,7 +1041,7 @@ class HashExtTest < ActiveSupport::TestCase assert_equal 3, new_hash[2] new_hash.default = 2 - assert_equal 2, new_hash[:non_existant] + assert_equal 2, new_hash[:non_existent] end def test_to_hash_with_raising_default_proc @@ -1073,10 +1080,10 @@ end class IWriteMyOwnXML def to_xml(options = {}) options[:indent] ||= 2 - xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent]) + xml = options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent]) xml.instruct! unless options[:skip_instruct] xml.level_one do - xml.tag!(:second_level, 'content') + xml.tag!(:second_level, "content") end end end @@ -1089,139 +1096,139 @@ class HashExtToParamTests < ActiveSupport::TestCase end def test_string_hash - assert_equal '', {}.to_param - assert_equal 'hello=world', { :hello => "world" }.to_param - assert_equal 'hello=10', { "hello" => 10 }.to_param - assert_equal 'hello=world&say_bye=true', {:hello => "world", "say_bye" => true}.to_param + assert_equal "", {}.to_param + assert_equal "hello=world", { hello: "world" }.to_param + assert_equal "hello=10", { "hello" => 10 }.to_param + assert_equal "hello=world&say_bye=true", { :hello => "world", "say_bye" => true }.to_param end def test_number_hash - assert_equal '10=20&30=40&50=60', {10 => 20, 30 => 40, 50 => 60}.to_param + assert_equal "10=20&30=40&50=60", { 10 => 20, 30 => 40, 50 => 60 }.to_param end def test_to_param_hash - assert_equal 'custom-1=param-1&custom2-1=param2-1', {ToParam.new('custom') => ToParam.new('param'), ToParam.new('custom2') => ToParam.new('param2')}.to_param + assert_equal "custom-1=param-1&custom2-1=param2-1", { ToParam.new("custom") => ToParam.new("param"), ToParam.new("custom2") => ToParam.new("param2") }.to_param end def test_to_param_hash_escapes_its_keys_and_values - assert_equal 'param+1=A+string+with+%2F+characters+%26+that+should+be+%3F+escaped', { 'param 1' => 'A string with / characters & that should be ? escaped' }.to_param + assert_equal "param+1=A+string+with+%2F+characters+%26+that+should+be+%3F+escaped", { "param 1" => "A string with / characters & that should be ? escaped" }.to_param end def test_to_param_orders_by_key_in_ascending_order - assert_equal 'a=2&b=1&c=0', Hash[*%w(b 1 c 0 a 2)].to_param + assert_equal "a=2&b=1&c=0", Hash[*%w(b 1 c 0 a 2)].to_param end end class HashToXmlTest < ActiveSupport::TestCase def setup - @xml_options = { :root => :person, :skip_instruct => true, :indent => 0 } + @xml_options = { root: :person, skip_instruct: true, indent: 0 } end def test_one_level - xml = { :name => "David", :street => "Paulina" }.to_xml(@xml_options) + xml = { name: "David", street: "Paulina" }.to_xml(@xml_options) assert_equal "<person>", xml.first(8) - assert xml.include?(%(<street>Paulina</street>)) - assert xml.include?(%(<name>David</name>)) + assert_includes xml, %(<street>Paulina</street>) + assert_includes xml, %(<name>David</name>) end def test_one_level_dasherize_false - xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:dasherize => false)) + xml = { name: "David", street_name: "Paulina" }.to_xml(@xml_options.merge(dasherize: false)) assert_equal "<person>", xml.first(8) - assert xml.include?(%(<street_name>Paulina</street_name>)) - assert xml.include?(%(<name>David</name>)) + assert_includes xml, %(<street_name>Paulina</street_name>) + assert_includes xml, %(<name>David</name>) end def test_one_level_dasherize_true - xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:dasherize => true)) + xml = { name: "David", street_name: "Paulina" }.to_xml(@xml_options.merge(dasherize: true)) assert_equal "<person>", xml.first(8) - assert xml.include?(%(<street-name>Paulina</street-name>)) - assert xml.include?(%(<name>David</name>)) + assert_includes xml, %(<street-name>Paulina</street-name>) + assert_includes xml, %(<name>David</name>) end def test_one_level_camelize_true - xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:camelize => true)) + xml = { name: "David", street_name: "Paulina" }.to_xml(@xml_options.merge(camelize: true)) assert_equal "<Person>", xml.first(8) - assert xml.include?(%(<StreetName>Paulina</StreetName>)) - assert xml.include?(%(<Name>David</Name>)) + assert_includes xml, %(<StreetName>Paulina</StreetName>) + assert_includes xml, %(<Name>David</Name>) end def test_one_level_camelize_lower - xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:camelize => :lower)) + xml = { name: "David", street_name: "Paulina" }.to_xml(@xml_options.merge(camelize: :lower)) assert_equal "<person>", xml.first(8) - assert xml.include?(%(<streetName>Paulina</streetName>)) - assert xml.include?(%(<name>David</name>)) + assert_includes xml, %(<streetName>Paulina</streetName>) + assert_includes xml, %(<name>David</name>) end def test_one_level_with_types - xml = { :name => "David", :street => "Paulina", :age => 26, :age_in_millis => 820497600000, :moved_on => Date.new(2005, 11, 15), :resident => :yes }.to_xml(@xml_options) + xml = { name: "David", street: "Paulina", age: 26, age_in_millis: 820497600000, moved_on: Date.new(2005, 11, 15), resident: :yes }.to_xml(@xml_options) assert_equal "<person>", xml.first(8) - assert xml.include?(%(<street>Paulina</street>)) - assert xml.include?(%(<name>David</name>)) - assert xml.include?(%(<age type="integer">26</age>)) - assert xml.include?(%(<age-in-millis type="integer">820497600000</age-in-millis>)) - assert xml.include?(%(<moved-on type="date">2005-11-15</moved-on>)) - assert xml.include?(%(<resident type="symbol">yes</resident>)) + assert_includes xml, %(<street>Paulina</street>) + assert_includes xml, %(<name>David</name>) + assert_includes xml, %(<age type="integer">26</age>) + assert_includes xml, %(<age-in-millis type="integer">820497600000</age-in-millis>) + assert_includes xml, %(<moved-on type="date">2005-11-15</moved-on>) + assert_includes xml, %(<resident type="symbol">yes</resident>) end def test_one_level_with_nils - xml = { :name => "David", :street => "Paulina", :age => nil }.to_xml(@xml_options) + xml = { name: "David", street: "Paulina", age: nil }.to_xml(@xml_options) assert_equal "<person>", xml.first(8) - assert xml.include?(%(<street>Paulina</street>)) - assert xml.include?(%(<name>David</name>)) - assert xml.include?(%(<age nil="true"/>)) + assert_includes xml, %(<street>Paulina</street>) + assert_includes xml, %(<name>David</name>) + assert_includes xml, %(<age nil="true"/>) end def test_one_level_with_skipping_types - xml = { :name => "David", :street => "Paulina", :age => nil }.to_xml(@xml_options.merge(:skip_types => true)) + xml = { name: "David", street: "Paulina", age: nil }.to_xml(@xml_options.merge(skip_types: true)) assert_equal "<person>", xml.first(8) - assert xml.include?(%(<street>Paulina</street>)) - assert xml.include?(%(<name>David</name>)) - assert xml.include?(%(<age nil="true"/>)) + assert_includes xml, %(<street>Paulina</street>) + assert_includes xml, %(<name>David</name>) + assert_includes xml, %(<age nil="true"/>) end def test_one_level_with_yielding - xml = { :name => "David", :street => "Paulina" }.to_xml(@xml_options) do |x| + xml = { name: "David", street: "Paulina" }.to_xml(@xml_options) do |x| x.creator("Rails") end assert_equal "<person>", xml.first(8) - assert xml.include?(%(<street>Paulina</street>)) - assert xml.include?(%(<name>David</name>)) - assert xml.include?(%(<creator>Rails</creator>)) + assert_includes xml, %(<street>Paulina</street>) + assert_includes xml, %(<name>David</name>) + assert_includes xml, %(<creator>Rails</creator>) end def test_two_levels - xml = { :name => "David", :address => { :street => "Paulina" } }.to_xml(@xml_options) + xml = { name: "David", address: { street: "Paulina" } }.to_xml(@xml_options) assert_equal "<person>", xml.first(8) - assert xml.include?(%(<address><street>Paulina</street></address>)) - assert xml.include?(%(<name>David</name>)) + assert_includes xml, %(<address><street>Paulina</street></address>) + assert_includes xml, %(<name>David</name>) end def test_two_levels_with_second_level_overriding_to_xml - xml = { :name => "David", :address => { :street => "Paulina" }, :child => IWriteMyOwnXML.new }.to_xml(@xml_options) + xml = { name: "David", address: { street: "Paulina" }, child: IWriteMyOwnXML.new }.to_xml(@xml_options) assert_equal "<person>", xml.first(8) - assert xml.include?(%(<address><street>Paulina</street></address>)) - assert xml.include?(%(<level_one><second_level>content</second_level></level_one>)) + assert_includes xml, %(<address><street>Paulina</street></address>) + assert_includes xml, %(<level_one><second_level>content</second_level></level_one>) end def test_two_levels_with_array - xml = { :name => "David", :addresses => [{ :street => "Paulina" }, { :street => "Evergreen" }] }.to_xml(@xml_options) + xml = { name: "David", addresses: [{ street: "Paulina" }, { street: "Evergreen" }] }.to_xml(@xml_options) assert_equal "<person>", xml.first(8) - assert xml.include?(%(<addresses type="array"><address>)) - assert xml.include?(%(<address><street>Paulina</street></address>)) - assert xml.include?(%(<address><street>Evergreen</street></address>)) - assert xml.include?(%(<name>David</name>)) + assert_includes xml, %(<addresses type="array"><address>) + assert_includes xml, %(<address><street>Paulina</street></address>) + assert_includes xml, %(<address><street>Evergreen</street></address>) + assert_includes xml, %(<name>David</name>) end def test_three_levels_with_array - xml = { :name => "David", :addresses => [{ :streets => [ { :name => "Paulina" }, { :name => "Paulina" } ] } ] }.to_xml(@xml_options) - assert xml.include?(%(<addresses type="array"><address><streets type="array"><street><name>)) + xml = { name: "David", addresses: [{ streets: [ { name: "Paulina" }, { name: "Paulina" } ] } ] }.to_xml(@xml_options) + assert_includes xml, %(<addresses type="array"><address><streets type="array"><street><name>) end def test_timezoned_attributes xml = { - :created_at => Time.utc(1999,2,2), - :local_created_at => Time.utc(1999,2,2).in_time_zone('Eastern Time (US & Canada)') + created_at: Time.utc(1999,2,2), + local_created_at: Time.utc(1999,2,2).in_time_zone("Eastern Time (US & Canada)") }.to_xml(@xml_options) assert_match %r{<created-at type=\"dateTime\">1999-02-02T00:00:00Z</created-at>}, xml assert_match %r{<local-created-at type=\"dateTime\">1999-02-01T19:00:00-05:00</local-created-at>}, xml @@ -1260,17 +1267,17 @@ class HashToXmlTest < ActiveSupport::TestCase EOT expected_topic_hash = { - :title => "The First Topic", - :author_name => "David", - :id => 1, - :approved => false, - :replies_count => 0, - :replies_close_in => 2592000000, - :written_on => Date.new(2003, 7, 16), - :viewed_at => Time.utc(2003, 7, 16, 9, 28), - :content => "Have a nice day", - :author_email_address => "david@loudthinking.com", - :parent_id => nil + title: "The First Topic", + author_name: "David", + id: 1, + approved: false, + replies_count: 0, + replies_close_in: 2592000000, + written_on: Date.new(2003, 7, 16), + viewed_at: Time.utc(2003, 7, 16, 9, 28), + content: "Have a nice day", + author_email_address: "david@loudthinking.com", + parent_id: nil }.stringify_keys assert_equal expected_topic_hash, Hash.from_xml(topics_xml)["topics"].first @@ -1295,18 +1302,18 @@ class HashToXmlTest < ActiveSupport::TestCase EOT expected_topic_hash = { - :title => "The First Topic", - :author_name => "David", - :id => 1, - :approved => true, - :replies_count => 0, - :replies_close_in => 2592000000, - :written_on => Date.new(2003, 7, 16), - :viewed_at => Time.utc(2003, 7, 16, 9, 28), - :author_email_address => "david@loudthinking.com", - :parent_id => nil, - :ad_revenue => BigDecimal("1.50"), - :optimum_viewing_angle => 135.0, + title: "The First Topic", + author_name: "David", + id: 1, + approved: true, + replies_count: 0, + replies_close_in: 2592000000, + written_on: Date.new(2003, 7, 16), + viewed_at: Time.utc(2003, 7, 16, 9, 28), + author_email_address: "david@loudthinking.com", + parent_id: nil, + ad_revenue: BigDecimal("1.50"), + optimum_viewing_angle: 135.0, }.stringify_keys assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["topic"] @@ -1325,12 +1332,12 @@ class HashToXmlTest < ActiveSupport::TestCase EOT expected_topic_hash = { - :title => nil, - :id => nil, - :approved => nil, - :written_on => nil, - :viewed_at => nil, - :parent_id => nil + title: nil, + id: nil, + approved: nil, + written_on: nil, + viewed_at: nil, + parent_id: nil }.stringify_keys assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["topic"] @@ -1369,17 +1376,17 @@ class HashToXmlTest < ActiveSupport::TestCase EOT expected_topic_hash = { - :title => "The First Topic", - :author_name => "David", - :id => 1, - :approved => false, - :replies_count => 0, - :replies_close_in => 2592000000, - :written_on => Date.new(2003, 7, 16), - :viewed_at => Time.utc(2003, 7, 16, 9, 28), - :content => "Have a nice day", - :author_email_address => "david@loudthinking.com", - :parent_id => nil + title: "The First Topic", + author_name: "David", + id: 1, + approved: false, + replies_count: 0, + replies_close_in: 2592000000, + written_on: Date.new(2003, 7, 16), + viewed_at: Time.utc(2003, 7, 16, 9, 28), + content: "Have a nice day", + author_email_address: "david@loudthinking.com", + parent_id: nil }.stringify_keys assert_equal expected_topic_hash, Hash.from_xml(topics_xml)["topics"].first @@ -1395,14 +1402,14 @@ class HashToXmlTest < ActiveSupport::TestCase EOT expected_topic_hash = { - :id => "175756086", - :owner => "55569174@N00", - :secret => "0279bf37a1", - :server => "76", - :title => "Colored Pencil PhotoBooth Fun", - :ispublic => "1", - :isfriend => "0", - :isfamily => "0", + id: "175756086", + owner: "55569174@N00", + secret: "0279bf37a1", + server: "76", + title: "Colored Pencil PhotoBooth Fun", + ispublic: "1", + isfriend: "0", + isfamily: "0", }.stringify_keys assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["rsp"]["photos"]["photo"] @@ -1430,7 +1437,7 @@ class HashToXmlTest < ActiveSupport::TestCase <posts type="array"></posts> </blog> XML - expected_blog_hash = {"blog" => {"posts" => []}} + expected_blog_hash = { "blog" => { "posts" => [] } } assert_equal expected_blog_hash, Hash.from_xml(blog_xml) end @@ -1441,7 +1448,7 @@ class HashToXmlTest < ActiveSupport::TestCase </posts> </blog> XML - expected_blog_hash = {"blog" => {"posts" => []}} + expected_blog_hash = { "blog" => { "posts" => [] } } assert_equal expected_blog_hash, Hash.from_xml(blog_xml) end @@ -1453,7 +1460,7 @@ class HashToXmlTest < ActiveSupport::TestCase </posts> </blog> XML - expected_blog_hash = {"blog" => {"posts" => ["a post"]}} + expected_blog_hash = { "blog" => { "posts" => ["a post"] } } assert_equal expected_blog_hash, Hash.from_xml(blog_xml) end @@ -1466,7 +1473,7 @@ class HashToXmlTest < ActiveSupport::TestCase </posts> </blog> XML - expected_blog_hash = {"blog" => {"posts" => ["a post", "another post"]}} + expected_blog_hash = { "blog" => { "posts" => ["a post", "another post"] } } assert_equal expected_blog_hash, Hash.from_xml(blog_xml) end @@ -1478,12 +1485,12 @@ class HashToXmlTest < ActiveSupport::TestCase </blog> XML hash = Hash.from_xml(blog_xml) - assert hash.has_key?('blog') - assert hash['blog'].has_key?('logo') + assert hash.has_key?("blog") + assert hash["blog"].has_key?("logo") - file = hash['blog']['logo'] - assert_equal 'logo.png', file.original_filename - assert_equal 'image/png', file.content_type + file = hash["blog"]["logo"] + assert_equal "logo.png", file.original_filename + assert_equal "image/png", file.content_type end def test_file_from_xml_with_defaults @@ -1493,9 +1500,9 @@ class HashToXmlTest < ActiveSupport::TestCase </logo> </blog> XML - file = Hash.from_xml(blog_xml)['blog']['logo'] - assert_equal 'untitled', file.original_filename - assert_equal 'application/octet-stream', file.content_type + file = Hash.from_xml(blog_xml)["blog"]["logo"] + assert_equal "untitled", file.original_filename + assert_equal "application/octet-stream", file.content_type end def test_tag_with_attrs_and_whitespace @@ -1504,7 +1511,7 @@ class HashToXmlTest < ActiveSupport::TestCase </blog> XML hash = Hash.from_xml(xml) - assert_equal "bacon is the best", hash['blog']['name'] + assert_equal "bacon is the best", hash["blog"]["name"] end def test_empty_cdata_from_xml @@ -1527,13 +1534,13 @@ class HashToXmlTest < ActiveSupport::TestCase EOT expected_bacon_hash = { - :weight => 0.5, - :chunky => true, - :price => BigDecimal("12.50"), - :expires_at => Time.utc(2007,12,25,12,34,56), - :notes => "", - :illustration => "babe.png", - :caption => "That'll do, pig." + weight: 0.5, + chunky: true, + price: BigDecimal("12.50"), + expires_at: Time.utc(2007,12,25,12,34,56), + notes: "", + illustration: "babe.png", + caption: "That'll do, pig." }.stringify_keys assert_equal expected_bacon_hash, Hash.from_xml(bacon_xml)["bacon"] @@ -1549,8 +1556,8 @@ class HashToXmlTest < ActiveSupport::TestCase EOT expected_product_hash = { - :weight => 0.5, - :image => {'type' => 'ProductImage', 'filename' => 'image.gif' }, + weight: 0.5, + image: { "type" => "ProductImage", "filename" => "image.gif" }, }.stringify_keys assert_equal expected_product_hash, Hash.from_xml(product_xml)["product"] @@ -1573,17 +1580,17 @@ class HashToXmlTest < ActiveSupport::TestCase end def test_from_xml_array_one - expected = { 'numbers' => { 'type' => 'Array', 'value' => '1' }} + expected = { "numbers" => { "type" => "Array", "value" => "1" } } assert_equal expected, Hash.from_xml('<numbers type="Array"><value>1</value></numbers>') end def test_from_xml_array_many - expected = { 'numbers' => { 'type' => 'Array', 'value' => [ '1', '2' ] }} + expected = { "numbers" => { "type" => "Array", "value" => [ "1", "2" ] } } assert_equal expected, Hash.from_xml('<numbers type="Array"><value>1</value><value>2</value></numbers>') end def test_from_trusted_xml_allows_symbol_and_yaml_types - expected = { 'product' => { 'name' => :value }} + expected = { "product" => { "name" => :value } } assert_equal expected, Hash.from_trusted_xml('<product><name type="symbol">value</name></product>') assert_equal expected, Hash.from_trusted_xml('<product><name type="yaml">:value</name></product>') end @@ -1646,8 +1653,8 @@ class HashToXmlTest < ActiveSupport::TestCase # The XML builder seems to fail miserably when trying to tag something # with the same name as a Kernel method (throw, test, loop, select ...) def test_kernel_method_names_to_xml - hash = { :throw => { :ball => 'red' } } - expected = '<person><throw><ball>red</ball></throw></person>' + hash = { throw: { ball: "red" } } + expected = "<person><throw><ball>red</ball></throw></person>" assert_nothing_raised do assert_equal expected, hash.to_xml(@xml_options) @@ -1662,30 +1669,30 @@ class HashToXmlTest < ActiveSupport::TestCase def test_escaping_to_xml hash = { - :bare_string => 'First & Last Name', - :pre_escaped_string => 'First & Last Name' + bare_string: "First & Last Name", + pre_escaped_string: "First & Last Name" }.stringify_keys - expected_xml = '<person><bare-string>First & Last Name</bare-string><pre-escaped-string>First &amp; Last Name</pre-escaped-string></person>' + expected_xml = "<person><bare-string>First & Last Name</bare-string><pre-escaped-string>First &amp; Last Name</pre-escaped-string></person>" assert_equal expected_xml, hash.to_xml(@xml_options) end def test_unescaping_from_xml - xml_string = '<person><bare-string>First & Last Name</bare-string><pre-escaped-string>First &amp; Last Name</pre-escaped-string></person>' + xml_string = "<person><bare-string>First & Last Name</bare-string><pre-escaped-string>First &amp; Last Name</pre-escaped-string></person>" expected_hash = { - :bare_string => 'First & Last Name', - :pre_escaped_string => 'First & Last Name' + bare_string: "First & Last Name", + pre_escaped_string: "First & Last Name" }.stringify_keys - assert_equal expected_hash, Hash.from_xml(xml_string)['person'] + assert_equal expected_hash, Hash.from_xml(xml_string)["person"] end def test_roundtrip_to_xml_from_xml hash = { - :bare_string => 'First & Last Name', - :pre_escaped_string => 'First & Last Name' + bare_string: "First & Last Name", + pre_escaped_string: "First & Last Name" }.stringify_keys - assert_equal hash, Hash.from_xml(hash.to_xml(@xml_options))['person'] + assert_equal hash, Hash.from_xml(hash.to_xml(@xml_options))["person"] end def test_datetime_xml_type_with_utc_time @@ -1694,7 +1701,7 @@ class HashToXmlTest < ActiveSupport::TestCase <alert_at type="datetime">2008-02-10T15:30:45Z</alert_at> </alert> XML - alert_at = Hash.from_xml(alert_xml)['alert']['alert_at'] + alert_at = Hash.from_xml(alert_xml)["alert"]["alert_at"] assert alert_at.utc? assert_equal Time.utc(2008, 2, 10, 15, 30, 45), alert_at end @@ -1705,7 +1712,7 @@ class HashToXmlTest < ActiveSupport::TestCase <alert_at type="datetime">2008-02-10T10:30:45-05:00</alert_at> </alert> XML - alert_at = Hash.from_xml(alert_xml)['alert']['alert_at'] + alert_at = Hash.from_xml(alert_xml)["alert"]["alert_at"] assert alert_at.utc? assert_equal Time.utc(2008, 2, 10, 15, 30, 45), alert_at end @@ -1716,7 +1723,7 @@ class HashToXmlTest < ActiveSupport::TestCase <alert_at type="datetime">2050-02-10T15:30:45Z</alert_at> </alert> XML - alert_at = Hash.from_xml(alert_xml)['alert']['alert_at'] + alert_at = Hash.from_xml(alert_xml)["alert"]["alert_at"] assert alert_at.utc? assert_equal 2050, alert_at.year assert_equal 2, alert_at.month @@ -1727,20 +1734,20 @@ class HashToXmlTest < ActiveSupport::TestCase end def test_to_xml_dups_options - options = {:skip_instruct => true} + options = { skip_instruct: true } {}.to_xml(options) # :builder, etc, shouldn't be added to options - assert_equal({:skip_instruct => true}, options) + assert_equal({ skip_instruct: true }, options) end def test_expansion_count_is_limited expected = case ActiveSupport::XmlMini.backend.name - when 'ActiveSupport::XmlMini_REXML'; RuntimeError - when 'ActiveSupport::XmlMini_Nokogiri'; Nokogiri::XML::SyntaxError - when 'ActiveSupport::XmlMini_NokogiriSAX'; RuntimeError - when 'ActiveSupport::XmlMini_LibXML'; LibXML::XML::Error - when 'ActiveSupport::XmlMini_LibXMLSAX'; LibXML::XML::Error + when "ActiveSupport::XmlMini_REXML"; RuntimeError + when "ActiveSupport::XmlMini_Nokogiri"; Nokogiri::XML::SyntaxError + when "ActiveSupport::XmlMini_NokogiriSAX"; RuntimeError + when "ActiveSupport::XmlMini_LibXML"; LibXML::XML::Error + when "ActiveSupport::XmlMini_LibXMLSAX"; LibXML::XML::Error end assert_raise expected do diff --git a/activesupport/test/core_ext/integer_ext_test.rb b/activesupport/test/core_ext/integer_ext_test.rb index 41736fb672..137e8ce85f 100644 --- a/activesupport/test/core_ext/integer_ext_test.rb +++ b/activesupport/test/core_ext/integer_ext_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/integer' +require "abstract_unit" +require "active_support/core_ext/integer" class IntegerExtTest < ActiveSupport::TestCase PRIME = 22953686867719691230002707821868552601124472329079 @@ -19,12 +19,12 @@ class IntegerExtTest < ActiveSupport::TestCase def test_ordinalize # These tests are mostly just to ensure that the ordinalize method exists. # Its results are tested comprehensively in the inflector test cases. - assert_equal '1st', 1.ordinalize - assert_equal '8th', 8.ordinalize + assert_equal "1st", 1.ordinalize + assert_equal "8th", 8.ordinalize end def test_ordinal - assert_equal 'st', 1.ordinal - assert_equal 'th', 8.ordinal + assert_equal "st", 1.ordinal + assert_equal "th", 8.ordinal end end diff --git a/activesupport/test/core_ext/kernel/concern_test.rb b/activesupport/test/core_ext/kernel/concern_test.rb index 478a00d2d2..e7e4f99d7e 100644 --- a/activesupport/test/core_ext/kernel/concern_test.rb +++ b/activesupport/test/core_ext/kernel/concern_test.rb @@ -1,9 +1,9 @@ -require 'abstract_unit' -require 'active_support/core_ext/kernel/concern' +require "abstract_unit" +require "active_support/core_ext/kernel/concern" class KernelConcernTest < ActiveSupport::TestCase def test_may_be_defined_at_toplevel - mod = ::TOPLEVEL_BINDING.eval 'concern(:ToplevelConcern) { }' + mod = ::TOPLEVEL_BINDING.eval "concern(:ToplevelConcern) { }" assert_equal mod, ::ToplevelConcern assert_kind_of ActiveSupport::Concern, ::ToplevelConcern assert_not Object.ancestors.include?(::ToplevelConcern), mod.ancestors.inspect diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb index 503e6595cb..db0008b735 100644 --- a/activesupport/test/core_ext/kernel_test.rb +++ b/activesupport/test/core_ext/kernel_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/kernel' +require "abstract_unit" +require "active_support/core_ext/kernel" class KernelTest < ActiveSupport::TestCase def test_silence_warnings diff --git a/activesupport/test/core_ext/load_error_test.rb b/activesupport/test/core_ext/load_error_test.rb index b2a75a2bcc..b50a35b2cb 100644 --- a/activesupport/test/core_ext/load_error_test.rb +++ b/activesupport/test/core_ext/load_error_test.rb @@ -1,6 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/load_error' - +require "abstract_unit" +require "active_support/core_ext/load_error" class TestMissingSourceFile < ActiveSupport::TestCase def test_it_is_deprecated @@ -15,12 +14,12 @@ class TestLoadError < ActiveSupport::TestCase assert_raise(LoadError) { require 'no_this_file_don\'t_exist' } end def test_with_load - assert_raise(LoadError) { load 'nor_does_this_one' } + assert_raise(LoadError) { load "nor_does_this_one" } end def test_path - begin load 'nor/this/one.rb' + begin load "nor/this/one.rb" rescue LoadError => e - assert_equal 'nor/this/one.rb', e.path + assert_equal "nor/this/one.rb", e.path end end end diff --git a/activesupport/test/core_ext/marshal_test.rb b/activesupport/test/core_ext/marshal_test.rb index 825df439a5..a899f98705 100644 --- a/activesupport/test/core_ext/marshal_test.rb +++ b/activesupport/test/core_ext/marshal_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'active_support/core_ext/marshal' -require 'dependencies_test_helpers' +require "abstract_unit" +require "active_support/core_ext/marshal" +require "dependencies_test_helpers" class MarshalTest < ActiveSupport::TestCase include ActiveSupport::Testing::Isolation @@ -12,7 +12,7 @@ class MarshalTest < ActiveSupport::TestCase end test "that Marshal#load still works" do - sanity_data = ["test", [1, 2, 3], {a: [1, 2, 3]}, ActiveSupport::TestCase] + sanity_data = ["test", [1, 2, 3], { a: [1, 2, 3] }, ActiveSupport::TestCase] sanity_data.each do |obj| dumped = Marshal.dump(obj) assert_equal Marshal.method(:load).super_method.call(dumped), Marshal.load(dumped) @@ -29,7 +29,12 @@ class MarshalTest < ActiveSupport::TestCase ActiveSupport::Dependencies.clear with_autoloading_fixtures do - assert_kind_of EM, Marshal.load(dumped) + object = nil + assert_nothing_raised do + object = Marshal.load(dumped) + end + + assert_kind_of EM, object end end @@ -43,7 +48,12 @@ class MarshalTest < ActiveSupport::TestCase ActiveSupport::Dependencies.clear with_autoloading_fixtures do - assert_kind_of ClassFolder::ClassFolderSubclass, Marshal.load(dumped) + object = nil + assert_nothing_raised do + object = Marshal.load(dumped) + end + + assert_kind_of ClassFolder::ClassFolderSubclass, object end end @@ -64,6 +74,17 @@ class MarshalTest < ActiveSupport::TestCase end end + test "when one constant resolves to another" do + class Parent; C = Class.new; end + class Child < Parent; C = Class.new; end + + dump = Marshal.dump(Child::C.new) + + Child.send(:remove_const, :C) + + assert_raise(ArgumentError) { Marshal.load(dump) } + end + test "that a real missing class is causing an exception" do dumped = nil with_autoloading_fixtures do @@ -96,7 +117,7 @@ class MarshalTest < ActiveSupport::TestCase Marshal.load(dumped) end - assert_nothing_raised("EM failed to load while we expect only SomeClass to fail loading") do + assert_nothing_raised do EM.new end @@ -117,7 +138,12 @@ class MarshalTest < ActiveSupport::TestCase ActiveSupport::Dependencies.clear with_autoloading_fixtures do - assert_kind_of EM, Marshal.load(f) + object = nil + assert_nothing_raised do + object = Marshal.load(f) + end + + assert_kind_of EM, object end end end diff --git a/activesupport/test/core_ext/module/anonymous_test.rb b/activesupport/test/core_ext/module/anonymous_test.rb index cb556af772..f885444284 100644 --- a/activesupport/test/core_ext/module/anonymous_test.rb +++ b/activesupport/test/core_ext/module/anonymous_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/module/anonymous' +require "abstract_unit" +require "active_support/core_ext/module/anonymous" class AnonymousTest < ActiveSupport::TestCase test "an anonymous class or module are anonymous" do @@ -11,4 +11,4 @@ class AnonymousTest < ActiveSupport::TestCase assert !Kernel.anonymous? assert !Object.anonymous? end -end
\ No newline at end of file +end diff --git a/activesupport/test/core_ext/module/attr_internal_test.rb b/activesupport/test/core_ext/module/attr_internal_test.rb index 2aea14cf2b..8458e278ee 100644 --- a/activesupport/test/core_ext/module/attr_internal_test.rb +++ b/activesupport/test/core_ext/module/attr_internal_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/module/attr_internal' +require "abstract_unit" +require "active_support/core_ext/module/attr_internal" class AttrInternalTest < ActiveSupport::TestCase def setup @@ -10,44 +10,44 @@ class AttrInternalTest < ActiveSupport::TestCase def test_reader assert_nothing_raised { @target.attr_internal_reader :foo } - assert !@instance.instance_variable_defined?('@_foo') + assert !@instance.instance_variable_defined?("@_foo") assert_raise(NoMethodError) { @instance.foo = 1 } - @instance.instance_variable_set('@_foo', 1) + @instance.instance_variable_set("@_foo", 1) assert_nothing_raised { assert_equal 1, @instance.foo } end def test_writer assert_nothing_raised { @target.attr_internal_writer :foo } - assert !@instance.instance_variable_defined?('@_foo') + assert !@instance.instance_variable_defined?("@_foo") assert_nothing_raised { assert_equal 1, @instance.foo = 1 } - assert_equal 1, @instance.instance_variable_get('@_foo') + assert_equal 1, @instance.instance_variable_get("@_foo") assert_raise(NoMethodError) { @instance.foo } end def test_accessor assert_nothing_raised { @target.attr_internal :foo } - assert !@instance.instance_variable_defined?('@_foo') + assert !@instance.instance_variable_defined?("@_foo") assert_nothing_raised { assert_equal 1, @instance.foo = 1 } - assert_equal 1, @instance.instance_variable_get('@_foo') + assert_equal 1, @instance.instance_variable_get("@_foo") assert_nothing_raised { assert_equal 1, @instance.foo } end def test_naming_format - assert_equal '@_%s', Module.attr_internal_naming_format - assert_nothing_raised { Module.attr_internal_naming_format = '@abc%sdef' } + assert_equal "@_%s", Module.attr_internal_naming_format + assert_nothing_raised { Module.attr_internal_naming_format = "@abc%sdef" } @target.attr_internal :foo - assert !@instance.instance_variable_defined?('@_foo') - assert !@instance.instance_variable_defined?('@abcfoodef') + assert !@instance.instance_variable_defined?("@_foo") + assert !@instance.instance_variable_defined?("@abcfoodef") assert_nothing_raised { @instance.foo = 1 } - assert !@instance.instance_variable_defined?('@_foo') - assert @instance.instance_variable_defined?('@abcfoodef') + assert !@instance.instance_variable_defined?("@_foo") + assert @instance.instance_variable_defined?("@abcfoodef") ensure - Module.attr_internal_naming_format = '@_%s' + Module.attr_internal_naming_format = "@_%s" 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 65fadc5c20..b816fa50e3 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 @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/module/attribute_accessors_per_thread' +require "abstract_unit" +require "active_support/core_ext/module/attribute_accessors_per_thread" class ModuleAttributeAccessorPerThreadTest < ActiveSupport::TestCase def setup @@ -8,6 +8,12 @@ class ModuleAttributeAccessorPerThreadTest < ActiveSupport::TestCase thread_mattr_accessor :bar, instance_writer: false thread_mattr_reader :shaq, instance_reader: false thread_mattr_accessor :camp, instance_accessor: false + + def self.name; "MyClass" end + end + + @subclass = Class.new(@class) do + def self.name; "SubMyClass" end end @object = @class.new @@ -57,23 +63,23 @@ class ModuleAttributeAccessorPerThreadTest < ActiveSupport::TestCase def test_values_should_not_bleed_between_threads threads = [] threads << Thread.new do - @class.foo = 'things' + @class.foo = "things" sleep 1 - assert_equal 'things', @class.foo + assert_equal "things", @class.foo end threads << Thread.new do - @class.foo = 'other things' + @class.foo = "other things" sleep 1 - assert_equal 'other things', @class.foo + assert_equal "other things", @class.foo end - + threads << Thread.new do - @class.foo = 'really other things' + @class.foo = "really other things" sleep 1 - assert_equal 'really other things', @class.foo + assert_equal "really other things", @class.foo end - + threads.each { |t| t.join } end @@ -106,4 +112,20 @@ class ModuleAttributeAccessorPerThreadTest < ActiveSupport::TestCase end assert_equal "invalid attribute name: 2valid_part", exception.message end + + def test_should_return_same_value_by_class_or_instance_accessor + @class.foo = "fries" + + assert_equal @class.foo, @object.foo + end + + def test_should_not_affect_superclass_if_subclass_set_value + @class.foo = "super" + assert_equal @class.foo, "super" + assert_nil @subclass.foo + + @subclass.foo = "sub" + assert_equal @class.foo, "super" + assert_equal @subclass.foo, "sub" + end end diff --git a/activesupport/test/core_ext/module/attribute_accessor_test.rb b/activesupport/test/core_ext/module/attribute_accessor_test.rb index 0b0f3a2808..464a000d59 100644 --- a/activesupport/test/core_ext/module/attribute_accessor_test.rb +++ b/activesupport/test/core_ext/module/attribute_accessor_test.rb @@ -1,17 +1,17 @@ -require 'abstract_unit' -require 'active_support/core_ext/module/attribute_accessors' +require "abstract_unit" +require "active_support/core_ext/module/attribute_accessors" class ModuleAttributeAccessorTest < ActiveSupport::TestCase def setup m = @module = Module.new do mattr_accessor :foo - mattr_accessor :bar, :instance_writer => false - mattr_reader :shaq, :instance_reader => false - mattr_accessor :camp, :instance_accessor => false + mattr_accessor :bar, instance_writer: false + mattr_reader :shaq, instance_reader: false + mattr_accessor :camp, instance_accessor: false - cattr_accessor(:defa) { 'default_accessor_value' } - cattr_reader(:defr) { 'default_reader_value' } - cattr_writer(:defw) { 'default_writer_value' } + cattr_accessor(:defa) { "default_accessor_value" } + cattr_reader(:defr) { "default_reader_value" } + cattr_writer(:defw) { "default_writer_value" } cattr_accessor(:quux) { :quux } end @class = Class.new @@ -86,14 +86,14 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase end def test_should_use_default_value_if_block_passed - assert_equal 'default_accessor_value', @module.defa - assert_equal 'default_reader_value', @module.defr - assert_equal 'default_writer_value', @module.class_variable_get('@@defw') + assert_equal "default_accessor_value", @module.defa + assert_equal "default_reader_value", @module.defr + assert_equal "default_writer_value", @module.class_variable_get("@@defw") end def test_should_not_invoke_default_value_block_multiple_times count = 0 - @module.cattr_accessor(:defcount){ count += 1 } + @module.cattr_accessor(:defcount) { count += 1 } assert_equal 1, count 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 29c3053b47..d8c2dfd6b8 100644 --- a/activesupport/test/core_ext/module/attribute_aliasing_test.rb +++ b/activesupport/test/core_ext/module/attribute_aliasing_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/module/aliasing' +require "abstract_unit" +require "active_support/core_ext/module/aliasing" module AttributeAliasing class Content diff --git a/activesupport/test/core_ext/module/concerning_test.rb b/activesupport/test/core_ext/module/concerning_test.rb index 07d860b71c..098036828a 100644 --- a/activesupport/test/core_ext/module/concerning_test.rb +++ b/activesupport/test/core_ext/module/concerning_test.rb @@ -1,10 +1,10 @@ -require 'abstract_unit' -require 'active_support/core_ext/module/concerning' +require "abstract_unit" +require "active_support/core_ext/module/concerning" class ModuleConcerningTest < ActiveSupport::TestCase def test_concerning_declares_a_concern_and_includes_it_immediately - klass = Class.new { concerning(:Foo) { } } - assert klass.ancestors.include?(klass::Foo), klass.ancestors.inspect + klass = Class.new { concerning(:Foo) {} } + assert_includes klass.ancestors, klass::Foo, klass.ancestors.inspect end end @@ -21,13 +21,13 @@ class ModuleConcernTest < ActiveSupport::TestCase assert klass.const_defined?(:Baz, false) assert !ModuleConcernTest.const_defined?(:Baz) assert_kind_of ActiveSupport::Concern, klass::Baz - assert !klass.ancestors.include?(klass::Baz), klass.ancestors.inspect + assert_not_includes klass.ancestors, klass::Baz, klass.ancestors.inspect # Public method visibility by default - assert klass::Baz.public_instance_methods.map(&:to_s).include?('should_be_public') + assert_includes klass::Baz.public_instance_methods.map(&:to_s), "should_be_public" # Calls included hook - assert_equal 1, Class.new { include klass::Baz }.instance_variable_get('@foo') + assert_equal 1, Class.new { include klass::Baz }.instance_variable_get("@foo") end class Foo diff --git a/activesupport/test/core_ext/module/qualified_const_test.rb b/activesupport/test/core_ext/module/qualified_const_test.rb index a3146cabe1..418bc80ab9 100644 --- a/activesupport/test/core_ext/module/qualified_const_test.rb +++ b/activesupport/test/core_ext/module/qualified_const_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/module/qualified_const' +require "abstract_unit" +require "active_support/core_ext/module/qualified_const" module QualifiedConstTestMod X = false @@ -68,7 +68,7 @@ class QualifiedConstTest < ActiveSupport::TestCase assert_equal 1, QualifiedConstTestMod.qualified_const_get("N::X") assert_equal 2, QualifiedConstTestMod.qualified_const_get("M::C::X") - assert_raise(NameError) { QualifiedConstTestMod.qualified_const_get("M::C::Y")} + assert_raise(NameError) { QualifiedConstTestMod.qualified_const_get("M::C::Y") } end end @@ -89,8 +89,8 @@ class QualifiedConstTest < ActiveSupport::TestCase end ensure silence_warnings do - QualifiedConstTestMod.qualified_const_set('QualifiedConstTestMod::X', false) - QualifiedConstTestMod::M.qualified_const_set('X', 1) + QualifiedConstTestMod.qualified_const_set("QualifiedConstTestMod::X", false) + QualifiedConstTestMod::M.qualified_const_set("X", 1) end end end @@ -98,21 +98,21 @@ class QualifiedConstTest < ActiveSupport::TestCase test "reject absolute paths" do assert_deprecated do - assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_defined?("::X")} - assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_defined?("::X::Y")} + assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_defined?("::X") } + assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_defined?("::X::Y") } - assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_get("::X")} - assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_get("::X::Y")} + assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_get("::X") } + assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_get("::X::Y") } - assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_set("::X", nil)} - assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_set("::X::Y", nil)} + assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_set("::X", nil) } + assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_set("::X::Y", nil) } end end private - def assert_raise_with_message(expected_exception, expected_message, &block) - exception = assert_raise(expected_exception, &block) - assert_equal expected_message, exception.message - end + def assert_raise_with_message(expected_exception, expected_message, &block) + exception = assert_raise(expected_exception, &block) + assert_equal expected_message, exception.message + end end diff --git a/activesupport/test/core_ext/module/reachable_test.rb b/activesupport/test/core_ext/module/reachable_test.rb index 80eb31a5c4..487c7dee16 100644 --- a/activesupport/test/core_ext/module/reachable_test.rb +++ b/activesupport/test/core_ext/module/reachable_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/module/reachable' +require "abstract_unit" +require "active_support/core_ext/module/reachable" class AnonymousTest < ActiveSupport::TestCase test "an anonymous class or module is not reachable" do @@ -38,4 +38,4 @@ class AnonymousTest < ActiveSupport::TestCase assert !c.reachable? assert !m.reachable? end -end
\ No newline at end of file +end diff --git a/activesupport/test/core_ext/module/remove_method_test.rb b/activesupport/test/core_ext/module/remove_method_test.rb index 0d684dc70e..0c627f1e74 100644 --- a/activesupport/test/core_ext/module/remove_method_test.rb +++ b/activesupport/test/core_ext/module/remove_method_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/module/remove_method' +require "abstract_unit" +require "active_support/core_ext/module/remove_method" module RemoveMethodTests class A @@ -26,26 +26,25 @@ module RemoveMethodTests end class RemoveMethodTest < ActiveSupport::TestCase - def test_remove_method_from_an_object - RemoveMethodTests::A.class_eval{ - self.remove_possible_method(:do_something) + RemoveMethodTests::A.class_eval { + remove_possible_method(:do_something) } assert !RemoveMethodTests::A.new.respond_to?(:do_something) end def test_remove_singleton_method_from_an_object - RemoveMethodTests::A.class_eval{ - self.remove_possible_singleton_method(:do_something_else) + RemoveMethodTests::A.class_eval { + remove_possible_singleton_method(:do_something_else) } assert !RemoveMethodTests::A.respond_to?(:do_something_else) end def test_redefine_method_in_an_object - RemoveMethodTests::A.class_eval{ - self.redefine_method(:do_something) { return 100 } - self.redefine_method(:do_something_protected) { return 100 } - self.redefine_method(:do_something_private) { return 100 } + RemoveMethodTests::A.class_eval { + redefine_method(:do_something) { return 100 } + redefine_method(:do_something_protected) { return 100 } + redefine_method(:do_something_private) { return 100 } } assert_equal 100, RemoveMethodTests::A.new.do_something assert_equal 100, RemoveMethodTests::A.new.send(:do_something_protected) @@ -55,5 +54,4 @@ class RemoveMethodTest < ActiveSupport::TestCase assert RemoveMethodTests::A.protected_method_defined? :do_something_protected assert RemoveMethodTests::A.private_method_defined? :do_something_private end - end diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb index 0ed66f8c37..36073b28b7 100644 --- a/activesupport/test/core_ext/module_test.rb +++ b/activesupport/test/core_ext/module_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/module' +require "abstract_unit" +require "active_support/core_ext/module" module One Constant1 = "Hello World" @@ -25,50 +25,56 @@ Somewhere = Struct.new(:street, :city) do end class Someone < Struct.new(:name, :place) - delegate :street, :city, :to_f, :to => :place - delegate :name=, :to => :place, :prefix => true - delegate :upcase, :to => "place.city" - delegate :table_name, :to => :class - delegate :table_name, :to => :class, :prefix => true + delegate :street, :city, :to_f, to: :place + delegate :name=, to: :place, prefix: true + delegate :upcase, to: "place.city" + delegate :table_name, to: :class + delegate :table_name, to: :class, prefix: true def self.table_name - 'some_table' + "some_table" end FAILED_DELEGATE_LINE = __LINE__ + 1 - delegate :foo, :to => :place + delegate :foo, to: :place FAILED_DELEGATE_LINE_2 = __LINE__ + 1 - delegate :bar, :to => :place, :allow_nil => true + delegate :bar, to: :place, allow_nil: true + + private + + def private_name + "Private" + end end Invoice = Struct.new(:client) do - delegate :street, :city, :name, :to => :client, :prefix => true - delegate :street, :city, :name, :to => :client, :prefix => :customer + delegate :street, :city, :name, to: :client, prefix: true + delegate :street, :city, :name, to: :client, prefix: :customer end Project = Struct.new(:description, :person) do - delegate :name, :to => :person, :allow_nil => true - delegate :to_f, :to => :description, :allow_nil => true + delegate :name, to: :person, allow_nil: true + delegate :to_f, to: :description, allow_nil: true end Developer = Struct.new(:client) do - delegate :name, :to => :client, :prefix => nil + delegate :name, to: :client, prefix: nil end Event = Struct.new(:case) do - delegate :foo, :to => :case + delegate :foo, to: :case end Tester = Struct.new(:client) do - delegate :name, :to => :client, :prefix => false + delegate :name, to: :client, prefix: false def foo; 1; end end Product = Struct.new(:name) do - delegate :name, :to => :manufacturer, :prefix => true - delegate :name, :to => :type, :prefix => true + delegate :name, to: :manufacturer, prefix: true + delegate :name, to: :type, prefix: true def manufacturer @manufacturer ||= begin @@ -83,6 +89,20 @@ Product = Struct.new(:name) do end end +DecoratedTester = Struct.new(:client) do + delegate_missing_to :client +end + +class DecoratedReserved + delegate_missing_to :case + + attr_reader :case + + def initialize(kase) + @case = kase + end +end + class Block def hello? true @@ -94,15 +114,15 @@ HasBlock = Struct.new(:block) do end class ParameterSet - delegate :[], :[]=, :to => :@params + delegate :[], :[]=, to: :@params def initialize - @params = {:foo => "bar"} + @params = { foo: "bar" } end end class Name - delegate :upcase, :to => :@full_name + delegate :upcase, to: :@full_name def initialize(first, last) @full_name = "#{first} #{last}" @@ -112,8 +132,8 @@ end class SideEffect attr_reader :ints - delegate :to_i, :to => :shift, :allow_nil => true - delegate :to_s, :to => :shift + delegate :to_i, to: :shift, allow_nil: true + delegate :to_s, to: :shift def initialize @ints = [1, 2, 3] @@ -160,8 +180,8 @@ class ModuleTest < ActiveSupport::TestCase end def test_delegation_to_class_method - assert_equal 'some_table', @david.table_name - assert_equal 'some_table', @david.class_table_name + assert_equal "some_table", @david.table_name + assert_equal "some_table", @david.class_table_name end def test_missing_delegation_target @@ -169,7 +189,22 @@ class ModuleTest < ActiveSupport::TestCase Name.send :delegate, :nowhere end assert_raise(ArgumentError) do - Name.send :delegate, :noplace, :tos => :hollywood + Name.send :delegate, :noplace, tos: :hollywood + end + end + + def test_delegation_target_when_prefix_is_true + assert_nothing_raised do + Name.send :delegate, :go, to: :you, prefix: true + end + assert_nothing_raised do + Name.send :delegate, :go, to: :_you, prefix: true + end + assert_raise(ArgumentError) do + Name.send :delegate, :go, to: :You, prefix: true + end + assert_raise(ArgumentError) do + Name.send :delegate, :go, to: :@you, prefix: true end end @@ -198,7 +233,7 @@ class ModuleTest < ActiveSupport::TestCase def initialize(client) @client = client end - delegate :name, :address, :to => :@client, :prefix => true + delegate :name, :address, to: :@client, prefix: true end end end @@ -226,7 +261,7 @@ class ModuleTest < ActiveSupport::TestCase def test_delegation_with_allow_nil_and_nil_value_and_prefix Project.class_eval do - delegate :name, :to => :person, :allow_nil => true, :prefix => true + delegate :name, to: :person, allow_nil: true, prefix: true end rails = Project.new("Rails") assert_nil rails.person_name @@ -255,7 +290,7 @@ class ModuleTest < ActiveSupport::TestCase assert_nothing_raised do Class.new(parent) do class << self - delegate :parent_method, :to => :superclass + delegate :parent_method, to: :superclass end end end @@ -277,7 +312,7 @@ class ModuleTest < ActiveSupport::TestCase rescue NoMethodError => e file_and_line = "#{__FILE__}:#{Someone::FAILED_DELEGATE_LINE}" # We can't simply check the first line of the backtrace, because JRuby reports the call to __send__ in the backtrace. - assert e.backtrace.any?{|a| a.include?(file_and_line)}, + assert e.backtrace.any? { |a| a.include?(file_and_line) }, "[#{e.backtrace.inspect}] did not include [#{file_and_line}]" end @@ -287,7 +322,7 @@ class ModuleTest < ActiveSupport::TestCase rescue NoMethodError => e file_and_line = "#{__FILE__}:#{Someone::FAILED_DELEGATE_LINE_2}" # We can't simply check the first line of the backtrace, because JRuby reports the call to __send__ in the backtrace. - assert e.backtrace.any?{|a| a.include?(file_and_line)}, + assert e.backtrace.any? { |a| a.include?(file_and_line) }, "[#{e.backtrace.inspect}] did not include [#{file_and_line}]" end @@ -297,12 +332,12 @@ class ModuleTest < ActiveSupport::TestCase assert_equal 1, se.to_i assert_equal [2, 3], se.ints - assert_equal '2', se.to_s + assert_equal "2", se.to_s assert_equal [3], se.ints end def test_delegation_doesnt_mask_nested_no_method_error_on_nil_receiver - product = Product.new('Widget') + product = Product.new("Widget") # Nested NoMethodError is a different name from the delegation assert_raise(NoMethodError) { product.manufacturer_name } @@ -316,6 +351,30 @@ class ModuleTest < ActiveSupport::TestCase assert has_block.hello? end + def test_delegate_to_missing_with_method + assert_equal "David", DecoratedTester.new(@david).name + end + + def test_delegate_to_missing_with_reserved_methods + assert_equal "David", DecoratedReserved.new(@david).name + end + + def test_delegate_to_missing_does_not_delegate_to_private_methods + e = assert_raises(NoMethodError) do + DecoratedReserved.new(@david).private_name + end + + assert_match(/undefined method `private_name' for/, e.message) + end + + def test_delegate_to_missing_does_not_delegate_to_fake_methods + e = assert_raises(NoMethodError) do + DecoratedReserved.new(@david).my_fake_method + end + + 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 @@ -328,7 +387,13 @@ class ModuleTest < ActiveSupport::TestCase end def test_local_constants - assert_equal %w(Constant1 Constant3), Ab.local_constants.sort.map(&:to_s) + ActiveSupport::Deprecation.silence do + assert_equal %w(Constant1 Constant3), Ab.local_constants.sort.map(&:to_s) + end + end + + def test_local_constants_is_deprecated + assert_deprecated { Ab.local_constants.sort.map(&:to_s) } end end @@ -343,11 +408,11 @@ end module BarMethods def bar_with_baz - bar_without_baz << '_with_baz' + bar_without_baz << "_with_baz" end def quux_with_baz! - quux_without_baz! << '_with_baz' + quux_without_baz! << "_with_baz" end def quux_with_baz? @@ -355,17 +420,17 @@ module BarMethods end def quux_with_baz=(v) - send(:quux_without_baz=, v) << '_with_baz' + send(:quux_without_baz=, v) << "_with_baz" end def duck_with_orange - duck_without_orange << '_with_orange' + duck_without_orange << "_with_orange" end end class MethodAliasingTest < ActiveSupport::TestCase def setup - Object.const_set :FooClassWithBarMethod, Class.new { def bar() 'bar' end } + Object.const_set :FooClassWithBarMethod, Class.new { def bar() "bar" end } @instance = FooClassWithBarMethod.new end @@ -396,7 +461,7 @@ class MethodAliasingTest < ActiveSupport::TestCase assert !@instance.respond_to?(method) end - assert_equal 'bar', @instance.bar + assert_equal "bar", @instance.bar FooClassWithBarMethod.class_eval { include BarMethodAliaser } @@ -404,15 +469,15 @@ class MethodAliasingTest < ActiveSupport::TestCase assert_respond_to @instance, method end - assert_equal 'bar_with_baz', @instance.bar - assert_equal 'bar', @instance.bar_without_baz + assert_equal "bar_with_baz", @instance.bar + assert_equal "bar", @instance.bar_without_baz end end def test_alias_method_chain_with_punctuation_method assert_deprecated(/alias_method_chain/) do FooClassWithBarMethod.class_eval do - def quux!; 'quux' end + def quux!; "quux" end end assert !@instance.respond_to?(:quux_with_baz!) @@ -422,17 +487,17 @@ class MethodAliasingTest < ActiveSupport::TestCase end assert_respond_to @instance, :quux_with_baz! - assert_equal 'quux_with_baz', @instance.quux! - assert_equal 'quux', @instance.quux_without_baz! + assert_equal "quux_with_baz", @instance.quux! + assert_equal "quux", @instance.quux_without_baz! end end def test_alias_method_chain_with_same_names_between_predicates_and_bang_methods assert_deprecated(/alias_method_chain/) do FooClassWithBarMethod.class_eval do - def quux!; 'quux!' end + def quux!; "quux!" end def quux?; true end - def quux=(v); 'quux=' end + def quux=(v); "quux=" end end assert !@instance.respond_to?(:quux_with_baz!) @@ -444,32 +509,31 @@ class MethodAliasingTest < ActiveSupport::TestCase assert_respond_to @instance, :quux_with_baz? assert_respond_to @instance, :quux_with_baz= - FooClassWithBarMethod.alias_method_chain :quux!, :baz - assert_equal 'quux!_with_baz', @instance.quux! - assert_equal 'quux!', @instance.quux_without_baz! + assert_equal "quux!_with_baz", @instance.quux! + assert_equal "quux!", @instance.quux_without_baz! FooClassWithBarMethod.alias_method_chain :quux?, :baz assert_equal false, @instance.quux? assert_equal true, @instance.quux_without_baz? FooClassWithBarMethod.alias_method_chain :quux=, :baz - assert_equal 'quux=_with_baz', @instance.send(:quux=, 1234) - assert_equal 'quux=', @instance.send(:quux_without_baz=, 1234) + assert_equal "quux=_with_baz", @instance.send(:quux=, 1234) + assert_equal "quux=", @instance.send(:quux_without_baz=, 1234) end end def test_alias_method_chain_with_feature_punctuation assert_deprecated(/alias_method_chain/) do FooClassWithBarMethod.class_eval do - def quux; 'quux' end - def quux?; 'quux?' end + def quux; "quux" end + def quux?; "quux?" end include BarMethodAliaser alias_method_chain :quux, :baz! end assert_nothing_raised do - assert_equal 'quux_with_baz', @instance.quux_with_baz! + assert_equal "quux_with_baz", @instance.quux_with_baz! end assert_raise(NameError) do @@ -492,15 +556,15 @@ class MethodAliasingTest < ActiveSupport::TestCase end assert_not_nil args - assert_equal 'quux', args[0] - assert_equal '?', args[1] + assert_equal "quux", args[0] + assert_equal "?", args[1] end end def test_alias_method_chain_preserves_private_method_status assert_deprecated(/alias_method_chain/) do FooClassWithBarMethod.class_eval do - def duck; 'duck' end + def duck; "duck" end include BarMethodAliaser private :duck alias_method_chain :duck, :orange @@ -510,7 +574,7 @@ class MethodAliasingTest < ActiveSupport::TestCase @instance.duck end - assert_equal 'duck_with_orange', @instance.instance_eval { duck } + assert_equal "duck_with_orange", @instance.instance_eval { duck } assert FooClassWithBarMethod.private_method_defined?(:duck) end end @@ -518,7 +582,7 @@ class MethodAliasingTest < ActiveSupport::TestCase def test_alias_method_chain_preserves_protected_method_status assert_deprecated(/alias_method_chain/) do FooClassWithBarMethod.class_eval do - def duck; 'duck' end + def duck; "duck" end include BarMethodAliaser protected :duck alias_method_chain :duck, :orange @@ -528,7 +592,7 @@ class MethodAliasingTest < ActiveSupport::TestCase @instance.duck end - assert_equal 'duck_with_orange', @instance.instance_eval { duck } + assert_equal "duck_with_orange", @instance.instance_eval { duck } assert FooClassWithBarMethod.protected_method_defined?(:duck) end end @@ -536,13 +600,13 @@ class MethodAliasingTest < ActiveSupport::TestCase def test_alias_method_chain_preserves_public_method_status assert_deprecated(/alias_method_chain/) do FooClassWithBarMethod.class_eval do - def duck; 'duck' end + def duck; "duck" end include BarMethodAliaser public :duck alias_method_chain :duck, :orange end - assert_equal 'duck_with_orange', @instance.duck + assert_equal "duck_with_orange", @instance.duck assert FooClassWithBarMethod.public_method_defined?(:duck) end end diff --git a/activesupport/test/core_ext/name_error_test.rb b/activesupport/test/core_ext/name_error_test.rb index 7525f80cf0..fdb9493d3c 100644 --- a/activesupport/test/core_ext/name_error_test.rb +++ b/activesupport/test/core_ext/name_error_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/name_error' +require "abstract_unit" +require "active_support/core_ext/name_error" class NameErrorTest < ActiveSupport::TestCase def test_name_error_should_set_missing_name diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb index 5654aeb4f8..5e824747ed 100644 --- a/activesupport/test/core_ext/numeric_ext_test.rb +++ b/activesupport/test/core_ext/numeric_ext_test.rb @@ -1,7 +1,7 @@ -require 'abstract_unit' -require 'active_support/time' -require 'active_support/core_ext/numeric' -require 'active_support/core_ext/integer' +require "abstract_unit" +require "active_support/time" +require "active_support/core_ext/numeric" +require "active_support/core_ext/integer" class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase def setup @@ -23,41 +23,41 @@ class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase end def test_irregular_durations - assert_equal @now.advance(:days => 3000), 3000.days.since(@now) - assert_equal @now.advance(:months => 1), 1.month.since(@now) - assert_equal @now.advance(:months => -1), 1.month.until(@now) - assert_equal @now.advance(:years => 20), 20.years.since(@now) - assert_equal @dtnow.advance(:days => 3000), 3000.days.since(@dtnow) - assert_equal @dtnow.advance(:months => 1), 1.month.since(@dtnow) - assert_equal @dtnow.advance(:months => -1), 1.month.until(@dtnow) - assert_equal @dtnow.advance(:years => 20), 20.years.since(@dtnow) + assert_equal @now.advance(days: 3000), 3000.days.since(@now) + assert_equal @now.advance(months: 1), 1.month.since(@now) + assert_equal @now.advance(months: -1), 1.month.until(@now) + assert_equal @now.advance(years: 20), 20.years.since(@now) + assert_equal @dtnow.advance(days: 3000), 3000.days.since(@dtnow) + assert_equal @dtnow.advance(months: 1), 1.month.since(@dtnow) + assert_equal @dtnow.advance(months: -1), 1.month.until(@dtnow) + assert_equal @dtnow.advance(years: 20), 20.years.since(@dtnow) end def test_duration_addition - assert_equal @now.advance(:days => 1).advance(:months => 1), (1.day + 1.month).since(@now) - assert_equal @now.advance(:days => 7), (1.week + 5.seconds - 5.seconds).since(@now) - assert_equal @now.advance(:years => 2), (4.years - 2.years).since(@now) - assert_equal @dtnow.advance(:days => 1).advance(:months => 1), (1.day + 1.month).since(@dtnow) - assert_equal @dtnow.advance(:days => 7), (1.week + 5.seconds - 5.seconds).since(@dtnow) - assert_equal @dtnow.advance(:years => 2), (4.years - 2.years).since(@dtnow) + assert_equal @now.advance(days: 1).advance(months: 1), (1.day + 1.month).since(@now) + assert_equal @now.advance(days: 7), (1.week + 5.seconds - 5.seconds).since(@now) + assert_equal @now.advance(years: 2), (4.years - 2.years).since(@now) + assert_equal @dtnow.advance(days: 1).advance(months: 1), (1.day + 1.month).since(@dtnow) + assert_equal @dtnow.advance(days: 7), (1.week + 5.seconds - 5.seconds).since(@dtnow) + assert_equal @dtnow.advance(years: 2), (4.years - 2.years).since(@dtnow) end def test_time_plus_duration assert_equal @now + 8, @now + 8.seconds assert_equal @now + 22.9, @now + 22.9.seconds - assert_equal @now.advance(:days => 15), @now + 15.days - assert_equal @now.advance(:months => 1), @now + 1.month + assert_equal @now.advance(days: 15), @now + 15.days + assert_equal @now.advance(months: 1), @now + 1.month assert_equal @dtnow.since(8), @dtnow + 8.seconds assert_equal @dtnow.since(22.9), @dtnow + 22.9.seconds - assert_equal @dtnow.advance(:days => 15), @dtnow + 15.days - assert_equal @dtnow.advance(:months => 1), @dtnow + 1.month + assert_equal @dtnow.advance(days: 15), @dtnow + 15.days + assert_equal @dtnow.advance(months: 1), @dtnow + 1.month end def test_chaining_duration_operations - assert_equal @now.advance(:days => 2).advance(:months => -3), @now + 2.days - 3.months - assert_equal @now.advance(:days => 1).advance(:months => 2), @now + 1.day + 2.months - assert_equal @dtnow.advance(:days => 2).advance(:months => -3), @dtnow + 2.days - 3.months - assert_equal @dtnow.advance(:days => 1).advance(:months => 2), @dtnow + 1.day + 2.months + assert_equal @now.advance(days: 2).advance(months: -3), @now + 2.days - 3.months + assert_equal @now.advance(days: 1).advance(months: 2), @now + 1.day + 2.months + assert_equal @dtnow.advance(days: 2).advance(months: -3), @dtnow + 2.days - 3.months + assert_equal @dtnow.advance(days: 1).advance(months: 2), @dtnow + 1.day + 2.months end def test_duration_after_conversion_is_no_longer_accurate @@ -87,8 +87,8 @@ class NumericExtDateTest < ActiveSupport::TestCase end def test_chaining_duration_operations - assert_equal @today.advance(:days => 2).advance(:months => -3), @today + 2.days - 3.months - assert_equal @today.advance(:days => 1).advance(:months => 2), @today + 1.day + 2.months + assert_equal @today.advance(days: 2).advance(months: -3), @today + 2.days - 3.months + assert_equal @today.advance(days: 1).advance(months: 2), @today + 1.day + 2.months end def test_add_one_year_to_leap_day @@ -154,53 +154,52 @@ class NumericExtFormattingTest < ActiveSupport::TestCase def test_to_s__phone assert_equal("555-1234", 5551234.to_s(:phone)) assert_equal("800-555-1212", 8005551212.to_s(:phone)) - assert_equal("(800) 555-1212", 8005551212.to_s(:phone, :area_code => true)) - assert_equal("800 555 1212", 8005551212.to_s(:phone, :delimiter => " ")) - assert_equal("(800) 555-1212 x 123", 8005551212.to_s(:phone, :area_code => true, :extension => 123)) - assert_equal("800-555-1212", 8005551212.to_s(:phone, :extension => " ")) - assert_equal("555.1212", 5551212.to_s(:phone, :delimiter => '.')) - assert_equal("+1-800-555-1212", 8005551212.to_s(:phone, :country_code => 1)) - assert_equal("+18005551212", 8005551212.to_s(:phone, :country_code => 1, :delimiter => '')) + assert_equal("(800) 555-1212", 8005551212.to_s(:phone, area_code: true)) + assert_equal("800 555 1212", 8005551212.to_s(:phone, delimiter: " ")) + assert_equal("(800) 555-1212 x 123", 8005551212.to_s(:phone, area_code: true, extension: 123)) + assert_equal("800-555-1212", 8005551212.to_s(:phone, extension: " ")) + assert_equal("555.1212", 5551212.to_s(:phone, delimiter: ".")) + assert_equal("+1-800-555-1212", 8005551212.to_s(:phone, country_code: 1)) + assert_equal("+18005551212", 8005551212.to_s(:phone, country_code: 1, delimiter: "")) assert_equal("22-555-1212", 225551212.to_s(:phone)) - assert_equal("+45-22-555-1212", 225551212.to_s(:phone, :country_code => 45)) + assert_equal("+45-22-555-1212", 225551212.to_s(:phone, country_code: 45)) end def test_to_s__currency assert_equal("$1,234,567,890.50", 1234567890.50.to_s(:currency)) assert_equal("$1,234,567,890.51", 1234567890.506.to_s(:currency)) assert_equal("-$1,234,567,890.50", -1234567890.50.to_s(:currency)) - assert_equal("-$ 1,234,567,890.50", -1234567890.50.to_s(:currency, :format => "%u %n")) - assert_equal("($1,234,567,890.50)", -1234567890.50.to_s(:currency, :negative_format => "(%u%n)")) - assert_equal("$1,234,567,892", 1234567891.50.to_s(:currency, :precision => 0)) - assert_equal("$1,234,567,890.5", 1234567890.50.to_s(:currency, :precision => 1)) - assert_equal("£1234567890,50", 1234567890.50.to_s(:currency, :unit => "£", :separator => ",", :delimiter => "")) + assert_equal("-$ 1,234,567,890.50", -1234567890.50.to_s(:currency, format: "%u %n")) + assert_equal("($1,234,567,890.50)", -1234567890.50.to_s(:currency, negative_format: "(%u%n)")) + assert_equal("$1,234,567,892", 1234567891.50.to_s(:currency, precision: 0)) + assert_equal("$1,234,567,890.5", 1234567890.50.to_s(:currency, precision: 1)) + assert_equal("£1234567890,50", 1234567890.50.to_s(:currency, unit: "£", separator: ",", delimiter: "")) end - def test_to_s__rounded assert_equal("-111.235", -111.2346.to_s(:rounded)) assert_equal("111.235", 111.2346.to_s(:rounded)) - assert_equal("31.83", 31.825.to_s(:rounded, :precision => 2)) - assert_equal("111.23", 111.2346.to_s(:rounded, :precision => 2)) - assert_equal("111.00", 111.to_s(:rounded, :precision => 2)) - assert_equal("3268", (32.6751 * 100.00).to_s(:rounded, :precision => 0)) - assert_equal("112", 111.50.to_s(:rounded, :precision => 0)) - assert_equal("1234567892", 1234567891.50.to_s(:rounded, :precision => 0)) - assert_equal("0", 0.to_s(:rounded, :precision => 0)) - assert_equal("0.00100", 0.001.to_s(:rounded, :precision => 5)) - assert_equal("0.001", 0.00111.to_s(:rounded, :precision => 3)) - assert_equal("10.00", 9.995.to_s(:rounded, :precision => 2)) - assert_equal("11.00", 10.995.to_s(:rounded, :precision => 2)) - assert_equal("0.00", -0.001.to_s(:rounded, :precision => 2)) + assert_equal("31.83", 31.825.to_s(:rounded, precision: 2)) + assert_equal("111.23", 111.2346.to_s(:rounded, precision: 2)) + assert_equal("111.00", 111.to_s(:rounded, precision: 2)) + assert_equal("3268", (32.6751 * 100.00).to_s(:rounded, precision: 0)) + assert_equal("112", 111.50.to_s(:rounded, precision: 0)) + assert_equal("1234567892", 1234567891.50.to_s(:rounded, precision: 0)) + assert_equal("0", 0.to_s(:rounded, precision: 0)) + assert_equal("0.00100", 0.001.to_s(:rounded, precision: 5)) + assert_equal("0.001", 0.00111.to_s(:rounded, precision: 3)) + assert_equal("10.00", 9.995.to_s(:rounded, precision: 2)) + assert_equal("11.00", 10.995.to_s(:rounded, precision: 2)) + assert_equal("0.00", -0.001.to_s(:rounded, precision: 2)) end def test_to_s__percentage assert_equal("100.000%", 100.to_s(:percentage)) - assert_equal("100%", 100.to_s(:percentage, :precision => 0)) - assert_equal("302.06%", 302.0574.to_s(:percentage, :precision => 2)) - assert_equal("123.4%", 123.400.to_s(:percentage, :precision => 3, :strip_insignificant_zeros => true)) - assert_equal("1.000,000%", 1000.to_s(:percentage, :delimiter => '.', :separator => ',')) - assert_equal("1000.000 %", 1000.to_s(:percentage, :format => "%n %")) + assert_equal("100%", 100.to_s(:percentage, precision: 0)) + assert_equal("302.06%", 302.0574.to_s(:percentage, precision: 2)) + assert_equal("123.4%", 123.400.to_s(:percentage, precision: 3, strip_insignificant_zeros: true)) + assert_equal("1.000,000%", 1000.to_s(:percentage, delimiter: ".", separator: ",")) + assert_equal("1000.000 %", 1000.to_s(:percentage, format: "%n %")) end def test_to_s__delimited @@ -216,188 +215,180 @@ class NumericExtFormattingTest < ActiveSupport::TestCase end def test_to_s__delimited__with_options_hash - assert_equal '12 345 678', 12345678.to_s(:delimited, :delimiter => ' ') - assert_equal '12,345,678-05', 12345678.05.to_s(:delimited, :separator => '-') - assert_equal '12.345.678,05', 12345678.05.to_s(:delimited, :separator => ',', :delimiter => '.') - assert_equal '12.345.678,05', 12345678.05.to_s(:delimited, :delimiter => '.', :separator => ',') + assert_equal "12 345 678", 12345678.to_s(:delimited, delimiter: " ") + assert_equal "12,345,678-05", 12345678.05.to_s(:delimited, separator: "-") + assert_equal "12.345.678,05", 12345678.05.to_s(:delimited, separator: ",", delimiter: ".") + assert_equal "12.345.678,05", 12345678.05.to_s(:delimited, delimiter: ".", separator: ",") end - def test_to_s__rounded_with_custom_delimiter_and_separator - assert_equal '31,83', 31.825.to_s(:rounded, :precision => 2, :separator => ',') - assert_equal '1.231,83', 1231.825.to_s(:rounded, :precision => 2, :separator => ',', :delimiter => '.') + assert_equal "31,83", 31.825.to_s(:rounded, precision: 2, separator: ",") + assert_equal "1.231,83", 1231.825.to_s(:rounded, precision: 2, separator: ",", delimiter: ".") end def test_to_s__rounded__with_significant_digits - assert_equal "124000", 123987.to_s(:rounded, :precision => 3, :significant => true) - assert_equal "120000000", 123987876.to_s(:rounded, :precision => 2, :significant => true ) - assert_equal "9775", 9775.to_s(:rounded, :precision => 4, :significant => true ) - assert_equal "5.4", 5.3923.to_s(:rounded, :precision => 2, :significant => true ) - assert_equal "5", 5.3923.to_s(:rounded, :precision => 1, :significant => true ) - assert_equal "1", 1.232.to_s(:rounded, :precision => 1, :significant => true ) - assert_equal "7", 7.to_s(:rounded, :precision => 1, :significant => true ) - assert_equal "1", 1.to_s(:rounded, :precision => 1, :significant => true ) - assert_equal "53", 52.7923.to_s(:rounded, :precision => 2, :significant => true ) - assert_equal "9775.00", 9775.to_s(:rounded, :precision => 6, :significant => true ) - assert_equal "5.392900", 5.3929.to_s(:rounded, :precision => 7, :significant => true ) - assert_equal "0.0", 0.to_s(:rounded, :precision => 2, :significant => true ) - assert_equal "0", 0.to_s(:rounded, :precision => 1, :significant => true ) - assert_equal "0.0001", 0.0001.to_s(:rounded, :precision => 1, :significant => true ) - assert_equal "0.000100", 0.0001.to_s(:rounded, :precision => 3, :significant => true ) - assert_equal "0.0001", 0.0001111.to_s(:rounded, :precision => 1, :significant => true ) - assert_equal "10.0", 9.995.to_s(:rounded, :precision => 3, :significant => true) - assert_equal "9.99", 9.994.to_s(:rounded, :precision => 3, :significant => true) - assert_equal "11.0", 10.995.to_s(:rounded, :precision => 3, :significant => true) + assert_equal "124000", 123987.to_s(:rounded, precision: 3, significant: true) + assert_equal "120000000", 123987876.to_s(:rounded, precision: 2, significant: true ) + assert_equal "9775", 9775.to_s(:rounded, precision: 4, significant: true ) + assert_equal "5.4", 5.3923.to_s(:rounded, precision: 2, significant: true ) + assert_equal "5", 5.3923.to_s(:rounded, precision: 1, significant: true ) + assert_equal "1", 1.232.to_s(:rounded, precision: 1, significant: true ) + assert_equal "7", 7.to_s(:rounded, precision: 1, significant: true ) + assert_equal "1", 1.to_s(:rounded, precision: 1, significant: true ) + assert_equal "53", 52.7923.to_s(:rounded, precision: 2, significant: true ) + assert_equal "9775.00", 9775.to_s(:rounded, precision: 6, significant: true ) + assert_equal "5.392900", 5.3929.to_s(:rounded, precision: 7, significant: true ) + assert_equal "0.0", 0.to_s(:rounded, precision: 2, significant: true ) + assert_equal "0", 0.to_s(:rounded, precision: 1, significant: true ) + assert_equal "0.0001", 0.0001.to_s(:rounded, precision: 1, significant: true ) + assert_equal "0.000100", 0.0001.to_s(:rounded, precision: 3, significant: true ) + assert_equal "0.0001", 0.0001111.to_s(:rounded, precision: 1, significant: true ) + assert_equal "10.0", 9.995.to_s(:rounded, precision: 3, significant: true) + assert_equal "9.99", 9.994.to_s(:rounded, precision: 3, significant: true) + assert_equal "11.0", 10.995.to_s(:rounded, precision: 3, significant: true) end def test_to_s__rounded__with_strip_insignificant_zeros - assert_equal "9775.43", 9775.43.to_s(:rounded, :precision => 4, :strip_insignificant_zeros => true ) - assert_equal "9775.2", 9775.2.to_s(:rounded, :precision => 6, :significant => true, :strip_insignificant_zeros => true ) - assert_equal "0", 0.to_s(:rounded, :precision => 6, :significant => true, :strip_insignificant_zeros => true ) + assert_equal "9775.43", 9775.43.to_s(:rounded, precision: 4, strip_insignificant_zeros: true ) + assert_equal "9775.2", 9775.2.to_s(:rounded, precision: 6, significant: true, strip_insignificant_zeros: true ) + assert_equal "0", 0.to_s(:rounded, precision: 6, significant: true, strip_insignificant_zeros: true ) end def test_to_s__rounded__with_significant_true_and_zero_precision # Zero precision with significant is a mistake (would always return zero), # so we treat it as if significant was false (increases backwards compatibility for number_to_human_size) - assert_equal "124", 123.987.to_s(:rounded, :precision => 0, :significant => true) - assert_equal "12", 12.to_s(:rounded, :precision => 0, :significant => true ) + assert_equal "124", 123.987.to_s(:rounded, precision: 0, significant: true) + assert_equal "12", 12.to_s(:rounded, precision: 0, significant: true ) end def test_to_s__human_size - assert_equal '0 Bytes', 0.to_s(:human_size) - assert_equal '1 Byte', 1.to_s(:human_size) - assert_equal '3 Bytes', 3.14159265.to_s(:human_size) - assert_equal '123 Bytes', 123.0.to_s(:human_size) - assert_equal '123 Bytes', 123.to_s(:human_size) - assert_equal '1.21 KB', 1234.to_s(:human_size) - assert_equal '12.1 KB', 12345.to_s(:human_size) - assert_equal '1.18 MB', 1234567.to_s(:human_size) - assert_equal '1.15 GB', 1234567890.to_s(:human_size) - assert_equal '1.12 TB', 1234567890123.to_s(:human_size) - assert_equal '1.1 PB', 1234567890123456.to_s(:human_size) - assert_equal '1.07 EB', 1234567890123456789.to_s(:human_size) - assert_equal '1030 EB', exabytes(1026).to_s(:human_size) - assert_equal '444 KB', kilobytes(444).to_s(:human_size) - assert_equal '1020 MB', megabytes(1023).to_s(:human_size) - assert_equal '3 TB', terabytes(3).to_s(:human_size) - assert_equal '1.2 MB', 1234567.to_s(:human_size, :precision => 2) - assert_equal '3 Bytes', 3.14159265.to_s(:human_size, :precision => 4) - assert_equal '1 KB', kilobytes(1.0123).to_s(:human_size, :precision => 2) - assert_equal '1.01 KB', kilobytes(1.0100).to_s(:human_size, :precision => 4) - assert_equal '10 KB', kilobytes(10.000).to_s(:human_size, :precision => 4) - assert_equal '1 Byte', 1.1.to_s(:human_size) - assert_equal '10 Bytes', 10.to_s(:human_size) + assert_equal "0 Bytes", 0.to_s(:human_size) + assert_equal "1 Byte", 1.to_s(:human_size) + assert_equal "3 Bytes", 3.14159265.to_s(:human_size) + assert_equal "123 Bytes", 123.0.to_s(:human_size) + assert_equal "123 Bytes", 123.to_s(:human_size) + assert_equal "1.21 KB", 1234.to_s(:human_size) + assert_equal "12.1 KB", 12345.to_s(:human_size) + assert_equal "1.18 MB", 1234567.to_s(:human_size) + assert_equal "1.15 GB", 1234567890.to_s(:human_size) + assert_equal "1.12 TB", 1234567890123.to_s(:human_size) + assert_equal "1.1 PB", 1234567890123456.to_s(:human_size) + assert_equal "1.07 EB", 1234567890123456789.to_s(:human_size) + assert_equal "1030 EB", exabytes(1026).to_s(:human_size) + assert_equal "444 KB", kilobytes(444).to_s(:human_size) + assert_equal "1020 MB", megabytes(1023).to_s(:human_size) + assert_equal "3 TB", terabytes(3).to_s(:human_size) + assert_equal "1.2 MB", 1234567.to_s(:human_size, precision: 2) + assert_equal "3 Bytes", 3.14159265.to_s(:human_size, precision: 4) + assert_equal "1 KB", kilobytes(1.0123).to_s(:human_size, precision: 2) + assert_equal "1.01 KB", kilobytes(1.0100).to_s(:human_size, precision: 4) + assert_equal "10 KB", kilobytes(10.000).to_s(:human_size, precision: 4) + assert_equal "1 Byte", 1.1.to_s(:human_size) + assert_equal "10 Bytes", 10.to_s(:human_size) end def test_to_s__human_size_with_si_prefix assert_deprecated do - assert_equal '3 Bytes', 3.14159265.to_s(:human_size, :prefix => :si) - assert_equal '123 Bytes', 123.0.to_s(:human_size, :prefix => :si) - assert_equal '123 Bytes', 123.to_s(:human_size, :prefix => :si) - assert_equal '1.23 KB', 1234.to_s(:human_size, :prefix => :si) - assert_equal '12.3 KB', 12345.to_s(:human_size, :prefix => :si) - assert_equal '1.23 MB', 1234567.to_s(:human_size, :prefix => :si) - assert_equal '1.23 GB', 1234567890.to_s(:human_size, :prefix => :si) - assert_equal '1.23 TB', 1234567890123.to_s(:human_size, :prefix => :si) - assert_equal '1.23 PB', 1234567890123456.to_s(:human_size, :prefix => :si) - assert_equal '1.23 EB', 1234567890123456789.to_s(:human_size, :prefix => :si) + assert_equal "3 Bytes", 3.14159265.to_s(:human_size, prefix: :si) + assert_equal "123 Bytes", 123.0.to_s(:human_size, prefix: :si) + assert_equal "123 Bytes", 123.to_s(:human_size, prefix: :si) + assert_equal "1.23 KB", 1234.to_s(:human_size, prefix: :si) + assert_equal "12.3 KB", 12345.to_s(:human_size, prefix: :si) + assert_equal "1.23 MB", 1234567.to_s(:human_size, prefix: :si) + assert_equal "1.23 GB", 1234567890.to_s(:human_size, prefix: :si) + assert_equal "1.23 TB", 1234567890123.to_s(:human_size, prefix: :si) + assert_equal "1.23 PB", 1234567890123456.to_s(:human_size, prefix: :si) + assert_equal "1.23 EB", 1234567890123456789.to_s(:human_size, prefix: :si) end end def test_to_s__human_size_with_options_hash - assert_equal '1.2 MB', 1234567.to_s(:human_size, :precision => 2) - assert_equal '3 Bytes', 3.14159265.to_s(:human_size, :precision => 4) - assert_equal '1 KB', kilobytes(1.0123).to_s(:human_size, :precision => 2) - assert_equal '1.01 KB', kilobytes(1.0100).to_s(:human_size, :precision => 4) - assert_equal '10 KB', kilobytes(10.000).to_s(:human_size, :precision => 4) - assert_equal '1 TB', 1234567890123.to_s(:human_size, :precision => 1) - assert_equal '500 MB', 524288000.to_s(:human_size, :precision=>3) - assert_equal '10 MB', 9961472.to_s(:human_size, :precision=>0) - assert_equal '40 KB', 41010.to_s(:human_size, :precision => 1) - assert_equal '40 KB', 41100.to_s(:human_size, :precision => 2) - assert_equal '1.0 KB', kilobytes(1.0123).to_s(:human_size, :precision => 2, :strip_insignificant_zeros => false) - assert_equal '1.012 KB', kilobytes(1.0123).to_s(:human_size, :precision => 3, :significant => false) - assert_equal '1 KB', kilobytes(1.0123).to_s(:human_size, :precision => 0, :significant => true) #ignores significant it precision is 0 + assert_equal "1.2 MB", 1234567.to_s(:human_size, precision: 2) + assert_equal "3 Bytes", 3.14159265.to_s(:human_size, precision: 4) + assert_equal "1 KB", kilobytes(1.0123).to_s(:human_size, precision: 2) + assert_equal "1.01 KB", kilobytes(1.0100).to_s(:human_size, precision: 4) + assert_equal "10 KB", kilobytes(10.000).to_s(:human_size, precision: 4) + assert_equal "1 TB", 1234567890123.to_s(:human_size, precision: 1) + assert_equal "500 MB", 524288000.to_s(:human_size, precision: 3) + assert_equal "10 MB", 9961472.to_s(:human_size, precision: 0) + assert_equal "40 KB", 41010.to_s(:human_size, precision: 1) + assert_equal "40 KB", 41100.to_s(:human_size, precision: 2) + assert_equal "1.0 KB", kilobytes(1.0123).to_s(:human_size, precision: 2, strip_insignificant_zeros: false) + assert_equal "1.012 KB", kilobytes(1.0123).to_s(:human_size, precision: 3, significant: false) + assert_equal "1 KB", kilobytes(1.0123).to_s(:human_size, precision: 0, significant: true) #ignores significant it precision is 0 end def test_to_s__human_size_with_custom_delimiter_and_separator - assert_equal '1,01 KB', kilobytes(1.0123).to_s(:human_size, :precision => 3, :separator => ',') - assert_equal '1,01 KB', kilobytes(1.0100).to_s(:human_size, :precision => 4, :separator => ',') - assert_equal '1.000,1 TB', terabytes(1000.1).to_s(:human_size, :precision => 5, :delimiter => '.', :separator => ',') + assert_equal "1,01 KB", kilobytes(1.0123).to_s(:human_size, precision: 3, separator: ",") + assert_equal "1,01 KB", kilobytes(1.0100).to_s(:human_size, precision: 4, separator: ",") + assert_equal "1.000,1 TB", terabytes(1000.1).to_s(:human_size, precision: 5, delimiter: ".", separator: ",") end def test_number_to_human - assert_equal '-123', -123.to_s(:human) - assert_equal '-0.5', -0.5.to_s(:human) - assert_equal '0', 0.to_s(:human) - assert_equal '0.5', 0.5.to_s(:human) - assert_equal '123', 123.to_s(:human) - assert_equal '1.23 Thousand', 1234.to_s(:human) - assert_equal '12.3 Thousand', 12345.to_s(:human) - assert_equal '1.23 Million', 1234567.to_s(:human) - assert_equal '1.23 Billion', 1234567890.to_s(:human) - assert_equal '1.23 Trillion', 1234567890123.to_s(:human) - assert_equal '1.23 Quadrillion', 1234567890123456.to_s(:human) - assert_equal '1230 Quadrillion', 1234567890123456789.to_s(:human) - assert_equal '490 Thousand', 489939.to_s(:human, :precision => 2) - assert_equal '489.9 Thousand', 489939.to_s(:human, :precision => 4) - assert_equal '489 Thousand', 489000.to_s(:human, :precision => 4) - assert_equal '489.0 Thousand', 489000.to_s(:human, :precision => 4, :strip_insignificant_zeros => false) - assert_equal '1.2346 Million', 1234567.to_s(:human, :precision => 4, :significant => false) - assert_equal '1,2 Million', 1234567.to_s(:human, :precision => 1, :significant => false, :separator => ',') - assert_equal '1 Million', 1234567.to_s(:human, :precision => 0, :significant => true, :separator => ',') #significant forced to false + assert_equal "-123", -123.to_s(:human) + assert_equal "-0.5", -0.5.to_s(:human) + assert_equal "0", 0.to_s(:human) + assert_equal "0.5", 0.5.to_s(:human) + assert_equal "123", 123.to_s(:human) + assert_equal "1.23 Thousand", 1234.to_s(:human) + assert_equal "12.3 Thousand", 12345.to_s(:human) + assert_equal "1.23 Million", 1234567.to_s(:human) + assert_equal "1.23 Billion", 1234567890.to_s(:human) + assert_equal "1.23 Trillion", 1234567890123.to_s(:human) + assert_equal "1.23 Quadrillion", 1234567890123456.to_s(:human) + assert_equal "1230 Quadrillion", 1234567890123456789.to_s(:human) + assert_equal "490 Thousand", 489939.to_s(:human, precision: 2) + assert_equal "489.9 Thousand", 489939.to_s(:human, precision: 4) + assert_equal "489 Thousand", 489000.to_s(:human, precision: 4) + assert_equal "489.0 Thousand", 489000.to_s(:human, precision: 4, strip_insignificant_zeros: false) + assert_equal "1.2346 Million", 1234567.to_s(:human, precision: 4, significant: false) + assert_equal "1,2 Million", 1234567.to_s(:human, precision: 1, significant: false, separator: ",") + assert_equal "1 Million", 1234567.to_s(:human, precision: 0, significant: true, separator: ",") #significant forced to false end def test_number_to_human_with_custom_units #Only integers - volume = {:unit => "ml", :thousand => "lt", :million => "m3"} - assert_equal '123 lt', 123456.to_s(:human, :units => volume) - assert_equal '12 ml', 12.to_s(:human, :units => volume) - assert_equal '1.23 m3', 1234567.to_s(:human, :units => volume) + volume = { unit: "ml", thousand: "lt", million: "m3" } + assert_equal "123 lt", 123456.to_s(:human, units: volume) + assert_equal "12 ml", 12.to_s(:human, units: volume) + assert_equal "1.23 m3", 1234567.to_s(:human, units: volume) #Including fractionals - distance = {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"} - assert_equal '1.23 mm', 0.00123.to_s(:human, :units => distance) - assert_equal '1.23 cm', 0.0123.to_s(:human, :units => distance) - assert_equal '1.23 dm', 0.123.to_s(:human, :units => distance) - assert_equal '1.23 m', 1.23.to_s(:human, :units => distance) - assert_equal '1.23 dam', 12.3.to_s(:human, :units => distance) - assert_equal '1.23 hm', 123.to_s(:human, :units => distance) - assert_equal '1.23 km', 1230.to_s(:human, :units => distance) - assert_equal '1.23 km', 1230.to_s(:human, :units => distance) - assert_equal '1.23 km', 1230.to_s(:human, :units => distance) - assert_equal '12.3 km', 12300.to_s(:human, :units => distance) + distance = { mili: "mm", centi: "cm", deci: "dm", unit: "m", ten: "dam", hundred: "hm", thousand: "km" } + assert_equal "1.23 mm", 0.00123.to_s(:human, units: distance) + assert_equal "1.23 cm", 0.0123.to_s(:human, units: distance) + assert_equal "1.23 dm", 0.123.to_s(:human, units: distance) + assert_equal "1.23 m", 1.23.to_s(:human, units: distance) + assert_equal "1.23 dam", 12.3.to_s(:human, units: distance) + assert_equal "1.23 hm", 123.to_s(:human, units: distance) + assert_equal "1.23 km", 1230.to_s(:human, units: distance) + assert_equal "1.23 km", 1230.to_s(:human, units: distance) + assert_equal "1.23 km", 1230.to_s(:human, units: distance) + assert_equal "12.3 km", 12300.to_s(:human, units: distance) #The quantifiers don't need to be a continuous sequence - gangster = {:hundred => "hundred bucks", :million => "thousand quids"} - assert_equal '1 hundred bucks', 100.to_s(:human, :units => gangster) - assert_equal '25 hundred bucks', 2500.to_s(:human, :units => gangster) - assert_equal '25 thousand quids', 25000000.to_s(:human, :units => gangster) - assert_equal '12300 thousand quids', 12345000000.to_s(:human, :units => gangster) + gangster = { hundred: "hundred bucks", million: "thousand quids" } + assert_equal "1 hundred bucks", 100.to_s(:human, units: gangster) + assert_equal "25 hundred bucks", 2500.to_s(:human, units: gangster) + assert_equal "25 thousand quids", 25000000.to_s(:human, units: gangster) + assert_equal "12300 thousand quids", 12345000000.to_s(:human, units: gangster) #Spaces are stripped from the resulting string - assert_equal '4', 4.to_s(:human, :units => {:unit => "", :ten => 'tens '}) - assert_equal '4.5 tens', 45.to_s(:human, :units => {:unit => "", :ten => ' tens '}) + assert_equal "4", 4.to_s(:human, units: { unit: "", ten: "tens " }) + assert_equal "4.5 tens", 45.to_s(:human, units: { unit: "", ten: " tens " }) end def test_number_to_human_with_custom_format - assert_equal '123 times Thousand', 123456.to_s(:human, :format => "%n times %u") - volume = {:unit => "ml", :thousand => "lt", :million => "m3"} - assert_equal '123.lt', 123456.to_s(:human, :units => volume, :format => "%n.%u") + assert_equal "123 times Thousand", 123456.to_s(:human, format: "%n times %u") + volume = { unit: "ml", thousand: "lt", million: "m3" } + assert_equal "123.lt", 123456.to_s(:human, units: volume, format: "%n.%u") end def test_to_s__injected_on_proper_types - assert_equal Fixnum, 1230.class - assert_equal '1.23 Thousand', 1230.to_s(:human) - - assert_equal Float, Float(1230).class - assert_equal '1.23 Thousand', Float(1230).to_s(:human) - - assert_equal Bignum, (100**10).class - assert_equal '100000 Quadrillion', (100**10).to_s(:human) - - assert_equal BigDecimal, BigDecimal("1000010").class - assert_equal '1 Million', BigDecimal("1000010").to_s(:human) + assert_equal "1.23 Thousand", 1230.to_s(:human) + assert_equal "1.23 Thousand", Float(1230).to_s(:human) + assert_equal "100000 Quadrillion", (100**10).to_s(:human) + assert_equal "1 Million", BigDecimal("1000010").to_s(:human) end def test_to_formatted_s_is_deprecated @@ -407,23 +398,23 @@ class NumericExtFormattingTest < ActiveSupport::TestCase end def test_to_s_with_invalid_formatter - assert_equal '123', 123.to_s(:invalid) - assert_equal '2.5', 2.5.to_s(:invalid) - assert_equal '100000000000000000000', (100**10).to_s(:invalid) - assert_equal '1000010.0', BigDecimal("1000010").to_s(:invalid) + assert_equal "123", 123.to_s(:invalid) + assert_equal "2.5", 2.5.to_s(:invalid) + assert_equal "100000000000000000000", (100**10).to_s(:invalid) + assert_equal "1000010.0", BigDecimal("1000010").to_s(:invalid) end def test_default_to_s - assert_equal '123', 123.to_s - assert_equal '1111011', 123.to_s(2) + assert_equal "123", 123.to_s + assert_equal "1111011", 123.to_s(2) - assert_equal '2.5', 2.5.to_s + assert_equal "2.5", 2.5.to_s - assert_equal '100000000000000000000', (100**10).to_s - assert_equal '1010110101111000111010111100010110101100011000100000000000000000000', (100**10).to_s(2) + assert_equal "100000000000000000000", (100**10).to_s + assert_equal "1010110101111000111010111100010110101100011000100000000000000000000", (100**10).to_s(2) - assert_equal '1000010.0', BigDecimal("1000010").to_s - assert_equal '10000 10.0', BigDecimal("1000010").to_s('5F') + assert_equal "1000010.0", BigDecimal("1000010").to_s + assert_equal "10000 10.0", BigDecimal("1000010").to_s("5F") end def test_in_milliseconds diff --git a/activesupport/test/core_ext/object/acts_like_test.rb b/activesupport/test/core_ext/object/acts_like_test.rb index e68b1d23cb..631f4e63a8 100644 --- a/activesupport/test/core_ext/object/acts_like_test.rb +++ b/activesupport/test/core_ext/object/acts_like_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/object' +require "abstract_unit" +require "active_support/core_ext/object" class ObjectTests < ActiveSupport::TestCase class DuckTime diff --git a/activesupport/test/core_ext/object/blank_test.rb b/activesupport/test/core_ext/object/blank_test.rb index a142096993..ab0676524e 100644 --- a/activesupport/test/core_ext/object/blank_test.rb +++ b/activesupport/test/core_ext/object/blank_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/object/blank' +require "abstract_unit" +require "active_support/core_ext/object/blank" class BlankTest < ActiveSupport::TestCase class EmptyTrue @@ -14,8 +14,8 @@ class BlankTest < ActiveSupport::TestCase end end - BLANK = [ EmptyTrue.new, nil, false, '', ' ', " \n\t \r ", ' ', "\u00a0", [], {} ] - NOT = [ EmptyFalse.new, Object.new, true, 0, 1, 'a', [nil], { nil => 0 } ] + BLANK = [ EmptyTrue.new, nil, false, "", " ", " \n\t \r ", " ", "\u00a0", [], {} ] + NOT = [ EmptyFalse.new, Object.new, true, 0, 1, "a", [nil], { nil => 0 } ] def test_blank BLANK.each { |v| assert_equal true, v.blank?, "#{v.inspect} should be blank" } diff --git a/activesupport/test/core_ext/object/deep_dup_test.rb b/activesupport/test/core_ext/object/deep_dup_test.rb index 791b5e7172..e335ec1b40 100644 --- a/activesupport/test/core_ext/object/deep_dup_test.rb +++ b/activesupport/test/core_ext/object/deep_dup_test.rb @@ -1,8 +1,7 @@ -require 'abstract_unit' -require 'active_support/core_ext/object' +require "abstract_unit" +require "active_support/core_ext/object" class DeepDupTest < ActiveSupport::TestCase - def test_array_deep_dup array = [1, [2, 3]] dup = array.deep_dup @@ -12,15 +11,15 @@ class DeepDupTest < ActiveSupport::TestCase end def test_hash_deep_dup - hash = { :a => { :b => 'b' } } + hash = { a: { b: "b" } } dup = hash.deep_dup - dup[:a][:c] = 'c' + dup[:a][:c] = "c" assert_equal nil, hash[:a][:c] - assert_equal 'c', dup[:a][:c] + assert_equal "c", dup[:a][:c] end def test_array_deep_dup_with_hash_inside - array = [1, { :a => 2, :b => 3 } ] + array = [1, { a: 2, b: 3 } ] dup = array.deep_dup dup[1][:c] = 4 assert_equal nil, array[1][:c] @@ -28,16 +27,16 @@ class DeepDupTest < ActiveSupport::TestCase end def test_hash_deep_dup_with_array_inside - hash = { :a => [1, 2] } + hash = { a: [1, 2] } dup = hash.deep_dup - dup[:a][2] = 'c' + dup[:a][2] = "c" assert_equal nil, hash[:a][2] - assert_equal 'c', dup[:a][2] + assert_equal "c", dup[:a][2] end def test_deep_dup_initialize zero_hash = Hash.new 0 - hash = { :a => zero_hash } + hash = { a: zero_hash } dup = hash.deep_dup assert_equal 0, dup[:a][44] end @@ -51,9 +50,8 @@ class DeepDupTest < ActiveSupport::TestCase end def test_deep_dup_with_hash_class_key - hash = { Fixnum => 1 } + hash = { Integer => 1 } dup = hash.deep_dup assert_equal 1, dup.keys.length end - end diff --git a/activesupport/test/core_ext/object/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb index 042f5cfb34..2cbfefe235 100644 --- a/activesupport/test/core_ext/object/duplicable_test.rb +++ b/activesupport/test/core_ext/object/duplicable_test.rb @@ -1,12 +1,12 @@ -require 'abstract_unit' -require 'bigdecimal' -require 'active_support/core_ext/object/duplicable' -require 'active_support/core_ext/numeric/time' +require "abstract_unit" +require "bigdecimal" +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') + ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new] + ALLOW_DUP << BigDecimal.new("4.56") def test_duplicable rubinius_skip "* Method#dup is allowed at the moment on Rubinius\n" \ diff --git a/activesupport/test/core_ext/object/inclusion_test.rb b/activesupport/test/core_ext/object/inclusion_test.rb index 32d512eca3..be211ec7dc 100644 --- a/activesupport/test/core_ext/object/inclusion_test.rb +++ b/activesupport/test/core_ext/object/inclusion_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/object/inclusion' +require "abstract_unit" +require "active_support/core_ext/object/inclusion" class InTest < ActiveSupport::TestCase def test_in_array @@ -46,11 +46,11 @@ class InTest < ActiveSupport::TestCase assert !A.in?(A) assert !A.in?(D) end - + def test_no_method_catching assert_raise(ArgumentError) { 1.in?(1) } end - + def test_presence_in assert_equal "stuff", "stuff".presence_in(%w( lots of stuff )) assert_nil "stuff".presence_in(%w( lots of crap )) diff --git a/activesupport/test/core_ext/object/instance_variables_test.rb b/activesupport/test/core_ext/object/instance_variables_test.rb index 9f4c5dc4f1..5bdb2fbc35 100644 --- a/activesupport/test/core_ext/object/instance_variables_test.rb +++ b/activesupport/test/core_ext/object/instance_variables_test.rb @@ -1,11 +1,11 @@ -require 'abstract_unit' -require 'active_support/core_ext/object' +require "abstract_unit" +require "active_support/core_ext/object" class ObjectInstanceVariableTest < ActiveSupport::TestCase def setup @source, @dest = Object.new, Object.new - @source.instance_variable_set(:@bar, 'bar') - @source.instance_variable_set(:@baz, 'baz') + @source.instance_variable_set(:@bar, "bar") + @source.instance_variable_set(:@baz, "baz") end def test_instance_variable_names @@ -13,19 +13,19 @@ class ObjectInstanceVariableTest < ActiveSupport::TestCase end def test_instance_values - assert_equal({'bar' => 'bar', 'baz' => 'baz'}, @source.instance_values) + assert_equal({ "bar" => "bar", "baz" => "baz" }, @source.instance_values) end def test_instance_exec_passes_arguments_to_block - assert_equal %w(hello goodbye), 'hello'.instance_exec('goodbye') { |v| [self, v] } + assert_equal %w(hello goodbye), "hello".instance_exec("goodbye") { |v| [self, v] } end def test_instance_exec_with_frozen_obj - assert_equal %w(olleh goodbye), 'hello'.freeze.instance_exec('goodbye') { |v| [reverse, v] } + assert_equal %w(olleh goodbye), "hello".freeze.instance_exec("goodbye") { |v| [reverse, v] } end def test_instance_exec_nested - assert_equal %w(goodbye olleh bar), 'hello'.instance_exec('goodbye') { |arg| - [arg] + instance_exec('bar') { |v| [reverse, v] } } + assert_equal %w(goodbye olleh bar), "hello".instance_exec("goodbye") { |arg| + [arg] + instance_exec("bar") { |v| [reverse, v] } } end end diff --git a/activesupport/test/core_ext/object/json_cherry_pick_test.rb b/activesupport/test/core_ext/object/json_cherry_pick_test.rb index 2f7ea3a497..dd4e90918e 100644 --- a/activesupport/test/core_ext/object/json_cherry_pick_test.rb +++ b/activesupport/test/core_ext/object/json_cherry_pick_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" # These test cases were added to test that cherry-picking the json extensions # works correctly, primarily for dependencies problems reported in #16131. They @@ -9,7 +9,7 @@ class JsonCherryPickTest < ActiveSupport::TestCase include ActiveSupport::Testing::Isolation def test_time_as_json - require_or_skip 'active_support/core_ext/object/json' + require_or_skip "active_support/core_ext/object/json" expected = Time.new(2004, 7, 25) actual = Time.parse(expected.as_json) @@ -18,7 +18,7 @@ class JsonCherryPickTest < ActiveSupport::TestCase end def test_date_as_json - require_or_skip 'active_support/core_ext/object/json' + require_or_skip "active_support/core_ext/object/json" expected = Date.new(2004, 7, 25) actual = Date.parse(expected.as_json) @@ -27,7 +27,7 @@ class JsonCherryPickTest < ActiveSupport::TestCase end def test_datetime_as_json - require_or_skip 'active_support/core_ext/object/json' + require_or_skip "active_support/core_ext/object/json" expected = DateTime.new(2004, 7, 25) actual = DateTime.parse(expected.as_json) diff --git a/activesupport/test/core_ext/object/json_gem_encoding_test.rb b/activesupport/test/core_ext/object/json_gem_encoding_test.rb index 02ab17fb64..f5016d0c2a 100644 --- a/activesupport/test/core_ext/object/json_gem_encoding_test.rb +++ b/activesupport/test/core_ext/object/json_gem_encoding_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'json' -require 'json/encoding_test_cases' +require "abstract_unit" +require "json" +require "json/encoding_test_cases" # These test cases were added to test that we do not interfere with json gem's # output when the AS encoder is loaded, primarily for problems reported in @@ -10,10 +10,10 @@ require 'json/encoding_test_cases' # The AS::JSON encoder requires the BigDecimal core_ext, which, unfortunately, # changes the BigDecimal#to_s output, and consequently the JSON gem output. So -# we need to require this unfront to ensure we don't get a false failure, but +# we need to require this upfront to ensure we don't get a false failure, but # ideally we should just fix the BigDecimal core_ext to not change to_s without # arguments. -require 'active_support/core_ext/big_decimal' +require "active_support/core_ext/big_decimal" class JsonGemEncodingTest < ActiveSupport::TestCase include ActiveSupport::Testing::Isolation @@ -48,7 +48,7 @@ class JsonGemEncodingTest < ActiveSupport::TestCase exception = e end - require_or_skip 'active_support/core_ext/object/json' + require_or_skip "active_support/core_ext/object/json" if exception assert_raises_with_message JSON::GeneratorError, e.message do diff --git a/activesupport/test/core_ext/object/to_param_test.rb b/activesupport/test/core_ext/object/to_param_test.rb index 30a7557dc2..56246b24f3 100644 --- a/activesupport/test/core_ext/object/to_param_test.rb +++ b/activesupport/test/core_ext/object/to_param_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/object/to_param' +require "abstract_unit" +require "active_support/core_ext/object/to_param" class ToParamTest < ActiveSupport::TestCase class CustomString < String @@ -10,8 +10,8 @@ class ToParamTest < ActiveSupport::TestCase def test_object foo = Object.new - def foo.to_s; 'foo' end - assert_equal 'foo', foo.to_param + def foo.to_s; "foo" end + assert_equal "foo", foo.to_param end def test_nil @@ -25,13 +25,13 @@ class ToParamTest < ActiveSupport::TestCase def test_array # Empty Array - assert_equal '', [].to_param + assert_equal "", [].to_param array = [1, 2, 3, 4] assert_equal "1/2/3/4", array.to_param # Array of different objects - array = [1, '3', { a: 1, b: 2 }, nil, true, false, CustomString.new('object')] + array = [1, "3", { a: 1, b: 2 }, nil, true, false, CustomString.new("object")] assert_equal "1/3/a=1&b=2//true/false/custom-object", array.to_param end end diff --git a/activesupport/test/core_ext/object/to_query_test.rb b/activesupport/test/core_ext/object/to_query_test.rb index 09cab3ed35..298c8bf373 100644 --- a/activesupport/test/core_ext/object/to_query_test.rb +++ b/activesupport/test/core_ext/object/to_query_test.rb @@ -1,82 +1,82 @@ -require 'abstract_unit' -require 'active_support/ordered_hash' -require 'active_support/core_ext/object/to_query' -require 'active_support/core_ext/string/output_safety' +require "abstract_unit" +require "active_support/ordered_hash" +require "active_support/core_ext/object/to_query" +require "active_support/core_ext/string/output_safety" class ToQueryTest < ActiveSupport::TestCase def test_simple_conversion - assert_query_equal 'a=10', :a => 10 + assert_query_equal "a=10", a: 10 end def test_cgi_escaping - assert_query_equal 'a%3Ab=c+d', 'a:b' => 'c d' + assert_query_equal "a%3Ab=c+d", "a:b" => "c d" end def test_html_safe_parameter_key - assert_query_equal 'a%3Ab=c+d', 'a:b'.html_safe => 'c d' + assert_query_equal "a%3Ab=c+d", "a:b".html_safe => "c d" end def test_html_safe_parameter_value - assert_query_equal 'a=%5B10%5D', 'a' => '[10]'.html_safe + assert_query_equal "a=%5B10%5D", "a" => "[10]".html_safe end def test_nil_parameter_value empty = Object.new def empty.to_param; nil end - assert_query_equal 'a=', 'a' => empty + assert_query_equal "a=", "a" => empty end def test_nested_conversion - assert_query_equal 'person%5Blogin%5D=seckar&person%5Bname%5D=Nicholas', - :person => Hash[:login, 'seckar', :name, 'Nicholas'] + assert_query_equal "person%5Blogin%5D=seckar&person%5Bname%5D=Nicholas", + person: Hash[:login, "seckar", :name, "Nicholas"] end def test_multiple_nested - assert_query_equal 'account%5Bperson%5D%5Bid%5D=20&person%5Bid%5D=10', - Hash[:account, {:person => {:id => 20}}, :person, {:id => 10}] + assert_query_equal "account%5Bperson%5D%5Bid%5D=20&person%5Bid%5D=10", + Hash[:account, { person: { id: 20 } }, :person, { id: 10 }] end def test_array_values - assert_query_equal 'person%5Bid%5D%5B%5D=10&person%5Bid%5D%5B%5D=20', - :person => {:id => [10, 20]} + assert_query_equal "person%5Bid%5D%5B%5D=10&person%5Bid%5D%5B%5D=20", + person: { id: [10, 20] } end def test_array_values_are_not_sorted - assert_query_equal 'person%5Bid%5D%5B%5D=20&person%5Bid%5D%5B%5D=10', - :person => {:id => [20, 10]} + assert_query_equal "person%5Bid%5D%5B%5D=20&person%5Bid%5D%5B%5D=10", + person: { id: [20, 10] } end def test_empty_array - assert_equal "person%5B%5D=", [].to_query('person') + assert_equal "person%5B%5D=", [].to_query("person") end def test_nested_empty_hash - assert_equal '', + assert_equal "", {}.to_query - assert_query_equal 'a=1&b%5Bc%5D=3', - { a: 1, b: { c: 3, d: {} } } - assert_query_equal '', - { a: {b: {c: {}}} } - assert_query_equal 'b%5Bc%5D=false&b%5Be%5D=&b%5Bf%5D=&p=12', - { p: 12, b: { c: false, e: nil, f: '' } } - assert_query_equal 'b%5Bc%5D=3&b%5Bf%5D=', - { b: { c: 3, k: {}, f: '' } } - assert_query_equal 'b=3', - {a: [], b: 3} + assert_query_equal "a=1&b%5Bc%5D=3", + a: 1, b: { c: 3, d: {} } + assert_query_equal "", + a: { b: { c: {} } } + assert_query_equal "b%5Bc%5D=false&b%5Be%5D=&b%5Bf%5D=&p=12", + p: 12, b: { c: false, e: nil, f: "" } + assert_query_equal "b%5Bc%5D=3&b%5Bf%5D=", + b: { c: 3, k: {}, f: "" } + assert_query_equal "b=3", + a: [], b: 3 end def test_hash_with_namespace - hash = { name: 'Nakshay', nationality: 'Indian' } - assert_equal "user%5Bname%5D=Nakshay&user%5Bnationality%5D=Indian", hash.to_query('user') + hash = { name: "Nakshay", nationality: "Indian" } + assert_equal "user%5Bname%5D=Nakshay&user%5Bnationality%5D=Indian", hash.to_query("user") end def test_hash_sorted_lexicographically - hash = { type: 'human', name: 'Nakshay' } + hash = { type: "human", name: "Nakshay" } assert_equal "name=Nakshay&type=human", hash.to_query end private def assert_query_equal(expected, actual) - assert_equal expected.split('&'), actual.to_query.split('&') + assert_equal expected.split("&"), actual.to_query.split("&") end end diff --git a/activesupport/test/core_ext/object/try_test.rb b/activesupport/test/core_ext/object/try_test.rb index 25bf0207b8..5c8bf59952 100644 --- a/activesupport/test/core_ext/object/try_test.rb +++ b/activesupport/test/core_ext/object/try_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/object' +require "abstract_unit" +require "active_support/core_ext/object" class ObjectTryTest < ActiveSupport::TestCase def setup @@ -15,7 +15,7 @@ class ObjectTryTest < ActiveSupport::TestCase def test_nonexisting_method_with_arguments method = :undefined_method assert !@string.respond_to?(method) - assert_nil @string.try(method, 'llo', 'y') + assert_nil @string.try(method, "llo", "y") end def test_nonexisting_method_bang @@ -27,7 +27,7 @@ class ObjectTryTest < ActiveSupport::TestCase def test_nonexisting_method_with_arguments_bang method = :undefined_method assert !@string.respond_to?(method) - assert_raise(NoMethodError) { @string.try!(method, 'llo', 'y') } + assert_raise(NoMethodError) { @string.try!(method, "llo", "y") } end def test_valid_method @@ -35,11 +35,11 @@ class ObjectTryTest < ActiveSupport::TestCase end def test_argument_forwarding - assert_equal 'Hey', @string.try(:sub, 'llo', 'y') + assert_equal "Hey", @string.try(:sub, "llo", "y") end def test_block_forwarding - assert_equal 'Hey', @string.try(:sub, 'llo') { |match| 'y' } + assert_equal "Hey", @string.try(:sub, "llo") { |match| "y" } end def test_nil_to_type @@ -48,7 +48,7 @@ class ObjectTryTest < ActiveSupport::TestCase end def test_false_try - assert_equal 'false', false.try(:to_s) + assert_equal "false", false.try(:to_s) end def test_try_only_block @@ -78,7 +78,7 @@ class ObjectTryTest < ActiveSupport::TestCase private def private_method - 'private method' + "private method" end end @@ -90,7 +90,7 @@ class ObjectTryTest < ActiveSupport::TestCase private def private_method - 'private method' + "private method" end end @@ -99,22 +99,22 @@ class ObjectTryTest < ActiveSupport::TestCase class Decorator < SimpleDelegator def delegator_method - 'delegator method' + "delegator method" end def reverse - 'overridden reverse' + "overridden reverse" end private def private_delegator_method - 'private delegator method' + "private delegator method" end end def test_try_with_method_on_delegator - assert_equal 'delegator method', Decorator.new(@string).try(:delegator_method) + assert_equal "delegator method", Decorator.new(@string).try(:delegator_method) end def test_try_with_method_on_delegator_target @@ -122,7 +122,7 @@ class ObjectTryTest < ActiveSupport::TestCase end def test_try_with_overridden_method_on_delegator - assert_equal 'overridden reverse', Decorator.new(@string).reverse + assert_equal "overridden reverse", Decorator.new(@string).reverse end def test_try_with_private_method_on_delegator @@ -140,7 +140,7 @@ class ObjectTryTest < ActiveSupport::TestCase private def private_method - 'private method' + "private method" end end @@ -152,7 +152,7 @@ class ObjectTryTest < ActiveSupport::TestCase private def private_method - 'private method' + "private method" end end diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb index f28cebda3d..fc0e72e09a 100644 --- a/activesupport/test/core_ext/range_ext_test.rb +++ b/activesupport/test/core_ext/range_ext_test.rb @@ -1,7 +1,7 @@ -require 'abstract_unit' -require 'active_support/time' -require 'active_support/core_ext/numeric' -require 'active_support/core_ext/range' +require "abstract_unit" +require "active_support/time" +require "active_support/core_ext/numeric" +require "active_support/core_ext/range" class RangeTest < ActiveSupport::TestCase def test_to_s_from_dates @@ -66,15 +66,15 @@ class RangeTest < ActiveSupport::TestCase end def test_exclusive_end_should_not_include_identical_with_inclusive_end - assert !(1...10).include?(1..10) + assert_not_includes (1...10), 1..10 end def test_should_not_include_overlapping_first - assert !(2..8).include?(1..3) + assert_not_includes (2..8), 1..3 end def test_should_not_include_overlapping_last - assert !(2..8).include?(5..9) + assert_not_includes (2..8), 5..9 end def test_should_include_identical_exclusive_with_floats @@ -99,21 +99,21 @@ class RangeTest < ActiveSupport::TestCase end def test_each_on_time_with_zone - twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone['Eastern Time (US & Canada)'] , Time.utc(2006,11,28,10,30)) + twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"] , Time.utc(2006,11,28,10,30)) assert_raises TypeError do ((twz - 1.hour)..twz).each {} end end def test_step_on_time_with_zone - twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone['Eastern Time (US & Canada)'] , Time.utc(2006,11,28,10,30)) + twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"] , Time.utc(2006,11,28,10,30)) assert_raises TypeError do ((twz - 1.hour)..twz).step(1) {} end end def test_include_on_time_with_zone - twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone['Eastern Time (US & Canada)'] , Time.utc(2006,11,28,10,30)) + twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"] , Time.utc(2006,11,28,10,30)) assert_raises TypeError do ((twz - 1.hour)..twz).include?(twz) end diff --git a/activesupport/test/core_ext/regexp_ext_test.rb b/activesupport/test/core_ext/regexp_ext_test.rb index c2398d31bd..e569a9f234 100644 --- a/activesupport/test/core_ext/regexp_ext_test.rb +++ b/activesupport/test/core_ext/regexp_ext_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/regexp' +require "abstract_unit" +require "active_support/core_ext/regexp" class RegexpExtAccessTests < ActiveSupport::TestCase def test_multiline @@ -7,4 +7,28 @@ class RegexpExtAccessTests < ActiveSupport::TestCase assert_equal false, //.multiline? assert_equal false, /(?m:)/.multiline? end + + # Based on https://github.com/ruby/ruby/blob/trunk/test/ruby/test_regexp.rb. + def test_match_p + /back(...)/ =~ "backref" + # must match here, but not in a separate method, e.g., assert_send, + # to check if $~ is affected or not. + assert_equal false, //.match?(nil) + assert_equal true, //.match?("") + assert_equal true, /.../.match?(:abc) + assert_raise(TypeError) { /.../.match?(Object.new) } + assert_equal true, /b/.match?("abc") + assert_equal true, /b/.match?("abc", 1) + assert_equal true, /../.match?("abc", 1) + assert_equal true, /../.match?("abc", -2) + assert_equal false, /../.match?("abc", -4) + assert_equal false, /../.match?("abc", 4) + assert_equal true, /\z/.match?("") + assert_equal true, /\z/.match?("abc") + assert_equal true, /R.../.match?("Ruby") + assert_equal false, /R.../.match?("Ruby", 1) + assert_equal false, /P.../.match?("Ruby") + assert_equal "backref", $& + assert_equal "ref", $1 + end end diff --git a/activesupport/test/core_ext/secure_random_test.rb b/activesupport/test/core_ext/secure_random_test.rb index dfacb7fe9f..fc25f6ab41 100644 --- a/activesupport/test/core_ext/secure_random_test.rb +++ b/activesupport/test/core_ext/secure_random_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/securerandom' +require "abstract_unit" +require "active_support/core_ext/securerandom" class SecureRandomTest < ActiveSupport::TestCase def test_base58 diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 2e69816364..4b40503d22 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -1,15 +1,16 @@ -require 'date' -require 'abstract_unit' -require 'inflector_test_cases' -require 'constantize_test_cases' - -require 'active_support/inflector' -require 'active_support/core_ext/string' -require 'active_support/time' -require 'active_support/core_ext/string/strip' -require 'active_support/core_ext/string/output_safety' -require 'active_support/core_ext/string/indent' -require 'time_zone_test_helpers' +require "date" +require "abstract_unit" +require "inflector_test_cases" +require "constantize_test_cases" + +require "active_support/inflector" +require "active_support/core_ext/string" +require "active_support/time" +require "active_support/core_ext/string/strip" +require "active_support/core_ext/string/output_safety" +require "active_support/core_ext/string/indent" +require "time_zone_test_helpers" +require "yaml" class StringInflectionsTest < ActiveSupport::TestCase include InflectorTestCases @@ -17,12 +18,12 @@ class StringInflectionsTest < ActiveSupport::TestCase include TimeZoneTestHelpers def test_strip_heredoc_on_an_empty_string - assert_equal '', ''.strip_heredoc + assert_equal "", "".strip_heredoc end def test_strip_heredoc_on_a_string_with_no_lines - assert_equal 'x', 'x'.strip_heredoc - assert_equal 'x', ' x'.strip_heredoc + assert_equal "x", "x".strip_heredoc + assert_equal "x", " x".strip_heredoc end def test_strip_heredoc_on_a_heredoc_with_no_margin @@ -59,7 +60,7 @@ class StringInflectionsTest < ActiveSupport::TestCase assert_equal("blargles", "blargle".pluralize(2)) end - test 'pluralize with count = 1 still returns new string' do + test "pluralize with count = 1 still returns new string" do name = "Kuldeep" assert_not_same name.pluralize(1), name end @@ -76,6 +77,18 @@ class StringInflectionsTest < ActiveSupport::TestCase end end + def test_upcase_first + assert_equal "What a Lovely Day", "what a Lovely Day".upcase_first + end + + def test_upcase_first_with_one_char + assert_equal "W", "w".upcase_first + end + + def test_upcase_first_with_empty_string + assert_equal "", "".upcase_first + end + def test_camelize CamelToUnderscore.each do |camel, underscore| assert_equal(camel, underscore.camelize) @@ -83,7 +96,7 @@ class StringInflectionsTest < ActiveSupport::TestCase end def test_camelize_lower - assert_equal('capital', 'Capital'.camelize(:lower)) + assert_equal("capital", "Capital".camelize(:lower)) end def test_dasherize @@ -151,41 +164,41 @@ class StringInflectionsTest < ActiveSupport::TestCase def test_string_parameterized_no_separator StringToParameterizeWithNoSeparator.each do |normal, slugged| - assert_equal(normal.parameterize(separator: ''), slugged) + assert_equal(normal.parameterize(separator: ""), slugged) end end def test_string_parameterized_no_separator_deprecated StringToParameterizeWithNoSeparator.each do |normal, slugged| assert_deprecated(/Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: ''` instead./i) do - assert_equal(normal.parameterize(''), slugged) + assert_equal(normal.parameterize(""), slugged) end 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(normal.parameterize(separator: "", preserve_case: true), slugged) end end def test_string_parameterized_underscore StringToParameterizeWithUnderscore.each do |normal, slugged| - assert_equal(normal.parameterize(separator: '_'), slugged) + assert_equal(normal.parameterize(separator: "_"), slugged) end end def test_string_parameterized_underscore_deprecated StringToParameterizeWithUnderscore.each do |normal, slugged| assert_deprecated(/Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '_'` instead./i) do - assert_equal(normal.parameterize('_'), slugged) + assert_equal(normal.parameterize("_"), slugged) end 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(normal.parameterize(separator: "_", preserve_case: true), slugged) end end @@ -202,23 +215,23 @@ class StringInflectionsTest < ActiveSupport::TestCase end def test_humanize_with_html_escape - assert_equal 'Hello', ERB::Util.html_escape("hello").humanize + assert_equal "Hello", ERB::Util.html_escape("hello").humanize end def test_ord - assert_equal 97, 'a'.ord - assert_equal 97, 'abc'.ord + assert_equal 97, "a".ord + assert_equal 97, "abc".ord end def test_starts_ends_with_alias s = "hello" - assert s.starts_with?('h') - assert s.starts_with?('hel') - assert !s.starts_with?('el') + assert s.starts_with?("h") + assert s.starts_with?("hel") + assert !s.starts_with?("el") - assert s.ends_with?('o') - assert s.ends_with?('lo') - assert !s.ends_with?('el') + assert s.ends_with?("o") + assert s.ends_with?("lo") + assert !s.ends_with?("el") end def test_string_squish @@ -250,16 +263,16 @@ class StringInflectionsTest < ActiveSupport::TestCase end def test_truncate_with_omission_and_separator - assert_equal "Hello[...]", "Hello World!".truncate(10, :omission => "[...]") - assert_equal "Hello[...]", "Hello Big World!".truncate(13, :omission => "[...]", :separator => ' ') - assert_equal "Hello Big[...]", "Hello Big World!".truncate(14, :omission => "[...]", :separator => ' ') - assert_equal "Hello Big[...]", "Hello Big World!".truncate(15, :omission => "[...]", :separator => ' ') + assert_equal "Hello[...]", "Hello World!".truncate(10, omission: "[...]") + assert_equal "Hello[...]", "Hello Big World!".truncate(13, omission: "[...]", separator: " ") + assert_equal "Hello Big[...]", "Hello Big World!".truncate(14, omission: "[...]", separator: " ") + assert_equal "Hello Big[...]", "Hello Big World!".truncate(15, omission: "[...]", separator: " ") end def test_truncate_with_omission_and_regexp_separator - assert_equal "Hello[...]", "Hello Big World!".truncate(13, :omission => "[...]", :separator => /\s/) - assert_equal "Hello Big[...]", "Hello Big World!".truncate(14, :omission => "[...]", :separator => /\s/) - assert_equal "Hello Big[...]", "Hello Big World!".truncate(15, :omission => "[...]", :separator => /\s/) + assert_equal "Hello[...]", "Hello Big World!".truncate(13, omission: "[...]", separator: /\s/) + assert_equal "Hello Big[...]", "Hello Big World!".truncate(14, omission: "[...]", separator: /\s/) + assert_equal "Hello Big[...]", "Hello Big World!".truncate(15, omission: "[...]", separator: /\s/) end def test_truncate_words @@ -268,19 +281,19 @@ class StringInflectionsTest < ActiveSupport::TestCase end def test_truncate_words_with_omission - assert_equal "Hello Big World!", "Hello Big World!".truncate_words(3, :omission => "[...]") - assert_equal "Hello Big[...]", "Hello Big World!".truncate_words(2, :omission => "[...]") + assert_equal "Hello Big World!", "Hello Big World!".truncate_words(3, omission: "[...]") + assert_equal "Hello Big[...]", "Hello Big World!".truncate_words(2, omission: "[...]") end def test_truncate_words_with_separator - assert_equal "Hello<br>Big<br>World!...", "Hello<br>Big<br>World!<br>".truncate_words(3, :separator => '<br>') - assert_equal "Hello<br>Big<br>World!", "Hello<br>Big<br>World!".truncate_words(3, :separator => '<br>') - assert_equal "Hello\n<br>Big...", "Hello\n<br>Big<br>Wide<br>World!".truncate_words(2, :separator => '<br>') + assert_equal "Hello<br>Big<br>World!...", "Hello<br>Big<br>World!<br>".truncate_words(3, separator: "<br>") + assert_equal "Hello<br>Big<br>World!", "Hello<br>Big<br>World!".truncate_words(3, separator: "<br>") + assert_equal "Hello\n<br>Big...", "Hello\n<br>Big<br>Wide<br>World!".truncate_words(2, separator: "<br>") end def test_truncate_words_with_separator_and_omission - assert_equal "Hello<br>Big<br>World![...]", "Hello<br>Big<br>World!<br>".truncate_words(3, :omission => "[...]", :separator => '<br>') - assert_equal "Hello<br>Big<br>World!", "Hello<br>Big<br>World!".truncate_words(3, :omission => "[...]", :separator => '<br>') + assert_equal "Hello<br>Big<br>World![...]", "Hello<br>Big<br>World!<br>".truncate_words(3, omission: "[...]", separator: "<br>") + assert_equal "Hello<br>Big<br>World!", "Hello<br>Big<br>World!".truncate_words(3, omission: "[...]", separator: "<br>") end def test_truncate_words_with_complex_string @@ -332,7 +345,7 @@ class StringInflectionsTest < ActiveSupport::TestCase end class StringAccessTest < ActiveSupport::TestCase - test "#at with Fixnum, returns a substring of one character at that position" do + test "#at with Integer, returns a substring of one character at that position" do assert_equal "h", "hello".at(0) end @@ -345,19 +358,19 @@ class StringAccessTest < ActiveSupport::TestCase assert_equal nil, "hello".at(/nonexisting/) end - test "#from with positive Fixnum, returns substring from the given position to the end" do + test "#from with positive Integer, returns substring from the given position to the end" do assert_equal "llo", "hello".from(2) end - test "#from with negative Fixnum, position is counted from the end" do + test "#from with negative Integer, position is counted from the end" do assert_equal "lo", "hello".from(-2) end - test "#to with positive Fixnum, substring from the beginning to the given position" do + test "#to with positive Integer, substring from the beginning to the given position" do assert_equal "hel", "hello".to(2) end - test "#to with negative Fixnum, position is counted from the end" do + test "#to with negative Integer, position is counted from the end" do assert_equal "hell", "hello".to(-2) end @@ -368,17 +381,17 @@ class StringAccessTest < ActiveSupport::TestCase test "#first returns the first character" do assert_equal "h", "hello".first - assert_equal 'x', 'x'.first + assert_equal "x", "x".first end - test "#first with Fixnum, returns a substring from the beginning to position" do + test "#first with Integer, returns a substring from the beginning to position" do assert_equal "he", "hello".first(2) assert_equal "", "hello".first(0) assert_equal "hello", "hello".first(10) - assert_equal 'x', 'x'.first(4) + assert_equal "x", "x".first(4) end - test "#first with Fixnum >= string length still returns a new string" do + test "#first with Integer >= string length still returns a new string" do string = "hello" different_string = string.first(5) assert_not_same different_string, string @@ -386,17 +399,17 @@ class StringAccessTest < ActiveSupport::TestCase test "#last returns the last character" do assert_equal "o", "hello".last - assert_equal 'x', 'x'.last + assert_equal "x", "x".last end - test "#last with Fixnum, returns a substring from the end to position" do + test "#last with Integer, returns a substring from the end to position" do assert_equal "llo", "hello".last(3) assert_equal "hello", "hello".last(10) assert_equal "", "hello".last(0) - assert_equal 'x', 'x'.last(4) + assert_equal "x", "x".last(4) end - test "#last with Fixnum >= string length still returns a new string" do + test "#last with Integer >= string length still returns a new string" do string = "hello" different_string = string.last(5) assert_not_same different_string, string @@ -444,16 +457,24 @@ class StringConversionsTest < ActiveSupport::TestCase assert_equal Time.local(2011, 2, 27, 17, 50), "2011-02-27 13:50 -0100".to_time assert_equal Time.utc(2011, 2, 27, 23, 50), "2011-02-27 22:50 -0100".to_time(:utc) assert_equal Time.local(2005, 2, 27, 22, 50), "2005-02-27 14:50 -0500".to_time + assert_nil "010".to_time assert_nil "".to_time end end def test_string_to_time_utc_offset with_env_tz "US/Eastern" do - assert_equal 0, "2005-02-27 23:50".to_time(:utc).utc_offset - assert_equal(-18000, "2005-02-27 23:50".to_time.utc_offset) - assert_equal 0, "2005-02-27 22:50 -0100".to_time(:utc).utc_offset - assert_equal(-18000, "2005-02-27 22:50 -0100".to_time.utc_offset) + if ActiveSupport.to_time_preserves_timezone + assert_equal 0, "2005-02-27 23:50".to_time(:utc).utc_offset + assert_equal(-18000, "2005-02-27 23:50".to_time.utc_offset) + assert_equal 0, "2005-02-27 22:50 -0100".to_time(:utc).utc_offset + assert_equal(-3600, "2005-02-27 22:50 -0100".to_time.utc_offset) + else + assert_equal 0, "2005-02-27 23:50".to_time(:utc).utc_offset + assert_equal(-18000, "2005-02-27 23:50".to_time.utc_offset) + assert_equal 0, "2005-02-27 22:50 -0100".to_time(:utc).utc_offset + assert_equal(-18000, "2005-02-27 22:50 -0100".to_time.utc_offset) + end end end @@ -612,14 +633,14 @@ end class StringBehaviourTest < ActiveSupport::TestCase def test_acts_like_string - assert 'Bambi'.acts_like_string? + assert "Bambi".acts_like_string? end end class CoreExtStringMultibyteTest < ActiveSupport::TestCase - UTF8_STRING = 'ã“ã«ã¡ã‚' - ASCII_STRING = 'ohayo'.encode('US-ASCII') - EUC_JP_STRING = 'ã•よãªã‚‰'.encode('EUC-JP') + UTF8_STRING = "ã“ã«ã¡ã‚" + ASCII_STRING = "ohayo".encode("US-ASCII") + EUC_JP_STRING = "ã•よãªã‚‰".encode("EUC-JP") INVALID_UTF8_STRING = "\270\236\010\210\245" def test_core_ext_adds_mb_chars @@ -661,7 +682,7 @@ class OutputSafetyTest < ActiveSupport::TestCase assert_equal @string, @string.html_safe end - test "A fixnum is safe by default" do + test "An integer is safe by default" do assert 5.html_safe? end @@ -792,15 +813,15 @@ class OutputSafetyTest < ActiveSupport::TestCase assert_equal ["<p>", "<b>", "<h1>"], @other_string end - test "Concatting a fixnum to safe always yields safe" do + test "Concatting an integer to safe always yields safe" do string = @string.html_safe string = string.concat(13) assert_equal "hello".concat(13), string assert string.html_safe? end - test 'emits normal string yaml' do - assert_equal 'foo'.to_yaml, 'foo'.html_safe.to_yaml(:foo => 1) + test "emits normal string yaml" do + assert_equal "foo".to_yaml, "foo".html_safe.to_yaml(foo: 1) end test "call to_param returns a normal string" do @@ -811,7 +832,7 @@ class OutputSafetyTest < ActiveSupport::TestCase test "ERB::Util.html_escape should escape unsafe characters" do string = '<>&"\'' - expected = '<>&"'' + expected = "<>&"'" assert_equal expected, ERB::Util.html_escape(string) end @@ -827,7 +848,7 @@ class OutputSafetyTest < ActiveSupport::TestCase end test "ERB::Util.html_escape_once only escapes once" do - string = '1 < 2 & 3' + string = "1 < 2 & 3" escaped_string = "1 < 2 & 3" assert_equal escaped_string, ERB::Util.html_escape_once(string) @@ -842,15 +863,15 @@ class OutputSafetyTest < ActiveSupport::TestCase end class StringExcludeTest < ActiveSupport::TestCase - test 'inverse of #include' do - assert_equal false, 'foo'.exclude?('o') - assert_equal true, 'foo'.exclude?('p') + test "inverse of #include" do + assert_equal false, "foo".exclude?("o") + assert_equal true, "foo".exclude?("p") end end class StringIndentTest < ActiveSupport::TestCase - test 'does not indent strings that only contain newlines (edge cases)' do - ['', "\n", "\n" * 7].each do |str| + test "does not indent strings that only contain newlines (edge cases)" do + ["", "\n", "\n" * 7].each do |str| assert_nil str.indent!(8) assert_equal str, str.indent(8) assert_equal str, str.indent(1, "\t") @@ -872,8 +893,8 @@ class StringIndentTest < ActiveSupport::TestCase # Nothing is said about existing indentation that mixes spaces and tabs, so # there is nothing to test. - test 'uses the indent char if passed' do - assert_equal <<EXPECTED, <<ACTUAL.indent(4, '.') + test "uses the indent char if passed" do + assert_equal <<EXPECTED, <<ACTUAL.indent(4, ".") .... def some_method(x, y) .... some_code .... end @@ -883,7 +904,7 @@ EXPECTED end ACTUAL - assert_equal <<EXPECTED, <<ACTUAL.indent(2, ' ') + assert_equal <<EXPECTED, <<ACTUAL.indent(2, " ") def some_method(x, y) some_code end @@ -898,7 +919,7 @@ ACTUAL assert_equal " foo\n\n bar", "foo\n\nbar".indent(1) end - test 'indents blank lines if told so' do + test "indents blank lines if told so" do assert_equal " foo\n \n bar", "foo\n\nbar".indent(1, nil, true) end end diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index e45df63fd5..a6c9a7d5a8 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -1,7 +1,7 @@ -require 'abstract_unit' -require 'active_support/time' -require 'core_ext/date_and_time_behavior' -require 'time_zone_test_helpers' +require "abstract_unit" +require "active_support/time" +require "core_ext/date_and_time_behavior" +require "time_zone_test_helpers" class TimeExtCalculationsTest < ActiveSupport::TestCase def date_time_init(year,month,day,hour,minute,second,usec=0) @@ -20,42 +20,42 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_seconds_since_midnight_at_daylight_savings_time_start - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do # dt: US: 2005 April 3rd 2:00am ST => April 3rd 3:00am DT - assert_equal 2*3600-1, Time.local(2005,4,3,1,59,59).seconds_since_midnight, 'just before DST start' - assert_equal 2*3600+1, Time.local(2005,4,3,3, 0, 1).seconds_since_midnight, 'just after DST start' + assert_equal 2*3600-1, Time.local(2005,4,3,1,59,59).seconds_since_midnight, "just before DST start" + assert_equal 2*3600+1, Time.local(2005,4,3,3, 0, 1).seconds_since_midnight, "just after DST start" end - with_env_tz 'NZ' do + with_env_tz "NZ" do # dt: New Zealand: 2006 October 1st 2:00am ST => October 1st 3:00am DT - assert_equal 2*3600-1, Time.local(2006,10,1,1,59,59).seconds_since_midnight, 'just before DST start' - assert_equal 2*3600+1, Time.local(2006,10,1,3, 0, 1).seconds_since_midnight, 'just after DST start' + assert_equal 2*3600-1, Time.local(2006,10,1,1,59,59).seconds_since_midnight, "just before DST start" + assert_equal 2*3600+1, Time.local(2006,10,1,3, 0, 1).seconds_since_midnight, "just after DST start" end end def test_seconds_since_midnight_at_daylight_savings_time_end - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do # st: US: 2005 October 30th 2:00am DT => October 30th 1:00am ST # avoid setting a time between 1:00 and 2:00 since that requires specifying whether DST is active - assert_equal 1*3600-1, Time.local(2005,10,30,0,59,59).seconds_since_midnight, 'just before DST end' - assert_equal 3*3600+1, Time.local(2005,10,30,2, 0, 1).seconds_since_midnight, 'just after DST end' + assert_equal 1*3600-1, Time.local(2005,10,30,0,59,59).seconds_since_midnight, "just before DST end" + assert_equal 3*3600+1, Time.local(2005,10,30,2, 0, 1).seconds_since_midnight, "just after DST end" # now set a time between 1:00 and 2:00 by specifying whether DST is active # uses: Time.local( sec, min, hour, day, month, year, wday, yday, isdst, tz ) - assert_equal 1*3600+30*60, Time.local(0,30,1,30,10,2005,0,0,true,ENV['TZ']).seconds_since_midnight, 'before DST end' - assert_equal 2*3600+30*60, Time.local(0,30,1,30,10,2005,0,0,false,ENV['TZ']).seconds_since_midnight, 'after DST end' + assert_equal 1*3600+30*60, Time.local(0,30,1,30,10,2005,0,0,true,ENV["TZ"]).seconds_since_midnight, "before DST end" + assert_equal 2*3600+30*60, Time.local(0,30,1,30,10,2005,0,0,false,ENV["TZ"]).seconds_since_midnight, "after DST end" end - with_env_tz 'NZ' do + with_env_tz "NZ" do # st: New Zealand: 2006 March 19th 3:00am DT => March 19th 2:00am ST # avoid setting a time between 2:00 and 3:00 since that requires specifying whether DST is active - assert_equal 2*3600-1, Time.local(2006,3,19,1,59,59).seconds_since_midnight, 'just before DST end' - assert_equal 4*3600+1, Time.local(2006,3,19,3, 0, 1).seconds_since_midnight, 'just after DST end' + assert_equal 2*3600-1, Time.local(2006,3,19,1,59,59).seconds_since_midnight, "just before DST end" + assert_equal 4*3600+1, Time.local(2006,3,19,3, 0, 1).seconds_since_midnight, "just after DST end" # now set a time between 2:00 and 3:00 by specifying whether DST is active # uses: Time.local( sec, min, hour, day, month, year, wday, yday, isdst, tz ) - assert_equal 2*3600+30*60, Time.local(0,30,2,19,3,2006,0,0,true, ENV['TZ']).seconds_since_midnight, 'before DST end' - assert_equal 3*3600+30*60, Time.local(0,30,2,19,3,2006,0,0,false,ENV['TZ']).seconds_since_midnight, 'after DST end' + assert_equal 2*3600+30*60, Time.local(0,30,2,19,3,2006,0,0,true, ENV["TZ"]).seconds_since_midnight, "before DST end" + assert_equal 3*3600+30*60, Time.local(0,30,2,19,3,2006,0,0,false,ENV["TZ"]).seconds_since_midnight, "after DST end" end end @@ -68,66 +68,80 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_seconds_until_end_of_day_at_daylight_savings_time_start - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do # dt: US: 2005 April 3rd 2:00am ST => April 3rd 3:00am DT - assert_equal 21*3600, Time.local(2005,4,3,1,59,59).seconds_until_end_of_day, 'just before DST start' - assert_equal 21*3600-2, Time.local(2005,4,3,3,0,1).seconds_until_end_of_day, 'just after DST start' + assert_equal 21*3600, Time.local(2005,4,3,1,59,59).seconds_until_end_of_day, "just before DST start" + assert_equal 21*3600-2, Time.local(2005,4,3,3,0,1).seconds_until_end_of_day, "just after DST start" end - with_env_tz 'NZ' do + with_env_tz "NZ" do # dt: New Zealand: 2006 October 1st 2:00am ST => October 1st 3:00am DT - assert_equal 21*3600, Time.local(2006,10,1,1,59,59).seconds_until_end_of_day, 'just before DST start' - assert_equal 21*3600-2, Time.local(2006,10,1,3,0,1).seconds_until_end_of_day, 'just after DST start' + assert_equal 21*3600, Time.local(2006,10,1,1,59,59).seconds_until_end_of_day, "just before DST start" + assert_equal 21*3600-2, Time.local(2006,10,1,3,0,1).seconds_until_end_of_day, "just after DST start" end end def test_seconds_until_end_of_day_at_daylight_savings_time_end - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do # st: US: 2005 October 30th 2:00am DT => October 30th 1:00am ST # avoid setting a time between 1:00 and 2:00 since that requires specifying whether DST is active - assert_equal 24*3600, Time.local(2005,10,30,0,59,59).seconds_until_end_of_day, 'just before DST end' - assert_equal 22*3600-2, Time.local(2005,10,30,2,0,1).seconds_until_end_of_day, 'just after DST end' + assert_equal 24*3600, Time.local(2005,10,30,0,59,59).seconds_until_end_of_day, "just before DST end" + assert_equal 22*3600-2, Time.local(2005,10,30,2,0,1).seconds_until_end_of_day, "just after DST end" # now set a time between 1:00 and 2:00 by specifying whether DST is active # uses: Time.local( sec, min, hour, day, month, year, wday, yday, isdst, tz ) - assert_equal 24*3600-30*60-1, Time.local(0,30,1,30,10,2005,0,0,true,ENV['TZ']).seconds_until_end_of_day, 'before DST end' - assert_equal 23*3600-30*60-1, Time.local(0,30,1,30,10,2005,0,0,false,ENV['TZ']).seconds_until_end_of_day, 'after DST end' + assert_equal 24*3600-30*60-1, Time.local(0,30,1,30,10,2005,0,0,true,ENV["TZ"]).seconds_until_end_of_day, "before DST end" + assert_equal 23*3600-30*60-1, Time.local(0,30,1,30,10,2005,0,0,false,ENV["TZ"]).seconds_until_end_of_day, "after DST end" end - with_env_tz 'NZ' do + with_env_tz "NZ" do # st: New Zealand: 2006 March 19th 3:00am DT => March 19th 2:00am ST # avoid setting a time between 2:00 and 3:00 since that requires specifying whether DST is active - assert_equal 23*3600, Time.local(2006,3,19,1,59,59).seconds_until_end_of_day, 'just before DST end' - assert_equal 21*3600-2, Time.local(2006,3,19,3,0,1).seconds_until_end_of_day, 'just after DST end' + assert_equal 23*3600, Time.local(2006,3,19,1,59,59).seconds_until_end_of_day, "just before DST end" + assert_equal 21*3600-2, Time.local(2006,3,19,3,0,1).seconds_until_end_of_day, "just after DST end" # now set a time between 2:00 and 3:00 by specifying whether DST is active # uses: Time.local( sec, min, hour, day, month, year, wday, yday, isdst, tz ) - assert_equal 23*3600-30*60-1, Time.local(0,30,2,19,3,2006,0,0,true, ENV['TZ']).seconds_until_end_of_day, 'before DST end' - assert_equal 22*3600-30*60-1, Time.local(0,30,2,19,3,2006,0,0,false,ENV['TZ']).seconds_until_end_of_day, 'after DST end' + assert_equal 23*3600-30*60-1, Time.local(0,30,2,19,3,2006,0,0,true, ENV["TZ"]).seconds_until_end_of_day, "before DST end" + assert_equal 22*3600-30*60-1, Time.local(0,30,2,19,3,2006,0,0,false,ENV["TZ"]).seconds_until_end_of_day, "after DST end" end end + def test_sec_fraction + time = Time.utc(2016, 4, 23, 0, 0, Rational(1,10000000000)) + assert_equal Rational(1,10000000000), time.sec_fraction + + time = Time.utc(2016, 4, 23, 0, 0, 0.0000000001) + assert_equal 0.0000000001.to_r, time.sec_fraction + + time = Time.utc(2016, 4, 23, 0, 0, 0, Rational(1,10000)) + assert_equal Rational(1,10000000000), time.sec_fraction + + time = Time.utc(2016, 4, 23, 0, 0, 0, 0.0001) + assert_equal 0.0001.to_r / 1000000, time.sec_fraction + end + def test_beginning_of_day assert_equal Time.local(2005,2,4,0,0,0), Time.local(2005,2,4,10,10,10).beginning_of_day - with_env_tz 'US/Eastern' do - assert_equal Time.local(2006,4,2,0,0,0), Time.local(2006,4,2,10,10,10).beginning_of_day, 'start DST' - assert_equal Time.local(2006,10,29,0,0,0), Time.local(2006,10,29,10,10,10).beginning_of_day, 'ends DST' + with_env_tz "US/Eastern" do + assert_equal Time.local(2006,4,2,0,0,0), Time.local(2006,4,2,10,10,10).beginning_of_day, "start DST" + assert_equal Time.local(2006,10,29,0,0,0), Time.local(2006,10,29,10,10,10).beginning_of_day, "ends DST" end - with_env_tz 'NZ' do - assert_equal Time.local(2006,3,19,0,0,0), Time.local(2006,3,19,10,10,10).beginning_of_day, 'ends DST' - assert_equal Time.local(2006,10,1,0,0,0), Time.local(2006,10,1,10,10,10).beginning_of_day, 'start DST' + with_env_tz "NZ" do + assert_equal Time.local(2006,3,19,0,0,0), Time.local(2006,3,19,10,10,10).beginning_of_day, "ends DST" + assert_equal Time.local(2006,10,1,0,0,0), Time.local(2006,10,1,10,10,10).beginning_of_day, "start DST" end end def test_middle_of_day assert_equal Time.local(2005,2,4,12,0,0), Time.local(2005,2,4,10,10,10).middle_of_day - with_env_tz 'US/Eastern' do - assert_equal Time.local(2006,4,2,12,0,0), Time.local(2006,4,2,10,10,10).middle_of_day, 'start DST' - assert_equal Time.local(2006,10,29,12,0,0), Time.local(2006,10,29,10,10,10).middle_of_day, 'ends DST' + with_env_tz "US/Eastern" do + assert_equal Time.local(2006,4,2,12,0,0), Time.local(2006,4,2,10,10,10).middle_of_day, "start DST" + assert_equal Time.local(2006,10,29,12,0,0), Time.local(2006,10,29,10,10,10).middle_of_day, "ends DST" end - with_env_tz 'NZ' do - assert_equal Time.local(2006,3,19,12,0,0), Time.local(2006,3,19,10,10,10).middle_of_day, 'ends DST' - assert_equal Time.local(2006,10,1,12,0,0), Time.local(2006,10,1,10,10,10).middle_of_day, 'start DST' + with_env_tz "NZ" do + assert_equal Time.local(2006,3,19,12,0,0), Time.local(2006,3,19,10,10,10).middle_of_day, "ends DST" + assert_equal Time.local(2006,10,1,12,0,0), Time.local(2006,10,1,10,10,10).middle_of_day, "start DST" end end @@ -141,16 +155,16 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase def test_end_of_day assert_equal Time.local(2007,8,12,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,12,10,10,10).end_of_day - with_env_tz 'US/Eastern' do - assert_equal Time.local(2007,4,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,4,2,10,10,10).end_of_day, 'start DST' - assert_equal Time.local(2007,10,29,23,59,59,Rational(999999999, 1000)), Time.local(2007,10,29,10,10,10).end_of_day, 'ends DST' + with_env_tz "US/Eastern" do + assert_equal Time.local(2007,4,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,4,2,10,10,10).end_of_day, "start DST" + assert_equal Time.local(2007,10,29,23,59,59,Rational(999999999, 1000)), Time.local(2007,10,29,10,10,10).end_of_day, "ends DST" end - with_env_tz 'NZ' do - assert_equal Time.local(2006,3,19,23,59,59,Rational(999999999, 1000)), Time.local(2006,3,19,10,10,10).end_of_day, 'ends DST' - assert_equal Time.local(2006,10,1,23,59,59,Rational(999999999, 1000)), Time.local(2006,10,1,10,10,10).end_of_day, 'start DST' + with_env_tz "NZ" do + assert_equal Time.local(2006,3,19,23,59,59,Rational(999999999, 1000)), Time.local(2006,3,19,10,10,10).end_of_day, "ends DST" + assert_equal Time.local(2006,10,1,23,59,59,Rational(999999999, 1000)), Time.local(2006,10,1,10,10,10).end_of_day, "start DST" end - with_env_tz 'Asia/Yekaterinburg' do - assert_equal Time.local(2015, 2, 8, 23, 59, 59, Rational(999999999, 1000)), Time.new(2015, 2, 8, 8, 0, 0, '+05:00').end_of_day + with_env_tz "Asia/Yekaterinburg" do + assert_equal Time.local(2015, 2, 8, 23, 59, 59, Rational(999999999, 1000)), Time.new(2015, 2, 8, 8, 0, 0, "+05:00").end_of_day end end @@ -174,74 +188,74 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_daylight_savings_time_crossings_backward_start - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do # dt: US: 2005 April 3rd 4:18am - assert_equal Time.local(2005,4,2,3,18,0), Time.local(2005,4,3,4,18,0).ago(24.hours), 'dt-24.hours=>st' - assert_equal Time.local(2005,4,2,3,18,0), Time.local(2005,4,3,4,18,0).ago(86400), 'dt-86400=>st' - assert_equal Time.local(2005,4,2,3,18,0), Time.local(2005,4,3,4,18,0).ago(86400.seconds), 'dt-86400.seconds=>st' + assert_equal Time.local(2005,4,2,3,18,0), Time.local(2005,4,3,4,18,0).ago(24.hours), "dt-24.hours=>st" + assert_equal Time.local(2005,4,2,3,18,0), Time.local(2005,4,3,4,18,0).ago(86400), "dt-86400=>st" + assert_equal Time.local(2005,4,2,3,18,0), Time.local(2005,4,3,4,18,0).ago(86400.seconds), "dt-86400.seconds=>st" - assert_equal Time.local(2005,4,1,4,18,0), Time.local(2005,4,2,4,18,0).ago(24.hours), 'st-24.hours=>st' - assert_equal Time.local(2005,4,1,4,18,0), Time.local(2005,4,2,4,18,0).ago(86400), 'st-86400=>st' - assert_equal Time.local(2005,4,1,4,18,0), Time.local(2005,4,2,4,18,0).ago(86400.seconds), 'st-86400.seconds=>st' + assert_equal Time.local(2005,4,1,4,18,0), Time.local(2005,4,2,4,18,0).ago(24.hours), "st-24.hours=>st" + assert_equal Time.local(2005,4,1,4,18,0), Time.local(2005,4,2,4,18,0).ago(86400), "st-86400=>st" + assert_equal Time.local(2005,4,1,4,18,0), Time.local(2005,4,2,4,18,0).ago(86400.seconds), "st-86400.seconds=>st" end - with_env_tz 'NZ' do + with_env_tz "NZ" do # dt: New Zealand: 2006 October 1st 4:18am - assert_equal Time.local(2006,9,30,3,18,0), Time.local(2006,10,1,4,18,0).ago(24.hours), 'dt-24.hours=>st' - assert_equal Time.local(2006,9,30,3,18,0), Time.local(2006,10,1,4,18,0).ago(86400), 'dt-86400=>st' - assert_equal Time.local(2006,9,30,3,18,0), Time.local(2006,10,1,4,18,0).ago(86400.seconds), 'dt-86400.seconds=>st' + assert_equal Time.local(2006,9,30,3,18,0), Time.local(2006,10,1,4,18,0).ago(24.hours), "dt-24.hours=>st" + assert_equal Time.local(2006,9,30,3,18,0), Time.local(2006,10,1,4,18,0).ago(86400), "dt-86400=>st" + assert_equal Time.local(2006,9,30,3,18,0), Time.local(2006,10,1,4,18,0).ago(86400.seconds), "dt-86400.seconds=>st" - assert_equal Time.local(2006,9,29,4,18,0), Time.local(2006,9,30,4,18,0).ago(24.hours), 'st-24.hours=>st' - assert_equal Time.local(2006,9,29,4,18,0), Time.local(2006,9,30,4,18,0).ago(86400), 'st-86400=>st' - assert_equal Time.local(2006,9,29,4,18,0), Time.local(2006,9,30,4,18,0).ago(86400.seconds), 'st-86400.seconds=>st' + assert_equal Time.local(2006,9,29,4,18,0), Time.local(2006,9,30,4,18,0).ago(24.hours), "st-24.hours=>st" + assert_equal Time.local(2006,9,29,4,18,0), Time.local(2006,9,30,4,18,0).ago(86400), "st-86400=>st" + assert_equal Time.local(2006,9,29,4,18,0), Time.local(2006,9,30,4,18,0).ago(86400.seconds), "st-86400.seconds=>st" end end def test_daylight_savings_time_crossings_backward_end - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do # st: US: 2005 October 30th 4:03am - assert_equal Time.local(2005,10,29,5,3), Time.local(2005,10,30,4,3,0).ago(24.hours), 'st-24.hours=>dt' - assert_equal Time.local(2005,10,29,5,3), Time.local(2005,10,30,4,3,0).ago(86400), 'st-86400=>dt' - assert_equal Time.local(2005,10,29,5,3), Time.local(2005,10,30,4,3,0).ago(86400.seconds), 'st-86400.seconds=>dt' + assert_equal Time.local(2005,10,29,5,3), Time.local(2005,10,30,4,3,0).ago(24.hours), "st-24.hours=>dt" + assert_equal Time.local(2005,10,29,5,3), Time.local(2005,10,30,4,3,0).ago(86400), "st-86400=>dt" + assert_equal Time.local(2005,10,29,5,3), Time.local(2005,10,30,4,3,0).ago(86400.seconds), "st-86400.seconds=>dt" - assert_equal Time.local(2005,10,28,4,3), Time.local(2005,10,29,4,3,0).ago(24.hours), 'dt-24.hours=>dt' - assert_equal Time.local(2005,10,28,4,3), Time.local(2005,10,29,4,3,0).ago(86400), 'dt-86400=>dt' - assert_equal Time.local(2005,10,28,4,3), Time.local(2005,10,29,4,3,0).ago(86400.seconds), 'dt-86400.seconds=>dt' + assert_equal Time.local(2005,10,28,4,3), Time.local(2005,10,29,4,3,0).ago(24.hours), "dt-24.hours=>dt" + assert_equal Time.local(2005,10,28,4,3), Time.local(2005,10,29,4,3,0).ago(86400), "dt-86400=>dt" + assert_equal Time.local(2005,10,28,4,3), Time.local(2005,10,29,4,3,0).ago(86400.seconds), "dt-86400.seconds=>dt" end - with_env_tz 'NZ' do + with_env_tz "NZ" do # st: New Zealand: 2006 March 19th 4:03am - assert_equal Time.local(2006,3,18,5,3), Time.local(2006,3,19,4,3,0).ago(24.hours), 'st-24.hours=>dt' - assert_equal Time.local(2006,3,18,5,3), Time.local(2006,3,19,4,3,0).ago(86400), 'st-86400=>dt' - assert_equal Time.local(2006,3,18,5,3), Time.local(2006,3,19,4,3,0).ago(86400.seconds), 'st-86400.seconds=>dt' + assert_equal Time.local(2006,3,18,5,3), Time.local(2006,3,19,4,3,0).ago(24.hours), "st-24.hours=>dt" + assert_equal Time.local(2006,3,18,5,3), Time.local(2006,3,19,4,3,0).ago(86400), "st-86400=>dt" + assert_equal Time.local(2006,3,18,5,3), Time.local(2006,3,19,4,3,0).ago(86400.seconds), "st-86400.seconds=>dt" - assert_equal Time.local(2006,3,17,4,3), Time.local(2006,3,18,4,3,0).ago(24.hours), 'dt-24.hours=>dt' - assert_equal Time.local(2006,3,17,4,3), Time.local(2006,3,18,4,3,0).ago(86400), 'dt-86400=>dt' - assert_equal Time.local(2006,3,17,4,3), Time.local(2006,3,18,4,3,0).ago(86400.seconds), 'dt-86400.seconds=>dt' + assert_equal Time.local(2006,3,17,4,3), Time.local(2006,3,18,4,3,0).ago(24.hours), "dt-24.hours=>dt" + assert_equal Time.local(2006,3,17,4,3), Time.local(2006,3,18,4,3,0).ago(86400), "dt-86400=>dt" + assert_equal Time.local(2006,3,17,4,3), Time.local(2006,3,18,4,3,0).ago(86400.seconds), "dt-86400.seconds=>dt" end end def test_daylight_savings_time_crossings_backward_start_1day - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do # dt: US: 2005 April 3rd 4:18am - assert_equal Time.local(2005,4,2,4,18,0), Time.local(2005,4,3,4,18,0).ago(1.day), 'dt-1.day=>st' - assert_equal Time.local(2005,4,1,4,18,0), Time.local(2005,4,2,4,18,0).ago(1.day), 'st-1.day=>st' + assert_equal Time.local(2005,4,2,4,18,0), Time.local(2005,4,3,4,18,0).ago(1.day), "dt-1.day=>st" + assert_equal Time.local(2005,4,1,4,18,0), Time.local(2005,4,2,4,18,0).ago(1.day), "st-1.day=>st" end - with_env_tz 'NZ' do + with_env_tz "NZ" do # dt: New Zealand: 2006 October 1st 4:18am - assert_equal Time.local(2006,9,30,4,18,0), Time.local(2006,10,1,4,18,0).ago(1.day), 'dt-1.day=>st' - assert_equal Time.local(2006,9,29,4,18,0), Time.local(2006,9,30,4,18,0).ago(1.day), 'st-1.day=>st' + assert_equal Time.local(2006,9,30,4,18,0), Time.local(2006,10,1,4,18,0).ago(1.day), "dt-1.day=>st" + assert_equal Time.local(2006,9,29,4,18,0), Time.local(2006,9,30,4,18,0).ago(1.day), "st-1.day=>st" end end def test_daylight_savings_time_crossings_backward_end_1day - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do # st: US: 2005 October 30th 4:03am - assert_equal Time.local(2005,10,29,4,3), Time.local(2005,10,30,4,3,0).ago(1.day), 'st-1.day=>dt' - assert_equal Time.local(2005,10,28,4,3), Time.local(2005,10,29,4,3,0).ago(1.day), 'dt-1.day=>dt' + assert_equal Time.local(2005,10,29,4,3), Time.local(2005,10,30,4,3,0).ago(1.day), "st-1.day=>dt" + assert_equal Time.local(2005,10,28,4,3), Time.local(2005,10,29,4,3,0).ago(1.day), "dt-1.day=>dt" end - with_env_tz 'NZ' do + with_env_tz "NZ" do # st: New Zealand: 2006 March 19th 4:03am - assert_equal Time.local(2006,3,18,4,3), Time.local(2006,3,19,4,3,0).ago(1.day), 'st-1.day=>dt' - assert_equal Time.local(2006,3,17,4,3), Time.local(2006,3,18,4,3,0).ago(1.day), 'dt-1.day=>dt' + assert_equal Time.local(2006,3,18,4,3), Time.local(2006,3,19,4,3,0).ago(1.day), "st-1.day=>dt" + assert_equal Time.local(2006,3,17,4,3), Time.local(2006,3,18,4,3,0).ago(1.day), "dt-1.day=>dt" end end @@ -255,250 +269,250 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_daylight_savings_time_crossings_forward_start - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do # st: US: 2005 April 2nd 7:27pm - assert_equal Time.local(2005,4,3,20,27,0), Time.local(2005,4,2,19,27,0).since(24.hours), 'st+24.hours=>dt' - assert_equal Time.local(2005,4,3,20,27,0), Time.local(2005,4,2,19,27,0).since(86400), 'st+86400=>dt' - assert_equal Time.local(2005,4,3,20,27,0), Time.local(2005,4,2,19,27,0).since(86400.seconds), 'st+86400.seconds=>dt' + assert_equal Time.local(2005,4,3,20,27,0), Time.local(2005,4,2,19,27,0).since(24.hours), "st+24.hours=>dt" + assert_equal Time.local(2005,4,3,20,27,0), Time.local(2005,4,2,19,27,0).since(86400), "st+86400=>dt" + assert_equal Time.local(2005,4,3,20,27,0), Time.local(2005,4,2,19,27,0).since(86400.seconds), "st+86400.seconds=>dt" - assert_equal Time.local(2005,4,4,19,27,0), Time.local(2005,4,3,19,27,0).since(24.hours), 'dt+24.hours=>dt' - assert_equal Time.local(2005,4,4,19,27,0), Time.local(2005,4,3,19,27,0).since(86400), 'dt+86400=>dt' - assert_equal Time.local(2005,4,4,19,27,0), Time.local(2005,4,3,19,27,0).since(86400.seconds), 'dt+86400.seconds=>dt' + assert_equal Time.local(2005,4,4,19,27,0), Time.local(2005,4,3,19,27,0).since(24.hours), "dt+24.hours=>dt" + assert_equal Time.local(2005,4,4,19,27,0), Time.local(2005,4,3,19,27,0).since(86400), "dt+86400=>dt" + assert_equal Time.local(2005,4,4,19,27,0), Time.local(2005,4,3,19,27,0).since(86400.seconds), "dt+86400.seconds=>dt" end - with_env_tz 'NZ' do + with_env_tz "NZ" do # st: New Zealand: 2006 September 30th 7:27pm - assert_equal Time.local(2006,10,1,20,27,0), Time.local(2006,9,30,19,27,0).since(24.hours), 'st+24.hours=>dt' - assert_equal Time.local(2006,10,1,20,27,0), Time.local(2006,9,30,19,27,0).since(86400), 'st+86400=>dt' - assert_equal Time.local(2006,10,1,20,27,0), Time.local(2006,9,30,19,27,0).since(86400.seconds), 'st+86400.seconds=>dt' + assert_equal Time.local(2006,10,1,20,27,0), Time.local(2006,9,30,19,27,0).since(24.hours), "st+24.hours=>dt" + assert_equal Time.local(2006,10,1,20,27,0), Time.local(2006,9,30,19,27,0).since(86400), "st+86400=>dt" + assert_equal Time.local(2006,10,1,20,27,0), Time.local(2006,9,30,19,27,0).since(86400.seconds), "st+86400.seconds=>dt" - assert_equal Time.local(2006,10,2,19,27,0), Time.local(2006,10,1,19,27,0).since(24.hours), 'dt+24.hours=>dt' - assert_equal Time.local(2006,10,2,19,27,0), Time.local(2006,10,1,19,27,0).since(86400), 'dt+86400=>dt' - assert_equal Time.local(2006,10,2,19,27,0), Time.local(2006,10,1,19,27,0).since(86400.seconds), 'dt+86400.seconds=>dt' + assert_equal Time.local(2006,10,2,19,27,0), Time.local(2006,10,1,19,27,0).since(24.hours), "dt+24.hours=>dt" + assert_equal Time.local(2006,10,2,19,27,0), Time.local(2006,10,1,19,27,0).since(86400), "dt+86400=>dt" + assert_equal Time.local(2006,10,2,19,27,0), Time.local(2006,10,1,19,27,0).since(86400.seconds), "dt+86400.seconds=>dt" end end def test_daylight_savings_time_crossings_forward_start_1day - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do # st: US: 2005 April 2nd 7:27pm - assert_equal Time.local(2005,4,3,19,27,0), Time.local(2005,4,2,19,27,0).since(1.day), 'st+1.day=>dt' - assert_equal Time.local(2005,4,4,19,27,0), Time.local(2005,4,3,19,27,0).since(1.day), 'dt+1.day=>dt' + assert_equal Time.local(2005,4,3,19,27,0), Time.local(2005,4,2,19,27,0).since(1.day), "st+1.day=>dt" + assert_equal Time.local(2005,4,4,19,27,0), Time.local(2005,4,3,19,27,0).since(1.day), "dt+1.day=>dt" end - with_env_tz 'NZ' do + with_env_tz "NZ" do # st: New Zealand: 2006 September 30th 7:27pm - assert_equal Time.local(2006,10,1,19,27,0), Time.local(2006,9,30,19,27,0).since(1.day), 'st+1.day=>dt' - assert_equal Time.local(2006,10,2,19,27,0), Time.local(2006,10,1,19,27,0).since(1.day), 'dt+1.day=>dt' + assert_equal Time.local(2006,10,1,19,27,0), Time.local(2006,9,30,19,27,0).since(1.day), "st+1.day=>dt" + assert_equal Time.local(2006,10,2,19,27,0), Time.local(2006,10,1,19,27,0).since(1.day), "dt+1.day=>dt" end end def test_daylight_savings_time_crossings_forward_start_tomorrow - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do # st: US: 2005 April 2nd 7:27pm - assert_equal Time.local(2005,4,3,19,27,0), Time.local(2005,4,2,19,27,0).tomorrow, 'st+1.day=>dt' - assert_equal Time.local(2005,4,4,19,27,0), Time.local(2005,4,3,19,27,0).tomorrow, 'dt+1.day=>dt' + assert_equal Time.local(2005,4,3,19,27,0), Time.local(2005,4,2,19,27,0).tomorrow, "st+1.day=>dt" + assert_equal Time.local(2005,4,4,19,27,0), Time.local(2005,4,3,19,27,0).tomorrow, "dt+1.day=>dt" end - with_env_tz 'NZ' do + with_env_tz "NZ" do # st: New Zealand: 2006 September 30th 7:27pm - assert_equal Time.local(2006,10,1,19,27,0), Time.local(2006,9,30,19,27,0).tomorrow, 'st+1.day=>dt' - assert_equal Time.local(2006,10,2,19,27,0), Time.local(2006,10,1,19,27,0).tomorrow, 'dt+1.day=>dt' + assert_equal Time.local(2006,10,1,19,27,0), Time.local(2006,9,30,19,27,0).tomorrow, "st+1.day=>dt" + assert_equal Time.local(2006,10,2,19,27,0), Time.local(2006,10,1,19,27,0).tomorrow, "dt+1.day=>dt" end end def test_daylight_savings_time_crossings_backward_start_yesterday - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do # st: US: 2005 April 2nd 7:27pm - assert_equal Time.local(2005,4,2,19,27,0), Time.local(2005,4,3,19,27,0).yesterday, 'dt-1.day=>st' - assert_equal Time.local(2005,4,3,19,27,0), Time.local(2005,4,4,19,27,0).yesterday, 'dt-1.day=>dt' + assert_equal Time.local(2005,4,2,19,27,0), Time.local(2005,4,3,19,27,0).yesterday, "dt-1.day=>st" + assert_equal Time.local(2005,4,3,19,27,0), Time.local(2005,4,4,19,27,0).yesterday, "dt-1.day=>dt" end - with_env_tz 'NZ' do + with_env_tz "NZ" do # st: New Zealand: 2006 September 30th 7:27pm - assert_equal Time.local(2006,9,30,19,27,0), Time.local(2006,10,1,19,27,0).yesterday, 'dt-1.day=>st' - assert_equal Time.local(2006,10,1,19,27,0), Time.local(2006,10,2,19,27,0).yesterday, 'dt-1.day=>dt' + assert_equal Time.local(2006,9,30,19,27,0), Time.local(2006,10,1,19,27,0).yesterday, "dt-1.day=>st" + assert_equal Time.local(2006,10,1,19,27,0), Time.local(2006,10,2,19,27,0).yesterday, "dt-1.day=>dt" end end def test_daylight_savings_time_crossings_forward_end - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do # dt: US: 2005 October 30th 12:45am - assert_equal Time.local(2005,10,30,23,45,0), Time.local(2005,10,30,0,45,0).since(24.hours), 'dt+24.hours=>st' - assert_equal Time.local(2005,10,30,23,45,0), Time.local(2005,10,30,0,45,0).since(86400), 'dt+86400=>st' - assert_equal Time.local(2005,10,30,23,45,0), Time.local(2005,10,30,0,45,0).since(86400.seconds), 'dt+86400.seconds=>st' + assert_equal Time.local(2005,10,30,23,45,0), Time.local(2005,10,30,0,45,0).since(24.hours), "dt+24.hours=>st" + assert_equal Time.local(2005,10,30,23,45,0), Time.local(2005,10,30,0,45,0).since(86400), "dt+86400=>st" + assert_equal Time.local(2005,10,30,23,45,0), Time.local(2005,10,30,0,45,0).since(86400.seconds), "dt+86400.seconds=>st" - assert_equal Time.local(2005,11, 1,0,45,0), Time.local(2005,10,31,0,45,0).since(24.hours), 'st+24.hours=>st' - assert_equal Time.local(2005,11, 1,0,45,0), Time.local(2005,10,31,0,45,0).since(86400), 'st+86400=>st' - assert_equal Time.local(2005,11, 1,0,45,0), Time.local(2005,10,31,0,45,0).since(86400.seconds), 'st+86400.seconds=>st' + assert_equal Time.local(2005,11, 1,0,45,0), Time.local(2005,10,31,0,45,0).since(24.hours), "st+24.hours=>st" + assert_equal Time.local(2005,11, 1,0,45,0), Time.local(2005,10,31,0,45,0).since(86400), "st+86400=>st" + assert_equal Time.local(2005,11, 1,0,45,0), Time.local(2005,10,31,0,45,0).since(86400.seconds), "st+86400.seconds=>st" end - with_env_tz 'NZ' do + with_env_tz "NZ" do # dt: New Zealand: 2006 March 19th 1:45am - assert_equal Time.local(2006,3,20,0,45,0), Time.local(2006,3,19,1,45,0).since(24.hours), 'dt+24.hours=>st' - assert_equal Time.local(2006,3,20,0,45,0), Time.local(2006,3,19,1,45,0).since(86400), 'dt+86400=>st' - assert_equal Time.local(2006,3,20,0,45,0), Time.local(2006,3,19,1,45,0).since(86400.seconds), 'dt+86400.seconds=>st' + assert_equal Time.local(2006,3,20,0,45,0), Time.local(2006,3,19,1,45,0).since(24.hours), "dt+24.hours=>st" + assert_equal Time.local(2006,3,20,0,45,0), Time.local(2006,3,19,1,45,0).since(86400), "dt+86400=>st" + assert_equal Time.local(2006,3,20,0,45,0), Time.local(2006,3,19,1,45,0).since(86400.seconds), "dt+86400.seconds=>st" - assert_equal Time.local(2006,3,21,1,45,0), Time.local(2006,3,20,1,45,0).since(24.hours), 'st+24.hours=>st' - assert_equal Time.local(2006,3,21,1,45,0), Time.local(2006,3,20,1,45,0).since(86400), 'st+86400=>st' - assert_equal Time.local(2006,3,21,1,45,0), Time.local(2006,3,20,1,45,0).since(86400.seconds), 'st+86400.seconds=>st' + assert_equal Time.local(2006,3,21,1,45,0), Time.local(2006,3,20,1,45,0).since(24.hours), "st+24.hours=>st" + assert_equal Time.local(2006,3,21,1,45,0), Time.local(2006,3,20,1,45,0).since(86400), "st+86400=>st" + assert_equal Time.local(2006,3,21,1,45,0), Time.local(2006,3,20,1,45,0).since(86400.seconds), "st+86400.seconds=>st" end end def test_daylight_savings_time_crossings_forward_end_1day - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do # dt: US: 2005 October 30th 12:45am - assert_equal Time.local(2005,10,31,0,45,0), Time.local(2005,10,30,0,45,0).since(1.day), 'dt+1.day=>st' - assert_equal Time.local(2005,11, 1,0,45,0), Time.local(2005,10,31,0,45,0).since(1.day), 'st+1.day=>st' + assert_equal Time.local(2005,10,31,0,45,0), Time.local(2005,10,30,0,45,0).since(1.day), "dt+1.day=>st" + assert_equal Time.local(2005,11, 1,0,45,0), Time.local(2005,10,31,0,45,0).since(1.day), "st+1.day=>st" end - with_env_tz 'NZ' do + with_env_tz "NZ" do # dt: New Zealand: 2006 March 19th 1:45am - assert_equal Time.local(2006,3,20,1,45,0), Time.local(2006,3,19,1,45,0).since(1.day), 'dt+1.day=>st' - assert_equal Time.local(2006,3,21,1,45,0), Time.local(2006,3,20,1,45,0).since(1.day), 'st+1.day=>st' + assert_equal Time.local(2006,3,20,1,45,0), Time.local(2006,3,19,1,45,0).since(1.day), "dt+1.day=>st" + assert_equal Time.local(2006,3,21,1,45,0), Time.local(2006,3,20,1,45,0).since(1.day), "st+1.day=>st" end end def test_daylight_savings_time_crossings_forward_end_tomorrow - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do # dt: US: 2005 October 30th 12:45am - assert_equal Time.local(2005,10,31,0,45,0), Time.local(2005,10,30,0,45,0).tomorrow, 'dt+1.day=>st' - assert_equal Time.local(2005,11, 1,0,45,0), Time.local(2005,10,31,0,45,0).tomorrow, 'st+1.day=>st' + assert_equal Time.local(2005,10,31,0,45,0), Time.local(2005,10,30,0,45,0).tomorrow, "dt+1.day=>st" + assert_equal Time.local(2005,11, 1,0,45,0), Time.local(2005,10,31,0,45,0).tomorrow, "st+1.day=>st" end - with_env_tz 'NZ' do + with_env_tz "NZ" do # dt: New Zealand: 2006 March 19th 1:45am - assert_equal Time.local(2006,3,20,1,45,0), Time.local(2006,3,19,1,45,0).tomorrow, 'dt+1.day=>st' - assert_equal Time.local(2006,3,21,1,45,0), Time.local(2006,3,20,1,45,0).tomorrow, 'st+1.day=>st' + assert_equal Time.local(2006,3,20,1,45,0), Time.local(2006,3,19,1,45,0).tomorrow, "dt+1.day=>st" + assert_equal Time.local(2006,3,21,1,45,0), Time.local(2006,3,20,1,45,0).tomorrow, "st+1.day=>st" end end def test_daylight_savings_time_crossings_backward_end_yesterday - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do # dt: US: 2005 October 30th 12:45am - assert_equal Time.local(2005,10,30,0,45,0), Time.local(2005,10,31,0,45,0).yesterday, 'st-1.day=>dt' - assert_equal Time.local(2005,10, 31,0,45,0), Time.local(2005,11,1,0,45,0).yesterday, 'st-1.day=>st' + assert_equal Time.local(2005,10,30,0,45,0), Time.local(2005,10,31,0,45,0).yesterday, "st-1.day=>dt" + assert_equal Time.local(2005,10, 31,0,45,0), Time.local(2005,11,1,0,45,0).yesterday, "st-1.day=>st" end - with_env_tz 'NZ' do + with_env_tz "NZ" do # dt: New Zealand: 2006 March 19th 1:45am - assert_equal Time.local(2006,3,19,1,45,0), Time.local(2006,3,20,1,45,0).yesterday, 'st-1.day=>dt' - assert_equal Time.local(2006,3,20,1,45,0), Time.local(2006,3,21,1,45,0).yesterday, 'st-1.day=>st' + assert_equal Time.local(2006,3,19,1,45,0), Time.local(2006,3,20,1,45,0).yesterday, "st-1.day=>dt" + assert_equal Time.local(2006,3,20,1,45,0), Time.local(2006,3,21,1,45,0).yesterday, "st-1.day=>st" end end def test_change - assert_equal Time.local(2006,2,22,15,15,10), Time.local(2005,2,22,15,15,10).change(:year => 2006) - assert_equal Time.local(2005,6,22,15,15,10), Time.local(2005,2,22,15,15,10).change(:month => 6) - assert_equal Time.local(2012,9,22,15,15,10), Time.local(2005,2,22,15,15,10).change(:year => 2012, :month => 9) - assert_equal Time.local(2005,2,22,16), Time.local(2005,2,22,15,15,10).change(:hour => 16) - assert_equal Time.local(2005,2,22,16,45), Time.local(2005,2,22,15,15,10).change(:hour => 16, :min => 45) - assert_equal Time.local(2005,2,22,15,45), Time.local(2005,2,22,15,15,10).change(:min => 45) - - assert_equal Time.local(2005,1,2, 5, 0, 0, 0), Time.local(2005,1,2,11,22,33,44).change(:hour => 5) - assert_equal Time.local(2005,1,2,11, 6, 0, 0), Time.local(2005,1,2,11,22,33,44).change(:min => 6) - assert_equal Time.local(2005,1,2,11,22, 7, 0), Time.local(2005,1,2,11,22,33,44).change(:sec => 7) - assert_equal Time.local(2005,1,2,11,22,33, 8), Time.local(2005,1,2,11,22,33,44).change(:usec => 8) - assert_equal Time.local(2005,1,2,11,22,33, 8), Time.local(2005,1,2,11,22,33,2).change(:nsec => 8000) - assert_raise(ArgumentError) { Time.local(2005,1,2,11,22,33, 8).change(:usec => 1, :nsec => 1) } - assert_nothing_raised(ArgumentError) { Time.new(2015, 5, 9, 10, 00, 00, '+03:00').change(nsec: 999999999) } + assert_equal Time.local(2006,2,22,15,15,10), Time.local(2005,2,22,15,15,10).change(year: 2006) + assert_equal Time.local(2005,6,22,15,15,10), Time.local(2005,2,22,15,15,10).change(month: 6) + assert_equal Time.local(2012,9,22,15,15,10), Time.local(2005,2,22,15,15,10).change(year: 2012, month: 9) + assert_equal Time.local(2005,2,22,16), Time.local(2005,2,22,15,15,10).change(hour: 16) + assert_equal Time.local(2005,2,22,16,45), Time.local(2005,2,22,15,15,10).change(hour: 16, min: 45) + assert_equal Time.local(2005,2,22,15,45), Time.local(2005,2,22,15,15,10).change(min: 45) + + assert_equal Time.local(2005,1,2, 5, 0, 0, 0), Time.local(2005,1,2,11,22,33,44).change(hour: 5) + assert_equal Time.local(2005,1,2,11, 6, 0, 0), Time.local(2005,1,2,11,22,33,44).change(min: 6) + assert_equal Time.local(2005,1,2,11,22, 7, 0), Time.local(2005,1,2,11,22,33,44).change(sec: 7) + assert_equal Time.local(2005,1,2,11,22,33, 8), Time.local(2005,1,2,11,22,33,44).change(usec: 8) + assert_equal Time.local(2005,1,2,11,22,33, 8), Time.local(2005,1,2,11,22,33,2).change(nsec: 8000) + assert_raise(ArgumentError) { Time.local(2005,1,2,11,22,33, 8).change(usec: 1, nsec: 1) } + assert_nothing_raised { Time.new(2015, 5, 9, 10, 00, 00, "+03:00").change(nsec: 999999999) } end def test_utc_change - assert_equal Time.utc(2006,2,22,15,15,10), Time.utc(2005,2,22,15,15,10).change(:year => 2006) - assert_equal Time.utc(2005,6,22,15,15,10), Time.utc(2005,2,22,15,15,10).change(:month => 6) - assert_equal Time.utc(2012,9,22,15,15,10), Time.utc(2005,2,22,15,15,10).change(:year => 2012, :month => 9) - assert_equal Time.utc(2005,2,22,16), Time.utc(2005,2,22,15,15,10).change(:hour => 16) - assert_equal Time.utc(2005,2,22,16,45), Time.utc(2005,2,22,15,15,10).change(:hour => 16, :min => 45) - assert_equal Time.utc(2005,2,22,15,45), Time.utc(2005,2,22,15,15,10).change(:min => 45) - assert_equal Time.utc(2005,1,2,11,22,33,8), Time.utc(2005,1,2,11,22,33,2).change(:nsec => 8000) + assert_equal Time.utc(2006,2,22,15,15,10), Time.utc(2005,2,22,15,15,10).change(year: 2006) + assert_equal Time.utc(2005,6,22,15,15,10), Time.utc(2005,2,22,15,15,10).change(month: 6) + assert_equal Time.utc(2012,9,22,15,15,10), Time.utc(2005,2,22,15,15,10).change(year: 2012, month: 9) + assert_equal Time.utc(2005,2,22,16), Time.utc(2005,2,22,15,15,10).change(hour: 16) + assert_equal Time.utc(2005,2,22,16,45), Time.utc(2005,2,22,15,15,10).change(hour: 16, min: 45) + assert_equal Time.utc(2005,2,22,15,45), Time.utc(2005,2,22,15,15,10).change(min: 45) + assert_equal Time.utc(2005,1,2,11,22,33,8), Time.utc(2005,1,2,11,22,33,2).change(nsec: 8000) end def test_offset_change - assert_equal Time.new(2006,2,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:year => 2006) - assert_equal Time.new(2005,6,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:month => 6) - assert_equal Time.new(2012,9,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:year => 2012, :month => 9) - assert_equal Time.new(2005,2,22,16,0,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:hour => 16) - assert_equal Time.new(2005,2,22,16,45,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:hour => 16, :min => 45) - assert_equal Time.new(2005,2,22,15,45,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:min => 45) - assert_equal Time.new(2005,2,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,0,"-08:00").change(:sec => 10) - assert_equal 10, Time.new(2005,2,22,15,15,0,"-08:00").change(:usec => 10).usec - assert_equal 10, Time.new(2005,2,22,15,15,0,"-08:00").change(:nsec => 10).nsec - assert_raise(ArgumentError) { Time.new(2005, 2, 22, 15, 15, 45, "-08:00").change(:usec => 1000000) } - assert_raise(ArgumentError) { Time.new(2005, 2, 22, 15, 15, 45, "-08:00").change(:nsec => 1000000000) } + assert_equal Time.new(2006,2,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(year: 2006) + assert_equal Time.new(2005,6,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(month: 6) + assert_equal Time.new(2012,9,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(year: 2012, month: 9) + assert_equal Time.new(2005,2,22,16,0,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(hour: 16) + assert_equal Time.new(2005,2,22,16,45,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(hour: 16, min: 45) + assert_equal Time.new(2005,2,22,15,45,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(min: 45) + assert_equal Time.new(2005,2,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,0,"-08:00").change(sec: 10) + assert_equal 10, Time.new(2005,2,22,15,15,0,"-08:00").change(usec: 10).usec + assert_equal 10, Time.new(2005,2,22,15,15,0,"-08:00").change(nsec: 10).nsec + assert_raise(ArgumentError) { Time.new(2005, 2, 22, 15, 15, 45, "-08:00").change(usec: 1000000) } + assert_raise(ArgumentError) { Time.new(2005, 2, 22, 15, 15, 45, "-08:00").change(nsec: 1000000000) } end def test_advance - assert_equal Time.local(2006,2,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(:years => 1) - assert_equal Time.local(2005,6,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(:months => 4) - assert_equal Time.local(2005,3,21,15,15,10), Time.local(2005,2,28,15,15,10).advance(:weeks => 3) - assert_equal Time.local(2005,3,25,3,15,10), Time.local(2005,2,28,15,15,10).advance(:weeks => 3.5) - assert_in_delta Time.local(2005,3,26,12,51,10), Time.local(2005,2,28,15,15,10).advance(:weeks => 3.7), 1 - assert_equal Time.local(2005,3,5,15,15,10), Time.local(2005,2,28,15,15,10).advance(:days => 5) - assert_equal Time.local(2005,3,6,3,15,10), Time.local(2005,2,28,15,15,10).advance(:days => 5.5) - assert_in_delta Time.local(2005,3,6,8,3,10), Time.local(2005,2,28,15,15,10).advance(:days => 5.7), 1 - assert_equal Time.local(2012,9,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(:years => 7, :months => 7) - assert_equal Time.local(2013,10,3,15,15,10), Time.local(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :days => 5) - assert_equal Time.local(2013,10,17,15,15,10), Time.local(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5) - assert_equal Time.local(2001,12,27,15,15,10), Time.local(2005,2,28,15,15,10).advance(:years => -3, :months => -2, :days => -1) - assert_equal Time.local(2005,2,28,15,15,10), Time.local(2004,2,29,15,15,10).advance(:years => 1) #leap day plus one year - assert_equal Time.local(2005,2,28,20,15,10), Time.local(2005,2,28,15,15,10).advance(:hours => 5) - assert_equal Time.local(2005,2,28,15,22,10), Time.local(2005,2,28,15,15,10).advance(:minutes => 7) - assert_equal Time.local(2005,2,28,15,15,19), Time.local(2005,2,28,15,15,10).advance(:seconds => 9) - assert_equal Time.local(2005,2,28,20,22,19), Time.local(2005,2,28,15,15,10).advance(:hours => 5, :minutes => 7, :seconds => 9) - assert_equal Time.local(2005,2,28,10,8,1), Time.local(2005,2,28,15,15,10).advance(:hours => -5, :minutes => -7, :seconds => -9) - assert_equal Time.local(2013,10,17,20,22,19), Time.local(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5, :hours => 5, :minutes => 7, :seconds => 9) + assert_equal Time.local(2006,2,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(years: 1) + assert_equal Time.local(2005,6,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(months: 4) + assert_equal Time.local(2005,3,21,15,15,10), Time.local(2005,2,28,15,15,10).advance(weeks: 3) + assert_equal Time.local(2005,3,25,3,15,10), Time.local(2005,2,28,15,15,10).advance(weeks: 3.5) + assert_in_delta Time.local(2005,3,26,12,51,10), Time.local(2005,2,28,15,15,10).advance(weeks: 3.7), 1 + assert_equal Time.local(2005,3,5,15,15,10), Time.local(2005,2,28,15,15,10).advance(days: 5) + assert_equal Time.local(2005,3,6,3,15,10), Time.local(2005,2,28,15,15,10).advance(days: 5.5) + assert_in_delta Time.local(2005,3,6,8,3,10), Time.local(2005,2,28,15,15,10).advance(days: 5.7), 1 + assert_equal Time.local(2012,9,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(years: 7, months: 7) + assert_equal Time.local(2013,10,3,15,15,10), Time.local(2005,2,28,15,15,10).advance(years: 7, months: 19, days: 5) + assert_equal Time.local(2013,10,17,15,15,10), Time.local(2005,2,28,15,15,10).advance(years: 7, months: 19, weeks: 2, days: 5) + assert_equal Time.local(2001,12,27,15,15,10), Time.local(2005,2,28,15,15,10).advance(years: -3, months: -2, days: -1) + assert_equal Time.local(2005,2,28,15,15,10), Time.local(2004,2,29,15,15,10).advance(years: 1) #leap day plus one year + assert_equal Time.local(2005,2,28,20,15,10), Time.local(2005,2,28,15,15,10).advance(hours: 5) + assert_equal Time.local(2005,2,28,15,22,10), Time.local(2005,2,28,15,15,10).advance(minutes: 7) + assert_equal Time.local(2005,2,28,15,15,19), Time.local(2005,2,28,15,15,10).advance(seconds: 9) + assert_equal Time.local(2005,2,28,20,22,19), Time.local(2005,2,28,15,15,10).advance(hours: 5, minutes: 7, seconds: 9) + assert_equal Time.local(2005,2,28,10,8,1), Time.local(2005,2,28,15,15,10).advance(hours: -5, minutes: -7, seconds: -9) + assert_equal Time.local(2013,10,17,20,22,19), Time.local(2005,2,28,15,15,10).advance(years: 7, months: 19, weeks: 2, days: 5, hours: 5, minutes: 7, seconds: 9) end def test_utc_advance - assert_equal Time.utc(2006,2,22,15,15,10), Time.utc(2005,2,22,15,15,10).advance(:years => 1) - assert_equal Time.utc(2005,6,22,15,15,10), Time.utc(2005,2,22,15,15,10).advance(:months => 4) - assert_equal Time.utc(2005,3,21,15,15,10), Time.utc(2005,2,28,15,15,10).advance(:weeks => 3) - assert_equal Time.utc(2005,3,25,3,15,10), Time.utc(2005,2,28,15,15,10).advance(:weeks => 3.5) - assert_in_delta Time.utc(2005,3,26,12,51,10), Time.utc(2005,2,28,15,15,10).advance(:weeks => 3.7), 1 - assert_equal Time.utc(2005,3,5,15,15,10), Time.utc(2005,2,28,15,15,10).advance(:days => 5) - assert_equal Time.utc(2005,3,6,3,15,10), Time.utc(2005,2,28,15,15,10).advance(:days => 5.5) - assert_in_delta Time.utc(2005,3,6,8,3,10), Time.utc(2005,2,28,15,15,10).advance(:days => 5.7), 1 - assert_equal Time.utc(2012,9,22,15,15,10), Time.utc(2005,2,22,15,15,10).advance(:years => 7, :months => 7) - assert_equal Time.utc(2013,10,3,15,15,10), Time.utc(2005,2,22,15,15,10).advance(:years => 7, :months => 19, :days => 11) - assert_equal Time.utc(2013,10,17,15,15,10), Time.utc(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5) - assert_equal Time.utc(2001,12,27,15,15,10), Time.utc(2005,2,28,15,15,10).advance(:years => -3, :months => -2, :days => -1) - assert_equal Time.utc(2005,2,28,15,15,10), Time.utc(2004,2,29,15,15,10).advance(:years => 1) #leap day plus one year - assert_equal Time.utc(2005,2,28,20,15,10), Time.utc(2005,2,28,15,15,10).advance(:hours => 5) - assert_equal Time.utc(2005,2,28,15,22,10), Time.utc(2005,2,28,15,15,10).advance(:minutes => 7) - assert_equal Time.utc(2005,2,28,15,15,19), Time.utc(2005,2,28,15,15,10).advance(:seconds => 9) - assert_equal Time.utc(2005,2,28,20,22,19), Time.utc(2005,2,28,15,15,10).advance(:hours => 5, :minutes => 7, :seconds => 9) - assert_equal Time.utc(2005,2,28,10,8,1), Time.utc(2005,2,28,15,15,10).advance(:hours => -5, :minutes => -7, :seconds => -9) - assert_equal Time.utc(2013,10,17,20,22,19), Time.utc(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5, :hours => 5, :minutes => 7, :seconds => 9) + assert_equal Time.utc(2006,2,22,15,15,10), Time.utc(2005,2,22,15,15,10).advance(years: 1) + assert_equal Time.utc(2005,6,22,15,15,10), Time.utc(2005,2,22,15,15,10).advance(months: 4) + assert_equal Time.utc(2005,3,21,15,15,10), Time.utc(2005,2,28,15,15,10).advance(weeks: 3) + assert_equal Time.utc(2005,3,25,3,15,10), Time.utc(2005,2,28,15,15,10).advance(weeks: 3.5) + assert_in_delta Time.utc(2005,3,26,12,51,10), Time.utc(2005,2,28,15,15,10).advance(weeks: 3.7), 1 + assert_equal Time.utc(2005,3,5,15,15,10), Time.utc(2005,2,28,15,15,10).advance(days: 5) + assert_equal Time.utc(2005,3,6,3,15,10), Time.utc(2005,2,28,15,15,10).advance(days: 5.5) + assert_in_delta Time.utc(2005,3,6,8,3,10), Time.utc(2005,2,28,15,15,10).advance(days: 5.7), 1 + assert_equal Time.utc(2012,9,22,15,15,10), Time.utc(2005,2,22,15,15,10).advance(years: 7, months: 7) + assert_equal Time.utc(2013,10,3,15,15,10), Time.utc(2005,2,22,15,15,10).advance(years: 7, months: 19, days: 11) + assert_equal Time.utc(2013,10,17,15,15,10), Time.utc(2005,2,28,15,15,10).advance(years: 7, months: 19, weeks: 2, days: 5) + assert_equal Time.utc(2001,12,27,15,15,10), Time.utc(2005,2,28,15,15,10).advance(years: -3, months: -2, days: -1) + assert_equal Time.utc(2005,2,28,15,15,10), Time.utc(2004,2,29,15,15,10).advance(years: 1) #leap day plus one year + assert_equal Time.utc(2005,2,28,20,15,10), Time.utc(2005,2,28,15,15,10).advance(hours: 5) + assert_equal Time.utc(2005,2,28,15,22,10), Time.utc(2005,2,28,15,15,10).advance(minutes: 7) + assert_equal Time.utc(2005,2,28,15,15,19), Time.utc(2005,2,28,15,15,10).advance(seconds: 9) + assert_equal Time.utc(2005,2,28,20,22,19), Time.utc(2005,2,28,15,15,10).advance(hours: 5, minutes: 7, seconds: 9) + assert_equal Time.utc(2005,2,28,10,8,1), Time.utc(2005,2,28,15,15,10).advance(hours: -5, minutes: -7, seconds: -9) + assert_equal Time.utc(2013,10,17,20,22,19), Time.utc(2005,2,28,15,15,10).advance(years: 7, months: 19, weeks: 2, days: 5, hours: 5, minutes: 7, seconds: 9) end def test_offset_advance - assert_equal Time.new(2006,2,22,15,15,10,'-08:00'), Time.new(2005,2,22,15,15,10,'-08:00').advance(:years => 1) - assert_equal Time.new(2005,6,22,15,15,10,'-08:00'), Time.new(2005,2,22,15,15,10,'-08:00').advance(:months => 4) - assert_equal Time.new(2005,3,21,15,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:weeks => 3) - assert_equal Time.new(2005,3,25,3,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:weeks => 3.5) - assert_in_delta Time.new(2005,3,26,12,51,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:weeks => 3.7), 1 - assert_equal Time.new(2005,3,5,15,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:days => 5) - assert_equal Time.new(2005,3,6,3,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:days => 5.5) - assert_in_delta Time.new(2005,3,6,8,3,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:days => 5.7), 1 - assert_equal Time.new(2012,9,22,15,15,10,'-08:00'), Time.new(2005,2,22,15,15,10,'-08:00').advance(:years => 7, :months => 7) - assert_equal Time.new(2013,10,3,15,15,10,'-08:00'), Time.new(2005,2,22,15,15,10,'-08:00').advance(:years => 7, :months => 19, :days => 11) - assert_equal Time.new(2013,10,17,15,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:years => 7, :months => 19, :weeks => 2, :days => 5) - assert_equal Time.new(2001,12,27,15,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:years => -3, :months => -2, :days => -1) - assert_equal Time.new(2005,2,28,15,15,10,'-08:00'), Time.new(2004,2,29,15,15,10,'-08:00').advance(:years => 1) #leap day plus one year - assert_equal Time.new(2005,2,28,20,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:hours => 5) - assert_equal Time.new(2005,2,28,15,22,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:minutes => 7) - assert_equal Time.new(2005,2,28,15,15,19,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:seconds => 9) - assert_equal Time.new(2005,2,28,20,22,19,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:hours => 5, :minutes => 7, :seconds => 9) - assert_equal Time.new(2005,2,28,10,8,1,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:hours => -5, :minutes => -7, :seconds => -9) - assert_equal Time.new(2013,10,17,20,22,19,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:years => 7, :months => 19, :weeks => 2, :days => 5, :hours => 5, :minutes => 7, :seconds => 9) + assert_equal Time.new(2006,2,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").advance(years: 1) + assert_equal Time.new(2005,6,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").advance(months: 4) + assert_equal Time.new(2005,3,21,15,15,10,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(weeks: 3) + assert_equal Time.new(2005,3,25,3,15,10,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(weeks: 3.5) + assert_in_delta Time.new(2005,3,26,12,51,10,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(weeks: 3.7), 1 + assert_equal Time.new(2005,3,5,15,15,10,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(days: 5) + assert_equal Time.new(2005,3,6,3,15,10,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(days: 5.5) + assert_in_delta Time.new(2005,3,6,8,3,10,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(days: 5.7), 1 + assert_equal Time.new(2012,9,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").advance(years: 7, months: 7) + assert_equal Time.new(2013,10,3,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").advance(years: 7, months: 19, days: 11) + assert_equal Time.new(2013,10,17,15,15,10,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(years: 7, months: 19, weeks: 2, days: 5) + assert_equal Time.new(2001,12,27,15,15,10,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(years: -3, months: -2, days: -1) + assert_equal Time.new(2005,2,28,15,15,10,"-08:00"), Time.new(2004,2,29,15,15,10,"-08:00").advance(years: 1) #leap day plus one year + assert_equal Time.new(2005,2,28,20,15,10,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(hours: 5) + assert_equal Time.new(2005,2,28,15,22,10,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(minutes: 7) + assert_equal Time.new(2005,2,28,15,15,19,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(seconds: 9) + assert_equal Time.new(2005,2,28,20,22,19,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(hours: 5, minutes: 7, seconds: 9) + assert_equal Time.new(2005,2,28,10,8,1,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(hours: -5, minutes: -7, seconds: -9) + assert_equal Time.new(2013,10,17,20,22,19,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(years: 7, months: 19, weeks: 2, days: 5, hours: 5, minutes: 7, seconds: 9) end def test_advance_with_nsec t = Time.at(0, Rational(108635108, 1000)) - assert_equal t, t.advance(:months => 0) + assert_equal t, t.advance(months: 0) end def test_advance_gregorian_proleptic - assert_equal Time.local(1582,10,14,15,15,10), Time.local(1582,10,15,15,15,10).advance(:days => -1) - assert_equal Time.local(1582,10,15,15,15,10), Time.local(1582,10,14,15,15,10).advance(:days => 1) - assert_equal Time.local(1582,10,5,15,15,10), Time.local(1582,10,4,15,15,10).advance(:days => 1) - assert_equal Time.local(1582,10,4,15,15,10), Time.local(1582,10,5,15,15,10).advance(:days => -1) + assert_equal Time.local(1582,10,14,15,15,10), Time.local(1582,10,15,15,15,10).advance(days: -1) + assert_equal Time.local(1582,10,15,15,15,10), Time.local(1582,10,14,15,15,10).advance(days: 1) + assert_equal Time.local(1582,10,5,15,15,10), Time.local(1582,10,4,15,15,10).advance(days: 1) + assert_equal Time.local(1582,10,4,15,15,10), Time.local(1582,10,5,15,15,10).advance(days: -1) end def test_last_week - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do assert_equal Time.local(2005,2,21), Time.local(2005,3,1,15,15,10).last_week assert_equal Time.local(2005,2,22), Time.local(2005,3,1,15,15,10).last_week(:tuesday) assert_equal Time.local(2005,2,25), Time.local(2005,3,1,15,15,10).last_week(:friday) @@ -508,20 +522,20 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_next_week_near_daylight_start - with_env_tz 'US/Eastern' do - assert_equal Time.local(2006,4,3), Time.local(2006,4,2,23,1,0).next_week, 'just crossed standard => daylight' + with_env_tz "US/Eastern" do + assert_equal Time.local(2006,4,3), Time.local(2006,4,2,23,1,0).next_week, "just crossed standard => daylight" end - with_env_tz 'NZ' do - assert_equal Time.local(2006,10,2), Time.local(2006,10,1,23,1,0).next_week, 'just crossed standard => daylight' + with_env_tz "NZ" do + assert_equal Time.local(2006,10,2), Time.local(2006,10,1,23,1,0).next_week, "just crossed standard => daylight" end end def test_next_week_near_daylight_end - with_env_tz 'US/Eastern' do - assert_equal Time.local(2006,10,30), Time.local(2006,10,29,23,1,0).next_week, 'just crossed daylight => standard' + with_env_tz "US/Eastern" do + assert_equal Time.local(2006,10,30), Time.local(2006,10,29,23,1,0).next_week, "just crossed daylight => standard" end - with_env_tz 'NZ' do - assert_equal Time.local(2006,3,20), Time.local(2006,3,19,23,1,0).next_week, 'just crossed daylight => standard' + with_env_tz "NZ" do + assert_equal Time.local(2006,3,20), Time.local(2006,3,19,23,1,0).next_week, "just crossed daylight => standard" end end @@ -550,8 +564,8 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_custom_date_format - Time::DATE_FORMATS[:custom] = '%Y%m%d%H%M%S' - assert_equal '20050221143000', Time.local(2005, 2, 21, 14, 30, 0).to_s(:custom) + Time::DATE_FORMATS[:custom] = "%Y%m%d%H%M%S" + assert_equal "20050221143000", Time.local(2005, 2, 21, 14, 30, 0).to_s(:custom) Time::DATE_FORMATS.delete(:custom) end @@ -561,17 +575,17 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase def test_to_datetime assert_equal Time.utc(2005, 2, 21, 17, 44, 30).to_datetime, DateTime.civil(2005, 2, 21, 17, 44, 30, 0) - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do assert_equal Time.local(2005, 2, 21, 17, 44, 30).to_datetime, DateTime.civil(2005, 2, 21, 17, 44, 30, Rational(Time.local(2005, 2, 21, 17, 44, 30).utc_offset, 86400)) end - with_env_tz 'NZ' do + with_env_tz "NZ" do assert_equal Time.local(2005, 2, 21, 17, 44, 30).to_datetime, DateTime.civil(2005, 2, 21, 17, 44, 30, Rational(Time.local(2005, 2, 21, 17, 44, 30).utc_offset, 86400)) end assert_equal ::Date::ITALY, Time.utc(2005, 2, 21, 17, 44, 30).to_datetime.start # use Ruby's default start value end def test_to_time - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do assert_equal Time, Time.local(2005, 2, 21, 17, 44, 30).to_time.class assert_equal Time.local(2005, 2, 21, 17, 44, 30), Time.local(2005, 2, 21, 17, 44, 30).to_time assert_equal Time.local(2005, 2, 21, 17, 44, 30).utc_offset, Time.local(2005, 2, 21, 17, 44, 30).to_time.utc_offset @@ -663,7 +677,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_past_with_time_current_as_time_local - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do Time.stub(:current, Time.local(2005,2,10,15,30,45)) do assert_equal true, Time.local(2005,2,10,15,30,44).past? assert_equal false, Time.local(2005,2,10,15,30,45).past? @@ -676,8 +690,8 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_past_with_time_current_as_time_with_zone - with_env_tz 'US/Eastern' do - twz = Time.utc(2005,2,10,15,30,45).in_time_zone('Central Time (US & Canada)') + with_env_tz "US/Eastern" do + twz = Time.utc(2005,2,10,15,30,45).in_time_zone("Central Time (US & Canada)") Time.stub(:current, twz) do assert_equal true, Time.local(2005,2,10,10,30,44).past? assert_equal false, Time.local(2005,2,10,10,30,45).past? @@ -690,7 +704,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_future_with_time_current_as_time_local - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do Time.stub(:current, Time.local(2005,2,10,15,30,45)) do assert_equal false, Time.local(2005,2,10,15,30,44).future? assert_equal false, Time.local(2005,2,10,15,30,45).future? @@ -703,8 +717,8 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_future_with_time_current_as_time_with_zone - with_env_tz 'US/Eastern' do - twz = Time.utc(2005,2,10,15,30,45).in_time_zone('Central Time (US & Canada)') + with_env_tz "US/Eastern" do + twz = Time.utc(2005,2,10,15,30,45).in_time_zone("Central Time (US & Canada)") Time.stub(:current, twz) do assert_equal false, Time.local(2005,2,10,10,30,44).future? assert_equal false, Time.local(2005,2,10,10,30,45).future? @@ -721,17 +735,17 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_formatted_offset_with_utc - assert_equal '+00:00', Time.utc(2000).formatted_offset - assert_equal '+0000', Time.utc(2000).formatted_offset(false) - assert_equal 'UTC', Time.utc(2000).formatted_offset(true, 'UTC') + assert_equal "+00:00", Time.utc(2000).formatted_offset + assert_equal "+0000", Time.utc(2000).formatted_offset(false) + assert_equal "UTC", Time.utc(2000).formatted_offset(true, "UTC") end def test_formatted_offset_with_local - with_env_tz 'US/Eastern' do - assert_equal '-05:00', Time.local(2000).formatted_offset - assert_equal '-0500', Time.local(2000).formatted_offset(false) - assert_equal '-04:00', Time.local(2000, 7).formatted_offset - assert_equal '-0400', Time.local(2000, 7).formatted_offset(false) + with_env_tz "US/Eastern" do + assert_equal "-05:00", Time.local(2000).formatted_offset + assert_equal "-0500", Time.local(2000).formatted_offset(false) + assert_equal "-04:00", Time.local(2000, 7).formatted_offset + assert_equal "-0400", Time.local(2000, 7).formatted_offset(false) end end @@ -748,16 +762,16 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_compare_with_time_with_zone - assert_equal 1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone['UTC'] ) - assert_equal 0, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['UTC'] ) - assert_equal(-1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone['UTC'] )) + assert_equal 1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone["UTC"] ) + assert_equal 0, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone["UTC"] ) + assert_equal(-1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone["UTC"] )) end def test_compare_with_string 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_equal nil, Time.utc(2000) <=> "Invalid as Time" end def test_at_with_datetime @@ -772,42 +786,42 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_at_with_datetime_returns_local_time - with_env_tz 'US/Eastern' do - dt = DateTime.civil(2000, 1, 1, 0, 0, 0, '+0') + with_env_tz "US/Eastern" do + dt = DateTime.civil(2000, 1, 1, 0, 0, 0, "+0") assert_equal Time.local(1999, 12, 31, 19, 0, 0), Time.at(dt) - assert_equal 'EST', Time.at(dt).zone + assert_equal "EST", Time.at(dt).zone assert_equal(-18000, Time.at(dt).utc_offset) # Daylight savings - dt = DateTime.civil(2000, 7, 1, 1, 0, 0, '+1') + dt = DateTime.civil(2000, 7, 1, 1, 0, 0, "+1") assert_equal Time.local(2000, 6, 30, 20, 0, 0), Time.at(dt) - assert_equal 'EDT', Time.at(dt).zone + assert_equal "EDT", Time.at(dt).zone assert_equal(-14400, Time.at(dt).utc_offset) end end def test_at_with_time_with_zone - assert_equal Time.utc(2000, 1, 1, 0, 0, 0), Time.at(ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['UTC'])) + assert_equal Time.utc(2000, 1, 1, 0, 0, 0), Time.at(ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone["UTC"])) # Only test this if the underlying Time.at raises a TypeError begin Time.at_without_coercion(Time.now, 0) rescue TypeError - assert_raise(TypeError) { assert_equal(Time.utc(2000, 1, 1, 0, 0, 0), Time.at(ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['UTC']), 0)) } + assert_raise(TypeError) { assert_equal(Time.utc(2000, 1, 1, 0, 0, 0), Time.at(ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone["UTC"]), 0)) } end end def test_at_with_time_with_zone_returns_local_time - with_env_tz 'US/Eastern' do - twz = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['London']) + with_env_tz "US/Eastern" do + twz = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone["London"]) assert_equal Time.local(1999, 12, 31, 19, 0, 0), Time.at(twz) - assert_equal 'EST', Time.at(twz).zone + assert_equal "EST", Time.at(twz).zone assert_equal(-18000, Time.at(twz).utc_offset) # Daylight savings - twz = ActiveSupport::TimeWithZone.new(Time.utc(2000, 7, 1, 0, 0, 0), ActiveSupport::TimeZone['London']) + twz = ActiveSupport::TimeWithZone.new(Time.utc(2000, 7, 1, 0, 0, 0), ActiveSupport::TimeZone["London"]) assert_equal Time.local(2000, 6, 30, 20, 0, 0), Time.at(twz) - assert_equal 'EDT', Time.at(twz).zone + assert_equal "EDT", Time.at(twz).zone assert_equal(-14400, Time.at(twz).utc_offset) end end @@ -817,33 +831,33 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_at_with_utc_time - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do assert_equal Time.utc(2000), Time.at(Time.utc(2000)) - assert_equal 'UTC', Time.at(Time.utc(2000)).zone + assert_equal "UTC", Time.at(Time.utc(2000)).zone assert_equal(0, Time.at(Time.utc(2000)).utc_offset) end end def test_at_with_local_time - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do assert_equal Time.local(2000), Time.at(Time.local(2000)) - assert_equal 'EST', Time.at(Time.local(2000)).zone + assert_equal "EST", Time.at(Time.local(2000)).zone assert_equal(-18000, Time.at(Time.local(2000)).utc_offset) assert_equal Time.local(2000, 7, 1), Time.at(Time.local(2000, 7, 1)) - assert_equal 'EDT', Time.at(Time.local(2000, 7, 1)).zone + assert_equal "EDT", Time.at(Time.local(2000, 7, 1)).zone assert_equal(-14400, Time.at(Time.local(2000, 7, 1)).utc_offset) end end def test_eql? - assert_equal true, Time.utc(2000).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UTC']) ) + assert_equal true, Time.utc(2000).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["UTC"]) ) assert_equal true, Time.utc(2000).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Hawaii"]) ) - assert_equal false,Time.utc(2000, 1, 1, 0, 0, 1).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UTC']) ) + assert_equal false,Time.utc(2000, 1, 1, 0, 0, 1).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["UTC"]) ) end def test_minus_with_time_with_zone - assert_equal 86_400.0, Time.utc(2000, 1, 2) - ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone['UTC'] ) + assert_equal 86_400.0, Time.utc(2000, 1, 2) - ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone["UTC"] ) end def test_minus_with_datetime @@ -851,7 +865,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_time_created_with_local_constructor_cannot_represent_times_during_hour_skipped_by_dst - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do # On Apr 2 2006 at 2:00AM in US, clocks were moved forward to 3:00AM. # Therefore, 2AM EST doesn't exist for this date; Time.local fails over to 3:00AM EDT assert_equal Time.local(2006, 4, 2, 3), Time.local(2006, 4, 2, 2) @@ -861,11 +875,11 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase def test_case_equality assert Time === Time.utc(2000) - assert Time === ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UTC']) + assert Time === ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["UTC"]) assert Time === Class.new(Time).utc(2000) assert_equal false, Time === DateTime.civil(2000) assert_equal false, Class.new(Time) === Time.utc(2000) - assert_equal false, Class.new(Time) === ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UTC']) + assert_equal false, Class.new(Time) === ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["UTC"]) end def test_all_day @@ -928,7 +942,7 @@ class TimeExtMarshalingTest < ActiveSupport::TestCase end def test_marshalling_preserves_fractional_seconds - t = Time.parse('00:00:00.500') + t = Time.parse("00:00:00.500") unmarshaled = Marshal.load(Marshal.dump(t)) assert_equal t.to_f, unmarshaled.to_f assert_equal t, unmarshaled diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 7acada011d..e35aa6e154 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -1,20 +1,23 @@ -require 'abstract_unit' -require 'active_support/time' -require 'time_zone_test_helpers' -require 'active_support/core_ext/string/strip' -require 'yaml' +require "abstract_unit" +require "active_support/time" +require "time_zone_test_helpers" +require "active_support/core_ext/string/strip" +require "yaml" class TimeWithZoneTest < ActiveSupport::TestCase include TimeZoneTestHelpers def setup @utc = Time.utc(2000, 1, 1, 0) - @time_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + @time_zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] @twz = ActiveSupport::TimeWithZone.new(@utc, @time_zone) + @dt_twz = ActiveSupport::TimeWithZone.new(@utc.to_datetime, @time_zone) end def test_utc assert_equal @utc, @twz.utc + assert_instance_of Time, @twz.utc + assert_instance_of Time, @dt_twz.utc end def test_time @@ -26,52 +29,54 @@ class TimeWithZoneTest < ActiveSupport::TestCase end def test_in_time_zone - Time.use_zone 'Alaska' do - assert_equal ActiveSupport::TimeWithZone.new(@utc, ActiveSupport::TimeZone['Alaska']), @twz.in_time_zone + Time.use_zone "Alaska" do + assert_equal ActiveSupport::TimeWithZone.new(@utc, ActiveSupport::TimeZone["Alaska"]), @twz.in_time_zone end end def test_in_time_zone_with_argument - assert_equal ActiveSupport::TimeWithZone.new(@utc, ActiveSupport::TimeZone['Alaska']), @twz.in_time_zone('Alaska') + assert_equal ActiveSupport::TimeWithZone.new(@utc, ActiveSupport::TimeZone["Alaska"]), @twz.in_time_zone("Alaska") end def test_in_time_zone_with_new_zone_equal_to_old_zone_does_not_create_new_object - assert_equal @twz.object_id, @twz.in_time_zone(ActiveSupport::TimeZone['Eastern Time (US & Canada)']).object_id + assert_equal @twz.object_id, @twz.in_time_zone(ActiveSupport::TimeZone["Eastern Time (US & Canada)"]).object_id end def test_in_time_zone_with_bad_argument - assert_raise(ArgumentError) { @twz.in_time_zone('No such timezone exists') } + assert_raise(ArgumentError) { @twz.in_time_zone("No such timezone exists") } assert_raise(ArgumentError) { @twz.in_time_zone(-15.hours) } assert_raise(ArgumentError) { @twz.in_time_zone(Object.new) } end def test_localtime assert_equal @twz.localtime, @twz.utc.getlocal + assert_instance_of Time, @twz.localtime + assert_instance_of Time, @dt_twz.localtime end def test_utc? assert_equal false, @twz.utc? - assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UTC']).utc? - assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Etc/UTC']).utc? - assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Universal']).utc? - assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UCT']).utc? - assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Etc/UCT']).utc? - assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Etc/Universal']).utc? + assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["UTC"]).utc? + assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Etc/UTC"]).utc? + assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Universal"]).utc? + assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["UCT"]).utc? + assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Etc/UCT"]).utc? + assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Etc/Universal"]).utc? - assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Africa/Abidjan']).utc? - assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Africa/Banjul']).utc? - assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Africa/Freetown']).utc? - assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['GMT']).utc? - assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['GMT0']).utc? - assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Greenwich']).utc? - assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Iceland']).utc? - assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Africa/Monrovia']).utc? + assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Africa/Abidjan"]).utc? + assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Africa/Banjul"]).utc? + assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Africa/Freetown"]).utc? + assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["GMT"]).utc? + assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["GMT0"]).utc? + assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Greenwich"]).utc? + assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Iceland"]).utc? + assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Africa/Monrovia"]).utc? end def test_formatted_offset - assert_equal '-05:00', @twz.formatted_offset - assert_equal '-04:00', ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).formatted_offset #dst + assert_equal "-05:00", @twz.formatted_offset + assert_equal "-04:00", ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).formatted_offset #dst end def test_dst? @@ -80,8 +85,8 @@ class TimeWithZoneTest < ActiveSupport::TestCase end def test_zone - assert_equal 'EST', @twz.zone - assert_equal 'EDT', ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).zone #dst + assert_equal "EST", @twz.zone + assert_equal "EDT", ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).zone #dst end def test_nsec @@ -93,28 +98,28 @@ class TimeWithZoneTest < ActiveSupport::TestCase end def test_strftime - assert_equal '1999-12-31 19:00:00 EST -0500', @twz.strftime('%Y-%m-%d %H:%M:%S %Z %z') + assert_equal "1999-12-31 19:00:00 EST -0500", @twz.strftime("%Y-%m-%d %H:%M:%S %Z %z") end def test_strftime_with_escaping - assert_equal '%Z %z', @twz.strftime('%%Z %%z') - assert_equal '%EST %-0500', @twz.strftime('%%%Z %%%z') + assert_equal "%Z %z", @twz.strftime("%%Z %%z") + assert_equal "%EST %-0500", @twz.strftime("%%%Z %%%z") end def test_inspect - assert_equal 'Fri, 31 Dec 1999 19:00:00 EST -05:00', @twz.inspect + assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect end def test_to_s - assert_equal '1999-12-31 19:00:00 -0500', @twz.to_s + assert_equal "1999-12-31 19:00:00 -0500", @twz.to_s end def test_to_formatted_s - assert_equal '1999-12-31 19:00:00 -0500', @twz.to_formatted_s + assert_equal "1999-12-31 19:00:00 -0500", @twz.to_formatted_s end def test_to_s_db - assert_equal '2000-01-01 00:00:00', @twz.to_s(:db) + assert_equal "2000-01-01 00:00:00", @twz.to_s(:db) end def test_xmlschema @@ -161,7 +166,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase time: 1999-12-31 19:00:00.000000000 Z EOF - assert_equal(yaml, { 'twz' => @twz }.to_yaml) + assert_equal(yaml, { "twz" => @twz }.to_yaml) end def test_yaml_load @@ -186,11 +191,11 @@ class TimeWithZoneTest < ActiveSupport::TestCase time: 1999-12-31 19:00:00.000000000 Z EOF - assert_equal({ 'twz' => @twz }, YAML.load(yaml)) + assert_equal({ "twz" => @twz }, YAML.load(yaml)) end def test_httpdate - assert_equal 'Sat, 01 Jan 2000 00:00:00 GMT', @twz.httpdate + assert_equal "Sat, 01 Jan 2000 00:00:00 GMT", @twz.httpdate end def test_rfc2822 @@ -210,9 +215,9 @@ class TimeWithZoneTest < ActiveSupport::TestCase end def test_compare_with_time_with_zone - assert_equal 1, @twz <=> ActiveSupport::TimeWithZone.new( Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone['UTC'] ) - assert_equal 0, @twz <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['UTC'] ) - assert_equal(-1, @twz <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone['UTC'] )) + assert_equal 1, @twz <=> ActiveSupport::TimeWithZone.new( Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone["UTC"] ) + assert_equal 0, @twz <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone["UTC"] ) + assert_equal(-1, @twz <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone["UTC"] )) end def test_between? @@ -230,7 +235,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase end def test_past_with_time_current_as_time_local - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do Time.stub(:current, Time.local(2005,2,10,15,30,45)) do assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).past? assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).past? @@ -249,7 +254,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase end def test_future_with_time_current_as_time_local - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do Time.stub(:current, Time.local(2005,2,10,15,30,45)) do assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).future? assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).future? @@ -317,38 +322,38 @@ class TimeWithZoneTest < ActiveSupport::TestCase end def test_minus_with_time - assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - Time.utc(2000, 1, 1) - assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone['Hawaii'] ) - Time.utc(2000, 1, 1) + assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone["UTC"] ) - Time.utc(2000, 1, 1) + assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone["Hawaii"] ) - Time.utc(2000, 1, 1) end def test_minus_with_time_precision - assert_equal 86_399.999999998, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone['UTC'] ) - Time.utc(2000, 1, 2, 0, 0, 0, Rational(1, 1000)) - assert_equal 86_399.999999998, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone['Hawaii'] ) - Time.utc(2000, 1, 2, 0, 0, 0, Rational(1, 1000)) + assert_equal 86_399.999999998, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone["UTC"] ) - Time.utc(2000, 1, 2, 0, 0, 0, Rational(1, 1000)) + assert_equal 86_399.999999998, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone["Hawaii"] ) - Time.utc(2000, 1, 2, 0, 0, 0, Rational(1, 1000)) end def test_minus_with_time_with_zone - twz1 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone['UTC'] ) - twz2 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) + twz1 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone["UTC"] ) + twz2 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone["UTC"] ) assert_equal 86_400.0, twz2 - twz1 end def test_minus_with_time_with_zone_precision - twz1 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 0, Rational(1, 1000)), ActiveSupport::TimeZone['UTC'] ) - twz2 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone['UTC'] ) + twz1 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 0, Rational(1, 1000)), ActiveSupport::TimeZone["UTC"] ) + twz2 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone["UTC"] ) assert_equal 86_399.999999998, twz2 - twz1 end def test_minus_with_datetime - assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - DateTime.civil(2000, 1, 1) + assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone["UTC"] ) - DateTime.civil(2000, 1, 1) end def test_minus_with_datetime_precision - assert_equal 86_399.999999999, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone['UTC'] ) - DateTime.civil(2000, 1, 1) + assert_equal 86_399.999999999, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone["UTC"] ) - DateTime.civil(2000, 1, 1) end def test_minus_with_wrapped_datetime - assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( DateTime.civil(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - Time.utc(2000, 1, 1) - assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( DateTime.civil(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - DateTime.civil(2000, 1, 1) + assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( DateTime.civil(2000, 1, 2), ActiveSupport::TimeZone["UTC"] ) - Time.utc(2000, 1, 1) + assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( DateTime.civil(2000, 1, 2), ActiveSupport::TimeZone["UTC"] ) - DateTime.civil(2000, 1, 1) end def test_plus_and_minus_enforce_spring_dst_rules @@ -356,15 +361,15 @@ class TimeWithZoneTest < ActiveSupport::TestCase twz = ActiveSupport::TimeWithZone.new(utc, @time_zone) assert_equal Time.utc(2006,4,2,1,59,59), twz.time assert_equal false, twz.dst? - assert_equal 'EST', twz.zone + assert_equal "EST", twz.zone twz = twz + 1 assert_equal Time.utc(2006,4,2,3), twz.time # adding 1 sec springs forward to 3:00AM EDT assert_equal true, twz.dst? - assert_equal 'EDT', twz.zone + assert_equal "EDT", twz.zone twz = twz - 1 # subtracting 1 second takes goes back to 1:59:59AM EST assert_equal Time.utc(2006,4,2,1,59,59), twz.time assert_equal false, twz.dst? - assert_equal 'EST', twz.zone + assert_equal "EST", twz.zone end def test_plus_and_minus_enforce_fall_dst_rules @@ -372,29 +377,29 @@ class TimeWithZoneTest < ActiveSupport::TestCase twz = ActiveSupport::TimeWithZone.new(utc, @time_zone) assert_equal Time.utc(2006,10,29,1,59,59), twz.time assert_equal true, twz.dst? - assert_equal 'EDT', twz.zone + assert_equal "EDT", twz.zone twz = twz + 1 assert_equal Time.utc(2006,10,29,1), twz.time # adding 1 sec falls back from 1:59:59 EDT to 1:00AM EST assert_equal false, twz.dst? - assert_equal 'EST', twz.zone + assert_equal "EST", twz.zone twz = twz - 1 assert_equal Time.utc(2006,10,29,1,59,59), twz.time # subtracting 1 sec goes back to 1:59:59AM EDT assert_equal true, twz.dst? - assert_equal 'EDT', twz.zone + assert_equal "EDT", twz.zone end def test_to_a - assert_equal [45, 30, 5, 1, 2, 2000, 2, 32, false, "HST"], ActiveSupport::TimeWithZone.new( Time.utc(2000, 2, 1, 15, 30, 45), ActiveSupport::TimeZone['Hawaii'] ).to_a + assert_equal [45, 30, 5, 1, 2, 2000, 2, 32, false, "HST"], ActiveSupport::TimeWithZone.new( Time.utc(2000, 2, 1, 15, 30, 45), ActiveSupport::TimeZone["Hawaii"] ).to_a end def test_to_f - result = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone['Hawaii'] ).to_f + result = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone["Hawaii"] ).to_f assert_equal 946684800.0, result assert_kind_of Float, result end def test_to_i - result = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone['Hawaii'] ).to_i + result = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone["Hawaii"] ).to_i assert_equal 946684800, result assert_kind_of Integer, result end @@ -406,18 +411,18 @@ class TimeWithZoneTest < ActiveSupport::TestCase end def test_to_r - result = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1), ActiveSupport::TimeZone['Hawaii']).to_r + result = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1), ActiveSupport::TimeZone["Hawaii"]).to_r assert_equal Rational(946684800, 1), result assert_kind_of Rational, result end def test_time_at - time = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1), ActiveSupport::TimeZone['Hawaii']) + time = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1), ActiveSupport::TimeZone["Hawaii"]) assert_equal time, Time.at(time) end def test_to_time - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do assert_equal Time, @twz.to_time.class assert_equal Time.local(1999, 12, 31, 19), @twz.to_time assert_equal Time.local(1999, 12, 31, 19).utc_offset, @twz.to_time.utc_offset @@ -426,13 +431,13 @@ class TimeWithZoneTest < ActiveSupport::TestCase def test_to_date # 1 sec before midnight Jan 1 EST - assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 4, 59, 59), ActiveSupport::TimeZone['Eastern Time (US & Canada)'] ).to_date + assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 4, 59, 59), ActiveSupport::TimeZone["Eastern Time (US & Canada)"] ).to_date # midnight Jan 1 EST - assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 5, 0, 0), ActiveSupport::TimeZone['Eastern Time (US & Canada)'] ).to_date + assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 5, 0, 0), ActiveSupport::TimeZone["Eastern Time (US & Canada)"] ).to_date # 1 sec before midnight Jan 2 EST - assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2, 4, 59, 59), ActiveSupport::TimeZone['Eastern Time (US & Canada)'] ).to_date + assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2, 4, 59, 59), ActiveSupport::TimeZone["Eastern Time (US & Canada)"] ).to_date # midnight Jan 2 EST - assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2, 5, 0, 0), ActiveSupport::TimeZone['Eastern Time (US & Canada)'] ).to_date + assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2, 5, 0, 0), ActiveSupport::TimeZone["Eastern Time (US & Canada)"] ).to_date end def test_to_datetime @@ -457,7 +462,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase end def test_class_name - assert_equal 'Time', ActiveSupport::TimeWithZone.name + assert_equal "Time", ActiveSupport::TimeWithZone.name end def test_method_missing_with_time_return_value @@ -470,19 +475,19 @@ class TimeWithZoneTest < ActiveSupport::TestCase mtime = Marshal.load(marshal_str) assert_equal Time.utc(2000, 1, 1, 0), mtime.utc assert mtime.utc.utc? - assert_equal ActiveSupport::TimeZone['Eastern Time (US & Canada)'], mtime.time_zone + assert_equal ActiveSupport::TimeZone["Eastern Time (US & Canada)"], mtime.time_zone assert_equal Time.utc(1999, 12, 31, 19), mtime.time assert mtime.time.utc? assert_equal @twz.inspect, mtime.inspect end def test_marshal_dump_and_load_with_tzinfo_identifier - twz = ActiveSupport::TimeWithZone.new(@utc, TZInfo::Timezone.get('America/New_York')) + twz = ActiveSupport::TimeWithZone.new(@utc, TZInfo::Timezone.get("America/New_York")) marshal_str = Marshal.dump(twz) mtime = Marshal.load(marshal_str) assert_equal Time.utc(2000, 1, 1, 0), mtime.utc assert mtime.utc.utc? - assert_equal 'America/New_York', mtime.time_zone.name + assert_equal "America/New_York", mtime.time_zone.name assert_equal Time.utc(1999, 12, 31, 19), mtime.time assert mtime.time.utc? assert_equal @twz.inspect, mtime.inspect @@ -503,8 +508,8 @@ class TimeWithZoneTest < ActiveSupport::TestCase def test_method_missing_with_non_time_return_value time = @twz.time - def time.foo; 'bar'; end - assert_equal 'bar', @twz.foo + def time.foo; "bar"; end + assert_equal "bar", @twz.foo end def test_date_part_value_methods @@ -538,9 +543,9 @@ class TimeWithZoneTest < ActiveSupport::TestCase end def test_utc_to_local_conversion_saves_period_in_instance_variable - assert_nil @twz.instance_variable_get('@period') + assert_nil @twz.instance_variable_get("@period") @twz.time - assert_kind_of TZInfo::TimezonePeriod, @twz.instance_variable_get('@period') + assert_kind_of TZInfo::TimezonePeriod, @twz.instance_variable_get("@period") end def test_instance_created_with_local_time_returns_correct_utc_time @@ -552,14 +557,14 @@ class TimeWithZoneTest < ActiveSupport::TestCase twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,2,2)) # first second of DST assert_equal Time.utc(2006,4,2,3), twz.time # springs forward to 3AM assert_equal true, twz.dst? - assert_equal 'EDT', twz.zone + assert_equal "EDT", twz.zone end def test_instance_created_with_local_time_enforces_fall_dst_rules twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,29,1)) # 1AM can be either DST or non-DST; we'll pick DST assert_equal Time.utc(2006,10,29,1), twz.time assert_equal true, twz.dst? - assert_equal 'EDT', twz.zone + assert_equal "EDT", twz.zone end def test_ruby_19_weekday_name_query_methods @@ -579,33 +584,33 @@ class TimeWithZoneTest < ActiveSupport::TestCase def test_change assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect - assert_equal "Mon, 31 Dec 2001 19:00:00 EST -05:00", @twz.change(:year => 2001).inspect - assert_equal "Wed, 31 Mar 1999 19:00:00 EST -05:00", @twz.change(:month => 3).inspect - assert_equal "Wed, 03 Mar 1999 19:00:00 EST -05:00", @twz.change(:month => 2).inspect - assert_equal "Wed, 15 Dec 1999 19:00:00 EST -05:00", @twz.change(:day => 15).inspect - assert_equal "Fri, 31 Dec 1999 06:00:00 EST -05:00", @twz.change(:hour => 6).inspect - assert_equal "Fri, 31 Dec 1999 19:15:00 EST -05:00", @twz.change(:min => 15).inspect - assert_equal "Fri, 31 Dec 1999 19:00:30 EST -05:00", @twz.change(:sec => 30).inspect + assert_equal "Mon, 31 Dec 2001 19:00:00 EST -05:00", @twz.change(year: 2001).inspect + assert_equal "Wed, 31 Mar 1999 19:00:00 EST -05:00", @twz.change(month: 3).inspect + assert_equal "Wed, 03 Mar 1999 19:00:00 EST -05:00", @twz.change(month: 2).inspect + assert_equal "Wed, 15 Dec 1999 19:00:00 EST -05:00", @twz.change(day: 15).inspect + assert_equal "Fri, 31 Dec 1999 06:00:00 EST -05:00", @twz.change(hour: 6).inspect + assert_equal "Fri, 31 Dec 1999 19:15:00 EST -05:00", @twz.change(min: 15).inspect + assert_equal "Fri, 31 Dec 1999 19:00:30 EST -05:00", @twz.change(sec: 30).inspect end def test_change_at_dst_boundary - twz = ActiveSupport::TimeWithZone.new(Time.at(1319936400).getutc, ActiveSupport::TimeZone['Madrid']) - assert_equal twz, twz.change(:min => 0) + twz = ActiveSupport::TimeWithZone.new(Time.at(1319936400).getutc, ActiveSupport::TimeZone["Madrid"]) + assert_equal twz, twz.change(min: 0) end def test_round_at_dst_boundary - twz = ActiveSupport::TimeWithZone.new(Time.at(1319936400).getutc, ActiveSupport::TimeZone['Madrid']) + twz = ActiveSupport::TimeWithZone.new(Time.at(1319936400).getutc, ActiveSupport::TimeZone["Madrid"]) assert_equal twz, twz.round end def test_advance assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect - assert_equal "Mon, 31 Dec 2001 19:00:00 EST -05:00", @twz.advance(:years => 2).inspect - assert_equal "Fri, 31 Mar 2000 19:00:00 EST -05:00", @twz.advance(:months => 3).inspect - assert_equal "Tue, 04 Jan 2000 19:00:00 EST -05:00", @twz.advance(:days => 4).inspect - assert_equal "Sat, 01 Jan 2000 01:00:00 EST -05:00", @twz.advance(:hours => 6).inspect - assert_equal "Fri, 31 Dec 1999 19:15:00 EST -05:00", @twz.advance(:minutes => 15).inspect - assert_equal "Fri, 31 Dec 1999 19:00:30 EST -05:00", @twz.advance(:seconds => 30).inspect + assert_equal "Mon, 31 Dec 2001 19:00:00 EST -05:00", @twz.advance(years: 2).inspect + assert_equal "Fri, 31 Mar 2000 19:00:00 EST -05:00", @twz.advance(months: 3).inspect + assert_equal "Tue, 04 Jan 2000 19:00:00 EST -05:00", @twz.advance(days: 4).inspect + assert_equal "Sat, 01 Jan 2000 01:00:00 EST -05:00", @twz.advance(hours: 6).inspect + assert_equal "Fri, 31 Dec 1999 19:15:00 EST -05:00", @twz.advance(minutes: 15).inspect + assert_equal "Fri, 31 Dec 1999 19:00:30 EST -05:00", @twz.advance(seconds: 30).inspect end def test_beginning_of_year @@ -670,6 +675,10 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal "Fri, 31 Dec 1999 19:00:01 EST -05:00", @twz.since(1).inspect end + def test_in + assert_equal "Fri, 31 Dec 1999 19:00:01 EST -05:00", @twz.in(1).inspect + end + def test_ago assert_equal "Fri, 31 Dec 1999 18:59:59 EST -05:00", @twz.ago(1).inspect end @@ -680,53 +689,61 @@ class TimeWithZoneTest < ActiveSupport::TestCase def test_advance_1_year_from_leap_day twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2004,2,29)) - assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.advance(:years => 1).inspect + assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.advance(years: 1).inspect assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.years_since(1).inspect assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.since(1.year).inspect + assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.in(1.year).inspect assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", (twz + 1.year).inspect end def test_advance_1_month_from_last_day_of_january twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2005,1,31)) - assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.advance(:months => 1).inspect + assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.advance(months: 1).inspect assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.months_since(1).inspect assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.since(1.month).inspect + assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.in(1.month).inspect assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", (twz + 1.month).inspect end def test_advance_1_month_from_last_day_of_january_during_leap_year twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2000,1,31)) - assert_equal "Tue, 29 Feb 2000 00:00:00 EST -05:00", twz.advance(:months => 1).inspect + assert_equal "Tue, 29 Feb 2000 00:00:00 EST -05:00", twz.advance(months: 1).inspect assert_equal "Tue, 29 Feb 2000 00:00:00 EST -05:00", twz.months_since(1).inspect assert_equal "Tue, 29 Feb 2000 00:00:00 EST -05:00", twz.since(1.month).inspect + assert_equal "Tue, 29 Feb 2000 00:00:00 EST -05:00", twz.in(1.month).inspect assert_equal "Tue, 29 Feb 2000 00:00:00 EST -05:00", (twz + 1.month).inspect end def test_advance_1_month_into_spring_dst_gap twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,3,2,2)) - assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.advance(:months => 1).inspect + assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.advance(months: 1).inspect assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.months_since(1).inspect assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.since(1.month).inspect + assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.in(1.month).inspect assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", (twz + 1.month).inspect end def test_advance_1_second_into_spring_dst_gap twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,2,1,59,59)) - assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.advance(:seconds => 1).inspect + assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.advance(seconds: 1).inspect assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", (twz + 1).inspect + assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", (twz + 1.second).inspect assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.since(1).inspect assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.since(1.second).inspect - assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", (twz + 1.second).inspect + assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.in(1).inspect + assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.in(1.second).inspect end def test_advance_1_day_across_spring_dst_transition twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,1,10,30)) # In 2006, spring DST transition occurred Apr 2 at 2AM; this day was only 23 hours long # When we advance 1 day, we want to end up at the same time on the next day - assert_equal "Sun, 02 Apr 2006 10:30:00 EDT -04:00", twz.advance(:days => 1).inspect + assert_equal "Sun, 02 Apr 2006 10:30:00 EDT -04:00", twz.advance(days: 1).inspect assert_equal "Sun, 02 Apr 2006 10:30:00 EDT -04:00", twz.since(1.days).inspect + assert_equal "Sun, 02 Apr 2006 10:30:00 EDT -04:00", twz.in(1.days).inspect assert_equal "Sun, 02 Apr 2006 10:30:00 EDT -04:00", (twz + 1.days).inspect assert_equal "Sun, 02 Apr 2006 10:30:01 EDT -04:00", twz.since(1.days + 1.second).inspect + assert_equal "Sun, 02 Apr 2006 10:30:01 EDT -04:00", twz.in(1.days + 1.second).inspect assert_equal "Sun, 02 Apr 2006 10:30:01 EDT -04:00", (twz + 1.days + 1.second).inspect end @@ -734,7 +751,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,2,10,30)) # In 2006, spring DST transition occurred Apr 2 at 2AM; this day was only 23 hours long # When we advance back 1 day, we want to end up at the same time on the previous day - assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(:days => -1).inspect + assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(days: -1).inspect assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.ago(1.days).inspect assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", (twz - 1.days).inspect assert_equal "Sat, 01 Apr 2006 10:30:01 EST -05:00", twz.ago(1.days - 1.second).inspect @@ -748,13 +765,17 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", (twz + 86400.seconds).inspect assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.since(86400).inspect assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.since(86400.seconds).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.advance(:seconds => 86400).inspect + assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.in(86400).inspect + assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.in(86400.seconds).inspect + assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.advance(seconds: 86400).inspect assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", (twz + 1440.minutes).inspect assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.since(1440.minutes).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.advance(:minutes => 1440).inspect + assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.in(1440.minutes).inspect + assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.advance(minutes: 1440).inspect assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", (twz + 24.hours).inspect assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.since(24.hours).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.advance(:hours => 24).inspect + assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.in(24.hours).inspect + assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.advance(hours: 24).inspect end def test_advance_1_day_expressed_as_number_of_seconds_minutes_or_hours_across_spring_dst_transition_backwards @@ -765,23 +786,25 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", (twz - 86400.seconds).inspect assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.ago(86400).inspect assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.ago(86400.seconds).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(:seconds => -86400).inspect + assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(seconds: -86400).inspect assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", (twz - 1440.minutes).inspect assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.ago(1440.minutes).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(:minutes => -1440).inspect + assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(minutes: -1440).inspect assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", (twz - 24.hours).inspect assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.ago(24.hours).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(:hours => -24).inspect + assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(hours: -24).inspect end def test_advance_1_day_across_fall_dst_transition twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,28,10,30)) # In 2006, fall DST transition occurred Oct 29 at 2AM; this day was 25 hours long # When we advance 1 day, we want to end up at the same time on the next day - assert_equal "Sun, 29 Oct 2006 10:30:00 EST -05:00", twz.advance(:days => 1).inspect + assert_equal "Sun, 29 Oct 2006 10:30:00 EST -05:00", twz.advance(days: 1).inspect assert_equal "Sun, 29 Oct 2006 10:30:00 EST -05:00", twz.since(1.days).inspect + assert_equal "Sun, 29 Oct 2006 10:30:00 EST -05:00", twz.in(1.days).inspect assert_equal "Sun, 29 Oct 2006 10:30:00 EST -05:00", (twz + 1.days).inspect assert_equal "Sun, 29 Oct 2006 10:30:01 EST -05:00", twz.since(1.days + 1.second).inspect + assert_equal "Sun, 29 Oct 2006 10:30:01 EST -05:00", twz.in(1.days + 1.second).inspect assert_equal "Sun, 29 Oct 2006 10:30:01 EST -05:00", (twz + 1.days + 1.second).inspect end @@ -789,7 +812,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,29,10,30)) # In 2006, fall DST transition occurred Oct 29 at 2AM; this day was 25 hours long # When we advance backwards 1 day, we want to end up at the same time on the previous day - assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.advance(:days => -1).inspect + assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.advance(days: -1).inspect assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.ago(1.days).inspect assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", (twz - 1.days).inspect assert_equal "Sat, 28 Oct 2006 10:30:01 EDT -04:00", twz.ago(1.days - 1.second).inspect @@ -803,13 +826,17 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", (twz + 86400.seconds).inspect assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.since(86400).inspect assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.since(86400.seconds).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.advance(:seconds => 86400).inspect + assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.in(86400).inspect + assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.in(86400.seconds).inspect + assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.advance(seconds: 86400).inspect assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", (twz + 1440.minutes).inspect assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.since(1440.minutes).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.advance(:minutes => 1440).inspect + assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.in(1440.minutes).inspect + assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.advance(minutes: 1440).inspect assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", (twz + 24.hours).inspect assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.since(24.hours).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.advance(:hours => 24).inspect + assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.in(24.hours).inspect + assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.advance(hours: 24).inspect end def test_advance_1_day_expressed_as_number_of_seconds_minutes_or_hours_across_fall_dst_transition_backwards @@ -820,26 +847,61 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", (twz - 86400.seconds).inspect assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.ago(86400).inspect assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.ago(86400.seconds).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.advance(:seconds => -86400).inspect + assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.advance(seconds: -86400).inspect assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", (twz - 1440.minutes).inspect assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.ago(1440.minutes).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.advance(:minutes => -1440).inspect + assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.advance(minutes: -1440).inspect assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", (twz - 24.hours).inspect assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.ago(24.hours).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.advance(:hours => -24).inspect + assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.advance(hours: -24).inspect + end + + def test_advance_1_week_across_spring_dst_transition + twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,1,10,30)) + assert_equal "Sat, 08 Apr 2006 10:30:00 EDT -04:00", twz.advance(weeks: 1).inspect + assert_equal "Sat, 08 Apr 2006 10:30:00 EDT -04:00", twz.weeks_since(1).inspect + assert_equal "Sat, 08 Apr 2006 10:30:00 EDT -04:00", twz.since(1.week).inspect + assert_equal "Sat, 08 Apr 2006 10:30:00 EDT -04:00", twz.in(1.week).inspect + assert_equal "Sat, 08 Apr 2006 10:30:00 EDT -04:00", (twz + 1.week).inspect + end + + def test_advance_1_week_across_spring_dst_transition_backwards + twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,8,10,30)) + assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(weeks: -1).inspect + assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.weeks_ago(1).inspect + assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.ago(1.week).inspect + assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", (twz - 1.week).inspect + end + + def test_advance_1_week_across_fall_dst_transition + twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,28,10,30)) + assert_equal "Sat, 04 Nov 2006 10:30:00 EST -05:00", twz.advance(weeks: 1).inspect + assert_equal "Sat, 04 Nov 2006 10:30:00 EST -05:00", twz.weeks_since(1).inspect + assert_equal "Sat, 04 Nov 2006 10:30:00 EST -05:00", twz.since(1.week).inspect + assert_equal "Sat, 04 Nov 2006 10:30:00 EST -05:00", twz.in(1.week).inspect + assert_equal "Sat, 04 Nov 2006 10:30:00 EST -05:00", (twz + 1.week).inspect + end + + def test_advance_1_week_across_fall_dst_transition_backwards + twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,11,4,10,30)) + assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.advance(weeks: -1).inspect + assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.weeks_ago(1).inspect + assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.ago(1.week).inspect + assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", (twz - 1.week).inspect end def test_advance_1_month_across_spring_dst_transition twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,1,10,30)) - assert_equal "Mon, 01 May 2006 10:30:00 EDT -04:00", twz.advance(:months => 1).inspect + assert_equal "Mon, 01 May 2006 10:30:00 EDT -04:00", twz.advance(months: 1).inspect assert_equal "Mon, 01 May 2006 10:30:00 EDT -04:00", twz.months_since(1).inspect assert_equal "Mon, 01 May 2006 10:30:00 EDT -04:00", twz.since(1.month).inspect + assert_equal "Mon, 01 May 2006 10:30:00 EDT -04:00", twz.in(1.month).inspect assert_equal "Mon, 01 May 2006 10:30:00 EDT -04:00", (twz + 1.month).inspect end def test_advance_1_month_across_spring_dst_transition_backwards twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,5,1,10,30)) - assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(:months => -1).inspect + assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(months: -1).inspect assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.months_ago(1).inspect assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.ago(1.month).inspect assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", (twz - 1.month).inspect @@ -847,15 +909,16 @@ class TimeWithZoneTest < ActiveSupport::TestCase def test_advance_1_month_across_fall_dst_transition twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,28,10,30)) - assert_equal "Tue, 28 Nov 2006 10:30:00 EST -05:00", twz.advance(:months => 1).inspect + assert_equal "Tue, 28 Nov 2006 10:30:00 EST -05:00", twz.advance(months: 1).inspect assert_equal "Tue, 28 Nov 2006 10:30:00 EST -05:00", twz.months_since(1).inspect assert_equal "Tue, 28 Nov 2006 10:30:00 EST -05:00", twz.since(1.month).inspect + assert_equal "Tue, 28 Nov 2006 10:30:00 EST -05:00", twz.in(1.month).inspect assert_equal "Tue, 28 Nov 2006 10:30:00 EST -05:00", (twz + 1.month).inspect end def test_advance_1_month_across_fall_dst_transition_backwards twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,11,28,10,30)) - assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.advance(:months => -1).inspect + assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.advance(months: -1).inspect assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.months_ago(1).inspect assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.ago(1.month).inspect assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", (twz - 1.month).inspect @@ -863,20 +926,24 @@ class TimeWithZoneTest < ActiveSupport::TestCase def test_advance_1_year twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2008,2,15,10,30)) - assert_equal "Sun, 15 Feb 2009 10:30:00 EST -05:00", twz.advance(:years => 1).inspect + assert_equal "Sun, 15 Feb 2009 10:30:00 EST -05:00", twz.advance(years: 1).inspect assert_equal "Sun, 15 Feb 2009 10:30:00 EST -05:00", twz.years_since(1).inspect + assert_equal "Sun, 15 Feb 2009 10:30:00 EST -05:00", twz.since(1.year).inspect + assert_equal "Sun, 15 Feb 2009 10:30:00 EST -05:00", twz.in(1.year).inspect assert_equal "Sun, 15 Feb 2009 10:30:00 EST -05:00", (twz + 1.year).inspect - assert_equal "Thu, 15 Feb 2007 10:30:00 EST -05:00", twz.advance(:years => -1).inspect + assert_equal "Thu, 15 Feb 2007 10:30:00 EST -05:00", twz.advance(years: -1).inspect assert_equal "Thu, 15 Feb 2007 10:30:00 EST -05:00", twz.years_ago(1).inspect assert_equal "Thu, 15 Feb 2007 10:30:00 EST -05:00", (twz - 1.year).inspect end def test_advance_1_year_during_dst twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2008,7,15,10,30)) - assert_equal "Wed, 15 Jul 2009 10:30:00 EDT -04:00", twz.advance(:years => 1).inspect + assert_equal "Wed, 15 Jul 2009 10:30:00 EDT -04:00", twz.advance(years: 1).inspect assert_equal "Wed, 15 Jul 2009 10:30:00 EDT -04:00", twz.years_since(1).inspect + assert_equal "Wed, 15 Jul 2009 10:30:00 EDT -04:00", twz.since(1.year).inspect + assert_equal "Wed, 15 Jul 2009 10:30:00 EDT -04:00", twz.in(1.year).inspect assert_equal "Wed, 15 Jul 2009 10:30:00 EDT -04:00", (twz + 1.year).inspect - assert_equal "Sun, 15 Jul 2007 10:30:00 EDT -04:00", twz.advance(:years => -1).inspect + assert_equal "Sun, 15 Jul 2007 10:30:00 EDT -04:00", twz.advance(years: -1).inspect assert_equal "Sun, 15 Jul 2007 10:30:00 EDT -04:00", twz.years_ago(1).inspect assert_equal "Sun, 15 Jul 2007 10:30:00 EDT -04:00", (twz - 1.year).inspect end @@ -904,13 +971,13 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase end def test_in_time_zone - Time.use_zone 'Alaska' do - assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @t.in_time_zone.inspect - assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @dt.in_time_zone.inspect + Time.use_zone "Alaska" do + assert_equal "Fri, 31 Dec 1999 15:00:00 AKST -09:00", @t.in_time_zone.inspect + assert_equal "Fri, 31 Dec 1999 15:00:00 AKST -09:00", @dt.in_time_zone.inspect end - Time.use_zone 'Hawaii' do - assert_equal 'Fri, 31 Dec 1999 14:00:00 HST -10:00', @t.in_time_zone.inspect - assert_equal 'Fri, 31 Dec 1999 14:00:00 HST -10:00', @dt.in_time_zone.inspect + Time.use_zone "Hawaii" do + assert_equal "Fri, 31 Dec 1999 14:00:00 HST -10:00", @t.in_time_zone.inspect + assert_equal "Fri, 31 Dec 1999 14:00:00 HST -10:00", @dt.in_time_zone.inspect end Time.use_zone nil do assert_equal @t, @t.in_time_zone @@ -920,20 +987,20 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase def test_nil_time_zone Time.use_zone nil do - assert !@t.in_time_zone.respond_to?(:period), 'no period method' - assert !@dt.in_time_zone.respond_to?(:period), 'no period method' + assert !@t.in_time_zone.respond_to?(:period), "no period method" + assert !@dt.in_time_zone.respond_to?(:period), "no period method" end end def test_in_time_zone_with_argument - Time.use_zone 'Eastern Time (US & Canada)' do # Time.zone will not affect #in_time_zone(zone) - assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @t.in_time_zone('Alaska').inspect - assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @dt.in_time_zone('Alaska').inspect - assert_equal 'Fri, 31 Dec 1999 14:00:00 HST -10:00', @t.in_time_zone('Hawaii').inspect - assert_equal 'Fri, 31 Dec 1999 14:00:00 HST -10:00', @dt.in_time_zone('Hawaii').inspect - assert_equal 'Sat, 01 Jan 2000 00:00:00 UTC +00:00', @t.in_time_zone('UTC').inspect - assert_equal 'Sat, 01 Jan 2000 00:00:00 UTC +00:00', @dt.in_time_zone('UTC').inspect - assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @t.in_time_zone(-9.hours).inspect + Time.use_zone "Eastern Time (US & Canada)" do # Time.zone will not affect #in_time_zone(zone) + assert_equal "Fri, 31 Dec 1999 15:00:00 AKST -09:00", @t.in_time_zone("Alaska").inspect + assert_equal "Fri, 31 Dec 1999 15:00:00 AKST -09:00", @dt.in_time_zone("Alaska").inspect + assert_equal "Fri, 31 Dec 1999 14:00:00 HST -10:00", @t.in_time_zone("Hawaii").inspect + assert_equal "Fri, 31 Dec 1999 14:00:00 HST -10:00", @dt.in_time_zone("Hawaii").inspect + assert_equal "Sat, 01 Jan 2000 00:00:00 UTC +00:00", @t.in_time_zone("UTC").inspect + assert_equal "Sat, 01 Jan 2000 00:00:00 UTC +00:00", @dt.in_time_zone("UTC").inspect + assert_equal "Fri, 31 Dec 1999 15:00:00 AKST -09:00", @t.in_time_zone(-9.hours).inspect end end @@ -947,119 +1014,119 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase end def test_in_time_zone_with_time_local_instance - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do time = Time.local(1999, 12, 31, 19) # == Time.utc(2000) - assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', time.in_time_zone('Alaska').inspect + assert_equal "Fri, 31 Dec 1999 15:00:00 AKST -09:00", time.in_time_zone("Alaska").inspect end end def test_localtime - Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + Time.zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] assert_equal @dt.in_time_zone.localtime, @dt.in_time_zone.utc.to_time.getlocal end def test_use_zone - Time.zone = 'Alaska' - Time.use_zone 'Hawaii' do - assert_equal ActiveSupport::TimeZone['Hawaii'], Time.zone + Time.zone = "Alaska" + Time.use_zone "Hawaii" do + assert_equal ActiveSupport::TimeZone["Hawaii"], Time.zone end - assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone + assert_equal ActiveSupport::TimeZone["Alaska"], Time.zone end def test_use_zone_with_exception_raised - Time.zone = 'Alaska' + Time.zone = "Alaska" assert_raise RuntimeError do - Time.use_zone('Hawaii') { raise RuntimeError } + Time.use_zone("Hawaii") { raise RuntimeError } end - assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone + assert_equal ActiveSupport::TimeZone["Alaska"], Time.zone end def test_use_zone_raises_on_invalid_timezone - Time.zone = 'Alaska' + Time.zone = "Alaska" assert_raise ArgumentError do - Time.use_zone("No such timezone exists") { } + Time.use_zone("No such timezone exists") {} end - assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone + assert_equal ActiveSupport::TimeZone["Alaska"], Time.zone end def test_time_zone_getter_and_setter - Time.zone = ActiveSupport::TimeZone['Alaska'] - assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone - Time.zone = 'Alaska' - assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone + Time.zone = ActiveSupport::TimeZone["Alaska"] + assert_equal ActiveSupport::TimeZone["Alaska"], Time.zone + Time.zone = "Alaska" + assert_equal ActiveSupport::TimeZone["Alaska"], Time.zone Time.zone = -9.hours - assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone + assert_equal ActiveSupport::TimeZone["Alaska"], Time.zone Time.zone = nil assert_equal nil, Time.zone end def test_time_zone_getter_and_setter_with_zone_default_set old_zone_default = Time.zone_default - Time.zone_default = ActiveSupport::TimeZone['Alaska'] - assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone - Time.zone = ActiveSupport::TimeZone['Hawaii'] - assert_equal ActiveSupport::TimeZone['Hawaii'], Time.zone + Time.zone_default = ActiveSupport::TimeZone["Alaska"] + assert_equal ActiveSupport::TimeZone["Alaska"], Time.zone + Time.zone = ActiveSupport::TimeZone["Hawaii"] + assert_equal ActiveSupport::TimeZone["Hawaii"], Time.zone Time.zone = nil - assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone + assert_equal ActiveSupport::TimeZone["Alaska"], Time.zone ensure Time.zone_default = old_zone_default end def test_time_zone_setter_is_thread_safe - Time.use_zone 'Paris' do - t1 = Thread.new { Time.zone = 'Alaska' }.join - t2 = Thread.new { Time.zone = 'Hawaii' }.join + Time.use_zone "Paris" do + t1 = Thread.new { Time.zone = "Alaska" }.join + t2 = Thread.new { Time.zone = "Hawaii" }.join assert t1.stop?, "Thread 1 did not finish running" assert t2.stop?, "Thread 2 did not finish running" - assert_equal ActiveSupport::TimeZone['Paris'], Time.zone - assert_equal ActiveSupport::TimeZone['Alaska'], t1[:time_zone] - assert_equal ActiveSupport::TimeZone['Hawaii'], t2[:time_zone] + assert_equal ActiveSupport::TimeZone["Paris"], Time.zone + assert_equal ActiveSupport::TimeZone["Alaska"], t1[:time_zone] + assert_equal ActiveSupport::TimeZone["Hawaii"], t2[:time_zone] end end def test_time_zone_setter_with_tzinfo_timezone_object_wraps_in_rails_time_zone - tzinfo = TZInfo::Timezone.get('America/New_York') + tzinfo = TZInfo::Timezone.get("America/New_York") Time.zone = tzinfo assert_kind_of ActiveSupport::TimeZone, Time.zone assert_equal tzinfo, Time.zone.tzinfo - assert_equal 'America/New_York', Time.zone.name + assert_equal "America/New_York", Time.zone.name assert_equal(-18_000, Time.zone.utc_offset) end def test_time_zone_setter_with_tzinfo_timezone_identifier_does_lookup_and_wraps_in_rails_time_zone - Time.zone = 'America/New_York' + Time.zone = "America/New_York" assert_kind_of ActiveSupport::TimeZone, Time.zone - assert_equal 'America/New_York', Time.zone.tzinfo.name - assert_equal 'America/New_York', Time.zone.name + assert_equal "America/New_York", Time.zone.tzinfo.name + assert_equal "America/New_York", Time.zone.name assert_equal(-18_000, Time.zone.utc_offset) end def test_time_zone_setter_with_invalid_zone - assert_raise(ArgumentError){ Time.zone = "No such timezone exists" } - assert_raise(ArgumentError){ Time.zone = -15.hours } - assert_raise(ArgumentError){ Time.zone = Object.new } + assert_raise(ArgumentError) { Time.zone = "No such timezone exists" } + assert_raise(ArgumentError) { Time.zone = -15.hours } + assert_raise(ArgumentError) { Time.zone = Object.new } end def test_find_zone_without_bang_returns_nil_if_time_zone_can_not_be_found - assert_nil Time.find_zone('No such timezone exists') + assert_nil Time.find_zone("No such timezone exists") assert_nil Time.find_zone(-15.hours) assert_nil Time.find_zone(Object.new) end def test_find_zone_with_bang_raises_if_time_zone_can_not_be_found - assert_raise(ArgumentError) { Time.find_zone!('No such timezone exists') } + assert_raise(ArgumentError) { Time.find_zone!("No such timezone exists") } assert_raise(ArgumentError) { Time.find_zone!(-15.hours) } assert_raise(ArgumentError) { Time.find_zone!(Object.new) } end def test_time_zone_setter_with_find_zone_without_bang - assert_nil Time.zone = Time.find_zone('No such timezone exists') + assert_nil Time.zone = Time.find_zone("No such timezone exists") assert_nil Time.zone = Time.find_zone(-15.hours) assert_nil Time.zone = Time.find_zone(Object.new) end def test_current_returns_time_now_when_zone_not_set - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do Time.stub(:now, Time.local(2000)) do assert_equal false, Time.current.is_a?(ActiveSupport::TimeWithZone) assert_equal Time.local(2000), Time.current @@ -1068,22 +1135,22 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase end def test_current_returns_time_zone_now_when_zone_set - Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - with_env_tz 'US/Eastern' do + Time.zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + with_env_tz "US/Eastern" do Time.stub(:now, Time.local(2000)) do assert_equal true, Time.current.is_a?(ActiveSupport::TimeWithZone) - assert_equal 'Eastern Time (US & Canada)', Time.current.time_zone.name + assert_equal "Eastern Time (US & Canada)", Time.current.time_zone.name assert_equal Time.utc(2000), Time.current.time end end end def test_time_in_time_zone_doesnt_affect_receiver - with_env_tz 'Europe/London' do + with_env_tz "Europe/London" do time = Time.local(2000, 7, 1) - time_with_zone = time.in_time_zone('Eastern Time (US & Canada)') + time_with_zone = time.in_time_zone("Eastern Time (US & Canada)") assert_equal Time.utc(2000, 6, 30, 23, 0, 0), time_with_zone - assert_not time.utc?, 'time expected to be local, but is UTC' + assert_not time.utc?, "time expected to be local, but is UTC" end end end @@ -1096,11 +1163,11 @@ class TimeWithZoneMethodsForDate < ActiveSupport::TestCase end def test_in_time_zone - with_tz_default 'Alaska' do - assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @d.in_time_zone.inspect + with_tz_default "Alaska" do + assert_equal "Sat, 01 Jan 2000 00:00:00 AKST -09:00", @d.in_time_zone.inspect end - with_tz_default 'Hawaii' do - assert_equal 'Sat, 01 Jan 2000 00:00:00 HST -10:00', @d.in_time_zone.inspect + with_tz_default "Hawaii" do + assert_equal "Sat, 01 Jan 2000 00:00:00 HST -10:00", @d.in_time_zone.inspect end with_tz_default nil do assert_equal @d.to_time, @d.in_time_zone @@ -1109,16 +1176,16 @@ class TimeWithZoneMethodsForDate < ActiveSupport::TestCase def test_nil_time_zone with_tz_default nil do - assert !@d.in_time_zone.respond_to?(:period), 'no period method' + assert !@d.in_time_zone.respond_to?(:period), "no period method" end end def test_in_time_zone_with_argument - with_tz_default 'Eastern Time (US & Canada)' do # Time.zone will not affect #in_time_zone(zone) - assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @d.in_time_zone('Alaska').inspect - assert_equal 'Sat, 01 Jan 2000 00:00:00 HST -10:00', @d.in_time_zone('Hawaii').inspect - assert_equal 'Sat, 01 Jan 2000 00:00:00 UTC +00:00', @d.in_time_zone('UTC').inspect - assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @d.in_time_zone(-9.hours).inspect + with_tz_default "Eastern Time (US & Canada)" do # Time.zone will not affect #in_time_zone(zone) + assert_equal "Sat, 01 Jan 2000 00:00:00 AKST -09:00", @d.in_time_zone("Alaska").inspect + assert_equal "Sat, 01 Jan 2000 00:00:00 HST -10:00", @d.in_time_zone("Hawaii").inspect + assert_equal "Sat, 01 Jan 2000 00:00:00 UTC +00:00", @d.in_time_zone("UTC").inspect + assert_equal "Sat, 01 Jan 2000 00:00:00 AKST -09:00", @d.in_time_zone(-9.hours).inspect end end @@ -1139,15 +1206,15 @@ class TimeWithZoneMethodsForString < ActiveSupport::TestCase end def test_in_time_zone - with_tz_default 'Alaska' do - assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @s.in_time_zone.inspect - assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @u.in_time_zone.inspect - assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @z.in_time_zone.inspect + with_tz_default "Alaska" do + assert_equal "Sat, 01 Jan 2000 00:00:00 AKST -09:00", @s.in_time_zone.inspect + assert_equal "Fri, 31 Dec 1999 15:00:00 AKST -09:00", @u.in_time_zone.inspect + assert_equal "Fri, 31 Dec 1999 15:00:00 AKST -09:00", @z.in_time_zone.inspect end - with_tz_default 'Hawaii' do - assert_equal 'Sat, 01 Jan 2000 00:00:00 HST -10:00', @s.in_time_zone.inspect - assert_equal 'Fri, 31 Dec 1999 14:00:00 HST -10:00', @u.in_time_zone.inspect - assert_equal 'Fri, 31 Dec 1999 14:00:00 HST -10:00', @z.in_time_zone.inspect + with_tz_default "Hawaii" do + assert_equal "Sat, 01 Jan 2000 00:00:00 HST -10:00", @s.in_time_zone.inspect + assert_equal "Fri, 31 Dec 1999 14:00:00 HST -10:00", @u.in_time_zone.inspect + assert_equal "Fri, 31 Dec 1999 14:00:00 HST -10:00", @z.in_time_zone.inspect end with_tz_default nil do assert_equal @s.to_time, @s.in_time_zone @@ -1158,26 +1225,26 @@ class TimeWithZoneMethodsForString < ActiveSupport::TestCase def test_nil_time_zone with_tz_default nil do - assert !@s.in_time_zone.respond_to?(:period), 'no period method' - assert !@u.in_time_zone.respond_to?(:period), 'no period method' - assert !@z.in_time_zone.respond_to?(:period), 'no period method' + assert !@s.in_time_zone.respond_to?(:period), "no period method" + assert !@u.in_time_zone.respond_to?(:period), "no period method" + assert !@z.in_time_zone.respond_to?(:period), "no period method" end end def test_in_time_zone_with_argument - with_tz_default 'Eastern Time (US & Canada)' do # Time.zone will not affect #in_time_zone(zone) - assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @s.in_time_zone('Alaska').inspect - assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @u.in_time_zone('Alaska').inspect - assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @z.in_time_zone('Alaska').inspect - assert_equal 'Sat, 01 Jan 2000 00:00:00 HST -10:00', @s.in_time_zone('Hawaii').inspect - assert_equal 'Fri, 31 Dec 1999 14:00:00 HST -10:00', @u.in_time_zone('Hawaii').inspect - assert_equal 'Fri, 31 Dec 1999 14:00:00 HST -10:00', @z.in_time_zone('Hawaii').inspect - assert_equal 'Sat, 01 Jan 2000 00:00:00 UTC +00:00', @s.in_time_zone('UTC').inspect - assert_equal 'Sat, 01 Jan 2000 00:00:00 UTC +00:00', @u.in_time_zone('UTC').inspect - assert_equal 'Sat, 01 Jan 2000 00:00:00 UTC +00:00', @z.in_time_zone('UTC').inspect - assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @s.in_time_zone(-9.hours).inspect - assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @u.in_time_zone(-9.hours).inspect - assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @z.in_time_zone(-9.hours).inspect + with_tz_default "Eastern Time (US & Canada)" do # Time.zone will not affect #in_time_zone(zone) + assert_equal "Sat, 01 Jan 2000 00:00:00 AKST -09:00", @s.in_time_zone("Alaska").inspect + assert_equal "Fri, 31 Dec 1999 15:00:00 AKST -09:00", @u.in_time_zone("Alaska").inspect + assert_equal "Fri, 31 Dec 1999 15:00:00 AKST -09:00", @z.in_time_zone("Alaska").inspect + assert_equal "Sat, 01 Jan 2000 00:00:00 HST -10:00", @s.in_time_zone("Hawaii").inspect + assert_equal "Fri, 31 Dec 1999 14:00:00 HST -10:00", @u.in_time_zone("Hawaii").inspect + assert_equal "Fri, 31 Dec 1999 14:00:00 HST -10:00", @z.in_time_zone("Hawaii").inspect + assert_equal "Sat, 01 Jan 2000 00:00:00 UTC +00:00", @s.in_time_zone("UTC").inspect + assert_equal "Sat, 01 Jan 2000 00:00:00 UTC +00:00", @u.in_time_zone("UTC").inspect + assert_equal "Sat, 01 Jan 2000 00:00:00 UTC +00:00", @z.in_time_zone("UTC").inspect + assert_equal "Sat, 01 Jan 2000 00:00:00 AKST -09:00", @s.in_time_zone(-9.hours).inspect + assert_equal "Fri, 31 Dec 1999 15:00:00 AKST -09:00", @u.in_time_zone(-9.hours).inspect + assert_equal "Fri, 31 Dec 1999 15:00:00 AKST -09:00", @z.in_time_zone(-9.hours).inspect end end diff --git a/activesupport/test/core_ext/uri_ext_test.rb b/activesupport/test/core_ext/uri_ext_test.rb index 1694fe7e72..0f13ca9c0e 100644 --- a/activesupport/test/core_ext/uri_ext_test.rb +++ b/activesupport/test/core_ext/uri_ext_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'uri' -require 'active_support/core_ext/uri' +require "abstract_unit" +require "uri" +require "active_support/core_ext/uri" class URIExtTest < ActiveSupport::TestCase def test_uri_decode_handle_multibyte diff --git a/activesupport/test/dependencies/conflict.rb b/activesupport/test/dependencies/conflict.rb index e888b7b54c..7eff49bbfa 100644 --- a/activesupport/test/dependencies/conflict.rb +++ b/activesupport/test/dependencies/conflict.rb @@ -1 +1 @@ -Conflict = 1
\ No newline at end of file +Conflict = 1 diff --git a/activesupport/test/dependencies/cross_site_depender.rb b/activesupport/test/dependencies/cross_site_depender.rb index a31015fc5e..fbc3b64f56 100644 --- a/activesupport/test/dependencies/cross_site_depender.rb +++ b/activesupport/test/dependencies/cross_site_depender.rb @@ -1,3 +1,3 @@ class CrossSiteDepender CrossSiteDependency -end
\ No newline at end of file +end diff --git a/activesupport/test/dependencies/mutual_one.rb b/activesupport/test/dependencies/mutual_one.rb index 576eb31711..05f08f82d3 100644 --- a/activesupport/test/dependencies/mutual_one.rb +++ b/activesupport/test/dependencies/mutual_one.rb @@ -1,4 +1,4 @@ $mutual_dependencies_count += 1 -require_dependency 'mutual_two' -require_dependency 'mutual_two.rb' -require_dependency 'mutual_two' +require_dependency "mutual_two" +require_dependency "mutual_two.rb" +require_dependency "mutual_two" diff --git a/activesupport/test/dependencies/mutual_two.rb b/activesupport/test/dependencies/mutual_two.rb index fdbc2dcd84..1d87d334af 100644 --- a/activesupport/test/dependencies/mutual_two.rb +++ b/activesupport/test/dependencies/mutual_two.rb @@ -1,4 +1,4 @@ $mutual_dependencies_count += 1 -require_dependency 'mutual_one.rb' -require_dependency 'mutual_one' -require_dependency 'mutual_one.rb' +require_dependency "mutual_one.rb" +require_dependency "mutual_one" +require_dependency "mutual_one.rb" diff --git a/activesupport/test/dependencies/raises_exception.rb b/activesupport/test/dependencies/raises_exception.rb index dd745ac20e..0a56680fe3 100644 --- a/activesupport/test/dependencies/raises_exception.rb +++ b/activesupport/test/dependencies/raises_exception.rb @@ -1,3 +1,3 @@ $raises_exception_load_count += 1 -raise Exception, 'Loading me failed, so do not add to loaded or history.' +raise Exception, "Loading me failed, so do not add to loaded or history." $raises_exception_load_count += 1 diff --git a/activesupport/test/dependencies/raises_exception_without_blame_file.rb b/activesupport/test/dependencies/raises_exception_without_blame_file.rb index 4b2da6ff30..7c3856b1e6 100644 --- a/activesupport/test/dependencies/raises_exception_without_blame_file.rb +++ b/activesupport/test/dependencies/raises_exception_without_blame_file.rb @@ -1,4 +1,4 @@ -exception = Exception.new('I am not blamable!') +exception = Exception.new("I am not blamable!") class << exception undef_method(:blame_file!) end diff --git a/activesupport/test/dependencies/requires_nonexistent0.rb b/activesupport/test/dependencies/requires_nonexistent0.rb index 7e24b3916c..7f8a0d8419 100644 --- a/activesupport/test/dependencies/requires_nonexistent0.rb +++ b/activesupport/test/dependencies/requires_nonexistent0.rb @@ -1 +1 @@ -require 'RMagickDontExistDude' +require "RMagickDontExistDude" diff --git a/activesupport/test/dependencies/requires_nonexistent1.rb b/activesupport/test/dependencies/requires_nonexistent1.rb index 41e6668164..0055177d67 100644 --- a/activesupport/test/dependencies/requires_nonexistent1.rb +++ b/activesupport/test/dependencies/requires_nonexistent1.rb @@ -1 +1 @@ -require_dependency 'requires_nonexistent0' +require_dependency "requires_nonexistent0" diff --git a/activesupport/test/dependencies/service_one.rb b/activesupport/test/dependencies/service_one.rb index f43bfea235..afc3042269 100644 --- a/activesupport/test/dependencies/service_one.rb +++ b/activesupport/test/dependencies/service_one.rb @@ -2,4 +2,4 @@ $loaded_service_one ||= 0 $loaded_service_one += 1 class ServiceOne -end
\ No newline at end of file +end diff --git a/activesupport/test/dependencies/service_two.rb b/activesupport/test/dependencies/service_two.rb index 5205a78bb8..aabfc3c553 100644 --- a/activesupport/test/dependencies/service_two.rb +++ b/activesupport/test/dependencies/service_two.rb @@ -1,2 +1,2 @@ class ServiceTwo -end
\ No newline at end of file +end diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 757e600646..54068f5a08 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -1,7 +1,7 @@ -require 'abstract_unit' -require 'pp' -require 'active_support/dependencies' -require 'dependencies_test_helpers' +require "abstract_unit" +require "pp" +require "active_support/dependencies" +require "dependencies_test_helpers" module ModuleWithMissing mattr_accessor :missing_count @@ -29,18 +29,18 @@ class DependenciesTest < ActiveSupport::TestCase def test_depend_on_path expected = assert_raises(LoadError) do - Kernel.require 'omgwtfbbq' + Kernel.require "omgwtfbbq" end e = assert_raises(LoadError) do - ActiveSupport::Dependencies.depend_on 'omgwtfbbq' + ActiveSupport::Dependencies.depend_on "omgwtfbbq" end assert_equal expected.path, e.path end def test_require_dependency_accepts_an_object_which_implements_to_path o = Object.new - def o.to_path; 'dependencies/service_one'; end + def o.to_path; "dependencies/service_one"; end assert_nothing_raised { require_dependency o } @@ -51,8 +51,8 @@ class DependenciesTest < ActiveSupport::TestCase def test_tracking_loaded_files with_loading do - require_dependency 'dependencies/service_one' - require_dependency 'dependencies/service_two' + require_dependency "dependencies/service_one" + require_dependency "dependencies/service_two" assert_equal 2, ActiveSupport::Dependencies.loaded.size end ensure @@ -61,8 +61,8 @@ class DependenciesTest < ActiveSupport::TestCase def test_tracking_identical_loaded_files with_loading do - require_dependency 'dependencies/service_one' - require_dependency 'dependencies/service_one' + require_dependency "dependencies/service_one" + require_dependency "dependencies/service_one" assert_equal 1, ActiveSupport::Dependencies.loaded.size end ensure @@ -75,32 +75,33 @@ class DependenciesTest < ActiveSupport::TestCase def test_dependency_which_raises_exception_isnt_added_to_loaded_set with_loading do - filename = 'dependencies/raises_exception' + filename = "dependencies/raises_exception" + expanded = File.expand_path(filename) $raises_exception_load_count = 0 5.times do |count| - e = assert_raise Exception, 'should have loaded dependencies/raises_exception which raises an exception' do + e = assert_raise Exception, "should have loaded dependencies/raises_exception which raises an exception" do require_dependency filename end - assert_equal 'Loading me failed, so do not add to loaded or history.', e.message + assert_equal "Loading me failed, so do not add to loaded or history.", e.message assert_equal count + 1, $raises_exception_load_count - assert_not ActiveSupport::Dependencies.loaded.include?(filename) - assert_not ActiveSupport::Dependencies.history.include?(filename) + assert_not ActiveSupport::Dependencies.loaded.include?(expanded) + assert_not ActiveSupport::Dependencies.history.include?(expanded) end end end def test_dependency_which_raises_doesnt_blindly_call_blame_file! with_loading do - filename = 'dependencies/raises_exception_without_blame_file' + filename = "dependencies/raises_exception_without_blame_file" assert_raises(Exception) { require_dependency filename } end end def test_warnings_should_be_enabled_on_first_load - with_loading 'dependencies' do + with_loading "dependencies" do old_warnings, ActiveSupport::Dependencies.warnings_on_first_load = ActiveSupport::Dependencies.warnings_on_first_load, true filename = "check_warnings" expanded = File.expand_path("#{File.dirname(__FILE__)}/dependencies/#{filename}") @@ -111,41 +112,41 @@ class DependenciesTest < ActiveSupport::TestCase silence_warnings { require_dependency filename } assert_equal 1, $check_warnings_load_count - assert_equal true, $checked_verbose, 'On first load warnings should be enabled.' + assert_equal true, $checked_verbose, "On first load warnings should be enabled." - assert ActiveSupport::Dependencies.loaded.include?(expanded) + assert_includes ActiveSupport::Dependencies.loaded, expanded ActiveSupport::Dependencies.clear assert_not ActiveSupport::Dependencies.loaded.include?(expanded) - assert ActiveSupport::Dependencies.history.include?(expanded) + assert_includes ActiveSupport::Dependencies.history, expanded 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_equal nil, $checked_verbose, "After first load warnings should be left alone." - assert ActiveSupport::Dependencies.loaded.include?(expanded) + assert_includes ActiveSupport::Dependencies.loaded, expanded ActiveSupport::Dependencies.clear assert_not ActiveSupport::Dependencies.loaded.include?(expanded) - assert ActiveSupport::Dependencies.history.include?(expanded) + assert_includes ActiveSupport::Dependencies.history, expanded enable_warnings { require_dependency filename } assert_equal 3, $check_warnings_load_count - assert_equal true, $checked_verbose, 'After first load warnings should be left alone.' + assert_equal true, $checked_verbose, "After first load warnings should be left alone." - assert ActiveSupport::Dependencies.loaded.include?(expanded) + assert_includes ActiveSupport::Dependencies.loaded, expanded ActiveSupport::Dependencies.warnings_on_first_load = old_warnings end end def test_mutual_dependencies_dont_infinite_loop - with_loading 'dependencies' do + with_loading "dependencies" do $mutual_dependencies_count = 0 - assert_nothing_raised { require_dependency 'mutual_one' } + assert_nothing_raised { require_dependency "mutual_one" } assert_equal 2, $mutual_dependencies_count ActiveSupport::Dependencies.clear $mutual_dependencies_count = 0 - assert_nothing_raised { require_dependency 'mutual_two' } + assert_nothing_raised { require_dependency "mutual_two" } assert_equal 2, $mutual_dependencies_count end end @@ -166,7 +167,7 @@ class DependenciesTest < ActiveSupport::TestCase def test_require_dependency_does_not_assume_any_particular_constant_is_defined with_autoloading_fixtures do - require_dependency 'typo' + require_dependency "typo" assert_equal 1, TypO end end @@ -174,7 +175,7 @@ class DependenciesTest < ActiveSupport::TestCase # Regression, see https://github.com/rails/rails/issues/16468. def test_require_dependency_interaction_with_autoloading with_autoloading_fixtures do - require_dependency 'typo' + require_dependency "typo" assert_equal 1, TypO e = assert_raise(LoadError) { Typo } @@ -252,7 +253,7 @@ class DependenciesTest < ActiveSupport::TestCase with_autoloading_fixtures do assert_kind_of Class, ClassFolder::ClassFolderSubclass assert_kind_of Class, ClassFolder - assert_equal 'indeed', ClassFolder::ClassFolderSubclass::ConstantInClassFolder + assert_equal "indeed", ClassFolder::ClassFolderSubclass::ConstantInClassFolder end ensure remove_constants(:ClassFolder) @@ -268,6 +269,28 @@ class DependenciesTest < ActiveSupport::TestCase remove_constants(:ModuleFolder) end + def test_raising_discards_autoloaded_constants + with_autoloading_fixtures do + assert_raises(Exception, "arbitray exception message") { RaisesArbitraryException } + assert_not defined?(A) + assert_not defined?(RaisesArbitraryException) + end + ensure + remove_constants(:A, :RaisesArbitraryException) + end + + def test_throwing_discards_autoloaded_constants + with_autoloading_fixtures do + catch :t do + Throws + end + assert_not defined?(A) + assert_not defined?(Throws) + end + ensure + remove_constants(:A, :Throws) + end + def test_doesnt_break_normal_require path = File.expand_path("../autoloading_fixtures/load_path", __FILE__) original_path = $:.dup @@ -313,7 +336,7 @@ class DependenciesTest < ActiveSupport::TestCase $:.push(path) with_loading do - assert_equal true, require('loaded_constant') + assert_equal true, require("loaded_constant") end ensure remove_constants(:LoadedConstant) @@ -327,7 +350,7 @@ class DependenciesTest < ActiveSupport::TestCase with_loading do Object.module_eval "module LoadedConstant; end" - assert_equal true, require('loaded_constant') + assert_equal true, require("loaded_constant") end ensure remove_constants(:LoadedConstant) @@ -340,8 +363,8 @@ class DependenciesTest < ActiveSupport::TestCase $:.push(path) with_loading do - require 'loaded_constant' - assert_equal false, require('loaded_constant') + require "loaded_constant" + assert_equal false, require("loaded_constant") end ensure remove_constants(:LoadedConstant) @@ -350,7 +373,7 @@ class DependenciesTest < ActiveSupport::TestCase def test_require_raises_load_error_when_file_not_found with_loading do - assert_raise(LoadError) { require 'this_file_dont_exist_dude' } + assert_raise(LoadError) { require "this_file_dont_exist_dude" } end end @@ -360,8 +383,8 @@ class DependenciesTest < ActiveSupport::TestCase $:.push(path) with_loading do - assert_equal true, load('loaded_constant.rb') - assert_equal true, load('loaded_constant.rb') + assert_equal true, load("loaded_constant.rb") + assert_equal true, load("loaded_constant.rb") end ensure remove_constants(:LoadedConstant) @@ -370,7 +393,7 @@ class DependenciesTest < ActiveSupport::TestCase def test_load_raises_load_error_when_file_not_found with_loading do - assert_raise(LoadError) { load 'this_file_dont_exist_dude.rb' } + assert_raise(LoadError) { load "this_file_dont_exist_dude.rb" } end end @@ -406,38 +429,38 @@ class DependenciesTest < ActiveSupport::TestCase end def test_loadable_constants_for_path_should_handle_empty_autoloads - assert_equal [], ActiveSupport::Dependencies.loadable_constants_for_path('hello') + assert_equal [], ActiveSupport::Dependencies.loadable_constants_for_path("hello") end def test_loadable_constants_for_path_should_handle_relative_paths - fake_root = 'dependencies' - relative_root = File.dirname(__FILE__) + '/dependencies' - ['', '/'].each do |suffix| + fake_root = "dependencies" + relative_root = File.dirname(__FILE__) + "/dependencies" + ["", "/"].each do |suffix| with_loading fake_root + suffix do - assert_equal ["A::B"], ActiveSupport::Dependencies.loadable_constants_for_path(relative_root + '/a/b') + assert_equal ["A::B"], ActiveSupport::Dependencies.loadable_constants_for_path(relative_root + "/a/b") end end end def test_loadable_constants_for_path_should_provide_all_results - fake_root = '/usr/apps/backpack' - with_loading fake_root, fake_root + '/lib' do + fake_root = "/usr/apps/backpack" + with_loading fake_root, fake_root + "/lib" do root = ActiveSupport::Dependencies.autoload_paths.first - assert_equal ["Lib::A::B", "A::B"], ActiveSupport::Dependencies.loadable_constants_for_path(root + '/lib/a/b') + assert_equal ["Lib::A::B", "A::B"], ActiveSupport::Dependencies.loadable_constants_for_path(root + "/lib/a/b") end end def test_loadable_constants_for_path_should_uniq_results - fake_root = '/usr/apps/backpack/lib' - with_loading fake_root, fake_root + '/' do + fake_root = "/usr/apps/backpack/lib" + with_loading fake_root, fake_root + "/" do root = ActiveSupport::Dependencies.autoload_paths.first - assert_equal ["A::B"], ActiveSupport::Dependencies.loadable_constants_for_path(root + '/a/b') + assert_equal ["A::B"], ActiveSupport::Dependencies.loadable_constants_for_path(root + "/a/b") end end def test_loadable_constants_with_load_path_without_trailing_slash - path = File.dirname(__FILE__) + '/autoloading_fixtures/class_folder/inline_class.rb' - with_loading 'autoloading_fixtures/class/' do + path = File.dirname(__FILE__) + "/autoloading_fixtures/class_folder/inline_class.rb" + with_loading "autoloading_fixtures/class/" do assert_equal [], ActiveSupport::Dependencies.loadable_constants_for_path(path) end end @@ -501,29 +524,29 @@ class DependenciesTest < ActiveSupport::TestCase end def test_file_search - with_loading 'dependencies' do + 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_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') + assert_equal nil, ActiveSupport::Dependencies.search_for_file("service_three") + assert_equal 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 end def test_file_search_uses_first_in_load_path - with_loading 'dependencies', 'autoloading_fixtures' do + with_loading "dependencies", "autoloading_fixtures" do deps, autoload = ActiveSupport::Dependencies.autoload_paths assert_match %r/dependencies/, deps assert_match %r/autoloading_fixtures/, autoload - assert_equal deps + '/conflict.rb', ActiveSupport::Dependencies.search_for_file('conflict') + assert_equal deps + "/conflict.rb", ActiveSupport::Dependencies.search_for_file("conflict") end - with_loading 'autoloading_fixtures', 'dependencies' do + with_loading "autoloading_fixtures", "dependencies" do autoload, deps = ActiveSupport::Dependencies.autoload_paths assert_match %r/dependencies/, deps assert_match %r/autoloading_fixtures/, autoload - assert_equal autoload + '/conflict.rb', ActiveSupport::Dependencies.search_for_file('conflict') + assert_equal autoload + "/conflict.rb", ActiveSupport::Dependencies.search_for_file("conflict") end end @@ -561,11 +584,11 @@ class DependenciesTest < ActiveSupport::TestCase def test_const_missing_in_anonymous_modules_raises_if_the_constant_belongs_to_Object with_autoloading_fixtures do - require_dependency 'em' + require_dependency "em" mod = Module.new e = assert_raise(NameError) { mod::EM } - assert_equal 'EM cannot be autoloaded from an anonymous class or module', e.message + assert_equal "EM cannot be autoloaded from an anonymous class or module", e.message assert_equal :EM, e.name end ensure @@ -573,7 +596,7 @@ class DependenciesTest < ActiveSupport::TestCase end def test_removal_from_tree_should_be_detected - with_loading 'dependencies' do + with_loading "dependencies" do c = ServiceOne ActiveSupport::Dependencies.clear assert_not defined?(ServiceOne) @@ -587,7 +610,7 @@ class DependenciesTest < ActiveSupport::TestCase end def test_references_should_work - with_loading 'dependencies' do + with_loading "dependencies" do c = ActiveSupport::Dependencies.reference("ServiceOne") service_one_first = ServiceOne assert_equal service_one_first, c.get("ServiceOne") @@ -602,7 +625,7 @@ class DependenciesTest < ActiveSupport::TestCase end def test_constantize_shortcut_for_cached_constant_lookups - with_loading 'dependencies' do + with_loading "dependencies" do assert_equal ServiceOne, ActiveSupport::Dependencies.constantize("ServiceOne") end ensure @@ -610,7 +633,7 @@ class DependenciesTest < ActiveSupport::TestCase end def test_nested_load_error_isnt_rescued - with_loading 'dependencies' do + with_loading "dependencies" do assert_raise(LoadError) do RequiresNonexistent1 end @@ -636,7 +659,7 @@ class DependenciesTest < ActiveSupport::TestCase def test_autoload_once_pathnames_do_not_add_to_autoloaded_constants with_autoloading_fixtures do - pathnames = ActiveSupport::Dependencies.autoload_paths.collect{|p| Pathname.new(p)} + pathnames = ActiveSupport::Dependencies.autoload_paths.collect { |p| Pathname.new(p) } ActiveSupport::Dependencies.autoload_paths = pathnames ActiveSupport::Dependencies.autoload_once_paths = pathnames @@ -654,7 +677,7 @@ class DependenciesTest < ActiveSupport::TestCase def test_application_should_special_case_application_controller with_autoloading_fixtures do - require_dependency 'application' + require_dependency "application" assert_equal 10, ApplicationController assert ActiveSupport::Dependencies.autoloaded?(:ApplicationController) end @@ -664,14 +687,14 @@ class DependenciesTest < ActiveSupport::TestCase def test_preexisting_constants_are_not_marked_as_autoloaded with_autoloading_fixtures do - require_dependency 'em' + require_dependency "em" assert ActiveSupport::Dependencies.autoloaded?(:EM) ActiveSupport::Dependencies.clear end Object.const_set :EM, Class.new with_autoloading_fixtures do - require_dependency 'em' + require_dependency "em" assert ! ActiveSupport::Dependencies.autoloaded?(:EM), "EM shouldn't be marked autoloaded!" ActiveSupport::Dependencies.clear end @@ -733,15 +756,15 @@ class DependenciesTest < ActiveSupport::TestCase end def test_new_contants_in_without_constants - assert_equal [], (ActiveSupport::Dependencies.new_constants_in(Object) { }) - assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? } + assert_equal [], (ActiveSupport::Dependencies.new_constants_in(Object) {}) + assert ActiveSupport::Dependencies.constant_watch_stack.all? { |k,v| v.empty? } end def test_new_constants_in_with_a_single_constant assert_equal ["Hello"], ActiveSupport::Dependencies.new_constants_in(Object) { Object.const_set :Hello, 10 }.map(&:to_s) - assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? } + assert ActiveSupport::Dependencies.constant_watch_stack.all? { |k,v| v.empty? } ensure remove_constants(:Hello) end @@ -758,7 +781,7 @@ class DependenciesTest < ActiveSupport::TestCase end assert_equal ["OuterAfter", "OuterBefore"], outer.sort.map(&:to_s) - assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? } + assert ActiveSupport::Dependencies.constant_watch_stack.all? { |k,v| v.empty? } ensure remove_constants(:OuterBefore, :Inner, :OuterAfter) end @@ -777,7 +800,7 @@ class DependenciesTest < ActiveSupport::TestCase M.const_set :OuterAfter, 30 end assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort - assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? } + assert ActiveSupport::Dependencies.constant_watch_stack.all? { |k,v| v.empty? } ensure remove_constants(:M) end @@ -795,7 +818,7 @@ class DependenciesTest < ActiveSupport::TestCase M.const_set :OuterAfter, 30 end assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort - assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? } + assert ActiveSupport::Dependencies.constant_watch_stack.all? { |k,v| v.empty? } ensure remove_constants(:M) end @@ -818,7 +841,7 @@ class DependenciesTest < ActiveSupport::TestCase assert_not defined?(MultipleConstantFile) assert_not defined?(SiblingConstant) - require_dependency 'multiple_constant_file' + require_dependency "multiple_constant_file" assert defined?(MultipleConstantFile) assert defined?(SiblingConstant) assert ActiveSupport::Dependencies.autoloaded?(:MultipleConstantFile) @@ -858,7 +881,7 @@ class DependenciesTest < ActiveSupport::TestCase assert_not defined?(ClassFolder::NestedClass) assert_not defined?(ClassFolder::SiblingClass) - require_dependency 'class_folder/nested_class' + require_dependency "class_folder/nested_class" assert defined?(ClassFolder::NestedClass) assert defined?(ClassFolder::SiblingClass) @@ -936,7 +959,7 @@ class DependenciesTest < ActiveSupport::TestCase e = assert_raise NameError do ::RaisesNameError::FooBarBaz.object_id end - assert_equal 'uninitialized constant RaisesNameError::FooBarBaz', e.message + assert_equal "uninitialized constant RaisesNameError::FooBarBaz", e.message assert !defined?(::RaisesNameError), "::RaisesNameError is defined but it should have failed!" end @@ -951,8 +974,8 @@ class DependenciesTest < ActiveSupport::TestCase end def test_remove_constant_handles_double_colon_at_start - Object.const_set 'DeleteMe', Module.new - DeleteMe.const_set 'OrMe', Module.new + Object.const_set "DeleteMe", Module.new + DeleteMe.const_set "OrMe", Module.new ActiveSupport::Dependencies.remove_constant "::DeleteMe::OrMe" assert_not defined?(DeleteMe::OrMe) assert defined?(DeleteMe) @@ -963,9 +986,9 @@ class DependenciesTest < ActiveSupport::TestCase end def test_remove_constant_does_not_trigger_loading_autoloads - constant = 'ShouldNotBeAutoloaded' + constant = "ShouldNotBeAutoloaded" Object.class_eval do - autoload constant, File.expand_path('../autoloading_fixtures/should_not_be_required', __FILE__) + autoload constant, File.expand_path("../autoloading_fixtures/should_not_be_required", __FILE__) end assert_nil ActiveSupport::Dependencies.remove_constant(constant), "Kernel#autoload has been triggered by remove_constant" @@ -978,8 +1001,8 @@ class DependenciesTest < ActiveSupport::TestCase with_autoloading_fixtures do _ = ::A # assignment to silence parse-time warning "possibly useless use of :: in void context" _ = ::A::B # assignment to silence parse-time warning "possibly useless use of :: in void context" - ActiveSupport::Dependencies.remove_constant('A') - ActiveSupport::Dependencies.remove_constant('A::B') + ActiveSupport::Dependencies.remove_constant("A") + ActiveSupport::Dependencies.remove_constant("A::B") assert_not defined?(A) end ensure @@ -1013,10 +1036,9 @@ class DependenciesTest < ActiveSupport::TestCase remove_constants(:A) end - def test_autoload_once_paths_should_behave_when_recursively_loading old_path = ActiveSupport::Dependencies.autoload_once_paths - with_loading 'dependencies', 'autoloading_fixtures' do + with_loading "dependencies", "autoloading_fixtures" do ActiveSupport::Dependencies.autoload_once_paths = [ActiveSupport::Dependencies.autoload_paths.last] assert_not defined?(CrossSiteDependency) assert_nothing_raised { CrossSiteDepender.nil? } @@ -1037,21 +1059,13 @@ class DependenciesTest < ActiveSupport::TestCase end def test_load_and_require_stay_private - assert Object.private_methods.include?(:load) - assert Object.private_methods.include?(:require) + assert_includes Object.private_methods, :load + assert_includes Object.private_methods, :require ActiveSupport::Dependencies.unhook! - assert Object.private_methods.include?(:load) - assert Object.private_methods.include?(:require) - ensure - ActiveSupport::Dependencies.hook! - end - - def test_unhook - ActiveSupport::Dependencies.unhook! - assert !Module.new.respond_to?(:const_missing_without_dependencies) - assert !Module.new.respond_to?(:load_without_new_constant_marking) + assert_includes Object.private_methods, :load + assert_includes Object.private_methods, :require ensure ActiveSupport::Dependencies.hook! end diff --git a/activesupport/test/dependencies_test_helpers.rb b/activesupport/test/dependencies_test_helpers.rb index e4d5197112..9bc63ed89e 100644 --- a/activesupport/test/dependencies_test_helpers.rb +++ b/activesupport/test/dependencies_test_helpers.rb @@ -17,7 +17,7 @@ module DependenciesTestHelpers end def with_autoloading_fixtures(&block) - with_loading 'autoloading_fixtures', &block + with_loading "autoloading_fixtures", &block end def remove_constants(*constants) @@ -25,4 +25,4 @@ module DependenciesTestHelpers Object.send(:remove_const, constant) if Object.const_defined?(constant) end end -end
\ No newline at end of file +end diff --git a/activesupport/test/deprecation/method_wrappers_test.rb b/activesupport/test/deprecation/method_wrappers_test.rb index 9a4ca2b217..85d057bb02 100644 --- a/activesupport/test/deprecation/method_wrappers_test.rb +++ b/activesupport/test/deprecation/method_wrappers_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/deprecation' +require "abstract_unit" +require "active_support/deprecation" class MethodWrappersTest < ActiveSupport::TestCase def setup @@ -11,7 +11,7 @@ class MethodWrappersTest < ActiveSupport::TestCase def test_deprecate_methods_warning_default warning = /old_method is deprecated and will be removed from Rails \d.\d \(use new_method instead\)/ - ActiveSupport::Deprecation.deprecate_methods(@klass, :old_method => :new_method) + ActiveSupport::Deprecation.deprecate_methods(@klass, old_method: :new_method) assert_deprecated(warning) { assert_equal "abc", @klass.new.old_method } end @@ -19,7 +19,7 @@ class MethodWrappersTest < ActiveSupport::TestCase def test_deprecate_methods_warning_with_optional_deprecator warning = /old_method is deprecated and will be removed from MyGem next-release \(use new_method instead\)/ deprecator = ActiveSupport::Deprecation.new("next-release", "MyGem") - ActiveSupport::Deprecation.deprecate_methods(@klass, :old_method => :new_method, :deprecator => deprecator) + ActiveSupport::Deprecation.deprecate_methods(@klass, old_method: :new_method, deprecator: deprecator) assert_deprecated(warning, deprecator) { assert_equal "abc", @klass.new.old_method } end @@ -27,7 +27,7 @@ class MethodWrappersTest < ActiveSupport::TestCase def test_deprecate_methods_warning_when_deprecated_with_custom_deprecator warning = /old_method is deprecated and will be removed from MyGem next-release \(use new_method instead\)/ deprecator = ActiveSupport::Deprecation.new("next-release", "MyGem") - deprecator.deprecate_methods(@klass, :old_method => :new_method) + deprecator.deprecate_methods(@klass, old_method: :new_method) assert_deprecated(warning, deprecator) { assert_equal "abc", @klass.new.old_method } end diff --git a/activesupport/test/deprecation/proxy_wrappers_test.rb b/activesupport/test/deprecation/proxy_wrappers_test.rb index e4f0f0f7c2..67afd75c44 100644 --- a/activesupport/test/deprecation/proxy_wrappers_test.rb +++ b/activesupport/test/deprecation/proxy_wrappers_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/deprecation' +require "abstract_unit" +require "active_support/deprecation" class ProxyWrappersTest < ActiveSupport::TestCase Waffles = false diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb index 45c88b79cb..e2a3331cfa 100644 --- a/activesupport/test/deprecation_test.rb +++ b/activesupport/test/deprecation_test.rb @@ -1,16 +1,16 @@ -require 'abstract_unit' -require 'active_support/testing/stream' +require "abstract_unit" +require "active_support/testing/stream" class Deprecatee def initialize @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request) - @_request = 'there we go' + @_request = "there we go" end def request; @_request end def old_request; @request end def partially(foo = nil) - ActiveSupport::Deprecation.warn('calling with foo=nil is out') if foo.nil? + ActiveSupport::Deprecation.warn("calling with foo=nil is out") if foo.nil? end def not() 2 end @@ -24,7 +24,7 @@ class Deprecatee def c; end def d; end def e; end - deprecate :a, :b, :c => :e, :d => "you now need to do something extra for this one" + deprecate :a, :b, c: :e, d: "you now need to do something extra for this one" def f=(v); end deprecate :f= @@ -32,10 +32,9 @@ class Deprecatee module B C = 1 end - A = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Deprecatee::A', 'Deprecatee::B::C') + A = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("Deprecatee::A", "Deprecatee::B::C") end - class DeprecationTest < ActiveSupport::TestCase include ActiveSupport::Testing::Stream @@ -79,7 +78,7 @@ class DeprecationTest < ActiveSupport::TestCase end def test_deprecate_object - deprecated_object = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(Object.new, ':bomb:') + deprecated_object = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(Object.new, ":bomb:") assert_deprecated(/:bomb:/) { deprecated_object.to_s } end @@ -104,7 +103,7 @@ class DeprecationTest < ActiveSupport::TestCase def test_raise_behaviour ActiveSupport::Deprecation.behavior = :raise - message = 'Revise this deprecated stuff now!' + message = "Revise this deprecated stuff now!" callstack = caller_locations e = assert_raise ActiveSupport::DeprecationException do @@ -119,7 +118,7 @@ class DeprecationTest < ActiveSupport::TestCase behavior = ActiveSupport::Deprecation.behavior.first content = capture(:stderr) { - assert_nil behavior.call('Some error!', ['call stack!']) + assert_nil behavior.call("Some error!", ["call stack!"]) } assert_match(/Some error!/, content) assert_match(/call stack!/, content) @@ -129,7 +128,7 @@ class DeprecationTest < ActiveSupport::TestCase ActiveSupport::Deprecation.behavior = :stderr content = capture(:stderr) { - ActiveSupport::Deprecation.warn('Instance error!', ['instance call stack!']) + ActiveSupport::Deprecation.warn("Instance error!", ["instance call stack!"]) } assert_match(/Instance error!/, content) @@ -141,16 +140,16 @@ class DeprecationTest < ActiveSupport::TestCase behavior = ActiveSupport::Deprecation.behavior.first stderr_output = capture(:stderr) { - assert_nil behavior.call('Some error!', ['call stack!']) + assert_nil behavior.call("Some error!", ["call stack!"]) } - assert stderr_output.blank? + assert stderr_output.empty? end def test_deprecated_instance_variable_proxy assert_not_deprecated { @dtc.request.size } - assert_deprecated('@request.size') { assert_equal @dtc.request.size, @dtc.old_request.size } - assert_deprecated('@request.to_s') { assert_equal @dtc.request.to_s, @dtc.old_request.to_s } + assert_deprecated("@request.size") { assert_equal @dtc.request.size, @dtc.old_request.size } + assert_deprecated("@request.to_s") { assert_equal @dtc.request.to_s, @dtc.old_request.to_s } end def test_deprecated_instance_variable_proxy_shouldnt_warn_on_inspect @@ -159,7 +158,7 @@ class DeprecationTest < ActiveSupport::TestCase def test_deprecated_constant_proxy assert_not_deprecated { Deprecatee::B::C } - assert_deprecated('Deprecatee::A') { assert_equal Deprecatee::B::C, Deprecatee::A } + assert_deprecated("Deprecatee::A") { assert_equal Deprecatee::B::C, Deprecatee::A } assert_not_deprecated { assert_equal Deprecatee::B::C.class, Deprecatee::A.class } end @@ -178,12 +177,12 @@ class DeprecationTest < ActiveSupport::TestCase end def test_assert_deprecated_matches_any_warning - assert_deprecated 'abc' do - ActiveSupport::Deprecation.warn 'abc' - ActiveSupport::Deprecation.warn 'def' + assert_deprecated "abc" do + ActiveSupport::Deprecation.warn "abc" + ActiveSupport::Deprecation.warn "def" end rescue Minitest::Assertion - flunk 'assert_deprecated should match any warning in block, not just the last one' + flunk "assert_deprecated should match any warning in block, not just the last one" end def test_assert_not_deprecated_returns_result_of_block @@ -191,17 +190,17 @@ class DeprecationTest < ActiveSupport::TestCase end def test_assert_deprecated_returns_result_of_block - result = assert_deprecated('abc') do - ActiveSupport::Deprecation.warn 'abc' + result = assert_deprecated("abc") do + ActiveSupport::Deprecation.warn "abc" 123 end assert_equal 123, result end def test_assert_deprecated_warn_work_with_default_behavior - ActiveSupport::Deprecation.instance_variable_set('@behavior', nil) - assert_deprecated('abc') do - ActiveSupport::Deprecation.warn 'abc' + ActiveSupport::Deprecation.instance_variable_set("@behavior", nil) + assert_deprecated("abc") do + ActiveSupport::Deprecation.warn "abc" end end @@ -280,7 +279,7 @@ class DeprecationTest < ActiveSupport::TestCase def test_deprecated_constant_with_deprecator_given deprecator = deprecator_with_messages klass = Class.new - klass.const_set(:OLD, ActiveSupport::Deprecation::DeprecatedConstantProxy.new('klass::OLD', 'Object', deprecator) ) + klass.const_set(:OLD, ActiveSupport::Deprecation::DeprecatedConstantProxy.new("klass::OLD", "Object", deprecator) ) assert_difference("deprecator.messages.size") do klass::OLD.to_s end @@ -341,7 +340,7 @@ class DeprecationTest < ActiveSupport::TestCase end def test_default_deprecation_horizon_should_always_bigger_than_current_rails_version - assert ActiveSupport::Deprecation.new.deprecation_horizon > ActiveSupport::VERSION::STRING + assert_operator ActiveSupport::Deprecation.new.deprecation_horizon, :>, ActiveSupport::VERSION::STRING end def test_default_gem_name @@ -353,7 +352,7 @@ class DeprecationTest < ActiveSupport::TestCase end def test_custom_gem_name - deprecator = ActiveSupport::Deprecation.new('2.0', 'Custom') + deprecator = ActiveSupport::Deprecation.new("2.0", "Custom") deprecator.send(:deprecated_method_warning, :deprecated_method, "You are calling deprecated method").tap do |message| assert_match(/is deprecated and will be removed from Custom/, message) @@ -364,11 +363,10 @@ class DeprecationTest < ActiveSupport::TestCase def deprecator_with_messages klass = Class.new(ActiveSupport::Deprecation) deprecator = klass.new - deprecator.behavior = Proc.new{|message, callstack| deprecator.messages << message} + deprecator.behavior = Proc.new { |message, callstack| deprecator.messages << message } def deprecator.messages @messages ||= [] end deprecator end - end diff --git a/activesupport/test/descendants_tracker_test_cases.rb b/activesupport/test/descendants_tracker_test_cases.rb index 69e046998e..09c5ce1f07 100644 --- a/activesupport/test/descendants_tracker_test_cases.rb +++ b/activesupport/test/descendants_tracker_test_cases.rb @@ -1,4 +1,4 @@ -require 'set' +require "set" module DescendantsTrackerTestCases class Parent @@ -42,24 +42,24 @@ module DescendantsTrackerTestCases protected - def assert_equal_sets(expected, actual) - assert_equal Set.new(expected), Set.new(actual) - end - - def mark_as_autoloaded(*klasses) - # If ActiveSupport::Dependencies is not loaded, forget about autoloading. - # This allows using AS::DescendantsTracker without AS::Dependencies. - if defined? ActiveSupport::Dependencies - old_autoloaded = ActiveSupport::Dependencies.autoloaded_constants.dup - ActiveSupport::Dependencies.autoloaded_constants = klasses.map(&:name) + def assert_equal_sets(expected, actual) + assert_equal Set.new(expected), Set.new(actual) end - old_descendants = ActiveSupport::DescendantsTracker.class_eval("@@direct_descendants").dup - old_descendants.each { |k, v| old_descendants[k] = v.dup } + def mark_as_autoloaded(*klasses) + # If ActiveSupport::Dependencies is not loaded, forget about autoloading. + # This allows using AS::DescendantsTracker without AS::Dependencies. + if defined? ActiveSupport::Dependencies + old_autoloaded = ActiveSupport::Dependencies.autoloaded_constants.dup + ActiveSupport::Dependencies.autoloaded_constants = klasses.map(&:name) + end + + old_descendants = ActiveSupport::DescendantsTracker.class_eval("@@direct_descendants").dup + old_descendants.each { |k, v| old_descendants[k] = v.dup } - yield - ensure - ActiveSupport::Dependencies.autoloaded_constants = old_autoloaded if defined? ActiveSupport::Dependencies - ActiveSupport::DescendantsTracker.class_eval("@@direct_descendants").replace(old_descendants) - end + yield + ensure + ActiveSupport::Dependencies.autoloaded_constants = old_autoloaded if defined? ActiveSupport::Dependencies + ActiveSupport::DescendantsTracker.class_eval("@@direct_descendants").replace(old_descendants) + end end diff --git a/activesupport/test/descendants_tracker_with_autoloading_test.rb b/activesupport/test/descendants_tracker_with_autoloading_test.rb index a2ae066a21..e202667a8a 100644 --- a/activesupport/test/descendants_tracker_with_autoloading_test.rb +++ b/activesupport/test/descendants_tracker_with_autoloading_test.rb @@ -1,7 +1,7 @@ -require 'abstract_unit' -require 'active_support/descendants_tracker' -require 'active_support/dependencies' -require 'descendants_tracker_test_cases' +require "abstract_unit" +require "active_support/descendants_tracker" +require "active_support/dependencies" +require "descendants_tracker_test_cases" class DescendantsTrackerWithAutoloadingTest < ActiveSupport::TestCase include DescendantsTrackerTestCases diff --git a/activesupport/test/descendants_tracker_without_autoloading_test.rb b/activesupport/test/descendants_tracker_without_autoloading_test.rb index 00b449af51..72adc30ace 100644 --- a/activesupport/test/descendants_tracker_without_autoloading_test.rb +++ b/activesupport/test/descendants_tracker_without_autoloading_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'active_support/descendants_tracker' -require 'descendants_tracker_test_cases' +require "abstract_unit" +require "active_support/descendants_tracker" +require "descendants_tracker_test_cases" class DescendantsTrackerWithoutAutoloadingTest < ActiveSupport::TestCase include DescendantsTrackerTestCases diff --git a/activesupport/test/evented_file_update_checker_test.rb b/activesupport/test/evented_file_update_checker_test.rb index bc3f77bd54..77d8dcb0f8 100644 --- a/activesupport/test/evented_file_update_checker_test.rb +++ b/activesupport/test/evented_file_update_checker_test.rb @@ -1,17 +1,17 @@ -require 'abstract_unit' -require 'pathname' -require 'file_update_checker_shared_tests' +require "abstract_unit" +require "pathname" +require "file_update_checker_shared_tests" class EventedFileUpdateCheckerTest < ActiveSupport::TestCase include FileUpdateCheckerSharedTests def setup - skip if ENV['LISTEN'] == '0' + skip if ENV["LISTEN"] == "0" super end def new_checker(files = [], dirs = {}, &block) - ActiveSupport::EventedFileUpdateChecker.new(files, dirs, &block).tap do + ActiveSupport::EventedFileUpdateChecker.new(files, dirs, &block).tap do |c| wait end end @@ -34,6 +34,50 @@ class EventedFileUpdateCheckerTest < ActiveSupport::TestCase super wait end + + test "notifies forked processes" do + jruby_skip "Forking not available on JRuby" + + FileUtils.touch(tmpfiles) + + checker = new_checker(tmpfiles) {} + assert !checker.updated? + + # Pipes used for flow controll across fork. + boot_reader, boot_writer = IO.pipe + touch_reader, touch_writer = IO.pipe + + pid = fork do + assert checker.updated? + + # Clear previous check value. + checker.execute + assert !checker.updated? + + # Fork is booted, ready for file to be touched + # notify parent process. + boot_writer.write("booted") + + # Wait for parent process to signal that file + # has been touched. + IO.select([touch_reader]) + + assert checker.updated? + end + + assert pid + + # Wait for fork to be booted before touching files. + IO.select([boot_reader]) + touch(tmpfiles) + + # Notify fork that files have been touched. + touch_writer.write("touched") + + assert checker.updated? + + Process.wait(pid) + end end class EventedFileUpdateCheckerPathHelperTest < ActiveSupport::TestCase @@ -45,33 +89,33 @@ class EventedFileUpdateCheckerPathHelperTest < ActiveSupport::TestCase @ph = ActiveSupport::EventedFileUpdateChecker::PathHelper.new end - test '#xpath returns the expanded path as a Pathname object' do + test "#xpath returns the expanded path as a Pathname object" do assert_equal pn(__FILE__).expand_path, @ph.xpath(__FILE__) end - test '#normalize_extension returns a bare extension as is' do - assert_equal 'rb', @ph.normalize_extension('rb') + test "#normalize_extension returns a bare extension as is" do + assert_equal "rb", @ph.normalize_extension("rb") end - test '#normalize_extension removes a leading dot' do - assert_equal 'rb', @ph.normalize_extension('.rb') + test "#normalize_extension removes a leading dot" do + assert_equal "rb", @ph.normalize_extension(".rb") end - test '#normalize_extension supports symbols' do - assert_equal 'rb', @ph.normalize_extension(:rb) + test "#normalize_extension supports symbols" do + assert_equal "rb", @ph.normalize_extension(:rb) end - test '#longest_common_subpath finds the longest common subpath, if there is one' do + test "#longest_common_subpath finds the longest common subpath, if there is one" do paths = %w( /foo/bar /foo/baz /foo/bar/baz/woo/zoo ).map { |path| pn(path) } - assert_equal pn('/foo'), @ph.longest_common_subpath(paths) + assert_equal pn("/foo"), @ph.longest_common_subpath(paths) end - test '#longest_common_subpath returns the root directory as an edge case' do + test "#longest_common_subpath returns the root directory as an edge case" do paths = %w( /foo/bar /foo/baz @@ -79,30 +123,30 @@ class EventedFileUpdateCheckerPathHelperTest < ActiveSupport::TestCase /wadus ).map { |path| pn(path) } - assert_equal pn('/'), @ph.longest_common_subpath(paths) + assert_equal pn("/"), @ph.longest_common_subpath(paths) end - test '#longest_common_subpath returns nil for an empty collection' do + test "#longest_common_subpath returns nil for an empty collection" do assert_nil @ph.longest_common_subpath([]) end - test '#existing_parent returns the most specific existing ascendant' do + test "#existing_parent returns the most specific existing ascendant" do wd = Pathname.getwd assert_equal wd, @ph.existing_parent(wd) - assert_equal wd, @ph.existing_parent(wd.join('non-existing/directory')) - assert_equal pn('/'), @ph.existing_parent(pn('/non-existing/directory')) + assert_equal wd, @ph.existing_parent(wd.join("non-existing/directory")) + assert_equal pn("/"), @ph.existing_parent(pn("/non-existing/directory")) end - test '#filter_out_descendants returns the same collection if there are no descendants (empty)' do + test "#filter_out_descendants returns the same collection if there are no descendants (empty)" do assert_equal [], @ph.filter_out_descendants([]) end - test '#filter_out_descendants returns the same collection if there are no descendants (one)' do - assert_equal ['/foo'], @ph.filter_out_descendants(['/foo']) + test "#filter_out_descendants returns the same collection if there are no descendants (one)" do + assert_equal ["/foo"], @ph.filter_out_descendants(["/foo"]) end - test '#filter_out_descendants returns the same collection if there are no descendants (several)' do + test "#filter_out_descendants returns the same collection if there are no descendants (several)" do paths = %w( /Rails.root/app/controllers /Rails.root/app/models @@ -112,7 +156,7 @@ class EventedFileUpdateCheckerPathHelperTest < ActiveSupport::TestCase assert_equal paths, @ph.filter_out_descendants(paths) end - test '#filter_out_descendants filters out descendants preserving order' do + test "#filter_out_descendants filters out descendants preserving order" do paths = %w( /Rails.root/app/controllers /Rails.root/app/controllers/concerns @@ -124,7 +168,7 @@ class EventedFileUpdateCheckerPathHelperTest < ActiveSupport::TestCase assert_equal paths.values_at(0, 2, 4), @ph.filter_out_descendants(paths) end - test '#filter_out_descendants works on path units' do + test "#filter_out_descendants works on path units" do paths = %w( /foo/bar /foo/barrrr @@ -133,7 +177,7 @@ class EventedFileUpdateCheckerPathHelperTest < ActiveSupport::TestCase assert_equal paths, @ph.filter_out_descendants(paths) end - test '#filter_out_descendants deals correctly with the root directory' do + test "#filter_out_descendants deals correctly with the root directory" do paths = %w( / /foo @@ -143,7 +187,7 @@ class EventedFileUpdateCheckerPathHelperTest < ActiveSupport::TestCase assert_equal paths.values_at(0), @ph.filter_out_descendants(paths) end - test '#filter_out_descendants preserves duplicates' do + test "#filter_out_descendants preserves duplicates" do paths = %w( /foo /foo/bar diff --git a/activesupport/test/executor_test.rb b/activesupport/test/executor_test.rb new file mode 100644 index 0000000000..03ec6020c3 --- /dev/null +++ b/activesupport/test/executor_test.rb @@ -0,0 +1,240 @@ +require "abstract_unit" + +class ExecutorTest < ActiveSupport::TestCase + class DummyError < RuntimeError + end + + def test_wrap_invokes_callbacks + called = [] + executor.to_run { called << :run } + executor.to_complete { called << :complete } + + executor.wrap do + called << :body + end + + assert_equal [:run, :body, :complete], called + end + + def test_callbacks_share_state + result = false + executor.to_run { @foo = true } + executor.to_complete { result = @foo } + + executor.wrap {} + + assert result + end + + def test_separated_calls_invoke_callbacks + called = [] + executor.to_run { called << :run } + executor.to_complete { called << :complete } + + state = executor.run! + called << :body + state.complete! + + assert_equal [:run, :body, :complete], called + end + + def test_exceptions_unwind + called = [] + executor.to_run { called << :run_1 } + executor.to_run { raise DummyError } + executor.to_run { called << :run_2 } + executor.to_complete { called << :complete } + + assert_raises(DummyError) do + executor.wrap { called << :body } + end + + assert_equal [:run_1, :complete], called + end + + def test_avoids_double_wrapping + called = [] + executor.to_run { called << :run } + executor.to_complete { called << :complete } + + executor.wrap do + called << :early + executor.wrap do + called << :body + end + called << :late + end + + assert_equal [:run, :early, :body, :late, :complete], called + end + + def test_hooks_carry_state + supplied_state = :none + + hook = Class.new do + define_method(:run) do + :some_state + end + + define_method(:complete) do |state| + supplied_state = state + end + end.new + + executor.register_hook(hook) + + executor.wrap {} + + assert_equal :some_state, supplied_state + end + + def test_nil_state_is_sufficient + supplied_state = :none + + hook = Class.new do + define_method(:run) do + nil + end + + define_method(:complete) do |state| + supplied_state = state + end + end.new + + executor.register_hook(hook) + + executor.wrap {} + + assert_equal nil, supplied_state + end + + def test_exception_skips_uninvoked_hook + supplied_state = :none + + hook = Class.new do + define_method(:run) do + :some_state + end + + define_method(:complete) do |state| + supplied_state = state + end + end.new + + executor.to_run do + raise DummyError + end + executor.register_hook(hook) + + assert_raises(DummyError) do + executor.wrap {} + end + + assert_equal :none, supplied_state + end + + def test_exception_unwinds_invoked_hook + supplied_state = :none + + hook = Class.new do + define_method(:run) do + :some_state + end + + define_method(:complete) do |state| + supplied_state = state + end + end.new + + executor.register_hook(hook) + executor.to_run do + raise DummyError + end + + assert_raises(DummyError) do + executor.wrap {} + end + + assert_equal :some_state, supplied_state + end + + def test_hook_insertion_order + invoked = [] + supplied_state = [] + + hook_class = Class.new do + attr_accessor :letter + + define_method(:initialize) do |letter| + self.letter = letter + end + + define_method(:run) do + invoked << :"run_#{letter}" + :"state_#{letter}" + end + + define_method(:complete) do |state| + invoked << :"complete_#{letter}" + supplied_state << state + end + end + + executor.register_hook(hook_class.new(:a)) + executor.register_hook(hook_class.new(:b)) + executor.register_hook(hook_class.new(:c), outer: true) + executor.register_hook(hook_class.new(:d)) + + executor.wrap {} + + assert_equal [:run_c, :run_a, :run_b, :run_d, :complete_a, :complete_b, :complete_d, :complete_c], invoked + assert_equal [:state_a, :state_b, :state_d, :state_c], supplied_state + end + + def test_class_serial_is_unaffected + skip if !defined?(RubyVM) + + hook = Class.new do + define_method(:run) do + nil + end + + define_method(:complete) do |state| + nil + end + end.new + + executor.register_hook(hook) + + before = RubyVM.stat(:class_serial) + executor.wrap {} + executor.wrap {} + executor.wrap {} + after = RubyVM.stat(:class_serial) + + assert_equal before, after + end + + def test_separate_classes_can_wrap + other_executor = Class.new(ActiveSupport::Executor) + + called = [] + executor.to_run { called << :run } + executor.to_complete { called << :complete } + other_executor.to_run { called << :other_run } + other_executor.to_complete { called << :other_complete } + + executor.wrap do + other_executor.wrap do + called << :body + end + end + + assert_equal [:run, :other_run, :body, :other_complete, :complete], called + end + + private + def executor + @executor ||= Class.new(ActiveSupport::Executor) + end +end diff --git a/activesupport/test/file_update_checker_shared_tests.rb b/activesupport/test/file_update_checker_shared_tests.rb index 9c07e38fe5..48cd387196 100644 --- a/activesupport/test/file_update_checker_shared_tests.rb +++ b/activesupport/test/file_update_checker_shared_tests.rb @@ -1,27 +1,29 @@ -require 'fileutils' +require "fileutils" module FileUpdateCheckerSharedTests extend ActiveSupport::Testing::Declarative include FileUtils def tmpdir - @tmpdir ||= Dir.mktmpdir(nil, __dir__) + @tmpdir end def tmpfile(name) - "#{tmpdir}/#{name}" + File.join(tmpdir, name) end def tmpfiles @tmpfiles ||= %w(foo.rb bar.rb baz.rb).map { |f| tmpfile(f) } end - def teardown - FileUtils.rm_rf(@tmpdir) if defined? @tmpdir + def run(*args) + capture_exceptions do + Dir.mktmpdir(nil, __dir__) { |dir| @tmpdir = dir; super } + end end - test 'should not execute the block if no paths are given' do - silence_warnings { require 'listen' } + test "should not execute the block if no paths are given" do + silence_warnings { require "listen" } i = 0 checker = new_checker { i += 1 } @@ -30,7 +32,7 @@ module FileUpdateCheckerSharedTests assert_equal 0, i end - test 'should not execute the block if no files change' do + test "should not execute the block if no files change" do i = 0 FileUtils.touch(tmpfiles) @@ -41,18 +43,19 @@ module FileUpdateCheckerSharedTests assert_equal 0, i end - test 'should execute the block once when files are created' do + test "should execute the block once when files are created" do i = 0 checker = new_checker(tmpfiles) { i += 1 } touch(tmpfiles) + wait assert checker.execute_if_updated assert_equal 1, i end - test 'should execute the block once when files are modified' do + test "should execute the block once when files are modified" do i = 0 FileUtils.touch(tmpfiles) @@ -60,12 +63,13 @@ module FileUpdateCheckerSharedTests checker = new_checker(tmpfiles) { i += 1 } touch(tmpfiles) + wait assert checker.execute_if_updated assert_equal 1, i end - test 'should execute the block once when files are deleted' do + test "should execute the block once when files are deleted" do i = 0 FileUtils.touch(tmpfiles) @@ -73,25 +77,25 @@ module FileUpdateCheckerSharedTests checker = new_checker(tmpfiles) { i += 1 } rm_f(tmpfiles) + wait assert checker.execute_if_updated assert_equal 1, i end - - test 'updated should become true when watched files are created' do + test "updated should become true when watched files are created" do i = 0 checker = new_checker(tmpfiles) { i += 1 } assert !checker.updated? touch(tmpfiles) + wait assert checker.updated? end - - test 'updated should become true when watched files are modified' do + test "updated should become true when watched files are modified" do i = 0 FileUtils.touch(tmpfiles) @@ -100,11 +104,12 @@ module FileUpdateCheckerSharedTests assert !checker.updated? touch(tmpfiles) + wait assert checker.updated? end - test 'updated should become true when watched files are deleted' do + test "updated should become true when watched files are deleted" do i = 0 FileUtils.touch(tmpfiles) @@ -113,11 +118,12 @@ module FileUpdateCheckerSharedTests assert !checker.updated? rm_f(tmpfiles) + wait assert checker.updated? end - test 'should be robust to handle files with wrong modified time' do + test "should be robust to handle files with wrong modified time" do i = 0 FileUtils.touch(tmpfiles) @@ -129,112 +135,140 @@ module FileUpdateCheckerSharedTests checker = new_checker(tmpfiles) { i += 1 } touch(tmpfiles[1..-1]) + wait + + assert checker.execute_if_updated + assert_equal 1, i + end + + test "should return max_time for files with mtime = Time.at(0)" do + i = 0 + + FileUtils.touch(tmpfiles) + + time = Time.at(0) # wrong mtime from the future + File.utime(time, time, tmpfiles[0]) + + checker = new_checker(tmpfiles) { i += 1 } + + touch(tmpfiles[1..-1]) + wait assert checker.execute_if_updated assert_equal 1, i end - test 'should cache updated result until execute' do + test "should cache updated result until execute" do i = 0 checker = new_checker(tmpfiles) { i += 1 } assert !checker.updated? touch(tmpfiles) + wait assert checker.updated? checker.execute assert !checker.updated? end - test 'should execute the block if files change in a watched directory one extension' do + test "should execute the block if files change in a watched directory one extension" do i = 0 checker = new_checker([], tmpdir => :rb) { i += 1 } - touch(tmpfile('foo.rb')) + touch(tmpfile("foo.rb")) + wait assert checker.execute_if_updated assert_equal 1, i end - test 'should execute the block if files change in a watched directory several extensions' do + test "should execute the block if files change in a watched directory several extensions" do i = 0 checker = new_checker([], tmpdir => [:rb, :txt]) { i += 1 } - touch(tmpfile('foo.rb')) + touch(tmpfile("foo.rb")) + wait assert checker.execute_if_updated assert_equal 1, i - touch(tmpfile('foo.txt')) + touch(tmpfile("foo.txt")) + wait assert checker.execute_if_updated assert_equal 2, i end - test 'should not execute the block if the file extension is not watched' do + test "should not execute the block if the file extension is not watched" do i = 0 checker = new_checker([], tmpdir => :txt) { i += 1 } - touch(tmpfile('foo.rb')) + touch(tmpfile("foo.rb")) + wait assert !checker.execute_if_updated assert_equal 0, i end - test 'does not assume files exist on instantiation' do + test "does not assume files exist on instantiation" do i = 0 - non_existing = tmpfile('non_existing.rb') + non_existing = tmpfile("non_existing.rb") checker = new_checker([non_existing]) { i += 1 } touch(non_existing) + wait assert checker.execute_if_updated assert_equal 1, i end - test 'detects files in new subdirectories' do + test "detects files in new subdirectories" do i = 0 checker = new_checker([], tmpdir => :rb) { i += 1 } - subdir = tmpfile('subdir') + subdir = tmpfile("subdir") mkdir(subdir) wait assert !checker.execute_if_updated assert_equal 0, i - touch("#{subdir}/nested.rb") + touch(File.join(subdir, "nested.rb")) + wait assert checker.execute_if_updated assert_equal 1, i end - test 'looked up extensions are inherited in subdirectories not listening to them' do + test "looked up extensions are inherited in subdirectories not listening to them" do i = 0 - subdir = tmpfile('subdir') + subdir = tmpfile("subdir") mkdir(subdir) checker = new_checker([], tmpdir => :rb, subdir => :txt) { i += 1 } - touch(tmpfile('new.txt')) + touch(tmpfile("new.txt")) + wait assert !checker.execute_if_updated assert_equal 0, i # subdir does not look for Ruby files, but its parent tmpdir does. - touch("#{subdir}/nested.rb") + touch(File.join(subdir, "nested.rb")) + wait assert checker.execute_if_updated assert_equal 1, i - touch("#{subdir}/nested.txt") + touch(File.join(subdir, "nested.txt")) + wait assert checker.execute_if_updated assert_equal 2, i diff --git a/activesupport/test/file_update_checker_test.rb b/activesupport/test/file_update_checker_test.rb index 752f7836cd..55b0b46644 100644 --- a/activesupport/test/file_update_checker_test.rb +++ b/activesupport/test/file_update_checker_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'file_update_checker_shared_tests' +require "abstract_unit" +require "file_update_checker_shared_tests" class FileUpdateCheckerTest < ActiveSupport::TestCase include FileUpdateCheckerSharedTests diff --git a/activesupport/test/fixtures/autoload/another_class.rb b/activesupport/test/fixtures/autoload/another_class.rb index a240b3de41..ab6e075ab4 100644 --- a/activesupport/test/fixtures/autoload/another_class.rb +++ b/activesupport/test/fixtures/autoload/another_class.rb @@ -1,2 +1,2 @@ class Fixtures::AnotherClass -end
\ No newline at end of file +end diff --git a/activesupport/test/fixtures/autoload/some_class.rb b/activesupport/test/fixtures/autoload/some_class.rb index 13b3c73ef5..30d41eb0bf 100644 --- a/activesupport/test/fixtures/autoload/some_class.rb +++ b/activesupport/test/fixtures/autoload/some_class.rb @@ -1,2 +1,2 @@ class Fixtures::Autoload::SomeClass -end
\ No newline at end of file +end diff --git a/activesupport/test/gzip_test.rb b/activesupport/test/gzip_test.rb index 0e3cf3b429..d88408b55c 100644 --- a/activesupport/test/gzip_test.rb +++ b/activesupport/test/gzip_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/object/blank' +require "abstract_unit" +require "active_support/core_ext/object/blank" class GzipTest < ActiveSupport::TestCase def test_compress_should_decompress_to_the_same_value @@ -13,9 +13,9 @@ class GzipTest < ActiveSupport::TestCase end def test_compress_should_return_a_binary_string - compressed = ActiveSupport::Gzip.compress('') + compressed = ActiveSupport::Gzip.compress("") - assert_equal Encoding.find('binary'), compressed.encoding + assert_equal Encoding.find("binary"), compressed.encoding assert !compressed.blank?, "a compressed blank string should not be blank" end diff --git a/activesupport/test/i18n_test.rb b/activesupport/test/i18n_test.rb index 3faa15e7fd..7d88d2dc6b 100644 --- a/activesupport/test/i18n_test.rb +++ b/activesupport/test/i18n_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'active_support/time' -require 'active_support/core_ext/array/conversions' +require "abstract_unit" +require "active_support/time" +require "active_support/core_ext/array/conversions" class I18nTest < ActiveSupport::TestCase def setup @@ -18,15 +18,15 @@ class I18nTest < ActiveSupport::TestCase end def test_date_localization_with_default_format - assert_equal @date.strftime("%Y-%m-%d"), I18n.localize(@date, :format => :default) + assert_equal @date.strftime("%Y-%m-%d"), I18n.localize(@date, format: :default) end def test_date_localization_with_short_format - assert_equal @date.strftime("%b %d"), I18n.localize(@date, :format => :short) + assert_equal @date.strftime("%b %d"), I18n.localize(@date, format: :short) end def test_date_localization_with_long_format - assert_equal @date.strftime("%B %d, %Y"), I18n.localize(@date, :format => :long) + assert_equal @date.strftime("%B %d, %Y"), I18n.localize(@date, format: :long) end def test_time_localization_should_use_default_format @@ -34,15 +34,15 @@ class I18nTest < ActiveSupport::TestCase end def test_time_localization_with_default_format - assert_equal @time.strftime("%a, %d %b %Y %H:%M:%S %z"), I18n.localize(@time, :format => :default) + assert_equal @time.strftime("%a, %d %b %Y %H:%M:%S %z"), I18n.localize(@time, format: :default) end def test_time_localization_with_short_format - assert_equal @time.strftime("%d %b %H:%M"), I18n.localize(@time, :format => :short) + assert_equal @time.strftime("%d %b %H:%M"), I18n.localize(@time, format: :short) end def test_time_localization_with_long_format - assert_equal @time.strftime("%B %d, %Y %H:%M"), I18n.localize(@time, :format => :long) + assert_equal @time.strftime("%B %d, %Y %H:%M"), I18n.localize(@time, format: :long) end def test_day_names @@ -66,39 +66,39 @@ class I18nTest < ActiveSupport::TestCase end def test_time_am - assert_equal 'am', I18n.translate(:'time.am') + assert_equal "am", I18n.translate(:'time.am') end def test_time_pm - assert_equal 'pm', I18n.translate(:'time.pm') + assert_equal "pm", I18n.translate(:'time.pm') end def test_words_connector - assert_equal ', ', I18n.translate(:'support.array.words_connector') + assert_equal ", ", I18n.translate(:'support.array.words_connector') end def test_two_words_connector - assert_equal ' and ', I18n.translate(:'support.array.two_words_connector') + assert_equal " and ", I18n.translate(:'support.array.two_words_connector') end def test_last_word_connector - assert_equal ', and ', I18n.translate(:'support.array.last_word_connector') + assert_equal ", and ", I18n.translate(:'support.array.last_word_connector') end def test_to_sentence default_two_words_connector = I18n.translate(:'support.array.two_words_connector') default_last_word_connector = I18n.translate(:'support.array.last_word_connector') - assert_equal 'a, b, and c', %w[a b c].to_sentence - I18n.backend.store_translations 'en', :support => { :array => { :two_words_connector => ' & ' } } - assert_equal 'a & b', %w[a b].to_sentence - I18n.backend.store_translations 'en', :support => { :array => { :last_word_connector => ' and ' } } - assert_equal 'a, b and c', %w[a b c].to_sentence + assert_equal "a, b, and c", %w[a b c].to_sentence + I18n.backend.store_translations "en", support: { array: { two_words_connector: " & " } } + assert_equal "a & b", %w[a b].to_sentence + I18n.backend.store_translations "en", support: { array: { last_word_connector: " and " } } + assert_equal "a, b and c", %w[a b c].to_sentence ensure - I18n.backend.store_translations 'en', :support => { :array => { :two_words_connector => default_two_words_connector } } - I18n.backend.store_translations 'en', :support => { :array => { :last_word_connector => default_last_word_connector } } + I18n.backend.store_translations "en", support: { array: { two_words_connector: default_two_words_connector } } + I18n.backend.store_translations "en", support: { array: { last_word_connector: default_last_word_connector } } end def test_to_sentence_with_empty_i18n_store - assert_equal 'a, b, and c', %w[a b c].to_sentence(locale: 'empty') + assert_equal "a, b, and c", %w[a b c].to_sentence(locale: "empty") end end diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index 06cd41c86d..1c5a4f378c 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -1,8 +1,8 @@ -require 'abstract_unit' -require 'active_support/inflector' +require "abstract_unit" +require "active_support/inflector" -require 'inflector_test_cases' -require 'constantize_test_cases' +require "inflector_test_cases" +require "constantize_test_cases" class InflectorTest < ActiveSupport::TestCase include InflectorTestCases @@ -80,7 +80,6 @@ class InflectorTest < ActiveSupport::TestCase end end - def test_overwrite_previous_inflectors assert_equal("series", ActiveSupport::Inflector.singularize("series")) ActiveSupport::Inflector.inflections.singular "series", "serie" @@ -101,11 +100,11 @@ class InflectorTest < ActiveSupport::TestCase end def test_camelize_with_lower_downcases_the_first_letter - assert_equal('capital', ActiveSupport::Inflector.camelize('Capital', false)) + assert_equal("capital", ActiveSupport::Inflector.camelize("Capital", false)) end def test_camelize_with_underscores - assert_equal("CamelCase", ActiveSupport::Inflector.camelize('Camel_Case')) + assert_equal("CamelCase", ActiveSupport::Inflector.camelize("Camel_Case")) end def test_acronyms @@ -246,12 +245,12 @@ 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 + # 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| @@ -269,7 +268,7 @@ class InflectorTest < ActiveSupport::TestCase 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: '_')) + assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, separator: "_")) end end @@ -277,7 +276,7 @@ class InflectorTest < ActiveSupport::TestCase jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" StringToParameterizeWithUnderscore.each do |some_string, parameterized_string| assert_deprecated(/Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '_'` instead./i) do - assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, '_')) + assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, "_")) end end end @@ -285,7 +284,7 @@ class InflectorTest < ActiveSupport::TestCase 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__')) + assert_equal(parameterized_string.gsub("-", "__sep__"), ActiveSupport::Inflector.parameterize(some_string, separator: "__sep__")) end end @@ -293,7 +292,7 @@ class InflectorTest < ActiveSupport::TestCase jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" StringToParameterized.each do |some_string, parameterized_string| assert_deprecated(/Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '__sep__'` instead./i) do - assert_equal(parameterized_string.gsub('-', '__sep__'), ActiveSupport::Inflector.parameterize(some_string, '__sep__')) + assert_equal(parameterized_string.gsub("-", "__sep__"), ActiveSupport::Inflector.parameterize(some_string, "__sep__")) end end end @@ -307,12 +306,12 @@ class InflectorTest < ActiveSupport::TestCase def test_classify_with_symbol assert_nothing_raised do - assert_equal 'FooBar', ActiveSupport::Inflector.classify(:foo_bars) + assert_equal "FooBar", ActiveSupport::Inflector.classify(:foo_bars) end end def test_classify_with_leading_schema_name - assert_equal 'FooBar', ActiveSupport::Inflector.classify('schema.foo_bar') + assert_equal "FooBar", ActiveSupport::Inflector.classify("schema.foo_bar") end def test_humanize @@ -403,24 +402,24 @@ class InflectorTest < ActiveSupport::TestCase def test_inflector_locality ActiveSupport::Inflector.inflections(:es) do |inflect| - inflect.plural(/$/, 's') - inflect.plural(/z$/i, 'ces') + inflect.plural(/$/, "s") + inflect.plural(/z$/i, "ces") - inflect.singular(/s$/, '') - inflect.singular(/es$/, '') + inflect.singular(/s$/, "") + inflect.singular(/es$/, "") - inflect.irregular('el', 'los') + inflect.irregular("el", "los") end - assert_equal('hijos', 'hijo'.pluralize(:es)) - assert_equal('luces', 'luz'.pluralize(:es)) - assert_equal('luzs', 'luz'.pluralize) + assert_equal("hijos", "hijo".pluralize(:es)) + assert_equal("luces", "luz".pluralize(:es)) + assert_equal("luzs", "luz".pluralize) - assert_equal('sociedad', 'sociedades'.singularize(:es)) - assert_equal('sociedade', 'sociedades'.singularize) + assert_equal("sociedad", "sociedades".singularize(:es)) + assert_equal("sociedade", "sociedades".singularize) - assert_equal('los', 'el'.pluralize(:es)) - assert_equal('els', 'el'.pluralize) + assert_equal("los", "el".pluralize(:es)) + assert_equal("els", "el".pluralize) ActiveSupport::Inflector.inflections(:es) { |inflect| inflect.clear } @@ -435,7 +434,7 @@ class InflectorTest < ActiveSupport::TestCase # ensure any data is present inflect.plural(/(quiz)$/i, '\1zes') inflect.singular(/(database)s$/i, '\1') - inflect.uncountable('series') + inflect.uncountable("series") inflect.human("col_rpted_bugs", "Reported bugs") inflect.clear :all @@ -452,7 +451,7 @@ class InflectorTest < ActiveSupport::TestCase # ensure any data is present inflect.plural(/(quiz)$/i, '\1zes') inflect.singular(/(database)s$/i, '\1') - inflect.uncountable('series') + inflect.uncountable("series") inflect.human("col_rpted_bugs", "Reported bugs") inflect.clear diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index 14fe97a986..a8283301b3 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -128,10 +128,10 @@ module InflectorTestCases } SymbolToLowerCamel = { - :product => 'product', - :special_guest => 'specialGuest', - :application_controller => 'applicationController', - :area51_controller => 'area51Controller' + product: "product", + special_guest: "specialGuest", + application_controller: "applicationController", + area51_controller: "area51Controller" } CamelToUnderscoreWithoutReverse = { @@ -176,12 +176,12 @@ module InflectorTestCases StringToParameterizedPreserveCase = { "Donald E. Knuth" => "Donald-E-Knuth", - "Random text with *(bad)* characters" => "Random-text-with-bad-characters", - "Allow_Under_Scores" => "Allow_Under_Scores", - "Trailing bad characters!@#" => "Trailing-bad-characters", - "!@#Leading bad characters" => "Leading-bad-characters", - "Squeeze separators" => "Squeeze-separators", - "Test with + sign" => "Test-with-sign", + "Random text with *(bad)* characters" => "Random-text-with-bad-characters", + "Allow_Under_Scores" => "Allow_Under_Scores", + "Trailing bad characters!@#" => "Trailing-bad-characters", + "!@#Leading bad characters" => "Leading-bad-characters", + "Squeeze separators" => "Squeeze-separators", + "Test with + sign" => "Test-with-sign", "Test with malformed utf8 \xA9" => "Test-with-malformed-utf8" } @@ -199,11 +199,11 @@ module InflectorTestCases StringToParameterizePreserveCaseWithNoSeparator = { "Donald E. Knuth" => "DonaldEKnuth", "With-some-dashes" => "With-some-dashes", - "Random text with *(bad)* characters" => "Randomtextwithbadcharacters", - "Trailing bad characters!@#" => "Trailingbadcharacters", - "!@#Leading bad characters" => "Leadingbadcharacters", - "Squeeze separators" => "Squeezeseparators", - "Test with + sign" => "Testwithsign", + "Random text with *(bad)* characters" => "Randomtextwithbadcharacters", + "Trailing bad characters!@#" => "Trailingbadcharacters", + "!@#Leading bad characters" => "Leadingbadcharacters", + "Squeeze separators" => "Squeezeseparators", + "Test with + sign" => "Testwithsign", "Test with malformed utf8 \xA9" => "Testwithmalformedutf8" } @@ -220,14 +220,14 @@ module InflectorTestCases } StringToParameterizePreserceCaseWithUnderscore = { - "Donald E. Knuth" => "Donald_E_Knuth", + "Donald E. Knuth" => "Donald_E_Knuth", "Random text with *(bad)* characters" => "Random_text_with_bad_characters", - "With-some-dashes" => "With-some-dashes", - "Allow_Under_Scores" => "Allow_Under_Scores", - "Trailing bad characters!@#" => "Trailing_bad_characters", - "!@#Leading bad characters" => "Leading_bad_characters", - "Squeeze separators" => "Squeeze_separators", - "Test with + sign" => "Test_with_sign", + "With-some-dashes" => "With-some-dashes", + "Allow_Under_Scores" => "Allow_Under_Scores", + "Trailing bad characters!@#" => "Trailing_bad_characters", + "!@#Leading bad characters" => "Leading_bad_characters", + "Squeeze separators" => "Squeeze_separators", + "Test with + sign" => "Test_with_sign", "Test with malformed utf8 \xA9" => "Test_with_malformed_utf8" } @@ -241,11 +241,11 @@ module InflectorTestCases } UnderscoreToHuman = { - 'employee_salary' => 'Employee salary', - 'employee_id' => 'Employee', - 'underground' => 'Underground', - '_id' => 'Id', - '_external_id' => 'External' + "employee_salary" => "Employee salary", + "employee_id" => "Employee", + "underground" => "Underground", + "_id" => "Id", + "_external_id" => "External" } UnderscoreToHumanWithoutCapitalize = { @@ -255,22 +255,23 @@ module InflectorTestCases } MixtureToTitleCase = { - 'active_record' => 'Active Record', - 'ActiveRecord' => 'Active Record', - 'action web service' => 'Action Web Service', - 'Action Web Service' => 'Action Web Service', - 'Action web service' => 'Action Web Service', - 'actionwebservice' => 'Actionwebservice', - 'Actionwebservice' => 'Actionwebservice', + "active_record" => "Active Record", + "ActiveRecord" => "Active Record", + "action web service" => "Action Web Service", + "Action Web Service" => "Action Web Service", + "Action web service" => "Action Web Service", + "actionwebservice" => "Actionwebservice", + "Actionwebservice" => "Actionwebservice", "david's code" => "David's Code", "David's code" => "David's Code", "david's Code" => "David's Code", "sgt. pepper's" => "Sgt. Pepper's", "i've just seen a face" => "I've Just Seen A Face", "maybe you'll be there" => "Maybe You'll Be There", - "¿por qué?" => '¿Por Qué?', + "¿por qué?" => "¿Por Qué?", "Fred’s" => "Fred’s", - "Fred`s" => "Fred`s" + "Fred`s" => "Fred`s", + ActiveSupport::SafeBuffer.new("confirmation num") => "Confirmation Num" } OrdinalNumbers = { @@ -344,13 +345,13 @@ module InflectorTestCases } Irregularities = { - 'person' => 'people', - 'man' => 'men', - 'child' => 'children', - 'sex' => 'sexes', - 'move' => 'moves', - 'cow' => 'kine', # Test inflections with different starting letters - 'zombie' => 'zombies', - 'genus' => 'genera' + "person" => "people", + "man" => "men", + "child" => "children", + "sex" => "sexes", + "move" => "moves", + "cow" => "kine", # Test inflections with different starting letters + "zombie" => "zombies", + "genus" => "genera" } end diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb index f2fc456f4b..dd8382754b 100644 --- a/activesupport/test/json/decoding_test.rb +++ b/activesupport/test/json/decoding_test.rb @@ -1,8 +1,11 @@ -require 'abstract_unit' -require 'active_support/json' -require 'active_support/time' +require "abstract_unit" +require "active_support/json" +require "active_support/time" +require "time_zone_test_helpers" class TestJSONDecoding < ActiveSupport::TestCase + include TimeZoneTestHelpers + class Foo def self.json_create(object) "Foo" @@ -10,72 +13,75 @@ class TestJSONDecoding < ActiveSupport::TestCase end TESTS = { - %q({"returnTo":{"\/categories":"\/"}}) => {"returnTo" => {"/categories" => "/"}}, - %q({"return\\"To\\":":{"\/categories":"\/"}}) => {"return\"To\":" => {"/categories" => "/"}}, - %q({"returnTo":{"\/categories":1}}) => {"returnTo" => {"/categories" => 1}}, - %({"returnTo":[1,"a"]}) => {"returnTo" => [1, "a"]}, - %({"returnTo":[1,"\\"a\\",", "b"]}) => {"returnTo" => [1, "\"a\",", "b"]}, - %({"a": "'", "b": "5,000"}) => {"a" => "'", "b" => "5,000"}, - %({"a": "a's, b's and c's", "b": "5,000"}) => {"a" => "a's, b's and c's", "b" => "5,000"}, + %q({"returnTo":{"\/categories":"\/"}}) => { "returnTo" => { "/categories" => "/" } }, + %q({"return\\"To\\":":{"\/categories":"\/"}}) => { "return\"To\":" => { "/categories" => "/" } }, + %q({"returnTo":{"\/categories":1}}) => { "returnTo" => { "/categories" => 1 } }, + %({"returnTo":[1,"a"]}) => { "returnTo" => [1, "a"] }, + %({"returnTo":[1,"\\"a\\",", "b"]}) => { "returnTo" => [1, "\"a\",", "b"] }, + %({"a": "'", "b": "5,000"}) => { "a" => "'", "b" => "5,000" }, + %({"a": "a's, b's and c's", "b": "5,000"}) => { "a" => "a's, b's and c's", "b" => "5,000" }, # multibyte - %({"matzue": "æ¾æ±Ÿ", "asakusa": "æµ…è‰"}) => {"matzue" => "æ¾æ±Ÿ", "asakusa" => "æµ…è‰"}, - %({"a": "2007-01-01"}) => {'a' => Date.new(2007, 1, 1)}, - %({"a": "2007-01-01 01:12:34 Z"}) => {'a' => Time.utc(2007, 1, 1, 1, 12, 34)}, + %({"matzue": "æ¾æ±Ÿ", "asakusa": "æµ…è‰"}) => { "matzue" => "æ¾æ±Ÿ", "asakusa" => "æµ…è‰" }, + %({"a": "2007-01-01"}) => { "a" => Date.new(2007, 1, 1) }, + %({"a": "2007-01-01 01:12:34 Z"}) => { "a" => Time.utc(2007, 1, 1, 1, 12, 34) }, %(["2007-01-01 01:12:34 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34)], %(["2007-01-01 01:12:34 Z", "2007-01-01 01:12:35 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34), Time.utc(2007, 1, 1, 1, 12, 35)], # no time zone - %({"a": "2007-01-01 01:12:34"}) => {'a' => "2007-01-01 01:12:34"}, + %({"a": "2007-01-01 01:12:34"}) => { "a" => Time.new(2007, 1, 1, 1, 12, 34, "-05:00") }, # invalid date - %({"a": "1089-10-40"}) => {'a' => "1089-10-40"}, + %({"a": "1089-10-40"}) => { "a" => "1089-10-40" }, # xmlschema date notation - %({"a": "2009-08-10T19:01:02Z"}) => {'a' => Time.utc(2009, 8, 10, 19, 1, 2)}, - %({"a": "2009-08-10T19:01:02+02:00"}) => {'a' => Time.utc(2009, 8, 10, 17, 1, 2)}, - %({"a": "2009-08-10T19:01:02-05:00"}) => {'a' => Time.utc(2009, 8, 11, 00, 1, 2)}, + %({"a": "2009-08-10T19:01:02"}) => { "a" => Time.new(2009, 8, 10, 19, 1, 2, "-04:00") }, + %({"a": "2009-08-10T19:01:02Z"}) => { "a" => Time.utc(2009, 8, 10, 19, 1, 2) }, + %({"a": "2009-08-10T19:01:02+02:00"}) => { "a" => Time.utc(2009, 8, 10, 17, 1, 2) }, + %({"a": "2009-08-10T19:01:02-05:00"}) => { "a" => Time.utc(2009, 8, 11, 00, 1, 2) }, # needs to be *exact* - %({"a": " 2007-01-01 01:12:34 Z "}) => {'a' => " 2007-01-01 01:12:34 Z "}, - %({"a": "2007-01-01 : it's your birthday"}) => {'a' => "2007-01-01 : it's your birthday"}, + %({"a": " 2007-01-01 01:12:34 Z "}) => { "a" => " 2007-01-01 01:12:34 Z " }, + %({"a": "2007-01-01 : it's your birthday"}) => { "a" => "2007-01-01 : it's your birthday" }, %([]) => [], %({}) => {}, - %({"a":1}) => {"a" => 1}, - %({"a": ""}) => {"a" => ""}, - %({"a":"\\""}) => {"a" => "\""}, - %({"a": null}) => {"a" => nil}, - %({"a": true}) => {"a" => true}, - %({"a": false}) => {"a" => false}, - %q({"bad":"\\\\","trailing":""}) => {"bad" => "\\", "trailing" => ""}, - %q({"a": "http:\/\/test.host\/posts\/1"}) => {"a" => "http://test.host/posts/1"}, - %q({"a": "\u003cunicode\u0020escape\u003e"}) => {"a" => "<unicode escape>"}, - %q({"a": "\\\\u0020skip double backslashes"}) => {"a" => "\\u0020skip double backslashes"}, - %q({"a": "\u003cbr /\u003e"}) => {'a' => "<br />"}, - %q({"b":["\u003ci\u003e","\u003cb\u003e","\u003cu\u003e"]}) => {'b' => ["<i>","<b>","<u>"]}, + %({"a":1}) => { "a" => 1 }, + %({"a": ""}) => { "a" => "" }, + %({"a":"\\""}) => { "a" => "\"" }, + %({"a": null}) => { "a" => nil }, + %({"a": true}) => { "a" => true }, + %({"a": false}) => { "a" => false }, + '{"bad":"\\\\","trailing":""}' => { "bad" => "\\", "trailing" => "" }, + %q({"a": "http:\/\/test.host\/posts\/1"}) => { "a" => "http://test.host/posts/1" }, + %q({"a": "\u003cunicode\u0020escape\u003e"}) => { "a" => "<unicode escape>" }, + '{"a": "\\\\u0020skip double backslashes"}' => { "a" => "\\u0020skip double backslashes" }, + %q({"a": "\u003cbr /\u003e"}) => { "a" => "<br />" }, + %q({"b":["\u003ci\u003e","\u003cb\u003e","\u003cu\u003e"]}) => { "b" => ["<i>","<b>","<u>"] }, # test combination of dates and escaped or unicode encoded data in arrays %q([{"d":"1970-01-01", "s":"\u0020escape"},{"d":"1970-01-01", "s":"\u0020escape"}]) => - [{'d' => Date.new(1970, 1, 1), 's' => ' escape'},{'d' => Date.new(1970, 1, 1), 's' => ' escape'}], + [{ "d" => Date.new(1970, 1, 1), "s" => " escape" },{ "d" => Date.new(1970, 1, 1), "s" => " escape" }], %q([{"d":"1970-01-01","s":"http:\/\/example.com"},{"d":"1970-01-01","s":"http:\/\/example.com"}]) => - [{'d' => Date.new(1970, 1, 1), 's' => 'http://example.com'}, - {'d' => Date.new(1970, 1, 1), 's' => 'http://example.com'}], + [{ "d" => Date.new(1970, 1, 1), "s" => "http://example.com" }, + { "d" => Date.new(1970, 1, 1), "s" => "http://example.com" }], # tests escaping of "\n" char with Yaml backend - %q({"a":"\n"}) => {"a"=>"\n"}, - %q({"a":"\u000a"}) => {"a"=>"\n"}, - %q({"a":"Line1\u000aLine2"}) => {"a"=>"Line1\nLine2"}, + %q({"a":"\n"}) => { "a"=>"\n" }, + %q({"a":"\u000a"}) => { "a"=>"\n" }, + %q({"a":"Line1\u000aLine2"}) => { "a"=>"Line1\nLine2" }, # prevent json unmarshalling - %q({"json_class":"TestJSONDecoding::Foo"}) => {"json_class"=>"TestJSONDecoding::Foo"}, + '{"json_class":"TestJSONDecoding::Foo"}' => { "json_class"=>"TestJSONDecoding::Foo" }, # json "fragments" - these are invalid JSON, but ActionPack relies on this - %q("a string") => "a string", - %q(1.1) => 1.1, - %q(1) => 1, - %q(-1) => -1, - %q(true) => true, - %q(false) => false, - %q(null) => nil + '"a string"' => "a string", + "1.1" => 1.1, + "1" => 1, + "-1" => -1, + "true" => true, + "false" => false, + "null" => nil } TESTS.each_with_index do |(json, expected), index| test "json decodes #{index}" do - with_parse_json_times(true) do - silence_warnings do - assert_equal expected, ActiveSupport::JSON.decode(json), "JSON decoding \ - failed for #{json}" + 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}" + end end end end @@ -83,7 +89,7 @@ class TestJSONDecoding < ActiveSupport::TestCase test "json decodes time json with time parsing disabled" do with_parse_json_times(false) do - expected = {"a" => "2007-01-01 01:12:34 Z"} + expected = { "a" => "2007-01-01 01:12:34 Z" } assert_equal expected, ActiveSupport::JSON.decode(%({"a": "2007-01-01 01:12:34 Z"})) end end @@ -101,12 +107,11 @@ class TestJSONDecoding < ActiveSupport::TestCase private - def with_parse_json_times(value) - old_value = ActiveSupport.parse_json_times - ActiveSupport.parse_json_times = value - yield - ensure - ActiveSupport.parse_json_times = old_value - end + def with_parse_json_times(value) + old_value = ActiveSupport.parse_json_times + ActiveSupport.parse_json_times = value + yield + ensure + ActiveSupport.parse_json_times = old_value + end end - diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index 9f4b62fd8b..d125cc939f 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -1,17 +1,21 @@ -require 'securerandom' -require 'abstract_unit' -require 'active_support/core_ext/string/inflections' -require 'active_support/json' -require 'active_support/time' -require 'time_zone_test_helpers' -require 'json/encoding_test_cases' +require "securerandom" +require "abstract_unit" +require "active_support/core_ext/string/inflections" +require "active_support/core_ext/regexp" +require "active_support/json" +require "active_support/time" +require "time_zone_test_helpers" +require "json/encoding_test_cases" class TestJSONEncoding < ActiveSupport::TestCase include TimeZoneTestHelpers def sorted_json(json) - return json unless json =~ /^\{.*\}$/ - '{' + json[1..-2].split(',').sort.join(',') + '}' + if json.start_with?("{") && json.end_with?("}") + "{" + json[1..-2].split(",").sort.join(",") + "}" + else + json + end end JSONTest::EncodingTestCases.constants.each do |class_tests| @@ -19,8 +23,10 @@ class TestJSONEncoding < ActiveSupport::TestCase begin prev = ActiveSupport.use_standard_json_time_format - ActiveSupport.escape_html_entities_in_json = class_tests !~ /^Standard/ - ActiveSupport.use_standard_json_time_format = class_tests =~ /^Standard/ + standard_class_tests = /Standard/.match?(class_tests) + + ActiveSupport.escape_html_entities_in_json = !standard_class_tests + ActiveSupport.use_standard_json_time_format = standard_class_tests JSONTest::EncodingTestCases.const_get(class_tests).each do |pair| assert_equal pair.last, sorted_json(ActiveSupport::JSON.encode(pair.first)) end @@ -41,12 +47,12 @@ class TestJSONEncoding < ActiveSupport::TestCase end def test_hash_encoding - assert_equal %({\"a\":\"b\"}), ActiveSupport::JSON.encode(:a => :b) - assert_equal %({\"a\":1}), ActiveSupport::JSON.encode('a' => 1) - assert_equal %({\"a\":[1,2]}), ActiveSupport::JSON.encode('a' => [1,2]) + assert_equal %({\"a\":\"b\"}), ActiveSupport::JSON.encode(a: :b) + assert_equal %({\"a\":1}), ActiveSupport::JSON.encode("a" => 1) + assert_equal %({\"a\":[1,2]}), ActiveSupport::JSON.encode("a" => [1,2]) assert_equal %({"1":2}), ActiveSupport::JSON.encode(1 => 2) - assert_equal %({\"a\":\"b\",\"c\":\"d\"}), sorted_json(ActiveSupport::JSON.encode(:a => :b, :c => :d)) + assert_equal %({\"a\":\"b\",\"c\":\"d\"}), sorted_json(ActiveSupport::JSON.encode(a: :b, c: :d)) end def test_hash_keys_encoding @@ -57,24 +63,24 @@ class TestJSONEncoding < ActiveSupport::TestCase end def test_utf8_string_encoded_properly - result = ActiveSupport::JSON.encode('€2.99') + result = ActiveSupport::JSON.encode("€2.99") assert_equal '"€2.99"', result assert_equal(Encoding::UTF_8, result.encoding) - result = ActiveSupport::JSON.encode('✎☺') + result = ActiveSupport::JSON.encode("✎☺") assert_equal '"✎☺"', result assert_equal(Encoding::UTF_8, result.encoding) end def test_non_utf8_string_transcodes - s = '二'.encode('Shift_JIS') + s = "二".encode("Shift_JIS") result = ActiveSupport::JSON.encode(s) assert_equal '"二"', result assert_equal Encoding::UTF_8, result.encoding end def test_wide_utf8_chars - w = '𠜎' + w = "𠜎" result = ActiveSupport::JSON.encode(w) assert_equal '"𠜎"', result end @@ -83,25 +89,25 @@ class TestJSONEncoding < ActiveSupport::TestCase hash = { string: "ð’‘" } json = ActiveSupport::JSON.encode(hash) decoded_hash = ActiveSupport::JSON.decode(json) - assert_equal "ð’‘", decoded_hash['string'] + assert_equal "ð’‘", decoded_hash["string"] end def test_hash_key_identifiers_are_always_quoted - values = {0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B"} + values = { 0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B" } assert_equal %w( "$" "A" "A0" "A0B" "_" "a" "0" "1" ).sort, object_keys(ActiveSupport::JSON.encode(values)) end def test_hash_should_allow_key_filtering_with_only - assert_equal %({"a":1}), ActiveSupport::JSON.encode({'a' => 1, :b => 2, :c => 3}, :only => 'a') + assert_equal %({"a":1}), ActiveSupport::JSON.encode({ "a" => 1, :b => 2, :c => 3 }, only: "a") end def test_hash_should_allow_key_filtering_with_except - assert_equal %({"b":2}), ActiveSupport::JSON.encode({'foo' => 'bar', :b => 2, :c => 3}, :except => ['foo', :c]) + assert_equal %({"b":2}), ActiveSupport::JSON.encode({ "foo" => "bar", :b => 2, :c => 3 }, except: ["foo", :c]) end def test_time_to_json_includes_local_offset with_standard_json_time_format(true) do - with_env_tz 'US/Eastern' do + with_env_tz "US/Eastern" do assert_equal %("2005-02-01T15:15:10.000-05:00"), ActiveSupport::JSON.encode(Time.local(2005,2,1,15,15,10)) end end @@ -109,7 +115,7 @@ class TestJSONEncoding < ActiveSupport::TestCase def test_hash_with_time_to_json with_standard_json_time_format(false) do - assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { :time => Time.utc(2009) }.to_json + assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { time: Time.utc(2009) }.to_json end end @@ -117,8 +123,8 @@ class TestJSONEncoding < ActiveSupport::TestCase assert_nothing_raised do hash = { "CHI" => { - :display_name => "chicago", - :latitude => 123.234 + display_name: "chicago", + latitude: 123.234 } } ActiveSupport::JSON.encode(hash) @@ -127,64 +133,64 @@ class TestJSONEncoding < ActiveSupport::TestCase def test_hash_like_with_options h = JSONTest::Hashlike.new - json = h.to_json :only => [:foo] + json = h.to_json only: [:foo] - assert_equal({"foo"=>"hello"}, JSON.parse(json)) + assert_equal({ "foo"=>"hello" }, JSON.parse(json)) end def test_object_to_json_with_options obj = Object.new obj.instance_variable_set :@foo, "hello" obj.instance_variable_set :@bar, "world" - json = obj.to_json :only => ["foo"] + json = obj.to_json only: ["foo"] - assert_equal({"foo"=>"hello"}, JSON.parse(json)) + assert_equal({ "foo"=>"hello" }, JSON.parse(json)) end def test_struct_to_json_with_options struct = Struct.new(:foo, :bar).new struct.foo = "hello" struct.bar = "world" - json = struct.to_json :only => [:foo] + json = struct.to_json only: [:foo] - assert_equal({"foo"=>"hello"}, JSON.parse(json)) + assert_equal({ "foo"=>"hello" }, JSON.parse(json)) end def test_hash_should_pass_encoding_options_to_children_in_as_json person = { - :name => 'John', - :address => { - :city => 'London', - :country => 'UK' + name: "John", + address: { + city: "London", + country: "UK" } } - json = person.as_json :only => [:address, :city] + json = person.as_json only: [:address, :city] - assert_equal({ 'address' => { 'city' => 'London' }}, json) + assert_equal({ "address" => { "city" => "London" } }, json) end def test_hash_should_pass_encoding_options_to_children_in_to_json person = { - :name => 'John', - :address => { - :city => 'London', - :country => 'UK' + name: "John", + address: { + city: "London", + country: "UK" } } - json = person.to_json :only => [:address, :city] + json = person.to_json only: [:address, :city] assert_equal(%({"address":{"city":"London"}}), json) end def test_array_should_pass_encoding_options_to_children_in_as_json people = [ - { :name => 'John', :address => { :city => 'London', :country => 'UK' }}, - { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }} + { name: "John", address: { city: "London", country: "UK" } }, + { name: "Jean", address: { city: "Paris" , country: "France" } } ] - json = people.as_json :only => [:address, :city] + json = people.as_json only: [:address, :city] expected = [ - { 'address' => { 'city' => 'London' }}, - { 'address' => { 'city' => 'Paris' }} + { "address" => { "city" => "London" } }, + { "address" => { "city" => "Paris" } } ] assert_equal(expected, json) @@ -192,10 +198,10 @@ class TestJSONEncoding < ActiveSupport::TestCase def test_array_should_pass_encoding_options_to_children_in_to_json people = [ - { :name => 'John', :address => { :city => 'London', :country => 'UK' }}, - { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }} + { name: "John", address: { city: "London", country: "UK" } }, + { name: "Jean", address: { city: "Paris" , country: "France" } } ] - json = people.to_json :only => [:address, :city] + json = people.to_json only: [:address, :city] assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) end @@ -204,8 +210,8 @@ class TestJSONEncoding < ActiveSupport::TestCase include Enumerable def initialize() @people = [ - { :name => 'John', :address => { :city => 'London', :country => 'UK' }}, - { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }} + { name: "John", address: { city: "London", country: "UK" } }, + { name: "Jean", address: { city: "Paris" , country: "France" } } ] end def each(*, &blk) @@ -217,32 +223,32 @@ class TestJSONEncoding < ActiveSupport::TestCase end def test_enumerable_should_generate_json_with_as_json - json = People.new.as_json :only => [:address, :city] + json = People.new.as_json only: [:address, :city] expected = [ - { 'address' => { 'city' => 'London' }}, - { 'address' => { 'city' => 'Paris' }} + { "address" => { "city" => "London" } }, + { "address" => { "city" => "Paris" } } ] assert_equal(expected, json) end def test_enumerable_should_generate_json_with_to_json - json = People.new.to_json :only => [:address, :city] + json = People.new.to_json only: [:address, :city] assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) end def test_enumerable_should_pass_encoding_options_to_children_in_as_json - json = People.new.each.as_json :only => [:address, :city] + json = People.new.each.as_json only: [:address, :city] expected = [ - { 'address' => { 'city' => 'London' }}, - { 'address' => { 'city' => 'Paris' }} + { "address" => { "city" => "London" } }, + { "address" => { "city" => "Paris" } } ] assert_equal(expected, json) end def test_enumerable_should_pass_encoding_options_to_children_in_to_json - json = People.new.each.to_json :only => [:address, :city] + json = People.new.each.to_json only: [:address, :city] assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) end @@ -261,9 +267,9 @@ class TestJSONEncoding < ActiveSupport::TestCase f.foo = "hello" f.bar = "world" - hash = {"foo" => f, "other_hash" => {"foo" => "other_foo", "test" => "other_test"}} - assert_equal({"foo"=>{"foo"=>"hello","bar"=>"world"}, - "other_hash" => {"foo"=>"other_foo","test"=>"other_test"}}, ActiveSupport::JSON.decode(hash.to_json)) + hash = { "foo" => f, "other_hash" => { "foo" => "other_foo", "test" => "other_test" } } + assert_equal({ "foo"=>{ "foo"=>"hello","bar"=>"world" }, + "other_hash" => { "foo"=>"other_foo","test"=>"other_test" } }, ActiveSupport::JSON.decode(hash.to_json)) end def test_array_to_json_should_not_keep_options_around @@ -271,9 +277,9 @@ class TestJSONEncoding < ActiveSupport::TestCase f.foo = "hello" f.bar = "world" - array = [f, {"foo" => "other_foo", "test" => "other_test"}] - assert_equal([{"foo"=>"hello","bar"=>"world"}, - {"foo"=>"other_foo","test"=>"other_test"}], ActiveSupport::JSON.decode(array.to_json)) + array = [f, { "foo" => "other_foo", "test" => "other_test" }] + assert_equal([{ "foo"=>"hello","bar"=>"world" }, + { "foo"=>"other_foo","test"=>"other_test" }], ActiveSupport::JSON.decode(array.to_json)) end class OptionsTest @@ -284,7 +290,7 @@ class TestJSONEncoding < ActiveSupport::TestCase def test_hash_as_json_without_options json = { foo: OptionsTest.new }.as_json - assert_equal({"foo" => :default}, json) + assert_equal({ "foo" => :default }, json) end def test_array_as_json_without_options @@ -293,13 +299,12 @@ class TestJSONEncoding < ActiveSupport::TestCase end def test_struct_encoding - Struct.new('UserNameAndEmail', :name, :email) - Struct.new('UserNameAndDate', :name, :date) - Struct.new('Custom', :name, :sub) - user_email = Struct::UserNameAndEmail.new 'David', 'sample@example.com' - user_birthday = Struct::UserNameAndDate.new 'David', Date.new(2010, 01, 01) - custom = Struct::Custom.new 'David', user_birthday - + Struct.new("UserNameAndEmail", :name, :email) + Struct.new("UserNameAndDate", :name, :date) + Struct.new("Custom", :name, :sub) + user_email = Struct::UserNameAndEmail.new "David", "sample@example.com" + user_birthday = Struct::UserNameAndDate.new "David", Date.new(2010, 01, 01) + custom = Struct::Custom.new "David", user_birthday json_strings = "" json_string_and_date = "" @@ -311,15 +316,15 @@ class TestJSONEncoding < ActiveSupport::TestCase json_custom = custom.to_json end - assert_equal({"name" => "David", + assert_equal({ "name" => "David", "sub" => { "name" => "David", - "date" => "2010-01-01" }}, ActiveSupport::JSON.decode(json_custom)) + "date" => "2010-01-01" } }, ActiveSupport::JSON.decode(json_custom)) - assert_equal({"name" => "David", "email" => "sample@example.com"}, + assert_equal({ "name" => "David", "email" => "sample@example.com" }, ActiveSupport::JSON.decode(json_strings)) - assert_equal({"name" => "David", "date" => "2010-01-01"}, + assert_equal({ "name" => "David", "date" => "2010-01-01" }, ActiveSupport::JSON.decode(json_string_and_date)) end @@ -376,7 +381,7 @@ EXPECTED def test_twz_to_json_with_use_standard_json_time_format_config_set_to_false with_standard_json_time_format(false) do - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone) assert_equal "\"1999/12/31 19:00:00 -0500\"", ActiveSupport::JSON.encode(time) end @@ -384,7 +389,7 @@ EXPECTED def test_twz_to_json_with_use_standard_json_time_format_config_set_to_true with_standard_json_time_format(true) do - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone) assert_equal "\"1999-12-31T19:00:00.000-05:00\"", ActiveSupport::JSON.encode(time) end @@ -393,7 +398,7 @@ EXPECTED def test_twz_to_json_with_custom_time_precision with_standard_json_time_format(true) do with_time_precision(0) do - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone) assert_equal "\"1999-12-31T19:00:00-05:00\"", ActiveSupport::JSON.encode(time) end @@ -417,11 +422,16 @@ EXPECTED end def test_twz_to_json_when_wrapping_a_date_time - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] time = ActiveSupport::TimeWithZone.new(DateTime.new(2000), zone) assert_equal '"1999-12-31T19:00:00.000-05:00"', ActiveSupport::JSON.encode(time) end + def test_exception_to_json + exception = Exception.new("foo") + assert_equal '"foo"', ActiveSupport::JSON.encode(exception) + end + protected def object_keys(json_object) diff --git a/activesupport/test/json/encoding_test_cases.rb b/activesupport/test/json/encoding_test_cases.rb index 0159ba8606..ff2ed3a788 100644 --- a/activesupport/test/json/encoding_test_cases.rb +++ b/activesupport/test/json/encoding_test_cases.rb @@ -1,4 +1,4 @@ -require 'bigdecimal' +require "bigdecimal" module JSONTest class Foo @@ -9,7 +9,7 @@ module JSONTest class Hashlike def to_hash - { :foo => "hello", :bar => "world" } + { foo: "hello", bar: "world" } end end @@ -39,20 +39,20 @@ module JSONTest [ 0.0/0.0, %(null) ], [ 1.0/0.0, %(null) ], [ -1.0/0.0, %(null) ], - [ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ], - [ BigDecimal('2.5'), %("#{BigDecimal('2.5')}") ]] + [ BigDecimal("0.0")/BigDecimal("0.0"), %(null) ], + [ BigDecimal("2.5"), %("#{BigDecimal('2.5')}") ]] - StringTests = [[ 'this is the <string>', %("this is the \\u003cstring\\u003e")], + StringTests = [[ "this is the <string>", %("this is the \\u003cstring\\u003e")], [ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ], - [ 'http://test.host/posts/1', %("http://test.host/posts/1")], + [ "http://test.host/posts/1", %("http://test.host/posts/1")], [ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\u2028\u2029", %("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\\u2028\\u2029") ]] - ArrayTests = [[ ['a', 'b', 'c'], %([\"a\",\"b\",\"c\"]) ], - [ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]] + ArrayTests = [[ ["a", "b", "c"], %([\"a\",\"b\",\"c\"]) ], + [ [1, "a", :b, nil, false], %([1,\"a\",\"b\",null,false]) ]] - HashTests = [[ {foo: "bar"}, %({\"foo\":\"bar\"}) ], - [ {1 => 1, 2 => 'a', 3 => :b, 4 => nil, 5 => false}, %({\"1\":1,\"2\":\"a\",\"3\":\"b\",\"4\":null,\"5\":false}) ]] + HashTests = [[ { foo: "bar" }, %({\"foo\":\"bar\"}) ], + [ { 1 => 1, 2 => "a", 3 => :b, 4 => nil, 5 => false }, %({\"1\":1,\"2\":\"a\",\"3\":\"b\",\"4\":null,\"5\":false}) ]] RangeTests = [[ 1..2, %("1..2")], [ 1...2, %("1...2")], @@ -67,15 +67,19 @@ module JSONTest StructTests = [[ MyStruct.new(:foo, "bar"), %({\"name\":\"foo\",\"value\":\"bar\"}) ], [ MyStruct.new(nil, nil), %({\"name\":null,\"value\":null}) ]] CustomTests = [[ Custom.new("custom"), '"custom"' ], - [ Custom.new(nil), 'null' ], + [ Custom.new(nil), "null" ], [ Custom.new(:a), '"a"' ], [ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ], - [ Custom.new({ :foo => "hello", :bar => "world" }), '{"bar":"world","foo":"hello"}' ], + [ Custom.new(foo: "hello", bar: "world"), '{"bar":"world","foo":"hello"}' ], [ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ], [ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]] RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']] + URITests = [[ URI.parse("http://example.com"), %("http://example.com") ]] + + PathnameTests = [[ Pathname.new("lib/index.rb"), %("lib/index.rb") ]] + DateTests = [[ Date.new(2005,2,1), %("2005/02/01") ]] TimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]] DateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]] @@ -83,6 +87,6 @@ module JSONTest StandardDateTests = [[ Date.new(2005,2,1), %("2005-02-01") ]] StandardTimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000Z") ]] StandardDateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000+00:00") ]] - StandardStringTests = [[ 'this is the <string>', %("this is the <string>")]] + StandardStringTests = [[ "this is the <string>", %("this is the <string>")]] end end diff --git a/activesupport/test/key_generator_test.rb b/activesupport/test/key_generator_test.rb index f7e8e9a795..89db9563ac 100644 --- a/activesupport/test/key_generator_test.rb +++ b/activesupport/test/key_generator_test.rb @@ -1,62 +1,77 @@ -require 'abstract_unit' +require "abstract_unit" begin - require 'openssl' + require "openssl" OpenSSL::PKCS5 rescue LoadError, NameError $stderr.puts "Skipping KeyGenerator test: broken OpenSSL install" else -require 'active_support/time' -require 'active_support/json' + require "active_support/time" + require "active_support/json" -class KeyGeneratorTest < ActiveSupport::TestCase - def setup - @secret = SecureRandom.hex(64) - @generator = ActiveSupport::KeyGenerator.new(@secret, :iterations=>2) - end + class KeyGeneratorTest < ActiveSupport::TestCase + def setup + @secret = SecureRandom.hex(64) + @generator = ActiveSupport::KeyGenerator.new(@secret, iterations: 2) + end - test "Generating a key of the default length" do - derived_key = @generator.generate_key("some_salt") - assert_kind_of String, derived_key - assert_equal OpenSSL::Digest::SHA1.new.block_length, derived_key.length, "Should have generated a key of the default size" - end + test "Generating a key of the default length" do + derived_key = @generator.generate_key("some_salt") + assert_kind_of String, derived_key + assert_equal 64, derived_key.length, "Should have generated a key of the default size" + end - test "Generating a key of an alternative length" do - derived_key = @generator.generate_key("some_salt", 32) - assert_kind_of String, derived_key - assert_equal 32, derived_key.length, "Should have generated a key of the right size" - end -end + test "Generating a key of an alternative length" do + derived_key = @generator.generate_key("some_salt", 32) + assert_kind_of String, derived_key + assert_equal 32, derived_key.length, "Should have generated a key of the right size" + end -class CachingKeyGeneratorTest < ActiveSupport::TestCase - def setup - @secret = SecureRandom.hex(64) - @generator = ActiveSupport::KeyGenerator.new(@secret, :iterations=>2) - @caching_generator = ActiveSupport::CachingKeyGenerator.new(@generator) - end + test "Expected results" do + # For any given set of inputs, this method must continue to return + # the same output: if it changes, any existing values relying on a + # key would break. + + expected = "b129376f68f1ecae788d7433310249d65ceec090ecacd4c872a3a9e9ec78e055739be5cc6956345d5ae38e7e1daa66f1de587dc8da2bf9e8b965af4b3918a122" + assert_equal expected, ActiveSupport::KeyGenerator.new("0" * 64).generate_key("some_salt").unpack("H*").first - test "Generating a cached key for same salt and key size" do - derived_key = @caching_generator.generate_key("some_salt", 32) - cached_key = @caching_generator.generate_key("some_salt", 32) + expected = "b129376f68f1ecae788d7433310249d65ceec090ecacd4c872a3a9e9ec78e055" + assert_equal expected, ActiveSupport::KeyGenerator.new("0" * 64).generate_key("some_salt", 32).unpack("H*").first - assert_equal derived_key, cached_key - assert_equal derived_key.object_id, cached_key.object_id + expected = "cbea7f7f47df705967dc508f4e446fd99e7797b1d70011c6899cd39bbe62907b8508337d678505a7dc8184e037f1003ba3d19fc5d829454668e91d2518692eae" + assert_equal expected, ActiveSupport::KeyGenerator.new("0" * 64, iterations: 2).generate_key("some_salt").unpack("H*").first + end end - test "Does not cache key for different salt" do - derived_key = @caching_generator.generate_key("some_salt", 32) - different_salt_key = @caching_generator.generate_key("other_salt", 32) + class CachingKeyGeneratorTest < ActiveSupport::TestCase + def setup + @secret = SecureRandom.hex(64) + @generator = ActiveSupport::KeyGenerator.new(@secret, iterations: 2) + @caching_generator = ActiveSupport::CachingKeyGenerator.new(@generator) + end - assert_not_equal derived_key, different_salt_key - end + test "Generating a cached key for same salt and key size" do + derived_key = @caching_generator.generate_key("some_salt", 32) + cached_key = @caching_generator.generate_key("some_salt", 32) + + assert_equal derived_key, cached_key + assert_equal derived_key.object_id, cached_key.object_id + end + + test "Does not cache key for different salt" do + derived_key = @caching_generator.generate_key("some_salt", 32) + different_salt_key = @caching_generator.generate_key("other_salt", 32) - test "Does not cache key for different length" do - derived_key = @caching_generator.generate_key("some_salt", 32) - different_length_key = @caching_generator.generate_key("some_salt", 64) + assert_not_equal derived_key, different_salt_key + end - assert_not_equal derived_key, different_length_key + test "Does not cache key for different length" do + derived_key = @caching_generator.generate_key("some_salt", 32) + different_length_key = @caching_generator.generate_key("some_salt", 64) + + assert_not_equal derived_key, different_length_key + end end -end end diff --git a/activesupport/test/lazy_load_hooks_test.rb b/activesupport/test/lazy_load_hooks_test.rb index 7851634dbf..3b1959a1c9 100644 --- a/activesupport/test/lazy_load_hooks_test.rb +++ b/activesupport/test/lazy_load_hooks_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class LazyLoadHooksTest < ActiveSupport::TestCase def test_basic_hook @@ -63,7 +63,7 @@ class LazyLoadHooksTest < ActiveSupport::TestCase def test_hook_with_yield_true i = 0 - ActiveSupport.on_load(:contextual_yield, :yield => true) do |obj| + ActiveSupport.on_load(:contextual_yield, yield: true) do |obj| i += obj.incr + incr_amt end assert_equal 0, i @@ -75,7 +75,7 @@ class LazyLoadHooksTest < ActiveSupport::TestCase i = 0 ActiveSupport.run_load_hooks(:contextual_yield_after, FakeContext.new(2)) assert_equal 0, i - ActiveSupport.on_load(:contextual_yield_after, :yield => true) do |obj| + ActiveSupport.on_load(:contextual_yield_after, yield: true) do |obj| i += obj.incr + incr_amt end assert_equal 7, i @@ -93,4 +93,4 @@ private @incr = incr end end -end
\ No newline at end of file +end diff --git a/activesupport/test/load_paths_test.rb b/activesupport/test/load_paths_test.rb deleted file mode 100644 index ac617a9fd8..0000000000 --- a/activesupport/test/load_paths_test.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'abstract_unit' - -class LoadPathsTest < ActiveSupport::TestCase - def test_uniq_load_paths - load_paths_count = $LOAD_PATH.inject({}) { |paths, path| - expanded_path = File.expand_path(path) - paths[expanded_path] ||= 0 - paths[expanded_path] += 1 - paths - } - load_paths_count[File.expand_path('../../lib', __FILE__)] -= 1 - - load_paths_count.select! { |k, v| v > 1 } - assert load_paths_count.empty?, load_paths_count.inspect - end -end diff --git a/activesupport/test/log_subscriber_test.rb b/activesupport/test/log_subscriber_test.rb index 998a6887c5..f6496ef7d6 100644 --- a/activesupport/test/log_subscriber_test.rb +++ b/activesupport/test/log_subscriber_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/log_subscriber/test_helper' +require "abstract_unit" +require "active_support/log_subscriber/test_helper" class MyLogSubscriber < ActiveSupport::LogSubscriber attr_reader :event @@ -116,7 +116,7 @@ class SyncLogSubscriberTest < ActiveSupport::TestCase wait assert_equal 1, @logger.logged(:info).size - assert_equal 'some_event.my_log_subscriber', @logger.logged(:info).last + assert_equal "some_event.my_log_subscriber", @logger.logged(:info).last assert_equal 1, @logger.logged(:error).size assert_match 'Could not log "puke.my_log_subscriber" event. RuntimeError: puke', @logger.logged(:error).last diff --git a/activesupport/test/logger_test.rb b/activesupport/test/logger_test.rb index 317e09b7f2..3f04783401 100644 --- a/activesupport/test/logger_test.rb +++ b/activesupport/test/logger_test.rb @@ -1,9 +1,9 @@ -require 'abstract_unit' -require 'multibyte_test_helpers' -require 'stringio' -require 'fileutils' -require 'tempfile' -require 'concurrent/atomics' +require "abstract_unit" +require "multibyte_test_helpers" +require "stringio" +require "fileutils" +require "tempfile" +require "concurrent/atomics" class LoggerTest < ActiveSupport::TestCase include MultibyteTestHelpers @@ -26,12 +26,12 @@ class LoggerTest < ActiveSupport::TestCase end def test_write_binary_data_to_existing_file - t = Tempfile.new ['development', 'log'] + t = Tempfile.new ["development", "log"] t.binmode - t.write 'hi mom!' + t.write "hi mom!" t.close - f = File.open(t.path, 'w') + f = File.open(t.path, "w") f.binmode logger = Logger.new f @@ -47,9 +47,9 @@ class LoggerTest < ActiveSupport::TestCase end def test_write_binary_data_create_file - fname = File.join Dir.tmpdir, 'lol', 'rofl.log' + fname = File.join Dir.tmpdir, "lol", "rofl.log" FileUtils.mkdir_p File.dirname(fname) - f = File.open(fname, 'w') + f = File.open(fname, "w") f.binmode logger = Logger.new f @@ -67,7 +67,7 @@ class LoggerTest < ActiveSupport::TestCase def test_should_log_debugging_message_when_debugging @logger.level = Logger::DEBUG @logger.add(Logger::DEBUG, @message) - assert @output.string.include?(@message) + assert_includes @output.string, @message end def test_should_not_log_debug_messages_when_log_level_is_info @@ -78,32 +78,32 @@ class LoggerTest < ActiveSupport::TestCase def test_should_add_message_passed_as_block_when_using_add @logger.level = Logger::INFO - @logger.add(Logger::INFO) {@message} - assert @output.string.include?(@message) + @logger.add(Logger::INFO) { @message } + assert_includes @output.string, @message end def test_should_add_message_passed_as_block_when_using_shortcut @logger.level = Logger::INFO - @logger.info {@message} - assert @output.string.include?(@message) + @logger.info { @message } + assert_includes @output.string, @message end def test_should_convert_message_to_string @logger.level = Logger::INFO @logger.info @integer_message - assert @output.string.include?(@integer_message.to_s) + assert_includes @output.string, @integer_message.to_s end def test_should_convert_message_to_string_when_passed_in_block @logger.level = Logger::INFO - @logger.info {@integer_message} - assert @output.string.include?(@integer_message.to_s) + @logger.info { @integer_message } + assert_includes @output.string, @integer_message.to_s end def test_should_not_evaluate_block_if_message_wont_be_logged @logger.level = Logger::INFO evaluated = false - @logger.add(Logger::DEBUG) {evaluated = true} + @logger.add(Logger::DEBUG) { evaluated = true } assert evaluated == false end @@ -115,7 +115,7 @@ class LoggerTest < ActiveSupport::TestCase def test_should_know_if_its_loglevel_is_below_a_given_level Logger::Severity.constants.each do |level| - next if level.to_s == 'UNKNOWN' + next if level.to_s == "UNKNOWN" @logger.level = Logger::Severity.const_get(level) - 1 assert @logger.send("#{level.downcase}?"), "didn't know if it was #{level.downcase}? or below" end @@ -125,10 +125,10 @@ class LoggerTest < ActiveSupport::TestCase @logger.level = Logger::INFO @logger.info(UNICODE_STRING) @logger.info(BYTE_STRING) - assert @output.string.include?(UNICODE_STRING) + assert_includes @output.string, UNICODE_STRING byte_string = @output.string.dup byte_string.force_encoding("ASCII-8BIT") - assert byte_string.include?(BYTE_STRING) + assert_includes byte_string, BYTE_STRING end def test_silencing_everything_but_errors @@ -138,7 +138,53 @@ class LoggerTest < ActiveSupport::TestCase end assert_not @output.string.include?("NOT THERE") - assert @output.string.include?("THIS IS HERE") + assert_includes @output.string, "THIS IS HERE" + end + + def test_logger_silencing_works_for_broadcast + another_output = StringIO.new + another_logger = ActiveSupport::Logger.new(another_output) + + @logger.extend ActiveSupport::Logger.broadcast(another_logger) + + @logger.debug "CORRECT DEBUG" + @logger.silence do |logger| + assert_kind_of ActiveSupport::Logger, logger + @logger.debug "FAILURE" + @logger.error "CORRECT ERROR" + end + + assert_includes @output.string, "CORRECT DEBUG" + assert_includes @output.string, "CORRECT ERROR" + assert_not @output.string.include?("FAILURE") + + assert_includes another_output.string, "CORRECT DEBUG" + assert_includes another_output.string, "CORRECT ERROR" + assert_not another_output.string.include?("FAILURE") + end + + def test_broadcast_silencing_does_not_break_plain_ruby_logger + another_output = StringIO.new + another_logger = ::Logger.new(another_output) + + @logger.extend ActiveSupport::Logger.broadcast(another_logger) + + @logger.debug "CORRECT DEBUG" + @logger.silence do |logger| + assert_kind_of ActiveSupport::Logger, logger + @logger.debug "FAILURE" + @logger.error "CORRECT ERROR" + end + + assert_includes @output.string, "CORRECT DEBUG" + assert_includes @output.string, "CORRECT ERROR" + assert_not @output.string.include?("FAILURE") + + assert_includes another_output.string, "CORRECT DEBUG" + assert_includes another_output.string, "CORRECT ERROR" + assert_includes another_output.string, "FAILURE" + # We can't silence plain ruby Logger cause with thread safety + # but at least we don't break it end def test_logger_level_per_object_thread_safety diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb index eb71369397..f8282c89ca 100644 --- a/activesupport/test/message_encryptor_test.rb +++ b/activesupport/test/message_encryptor_test.rb @@ -1,7 +1,7 @@ -require 'abstract_unit' -require 'openssl' -require 'active_support/time' -require 'active_support/json' +require "abstract_unit" +require "openssl" +require "active_support/time" +require "active_support/json" class MessageEncryptorTest < ActiveSupport::TestCase class JSONSerializer @@ -15,10 +15,10 @@ class MessageEncryptorTest < ActiveSupport::TestCase end def setup - @secret = SecureRandom.hex(64) - @verifier = ActiveSupport::MessageVerifier.new(@secret, :serializer => ActiveSupport::MessageEncryptor::NullSerializer) + @secret = SecureRandom.random_bytes(32) + @verifier = ActiveSupport::MessageVerifier.new(@secret, serializer: ActiveSupport::MessageEncryptor::NullSerializer) @encryptor = ActiveSupport::MessageEncryptor.new(@secret) - @data = { :some => "data", :now => Time.local(2010) } + @data = { some: "data", now: Time.local(2010) } end def test_encrypting_twice_yields_differing_cipher_text @@ -48,11 +48,21 @@ class MessageEncryptorTest < ActiveSupport::TestCase assert_equal @data, @encryptor.decrypt_and_verify(message) end + def test_backwards_compat_for_64_bytes_key + # 64 bit key + secret = ["3942b1bf81e622559ed509e3ff274a780784fe9e75b065866bd270438c74da822219de3156473cc27df1fd590e4baf68c95eeb537b6e4d4c5a10f41635b5597e"].pack("H*") + # Encryptor with 32 bit key, 64 bit secret for verifier + encryptor = ActiveSupport::MessageEncryptor.new(secret[0..31], secret) + # Message generated with 64 bit key + message = "eHdGeExnZEwvMSt3U3dKaFl1WFo0TjVvYzA0eGpjbm5WSkt5MXlsNzhpZ0ZnbWhBWFlQZTRwaXE1bVJCS2oxMDZhYVp2dVN3V0lNZUlWQ3c2eVhQbnhnVjFmeVVubmhRKzF3WnZyWHVNMDg9LS1HSisyakJVSFlPb05ISzRMaXRzcFdBPT0=--831a1d54a3cda8a0658dc668a03dedcbce13b5ca" + assert_equal "data", encryptor.decrypt_and_verify(message)[:some] + end + def test_alternative_serialization_method prev = ActiveSupport.use_standard_json_time_format ActiveSupport.use_standard_json_time_format = true - encryptor = ActiveSupport::MessageEncryptor.new(SecureRandom.hex(64), SecureRandom.hex(64), :serializer => JSONSerializer.new) - message = encryptor.encrypt_and_sign({ :foo => 123, 'bar' => Time.utc(2010) }) + encryptor = ActiveSupport::MessageEncryptor.new(SecureRandom.random_bytes(32), SecureRandom.random_bytes(128), serializer: JSONSerializer.new) + message = encryptor.encrypt_and_sign(:foo => 123, "bar" => Time.utc(2010)) exp = { "foo" => 123, "bar" => "2010-01-01T00:00:00.000Z" } assert_equal exp, encryptor.decrypt_and_verify(message) ensure @@ -70,23 +80,41 @@ class MessageEncryptorTest < ActiveSupport::TestCase assert_not_verified([iv, message] * bad_encoding_characters) end + def test_aead_mode_encryption + encryptor = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm") + message = encryptor.encrypt_and_sign(@data) + assert_equal @data, encryptor.decrypt_and_verify(message) + end + + def test_messing_with_aead_values_causes_failures + encryptor = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm") + text, iv, auth_tag = encryptor.encrypt_and_sign(@data).split("--") + assert_not_decrypted([iv, text, auth_tag] * "--") + assert_not_decrypted([munge(text), iv, auth_tag] * "--") + assert_not_decrypted([text, munge(iv), auth_tag] * "--") + assert_not_decrypted([text, iv, munge(auth_tag)] * "--") + assert_not_decrypted([munge(text), munge(iv), munge(auth_tag)] * "--") + assert_not_decrypted([text, iv] * "--") + assert_not_decrypted([text, iv, auth_tag[0..-2]] * "--") + end + private - def assert_not_decrypted(value) - assert_raise(ActiveSupport::MessageEncryptor::InvalidMessage) do - @encryptor.decrypt_and_verify(@verifier.generate(value)) + def assert_not_decrypted(value) + assert_raise(ActiveSupport::MessageEncryptor::InvalidMessage) do + @encryptor.decrypt_and_verify(@verifier.generate(value)) + end end - end - def assert_not_verified(value) - assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do - @encryptor.decrypt_and_verify(value) + def assert_not_verified(value) + assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do + @encryptor.decrypt_and_verify(value) + end end - end - def munge(base64_string) - bits = ::Base64.strict_decode64(base64_string) - bits.reverse! - ::Base64.strict_encode64(bits) - end + def munge(base64_string) + bits = ::Base64.strict_decode64(base64_string) + bits.reverse! + ::Base64.strict_encode64(bits) + end end diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb index 668d78492e..d56a46b250 100644 --- a/activesupport/test/message_verifier_test.rb +++ b/activesupport/test/message_verifier_test.rb @@ -1,10 +1,9 @@ -require 'abstract_unit' -require 'openssl' -require 'active_support/time' -require 'active_support/json' +require "abstract_unit" +require "openssl" +require "active_support/time" +require "active_support/json" class MessageVerifierTest < ActiveSupport::TestCase - class JSONSerializer def dump(value) ActiveSupport::JSON.encode(value) @@ -17,7 +16,7 @@ class MessageVerifierTest < ActiveSupport::TestCase def setup @verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!") - @data = { :some => "data", :now => Time.local(2010) } + @data = { some: "data", now: Time.local(2010) } end def test_valid_message @@ -49,8 +48,8 @@ class MessageVerifierTest < ActiveSupport::TestCase def test_alternative_serialization_method prev = ActiveSupport.use_standard_json_time_format ActiveSupport.use_standard_json_time_format = true - verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!", :serializer => JSONSerializer.new) - message = verifier.generate({ :foo => 123, 'bar' => Time.utc(2010) }) + verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!", serializer: JSONSerializer.new) + message = verifier.generate(:foo => 123, "bar" => Time.utc(2010)) exp = { "foo" => 123, "bar" => "2010-01-01T00:00:00.000Z" } assert_equal exp, verifier.verified(message) assert_equal exp, verifier.verify(message) @@ -81,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 exception.message, "Secret should not be nil." end end diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb index c1e0b19248..5ff1543328 100644 --- a/activesupport/test/multibyte_chars_test.rb +++ b/activesupport/test/multibyte_chars_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'multibyte_test_helpers' -require 'active_support/core_ext/string/multibyte' +require "abstract_unit" +require "multibyte_test_helpers" +require "active_support/core_ext/string/multibyte" class MultibyteCharsTest < ActiveSupport::TestCase include MultibyteTestHelpers @@ -16,7 +16,7 @@ class MultibyteCharsTest < ActiveSupport::TestCase end def test_should_allow_method_calls_to_string - @chars.wrapped_string.singleton_class.class_eval { def __method_for_multibyte_testing; 'result'; end } + @chars.wrapped_string.singleton_class.class_eval { def __method_for_multibyte_testing; "result"; end } assert_nothing_raised do @chars.__method_for_multibyte_testing @@ -27,14 +27,14 @@ class MultibyteCharsTest < ActiveSupport::TestCase end def test_forwarded_method_calls_should_return_new_chars_instance - @chars.wrapped_string.singleton_class.class_eval { def __method_for_multibyte_testing; 'result'; end } + @chars.wrapped_string.singleton_class.class_eval { def __method_for_multibyte_testing; "result"; end } assert_kind_of @proxy_class, @chars.__method_for_multibyte_testing assert_not_equal @chars.object_id, @chars.__method_for_multibyte_testing.object_id end def test_forwarded_bang_method_calls_should_return_the_original_chars_instance_when_result_is_not_nil - @chars.wrapped_string.singleton_class.class_eval { def __method_for_multibyte_testing!; 'result'; end } + @chars.wrapped_string.singleton_class.class_eval { def __method_for_multibyte_testing!; "result"; end } assert_kind_of @proxy_class, @chars.__method_for_multibyte_testing! assert_equal @chars.object_id, @chars.__method_for_multibyte_testing!.object_id @@ -51,7 +51,7 @@ class MultibyteCharsTest < ActiveSupport::TestCase end def test_forwarded_method_with_non_string_result_should_be_returned_vertabim - str = '' + 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 } @@ -59,15 +59,15 @@ class MultibyteCharsTest < ActiveSupport::TestCase end def test_should_concatenate - mb_a = 'a'.mb_chars - mb_b = 'b'.mb_chars - assert_equal 'ab', mb_a + 'b' - assert_equal 'ab', 'a' + mb_b - assert_equal 'ab', mb_a + mb_b + mb_a = "a".mb_chars + mb_b = "b".mb_chars + assert_equal "ab", mb_a + "b" + assert_equal "ab", "a" + mb_b + assert_equal "ab", mb_a + mb_b - assert_equal 'ab', mb_a << 'b' - assert_equal 'ab', 'a' << mb_b - assert_equal 'abb', mb_a << mb_b + assert_equal "ab", mb_a << "b" + assert_equal "ab", "a" << mb_b + assert_equal "abb", mb_a << mb_b end def test_consumes_utf8_strings @@ -77,8 +77,8 @@ class MultibyteCharsTest < ActiveSupport::TestCase end def test_concatenation_should_return_a_proxy_class_instance - assert_equal ActiveSupport::Multibyte.proxy_class, ('a'.mb_chars + 'b').class - assert_equal ActiveSupport::Multibyte.proxy_class, ('a'.mb_chars << 'b').class + assert_equal ActiveSupport::Multibyte.proxy_class, ("a".mb_chars + "b").class + assert_equal ActiveSupport::Multibyte.proxy_class, ("a".mb_chars << "b").class end def test_ascii_strings_are_treated_at_utf8_strings @@ -86,10 +86,10 @@ class MultibyteCharsTest < ActiveSupport::TestCase end def test_concatenate_should_return_proxy_instance - assert(('a'.mb_chars + 'b').kind_of?(@proxy_class)) - assert(('a'.mb_chars + 'b'.mb_chars).kind_of?(@proxy_class)) - assert(('a'.mb_chars << 'b').kind_of?(@proxy_class)) - assert(('a'.mb_chars << 'b'.mb_chars).kind_of?(@proxy_class)) + assert(("a".mb_chars + "b").kind_of?(@proxy_class)) + assert(("a".mb_chars + "b".mb_chars).kind_of?(@proxy_class)) + assert(("a".mb_chars << "b").kind_of?(@proxy_class)) + assert(("a".mb_chars << "b".mb_chars).kind_of?(@proxy_class)) end def test_should_return_string_as_json @@ -150,24 +150,24 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase end def test_string_methods_are_chainable - assert chars('').insert(0, '').kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').rjust(1).kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').ljust(1).kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').center(1).kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').rstrip.kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').lstrip.kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').strip.kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').reverse.kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars(' ').slice(0).kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').limit(0).kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').upcase.kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').downcase.kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').capitalize.kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').normalize.kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').decompose.kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').compose.kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').tidy_bytes.kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').swapcase.kind_of?(ActiveSupport::Multibyte.proxy_class) + assert chars("").insert(0, "").kind_of?(ActiveSupport::Multibyte.proxy_class) + assert chars("").rjust(1).kind_of?(ActiveSupport::Multibyte.proxy_class) + assert chars("").ljust(1).kind_of?(ActiveSupport::Multibyte.proxy_class) + assert chars("").center(1).kind_of?(ActiveSupport::Multibyte.proxy_class) + assert chars("").rstrip.kind_of?(ActiveSupport::Multibyte.proxy_class) + assert chars("").lstrip.kind_of?(ActiveSupport::Multibyte.proxy_class) + assert chars("").strip.kind_of?(ActiveSupport::Multibyte.proxy_class) + assert chars("").reverse.kind_of?(ActiveSupport::Multibyte.proxy_class) + assert chars(" ").slice(0).kind_of?(ActiveSupport::Multibyte.proxy_class) + assert chars("").limit(0).kind_of?(ActiveSupport::Multibyte.proxy_class) + assert chars("").upcase.kind_of?(ActiveSupport::Multibyte.proxy_class) + assert chars("").downcase.kind_of?(ActiveSupport::Multibyte.proxy_class) + assert chars("").capitalize.kind_of?(ActiveSupport::Multibyte.proxy_class) + assert chars("").normalize.kind_of?(ActiveSupport::Multibyte.proxy_class) + assert chars("").decompose.kind_of?(ActiveSupport::Multibyte.proxy_class) + assert chars("").compose.kind_of?(ActiveSupport::Multibyte.proxy_class) + assert chars("").tidy_bytes.kind_of?(ActiveSupport::Multibyte.proxy_class) + assert chars("").swapcase.kind_of?(ActiveSupport::Multibyte.proxy_class) end def test_should_be_equal_to_the_wrapped_string @@ -176,8 +176,8 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase end def test_should_not_be_equal_to_an_other_string - assert_not_equal @chars, 'other' - assert_not_equal 'other', @chars + assert_not_equal @chars, "other" + assert_not_equal "other", @chars end def test_sortability @@ -195,29 +195,29 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase end def test_should_use_character_offsets_for_insert_offsets - assert_equal '', ''.mb_chars.insert(0, '') - assert_equal 'ã“ã‚ã«ã¡ã‚', @chars.insert(1, 'ã‚') - assert_equal 'ã“ã‚ã‚ã‚ã«ã¡ã‚', @chars.insert(2, 'ã‚ã‚') - assert_equal 'ã‚ã“ã‚ã‚ã‚ã«ã¡ã‚', @chars.insert(0, 'ã‚') - assert_equal 'ã‚ã“ã‚ã‚ã‚ã«ã¡ã‚', @chars.wrapped_string + assert_equal "", "".mb_chars.insert(0, "") + assert_equal "ã“ã‚ã«ã¡ã‚", @chars.insert(1, "ã‚") + assert_equal "ã“ã‚ã‚ã‚ã«ã¡ã‚", @chars.insert(2, "ã‚ã‚") + assert_equal "ã‚ã“ã‚ã‚ã‚ã«ã¡ã‚", @chars.insert(0, "ã‚") + assert_equal "ã‚ã“ã‚ã‚ã‚ã«ã¡ã‚", @chars.wrapped_string end def test_insert_should_be_destructive - @chars.insert(1, 'ã‚') - assert_equal 'ã“ã‚ã«ã¡ã‚', @chars + @chars.insert(1, "ã‚") + assert_equal "ã“ã‚ã«ã¡ã‚", @chars end def test_insert_throws_index_error - assert_raise(IndexError) { @chars.insert(-12, 'ã‚')} - assert_raise(IndexError) { @chars.insert(12, 'ã‚') } + assert_raise(IndexError) { @chars.insert(-12, "ã‚") } + assert_raise(IndexError) { @chars.insert(12, "ã‚") } end def test_should_know_if_one_includes_the_other - assert @chars.include?('') - assert @chars.include?('ã¡') - assert @chars.include?('ã‚') - assert !@chars.include?('ã“ã¡ã‚') - assert !@chars.include?('a') + assert_includes @chars, "" + assert_includes @chars, "ã¡" + assert_includes @chars, "ã‚" + assert_not_includes @chars, "ã“ã¡ã‚" + assert_not_includes @chars, "a" end def test_include_raises_when_nil_is_passed @@ -227,62 +227,62 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase end def test_index_should_return_character_offset - assert_nil @chars.index('u') - assert_equal 0, @chars.index('ã“ã«') - assert_equal 2, @chars.index('ã¡') - assert_equal 2, @chars.index('ã¡', -2) - assert_equal nil, @chars.index('ã¡', -1) - assert_equal 3, @chars.index('ã‚') - assert_equal 5, 'ééxééx'.mb_chars.index('x', 4) + assert_nil @chars.index("u") + assert_equal 0, @chars.index("ã“ã«") + assert_equal 2, @chars.index("ã¡") + assert_equal 2, @chars.index("ã¡", -2) + assert_equal nil, @chars.index("ã¡", -1) + assert_equal 3, @chars.index("ã‚") + assert_equal 5, "ééxééx".mb_chars.index("x", 4) end def test_rindex_should_return_character_offset - assert_nil @chars.rindex('u') - assert_equal 1, @chars.rindex('ã«') - assert_equal 2, @chars.rindex('ã¡', -2) - assert_nil @chars.rindex('ã¡', -3) - assert_equal 6, 'Café périferôl'.mb_chars.rindex('é') - assert_equal 13, 'Café périferôl'.mb_chars.rindex(/\w/u) + assert_nil @chars.rindex("u") + assert_equal 1, @chars.rindex("ã«") + assert_equal 2, @chars.rindex("ã¡", -2) + assert_nil @chars.rindex("ã¡", -3) + assert_equal 6, "Café périferôl".mb_chars.rindex("é") + assert_equal 13, "Café périferôl".mb_chars.rindex(/\w/u) end def test_indexed_insert_should_take_character_offsets - @chars[2] = 'a' - assert_equal 'ã“ã«aã‚', @chars - @chars[2] = 'ηη' - assert_equal 'ã“ã«Î·Î·ã‚', @chars - @chars[3, 2] = 'λλλ' - assert_equal 'ã“ã«Î·Î»Î»Î»', @chars + @chars[2] = "a" + assert_equal "ã“ã«aã‚", @chars + @chars[2] = "ηη" + assert_equal "ã“ã«Î·Î·ã‚", @chars + @chars[3, 2] = "λλλ" + assert_equal "ã“ã«Î·Î»Î»Î»", @chars @chars[1, 0] = "λ" - assert_equal 'ã“λã«Î·Î»Î»Î»', @chars + assert_equal "ã“λã«Î·Î»Î»Î»", @chars @chars[4..6] = "ηη" - assert_equal 'ã“λã«Î·Î·Î·', @chars + assert_equal "ã“λã«Î·Î·Î·", @chars @chars[/ηη/] = "λλλ" - assert_equal 'ã“λã«Î»Î»Î»Î·', @chars + assert_equal "ã“λã«Î»Î»Î»Î·", @chars @chars[/(λλ)(.)/, 2] = "α" - assert_equal 'ã“λã«Î»Î»Î±Î·', @chars + assert_equal "ã“λã«Î»Î»Î±Î·", @chars @chars["α"] = "¢" - assert_equal 'ã“λã«Î»Î»Â¢Î·', @chars + assert_equal "ã“λã«Î»Î»Â¢Î·", @chars @chars["λλ"] = "ααα" - assert_equal 'ã“λã«Î±Î±Î±Â¢Î·', @chars + assert_equal "ã“λã«Î±Î±Î±Â¢Î·", @chars end def test_indexed_insert_should_raise_on_index_overflow before = @chars.to_s - assert_raise(IndexError) { @chars[10] = 'a' } - assert_raise(IndexError) { @chars[10, 4] = 'a' } - assert_raise(IndexError) { @chars[/ii/] = 'a' } - assert_raise(IndexError) { @chars[/()/, 10] = 'a' } + assert_raise(IndexError) { @chars[10] = "a" } + assert_raise(IndexError) { @chars[10, 4] = "a" } + assert_raise(IndexError) { @chars[/ii/] = "a" } + assert_raise(IndexError) { @chars[/()/, 10] = "a" } assert_equal before, @chars end def test_indexed_insert_should_raise_on_range_overflow before = @chars.to_s - assert_raise(RangeError) { @chars[10..12] = 'a' } + assert_raise(RangeError) { @chars[10..12] = "a" } assert_equal before, @chars end def test_rjust_should_raise_argument_errors_on_bad_arguments - assert_raise(ArgumentError) { @chars.rjust(10, '') } + assert_raise(ArgumentError) { @chars.rjust(10, "") } assert_raise(ArgumentError) { @chars.rjust } end @@ -292,15 +292,15 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase assert_equal UNICODE_STRING, @chars.rjust(4) assert_equal " #{UNICODE_STRING}", @chars.rjust(5) assert_equal " #{UNICODE_STRING}", @chars.rjust(7) - assert_equal "---#{UNICODE_STRING}", @chars.rjust(7, '-') - assert_equal "ααα#{UNICODE_STRING}", @chars.rjust(7, 'α') - assert_equal "aba#{UNICODE_STRING}", @chars.rjust(7, 'ab') - assert_equal "αηα#{UNICODE_STRING}", @chars.rjust(7, 'αη') - assert_equal "αηαη#{UNICODE_STRING}", @chars.rjust(8, 'αη') + assert_equal "---#{UNICODE_STRING}", @chars.rjust(7, "-") + assert_equal "ααα#{UNICODE_STRING}", @chars.rjust(7, "α") + assert_equal "aba#{UNICODE_STRING}", @chars.rjust(7, "ab") + assert_equal "αηα#{UNICODE_STRING}", @chars.rjust(7, "αη") + assert_equal "αηαη#{UNICODE_STRING}", @chars.rjust(8, "αη") end def test_ljust_should_raise_argument_errors_on_bad_arguments - assert_raise(ArgumentError) { @chars.ljust(10, '') } + assert_raise(ArgumentError) { @chars.ljust(10, "") } assert_raise(ArgumentError) { @chars.ljust } end @@ -310,15 +310,15 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase assert_equal UNICODE_STRING, @chars.ljust(4) assert_equal "#{UNICODE_STRING} ", @chars.ljust(5) assert_equal "#{UNICODE_STRING} ", @chars.ljust(7) - assert_equal "#{UNICODE_STRING}---", @chars.ljust(7, '-') - assert_equal "#{UNICODE_STRING}ααα", @chars.ljust(7, 'α') - assert_equal "#{UNICODE_STRING}aba", @chars.ljust(7, 'ab') - assert_equal "#{UNICODE_STRING}αηα", @chars.ljust(7, 'αη') - assert_equal "#{UNICODE_STRING}αηαη", @chars.ljust(8, 'αη') + assert_equal "#{UNICODE_STRING}---", @chars.ljust(7, "-") + assert_equal "#{UNICODE_STRING}ααα", @chars.ljust(7, "α") + assert_equal "#{UNICODE_STRING}aba", @chars.ljust(7, "ab") + assert_equal "#{UNICODE_STRING}αηα", @chars.ljust(7, "αη") + assert_equal "#{UNICODE_STRING}αηαη", @chars.ljust(8, "αη") end def test_center_should_raise_argument_errors_on_bad_arguments - assert_raise(ArgumentError) { @chars.center(10, '') } + assert_raise(ArgumentError) { @chars.center(10, "") } assert_raise(ArgumentError) { @chars.center } end @@ -329,15 +329,15 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase assert_equal "#{UNICODE_STRING} ", @chars.center(5) assert_equal " #{UNICODE_STRING} ", @chars.center(6) assert_equal " #{UNICODE_STRING} ", @chars.center(7) - assert_equal "--#{UNICODE_STRING}--", @chars.center(8, '-') - assert_equal "--#{UNICODE_STRING}---", @chars.center(9, '-') - assert_equal "αα#{UNICODE_STRING}αα", @chars.center(8, 'α') - assert_equal "αα#{UNICODE_STRING}ααα", @chars.center(9, 'α') - assert_equal "a#{UNICODE_STRING}ab", @chars.center(7, 'ab') - assert_equal "ab#{UNICODE_STRING}ab", @chars.center(8, 'ab') - assert_equal "abab#{UNICODE_STRING}abab", @chars.center(12, 'ab') - assert_equal "α#{UNICODE_STRING}αη", @chars.center(7, 'αη') - assert_equal "αη#{UNICODE_STRING}αη", @chars.center(8, 'αη') + assert_equal "--#{UNICODE_STRING}--", @chars.center(8, "-") + assert_equal "--#{UNICODE_STRING}---", @chars.center(9, "-") + assert_equal "αα#{UNICODE_STRING}αα", @chars.center(8, "α") + assert_equal "αα#{UNICODE_STRING}ααα", @chars.center(9, "α") + assert_equal "a#{UNICODE_STRING}ab", @chars.center(7, "ab") + assert_equal "ab#{UNICODE_STRING}ab", @chars.center(8, "ab") + assert_equal "abab#{UNICODE_STRING}abab", @chars.center(12, "ab") + assert_equal "α#{UNICODE_STRING}αη", @chars.center(7, "αη") + assert_equal "αη#{UNICODE_STRING}αη", @chars.center(8, "αη") end def test_lstrip_strips_whitespace_from_the_left_of_the_string @@ -367,20 +367,20 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase end def test_size_returns_characters_instead_of_bytes - assert_equal 0, ''.mb_chars.size + assert_equal 0, "".mb_chars.size assert_equal 4, @chars.size assert_equal 4, @chars.length assert_equal 5, ASCII_STRING.mb_chars.size end def test_reverse_reverses_characters - assert_equal '', ''.mb_chars.reverse - assert_equal 'ã‚ã¡ã«ã“', @chars.reverse + assert_equal "", "".mb_chars.reverse + assert_equal "ã‚ã¡ã«ã“", @chars.reverse end def test_reverse_should_work_with_normalized_strings - str = 'bös' - reversed_str = 'söb' + str = "bös" + reversed_str = "söb" assert_equal chars(reversed_str).normalize(:kc), chars(str).normalize(:kc).reverse assert_equal chars(reversed_str).normalize(:c), chars(str).normalize(:c).reverse assert_equal chars(reversed_str).normalize(:d), chars(str).normalize(:d).reverse @@ -390,27 +390,27 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase end def test_slice_should_take_character_offsets - assert_equal 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_equal '', ''.mb_chars.slice(0..10) - assert_equal 'ã«ã¡ã‚', @chars.slice(1..3) - assert_equal 'ã«ã¡ã‚', @chars.slice(1, 3) - assert_equal 'ã“', @chars.slice(0, 1) - assert_equal 'ã¡ã‚', @chars.slice(2..10) - assert_equal '', @chars.slice(4..10) - assert_equal 'ã«', @chars.slice(/ã«/u) - assert_equal 'ã«ã¡', @chars.slice(/ã«./u) + assert_equal 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_equal "", "".mb_chars.slice(0..10) + assert_equal "ã«ã¡ã‚", @chars.slice(1..3) + assert_equal "ã«ã¡ã‚", @chars.slice(1, 3) + assert_equal "ã“", @chars.slice(0, 1) + assert_equal "ã¡ã‚", @chars.slice(2..10) + assert_equal "", @chars.slice(4..10) + assert_equal "ã«", @chars.slice(/ã«/u) + assert_equal "ã«ã¡", @chars.slice(/ã«./u) assert_equal nil, @chars.slice(/unknown/u) - assert_equal 'ã«ã¡', @chars.slice(/(ã«ã¡)/u, 1) + assert_equal "ã«ã¡", @chars.slice(/(ã«ã¡)/u, 1) assert_equal nil, @chars.slice(/(ã«ã¡)/u, 2) assert_equal nil, @chars.slice(7..6) end def test_slice_bang_returns_sliced_out_substring - assert_equal 'ã«ã¡', @chars.slice!(1..2) + assert_equal "ã«ã¡", @chars.slice!(1..2) end def test_slice_bang_returns_nil_on_out_of_bound_arguments @@ -418,17 +418,17 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase end def test_slice_bang_removes_the_slice_from_the_receiver - chars = 'úüù'.mb_chars + chars = "úüù".mb_chars chars.slice!(0,2) - assert_equal 'ù', chars + assert_equal "ù", chars end def test_slice_bang_returns_nil_and_does_not_modify_receiver_if_out_of_bounds - string = 'úüù' + string = "úüù" chars = string.mb_chars assert_nil chars.slice!(4, 5) - assert_equal 'úüù', chars - assert_equal 'úüù', string + assert_equal "úüù", chars + assert_equal "úüù", string end def test_slice_should_throw_exceptions_on_invalid_arguments @@ -442,48 +442,48 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase end def test_upcase_should_upcase_ascii_characters - assert_equal '', ''.mb_chars.upcase - assert_equal 'ABC', 'aBc'.mb_chars.upcase + assert_equal "", "".mb_chars.upcase + assert_equal "ABC", "aBc".mb_chars.upcase end def test_downcase_should_downcase_ascii_characters - assert_equal '', ''.mb_chars.downcase - assert_equal 'abc', 'aBc'.mb_chars.downcase + assert_equal "", "".mb_chars.downcase + assert_equal "abc", "aBc".mb_chars.downcase end def test_swapcase_should_swap_ascii_characters - assert_equal '', ''.mb_chars.swapcase - assert_equal 'AbC', 'aBc'.mb_chars.swapcase + assert_equal "", "".mb_chars.swapcase + assert_equal "AbC", "aBc".mb_chars.swapcase end def test_capitalize_should_work_on_ascii_characters - assert_equal '', ''.mb_chars.capitalize - assert_equal 'Abc', 'abc'.mb_chars.capitalize + assert_equal "", "".mb_chars.capitalize + assert_equal "Abc", "abc".mb_chars.capitalize end def test_titleize_should_work_on_ascii_characters - assert_equal '', ''.mb_chars.titleize - assert_equal 'Abc Abc', 'abc abc'.mb_chars.titleize + assert_equal "", "".mb_chars.titleize + assert_equal "Abc Abc", "abc abc".mb_chars.titleize end def test_respond_to_knows_which_methods_the_proxy_responds_to - assert ''.mb_chars.respond_to?(:slice) # Defined on Chars - assert ''.mb_chars.respond_to?(:capitalize!) # Defined on Chars - assert ''.mb_chars.respond_to?(:gsub) # Defined on String - assert !''.mb_chars.respond_to?(:undefined_method) # Not defined + assert "".mb_chars.respond_to?(:slice) # Defined on Chars + assert "".mb_chars.respond_to?(:capitalize!) # Defined on Chars + assert "".mb_chars.respond_to?(:gsub) # Defined on String + assert !"".mb_chars.respond_to?(:undefined_method) # Not defined end def test_method_works_for_proxyed_methods - assert_equal 'll', 'hello'.mb_chars.method(:slice).call(2..3) # Defined on Chars - chars = 'hello'.mb_chars - assert_equal 'Hello', chars.method(:capitalize!).call # Defined on Chars - assert_equal 'Hello', chars - assert_equal 'jello', 'hello'.mb_chars.method(:gsub).call(/h/, 'j') # Defined on String - assert_raise(NameError){ ''.mb_chars.method(:undefined_method) } # Not defined + assert_equal "ll", "hello".mb_chars.method(:slice).call(2..3) # Defined on Chars + chars = "hello".mb_chars + assert_equal "Hello", chars.method(:capitalize!).call # Defined on Chars + assert_equal "Hello", chars + assert_equal "jello", "hello".mb_chars.method(:gsub).call(/h/, "j") # Defined on String + assert_raise(NameError) { "".mb_chars.method(:undefined_method) } # Not defined end def test_acts_like_string - assert 'Bambi'.mb_chars.acts_like_string? + assert "Bambi".mb_chars.acts_like_string? end end @@ -495,25 +495,25 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase def test_upcase_should_be_unicode_aware assert_equal "ÐБВГД\0F", chars("аБвгд\0f").upcase - assert_equal 'ã“ã«ã¡ã‚', chars('ã“ã«ã¡ã‚').upcase + assert_equal "ã“ã«ã¡ã‚", chars("ã“ã«ã¡ã‚").upcase end def test_downcase_should_be_unicode_aware assert_equal "абвгд\0f", chars("аБвгд\0F").downcase - assert_equal 'ã“ã«ã¡ã‚', chars('ã“ã«ã¡ã‚').downcase + assert_equal "ã“ã«ã¡ã‚", chars("ã“ã«ã¡ã‚").downcase end def test_swapcase_should_be_unicode_aware assert_equal "аaéÜ\0f", chars("ÐAÉü\0F").swapcase - assert_equal 'ã“ã«ã¡ã‚', chars('ã“ã«ã¡ã‚').swapcase + assert_equal "ã“ã«ã¡ã‚", chars("ã“ã«ã¡ã‚").swapcase end def test_capitalize_should_be_unicode_aware - { 'аБвг аБвг' => 'Ðбвг абвг', - 'аБвг ÐБВГ' => 'Ðбвг абвг', - 'ÐБВГ ÐБВГ' => 'Ðбвг абвг', - '' => '' }.each do |f,t| - assert_equal t, chars(f).capitalize + { "аБвг аБвг" => "Ðбвг абвг", + "аБвг ÐБВГ" => "Ðбвг абвг", + "ÐБВГ ÐБВГ" => "Ðбвг абвг", + "" => "" }.each do |f,t| + assert_equal t, chars(f).capitalize end end @@ -527,7 +527,7 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase end def test_limit_should_not_break_on_blank_strings - example = chars('') + example = chars("") assert_equal example, example.limit(0) assert_equal example, example.limit(1) end @@ -537,23 +537,23 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase bytesize = UNICODE_STRING.bytesize assert_equal UNICODE_STRING, example.limit(bytesize) - assert_equal '', example.limit(0) - assert_equal '', example.limit(1) - assert_equal 'ã“', example.limit(3) - assert_equal 'ã“ã«', example.limit(6) - assert_equal 'ã“ã«', example.limit(8) - assert_equal 'ã“ã«ã¡', example.limit(9) - assert_equal 'ã“ã«ã¡ã‚', example.limit(50) + assert_equal "", example.limit(0) + assert_equal "", example.limit(1) + assert_equal "ã“", example.limit(3) + assert_equal "ã“ã«", example.limit(6) + assert_equal "ã“ã«", example.limit(8) + assert_equal "ã“ã«ã¡", example.limit(9) + assert_equal "ã“ã«ã¡ã‚", example.limit(50) end def test_limit_should_work_on_an_ascii_string ascii = chars(ASCII_STRING) assert_equal ASCII_STRING, ascii.limit(ASCII_STRING.length) - assert_equal '', ascii.limit(0) - assert_equal 'o', ascii.limit(1) - assert_equal 'oh', ascii.limit(2) - assert_equal 'ohay', ascii.limit(4) - assert_equal 'ohayo', ascii.limit(50) + assert_equal "", ascii.limit(0) + assert_equal "o", ascii.limit(1) + assert_equal "oh", ascii.limit(2) + assert_equal "ohay", ascii.limit(4) + assert_equal "ohayo", ascii.limit(50) end def test_limit_should_keep_under_the_specified_byte_limit @@ -565,7 +565,7 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase def test_composition_exclusion_is_set_up_properly # Normalization of DEVANAGARI LETTER QA breaks when composition exclusion isn't used correctly - qa = [0x915, 0x93c].pack('U*') + qa = [0x915, 0x93c].pack("U*") assert_equal qa, chars(qa).normalize(:c) end @@ -575,7 +575,7 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase [ [0x0B47, 0x0300, 0x0B3E], [0x1100, 0x0300, 0x1161] - ].map { |c| c.pack('U*') }.each do |c| + ].map { |c| c.pack("U*") }.each do |c| assert_equal_codepoints c, chars(c).normalize(:c) end end @@ -599,7 +599,7 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase 323 # COMBINING DOT BELOW ].pack("U*") - assert_equal_codepoints '', chars('').normalize + assert_equal_codepoints "", chars("").normalize assert_equal_codepoints [44,105,106,328,323].pack("U*"), chars(comp_str).normalize(:kc).to_s assert_equal_codepoints [44,307,328,323].pack("U*"), chars(comp_str).normalize(:c).to_s assert_equal_codepoints [44,307,110,780,78,769].pack("U*"), chars(comp_str).normalize(:d).to_s @@ -608,10 +608,10 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase def test_should_compute_grapheme_length [ - ['', 0], - ['abc', 3], - ['ã“ã«ã¡ã‚', 4], - [[0x0924, 0x094D, 0x0930].pack('U*'), 2], + ["", 0], + ["abc", 3], + ["ã“ã«ã¡ã‚", 4], + [[0x0924, 0x094D, 0x0930].pack("U*"), 2], # GB3 [%w(cr lf), 1], # GB4 @@ -699,9 +699,9 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase end byte_string = "\270\236\010\210\245" - tidy_string = [0xb8, 0x17e, 0x8, 0x2c6, 0xa5].pack('U*') + tidy_string = [0xb8, 0x17e, 0x8, 0x2c6, 0xa5].pack("U*") assert_equal_codepoints tidy_string, chars(byte_string).tidy_bytes - assert_nothing_raised { chars(byte_string).tidy_bytes.to_s.unpack('U*') } + assert_nothing_raised { chars(byte_string).tidy_bytes.to_s.unpack("U*") } # UTF-8 leading byte followed by too few continuation bytes assert_equal_codepoints "\xc3\xb0\xc2\xa5\xc2\xa4\x21", chars("\xf0\xa5\xa4\x21").tidy_bytes @@ -720,16 +720,16 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase private - def string_from_classes(classes) - # Characters from the character classes as described in UAX #29 - character_from_class = { - :l => 0x1100, :v => 0x1160, :t => 0x11A8, :lv => 0xAC00, :lvt => 0xAC01, :cr => 0x000D, :lf => 0x000A, - :extend => 0x094D, :n => 0x64, :spacingmark => 0x0903, :r => 0x1F1E6, :control => 0x0001 - } - classes.collect do |k| - character_from_class[k.intern] - end.pack('U*') - end + def string_from_classes(classes) + # Characters from the character classes as described in UAX #29 + character_from_class = { + l: 0x1100, v: 0x1160, t: 0x11A8, lv: 0xAC00, lvt: 0xAC01, cr: 0x000D, lf: 0x000A, + extend: 0x094D, n: 0x64, spacingmark: 0x0903, r: 0x1F1E6, control: 0x0001 + } + classes.collect do |k| + character_from_class[k.intern] + end.pack("U*") + end end class MultibyteInternalsTest < ActiveSupport::TestCase diff --git a/activesupport/test/multibyte_conformance_test.rb b/activesupport/test/multibyte_conformance_test.rb index 5df8f32e46..bf004d7924 100644 --- a/activesupport/test/multibyte_conformance_test.rb +++ b/activesupport/test/multibyte_conformance_test.rb @@ -1,34 +1,14 @@ -require 'abstract_unit' -require 'multibyte_test_helpers' +require "abstract_unit" +require "multibyte_test_helpers" -require 'fileutils' -require 'open-uri' -require 'tmpdir' +require "fileutils" +require "open-uri" +require "tmpdir" class MultibyteConformanceTest < ActiveSupport::TestCase - class Downloader - def self.download(from, to) - unless File.exist?(to) - unless File.exist?(File.dirname(to)) - system "mkdir -p #{File.dirname(to)}" - end - open(from) do |source| - File.open(to, 'w') do |target| - source.each_line do |l| - target.write l - end - end - end - end - true - end - end - include MultibyteTestHelpers - UNIDATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd" - UNIDATA_FILE = '/NormalizationTest.txt' - CACHE_DIR = File.join(Dir.tmpdir, 'cache') + UNIDATA_FILE = "/NormalizationTest.txt" FileUtils.mkdir_p(CACHE_DIR) RUN_P = begin Downloader.download(UNIDATA_URL + UNIDATA_FILE, CACHE_DIR + UNIDATA_FILE) @@ -104,17 +84,17 @@ class MultibyteConformanceTest < ActiveSupport::TestCase protected def each_line_of_norm_tests(&block) - File.open(File.join(CACHE_DIR, UNIDATA_FILE), 'r') do | f | + File.open(File.join(CACHE_DIR, UNIDATA_FILE), "r") do | f | until f.eof? line = f.gets.chomp! - next if (line.empty? || line =~ /^\#/) + next if line.empty? || line.start_with?("#") cols, comment = line.split("#") cols = cols.split(";").map(&:strip).reject(&:empty?) next unless cols.length == 5 # codepoints are in hex in the test suite, pack wants them as integers - cols.map!{|c| c.split.map{|codepoint| codepoint.to_i(16)}.pack("U*") } + cols.map! { |c| c.split.map { |codepoint| codepoint.to_i(16) }.pack("U*") } cols << comment yield(*cols) @@ -123,6 +103,6 @@ class MultibyteConformanceTest < ActiveSupport::TestCase end def inspect_codepoints(str) - str.to_s.unpack("U*").map{|cp| cp.to_s(16) }.join(' ') + str.to_s.unpack("U*").map { |cp| cp.to_s(16) }.join(" ") end end diff --git a/activesupport/test/multibyte_grapheme_break_conformance_test.rb b/activesupport/test/multibyte_grapheme_break_conformance_test.rb index 229f24990e..04a7d290d9 100644 --- a/activesupport/test/multibyte_grapheme_break_conformance_test.rb +++ b/activesupport/test/multibyte_grapheme_break_conformance_test.rb @@ -1,37 +1,23 @@ # encoding: utf-8 -require 'abstract_unit' +require "abstract_unit" +require "multibyte_test_helpers" -require 'fileutils' -require 'open-uri' -require 'tmpdir' +require "fileutils" +require "open-uri" +require "tmpdir" class MultibyteGraphemeBreakConformanceTest < ActiveSupport::TestCase - class Downloader - def self.download(from, to) - unless File.exist?(to) - $stderr.puts "Downloading #{from} to #{to}" - unless File.exist?(File.dirname(to)) - system "mkdir -p #{File.dirname(to)}" - end - open(from) do |source| - File.open(to, 'w') do |target| - source.each_line do |l| - target.write l - end - end - end - end - end - end + include MultibyteTestHelpers - TEST_DATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd/auxiliary" - TEST_DATA_FILE = '/GraphemeBreakTest.txt' - CACHE_DIR = File.join(Dir.tmpdir, 'cache') + UNIDATA_FILE = "/auxiliary/GraphemeBreakTest.txt" + RUN_P = begin + Downloader.download(UNIDATA_URL + UNIDATA_FILE, CACHE_DIR + UNIDATA_FILE) + rescue + end def setup - FileUtils.mkdir_p(CACHE_DIR) - Downloader.download(TEST_DATA_URL + TEST_DATA_FILE, CACHE_DIR + TEST_DATA_FILE) + skip "Unable to download test data" unless RUN_P end def test_breaks @@ -46,20 +32,20 @@ class MultibyteGraphemeBreakConformanceTest < ActiveSupport::TestCase 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 - File.open(File.join(CACHE_DIR, TEST_DATA_FILE), 'r') do | f | - until f.eof? || (max_test_lines > 21 and lines > max_test_lines) + File.open(File.join(CACHE_DIR, UNIDATA_FILE), "r") do | f | + until f.eof? || (max_test_lines > 21 && lines > max_test_lines) lines += 1 line = f.gets.chomp! - next if (line.empty? || line =~ /^\#/) + next if line.empty? || line.start_with?("#") cols, comment = line.split("#") # Cluster breaks are represented by ÷ - clusters = cols.split("÷").map{|e| e.strip}.reject{|e| e.empty? } + clusters = cols.split("÷").map { |e| e.strip }.reject { |e| e.empty? } clusters = clusters.map do |cluster| # Codepoints within each cluster are separated by × - codepoints = cluster.split("×").map{|e| e.strip}.reject{|e| e.empty? } + codepoints = cluster.split("×").map { |e| e.strip }.reject { |e| e.empty? } # codepoints are in hex in the test suite, pack wants them as integers - codepoints.map{|codepoint| codepoint.to_i(16)} + codepoints.map { |codepoint| codepoint.to_i(16) } end # The tests contain a solitary U+D800 <Non Private Use High diff --git a/activesupport/test/multibyte_normalization_conformance_test.rb b/activesupport/test/multibyte_normalization_conformance_test.rb index 8bc91ef708..e013bd578f 100644 --- a/activesupport/test/multibyte_normalization_conformance_test.rb +++ b/activesupport/test/multibyte_normalization_conformance_test.rb @@ -1,41 +1,24 @@ # encoding: utf-8 -require 'abstract_unit' -require 'multibyte_test_helpers' +require "abstract_unit" +require "multibyte_test_helpers" -require 'fileutils' -require 'open-uri' -require 'tmpdir' +require "fileutils" +require "open-uri" +require "tmpdir" class MultibyteNormalizationConformanceTest < ActiveSupport::TestCase - class Downloader - def self.download(from, to) - unless File.exist?(to) - $stderr.puts "Downloading #{from} to #{to}" - unless File.exist?(File.dirname(to)) - system "mkdir -p #{File.dirname(to)}" - end - open(from) do |source| - File.open(to, 'w') do |target| - source.each_line do |l| - target.write l - end - end - end - end - end - end - include MultibyteTestHelpers - UNIDATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd" - UNIDATA_FILE = '/NormalizationTest.txt' - CACHE_DIR = File.join(Dir.tmpdir, 'cache') + UNIDATA_FILE = "/NormalizationTest.txt" + RUN_P = begin + Downloader.download(UNIDATA_URL + UNIDATA_FILE, CACHE_DIR + UNIDATA_FILE) + rescue + end def setup - FileUtils.mkdir_p(CACHE_DIR) - Downloader.download(UNIDATA_URL + UNIDATA_FILE, CACHE_DIR + UNIDATA_FILE) @proxy = ActiveSupport::Multibyte::Chars + skip "Unable to download test data" unless RUN_P end def test_normalizations_C @@ -104,18 +87,18 @@ class MultibyteNormalizationConformanceTest < ActiveSupport::TestCase 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 - File.open(File.join(CACHE_DIR, UNIDATA_FILE), 'r') do | f | - until f.eof? || (max_test_lines > 38 and lines > max_test_lines) + File.open(File.join(CACHE_DIR, UNIDATA_FILE), "r") do | f | + until f.eof? || (max_test_lines > 38 && lines > max_test_lines) lines += 1 line = f.gets.chomp! - next if (line.empty? || line =~ /^\#/) + next if line.empty? || line.start_with?("#") cols, comment = line.split("#") - cols = cols.split(";").map{|e| e.strip}.reject{|e| e.empty? } + cols = cols.split(";").map { |e| e.strip }.reject { |e| e.empty? } next unless cols.length == 5 # codepoints are in hex in the test suite, pack wants them as integers - cols.map!{|c| c.split.map{|codepoint| codepoint.to_i(16)}.pack("U*") } + cols.map! { |c| c.split.map { |codepoint| codepoint.to_i(16) }.pack("U*") } cols << comment yield(*cols) @@ -124,6 +107,6 @@ class MultibyteNormalizationConformanceTest < ActiveSupport::TestCase end def inspect_codepoints(str) - str.to_s.unpack("U*").map{|cp| cp.to_s(16) }.join(' ') + str.to_s.unpack("U*").map { |cp| cp.to_s(16) }.join(" ") end end diff --git a/activesupport/test/multibyte_proxy_test.rb b/activesupport/test/multibyte_proxy_test.rb index 360cf57302..c303097f80 100644 --- a/activesupport/test/multibyte_proxy_test.rb +++ b/activesupport/test/multibyte_proxy_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class MultibyteProxyText < ActiveSupport::TestCase class AsciiOnlyEncoder @@ -6,7 +6,7 @@ class MultibyteProxyText < ActiveSupport::TestCase alias to_s wrapped_string def initialize(string) - @wrapped_string = string.gsub(/[^\u0000-\u007F]/, '?') + @wrapped_string = string.gsub(/[^\u0000-\u007F]/, "?") end end diff --git a/activesupport/test/multibyte_test_helpers.rb b/activesupport/test/multibyte_test_helpers.rb index 58cf5488cd..22c586c50d 100644 --- a/activesupport/test/multibyte_test_helpers.rb +++ b/activesupport/test/multibyte_test_helpers.rb @@ -1,6 +1,28 @@ module MultibyteTestHelpers - UNICODE_STRING = 'ã“ã«ã¡ã‚'.freeze - ASCII_STRING = 'ohayo'.freeze + class Downloader + def self.download(from, to) + unless File.exist?(to) + unless File.exist?(File.dirname(to)) + system "mkdir -p #{File.dirname(to)}" + end + open(from) do |source| + File.open(to, "w") do |target| + source.each_line do |l| + target.write l + end + end + end + end + true + end + end + + UNIDATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd" + CACHE_DIR = "#{Dir.tmpdir}/cache/unicode_conformance" + FileUtils.mkdir_p(CACHE_DIR) + + UNICODE_STRING = "ã“ã«ã¡ã‚".freeze + ASCII_STRING = "ohayo".freeze BYTE_STRING = "\270\236\010\210\245".force_encoding("ASCII-8BIT").freeze def chars(str) @@ -8,7 +30,7 @@ module MultibyteTestHelpers end def inspect_codepoints(str) - str.to_s.unpack("U*").map{|cp| cp.to_s(16) }.join(' ') + str.to_s.unpack("U*").map { |cp| cp.to_s(16) }.join(" ") end def assert_equal_codepoints(expected, actual, message=nil) diff --git a/activesupport/test/multibyte_unicode_database_test.rb b/activesupport/test/multibyte_unicode_database_test.rb index dd33641ec2..3724782930 100644 --- a/activesupport/test/multibyte_unicode_database_test.rb +++ b/activesupport/test/multibyte_unicode_database_test.rb @@ -1,8 +1,6 @@ -require 'abstract_unit' - +require "abstract_unit" class MultibyteUnicodeDatabaseTest < ActiveSupport::TestCase - include ActiveSupport::Multibyte::Unicode def setup diff --git a/activesupport/test/notifications/evented_notification_test.rb b/activesupport/test/notifications/evented_notification_test.rb index f690ad43fc..688971c858 100644 --- a/activesupport/test/notifications/evented_notification_test.rb +++ b/activesupport/test/notifications/evented_notification_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module ActiveSupport module Notifications @@ -28,26 +28,26 @@ module ActiveSupport def test_evented_listener notifier = Fanout.new listener = Listener.new - notifier.subscribe 'hi', listener - notifier.start 'hi', 1, {} - notifier.start 'hi', 2, {} - notifier.finish 'hi', 2, {} - notifier.finish 'hi', 1, {} + notifier.subscribe "hi", listener + notifier.start "hi", 1, {} + notifier.start "hi", 2, {} + notifier.finish "hi", 2, {} + notifier.finish "hi", 1, {} assert_equal 4, listener.events.length assert_equal [ - [:start, 'hi', 1, {}], - [:start, 'hi', 2, {}], - [:finish, 'hi', 2, {}], - [:finish, 'hi', 1, {}], + [:start, "hi", 1, {}], + [:start, "hi", 2, {}], + [:finish, "hi", 2, {}], + [:finish, "hi", 1, {}], ], listener.events end def test_evented_listener_no_events notifier = Fanout.new listener = Listener.new - notifier.subscribe 'hi', listener - notifier.start 'world', 1, {} + notifier.subscribe "hi", listener + notifier.start "world", 1, {} assert_equal 0, listener.events.length end @@ -55,31 +55,31 @@ module ActiveSupport notifier = Fanout.new listener = Listener.new notifier.subscribe nil, listener - notifier.start 'hello', 1, {} - notifier.start 'world', 1, {} - notifier.finish 'world', 1, {} - notifier.finish 'hello', 1, {} + notifier.start "hello", 1, {} + notifier.start "world", 1, {} + notifier.finish "world", 1, {} + notifier.finish "hello", 1, {} assert_equal 4, listener.events.length assert_equal [ - [:start, 'hello', 1, {}], - [:start, 'world', 1, {}], - [:finish, 'world', 1, {}], - [:finish, 'hello', 1, {}], + [:start, "hello", 1, {}], + [:start, "world", 1, {}], + [:finish, "world", 1, {}], + [:finish, "hello", 1, {}], ], listener.events end def test_evented_listener_priority notifier = Fanout.new listener = ListenerWithTimedSupport.new - notifier.subscribe 'hi', listener + notifier.subscribe "hi", listener - notifier.start 'hi', 1, {} - notifier.finish 'hi', 1, {} + notifier.start "hi", 1, {} + notifier.finish "hi", 1, {} assert_equal [ - [:start, 'hi', 1, {}], - [:finish, 'hi', 1, {}] + [:start, "hi", 1, {}], + [:finish, "hi", 1, {}] ], listener.events end end diff --git a/activesupport/test/notifications/instrumenter_test.rb b/activesupport/test/notifications/instrumenter_test.rb index f46e96f636..e454e6897b 100644 --- a/activesupport/test/notifications/instrumenter_test.rb +++ b/activesupport/test/notifications/instrumenter_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/notifications/instrumenter' +require "abstract_unit" +require "active_support/notifications/instrumenter" module ActiveSupport module Notifications @@ -22,7 +22,7 @@ module ActiveSupport super @notifier = TestNotifier.new @instrumenter = Instrumenter.new @notifier - @payload = { :foo => Object.new } + @payload = { foo: Object.new } end def test_instrument @@ -39,7 +39,7 @@ module ActiveSupport assert_equal 1, notifier.finishes.size name, _, payload = notifier.finishes.first assert_equal "awesome", name - assert_equal Hash[:result => 2], payload + assert_equal Hash[result: 2], payload end def test_start diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb index 1cb17e6197..a6f0d82e8a 100644 --- a/activesupport/test/notifications_test.rb +++ b/activesupport/test/notifications_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/core_ext/module/delegation' +require "abstract_unit" +require "active_support/core_ext/module/delegation" module Notifications class TestCase < ActiveSupport::TestCase @@ -31,7 +31,7 @@ module Notifications expected = [name, name] events = [] - callback = lambda {|*_| events << _.first} + callback = lambda { |*_| events << _.first } ActiveSupport::Notifications.subscribed(callback, name) do ActiveSupport::Notifications.instrument(name) ActiveSupport::Notifications.instrument(name2) @@ -49,10 +49,10 @@ module Notifications old_notifier = ActiveSupport::Notifications.notifier ActiveSupport::Notifications.notifier = ActiveSupport::Notifications::Fanout.new - ActiveSupport::Notifications.subscribe('foo', TestSubscriber.new) + ActiveSupport::Notifications.subscribe("foo", TestSubscriber.new) - ActiveSupport::Notifications.instrument('foo') do - ActiveSupport::Notifications.subscribe('foo') {} + ActiveSupport::Notifications.instrument("foo") do + ActiveSupport::Notifications.subscribe("foo") {} end ensure ActiveSupport::Notifications.notifier = old_notifier @@ -141,26 +141,26 @@ module Notifications def test_log_subscriber_with_string events = [] - @notifier.subscribe('1') { |*args| events << args } + @notifier.subscribe("1") { |*args| events << args } - @notifier.publish '1' - @notifier.publish '1.a' - @notifier.publish 'a.1' + @notifier.publish "1" + @notifier.publish "1.a" + @notifier.publish "a.1" @notifier.wait - assert_equal [['1']], events + assert_equal [["1"]], events end def test_log_subscriber_with_pattern events = [] @notifier.subscribe(/\d/) { |*args| events << args } - @notifier.publish '1' - @notifier.publish 'a.1' - @notifier.publish '1.a' + @notifier.publish "1" + @notifier.publish "a.1" + @notifier.publish "1.a" @notifier.wait - assert_equal [['1'], ['a.1'], ['1.a']], events + assert_equal [["1"], ["a.1"], ["1.a"]], events end def test_multiple_log_subscribers @@ -188,7 +188,7 @@ module Notifications end class InstrumentationTest < TestCase - delegate :instrument, :to => ActiveSupport::Notifications + delegate :instrument, to: ActiveSupport::Notifications def test_instrument_returns_block_result assert_equal 2, instrument(:awesome) { 1 + 1 } @@ -198,7 +198,7 @@ module Notifications assert_equal 2, instrument(:awesome) { |p| p[:result] = 1 + 1 } assert_equal 1, @events.size assert_equal :awesome, @events.first.name - assert_equal Hash[:result => 2], @events.first.payload + assert_equal Hash[result: 2], @events.first.payload end def test_instrumenter_exposes_its_id @@ -206,24 +206,24 @@ module Notifications end def test_nested_events_can_be_instrumented - instrument(:awesome, :payload => "notifications") do - instrument(:wot, :payload => "child") do + instrument(:awesome, payload: "notifications") do + instrument(:wot, payload: "child") do 1 + 1 end assert_equal 1, @events.size assert_equal :wot, @events.first.name - assert_equal Hash[:payload => "child"], @events.first.payload + assert_equal Hash[payload: "child"], @events.first.payload end assert_equal 2, @events.size assert_equal :awesome, @events.last.name - assert_equal Hash[:payload => "notifications"], @events.last.payload + assert_equal Hash[payload: "notifications"], @events.last.payload end def test_instrument_publishes_when_exception_is_raised begin - instrument(:awesome, :payload => "notifications") do + instrument(:awesome, payload: "notifications") do raise "FAIL" end rescue RuntimeError => e @@ -231,15 +231,15 @@ module Notifications end assert_equal 1, @events.size - assert_equal Hash[:payload => "notifications", - :exception => ["RuntimeError", "FAIL"], :exception_object => e], @events.last.payload + assert_equal Hash[payload: "notifications", + exception: ["RuntimeError", "FAIL"], exception_object: e], @events.last.payload end def test_event_is_pushed_even_without_block - instrument(:awesome, :payload => "notifications") + instrument(:awesome, payload: "notifications") assert_equal 1, @events.size assert_equal :awesome, @events.last.name - assert_equal Hash[:payload => "notifications"], @events.last.payload + assert_equal Hash[payload: "notifications"], @events.last.payload end end @@ -254,8 +254,8 @@ module Notifications end def test_events_consumes_information_given_as_payload - event = event(:foo, Time.now, Time.now + 1, random_id, :payload => :bar) - assert_equal Hash[:payload => :bar], event.payload + event = event(:foo, Time.now, Time.now + 1, random_id, payload: :bar) + assert_equal Hash[payload: :bar], event.payload end def test_event_is_parent_based_on_children diff --git a/activesupport/test/number_helper_i18n_test.rb b/activesupport/test/number_helper_i18n_test.rb index e6925e9083..4f58e6607a 100644 --- a/activesupport/test/number_helper_i18n_test.rb +++ b/activesupport/test/number_helper_i18n_test.rb @@ -1,46 +1,46 @@ -require 'abstract_unit' -require 'active_support/number_helper' +require "abstract_unit" +require "active_support/number_helper" module ActiveSupport class NumberHelperI18nTest < ActiveSupport::TestCase include ActiveSupport::NumberHelper def setup - I18n.backend.store_translations 'ts', - :number => { - :format => { :precision => 3, :delimiter => ',', :separator => '.', :significant => false, :strip_insignificant_zeros => false }, - :currency => { :format => { :unit => '&$', :format => '%u - %n', :negative_format => '(%u - %n)', :precision => 2 } }, - :human => { - :format => { - :precision => 2, - :significant => true, - :strip_insignificant_zeros => true + I18n.backend.store_translations "ts", + number: { + format: { precision: 3, delimiter: ",", separator: ".", significant: false, strip_insignificant_zeros: false }, + currency: { format: { unit: "&$", format: "%u - %n", negative_format: "(%u - %n)", precision: 2 } }, + human: { + format: { + precision: 2, + significant: true, + strip_insignificant_zeros: true }, - :storage_units => { - :format => "%n %u", - :units => { - :byte => "b", - :kb => "k" + storage_units: { + format: "%n %u", + units: { + byte: "b", + kb: "k" } }, - :decimal_units => { - :format => "%n %u", - :units => { - :deci => {:one => "Tenth", :other => "Tenths"}, - :unit => "u", - :ten => {:one => "Ten", :other => "Tens"}, - :thousand => "t", - :million => "m", - :billion =>"b", - :trillion =>"t" , - :quadrillion =>"q" + decimal_units: { + format: "%n %u", + units: { + deci: { one: "Tenth", other: "Tenths" }, + unit: "u", + ten: { one: "Ten", other: "Tens" }, + thousand: "t", + million: "m", + billion: "b", + trillion: "t" , + quadrillion: "q" } } }, - :percentage => { :format => {:delimiter => '', :precision => 2, :strip_insignificant_zeros => true} }, - :precision => { :format => {:delimiter => '', :significant => true} } + percentage: { format: { delimiter: "", precision: 2, strip_insignificant_zeros: true } }, + precision: { format: { delimiter: "", significant: true } } }, - :custom_units_for_number_to_human => {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"} + custom_units_for_number_to_human: { mili: "mm", centi: "cm", deci: "dm", unit: "m", ten: "dam", hundred: "hm", thousand: "km" } end def teardown @@ -48,101 +48,101 @@ module ActiveSupport end def test_number_to_i18n_currency - assert_equal("&$ - 10.00", number_to_currency(10, :locale => 'ts')) - assert_equal("(&$ - 10.00)", number_to_currency(-10, :locale => 'ts')) - assert_equal("-10.00 - &$", number_to_currency(-10, :locale => 'ts', :format => "%n - %u")) + assert_equal("&$ - 10.00", number_to_currency(10, locale: "ts")) + assert_equal("(&$ - 10.00)", number_to_currency(-10, locale: "ts")) + assert_equal("-10.00 - &$", number_to_currency(-10, locale: "ts", format: "%n - %u")) end def test_number_to_currency_with_empty_i18n_store - assert_equal("$10.00", number_to_currency(10, :locale => 'empty')) - assert_equal("-$10.00", number_to_currency(-10, :locale => 'empty')) + assert_equal("$10.00", number_to_currency(10, locale: "empty")) + assert_equal("-$10.00", number_to_currency(-10, locale: "empty")) end def test_locale_default_format_has_precedence_over_helper_defaults - I18n.backend.store_translations 'ts', - { :number => { :format => { :separator => ";" } } } + I18n.backend.store_translations "ts", + number: { format: { separator: ";" } } - assert_equal("&$ - 10;00", number_to_currency(10, :locale => 'ts')) + assert_equal("&$ - 10;00", number_to_currency(10, locale: "ts")) end def test_number_to_currency_without_currency_negative_format - I18n.backend.store_translations 'no_negative_format', :number => { - :currency => { :format => { :unit => '@', :format => '%n %u' } } + I18n.backend.store_translations "no_negative_format", number: { + currency: { format: { unit: "@", format: "%n %u" } } } - assert_equal("-10.00 @", number_to_currency(-10, :locale => 'no_negative_format')) + assert_equal("-10.00 @", number_to_currency(-10, locale: "no_negative_format")) end def test_number_with_i18n_precision #Delimiter was set to "" - assert_equal("10000", number_to_rounded(10000, :locale => 'ts')) + assert_equal("10000", number_to_rounded(10000, locale: "ts")) #Precision inherited and significant was set - assert_equal("1.00", number_to_rounded(1.0, :locale => 'ts')) + assert_equal("1.00", number_to_rounded(1.0, locale: "ts")) end def test_number_with_i18n_precision_and_empty_i18n_store - assert_equal("123456789.123", number_to_rounded(123456789.123456789, :locale => 'empty')) - assert_equal("1.000", number_to_rounded(1.0000, :locale => 'empty')) + assert_equal("123456789.123", number_to_rounded(123456789.123456789, locale: "empty")) + assert_equal("1.000", number_to_rounded(1.0000, locale: "empty")) end def test_number_with_i18n_delimiter #Delimiter "," and separator "." - assert_equal("1,000,000.234", number_to_delimited(1000000.234, :locale => 'ts')) + assert_equal("1,000,000.234", number_to_delimited(1000000.234, locale: "ts")) end def test_number_with_i18n_delimiter_and_empty_i18n_store - assert_equal("1,000,000.234", number_to_delimited(1000000.234, :locale => 'empty')) + assert_equal("1,000,000.234", number_to_delimited(1000000.234, locale: "empty")) end def test_number_to_i18n_percentage # to see if strip_insignificant_zeros is true - assert_equal("1%", number_to_percentage(1, :locale => 'ts')) + assert_equal("1%", number_to_percentage(1, locale: "ts")) # precision is 2, significant should be inherited - assert_equal("1.24%", number_to_percentage(1.2434, :locale => 'ts')) + assert_equal("1.24%", number_to_percentage(1.2434, locale: "ts")) # no delimiter - assert_equal("12434%", number_to_percentage(12434, :locale => 'ts')) + assert_equal("12434%", number_to_percentage(12434, locale: "ts")) end def test_number_to_i18n_percentage_and_empty_i18n_store - assert_equal("1.000%", number_to_percentage(1, :locale => 'empty')) - assert_equal("1.243%", number_to_percentage(1.2434, :locale => 'empty')) - assert_equal("12434.000%", number_to_percentage(12434, :locale => 'empty')) + assert_equal("1.000%", number_to_percentage(1, locale: "empty")) + assert_equal("1.243%", number_to_percentage(1.2434, locale: "empty")) + assert_equal("12434.000%", number_to_percentage(12434, locale: "empty")) end def test_number_to_i18n_human_size #b for bytes and k for kbytes - assert_equal("2 k", number_to_human_size(2048, :locale => 'ts')) - assert_equal("42 b", number_to_human_size(42, :locale => 'ts')) + assert_equal("2 k", number_to_human_size(2048, locale: "ts")) + assert_equal("42 b", number_to_human_size(42, locale: "ts")) end def test_number_to_i18n_human_size_with_empty_i18n_store - assert_equal("2 KB", number_to_human_size(2048, :locale => 'empty')) - assert_equal("42 Bytes", number_to_human_size(42, :locale => 'empty')) + assert_equal("2 KB", number_to_human_size(2048, locale: "empty")) + assert_equal("42 Bytes", number_to_human_size(42, locale: "empty")) end def test_number_to_human_with_default_translation_scope #Using t for thousand - assert_equal "2 t", number_to_human(2000, :locale => 'ts') + assert_equal "2 t", number_to_human(2000, locale: "ts") #Significant was set to true with precision 2, using b for billion - assert_equal "1.2 b", number_to_human(1234567890, :locale => 'ts') + assert_equal "1.2 b", number_to_human(1234567890, locale: "ts") #Using pluralization (Ten/Tens and Tenth/Tenths) - assert_equal "1 Tenth", number_to_human(0.1, :locale => 'ts') - assert_equal "1.3 Tenth", number_to_human(0.134, :locale => 'ts') - assert_equal "2 Tenths", number_to_human(0.2, :locale => 'ts') - assert_equal "1 Ten", number_to_human(10, :locale => 'ts') - assert_equal "1.2 Ten", number_to_human(12, :locale => 'ts') - assert_equal "2 Tens", number_to_human(20, :locale => 'ts') + assert_equal "1 Tenth", number_to_human(0.1, locale: "ts") + assert_equal "1.3 Tenth", number_to_human(0.134, locale: "ts") + assert_equal "2 Tenths", number_to_human(0.2, locale: "ts") + assert_equal "1 Ten", number_to_human(10, locale: "ts") + assert_equal "1.2 Ten", number_to_human(12, locale: "ts") + assert_equal "2 Tens", number_to_human(20, locale: "ts") end def test_number_to_human_with_empty_i18n_store - assert_equal "2 Thousand", number_to_human(2000, :locale => 'empty') - assert_equal "1.23 Billion", number_to_human(1234567890, :locale => 'empty') + assert_equal "2 Thousand", number_to_human(2000, locale: "empty") + assert_equal "1.23 Billion", number_to_human(1234567890, locale: "empty") end def test_number_to_human_with_custom_translation_scope #Significant was set to true with precision 2, with custom translated units - assert_equal "4.3 cm", number_to_human(0.0432, :locale => 'ts', :units => :custom_units_for_number_to_human) + assert_equal "4.3 cm", number_to_human(0.0432, locale: "ts", units: :custom_units_for_number_to_human) end end end diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb index 6696111476..1a59210018 100644 --- a/activesupport/test/number_helper_test.rb +++ b/activesupport/test/number_helper_test.rb @@ -1,11 +1,10 @@ -require 'abstract_unit' -require 'active_support/number_helper' -require 'active_support/core_ext/string/output_safety' +require "abstract_unit" +require "active_support/number_helper" +require "active_support/core_ext/string/output_safety" module ActiveSupport module NumberHelper class NumberHelperTest < ActiveSupport::TestCase - class TestClassWithInstanceNumberHelpers include ActiveSupport::NumberHelper end @@ -46,17 +45,19 @@ module ActiveSupport [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| assert_equal("555-1234", number_helper.number_to_phone(5551234)) assert_equal("800-555-1212", number_helper.number_to_phone(8005551212)) - assert_equal("(800) 555-1212", number_helper.number_to_phone(8005551212, {:area_code => true})) - assert_equal("", number_helper.number_to_phone("", {:area_code => true})) - assert_equal("800 555 1212", number_helper.number_to_phone(8005551212, {:delimiter => " "})) - assert_equal("(800) 555-1212 x 123", number_helper.number_to_phone(8005551212, {:area_code => true, :extension => 123})) - assert_equal("800-555-1212", number_helper.number_to_phone(8005551212, :extension => " ")) - assert_equal("555.1212", number_helper.number_to_phone(5551212, :delimiter => '.')) + assert_equal("(800) 555-1212", number_helper.number_to_phone(8005551212, area_code: true)) + assert_equal("", number_helper.number_to_phone("", area_code: true)) + assert_equal("800 555 1212", number_helper.number_to_phone(8005551212, delimiter: " ")) + assert_equal("(800) 555-1212 x 123", number_helper.number_to_phone(8005551212, area_code: true, extension: 123)) + assert_equal("800-555-1212", number_helper.number_to_phone(8005551212, extension: " ")) + assert_equal("555.1212", number_helper.number_to_phone(5551212, delimiter: ".")) assert_equal("800-555-1212", number_helper.number_to_phone("8005551212")) - assert_equal("+1-800-555-1212", number_helper.number_to_phone(8005551212, :country_code => 1)) - assert_equal("+18005551212", number_helper.number_to_phone(8005551212, :country_code => 1, :delimiter => '')) + assert_equal("+1-800-555-1212", number_helper.number_to_phone(8005551212, country_code: 1)) + assert_equal("+18005551212", number_helper.number_to_phone(8005551212, country_code: 1, delimiter: "")) assert_equal("22-555-1212", number_helper.number_to_phone(225551212)) - assert_equal("+45-22-555-1212", number_helper.number_to_phone(225551212, :country_code => 45)) + assert_equal("+45-22-555-1212", number_helper.number_to_phone(225551212, country_code: 45)) + assert_equal("(755) 6123-4567", number_helper.number_to_phone(75561234567, pattern: /(\d{3,4})(\d{4})(\d{4})/, area_code: true)) + assert_equal("133-1234-5678", number_helper.number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})/)) end end @@ -65,28 +66,28 @@ module ActiveSupport assert_equal("$1,234,567,890.50", number_helper.number_to_currency(1234567890.50)) assert_equal("$1,234,567,890.51", number_helper.number_to_currency(1234567890.506)) assert_equal("-$1,234,567,890.50", number_helper.number_to_currency(-1234567890.50)) - assert_equal("-$ 1,234,567,890.50", number_helper.number_to_currency(-1234567890.50, {:format => "%u %n"})) - assert_equal("($1,234,567,890.50)", number_helper.number_to_currency(-1234567890.50, {:negative_format => "(%u%n)"})) - assert_equal("$1,234,567,892", number_helper.number_to_currency(1234567891.50, {:precision => 0})) - assert_equal("$1,234,567,890.5", number_helper.number_to_currency(1234567890.50, {:precision => 1})) - assert_equal("£1234567890,50", number_helper.number_to_currency(1234567890.50, {:unit => "£", :separator => ",", :delimiter => ""})) + assert_equal("-$ 1,234,567,890.50", number_helper.number_to_currency(-1234567890.50, format: "%u %n")) + assert_equal("($1,234,567,890.50)", number_helper.number_to_currency(-1234567890.50, negative_format: "(%u%n)")) + assert_equal("$1,234,567,892", number_helper.number_to_currency(1234567891.50, precision: 0)) + assert_equal("$1,234,567,890.5", number_helper.number_to_currency(1234567890.50, precision: 1)) + assert_equal("£1234567890,50", number_helper.number_to_currency(1234567890.50, unit: "£", separator: ",", delimiter: "")) assert_equal("$1,234,567,890.50", number_helper.number_to_currency("1234567890.50")) - assert_equal("1,234,567,890.50 Kč", number_helper.number_to_currency("1234567890.50", {:unit => "Kč", :format => "%n %u"})) - assert_equal("1,234,567,890.50 - Kč", number_helper.number_to_currency("-1234567890.50", {:unit => "Kč", :format => "%n %u", :negative_format => "%n - %u"})) - assert_equal("0.00", number_helper.number_to_currency(+0.0, {:unit => "", :negative_format => "(%n)"})) + assert_equal("1,234,567,890.50 Kč", number_helper.number_to_currency("1234567890.50", unit: "Kč", format: "%n %u")) + assert_equal("1,234,567,890.50 - Kč", number_helper.number_to_currency("-1234567890.50", unit: "Kč", format: "%n %u", negative_format: "%n - %u")) + assert_equal("0.00", number_helper.number_to_currency(+0.0, unit: "", negative_format: "(%n)")) end end def test_number_to_percentage [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| assert_equal("100.000%", number_helper.number_to_percentage(100)) - assert_equal("100%", number_helper.number_to_percentage(100, {:precision => 0})) - assert_equal("302.06%", number_helper.number_to_percentage(302.0574, {:precision => 2})) + assert_equal("100%", number_helper.number_to_percentage(100, precision: 0)) + assert_equal("302.06%", number_helper.number_to_percentage(302.0574, precision: 2)) assert_equal("100.000%", number_helper.number_to_percentage("100")) assert_equal("1000.000%", number_helper.number_to_percentage("1000")) - assert_equal("123.4%", number_helper.number_to_percentage(123.400, :precision => 3, :strip_insignificant_zeros => true)) - assert_equal("1.000,000%", number_helper.number_to_percentage(1000, :delimiter => '.', :separator => ',')) - assert_equal("1000.000 %", number_helper.number_to_percentage(1000, :format => "%n %")) + assert_equal("123.4%", number_helper.number_to_percentage(123.400, precision: 3, strip_insignificant_zeros: true)) + assert_equal("1.000,000%", number_helper.number_to_percentage(1000, delimiter: ".", separator: ",")) + assert_equal("1000.000 %", number_helper.number_to_percentage(1000, format: "%n %")) assert_equal("98a%", number_helper.number_to_percentage("98a")) assert_equal("NaN%", number_helper.number_to_percentage(Float::NAN)) assert_equal("Inf%", number_helper.number_to_percentage(Float::INFINITY)) @@ -120,9 +121,9 @@ module ActiveSupport def test_to_delimited_with_options_hash [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal '12 345 678', number_helper.number_to_delimited(12345678, :delimiter => ' ') - assert_equal '12,345,678-05', number_helper.number_to_delimited(12345678.05, :separator => '-') - assert_equal '12.345.678,05', number_helper.number_to_delimited(12345678.05, :separator => ',', :delimiter => '.') + assert_equal "12 345 678", number_helper.number_to_delimited(12345678, delimiter: " ") + assert_equal "12,345,678-05", number_helper.number_to_delimited(12345678.05, separator: "-") + assert_equal "12.345.678,05", number_helper.number_to_delimited(12345678.05, separator: ",", delimiter: ".") end end @@ -130,77 +131,77 @@ module ActiveSupport [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| assert_equal("-111.235", number_helper.number_to_rounded(-111.2346)) assert_equal("111.235", number_helper.number_to_rounded(111.2346)) - assert_equal("31.83", number_helper.number_to_rounded(31.825, :precision => 2)) - assert_equal("111.23", number_helper.number_to_rounded(111.2346, :precision => 2)) - assert_equal("111.00", number_helper.number_to_rounded(111, :precision => 2)) + assert_equal("31.83", number_helper.number_to_rounded(31.825, precision: 2)) + assert_equal("111.23", number_helper.number_to_rounded(111.2346, precision: 2)) + assert_equal("111.00", number_helper.number_to_rounded(111, precision: 2)) assert_equal("111.235", number_helper.number_to_rounded("111.2346")) - assert_equal("31.83", number_helper.number_to_rounded("31.825", :precision => 2)) - assert_equal("3268", number_helper.number_to_rounded((32.6751 * 100.00), :precision => 0)) - assert_equal("112", number_helper.number_to_rounded(111.50, :precision => 0)) - assert_equal("1234567892", number_helper.number_to_rounded(1234567891.50, :precision => 0)) - assert_equal("0", number_helper.number_to_rounded(0, :precision => 0)) - assert_equal("0.00100", number_helper.number_to_rounded(0.001, :precision => 5)) - assert_equal("0.001", number_helper.number_to_rounded(0.00111, :precision => 3)) - assert_equal("10.00", number_helper.number_to_rounded(9.995, :precision => 2)) - assert_equal("11.00", number_helper.number_to_rounded(10.995, :precision => 2)) - assert_equal("0.00", number_helper.number_to_rounded(-0.001, :precision => 2)) - - assert_equal("111.23460000000000000000", number_helper.number_to_rounded(111.2346, :precision => 20)) - assert_equal("111.23460000000000000000", number_helper.number_to_rounded(Rational(1112346, 10000), :precision => 20)) - assert_equal("111.23460000000000000000", number_helper.number_to_rounded('111.2346', :precision => 20)) - assert_equal("111.23460000000000000000", number_helper.number_to_rounded(BigDecimal(111.2346, Float::DIG), :precision => 20)) - assert_equal("111.2346" + "0"*96, number_helper.number_to_rounded('111.2346', :precision => 100)) - assert_equal("111.2346", number_helper.number_to_rounded(Rational(1112346, 10000), :precision => 4)) - assert_equal('0.00', number_helper.number_to_rounded(Rational(0, 1), :precision => 2)) + assert_equal("31.83", number_helper.number_to_rounded("31.825", precision: 2)) + assert_equal("3268", number_helper.number_to_rounded((32.6751 * 100.00), precision: 0)) + assert_equal("112", number_helper.number_to_rounded(111.50, precision: 0)) + assert_equal("1234567892", number_helper.number_to_rounded(1234567891.50, precision: 0)) + assert_equal("0", number_helper.number_to_rounded(0, precision: 0)) + assert_equal("0.00100", number_helper.number_to_rounded(0.001, precision: 5)) + assert_equal("0.001", number_helper.number_to_rounded(0.00111, precision: 3)) + assert_equal("10.00", number_helper.number_to_rounded(9.995, precision: 2)) + assert_equal("11.00", number_helper.number_to_rounded(10.995, precision: 2)) + assert_equal("0.00", number_helper.number_to_rounded(-0.001, precision: 2)) + + assert_equal("111.23460000000000000000", number_helper.number_to_rounded(111.2346, precision: 20)) + assert_equal("111.23460000000000000000", number_helper.number_to_rounded(Rational(1112346, 10000), precision: 20)) + assert_equal("111.23460000000000000000", number_helper.number_to_rounded("111.2346", precision: 20)) + assert_equal("111.23460000000000000000", number_helper.number_to_rounded(BigDecimal(111.2346, Float::DIG), precision: 20)) + assert_equal("111.2346" + "0"*96, number_helper.number_to_rounded("111.2346", precision: 100)) + assert_equal("111.2346", number_helper.number_to_rounded(Rational(1112346, 10000), precision: 4)) + assert_equal("0.00", number_helper.number_to_rounded(Rational(0, 1), precision: 2)) end end def test_to_rounded_with_custom_delimiter_and_separator [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal '31,83', number_helper.number_to_rounded(31.825, :precision => 2, :separator => ',') - assert_equal '1.231,83', number_helper.number_to_rounded(1231.825, :precision => 2, :separator => ',', :delimiter => '.') + assert_equal "31,83", number_helper.number_to_rounded(31.825, precision: 2, separator: ",") + assert_equal "1.231,83", number_helper.number_to_rounded(1231.825, precision: 2, separator: ",", delimiter: ".") end end def test_to_rounded_with_significant_digits [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal "124000", number_helper.number_to_rounded(123987, :precision => 3, :significant => true) - assert_equal "120000000", number_helper.number_to_rounded(123987876, :precision => 2, :significant => true ) - assert_equal "40000", number_helper.number_to_rounded("43523", :precision => 1, :significant => true ) - assert_equal "9775", number_helper.number_to_rounded(9775, :precision => 4, :significant => true ) - assert_equal "5.4", number_helper.number_to_rounded(5.3923, :precision => 2, :significant => true ) - assert_equal "5", number_helper.number_to_rounded(5.3923, :precision => 1, :significant => true ) - assert_equal "1", number_helper.number_to_rounded(1.232, :precision => 1, :significant => true ) - assert_equal "7", number_helper.number_to_rounded(7, :precision => 1, :significant => true ) - assert_equal "1", number_helper.number_to_rounded(1, :precision => 1, :significant => true ) - assert_equal "53", number_helper.number_to_rounded(52.7923, :precision => 2, :significant => true ) - assert_equal "9775.00", number_helper.number_to_rounded(9775, :precision => 6, :significant => true ) - assert_equal "5.392900", number_helper.number_to_rounded(5.3929, :precision => 7, :significant => true ) - assert_equal "0.0", number_helper.number_to_rounded(0, :precision => 2, :significant => true ) - assert_equal "0", number_helper.number_to_rounded(0, :precision => 1, :significant => true ) - assert_equal "0.0001", number_helper.number_to_rounded(0.0001, :precision => 1, :significant => true ) - assert_equal "0.000100", number_helper.number_to_rounded(0.0001, :precision => 3, :significant => true ) - assert_equal "0.0001", number_helper.number_to_rounded(0.0001111, :precision => 1, :significant => true ) - assert_equal "10.0", number_helper.number_to_rounded(9.995, :precision => 3, :significant => true) - assert_equal "9.99", number_helper.number_to_rounded(9.994, :precision => 3, :significant => true) - assert_equal "11.0", number_helper.number_to_rounded(10.995, :precision => 3, :significant => true) - - assert_equal "9775.0000000000000000", number_helper.number_to_rounded(9775, :precision => 20, :significant => true ) - assert_equal "9775.0000000000000000", number_helper.number_to_rounded(9775.0, :precision => 20, :significant => true ) - assert_equal "9775.0000000000000000", number_helper.number_to_rounded(Rational(9775, 1), :precision => 20, :significant => true ) - assert_equal "97.750000000000000000", number_helper.number_to_rounded(Rational(9775, 100), :precision => 20, :significant => true ) - assert_equal "9775.0000000000000000", number_helper.number_to_rounded(BigDecimal(9775), :precision => 20, :significant => true ) - assert_equal "9775.0000000000000000", number_helper.number_to_rounded("9775", :precision => 20, :significant => true ) - assert_equal "9775." + "0"*96, number_helper.number_to_rounded("9775", :precision => 100, :significant => true ) - assert_equal("97.7", number_helper.number_to_rounded(Rational(9772, 100), :precision => 3, :significant => true)) + assert_equal "124000", number_helper.number_to_rounded(123987, precision: 3, significant: true) + assert_equal "120000000", number_helper.number_to_rounded(123987876, precision: 2, significant: true ) + assert_equal "40000", number_helper.number_to_rounded("43523", precision: 1, significant: true ) + assert_equal "9775", number_helper.number_to_rounded(9775, precision: 4, significant: true ) + assert_equal "5.4", number_helper.number_to_rounded(5.3923, precision: 2, significant: true ) + assert_equal "5", number_helper.number_to_rounded(5.3923, precision: 1, significant: true ) + assert_equal "1", number_helper.number_to_rounded(1.232, precision: 1, significant: true ) + assert_equal "7", number_helper.number_to_rounded(7, precision: 1, significant: true ) + assert_equal "1", number_helper.number_to_rounded(1, precision: 1, significant: true ) + assert_equal "53", number_helper.number_to_rounded(52.7923, precision: 2, significant: true ) + assert_equal "9775.00", number_helper.number_to_rounded(9775, precision: 6, significant: true ) + assert_equal "5.392900", number_helper.number_to_rounded(5.3929, precision: 7, significant: true ) + assert_equal "0.0", number_helper.number_to_rounded(0, precision: 2, significant: true ) + assert_equal "0", number_helper.number_to_rounded(0, precision: 1, significant: true ) + assert_equal "0.0001", number_helper.number_to_rounded(0.0001, precision: 1, significant: true ) + assert_equal "0.000100", number_helper.number_to_rounded(0.0001, precision: 3, significant: true ) + assert_equal "0.0001", number_helper.number_to_rounded(0.0001111, precision: 1, significant: true ) + assert_equal "10.0", number_helper.number_to_rounded(9.995, precision: 3, significant: true) + assert_equal "9.99", number_helper.number_to_rounded(9.994, precision: 3, significant: true) + assert_equal "11.0", number_helper.number_to_rounded(10.995, precision: 3, significant: true) + + assert_equal "9775.0000000000000000", number_helper.number_to_rounded(9775, precision: 20, significant: true ) + assert_equal "9775.0000000000000000", number_helper.number_to_rounded(9775.0, precision: 20, significant: true ) + assert_equal "9775.0000000000000000", number_helper.number_to_rounded(Rational(9775, 1), precision: 20, significant: true ) + assert_equal "97.750000000000000000", number_helper.number_to_rounded(Rational(9775, 100), precision: 20, significant: true ) + assert_equal "9775.0000000000000000", number_helper.number_to_rounded(BigDecimal(9775), precision: 20, significant: true ) + assert_equal "9775.0000000000000000", number_helper.number_to_rounded("9775", precision: 20, significant: true ) + assert_equal "9775." + "0"*96, number_helper.number_to_rounded("9775", precision: 100, significant: true ) + assert_equal("97.7", number_helper.number_to_rounded(Rational(9772, 100), precision: 3, significant: true)) end end def test_to_rounded_with_strip_insignificant_zeros [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal "9775.43", number_helper.number_to_rounded(9775.43, :precision => 4, :strip_insignificant_zeros => true ) - assert_equal "9775.2", number_helper.number_to_rounded(9775.2, :precision => 6, :significant => true, :strip_insignificant_zeros => true ) - assert_equal "0", number_helper.number_to_rounded(0, :precision => 6, :significant => true, :strip_insignificant_zeros => true ) + assert_equal "9775.43", number_helper.number_to_rounded(9775.43, precision: 4, strip_insignificant_zeros: true ) + assert_equal "9775.2", number_helper.number_to_rounded(9775.2, precision: 6, significant: true, strip_insignificant_zeros: true ) + assert_equal "0", number_helper.number_to_rounded(0, precision: 6, significant: true, strip_insignificant_zeros: true ) end end @@ -208,156 +209,156 @@ module ActiveSupport [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| # Zero precision with significant is a mistake (would always return zero), # so we treat it as if significant was false (increases backwards compatibility for number_to_human_size) - assert_equal "124", number_helper.number_to_rounded(123.987, :precision => 0, :significant => true) - assert_equal "12", number_helper.number_to_rounded(12, :precision => 0, :significant => true ) - assert_equal "12", number_helper.number_to_rounded("12.3", :precision => 0, :significant => true ) + assert_equal "124", number_helper.number_to_rounded(123.987, precision: 0, significant: true) + assert_equal "12", number_helper.number_to_rounded(12, precision: 0, significant: true ) + assert_equal "12", number_helper.number_to_rounded("12.3", precision: 0, significant: true ) end end def test_number_number_to_human_size [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal '0 Bytes', number_helper.number_to_human_size(0) - assert_equal '1 Byte', number_helper.number_to_human_size(1) - assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265) - assert_equal '123 Bytes', number_helper.number_to_human_size(123.0) - assert_equal '123 Bytes', number_helper.number_to_human_size(123) - assert_equal '1.21 KB', number_helper.number_to_human_size(1234) - assert_equal '12.1 KB', number_helper.number_to_human_size(12345) - assert_equal '1.18 MB', number_helper.number_to_human_size(1234567) - assert_equal '1.15 GB', number_helper.number_to_human_size(1234567890) - assert_equal '1.12 TB', number_helper.number_to_human_size(1234567890123) - assert_equal '1.1 PB', number_helper.number_to_human_size(1234567890123456) - assert_equal '1.07 EB', number_helper.number_to_human_size(1234567890123456789) - assert_equal '1030 EB', number_helper.number_to_human_size(exabytes(1026)) - assert_equal '444 KB', number_helper.number_to_human_size(kilobytes(444)) - assert_equal '1020 MB', number_helper.number_to_human_size(megabytes(1023)) - assert_equal '3 TB', number_helper.number_to_human_size(terabytes(3)) - assert_equal '1.2 MB', number_helper.number_to_human_size(1234567, :precision => 2) - assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265, :precision => 4) - assert_equal '123 Bytes', number_helper.number_to_human_size('123') - assert_equal '1 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 2) - assert_equal '1.01 KB', number_helper.number_to_human_size(kilobytes(1.0100), :precision => 4) - assert_equal '10 KB', number_helper.number_to_human_size(kilobytes(10.000), :precision => 4) - assert_equal '1 Byte', number_helper.number_to_human_size(1.1) - assert_equal '10 Bytes', number_helper.number_to_human_size(10) + assert_equal "0 Bytes", number_helper.number_to_human_size(0) + assert_equal "1 Byte", number_helper.number_to_human_size(1) + assert_equal "3 Bytes", number_helper.number_to_human_size(3.14159265) + assert_equal "123 Bytes", number_helper.number_to_human_size(123.0) + assert_equal "123 Bytes", number_helper.number_to_human_size(123) + assert_equal "1.21 KB", number_helper.number_to_human_size(1234) + assert_equal "12.1 KB", number_helper.number_to_human_size(12345) + assert_equal "1.18 MB", number_helper.number_to_human_size(1234567) + assert_equal "1.15 GB", number_helper.number_to_human_size(1234567890) + assert_equal "1.12 TB", number_helper.number_to_human_size(1234567890123) + assert_equal "1.1 PB", number_helper.number_to_human_size(1234567890123456) + assert_equal "1.07 EB", number_helper.number_to_human_size(1234567890123456789) + assert_equal "1030 EB", number_helper.number_to_human_size(exabytes(1026)) + assert_equal "444 KB", number_helper.number_to_human_size(kilobytes(444)) + assert_equal "1020 MB", number_helper.number_to_human_size(megabytes(1023)) + assert_equal "3 TB", number_helper.number_to_human_size(terabytes(3)) + assert_equal "1.2 MB", number_helper.number_to_human_size(1234567, precision: 2) + assert_equal "3 Bytes", number_helper.number_to_human_size(3.14159265, precision: 4) + assert_equal "123 Bytes", number_helper.number_to_human_size("123") + assert_equal "1 KB", number_helper.number_to_human_size(kilobytes(1.0123), precision: 2) + assert_equal "1.01 KB", number_helper.number_to_human_size(kilobytes(1.0100), precision: 4) + assert_equal "10 KB", number_helper.number_to_human_size(kilobytes(10.000), precision: 4) + assert_equal "1 Byte", number_helper.number_to_human_size(1.1) + assert_equal "10 Bytes", number_helper.number_to_human_size(10) end end def test_number_to_human_size_with_si_prefix assert_deprecated do [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265, :prefix => :si) - assert_equal '123 Bytes', number_helper.number_to_human_size(123.0, :prefix => :si) - assert_equal '123 Bytes', number_helper.number_to_human_size(123, :prefix => :si) - assert_equal '1.23 KB', number_helper.number_to_human_size(1234, :prefix => :si) - assert_equal '12.3 KB', number_helper.number_to_human_size(12345, :prefix => :si) - assert_equal '1.23 MB', number_helper.number_to_human_size(1234567, :prefix => :si) - assert_equal '1.23 GB', number_helper.number_to_human_size(1234567890, :prefix => :si) - assert_equal '1.23 TB', number_helper.number_to_human_size(1234567890123, :prefix => :si) - assert_equal '1.23 PB', number_helper.number_to_human_size(1234567890123456, :prefix => :si) - assert_equal '1.23 EB', number_helper.number_to_human_size(1234567890123456789, :prefix => :si) + assert_equal "3 Bytes", number_helper.number_to_human_size(3.14159265, prefix: :si) + assert_equal "123 Bytes", number_helper.number_to_human_size(123.0, prefix: :si) + assert_equal "123 Bytes", number_helper.number_to_human_size(123, prefix: :si) + assert_equal "1.23 KB", number_helper.number_to_human_size(1234, prefix: :si) + assert_equal "12.3 KB", number_helper.number_to_human_size(12345, prefix: :si) + assert_equal "1.23 MB", number_helper.number_to_human_size(1234567, prefix: :si) + assert_equal "1.23 GB", number_helper.number_to_human_size(1234567890, prefix: :si) + assert_equal "1.23 TB", number_helper.number_to_human_size(1234567890123, prefix: :si) + assert_equal "1.23 PB", number_helper.number_to_human_size(1234567890123456, prefix: :si) + assert_equal "1.23 EB", number_helper.number_to_human_size(1234567890123456789, prefix: :si) end end end def test_number_to_human_size_with_options_hash [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal '1.2 MB', number_helper.number_to_human_size(1234567, :precision => 2) - assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265, :precision => 4) - assert_equal '1 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 2) - assert_equal '1.01 KB', number_helper.number_to_human_size(kilobytes(1.0100), :precision => 4) - assert_equal '10 KB', number_helper.number_to_human_size(kilobytes(10.000), :precision => 4) - assert_equal '1 TB', number_helper.number_to_human_size(1234567890123, :precision => 1) - assert_equal '500 MB', number_helper.number_to_human_size(524288000, :precision=>3) - assert_equal '10 MB', number_helper.number_to_human_size(9961472, :precision=>0) - assert_equal '40 KB', number_helper.number_to_human_size(41010, :precision => 1) - assert_equal '40 KB', number_helper.number_to_human_size(41100, :precision => 2) - assert_equal '1.0 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 2, :strip_insignificant_zeros => false) - assert_equal '1.012 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 3, :significant => false) - assert_equal '1 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 0, :significant => true) #ignores significant it precision is 0 + assert_equal "1.2 MB", number_helper.number_to_human_size(1234567, precision: 2) + assert_equal "3 Bytes", number_helper.number_to_human_size(3.14159265, precision: 4) + assert_equal "1 KB", number_helper.number_to_human_size(kilobytes(1.0123), precision: 2) + assert_equal "1.01 KB", number_helper.number_to_human_size(kilobytes(1.0100), precision: 4) + assert_equal "10 KB", number_helper.number_to_human_size(kilobytes(10.000), precision: 4) + assert_equal "1 TB", number_helper.number_to_human_size(1234567890123, precision: 1) + assert_equal "500 MB", number_helper.number_to_human_size(524288000, precision: 3) + assert_equal "10 MB", number_helper.number_to_human_size(9961472, precision: 0) + assert_equal "40 KB", number_helper.number_to_human_size(41010, precision: 1) + assert_equal "40 KB", number_helper.number_to_human_size(41100, precision: 2) + assert_equal "1.0 KB", number_helper.number_to_human_size(kilobytes(1.0123), precision: 2, strip_insignificant_zeros: false) + assert_equal "1.012 KB", number_helper.number_to_human_size(kilobytes(1.0123), precision: 3, significant: false) + assert_equal "1 KB", number_helper.number_to_human_size(kilobytes(1.0123), precision: 0, significant: true) #ignores significant it precision is 0 end end def test_number_to_human_size_with_custom_delimiter_and_separator [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal '1,01 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 3, :separator => ',') - assert_equal '1,01 KB', number_helper.number_to_human_size(kilobytes(1.0100), :precision => 4, :separator => ',') - assert_equal '1.000,1 TB', number_helper.number_to_human_size(terabytes(1000.1), :precision => 5, :delimiter => '.', :separator => ',') + assert_equal "1,01 KB", number_helper.number_to_human_size(kilobytes(1.0123), precision: 3, separator: ",") + assert_equal "1,01 KB", number_helper.number_to_human_size(kilobytes(1.0100), precision: 4, separator: ",") + assert_equal "1.000,1 TB", number_helper.number_to_human_size(terabytes(1000.1), precision: 5, delimiter: ".", separator: ",") end end def test_number_to_human [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal '-123', number_helper.number_to_human(-123) - assert_equal '-0.5', number_helper.number_to_human(-0.5) - assert_equal '0', number_helper.number_to_human(0) - assert_equal '0.5', number_helper.number_to_human(0.5) - assert_equal '123', number_helper.number_to_human(123) - assert_equal '1.23 Thousand', number_helper.number_to_human(1234) - assert_equal '12.3 Thousand', number_helper.number_to_human(12345) - assert_equal '1.23 Million', number_helper.number_to_human(1234567) - assert_equal '1.23 Billion', number_helper.number_to_human(1234567890) - assert_equal '1.23 Trillion', number_helper.number_to_human(1234567890123) - assert_equal '1.23 Quadrillion', number_helper.number_to_human(1234567890123456) - assert_equal '1230 Quadrillion', number_helper.number_to_human(1234567890123456789) - assert_equal '490 Thousand', number_helper.number_to_human(489939, :precision => 2) - assert_equal '489.9 Thousand', number_helper.number_to_human(489939, :precision => 4) - assert_equal '489 Thousand', number_helper.number_to_human(489000, :precision => 4) - assert_equal '489.0 Thousand', number_helper.number_to_human(489000, :precision => 4, :strip_insignificant_zeros => false) - assert_equal '1.2346 Million', number_helper.number_to_human(1234567, :precision => 4, :significant => false) - assert_equal '1,2 Million', number_helper.number_to_human(1234567, :precision => 1, :significant => false, :separator => ',') - assert_equal '1 Million', number_helper.number_to_human(1234567, :precision => 0, :significant => true, :separator => ',') #significant forced to false - assert_equal '1 Million', number_helper.number_to_human(999999) - assert_equal '1 Billion', number_helper.number_to_human(999999999) + assert_equal "-123", number_helper.number_to_human(-123) + assert_equal "-0.5", number_helper.number_to_human(-0.5) + assert_equal "0", number_helper.number_to_human(0) + assert_equal "0.5", number_helper.number_to_human(0.5) + assert_equal "123", number_helper.number_to_human(123) + assert_equal "1.23 Thousand", number_helper.number_to_human(1234) + assert_equal "12.3 Thousand", number_helper.number_to_human(12345) + assert_equal "1.23 Million", number_helper.number_to_human(1234567) + assert_equal "1.23 Billion", number_helper.number_to_human(1234567890) + assert_equal "1.23 Trillion", number_helper.number_to_human(1234567890123) + assert_equal "1.23 Quadrillion", number_helper.number_to_human(1234567890123456) + assert_equal "1230 Quadrillion", number_helper.number_to_human(1234567890123456789) + assert_equal "490 Thousand", number_helper.number_to_human(489939, precision: 2) + assert_equal "489.9 Thousand", number_helper.number_to_human(489939, precision: 4) + assert_equal "489 Thousand", number_helper.number_to_human(489000, precision: 4) + assert_equal "489.0 Thousand", number_helper.number_to_human(489000, precision: 4, strip_insignificant_zeros: false) + assert_equal "1.2346 Million", number_helper.number_to_human(1234567, precision: 4, significant: false) + assert_equal "1,2 Million", number_helper.number_to_human(1234567, precision: 1, significant: false, separator: ",") + assert_equal "1 Million", number_helper.number_to_human(1234567, precision: 0, significant: true, separator: ",") #significant forced to false + assert_equal "1 Million", number_helper.number_to_human(999999) + assert_equal "1 Billion", number_helper.number_to_human(999999999) end end def test_number_to_human_with_custom_units [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| #Only integers - volume = {:unit => "ml", :thousand => "lt", :million => "m3"} - assert_equal '123 lt', number_helper.number_to_human(123456, :units => volume) - assert_equal '12 ml', number_helper.number_to_human(12, :units => volume) - assert_equal '1.23 m3', number_helper.number_to_human(1234567, :units => volume) + volume = { unit: "ml", thousand: "lt", million: "m3" } + assert_equal "123 lt", number_helper.number_to_human(123456, units: volume) + assert_equal "12 ml", number_helper.number_to_human(12, units: volume) + assert_equal "1.23 m3", number_helper.number_to_human(1234567, units: volume) #Including fractionals - distance = {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"} - assert_equal '1.23 mm', number_helper.number_to_human(0.00123, :units => distance) - assert_equal '1.23 cm', number_helper.number_to_human(0.0123, :units => distance) - assert_equal '1.23 dm', number_helper.number_to_human(0.123, :units => distance) - assert_equal '1.23 m', number_helper.number_to_human(1.23, :units => distance) - assert_equal '1.23 dam', number_helper.number_to_human(12.3, :units => distance) - assert_equal '1.23 hm', number_helper.number_to_human(123, :units => distance) - assert_equal '1.23 km', number_helper.number_to_human(1230, :units => distance) - assert_equal '1.23 km', number_helper.number_to_human(1230, :units => distance) - assert_equal '1.23 km', number_helper.number_to_human(1230, :units => distance) - assert_equal '12.3 km', number_helper.number_to_human(12300, :units => distance) + distance = { mili: "mm", centi: "cm", deci: "dm", unit: "m", ten: "dam", hundred: "hm", thousand: "km" } + assert_equal "1.23 mm", number_helper.number_to_human(0.00123, units: distance) + assert_equal "1.23 cm", number_helper.number_to_human(0.0123, units: distance) + assert_equal "1.23 dm", number_helper.number_to_human(0.123, units: distance) + assert_equal "1.23 m", number_helper.number_to_human(1.23, units: distance) + assert_equal "1.23 dam", number_helper.number_to_human(12.3, units: distance) + assert_equal "1.23 hm", number_helper.number_to_human(123, units: distance) + assert_equal "1.23 km", number_helper.number_to_human(1230, units: distance) + assert_equal "1.23 km", number_helper.number_to_human(1230, units: distance) + assert_equal "1.23 km", number_helper.number_to_human(1230, units: distance) + assert_equal "12.3 km", number_helper.number_to_human(12300, units: distance) #The quantifiers don't need to be a continuous sequence - gangster = {:hundred => "hundred bucks", :million => "thousand quids"} - assert_equal '1 hundred bucks', number_helper.number_to_human(100, :units => gangster) - assert_equal '25 hundred bucks', number_helper.number_to_human(2500, :units => gangster) - assert_equal '25 thousand quids', number_helper.number_to_human(25000000, :units => gangster) - assert_equal '12300 thousand quids', number_helper.number_to_human(12345000000, :units => gangster) + gangster = { hundred: "hundred bucks", million: "thousand quids" } + assert_equal "1 hundred bucks", number_helper.number_to_human(100, units: gangster) + assert_equal "25 hundred bucks", number_helper.number_to_human(2500, units: gangster) + assert_equal "25 thousand quids", number_helper.number_to_human(25000000, units: gangster) + assert_equal "12300 thousand quids", number_helper.number_to_human(12345000000, units: gangster) #Spaces are stripped from the resulting string - assert_equal '4', number_helper.number_to_human(4, :units => {:unit => "", :ten => 'tens '}) - assert_equal '4.5 tens', number_helper.number_to_human(45, :units => {:unit => "", :ten => ' tens '}) + assert_equal "4", number_helper.number_to_human(4, units: { unit: "", ten: "tens " }) + assert_equal "4.5 tens", number_helper.number_to_human(45, units: { unit: "", ten: " tens " }) end end def test_number_to_human_with_custom_units_that_are_missing_the_needed_key [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal '123', number_helper.number_to_human(123, units: { thousand: 'k'}) - assert_equal '123', number_helper.number_to_human(123, units: {}) + assert_equal "123", number_helper.number_to_human(123, units: { thousand: "k" }) + assert_equal "123", number_helper.number_to_human(123, units: {}) end end def test_number_to_human_with_custom_format [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal '123 times Thousand', number_helper.number_to_human(123456, :format => "%n times %u") - volume = {:unit => "ml", :thousand => "lt", :million => "m3"} - assert_equal '123.lt', number_helper.number_to_human(123456, :units => volume, :format => "%n.%u") + assert_equal "123 times Thousand", number_helper.number_to_human(123456, format: "%n times %u") + volume = { unit: "ml", thousand: "lt", million: "m3" } + assert_equal "123.lt", number_helper.number_to_human(123456, units: volume, format: "%n.%u") end end @@ -375,34 +376,34 @@ module ActiveSupport def test_number_helpers_do_not_mutate_options_hash [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - options = { 'raise' => true } + options = { "raise" => true } number_helper.number_to_phone(1, options) - assert_equal({ 'raise' => true }, options) + assert_equal({ "raise" => true }, options) number_helper.number_to_currency(1, options) - assert_equal({ 'raise' => true }, options) + assert_equal({ "raise" => true }, options) number_helper.number_to_percentage(1, options) - assert_equal({ 'raise' => true }, options) + assert_equal({ "raise" => true }, options) number_helper.number_to_delimited(1, options) - assert_equal({ 'raise' => true }, options) + assert_equal({ "raise" => true }, options) number_helper.number_to_rounded(1, options) - assert_equal({ 'raise' => true }, options) + assert_equal({ "raise" => true }, options) number_helper.number_to_human_size(1, options) - assert_equal({ 'raise' => true }, options) + assert_equal({ "raise" => true }, options) number_helper.number_to_human(1, options) - assert_equal({ 'raise' => true }, options) + assert_equal({ "raise" => true }, options) end end def test_number_helpers_should_return_non_numeric_param_unchanged [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal("+1-x x 123", number_helper.number_to_phone("x", :country_code => 1, :extension => 123)) + assert_equal("+1-x x 123", number_helper.number_to_phone("x", country_code: 1, extension: 123)) assert_equal("x", number_helper.number_to_phone("x")) assert_equal("$x.", number_helper.number_to_currency("x.")) assert_equal("$x", number_helper.number_to_currency("x")) @@ -410,11 +411,10 @@ module ActiveSupport assert_equal("x", number_helper.number_to_delimited("x")) assert_equal("x.", number_helper.number_to_rounded("x.")) assert_equal("x", number_helper.number_to_rounded("x")) - assert_equal "x", number_helper.number_to_human_size('x') - assert_equal "x", number_helper.number_to_human('x') + assert_equal "x", number_helper.number_to_human_size("x") + assert_equal "x", number_helper.number_to_human("x") end end - end end end diff --git a/activesupport/test/option_merger_test.rb b/activesupport/test/option_merger_test.rb index 4c0364e68b..c5a6d304ee 100644 --- a/activesupport/test/option_merger_test.rb +++ b/activesupport/test/option_merger_test.rb @@ -1,13 +1,13 @@ -require 'abstract_unit' -require 'active_support/core_ext/object/with_options' +require "abstract_unit" +require "active_support/core_ext/object/with_options" class OptionMergerTest < ActiveSupport::TestCase def setup - @options = {:hello => 'world'} + @options = { hello: "world" } end def test_method_with_options_merges_options_when_options_are_present - local_options = {:cool => true} + local_options = { cool: true } with_options(@options) do |o| assert_equal local_options, method_with_options(local_options) @@ -24,7 +24,7 @@ class OptionMergerTest < ActiveSupport::TestCase end def test_method_with_options_allows_to_overwrite_options - local_options = {:hello => 'moon'} + local_options = { hello: "moon" } assert_equal @options.keys, local_options.keys with_options(@options) do |o| @@ -40,34 +40,34 @@ class OptionMergerTest < ActiveSupport::TestCase end def test_nested_method_with_options_containing_hashes_merge - with_options :conditions => { :method => :get } do |outer| - outer.with_options :conditions => { :domain => "www" } do |inner| - expected = { :conditions => { :method => :get, :domain => "www" } } + with_options conditions: { method: :get } do |outer| + outer.with_options conditions: { domain: "www" } do |inner| + expected = { conditions: { method: :get, domain: "www" } } assert_equal expected, inner.method_with_options end end end def test_nested_method_with_options_containing_hashes_overwrite - with_options :conditions => { :method => :get, :domain => "www" } do |outer| - outer.with_options :conditions => { :method => :post } do |inner| - expected = { :conditions => { :method => :post, :domain => "www" } } + with_options conditions: { method: :get, domain: "www" } do |outer| + outer.with_options conditions: { method: :post } do |inner| + expected = { conditions: { method: :post, domain: "www" } } assert_equal expected, inner.method_with_options end end end def test_nested_method_with_options_containing_hashes_going_deep - with_options :html => { :class => "foo", :style => { :margin => 0, :display => "block" } } do |outer| - outer.with_options :html => { :title => "bar", :style => { :margin => "1em", :color => "#fff" } } do |inner| - expected = { :html => { :class => "foo", :title => "bar", :style => { :margin => "1em", :display => "block", :color => "#fff" } } } + with_options html: { class: "foo", style: { margin: 0, display: "block" } } do |outer| + outer.with_options html: { title: "bar", style: { margin: "1em", color: "#fff" } } do |inner| + expected = { html: { class: "foo", title: "bar", style: { margin: "1em", display: "block", color: "#fff" } } } assert_equal expected, inner.method_with_options end end end def test_nested_method_with_options_using_lambda - local_lambda = lambda { { :lambda => true } } + local_lambda = lambda { { lambda: true } } with_options(@options) do |o| assert_equal @options.merge(local_lambda.call), o.method_with_options(local_lambda).call @@ -76,7 +76,7 @@ class OptionMergerTest < ActiveSupport::TestCase # Needed when counting objects with the ObjectSpace def test_option_merger_class_method - assert_equal ActiveSupport::OptionMerger, ActiveSupport::OptionMerger.new('', '').class + assert_equal ActiveSupport::OptionMerger, ActiveSupport::OptionMerger.new("", "").class end def test_option_merger_implicit_receiver diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb index 460a61613e..86da9f193a 100644 --- a/activesupport/test/ordered_hash_test.rb +++ b/activesupport/test/ordered_hash_test.rb @@ -1,8 +1,8 @@ -require 'abstract_unit' -require 'active_support/json' -require 'active_support/core_ext/object/json' -require 'active_support/core_ext/hash/indifferent_access' -require 'active_support/core_ext/array/extract_options' +require "abstract_unit" +require "active_support/json" +require "active_support/core_ext/object/json" +require "active_support/core_ext/hash/indifferent_access" +require "active_support/core_ext/array/extract_options" class OrderedHashTest < ActiveSupport::TestCase def setup @@ -27,7 +27,7 @@ class OrderedHashTest < ActiveSupport::TestCase end def test_assignment - key, value = 'purple', '5422a8' + key, value = "purple", "5422a8" @ordered_hash[key] = value assert_equal @keys.length + 1, @ordered_hash.length @@ -37,8 +37,8 @@ class OrderedHashTest < ActiveSupport::TestCase end def test_delete - key, value = 'white', 'ffffff' - bad_key = 'black' + key, value = "white", "ffffff" + bad_key = "black" @ordered_hash[key] = value assert_equal @keys.length + 1, @ordered_hash.length @@ -60,22 +60,22 @@ class OrderedHashTest < ActiveSupport::TestCase end def test_has_key - assert_equal true, @ordered_hash.has_key?('blue') - assert_equal true, @ordered_hash.key?('blue') - assert_equal true, @ordered_hash.include?('blue') - assert_equal true, @ordered_hash.member?('blue') + assert_equal true, @ordered_hash.has_key?("blue") + assert_equal true, @ordered_hash.key?("blue") + assert_equal true, @ordered_hash.include?("blue") + assert_equal true, @ordered_hash.member?("blue") - assert_equal false, @ordered_hash.has_key?('indigo') - assert_equal false, @ordered_hash.key?('indigo') - assert_equal false, @ordered_hash.include?('indigo') - assert_equal false, @ordered_hash.member?('indigo') + assert_equal false, @ordered_hash.has_key?("indigo") + assert_equal false, @ordered_hash.key?("indigo") + assert_equal false, @ordered_hash.include?("indigo") + assert_equal false, @ordered_hash.member?("indigo") end def test_has_value - assert_equal true, @ordered_hash.has_value?('000099') - assert_equal true, @ordered_hash.value?('000099') - assert_equal false, @ordered_hash.has_value?('ABCABC') - assert_equal false, @ordered_hash.value?('ABCABC') + assert_equal true, @ordered_hash.has_value?("000099") + assert_equal true, @ordered_hash.value?("000099") + assert_equal false, @ordered_hash.has_value?("ABCABC") + assert_equal false, @ordered_hash.value?("ABCABC") end def test_each_key @@ -94,13 +94,13 @@ class OrderedHashTest < ActiveSupport::TestCase def test_each values = [] - assert_equal @ordered_hash, @ordered_hash.each {|key, value| values << value} + assert_equal @ordered_hash, @ordered_hash.each { |key, value| values << value } assert_equal @values, values assert_kind_of Enumerator, @ordered_hash.each end def test_each_with_index - @ordered_hash.each_with_index { |pair, index| assert_equal [@keys[index], @values[index]], pair} + @ordered_hash.each_with_index { |pair, index| assert_equal [@keys[index], @values[index]], pair } end def test_each_pair @@ -127,24 +127,24 @@ class OrderedHashTest < ActiveSupport::TestCase def test_delete_if copy = @ordered_hash.dup - copy.delete('pink') - assert_equal copy, @ordered_hash.delete_if { |k, _| k == 'pink' } - assert !@ordered_hash.keys.include?('pink') + copy.delete("pink") + assert_equal copy, @ordered_hash.delete_if { |k, _| k == "pink" } + assert_not_includes @ordered_hash.keys, "pink" end def test_reject! - (copy = @ordered_hash.dup).delete('pink') - @ordered_hash.reject! { |k, _| k == 'pink' } + (copy = @ordered_hash.dup).delete("pink") + @ordered_hash.reject! { |k, _| k == "pink" } assert_equal copy, @ordered_hash - assert !@ordered_hash.keys.include?('pink') + assert_not_includes @ordered_hash.keys, "pink" end def test_reject copy = @ordered_hash.dup - new_ordered_hash = @ordered_hash.reject { |k, _| k == 'pink' } + new_ordered_hash = @ordered_hash.reject { |k, _| k == "pink" } assert_equal copy, @ordered_hash - assert !new_ordered_hash.keys.include?('pink') - assert @ordered_hash.keys.include?('pink') + assert_not_includes new_ordered_hash.keys, "pink" + assert_includes @ordered_hash.keys, "pink" assert_instance_of ActiveSupport::OrderedHash, new_ordered_hash end @@ -155,18 +155,18 @@ class OrderedHashTest < ActiveSupport::TestCase def test_merge other_hash = ActiveSupport::OrderedHash.new - other_hash['purple'] = '800080' - other_hash['violet'] = 'ee82ee' + other_hash["purple"] = "800080" + other_hash["violet"] = "ee82ee" merged = @ordered_hash.merge other_hash assert_equal merged.length, @ordered_hash.length + other_hash.length - assert_equal @keys + ['purple', 'violet'], merged.keys + assert_equal @keys + ["purple", "violet"], merged.keys end def test_merge_with_block hash = ActiveSupport::OrderedHash.new hash[:a] = 0 hash[:b] = 0 - merged = hash.merge(:b => 2, :c => 7) do |key, old_value, new_value| + merged = hash.merge(b: 2, c: 7) do |key, old_value, new_value| new_value + 1 end @@ -179,7 +179,7 @@ class OrderedHashTest < ActiveSupport::TestCase hash = ActiveSupport::OrderedHash.new hash[:a] = 0 hash[:b] = 0 - hash.merge!(:a => 1, :c => 7) do |key, old_value, new_value| + hash.merge!(a: 1, c: 7) do |key, old_value, new_value| new_value + 3 end @@ -191,7 +191,7 @@ class OrderedHashTest < ActiveSupport::TestCase def test_shift pair = @ordered_hash.shift assert_equal [@keys.first, @values.first], pair - assert !@ordered_hash.keys.include?(pair.first) + assert_not_includes @ordered_hash.keys, pair.first end def test_keys @@ -201,7 +201,7 @@ class OrderedHashTest < ActiveSupport::TestCase end def test_inspect - assert @ordered_hash.inspect.include?(@hash.inspect) + assert_includes @ordered_hash.inspect, @hash.inspect end def test_json @@ -220,11 +220,11 @@ class OrderedHashTest < ActiveSupport::TestCase alternate = ActiveSupport::OrderedHash[ [ [1, 2], [3, 4], - [ 'missing value' ] + [ "missing value" ] ]] assert_kind_of ActiveSupport::OrderedHash, alternate - assert_equal [1, 3, 'missing value'], alternate.keys + assert_equal [1, 3, "missing value"], alternate.keys assert_equal [2, 4, nil ], alternate.values end @@ -235,14 +235,14 @@ class OrderedHashTest < ActiveSupport::TestCase end def test_replace_updates_keys - @other_ordered_hash = ActiveSupport::OrderedHash[:black, '000000', :white, '000000'] + @other_ordered_hash = ActiveSupport::OrderedHash[:black, "000000", :white, "000000"] original = @ordered_hash.replace(@other_ordered_hash) assert_same original, @ordered_hash assert_equal @other_ordered_hash.keys, @ordered_hash.keys end def test_nested_under_indifferent_access - flash = {:a => ActiveSupport::OrderedHash[:b, 1, :c, 2]}.with_indifferent_access + flash = { a: ActiveSupport::OrderedHash[:b, 1, :c, 2] }.with_indifferent_access assert_kind_of ActiveSupport::OrderedHash, flash[:a] end @@ -295,17 +295,17 @@ class OrderedHashTest < ActiveSupport::TestCase def test_psych_serialize_tag yaml = Psych.dump(@ordered_hash) - assert_match '!omap', yaml + assert_match "!omap", yaml end def test_has_yaml_tag @ordered_hash[:array] = %w(a b c) - assert_match '!omap', YAML.dump(@ordered_hash) + assert_match "!omap", YAML.dump(@ordered_hash) end def test_update_sets_keys @updated_ordered_hash = ActiveSupport::OrderedHash.new - @updated_ordered_hash.update(:name => "Bob") + @updated_ordered_hash.update(name: "Bob") assert_equal [:name], @updated_ordered_hash.keys end diff --git a/activesupport/test/ordered_options_test.rb b/activesupport/test/ordered_options_test.rb index 18767a3536..0417911289 100644 --- a/activesupport/test/ordered_options_test.rb +++ b/activesupport/test/ordered_options_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/ordered_options' +require "abstract_unit" +require "active_support/ordered_options" class OrderedOptionsTest < ActiveSupport::TestCase def test_usage diff --git a/activesupport/test/reloader_test.rb b/activesupport/test/reloader_test.rb new file mode 100644 index 0000000000..67d8c4b0e3 --- /dev/null +++ b/activesupport/test/reloader_test.rb @@ -0,0 +1,85 @@ +require "abstract_unit" + +class ReloaderTest < ActiveSupport::TestCase + def test_prepare_callback + prepared = false + reloader.to_prepare { prepared = true } + + assert !prepared + reloader.prepare! + assert prepared + + prepared = false + reloader.wrap do + assert prepared + prepared = false + end + assert !prepared + end + + def test_only_run_when_check_passes + r = new_reloader { true } + invoked = false + r.to_run { invoked = true } + r.wrap {} + assert invoked + + r = new_reloader { false } + invoked = false + r.to_run { invoked = true } + r.wrap {} + assert !invoked + end + + def test_full_reload_sequence + called = [] + reloader.to_prepare { called << :prepare } + reloader.to_run { called << :reloader_run } + reloader.to_complete { called << :reloader_complete } + reloader.executor.to_run { called << :executor_run } + reloader.executor.to_complete { called << :executor_complete } + + reloader.wrap {} + assert_equal [:executor_run, :reloader_run, :prepare, :reloader_complete, :executor_complete], called + + called = [] + reloader.reload! + assert_equal [:executor_run, :reloader_run, :prepare, :reloader_complete, :executor_complete, :prepare], called + + reloader.check = lambda { false } + + called = [] + reloader.wrap {} + assert_equal [:executor_run, :executor_complete], called + + called = [] + reloader.reload! + assert_equal [:executor_run, :reloader_run, :prepare, :reloader_complete, :executor_complete, :prepare], called + end + + def test_class_unload_block + called = [] + reloader.before_class_unload { called << :before_unload } + reloader.after_class_unload { called << :after_unload } + reloader.to_run do + class_unload! do + called << :unload + end + end + reloader.wrap { called << :body } + + assert_equal [:before_unload, :unload, :after_unload, :body], called + end + + private + def new_reloader(&check) + Class.new(ActiveSupport::Reloader).tap do |r| + r.check = check + r.executor = Class.new(ActiveSupport::Executor) + end + end + + def reloader + @reloader ||= new_reloader { true } + end +end diff --git a/activesupport/test/rescuable_test.rb b/activesupport/test/rescuable_test.rb index bd43ad0797..7e5c3d1a8f 100644 --- a/activesupport/test/rescuable_test.rb +++ b/activesupport/test/rescuable_test.rb @@ -1,11 +1,8 @@ -require 'abstract_unit' +require "abstract_unit" class WraithAttack < StandardError end -class NuclearExplosion < StandardError -end - class MadRonon < StandardError end @@ -19,16 +16,20 @@ module WeirdError end class Stargate + # Nest this so the 'NuclearExplosion' handler needs a lexical const_get + # to find it. + class NuclearExplosion < StandardError; end + attr_accessor :result include ActiveSupport::Rescuable - rescue_from WraithAttack, :with => :sos_first + rescue_from WraithAttack, with: :sos_first - rescue_from WraithAttack, :with => :sos + rescue_from WraithAttack, with: :sos - rescue_from 'NuclearExplosion' do - @result = 'alldead' + rescue_from "NuclearExplosion" do + @result = "alldead" end rescue_from MadRonon do |e| @@ -36,7 +37,7 @@ class Stargate end rescue_from WeirdError do - @result = 'weird' + @result = "weird" end def dispatch(method) @@ -57,6 +58,14 @@ class Stargate raise MadRonon.new("dex") end + def fall_back_to_cause + # This exception is the cause and has a handler. + ronanize + rescue + # This is the exception we'll handle that doesn't have a cause. + raise "unhandled RuntimeError with a handleable cause" + end + def weird StandardError.new.tap do |exc| def exc.weird? @@ -68,13 +77,12 @@ class Stargate end def sos - @result = 'killed' + @result = "killed" end def sos_first - @result = 'sos_first' + @result = "sos_first" end - end class CoolStargate < Stargate @@ -82,14 +90,13 @@ class CoolStargate < Stargate include ActiveSupport::Rescuable - rescue_from CoolError, :with => :sos_cool_error + rescue_from CoolError, with: :sos_cool_error def sos_cool_error - @result = 'sos_cool_error' + @result = "sos_cool_error" end end - class RescuableTest < ActiveSupport::TestCase def setup @stargate = Stargate.new @@ -98,22 +105,22 @@ class RescuableTest < ActiveSupport::TestCase def test_rescue_from_with_method @stargate.dispatch :attack - assert_equal 'killed', @stargate.result + assert_equal "killed", @stargate.result end def test_rescue_from_with_block @stargate.dispatch :nuke - assert_equal 'alldead', @stargate.result + assert_equal "alldead", @stargate.result end def test_rescue_from_with_block_with_args @stargate.dispatch :ronanize - assert_equal 'dex', @stargate.result + assert_equal "dex", @stargate.result end def test_rescue_from_error_dispatchers_with_case_operator @stargate.dispatch :weird - assert_equal 'weird', @stargate.result + assert_equal "weird", @stargate.result end def test_rescues_defined_later_are_added_at_end_of_the_rescue_handlers_array @@ -127,4 +134,9 @@ class RescuableTest < ActiveSupport::TestCase result = @cool_stargate.send(:rescue_handlers).collect(&:first) assert_equal expected, result end + + def test_rescue_falls_back_to_exception_cause + @stargate.dispatch :fall_back_to_cause + assert_equal "unhandled RuntimeError with a handleable cause", @stargate.result + end end diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb index 18fb6d2fbf..4d083ab773 100644 --- a/activesupport/test/safe_buffer_test.rb +++ b/activesupport/test/safe_buffer_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'active_support/core_ext/string/inflections' -require 'yaml' +require "abstract_unit" +require "active_support/core_ext/string/inflections" +require "yaml" class SafeBufferTest < ActiveSupport::TestCase def setup @@ -8,7 +8,7 @@ class SafeBufferTest < ActiveSupport::TestCase end def test_titleize - assert_equal 'Foo', "foo".html_safe.titleize + assert_equal "Foo", "foo".html_safe.titleize end test "Should look like a string" do @@ -46,26 +46,26 @@ class SafeBufferTest < ActiveSupport::TestCase end test "Should be converted to_yaml" do - str = 'hello!' + str = "hello!" buf = ActiveSupport::SafeBuffer.new str yaml = buf.to_yaml assert_match(/^--- #{str}/, yaml) - assert_equal 'hello!', YAML.load(yaml) + assert_equal "hello!", YAML.load(yaml) end test "Should work in nested to_yaml conversion" do - str = 'hello!' - data = { 'str' => ActiveSupport::SafeBuffer.new(str) } + str = "hello!" + data = { "str" => ActiveSupport::SafeBuffer.new(str) } yaml = YAML.dump data - assert_equal({'str' => str}, YAML.load(yaml)) + assert_equal({ "str" => str }, YAML.load(yaml)) end test "Should work with primitive-like-strings in to_yaml conversion" do - assert_equal 'true', YAML.load(ActiveSupport::SafeBuffer.new('true').to_yaml) - assert_equal 'false', YAML.load(ActiveSupport::SafeBuffer.new('false').to_yaml) - assert_equal '1', YAML.load(ActiveSupport::SafeBuffer.new('1').to_yaml) - assert_equal '1.1', YAML.load(ActiveSupport::SafeBuffer.new('1.1').to_yaml) + assert_equal "true", YAML.load(ActiveSupport::SafeBuffer.new("true").to_yaml) + assert_equal "false", YAML.load(ActiveSupport::SafeBuffer.new("false").to_yaml) + assert_equal "1", YAML.load(ActiveSupport::SafeBuffer.new("1").to_yaml) + assert_equal "1.1", YAML.load(ActiveSupport::SafeBuffer.new("1.1").to_yaml) end test "Should work with underscore" do @@ -74,31 +74,31 @@ class SafeBufferTest < ActiveSupport::TestCase end test "Should not return safe buffer from gsub" do - altered_buffer = @buffer.gsub('', 'asdf') - assert_equal 'asdf', altered_buffer + altered_buffer = @buffer.gsub("", "asdf") + assert_equal "asdf", altered_buffer assert !altered_buffer.html_safe? end test "Should not return safe buffer from gsub!" do - @buffer.gsub!('', 'asdf') - assert_equal 'asdf', @buffer + @buffer.gsub!("", "asdf") + assert_equal "asdf", @buffer assert !@buffer.html_safe? end test "Should escape dirty buffers on add" do clean = "hello".html_safe - @buffer.gsub!('', '<>') + @buffer.gsub!("", "<>") assert_equal "hello<>", clean + @buffer end test "Should concat as a normal string when safe" do clean = "hello".html_safe - @buffer.gsub!('', '<>') + @buffer.gsub!("", "<>") assert_equal "<>hello", @buffer + clean end test "Should preserve html_safe? status on copy" do - @buffer.gsub!('', '<>') + @buffer.gsub!("", "<>") assert !@buffer.dup.html_safe? end @@ -110,7 +110,7 @@ class SafeBufferTest < ActiveSupport::TestCase end test "Should raise an error when safe_concat is called on unsafe buffers" do - @buffer.gsub!('', '<>') + @buffer.gsub!("", "<>") assert_raise ActiveSupport::SafeBuffer::SafeConcatError do @buffer.safe_concat "BUSTED" end @@ -121,12 +121,12 @@ class SafeBufferTest < ActiveSupport::TestCase end test "clone_empty returns an empty buffer" do - assert_equal '', ActiveSupport::SafeBuffer.new('foo').clone_empty + assert_equal "", ActiveSupport::SafeBuffer.new("foo").clone_empty end test "clone_empty keeps the original dirtyness" do assert @buffer.clone_empty.html_safe? - assert !@buffer.gsub!('', '').clone_empty.html_safe? + assert !@buffer.gsub!("", "").clone_empty.html_safe? end test "Should be safe when sliced if original value was safe" do @@ -136,7 +136,7 @@ class SafeBufferTest < ActiveSupport::TestCase end test "Should continue unsafe on slice" do - x = 'foo'.html_safe.gsub!('f', '<script>alert("lolpwnd");</script>') + x = "foo".html_safe.gsub!("f", '<script>alert("lolpwnd");</script>') # calling gsub! makes the dirty flag true assert !x.html_safe?, "should not be safe" @@ -148,33 +148,33 @@ class SafeBufferTest < ActiveSupport::TestCase assert !y.html_safe?, "should not be safe" end - test 'Should work with interpolation (array argument)' do - x = 'foo %s bar'.html_safe % ['qux'] - assert_equal 'foo qux bar', x + test "Should work with interpolation (array argument)" do + x = "foo %s bar".html_safe % ["qux"] + assert_equal "foo qux bar", x end - test 'Should work with interpolation (hash argument)' do - x = 'foo %{x} bar'.html_safe % { x: 'qux' } - assert_equal 'foo qux bar', x + test "Should work with interpolation (hash argument)" do + x = "foo %{x} bar".html_safe % { x: "qux" } + assert_equal "foo qux bar", x end - test 'Should escape unsafe interpolated args' do - x = 'foo %{x} bar'.html_safe % { x: '<br/>' } - assert_equal 'foo <br/> bar', x + test "Should escape unsafe interpolated args" do + x = "foo %{x} bar".html_safe % { x: "<br/>" } + assert_equal "foo <br/> bar", x end - test 'Should not escape safe interpolated args' do - x = 'foo %{x} bar'.html_safe % { x: '<br/>'.html_safe } - assert_equal 'foo <br/> bar', x + test "Should not escape safe interpolated args" do + x = "foo %{x} bar".html_safe % { x: "<br/>".html_safe } + assert_equal "foo <br/> bar", x end - test 'Should interpolate to a safe string' do - x = 'foo %{x} bar'.html_safe % { x: 'qux' } - assert x.html_safe?, 'should be safe' + test "Should interpolate to a safe string" do + x = "foo %{x} bar".html_safe % { x: "qux" } + assert x.html_safe?, "should be safe" end - test 'Should not affect frozen objects when accessing characters' do - x = 'Hello'.html_safe + test "Should not affect frozen objects when accessing characters" do + x = "Hello".html_safe assert_equal x[/a/, 1], nil end end diff --git a/activesupport/test/security_utils_test.rb b/activesupport/test/security_utils_test.rb index 08d2e3baa6..842bdd469d 100644 --- a/activesupport/test/security_utils_test.rb +++ b/activesupport/test/security_utils_test.rb @@ -1,9 +1,9 @@ -require 'abstract_unit' -require 'active_support/security_utils' +require "abstract_unit" +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 ActiveSupport::SecurityUtils.secure_compare("a", "a") + assert !ActiveSupport::SecurityUtils.secure_compare("a", "b") end end diff --git a/activesupport/test/share_lock_test.rb b/activesupport/test/share_lock_test.rb index acefa185a8..a5970591fa 100644 --- a/activesupport/test/share_lock_test.rb +++ b/activesupport/test/share_lock_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'concurrent/atomic/count_down_latch' -require 'active_support/concurrency/share_lock' +require "abstract_unit" +require "concurrent/atomic/count_down_latch" +require "active_support/concurrency/share_lock" class ShareLockTest < ActiveSupport::TestCase def setup @@ -17,7 +17,7 @@ class ShareLockTest < ActiveSupport::TestCase def test_sharing_doesnt_block with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_latch| - assert_threads_not_stuck(Thread.new {@lock.sharing {} }) + assert_threads_not_stuck(Thread.new { @lock.sharing {} }) end end @@ -489,90 +489,90 @@ class ShareLockTest < ActiveSupport::TestCase private - module CustomAssertions - SUFFICIENT_TIMEOUT = 0.2 + module CustomAssertions + SUFFICIENT_TIMEOUT = 0.2 - private + private - def assert_threads_stuck_but_releasable_by_latch(threads, latch) - assert_threads_stuck threads - latch.count_down - assert_threads_not_stuck threads - end + def assert_threads_stuck_but_releasable_by_latch(threads, latch) + assert_threads_stuck threads + latch.count_down + assert_threads_not_stuck threads + end - def assert_threads_stuck(threads) - sleep(SUFFICIENT_TIMEOUT) # give threads time to do their business - assert(Array(threads).all? { |t| t.join(0.001).nil? }) - end + def assert_threads_stuck(threads) + sleep(SUFFICIENT_TIMEOUT) # give threads time to do their business + assert(Array(threads).all? { |t| t.join(0.001).nil? }) + end - def assert_threads_not_stuck(threads) - assert(Array(threads).all? { |t| t.join(SUFFICIENT_TIMEOUT) }) + def assert_threads_not_stuck(threads) + assert(Array(threads).all? { |t| t.join(SUFFICIENT_TIMEOUT) }) + end end - end - class CustomAssertionsTest < ActiveSupport::TestCase - include CustomAssertions + class CustomAssertionsTest < ActiveSupport::TestCase + include CustomAssertions - def setup - @latch = Concurrent::CountDownLatch.new - @thread = Thread.new { @latch.wait } - end + def setup + @latch = Concurrent::CountDownLatch.new + @thread = Thread.new { @latch.wait } + end - def teardown - @latch.count_down - @thread.join - end + def teardown + @latch.count_down + @thread.join + end - def test_happy_path - assert_threads_stuck_but_releasable_by_latch @thread, @latch - end + def test_happy_path + assert_threads_stuck_but_releasable_by_latch @thread, @latch + end - def test_detects_stuck_thread - assert_raises(Minitest::Assertion) do - assert_threads_not_stuck @thread + def test_detects_stuck_thread + assert_raises(Minitest::Assertion) do + assert_threads_not_stuck @thread + end end - end - def test_detects_free_thread - @latch.count_down - assert_raises(Minitest::Assertion) do - assert_threads_stuck @thread + def test_detects_free_thread + @latch.count_down + assert_raises(Minitest::Assertion) do + assert_threads_stuck @thread + end end - end - def test_detects_already_released - @latch.count_down - assert_raises(Minitest::Assertion) do - assert_threads_stuck_but_releasable_by_latch @thread, @latch + def test_detects_already_released + @latch.count_down + assert_raises(Minitest::Assertion) do + assert_threads_stuck_but_releasable_by_latch @thread, @latch + end end - end - def test_detects_remains_latched - another_latch = Concurrent::CountDownLatch.new - assert_raises(Minitest::Assertion) do - assert_threads_stuck_but_releasable_by_latch @thread, another_latch + def test_detects_remains_latched + another_latch = Concurrent::CountDownLatch.new + assert_raises(Minitest::Assertion) do + assert_threads_stuck_but_releasable_by_latch @thread, another_latch + end end end - end - include CustomAssertions + include CustomAssertions - def with_thread_waiting_in_lock_section(lock_section) - in_section = Concurrent::CountDownLatch.new - section_release = Concurrent::CountDownLatch.new + def with_thread_waiting_in_lock_section(lock_section) + in_section = Concurrent::CountDownLatch.new + section_release = Concurrent::CountDownLatch.new - stuck_thread = Thread.new do - @lock.send(lock_section) do - in_section.count_down - section_release.wait + stuck_thread = Thread.new do + @lock.send(lock_section) do + in_section.count_down + section_release.wait + end end - end - in_section.wait + in_section.wait - yield section_release - ensure - section_release.count_down - stuck_thread.join # clean up - end + yield section_release + ensure + section_release.count_down + stuck_thread.join # clean up + end end diff --git a/activesupport/test/string_inquirer_test.rb b/activesupport/test/string_inquirer_test.rb index a2ed577eb0..d41e4d6800 100644 --- a/activesupport/test/string_inquirer_test.rb +++ b/activesupport/test/string_inquirer_test.rb @@ -1,8 +1,8 @@ -require 'abstract_unit' +require "abstract_unit" class StringInquirerTest < ActiveSupport::TestCase def setup - @string_inquirer = ActiveSupport::StringInquirer.new('production') + @string_inquirer = ActiveSupport::StringInquirer.new("production") end def test_match diff --git a/activesupport/test/subscriber_test.rb b/activesupport/test/subscriber_test.rb index a88d8d9eba..9127da35d4 100644 --- a/activesupport/test/subscriber_test.rb +++ b/activesupport/test/subscriber_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/subscriber' +require "abstract_unit" +require "active_support/subscriber" class TestSubscriber < ActiveSupport::Subscriber attach_to :doodle @@ -16,9 +16,9 @@ class TestSubscriber < ActiveSupport::Subscriber private - def private_party(event) - events << event - end + def private_party(event) + events << event + end end # Monkey patch subscriber to test that only one subscriber per method is added. diff --git a/activesupport/test/tagged_logging_test.rb b/activesupport/test/tagged_logging_test.rb index 917fa46c96..2469e827d4 100644 --- a/activesupport/test/tagged_logging_test.rb +++ b/activesupport/test/tagged_logging_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'active_support/logger' -require 'active_support/tagged_logging' +require "abstract_unit" +require "active_support/logger" +require "active_support/tagged_logging" class TaggedLoggingTest < ActiveSupport::TestCase class MyLogger < ::ActiveSupport::Logger @@ -14,7 +14,7 @@ class TaggedLoggingTest < ActiveSupport::TestCase @logger = ActiveSupport::TaggedLogging.new(MyLogger.new(@output)) end - test 'sets logger.formatter if missing and extends it with a tagging API' do + test "sets logger.formatter if missing and extends it with a tagging API" do logger = Logger.new(StringIO.new) assert_nil logger.formatter ActiveSupport::TaggedLogging.new(logger) @@ -43,14 +43,14 @@ class TaggedLoggingTest < ActiveSupport::TestCase end test "push and pop tags directly" do - assert_equal %w(A B C), @logger.push_tags('A', ['B', ' ', ['C']]) - @logger.info 'a' + assert_equal %w(A B C), @logger.push_tags("A", ["B", " ", ["C"]]) + @logger.info "a" assert_equal %w(C), @logger.pop_tags - @logger.info 'b' + @logger.info "b" assert_equal %w(B), @logger.pop_tags(1) - @logger.info 'c' + @logger.info "c" assert_equal [], @logger.clear_tags! - @logger.info 'd' + @logger.info "d" assert_equal "[A] [B] [C] a\n[A] [B] b\n[A] c\nd\n", @output.string end diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb index 18228a2ac5..d769a8c145 100644 --- a/activesupport/test/test_case_test.rb +++ b/activesupport/test/test_case_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class AssertDifferenceTest < ActiveSupport::TestCase def setup @@ -20,21 +20,21 @@ class AssertDifferenceTest < ActiveSupport::TestCase assert_equal true, assert_not(false) e = assert_raises(Minitest::Assertion) { assert_not true } - assert_equal 'Expected true to be nil or false', e.message + assert_equal "Expected true to be nil or false", e.message - e = assert_raises(Minitest::Assertion) { assert_not true, 'custom' } - assert_equal 'custom', e.message + e = assert_raises(Minitest::Assertion) { assert_not true, "custom" } + assert_equal "custom", e.message end def test_assert_no_difference_pass - assert_no_difference '@object.num' do + assert_no_difference "@object.num" do # ... end end def test_assert_no_difference_fail error = assert_raises(Minitest::Assertion) do - assert_no_difference '@object.num' do + assert_no_difference "@object.num" do @object.increment end end @@ -43,7 +43,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase def test_assert_no_difference_with_message_fail error = assert_raises(Minitest::Assertion) do - assert_no_difference '@object.num', 'Object Changed' do + assert_no_difference "@object.num", "Object Changed" do @object.increment end end @@ -51,13 +51,13 @@ class AssertDifferenceTest < ActiveSupport::TestCase end def test_assert_difference - assert_difference '@object.num', +1 do + assert_difference "@object.num", +1 do @object.increment end end def test_assert_difference_retval - incremented = assert_difference '@object.num', +1 do + incremented = assert_difference "@object.num", +1 do @object.increment end @@ -65,40 +65,40 @@ class AssertDifferenceTest < ActiveSupport::TestCase end def test_assert_difference_with_implicit_difference - assert_difference '@object.num' do + assert_difference "@object.num" do @object.increment end end def test_arbitrary_expression - assert_difference '@object.num + 1', +2 do + assert_difference "@object.num + 1", +2 do @object.increment @object.increment end end def test_negative_differences - assert_difference '@object.num', -1 do + assert_difference "@object.num", -1 do @object.decrement end end def test_expression_is_evaluated_in_the_appropriate_scope silence_warnings do - local_scope = local_scope = 'foo' - assert_difference('local_scope; @object.num') { @object.increment } + local_scope = local_scope = "foo" + assert_difference("local_scope; @object.num") { @object.increment } end end def test_array_of_expressions - assert_difference [ '@object.num', '@object.num + 1' ], +1 do + assert_difference [ "@object.num", "@object.num + 1" ], +1 do @object.increment end end def test_array_of_expressions_identify_failure assert_raises(Minitest::Assertion) do - assert_difference ['@object.num', '1 + 1'] do + assert_difference ["@object.num", "1 + 1"] do @object.increment end end @@ -106,11 +106,135 @@ class AssertDifferenceTest < ActiveSupport::TestCase def test_array_of_expressions_identify_failure_when_message_provided assert_raises(Minitest::Assertion) do - assert_difference ['@object.num', '1 + 1'], 1, 'something went wrong' do + assert_difference ["@object.num", "1 + 1"], 1, "something went wrong" do @object.increment end end end + + def test_assert_changes_pass + assert_changes "@object.num" do + @object.increment + end + end + + def test_assert_changes_pass_with_lambda + assert_changes -> { @object.num } do + @object.increment + end + end + + def test_assert_changes_with_from_option + assert_changes "@object.num", from: 0 do + @object.increment + end + end + + def test_assert_changes_with_from_option_with_wrong_value + assert_raises Minitest::Assertion do + assert_changes "@object.num", from: -1 do + @object.increment + end + end + end + + def test_assert_changes_with_from_option_with_nil + error = assert_raises Minitest::Assertion do + assert_changes "@object.num", from: nil do + @object.increment + end + end + assert_equal "\"@object.num\" isn't nil", error.message + end + + def test_assert_changes_with_to_option + assert_changes "@object.num", to: 1 do + @object.increment + end + end + + def test_assert_changes_with_wrong_to_option + assert_raises Minitest::Assertion do + assert_changes "@object.num", to: 2 do + @object.increment + end + end + end + + def test_assert_changes_with_from_option_and_to_option + assert_changes "@object.num", from: 0, to: 1 do + @object.increment + end + end + + def test_assert_changes_with_from_and_to_options_and_wrong_to_value + assert_raises Minitest::Assertion do + assert_changes "@object.num", from: 0, to: 2 do + @object.increment + end + end + end + + def test_assert_changes_works_with_any_object + retval = silence_warnings do + assert_changes :@new_object, from: nil, to: 42 do + @new_object = 42 + end + end + + assert_equal 42, retval + end + + def test_assert_changes_works_with_nil + oldval = @object + + retval = assert_changes :@object, from: oldval, to: nil do + @object = nil + end + + assert_nil retval + end + + def test_assert_changes_with_to_and_case_operator + token = nil + + assert_changes "token", to: /\w{32}/ do + token = SecureRandom.hex + end + end + + def test_assert_changes_with_to_and_from_and_case_operator + token = SecureRandom.hex + + assert_changes "token", from: /\w{32}/, to: /\w{32}/ do + token = SecureRandom.hex + end + end + + def test_assert_changes_with_message + error = assert_raises Minitest::Assertion do + assert_changes "@object.num", "@object.num should 1", to: 1 do + end + end + + assert_equal "@object.num should 1.\n\"@object.num\" didn't change to 1", error.message + end + + def test_assert_no_changes_pass + assert_no_changes "@object.num" do + # ... + end + end + + def test_assert_no_changes_with_message + error = assert_raises Minitest::Assertion do + assert_no_changes "@object.num", "@object.num should not change" do + @object.increment + end + end + + assert_equal "@object.num should not change.\n\"@object.num\" did change to 1.\nExpected: 0\n Actual: 1", error.message + end end class AlsoDoingNothingTest < ActiveSupport::TestCase @@ -170,7 +294,7 @@ end class TestCaseTaggedLoggingTest < ActiveSupport::TestCase def before_setup - require 'stringio' + require "stringio" @out = StringIO.new self.tagged_logger = ActiveSupport::TaggedLogging.new(Logger.new(@out)) super diff --git a/activesupport/test/testing/constant_lookup_test.rb b/activesupport/test/testing/constant_lookup_test.rb index 0f16419c8b..00e69fcdb5 100644 --- a/activesupport/test/testing/constant_lookup_test.rb +++ b/activesupport/test/testing/constant_lookup_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'dependencies_test_helpers' +require "abstract_unit" +require "dependencies_test_helpers" class Foo; end class Bar < Foo @@ -69,7 +69,7 @@ class ConstantLookupTest < ActiveSupport::TestCase def test_does_not_swallow_exception_on_no_name_error_within_constant assert_raises(NameError) do with_autoloading_fixtures do - self.class.determine_constant_from_test_name('RaisesNameError') + self.class.determine_constant_from_test_name("RaisesNameError") end end end diff --git a/activesupport/test/testing/file_fixtures_test.rb b/activesupport/test/testing/file_fixtures_test.rb index 91b8a9071c..e44fe58ce9 100644 --- a/activesupport/test/testing/file_fixtures_test.rb +++ b/activesupport/test/testing/file_fixtures_test.rb @@ -1,4 +1,6 @@ -require 'abstract_unit' +require "abstract_unit" + +require "pathname" class FileFixturesTest < ActiveSupport::TestCase self.file_fixture_path = File.expand_path("../../file_fixtures", __FILE__) @@ -6,7 +8,7 @@ class FileFixturesTest < ActiveSupport::TestCase test "#file_fixture returns Pathname to file fixture" do path = file_fixture("sample.txt") assert_kind_of Pathname, path - assert_match %r{activesupport/test/file_fixtures/sample.txt$}, path.to_s + assert_match %r{.*/test/file_fixtures/sample.txt$}, path.to_s end test "raises an exception when the fixture file does not exist" do @@ -23,6 +25,6 @@ class FileFixturesPathnameDirectoryTest < ActiveSupport::TestCase test "#file_fixture_path returns Pathname to file fixture" do path = file_fixture("sample.txt") assert_kind_of Pathname, path - assert_match %r{activesupport/test/file_fixtures/sample.txt$}, path.to_s + assert_match %r{.*/test/file_fixtures/sample.txt$}, path.to_s end end diff --git a/activesupport/test/testing/method_call_assertions_test.rb b/activesupport/test/testing/method_call_assertions_test.rb index 3e5ba7c079..7887933b15 100644 --- a/activesupport/test/testing/method_call_assertions_test.rb +++ b/activesupport/test/testing/method_call_assertions_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/testing/method_call_assertions' +require "abstract_unit" +require "active_support/testing/method_call_assertions" class MethodCallAssertionsTest < ActiveSupport::TestCase include ActiveSupport::Testing::MethodCallAssertions @@ -51,7 +51,7 @@ class MethodCallAssertionsTest < ActiveSupport::TestCase def test_assert_called_with_message error = assert_raises(Minitest::Assertion) do - assert_called(@object, :increment, 'dang it') do + assert_called(@object, :increment, "dang it") do # Call nothing... end end diff --git a/activesupport/test/time_travel_test.rb b/activesupport/test/time_travel_test.rb index 59c3e52c2f..c68be329bc 100644 --- a/activesupport/test/time_travel_test.rb +++ b/activesupport/test/time_travel_test.rb @@ -1,20 +1,20 @@ -require 'abstract_unit' -require 'active_support/core_ext/date_time' -require 'active_support/core_ext/numeric/time' +require "abstract_unit" +require "active_support/core_ext/date_time" +require "active_support/core_ext/numeric/time" class TimeTravelTest < ActiveSupport::TestCase - teardown do - travel_back - end - def test_time_helper_travel Time.stub(:now, Time.now) do - expected_time = Time.now + 1.day - travel 1.day + begin + expected_time = Time.now + 1.day + travel 1.day - assert_equal expected_time.to_s(:db), Time.now.to_s(:db) - assert_equal expected_time.to_date, Date.today - assert_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db) + assert_equal expected_time.to_s(:db), Time.now.to_s(:db) + assert_equal expected_time.to_date, Date.today + assert_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db) + ensure + travel_back + end end end @@ -36,12 +36,16 @@ class TimeTravelTest < ActiveSupport::TestCase def test_time_helper_travel_to Time.stub(:now, Time.now) do - expected_time = Time.new(2004, 11, 24, 01, 04, 44) - travel_to expected_time + begin + expected_time = Time.new(2004, 11, 24, 01, 04, 44) + travel_to expected_time - assert_equal expected_time, Time.now - assert_equal Date.new(2004, 11, 24), Date.today - assert_equal expected_time.to_datetime, DateTime.now + assert_equal expected_time, Time.now + assert_equal Date.new(2004, 11, 24), Date.today + assert_equal expected_time.to_datetime, DateTime.now + ensure + travel_back + end end end @@ -63,17 +67,68 @@ class TimeTravelTest < ActiveSupport::TestCase def test_time_helper_travel_back Time.stub(:now, Time.now) do - expected_time = Time.new(2004, 11, 24, 01, 04, 44) + begin + expected_time = Time.new(2004, 11, 24, 01, 04, 44) - travel_to expected_time - assert_equal expected_time, Time.now - assert_equal Date.new(2004, 11, 24), Date.today - assert_equal expected_time.to_datetime, DateTime.now - travel_back + travel_to expected_time + assert_equal expected_time, Time.now + assert_equal Date.new(2004, 11, 24), Date.today + assert_equal expected_time.to_datetime, DateTime.now + travel_back - assert_not_equal expected_time, Time.now - assert_not_equal Date.new(2004, 11, 24), Date.today - assert_not_equal expected_time.to_datetime, DateTime.now + assert_not_equal expected_time, Time.now + assert_not_equal Date.new(2004, 11, 24), Date.today + assert_not_equal expected_time.to_datetime, DateTime.now + ensure + travel_back + end + end + end + + def test_time_helper_travel_to_with_nested_calls_with_blocks + Time.stub(:now, Time.now) do + 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 + travel_to(inner_expected_time) do + #noop + end + end + end + end + end + + def test_time_helper_travel_to_with_nested_calls + Time.stub(:now, Time.now) do + 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_nothing_raised do + travel_to(inner_expected_time) + + assert_equal inner_expected_time, Time.now + end + end + end + end + + def test_time_helper_travel_to_with_subsequent_calls + Time.stub(:now, Time.now) do + begin + initial_expected_time = Time.new(2004, 11, 24, 01, 04, 44) + subsequent_expected_time = Time.new(2004, 10, 24, 01, 04, 44) + assert_nothing_raised do + travel_to initial_expected_time + travel_to subsequent_expected_time + + assert_equal subsequent_expected_time, Time.now + + travel_back + end + ensure + travel_back + end end end diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index 00d40c4497..bd29584478 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -1,25 +1,25 @@ -require 'abstract_unit' -require 'active_support/time' -require 'time_zone_test_helpers' -require 'yaml' +require "abstract_unit" +require "active_support/time" +require "time_zone_test_helpers" +require "yaml" class TimeZoneTest < ActiveSupport::TestCase include TimeZoneTestHelpers def test_utc_to_local - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] assert_equal Time.utc(1999, 12, 31, 19), zone.utc_to_local(Time.utc(2000, 1)) # standard offset -0500 assert_equal Time.utc(2000, 6, 30, 20), zone.utc_to_local(Time.utc(2000, 7)) # dst offset -0400 end def test_local_to_utc - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] assert_equal Time.utc(2000, 1, 1, 5), zone.local_to_utc(Time.utc(2000, 1)) # standard offset -0500 assert_equal Time.utc(2000, 7, 1, 4), zone.local_to_utc(Time.utc(2000, 7)) # dst offset -0400 end def test_period_for_local - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] assert_instance_of TZInfo::TimezonePeriod, zone.period_for_local(Time.utc(2000)) end @@ -39,7 +39,7 @@ class TimeZoneTest < ActiveSupport::TestCase end ActiveSupport::TimeZone.all.each do |zone| - name = zone.name.downcase.gsub(/[^a-z]/, '_') + name = zone.name.downcase.gsub(/[^a-z]/, "_") define_method("test_from_#{name}_to_map") do assert_instance_of ActiveSupport::TimeZone, ActiveSupport::TimeZone[zone.name] end @@ -51,8 +51,8 @@ class TimeZoneTest < ActiveSupport::TestCase end def test_now - with_env_tz 'US/Eastern' do - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'].dup + with_env_tz "US/Eastern" do + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"].dup def zone.time_now; Time.local(2000); end assert_instance_of ActiveSupport::TimeWithZone, zone.now assert_equal Time.utc(2000,1,1,5), zone.now.utc @@ -62,8 +62,8 @@ class TimeZoneTest < ActiveSupport::TestCase end def test_now_enforces_spring_dst_rules - with_env_tz 'US/Eastern' do - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'].dup + with_env_tz "US/Eastern" do + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"].dup def zone.time_now Time.local(2006,4,2,2) # 2AM springs forward to 3AM end @@ -74,8 +74,8 @@ class TimeZoneTest < ActiveSupport::TestCase end def test_now_enforces_fall_dst_rules - with_env_tz 'US/Eastern' do - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'].dup + with_env_tz "US/Eastern" do + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"].dup def zone.time_now Time.at(1162098000) # equivalent to 1AM DST end @@ -85,52 +85,52 @@ class TimeZoneTest < ActiveSupport::TestCase end def test_unknown_timezones_delegation_to_tzinfo - zone = ActiveSupport::TimeZone['America/Montevideo'] + zone = ActiveSupport::TimeZone["America/Montevideo"] assert_equal ActiveSupport::TimeZone, zone.class - assert_equal zone.object_id, ActiveSupport::TimeZone['America/Montevideo'].object_id + assert_equal zone.object_id, ActiveSupport::TimeZone["America/Montevideo"].object_id assert_equal Time.utc(2010, 1, 31, 22), zone.utc_to_local(Time.utc(2010, 2)) # daylight saving offset -0200 assert_equal Time.utc(2010, 3, 31, 21), zone.utc_to_local(Time.utc(2010, 4)) # standard offset -0300 end def test_today travel_to(Time.utc(2000, 1, 1, 4, 59, 59)) # 1 sec before midnight Jan 1 EST - assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today + assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].today travel_to(Time.utc(2000, 1, 1, 5)) # midnight Jan 1 EST - assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today + assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].today travel_to(Time.utc(2000, 1, 2, 4, 59, 59)) # 1 sec before midnight Jan 2 EST - assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today + assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].today travel_to(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST - assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today + assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].today travel_back end def test_tomorrow travel_to(Time.utc(2000, 1, 1, 4, 59, 59)) # 1 sec before midnight Jan 1 EST - assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].tomorrow + assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].tomorrow travel_to(Time.utc(2000, 1, 1, 5)) # midnight Jan 1 EST - assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].tomorrow + assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].tomorrow travel_to(Time.utc(2000, 1, 2, 4, 59, 59)) # 1 sec before midnight Jan 2 EST - assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].tomorrow + assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].tomorrow travel_to(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST - assert_equal Date.new(2000, 1, 3), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].tomorrow + assert_equal Date.new(2000, 1, 3), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].tomorrow travel_back end def test_yesterday travel_to(Time.utc(2000, 1, 1, 4, 59, 59)) # 1 sec before midnight Jan 1 EST - assert_equal Date.new(1999, 12, 30), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].yesterday + assert_equal Date.new(1999, 12, 30), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].yesterday travel_to(Time.utc(2000, 1, 1, 5)) # midnight Jan 1 EST - assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].yesterday + assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].yesterday travel_to(Time.utc(2000, 1, 2, 4, 59, 59)) # 1 sec before midnight Jan 2 EST - assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].yesterday + assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].yesterday travel_to(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST - assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].yesterday + assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone["Eastern Time (US & Canada)"].yesterday travel_back end def test_travel_to_a_date with_env_tz do - Time.use_zone('Hawaii') do + Time.use_zone("Hawaii") do date = Date.new(2014, 2, 18) time = date.midnight @@ -167,37 +167,37 @@ class TimeZoneTest < ActiveSupport::TestCase end def test_local_enforces_spring_dst_rules - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] twz = zone.local(2006,4,2,1,59,59) # 1 second before DST start assert_equal Time.utc(2006,4,2,1,59,59), twz.time assert_equal Time.utc(2006,4,2,6,59,59), twz.utc assert_equal false, twz.dst? - assert_equal 'EST', twz.zone + assert_equal "EST", twz.zone twz2 = zone.local(2006,4,2,2) # 2AM does not exist because at 2AM, time springs forward to 3AM assert_equal Time.utc(2006,4,2,3), twz2.time # twz is created for 3AM assert_equal Time.utc(2006,4,2,7), twz2.utc assert_equal true, twz2.dst? - assert_equal 'EDT', twz2.zone + assert_equal "EDT", twz2.zone twz3 = zone.local(2006,4,2,2,30) # 2:30AM does not exist because at 2AM, time springs forward to 3AM assert_equal Time.utc(2006,4,2,3,30), twz3.time # twz is created for 3:30AM assert_equal Time.utc(2006,4,2,7,30), twz3.utc assert_equal true, twz3.dst? - assert_equal 'EDT', twz3.zone + assert_equal "EDT", twz3.zone end def test_local_enforces_fall_dst_rules # 1AM during fall DST transition is ambiguous, it could be either DST or non-DST 1AM # Mirroring Time.local behavior, this method selects the DST time - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] twz = zone.local(2006,10,29,1) assert_equal Time.utc(2006,10,29,1), twz.time assert_equal Time.utc(2006,10,29,5), twz.utc assert_equal true, twz.dst? - assert_equal 'EDT', twz.zone + assert_equal "EDT", twz.zone end def test_at - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] secs = 946684800.0 twz = zone.at(secs) assert_equal Time.utc(1999,12,31,19), twz.time @@ -207,7 +207,7 @@ class TimeZoneTest < ActiveSupport::TestCase end def test_at_with_old_date - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] secs = DateTime.civil(1850).to_f twz = zone.at(secs) assert_equal [1850, 1, 1, 0], [twz.utc.year, twz.utc.mon, twz.utc.day, twz.utc.hour] @@ -216,8 +216,8 @@ class TimeZoneTest < ActiveSupport::TestCase end def test_parse - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - twz = zone.parse('1999-12-31 19:00:00') + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + twz = zone.parse("1999-12-31 19:00:00") assert_equal Time.utc(1999,12,31,19), twz.time assert_equal Time.utc(2000), twz.utc assert_equal zone, twz.time_zone @@ -226,97 +226,97 @@ class TimeZoneTest < ActiveSupport::TestCase def test_parse_string_with_timezone (-11..13).each do |timezone_offset| zone = ActiveSupport::TimeZone[timezone_offset] - twz = zone.parse('1999-12-31 19:00:00') + twz = zone.parse("1999-12-31 19:00:00") assert_equal twz, zone.parse(twz.to_s) end end def test_parse_with_old_date - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - twz = zone.parse('1883-12-31 19:00:00') + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + twz = zone.parse("1883-12-31 19:00:00") assert_equal [0,0,19,31,12,1883], twz.to_a[0,6] assert_equal zone, twz.time_zone end def test_parse_far_future_date_with_time_zone_offset_in_string - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - twz = zone.parse('2050-12-31 19:00:00 -10:00') # i.e., 2050-01-01 05:00:00 UTC + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + twz = zone.parse("2050-12-31 19:00:00 -10:00") # i.e., 2050-01-01 05:00:00 UTC assert_equal [0,0,0,1,1,2051], twz.to_a[0,6] assert_equal zone, twz.time_zone end def test_parse_returns_nil_when_string_without_date_information_is_passed_in - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - assert_nil zone.parse('foobar') - assert_nil zone.parse(' ') + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + assert_nil zone.parse("foobar") + assert_nil zone.parse(" ") end def test_parse_with_incomplete_date - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] zone.stub(:now, zone.local(1999,12,31)) do - twz = zone.parse('19:00:00') + twz = zone.parse("19:00:00") assert_equal Time.utc(1999,12,31,19), twz.time end end def test_parse_with_day_omitted - with_env_tz 'US/Eastern' do - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - assert_equal Time.local(2000, 2, 1), zone.parse('Feb', Time.local(2000, 1, 1)) - assert_equal Time.local(2005, 2, 1), zone.parse('Feb 2005', Time.local(2000, 1, 1)) - assert_equal Time.local(2005, 2, 2), zone.parse('2 Feb 2005', Time.local(2000, 1, 1)) + with_env_tz "US/Eastern" do + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + assert_equal Time.local(2000, 2, 1), zone.parse("Feb", Time.local(2000, 1, 1)) + assert_equal Time.local(2005, 2, 1), zone.parse("Feb 2005", Time.local(2000, 1, 1)) + assert_equal Time.local(2005, 2, 2), zone.parse("2 Feb 2005", Time.local(2000, 1, 1)) end end def test_parse_should_not_black_out_system_timezone_dst_jump - with_env_tz('EET') do - zone = ActiveSupport::TimeZone['Pacific Time (US & Canada)'] - twz = zone.parse('2012-03-25 03:29:00') + with_env_tz("EET") do + zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"] + twz = zone.parse("2012-03-25 03:29:00") assert_equal [0, 29, 3, 25, 3, 2012], twz.to_a[0,6] end end def test_parse_should_black_out_app_timezone_dst_jump - with_env_tz('EET') do - zone = ActiveSupport::TimeZone['Pacific Time (US & Canada)'] - twz = zone.parse('2012-03-11 02:29:00') + with_env_tz("EET") do + zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"] + twz = zone.parse("2012-03-11 02:29:00") assert_equal [0, 29, 3, 11, 3, 2012], twz.to_a[0,6] end end def test_parse_with_missing_time_components - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] zone.stub(:now, zone.local(1999, 12, 31, 12, 59, 59)) do - twz = zone.parse('2012-12-01') + twz = zone.parse("2012-12-01") assert_equal Time.utc(2012, 12, 1), twz.time end end def test_parse_with_javascript_date - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] twz = zone.parse("Mon May 28 2012 00:00:00 GMT-0700 (PDT)") assert_equal Time.utc(2012, 5, 28, 7, 0, 0), twz.utc end def test_parse_doesnt_use_local_dst - with_env_tz 'US/Eastern' do - zone = ActiveSupport::TimeZone['UTC'] - twz = zone.parse('2013-03-10 02:00:00') + with_env_tz "US/Eastern" do + zone = ActiveSupport::TimeZone["UTC"] + twz = zone.parse("2013-03-10 02:00:00") assert_equal Time.utc(2013, 3, 10, 2, 0, 0), twz.time end end def test_parse_handles_dst_jump - with_env_tz 'US/Eastern' do - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - twz = zone.parse('2013-03-10 02:00:00') + with_env_tz "US/Eastern" do + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + twz = zone.parse("2013-03-10 02:00:00") assert_equal Time.utc(2013, 3, 10, 3, 0, 0), twz.time end end def test_strptime - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - twz = zone.strptime('1999-12-31 12:00:00', '%Y-%m-%d %H:%M:%S') + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + twz = zone.strptime("1999-12-31 12:00:00", "%Y-%m-%d %H:%M:%S") assert_equal Time.utc(1999,12,31,17), twz assert_equal Time.utc(1999,12,31,12), twz.time assert_equal Time.utc(1999,12,31,17), twz.utc @@ -324,9 +324,9 @@ class TimeZoneTest < ActiveSupport::TestCase end def test_strptime_with_nondefault_time_zone - with_tz_default ActiveSupport::TimeZone['Pacific Time (US & Canada)'] do - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - twz = zone.strptime('1999-12-31 12:00:00', '%Y-%m-%d %H:%M:%S') + with_tz_default ActiveSupport::TimeZone["Pacific Time (US & Canada)"] do + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + twz = zone.strptime("1999-12-31 12:00:00", "%Y-%m-%d %H:%M:%S") assert_equal Time.utc(1999,12,31,17), twz assert_equal Time.utc(1999,12,31,12), twz.time assert_equal Time.utc(1999,12,31,17), twz.utc @@ -335,8 +335,8 @@ class TimeZoneTest < ActiveSupport::TestCase end def test_strptime_with_explicit_time_zone_as_abbrev - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - twz = zone.strptime('1999-12-31 12:00:00 PST', '%Y-%m-%d %H:%M:%S %Z') + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + twz = zone.strptime("1999-12-31 12:00:00 PST", "%Y-%m-%d %H:%M:%S %Z") assert_equal Time.utc(1999,12,31,20), twz assert_equal Time.utc(1999,12,31,15), twz.time assert_equal Time.utc(1999,12,31,20), twz.utc @@ -344,8 +344,8 @@ class TimeZoneTest < ActiveSupport::TestCase end def test_strptime_with_explicit_time_zone_as_h_offset - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - twz = zone.strptime('1999-12-31 12:00:00 -08', '%Y-%m-%d %H:%M:%S %:::z') + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + twz = zone.strptime("1999-12-31 12:00:00 -08", "%Y-%m-%d %H:%M:%S %:::z") assert_equal Time.utc(1999,12,31,20), twz assert_equal Time.utc(1999,12,31,15), twz.time assert_equal Time.utc(1999,12,31,20), twz.utc @@ -353,8 +353,8 @@ class TimeZoneTest < ActiveSupport::TestCase end def test_strptime_with_explicit_time_zone_as_hm_offset - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - twz = zone.strptime('1999-12-31 12:00:00 -08:00', '%Y-%m-%d %H:%M:%S %:z') + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + twz = zone.strptime("1999-12-31 12:00:00 -08:00", "%Y-%m-%d %H:%M:%S %:z") assert_equal Time.utc(1999,12,31,20), twz assert_equal Time.utc(1999,12,31,15), twz.time assert_equal Time.utc(1999,12,31,20), twz.utc @@ -362,8 +362,8 @@ class TimeZoneTest < ActiveSupport::TestCase end def test_strptime_with_explicit_time_zone_as_hms_offset - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - twz = zone.strptime('1999-12-31 12:00:00 -08:00:00', '%Y-%m-%d %H:%M:%S %::z') + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + twz = zone.strptime("1999-12-31 12:00:00 -08:00:00", "%Y-%m-%d %H:%M:%S %::z") assert_equal Time.utc(1999,12,31,20), twz assert_equal Time.utc(1999,12,31,15), twz.time assert_equal Time.utc(1999,12,31,20), twz.utc @@ -371,8 +371,8 @@ class TimeZoneTest < ActiveSupport::TestCase end def test_strptime_with_almost_explicit_time_zone - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - twz = zone.strptime('1999-12-31 12:00:00 %Z', '%Y-%m-%d %H:%M:%S %%Z') + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + twz = zone.strptime("1999-12-31 12:00:00 %Z", "%Y-%m-%d %H:%M:%S %%Z") assert_equal Time.utc(1999,12,31,17), twz assert_equal Time.utc(1999,12,31,12), twz.time assert_equal Time.utc(1999,12,31,17), twz.utc @@ -380,21 +380,57 @@ class TimeZoneTest < ActiveSupport::TestCase end def test_strptime_with_day_omitted - with_env_tz 'US/Eastern' do - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - assert_equal Time.local(2000, 2, 1), zone.strptime('Feb', '%b', Time.local(2000, 1, 1)) - assert_equal Time.local(2005, 2, 1), zone.strptime('Feb 2005', '%b %Y', Time.local(2000, 1, 1)) - assert_equal Time.local(2005, 2, 2), zone.strptime('2 Feb 2005', '%e %b %Y', Time.local(2000, 1, 1)) + with_env_tz "US/Eastern" do + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + assert_equal Time.local(2000, 2, 1), zone.strptime("Feb", "%b", Time.local(2000, 1, 1)) + assert_equal Time.local(2005, 2, 1), zone.strptime("Feb 2005", "%b %Y", Time.local(2000, 1, 1)) + assert_equal Time.local(2005, 2, 2), zone.strptime("2 Feb 2005", "%e %b %Y", Time.local(2000, 1, 1)) + end + end + + def test_strptime_with_malformed_string + with_env_tz "US/Eastern" do + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + assert_raise(ArgumentError) { zone.strptime("1999-12-31", "%Y/%m/%d") } + end + end + + def test_strptime_with_timestamp_seconds + with_env_tz "US/Eastern" do + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + time_str = "1470272280" + time = zone.strptime(time_str, "%s") + assert_equal Time.at(1470272280), time + end + end + + def test_strptime_with_timestamp_milliseconds + with_env_tz "US/Eastern" do + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + time_str = "1470272280000" + time = zone.strptime(time_str, "%Q") + assert_equal Time.at(1470272280), time end end def test_utc_offset_lazy_loaded_from_tzinfo_when_not_passed_in_to_initialize - tzinfo = TZInfo::Timezone.get('America/New_York') + 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_equal nil, zone.instance_variable_get("@utc_offset") assert_equal(-18_000, zone.utc_offset) end + def test_utc_offset_is_not_cached_when_current_period_gets_stale + tz = ActiveSupport::TimeZone.create("Moscow") + travel_to(Time.utc(2014, 10, 25, 21)) do # 1 hour before TZ change + assert_equal 14400, tz.utc_offset, "utc_offset should be initialized according to current_period" + end + + travel_to(Time.utc(2014, 10, 25, 22)) do # after TZ change + assert_equal 10800, tz.utc_offset, "utc_offset should not be cached when current_period gets stale" + end + end + def test_seconds_to_utc_offset_with_colon assert_equal "-06:00", ActiveSupport::TimeZone.seconds_to_utc_offset(-21_600) assert_equal "+00:00", ActiveSupport::TimeZone.seconds_to_utc_offset(0) @@ -414,48 +450,48 @@ class TimeZoneTest < ActiveSupport::TestCase end def test_formatted_offset_positive - zone = ActiveSupport::TimeZone['New Delhi'] + zone = ActiveSupport::TimeZone["New Delhi"] assert_equal "+05:30", zone.formatted_offset assert_equal "+0530", zone.formatted_offset(false) end def test_formatted_offset_negative - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] assert_equal "-05:00", zone.formatted_offset assert_equal "-0500", zone.formatted_offset(false) end def test_z_format_strings - zone = ActiveSupport::TimeZone['Tokyo'] + zone = ActiveSupport::TimeZone["Tokyo"] twz = zone.now - assert_equal '+0900', twz.strftime('%z') - assert_equal '+09:00', twz.strftime('%:z') - assert_equal '+09:00:00', twz.strftime('%::z') + assert_equal "+0900", twz.strftime("%z") + assert_equal "+09:00", twz.strftime("%:z") + assert_equal "+09:00:00", twz.strftime("%::z") end def test_formatted_offset_zero - zone = ActiveSupport::TimeZone['London'] + zone = ActiveSupport::TimeZone["London"] assert_equal "+00:00", zone.formatted_offset - assert_equal "UTC", zone.formatted_offset(true, 'UTC') + assert_equal "UTC", zone.formatted_offset(true, "UTC") end def test_zone_compare - zone1 = ActiveSupport::TimeZone['Central Time (US & Canada)'] # offset -0600 - zone2 = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] # offset -0500 + zone1 = ActiveSupport::TimeZone["Central Time (US & Canada)"] # offset -0600 + zone2 = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] # offset -0500 assert zone1 < zone2 assert zone2 > zone1 assert zone1 == zone1 end def test_zone_match - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] assert zone =~ /Eastern/ assert zone =~ /New_York/ assert zone !~ /Nonexistent_Place/ end def test_to_s - assert_equal "(GMT+05:30) New Delhi", ActiveSupport::TimeZone['New Delhi'].to_s + assert_equal "(GMT+05:30) New Delhi", ActiveSupport::TimeZone["New Delhi"].to_s end def test_all_sorted @@ -487,8 +523,13 @@ class TimeZoneTest < ActiveSupport::TestCase end def test_us_zones - assert ActiveSupport::TimeZone.us_zones.include?(ActiveSupport::TimeZone["Hawaii"]) - assert !ActiveSupport::TimeZone.us_zones.include?(ActiveSupport::TimeZone["Kuala Lumpur"]) + assert_includes ActiveSupport::TimeZone.us_zones, ActiveSupport::TimeZone["Hawaii"] + assert_not_includes ActiveSupport::TimeZone.us_zones, ActiveSupport::TimeZone["Kuala Lumpur"] + end + + def test_country_zones + assert_includes ActiveSupport::TimeZone.country_zones("ru"), ActiveSupport::TimeZone["Moscow"] + assert_not_includes ActiveSupport::TimeZone.country_zones(:ru), ActiveSupport::TimeZone["Kuala Lumpur"] end def test_to_yaml diff --git a/activesupport/test/time_zone_test_helpers.rb b/activesupport/test/time_zone_test_helpers.rb index 9632b89d09..e1d615d154 100644 --- a/activesupport/test/time_zone_test_helpers.rb +++ b/activesupport/test/time_zone_test_helpers.rb @@ -7,10 +7,18 @@ module TimeZoneTestHelpers Time.zone = old_tz end - def with_env_tz(new_tz = 'US/Eastern') - old_tz, ENV['TZ'] = ENV['TZ'], new_tz + def with_env_tz(new_tz = "US/Eastern") + old_tz, ENV["TZ"] = ENV["TZ"], new_tz yield ensure - old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') + old_tz ? ENV["TZ"] = old_tz : ENV.delete("TZ") + end + + def with_preserve_timezone(value) + old_preserve_tz = ActiveSupport.to_time_preserves_timezone + ActiveSupport.to_time_preserves_timezone = value + yield + ensure + ActiveSupport.to_time_preserves_timezone = old_preserve_tz end end diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb index 378421fedd..040ddd25fc 100644 --- a/activesupport/test/transliterate_test.rb +++ b/activesupport/test/transliterate_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/inflector/transliterate' +require "abstract_unit" +require "active_support/inflector/transliterate" class TransliterateTest < ActiveSupport::TestCase def test_transliterate_should_not_change_ascii_chars @@ -13,7 +13,7 @@ class TransliterateTest < ActiveSupport::TestCase # create string with range of Unicode's western characters with # diacritics, excluding the division and multiplication signs which for # some reason or other are floating in the middle of all the letters. - string = (0xC0..0x17E).to_a.reject {|c| [0xD7, 0xF7].include?(c)}.pack("U*") + string = (0xC0..0x17E).to_a.reject { |c| [0xD7, 0xF7].include?(c) }.pack("U*") string.each_char do |char| assert_match %r{^[a-zA-Z']*$}, ActiveSupport::Inflector.transliterate(char) end @@ -21,7 +21,7 @@ class TransliterateTest < ActiveSupport::TestCase def test_transliterate_should_work_with_custom_i18n_rules_and_uncomposed_utf8 char = [117, 776].pack("U*") # "ü" as ASCII "u" plus COMBINING DIAERESIS - I18n.backend.store_translations(:de, :i18n => {:transliterate => {:rule => {"ü" => "ue"}}}) + I18n.backend.store_translations(:de, i18n: { transliterate: { rule: { "ü" => "ue" } } }) default_locale, I18n.locale = I18n.locale, :de assert_equal "ue", ActiveSupport::Inflector.transliterate(char) ensure diff --git a/activesupport/test/xml_mini/jdom_engine_test.rb b/activesupport/test/xml_mini/jdom_engine_test.rb index ed4de8aba2..816d57972c 100644 --- a/activesupport/test/xml_mini/jdom_engine_test.rb +++ b/activesupport/test/xml_mini/jdom_engine_test.rb @@ -1,17 +1,16 @@ -if RUBY_PLATFORM =~ /java/ - require 'abstract_unit' - require 'active_support/xml_mini' - require 'active_support/core_ext/hash/conversions' - +if RUBY_PLATFORM.include?("java") + require "abstract_unit" + require "active_support/xml_mini" + require "active_support/core_ext/hash/conversions" class JDOMEngineTest < ActiveSupport::TestCase include ActiveSupport - FILES_DIR = File.dirname(__FILE__) + '/../fixtures/xml' + FILES_DIR = File.dirname(__FILE__) + "/../fixtures/xml" def setup @default_backend = XmlMini.backend - XmlMini.backend = 'JDOM' + XmlMini.backend = "JDOM" end def teardown @@ -19,18 +18,18 @@ if RUBY_PLATFORM =~ /java/ end def test_file_from_xml - hash = Hash.from_xml(<<-eoxml) + hash = Hash.from_xml(<<-eoxml) <blog> <logo type="file" name="logo.png" content_type="image/png"> </logo> </blog> eoxml - assert hash.has_key?('blog') - assert hash['blog'].has_key?('logo') + assert hash.has_key?("blog") + assert hash["blog"].has_key?("logo") - file = hash['blog']['logo'] - assert_equal 'logo.png', file.original_filename - assert_equal 'image/png', file.content_type + file = hash["blog"]["logo"] + assert_equal "logo.png", file.original_filename + assert_equal "image/png", file.content_type end def test_not_allowed_to_expand_entities_to_files @@ -40,7 +39,7 @@ if RUBY_PLATFORM =~ /java/ ]> <member>x&a;</member> EOT - assert_equal 'x', Hash.from_xml(attack_xml)["member"] + assert_equal "x", Hash.from_xml(attack_xml)["member"] end def test_not_allowed_to_expand_parameter_entities_to_files @@ -52,17 +51,16 @@ if RUBY_PLATFORM =~ /java/ <member>x&a;</member> EOT assert_raise Java::OrgXmlSax::SAXParseException do - assert_equal 'x', Hash.from_xml(attack_xml)["member"] + assert_equal "x", Hash.from_xml(attack_xml)["member"] end end - def test_not_allowed_to_load_external_doctypes attack_xml = <<-EOT <!DOCTYPE member SYSTEM "file://#{FILES_DIR}/jdom_doctype.dtd"> <member>x&a;</member> EOT - assert_equal 'x', Hash.from_xml(attack_xml)["member"] + assert_equal "x", Hash.from_xml(attack_xml)["member"] end def test_exception_thrown_on_expansion_attack @@ -86,13 +84,13 @@ if RUBY_PLATFORM =~ /java/ end def test_setting_JDOM_as_backend - XmlMini.backend = 'JDOM' + XmlMini.backend = "JDOM" assert_equal XmlMini_JDOM, XmlMini.backend end def test_blank_returns_empty_hash assert_equal({}, XmlMini.parse(nil)) - assert_equal({}, XmlMini.parse('')) + assert_equal({}, XmlMini.parse("")) end def test_array_type_makes_an_array @@ -178,11 +176,8 @@ if RUBY_PLATFORM =~ /java/ private def assert_equal_rexml(xml) parsed_xml = XmlMini.parse(xml) - hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } + hash = XmlMini.with_backend("REXML") { XmlMini.parse(xml) } assert_equal(hash, parsed_xml) end end - -else - # don't run these test because we aren't running in JRuby end diff --git a/activesupport/test/xml_mini/libxml_engine_test.rb b/activesupport/test/xml_mini/libxml_engine_test.rb index a8df2e1f7b..81b0d3c407 100644 --- a/activesupport/test/xml_mini/libxml_engine_test.rb +++ b/activesupport/test/xml_mini/libxml_engine_test.rb @@ -1,58 +1,58 @@ begin - require 'libxml' + require "libxml" rescue LoadError # Skip libxml tests else -require 'abstract_unit' -require 'active_support/xml_mini' -require 'active_support/core_ext/hash/conversions' + require "abstract_unit" + require "active_support/xml_mini" + require "active_support/core_ext/hash/conversions" -class LibxmlEngineTest < ActiveSupport::TestCase - include ActiveSupport + class LibxmlEngineTest < ActiveSupport::TestCase + include ActiveSupport - def setup - @default_backend = XmlMini.backend - XmlMini.backend = 'LibXML' + def setup + @default_backend = XmlMini.backend + XmlMini.backend = "LibXML" - LibXML::XML::Error.set_handler(&lambda { |error| }) #silence libxml, exceptions will do - end + LibXML::XML::Error.set_handler(&lambda { |error| }) #silence libxml, exceptions will do + end - def teardown - XmlMini.backend = @default_backend - end + def teardown + XmlMini.backend = @default_backend + end - def test_exception_thrown_on_expansion_attack - assert_raise LibXML::XML::Error do - attack_xml = %{<?xml version="1.0" encoding="UTF-8"?> - <!DOCTYPE member [ - <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;"> - <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;"> - <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;"> - <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;"> - <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;"> - <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;"> - <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"> - ]> - <member> - &a; - </member> - } - Hash.from_xml(attack_xml) + def test_exception_thrown_on_expansion_attack + assert_raise LibXML::XML::Error do + attack_xml = %{<?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE member [ + <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;"> + <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;"> + <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;"> + <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;"> + <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;"> + <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;"> + <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"> + ]> + <member> + &a; + </member> + } + Hash.from_xml(attack_xml) + end end - end - def test_setting_libxml_as_backend - XmlMini.backend = 'LibXML' - assert_equal XmlMini_LibXML, XmlMini.backend - end + def test_setting_libxml_as_backend + XmlMini.backend = "LibXML" + assert_equal XmlMini_LibXML, XmlMini.backend + end - def test_blank_returns_empty_hash - assert_equal({}, XmlMini.parse(nil)) - assert_equal({}, XmlMini.parse('')) - end + def test_blank_returns_empty_hash + assert_equal({}, XmlMini.parse(nil)) + assert_equal({}, XmlMini.parse("")) + end - def test_array_type_makes_an_array - assert_equal_rexml(<<-eoxml) + def test_array_type_makes_an_array + assert_equal_rexml(<<-eoxml) <blog> <posts type="array"> <post>a post</post> @@ -60,67 +60,67 @@ class LibxmlEngineTest < ActiveSupport::TestCase </posts> </blog> eoxml - end + end - def test_one_node_document_as_hash - assert_equal_rexml(<<-eoxml) - <products/> - eoxml - end + def test_one_node_document_as_hash + assert_equal_rexml(<<-eoxml) + <products/> + eoxml + end - def test_one_node_with_attributes_document_as_hash - assert_equal_rexml(<<-eoxml) - <products foo="bar"/> - eoxml - end + def test_one_node_with_attributes_document_as_hash + assert_equal_rexml(<<-eoxml) + <products foo="bar"/> + eoxml + end - def test_products_node_with_book_node_as_hash - assert_equal_rexml(<<-eoxml) + def test_products_node_with_book_node_as_hash + assert_equal_rexml(<<-eoxml) <products> <book name="awesome" id="12345" /> </products> eoxml - end + end - def test_products_node_with_two_book_nodes_as_hash - assert_equal_rexml(<<-eoxml) + def test_products_node_with_two_book_nodes_as_hash + assert_equal_rexml(<<-eoxml) <products> <book name="awesome" id="12345" /> <book name="america" id="67890" /> </products> eoxml - end + end - def test_single_node_with_content_as_hash - assert_equal_rexml(<<-eoxml) + def test_single_node_with_content_as_hash + assert_equal_rexml(<<-eoxml) <products> hello world </products> eoxml - end + end - def test_children_with_children - assert_equal_rexml(<<-eoxml) + def test_children_with_children + assert_equal_rexml(<<-eoxml) <root> <products> <book name="america" id="67890" /> </products> </root> eoxml - end + end - def test_children_with_text - assert_equal_rexml(<<-eoxml) + def test_children_with_text + assert_equal_rexml(<<-eoxml) <root> <products> hello everyone </products> </root> eoxml - end + end - def test_children_with_non_adjacent_text - assert_equal_rexml(<<-eoxml) + def test_children_with_non_adjacent_text + assert_equal_rexml(<<-eoxml) <root> good <products> @@ -129,10 +129,10 @@ class LibxmlEngineTest < ActiveSupport::TestCase morning </root> eoxml - end + end - def test_parse_from_io - io = StringIO.new(<<-eoxml) + def test_parse_from_io + io = StringIO.new(<<-eoxml) <root> good <products> @@ -141,31 +141,31 @@ class LibxmlEngineTest < ActiveSupport::TestCase morning </root> eoxml - assert_equal_rexml(io) - end + assert_equal_rexml(io) + end - def test_children_with_simple_cdata - assert_equal_rexml(<<-eoxml) + def test_children_with_simple_cdata + assert_equal_rexml(<<-eoxml) <root> <products> <![CDATA[cdatablock]]> </products> </root> eoxml - end + end - def test_children_with_multiple_cdata - assert_equal_rexml(<<-eoxml) + def test_children_with_multiple_cdata + assert_equal_rexml(<<-eoxml) <root> <products> <![CDATA[cdatablock1]]><![CDATA[cdatablock2]]> </products> </root> eoxml - end + end - def test_children_with_text_and_cdata - assert_equal_rexml(<<-eoxml) + def test_children_with_text_and_cdata + assert_equal_rexml(<<-eoxml) <root> <products> hello <![CDATA[cdatablock]]> @@ -173,32 +173,31 @@ class LibxmlEngineTest < ActiveSupport::TestCase </products> </root> eoxml - end + end - def test_children_with_blank_text - assert_equal_rexml(<<-eoxml) + def test_children_with_blank_text + assert_equal_rexml(<<-eoxml) <root> <products> </products> </root> eoxml - end + end - def test_children_with_blank_text_and_attribute - assert_equal_rexml(<<-eoxml) + def test_children_with_blank_text_and_attribute + assert_equal_rexml(<<-eoxml) <root> <products type="file"> </products> </root> eoxml - end - - - private - def assert_equal_rexml(xml) - parsed_xml = XmlMini.parse(xml) - xml.rewind if xml.respond_to?(:rewind) - hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } - assert_equal(hash, parsed_xml) end -end + + private + def assert_equal_rexml(xml) + parsed_xml = XmlMini.parse(xml) + xml.rewind if xml.respond_to?(:rewind) + hash = XmlMini.with_backend("REXML") { XmlMini.parse(xml) } + assert_equal(hash, parsed_xml) + end + end end diff --git a/activesupport/test/xml_mini/libxmlsax_engine_test.rb b/activesupport/test/xml_mini/libxmlsax_engine_test.rb index d6d90639e2..e25fa2813c 100644 --- a/activesupport/test/xml_mini/libxmlsax_engine_test.rb +++ b/activesupport/test/xml_mini/libxmlsax_engine_test.rb @@ -1,27 +1,27 @@ begin - require 'libxml' + require "libxml" rescue LoadError # Skip libxml tests else -require 'abstract_unit' -require 'active_support/xml_mini' -require 'active_support/core_ext/hash/conversions' + require "abstract_unit" + require "active_support/xml_mini" + require "active_support/core_ext/hash/conversions" -class LibXMLSAXEngineTest < ActiveSupport::TestCase - include ActiveSupport + class LibXMLSAXEngineTest < ActiveSupport::TestCase + include ActiveSupport - def setup - @default_backend = XmlMini.backend - XmlMini.backend = 'LibXMLSAX' - end + def setup + @default_backend = XmlMini.backend + XmlMini.backend = "LibXMLSAX" + end - def teardown - XmlMini.backend = @default_backend - end + def teardown + XmlMini.backend = @default_backend + end - def test_exception_thrown_on_expansion_attack - assert_raise LibXML::XML::Error do - attack_xml = <<-EOT + def test_exception_thrown_on_expansion_attack + assert_raise LibXML::XML::Error do + attack_xml = <<-EOT <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE member [ <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;"> @@ -37,22 +37,22 @@ class LibXMLSAXEngineTest < ActiveSupport::TestCase </member> EOT - Hash.from_xml(attack_xml) + Hash.from_xml(attack_xml) + end end - end - def test_setting_libxml_as_backend - XmlMini.backend = 'LibXMLSAX' - assert_equal XmlMini_LibXMLSAX, XmlMini.backend - end + def test_setting_libxml_as_backend + XmlMini.backend = "LibXMLSAX" + assert_equal XmlMini_LibXMLSAX, XmlMini.backend + end - def test_blank_returns_empty_hash - assert_equal({}, XmlMini.parse(nil)) - assert_equal({}, XmlMini.parse('')) - end + def test_blank_returns_empty_hash + assert_equal({}, XmlMini.parse(nil)) + assert_equal({}, XmlMini.parse("")) + end - def test_array_type_makes_an_array - assert_equal_rexml(<<-eoxml) + def test_array_type_makes_an_array + assert_equal_rexml(<<-eoxml) <blog> <posts type="array"> <post>a post</post> @@ -60,67 +60,67 @@ class LibXMLSAXEngineTest < ActiveSupport::TestCase </posts> </blog> eoxml - end + end - def test_one_node_document_as_hash - assert_equal_rexml(<<-eoxml) - <products/> - eoxml - end + def test_one_node_document_as_hash + assert_equal_rexml(<<-eoxml) + <products/> + eoxml + end - def test_one_node_with_attributes_document_as_hash - assert_equal_rexml(<<-eoxml) - <products foo="bar"/> - eoxml - end + def test_one_node_with_attributes_document_as_hash + assert_equal_rexml(<<-eoxml) + <products foo="bar"/> + eoxml + end - def test_products_node_with_book_node_as_hash - assert_equal_rexml(<<-eoxml) + def test_products_node_with_book_node_as_hash + assert_equal_rexml(<<-eoxml) <products> <book name="awesome" id="12345" /> </products> eoxml - end + end - def test_products_node_with_two_book_nodes_as_hash - assert_equal_rexml(<<-eoxml) + def test_products_node_with_two_book_nodes_as_hash + assert_equal_rexml(<<-eoxml) <products> <book name="awesome" id="12345" /> <book name="america" id="67890" /> </products> eoxml - end + end - def test_single_node_with_content_as_hash - assert_equal_rexml(<<-eoxml) + def test_single_node_with_content_as_hash + assert_equal_rexml(<<-eoxml) <products> hello world </products> eoxml - end + end - def test_children_with_children - assert_equal_rexml(<<-eoxml) + def test_children_with_children + assert_equal_rexml(<<-eoxml) <root> <products> <book name="america" id="67890" /> </products> </root> eoxml - end + end - def test_children_with_text - assert_equal_rexml(<<-eoxml) + def test_children_with_text + assert_equal_rexml(<<-eoxml) <root> <products> hello everyone </products> </root> eoxml - end + end - def test_children_with_non_adjacent_text - assert_equal_rexml(<<-eoxml) + def test_children_with_non_adjacent_text + assert_equal_rexml(<<-eoxml) <root> good <products> @@ -129,10 +129,10 @@ class LibXMLSAXEngineTest < ActiveSupport::TestCase morning </root> eoxml - end + end - def test_parse_from_io - io = StringIO.new(<<-eoxml) + def test_parse_from_io + io = StringIO.new(<<-eoxml) <root> good <products> @@ -141,31 +141,31 @@ class LibXMLSAXEngineTest < ActiveSupport::TestCase morning </root> eoxml - assert_equal_rexml(io) - end + assert_equal_rexml(io) + end - def test_children_with_simple_cdata - assert_equal_rexml(<<-eoxml) + def test_children_with_simple_cdata + assert_equal_rexml(<<-eoxml) <root> <products> <![CDATA[cdatablock]]> </products> </root> eoxml - end + end - def test_children_with_multiple_cdata - assert_equal_rexml(<<-eoxml) + def test_children_with_multiple_cdata + assert_equal_rexml(<<-eoxml) <root> <products> <![CDATA[cdatablock1]]><![CDATA[cdatablock2]]> </products> </root> eoxml - end + end - def test_children_with_text_and_cdata - assert_equal_rexml(<<-eoxml) + def test_children_with_text_and_cdata + assert_equal_rexml(<<-eoxml) <root> <products> hello <![CDATA[cdatablock]]> @@ -173,23 +173,23 @@ class LibXMLSAXEngineTest < ActiveSupport::TestCase </products> </root> eoxml - end + end - def test_children_with_blank_text - assert_equal_rexml(<<-eoxml) + def test_children_with_blank_text + assert_equal_rexml(<<-eoxml) <root> <products> </products> </root> eoxml - end - - private - def assert_equal_rexml(xml) - parsed_xml = XmlMini.parse(xml) - xml.rewind if xml.respond_to?(:rewind) - hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } - assert_equal(hash, parsed_xml) end -end + + private + def assert_equal_rexml(xml) + parsed_xml = XmlMini.parse(xml) + xml.rewind if xml.respond_to?(:rewind) + hash = XmlMini.with_backend("REXML") { XmlMini.parse(xml) } + assert_equal(hash, parsed_xml) + end + end end diff --git a/activesupport/test/xml_mini/nokogiri_engine_test.rb b/activesupport/test/xml_mini/nokogiri_engine_test.rb index 1314c9065a..44b82da4e4 100644 --- a/activesupport/test/xml_mini/nokogiri_engine_test.rb +++ b/activesupport/test/xml_mini/nokogiri_engine_test.rb @@ -1,40 +1,40 @@ begin - require 'nokogiri' + require "nokogiri" rescue LoadError # Skip nokogiri tests else -require 'abstract_unit' -require 'active_support/xml_mini' -require 'active_support/core_ext/hash/conversions' - -class NokogiriEngineTest < ActiveSupport::TestCase - def setup - @default_backend = ActiveSupport::XmlMini.backend - ActiveSupport::XmlMini.backend = 'Nokogiri' - end + require "abstract_unit" + require "active_support/xml_mini" + require "active_support/core_ext/hash/conversions" + + class NokogiriEngineTest < ActiveSupport::TestCase + def setup + @default_backend = ActiveSupport::XmlMini.backend + ActiveSupport::XmlMini.backend = "Nokogiri" + end - def teardown - ActiveSupport::XmlMini.backend = @default_backend - end + def teardown + ActiveSupport::XmlMini.backend = @default_backend + end - def test_file_from_xml - hash = Hash.from_xml(<<-eoxml) + def test_file_from_xml + hash = Hash.from_xml(<<-eoxml) <blog> <logo type="file" name="logo.png" content_type="image/png"> </logo> </blog> eoxml - assert hash.has_key?('blog') - assert hash['blog'].has_key?('logo') + assert hash.has_key?("blog") + assert hash["blog"].has_key?("logo") - file = hash['blog']['logo'] - assert_equal 'logo.png', file.original_filename - assert_equal 'image/png', file.content_type - end + file = hash["blog"]["logo"] + assert_equal "logo.png", file.original_filename + assert_equal "image/png", file.content_type + end - def test_exception_thrown_on_expansion_attack - assert_raise Nokogiri::XML::SyntaxError do - attack_xml = <<-EOT + def test_exception_thrown_on_expansion_attack + assert_raise Nokogiri::XML::SyntaxError do + attack_xml = <<-EOT <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE member [ <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;"> @@ -49,22 +49,22 @@ class NokogiriEngineTest < ActiveSupport::TestCase &a; </member> EOT - Hash.from_xml(attack_xml) + Hash.from_xml(attack_xml) + end end - end - def test_setting_nokogiri_as_backend - ActiveSupport::XmlMini.backend = 'Nokogiri' - assert_equal ActiveSupport::XmlMini_Nokogiri, ActiveSupport::XmlMini.backend - end + def test_setting_nokogiri_as_backend + ActiveSupport::XmlMini.backend = "Nokogiri" + assert_equal ActiveSupport::XmlMini_Nokogiri, ActiveSupport::XmlMini.backend + end - def test_blank_returns_empty_hash - assert_equal({}, ActiveSupport::XmlMini.parse(nil)) - assert_equal({}, ActiveSupport::XmlMini.parse('')) - end + def test_blank_returns_empty_hash + assert_equal({}, ActiveSupport::XmlMini.parse(nil)) + assert_equal({}, ActiveSupport::XmlMini.parse("")) + end - def test_array_type_makes_an_array - assert_equal_rexml(<<-eoxml) + def test_array_type_makes_an_array + assert_equal_rexml(<<-eoxml) <blog> <posts type="array"> <post>a post</post> @@ -72,67 +72,67 @@ class NokogiriEngineTest < ActiveSupport::TestCase </posts> </blog> eoxml - end + end - def test_one_node_document_as_hash - assert_equal_rexml(<<-eoxml) - <products/> - eoxml - end + def test_one_node_document_as_hash + assert_equal_rexml(<<-eoxml) + <products/> + eoxml + end - def test_one_node_with_attributes_document_as_hash - assert_equal_rexml(<<-eoxml) - <products foo="bar"/> - eoxml - end + def test_one_node_with_attributes_document_as_hash + assert_equal_rexml(<<-eoxml) + <products foo="bar"/> + eoxml + end - def test_products_node_with_book_node_as_hash - assert_equal_rexml(<<-eoxml) + def test_products_node_with_book_node_as_hash + assert_equal_rexml(<<-eoxml) <products> <book name="awesome" id="12345" /> </products> eoxml - end + end - def test_products_node_with_two_book_nodes_as_hash - assert_equal_rexml(<<-eoxml) + def test_products_node_with_two_book_nodes_as_hash + assert_equal_rexml(<<-eoxml) <products> <book name="awesome" id="12345" /> <book name="america" id="67890" /> </products> eoxml - end + end - def test_single_node_with_content_as_hash - assert_equal_rexml(<<-eoxml) + def test_single_node_with_content_as_hash + assert_equal_rexml(<<-eoxml) <products> hello world </products> eoxml - end + end - def test_children_with_children - assert_equal_rexml(<<-eoxml) + def test_children_with_children + assert_equal_rexml(<<-eoxml) <root> <products> <book name="america" id="67890" /> </products> </root> eoxml - end + end - def test_children_with_text - assert_equal_rexml(<<-eoxml) + def test_children_with_text + assert_equal_rexml(<<-eoxml) <root> <products> hello everyone </products> </root> eoxml - end + end - def test_children_with_non_adjacent_text - assert_equal_rexml(<<-eoxml) + def test_children_with_non_adjacent_text + assert_equal_rexml(<<-eoxml) <root> good <products> @@ -141,10 +141,10 @@ class NokogiriEngineTest < ActiveSupport::TestCase morning </root> eoxml - end + end - def test_parse_from_io - io = StringIO.new(<<-eoxml) + def test_parse_from_io + io = StringIO.new(<<-eoxml) <root> good <products> @@ -153,31 +153,31 @@ class NokogiriEngineTest < ActiveSupport::TestCase morning </root> eoxml - assert_equal_rexml(io) - end + assert_equal_rexml(io) + end - def test_children_with_simple_cdata - assert_equal_rexml(<<-eoxml) + def test_children_with_simple_cdata + assert_equal_rexml(<<-eoxml) <root> <products> <![CDATA[cdatablock]]> </products> </root> eoxml - end + end - def test_children_with_multiple_cdata - assert_equal_rexml(<<-eoxml) + def test_children_with_multiple_cdata + assert_equal_rexml(<<-eoxml) <root> <products> <![CDATA[cdatablock1]]><![CDATA[cdatablock2]]> </products> </root> eoxml - end + end - def test_children_with_text_and_cdata - assert_equal_rexml(<<-eoxml) + def test_children_with_text_and_cdata + assert_equal_rexml(<<-eoxml) <root> <products> hello <![CDATA[cdatablock]]> @@ -185,31 +185,31 @@ class NokogiriEngineTest < ActiveSupport::TestCase </products> </root> eoxml - end + end - def test_children_with_blank_text - assert_equal_rexml(<<-eoxml) + def test_children_with_blank_text + assert_equal_rexml(<<-eoxml) <root> <products> </products> </root> eoxml - end + end - def test_children_with_blank_text_and_attribute - assert_equal_rexml(<<-eoxml) + def test_children_with_blank_text_and_attribute + assert_equal_rexml(<<-eoxml) <root> <products type="file"> </products> </root> eoxml - end - - private - def assert_equal_rexml(xml) - parsed_xml = ActiveSupport::XmlMini.parse(xml) - xml.rewind if xml.respond_to?(:rewind) - hash = ActiveSupport::XmlMini.with_backend('REXML') { ActiveSupport::XmlMini.parse(xml) } - assert_equal(hash, parsed_xml) end -end + + private + def assert_equal_rexml(xml) + parsed_xml = ActiveSupport::XmlMini.parse(xml) + xml.rewind if xml.respond_to?(:rewind) + hash = ActiveSupport::XmlMini.with_backend("REXML") { ActiveSupport::XmlMini.parse(xml) } + assert_equal(hash, parsed_xml) + end + end end diff --git a/activesupport/test/xml_mini/nokogirisax_engine_test.rb b/activesupport/test/xml_mini/nokogirisax_engine_test.rb index 7978a50921..24b35cadf6 100644 --- a/activesupport/test/xml_mini/nokogirisax_engine_test.rb +++ b/activesupport/test/xml_mini/nokogirisax_engine_test.rb @@ -1,40 +1,40 @@ begin - require 'nokogiri' + require "nokogiri" rescue LoadError # Skip nokogiri tests else -require 'abstract_unit' -require 'active_support/xml_mini' -require 'active_support/core_ext/hash/conversions' - -class NokogiriSAXEngineTest < ActiveSupport::TestCase - def setup - @default_backend = ActiveSupport::XmlMini.backend - ActiveSupport::XmlMini.backend = 'NokogiriSAX' - end + require "abstract_unit" + require "active_support/xml_mini" + require "active_support/core_ext/hash/conversions" + + class NokogiriSAXEngineTest < ActiveSupport::TestCase + def setup + @default_backend = ActiveSupport::XmlMini.backend + ActiveSupport::XmlMini.backend = "NokogiriSAX" + end - def teardown - ActiveSupport::XmlMini.backend = @default_backend - end + def teardown + ActiveSupport::XmlMini.backend = @default_backend + end - def test_file_from_xml - hash = Hash.from_xml(<<-eoxml) + def test_file_from_xml + hash = Hash.from_xml(<<-eoxml) <blog> <logo type="file" name="logo.png" content_type="image/png"> </logo> </blog> eoxml - assert hash.has_key?('blog') - assert hash['blog'].has_key?('logo') + assert hash.has_key?("blog") + assert hash["blog"].has_key?("logo") - file = hash['blog']['logo'] - assert_equal 'logo.png', file.original_filename - assert_equal 'image/png', file.content_type - end + file = hash["blog"]["logo"] + assert_equal "logo.png", file.original_filename + assert_equal "image/png", file.content_type + end - def test_exception_thrown_on_expansion_attack - assert_raise RuntimeError do - attack_xml = <<-EOT + def test_exception_thrown_on_expansion_attack + assert_raise RuntimeError do + attack_xml = <<-EOT <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE member [ <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;"> @@ -50,22 +50,22 @@ class NokogiriSAXEngineTest < ActiveSupport::TestCase </member> EOT - Hash.from_xml(attack_xml) + Hash.from_xml(attack_xml) + end end - end - def test_setting_nokogirisax_as_backend - ActiveSupport::XmlMini.backend = 'NokogiriSAX' - assert_equal ActiveSupport::XmlMini_NokogiriSAX, ActiveSupport::XmlMini.backend - end + def test_setting_nokogirisax_as_backend + ActiveSupport::XmlMini.backend = "NokogiriSAX" + assert_equal ActiveSupport::XmlMini_NokogiriSAX, ActiveSupport::XmlMini.backend + end - def test_blank_returns_empty_hash - assert_equal({}, ActiveSupport::XmlMini.parse(nil)) - assert_equal({}, ActiveSupport::XmlMini.parse('')) - end + def test_blank_returns_empty_hash + assert_equal({}, ActiveSupport::XmlMini.parse(nil)) + assert_equal({}, ActiveSupport::XmlMini.parse("")) + end - def test_array_type_makes_an_array - assert_equal_rexml(<<-eoxml) + def test_array_type_makes_an_array + assert_equal_rexml(<<-eoxml) <blog> <posts type="array"> <post>a post</post> @@ -73,67 +73,67 @@ class NokogiriSAXEngineTest < ActiveSupport::TestCase </posts> </blog> eoxml - end + end - def test_one_node_document_as_hash - assert_equal_rexml(<<-eoxml) - <products/> - eoxml - end + def test_one_node_document_as_hash + assert_equal_rexml(<<-eoxml) + <products/> + eoxml + end - def test_one_node_with_attributes_document_as_hash - assert_equal_rexml(<<-eoxml) - <products foo="bar"/> - eoxml - end + def test_one_node_with_attributes_document_as_hash + assert_equal_rexml(<<-eoxml) + <products foo="bar"/> + eoxml + end - def test_products_node_with_book_node_as_hash - assert_equal_rexml(<<-eoxml) + def test_products_node_with_book_node_as_hash + assert_equal_rexml(<<-eoxml) <products> <book name="awesome" id="12345" /> </products> eoxml - end + end - def test_products_node_with_two_book_nodes_as_hash - assert_equal_rexml(<<-eoxml) + def test_products_node_with_two_book_nodes_as_hash + assert_equal_rexml(<<-eoxml) <products> <book name="awesome" id="12345" /> <book name="america" id="67890" /> </products> eoxml - end + end - def test_single_node_with_content_as_hash - assert_equal_rexml(<<-eoxml) + def test_single_node_with_content_as_hash + assert_equal_rexml(<<-eoxml) <products> hello world </products> eoxml - end + end - def test_children_with_children - assert_equal_rexml(<<-eoxml) + def test_children_with_children + assert_equal_rexml(<<-eoxml) <root> <products> <book name="america" id="67890" /> </products> </root> eoxml - end + end - def test_children_with_text - assert_equal_rexml(<<-eoxml) + def test_children_with_text + assert_equal_rexml(<<-eoxml) <root> <products> hello everyone </products> </root> eoxml - end + end - def test_children_with_non_adjacent_text - assert_equal_rexml(<<-eoxml) + def test_children_with_non_adjacent_text + assert_equal_rexml(<<-eoxml) <root> good <products> @@ -142,10 +142,10 @@ class NokogiriSAXEngineTest < ActiveSupport::TestCase morning </root> eoxml - end + end - def test_parse_from_io - io = StringIO.new(<<-eoxml) + def test_parse_from_io + io = StringIO.new(<<-eoxml) <root> good <products> @@ -154,31 +154,31 @@ class NokogiriSAXEngineTest < ActiveSupport::TestCase morning </root> eoxml - assert_equal_rexml(io) - end + assert_equal_rexml(io) + end - def test_children_with_simple_cdata - assert_equal_rexml(<<-eoxml) + def test_children_with_simple_cdata + assert_equal_rexml(<<-eoxml) <root> <products> <![CDATA[cdatablock]]> </products> </root> eoxml - end + end - def test_children_with_multiple_cdata - assert_equal_rexml(<<-eoxml) + def test_children_with_multiple_cdata + assert_equal_rexml(<<-eoxml) <root> <products> <![CDATA[cdatablock1]]><![CDATA[cdatablock2]]> </products> </root> eoxml - end + end - def test_children_with_text_and_cdata - assert_equal_rexml(<<-eoxml) + def test_children_with_text_and_cdata + assert_equal_rexml(<<-eoxml) <root> <products> hello <![CDATA[cdatablock]]> @@ -186,31 +186,31 @@ class NokogiriSAXEngineTest < ActiveSupport::TestCase </products> </root> eoxml - end + end - def test_children_with_blank_text - assert_equal_rexml(<<-eoxml) + def test_children_with_blank_text + assert_equal_rexml(<<-eoxml) <root> <products> </products> </root> eoxml - end + end - def test_children_with_blank_text_and_attribute - assert_equal_rexml(<<-eoxml) + def test_children_with_blank_text_and_attribute + assert_equal_rexml(<<-eoxml) <root> <products type="file"> </products> </root> eoxml - end - - private - def assert_equal_rexml(xml) - parsed_xml = ActiveSupport::XmlMini.parse(xml) - xml.rewind if xml.respond_to?(:rewind) - hash = ActiveSupport::XmlMini.with_backend('REXML') { ActiveSupport::XmlMini.parse(xml) } - assert_equal(hash, parsed_xml) end -end + + private + def assert_equal_rexml(xml) + parsed_xml = ActiveSupport::XmlMini.parse(xml) + xml.rewind if xml.respond_to?(:rewind) + hash = ActiveSupport::XmlMini.with_backend("REXML") { ActiveSupport::XmlMini.parse(xml) } + assert_equal(hash, parsed_xml) + end + end end diff --git a/activesupport/test/xml_mini/rexml_engine_test.rb b/activesupport/test/xml_mini/rexml_engine_test.rb index f0067ca656..dc62f3f671 100644 --- a/activesupport/test/xml_mini/rexml_engine_test.rb +++ b/activesupport/test/xml_mini/rexml_engine_test.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'active_support/xml_mini' +require "abstract_unit" +require "active_support/xml_mini" class REXMLEngineTest < ActiveSupport::TestCase def test_default_is_rexml @@ -7,12 +7,12 @@ class REXMLEngineTest < ActiveSupport::TestCase end def test_set_rexml_as_backend - ActiveSupport::XmlMini.backend = 'REXML' + ActiveSupport::XmlMini.backend = "REXML" assert_equal ActiveSupport::XmlMini_REXML, ActiveSupport::XmlMini.backend end def test_parse_from_io - ActiveSupport::XmlMini.backend = 'REXML' + ActiveSupport::XmlMini.backend = "REXML" io = StringIO.new(<<-eoxml) <root> good @@ -22,14 +22,23 @@ class REXMLEngineTest < ActiveSupport::TestCase morning </root> eoxml - assert_equal_rexml(io) + hash = ActiveSupport::XmlMini.parse(io) + assert hash.has_key?("root") + assert hash["root"].has_key?("products") + assert_match "good", hash["root"]["__content__"] + products = hash["root"]["products"] + assert products.has_key?("__content__") + assert_match "hello everyone", products["__content__"] end - private - def assert_equal_rexml(xml) - parsed_xml = ActiveSupport::XmlMini.parse(xml) - xml.rewind if xml.respond_to?(:rewind) - hash = ActiveSupport::XmlMini.with_backend('REXML') { ActiveSupport::XmlMini.parse(xml) } - assert_equal(hash, parsed_xml) - end + def test_parse_from_empty_string + ActiveSupport::XmlMini.backend = "REXML" + assert_equal({}, ActiveSupport::XmlMini.parse("")) + end + + def test_parse_from_frozen_string + ActiveSupport::XmlMini.backend = "REXML" + xml_string = "<root></root>".freeze + assert_equal({ "root" => {} }, ActiveSupport::XmlMini.parse(xml_string)) + end end diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb index 55e8181b54..b15ccfb764 100644 --- a/activesupport/test/xml_mini_test.rb +++ b/activesupport/test/xml_mini_test.rb @@ -1,9 +1,9 @@ -require 'abstract_unit' -require 'active_support/xml_mini' -require 'active_support/builder' -require 'active_support/core_ext/hash' -require 'active_support/core_ext/big_decimal' -require 'yaml' +require "abstract_unit" +require "active_support/xml_mini" +require "active_support/builder" +require "active_support/core_ext/hash" +require "active_support/core_ext/big_decimal" +require "yaml" module XmlMiniTest class RenameKeyTest < ActiveSupport::TestCase @@ -12,23 +12,23 @@ module XmlMiniTest end def test_rename_key_dasherizes_with_dasherize_true - assert_equal "my-key", ActiveSupport::XmlMini.rename_key("my_key", :dasherize => true) + assert_equal "my-key", ActiveSupport::XmlMini.rename_key("my_key", dasherize: true) end def test_rename_key_does_nothing_with_dasherize_false - assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :dasherize => false) + assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", dasherize: false) end def test_rename_key_camelizes_with_camelize_true - assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => true) + assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", camelize: true) end def test_rename_key_lower_camelizes_with_camelize_lower - assert_equal "myKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => :lower) + assert_equal "myKey", ActiveSupport::XmlMini.rename_key("my_key", camelize: :lower) end def test_rename_key_lower_camelizes_with_camelize_upper - assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => :upper) + assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", camelize: :upper) end def test_rename_key_does_not_dasherize_leading_underscores @@ -63,16 +63,16 @@ module XmlMiniTest def setup @xml = ActiveSupport::XmlMini - @options = {:skip_instruct => true, :builder => Builder::XmlMarkup.new} + @options = { skip_instruct: true, builder: Builder::XmlMarkup.new } end test "#to_tag accepts a callable object and passes options with the builder" do - @xml.to_tag(:some_tag, lambda {|o| o[:builder].br }, @options) + @xml.to_tag(:some_tag, lambda { |o| o[:builder].br }, @options) assert_xml "<br/>" end test "#to_tag accepts a callable object and passes options and tag name" do - @xml.to_tag(:tag, lambda {|o, t| o[:builder].b(t) }, @options) + @xml.to_tag(:tag, lambda { |o, t| o[:builder].b(t) }, @options) assert_xml "<b>tag</b>" end @@ -92,7 +92,7 @@ module XmlMiniTest end test "#to_tag should use the type value in the options hash" do - @xml.to_tag(:b, "blue", @options.merge(type: 'color')) + @xml.to_tag(:b, "blue", @options.merge(type: "color")) assert_xml( "<b type=\"color\">blue</b>" ) end @@ -122,7 +122,7 @@ module XmlMiniTest end test "#to_tag accepts datetime types" do - @xml.to_tag(:b, DateTime.new(2001,2,3,4,5,6,'+7'), @options) + @xml.to_tag(:b, DateTime.new(2001,2,3,4,5,6,"+7"), @options) assert_xml( "<b type=\"dateTime\">2001-02-03T04:05:06+07:00</b>") end @@ -233,22 +233,22 @@ module XmlMiniTest end def test_symbol - parser = @parsing['symbol'] - assert_equal :symbol, parser.call('symbol') + parser = @parsing["symbol"] + assert_equal :symbol, parser.call("symbol") assert_equal :symbol, parser.call(:symbol) assert_equal :'123', parser.call(123) assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) } end def test_date - parser = @parsing['date'] + parser = @parsing["date"] assert_equal Date.new(2013,11,12), parser.call("2013-11-12T0211Z") assert_raises(TypeError) { parser.call(1384190018) } assert_raises(ArgumentError) { parser.call("not really a date") } end def test_datetime - parser = @parsing['datetime'] + parser = @parsing["datetime"] assert_equal Time.new(2013,11,12,02,11,00,0), parser.call("2013-11-12T02:11:00Z") assert_equal DateTime.new(2013,11,12), parser.call("2013-11-12T0211Z") assert_equal DateTime.new(2013,11,12,02,11), parser.call("2013-11-12T02:11Z") @@ -257,7 +257,7 @@ module XmlMiniTest end def test_integer - parser = @parsing['integer'] + parser = @parsing["integer"] assert_equal 123, parser.call(123) assert_equal 123, parser.call(123.003) assert_equal 123, parser.call("123") @@ -266,7 +266,7 @@ module XmlMiniTest end def test_float - parser = @parsing['float'] + parser = @parsing["float"] assert_equal 123, parser.call("123") assert_equal 123.003, parser.call("123.003") assert_equal 123.0, parser.call("123,003") @@ -277,7 +277,7 @@ module XmlMiniTest end def test_decimal - parser = @parsing['decimal'] + parser = @parsing["decimal"] assert_equal 123, parser.call("123") assert_equal 123.003, parser.call("123.003") assert_equal 123.0, parser.call("123,003") @@ -288,7 +288,7 @@ module XmlMiniTest end def test_boolean - parser = @parsing['boolean'] + parser = @parsing["boolean"] [1, true, "1"].each do |value| assert parser.call(value) end @@ -299,7 +299,7 @@ module XmlMiniTest end def test_string - parser = @parsing['string'] + parser = @parsing["string"] assert_equal "123", parser.call(123) assert_equal "123", parser.call("123") assert_equal "[]", parser.call("[]") @@ -317,13 +317,13 @@ product: YAML expected = { "product"=> [ - {"sku"=>"BL394D", "quantity"=>4, "description"=>"Basketball"} + { "sku"=>"BL394D", "quantity"=>4, "description"=>"Basketball" } ] } - parser = @parsing['yaml'] + parser = @parsing["yaml"] assert_equal(expected, parser.call(yaml)) - assert_equal({1 => 'test'}, parser.call({1 => 'test'})) - assert_equal({"1 => 'test'"=>nil}, parser.call("{1 => 'test'}")) + assert_equal({ 1 => "test" }, parser.call(1 => "test")) + assert_equal({ "1 => 'test'"=>nil }, parser.call("{1 => 'test'}")) end def test_base64Binary_and_binary @@ -341,12 +341,12 @@ in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure. EXPECTED - parser = @parsing['base64Binary'] + parser = @parsing["base64Binary"] assert_equal expected_base64.gsub(/\n/," ").strip, parser.call(base64) parser.call("NON BASE64 INPUT") - parser = @parsing['binary'] - assert_equal expected_base64.gsub(/\n/," ").strip, parser.call(base64, 'encoding' => 'base64') + parser = @parsing["binary"] + assert_equal expected_base64.gsub(/\n/," ").strip, parser.call(base64, "encoding" => "base64") assert_equal "IGNORED INPUT", parser.call("IGNORED INPUT", {}) end end diff --git a/ci/travis.rb b/ci/travis.rb index 063c6acb07..1d658bae47 100755 --- a/ci/travis.rb +++ b/ci/travis.rb @@ -1,5 +1,5 @@ #!/usr/bin/env ruby -require 'fileutils' +require "fileutils" include FileUtils commands = [ @@ -15,16 +15,16 @@ end class Build MAP = { - 'railties' => 'railties', - 'ap' => 'actionpack', - 'am' => 'actionmailer', - 'amo' => 'activemodel', - 'as' => 'activesupport', - 'ar' => 'activerecord', - 'av' => 'actionview', - 'aj' => 'activejob', - 'ac' => 'actioncable', - 'guides' => 'guides' + "railties" => "railties", + "ap" => "actionpack", + "am" => "actionmailer", + "amo" => "activemodel", + "as" => "activesupport", + "ar" => "activerecord", + "av" => "actionview", + "aj" => "activejob", + "ac" => "actioncable", + "guides" => "guides" } attr_reader :component, :options @@ -55,41 +55,41 @@ class Build heading << "with #{adapter}" if activerecord? heading << "in isolation" if isolated? heading << "integration" if integration? - heading.join(' ') + heading.join(" ") end def tasks if activerecord? tasks = ["#{adapter}:#{'isolated_' if isolated?}test"] case adapter - when 'mysql2' - tasks.unshift 'db:mysql:rebuild' - when 'postgresql' - tasks.unshift 'db:postgresql:rebuild' + when "mysql2" + tasks.unshift "db:mysql:rebuild" + when "postgresql" + tasks.unshift "db:postgresql:rebuild" end tasks else - ["test", ('isolated' if isolated?), ('integration' if integration?)].compact.join(":") + ["test", ("isolated" if isolated?), ("integration" if integration?)].compact.join(":") end end def key key = [gem] key << adapter if activerecord? - key << 'isolated' if isolated? - key.join(':') + key << "isolated" if isolated? + key.join(":") end def activesupport? - gem == 'activesupport' + gem == "activesupport" end def activerecord? - gem == 'activerecord' + gem == "activerecord" end def guides? - gem == 'guides' + gem == "guides" end def isolated? @@ -97,16 +97,16 @@ class Build end def integration? - component.split(':').last == 'integration' + component.split(":").last == "integration" end def gem - MAP[component.split(':').first] + MAP[component.split(":").first] end alias :dir :gem def adapter - component.split(':').last + component.split(":").last end def rake(*tasks) @@ -120,38 +120,39 @@ class Build def env if activesupport? && !isolated? - # There is a known issue with the listen tests that casuses files to be - # incorrectly GC'ed even when they are still in-use. The current is to - # only run them in isolation to avoid randomly failing our test suite. - { 'LISTEN' => '0' } + # There is a known issue with the listen tests that causes files to be + # incorrectly GC'ed even when they are still in-use. The current solution + # is to only run them in isolation to avoid randomly failing our test suite. + { "LISTEN" => "0" } else {} end end def run_bug_report_templates - Dir.glob('bug_report_templates/*.rb').all? do |file| - system(Gem.ruby, '-w', file) + Dir.glob("bug_report_templates/*.rb").all? do |file| + system(Gem.ruby, "-w", file) end end end -if ENV['GEM']=='aj:integration' - ENV['QC_DATABASE_URL'] = 'postgres://postgres@localhost/active_jobs_qc_int_test' - ENV['QUE_DATABASE_URL'] = 'postgres://postgres@localhost/active_jobs_que_int_test' +if ENV["GEM"]=="aj:integration" + ENV["QC_DATABASE_URL"] = "postgres://postgres@localhost/active_jobs_qc_int_test" + ENV["QUE_DATABASE_URL"] = "postgres://postgres@localhost/active_jobs_que_int_test" end results = {} -ENV['GEM'].split(',').each do |gem| +ENV["GEM"].split(",").each do |gem| [false, true].each do |isolated| - next if ENV['TRAVIS_PULL_REQUEST'] && ENV['TRAVIS_PULL_REQUEST'] != 'false' && isolated - next if gem == 'railties' && isolated - next if gem == 'ac' && isolated - next if gem == 'aj:integration' && isolated - next if gem == 'guides' && isolated - - build = Build.new(gem, :isolated => isolated) + next if ENV["TRAVIS_PULL_REQUEST"] && ENV["TRAVIS_PULL_REQUEST"] != "false" && isolated + next if gem == "railties" && isolated + next if gem == "ac" && isolated + next if gem == "ac:integration" && isolated + next if gem == "aj:integration" && isolated + next if gem == "guides" && isolated + + build = Build.new(gem, isolated: isolated) results[build.key] = build.run! end diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md index d58016053b..2730d2dfea 100644 --- a/guides/CHANGELOG.md +++ b/guides/CHANGELOG.md @@ -1,28 +1,2 @@ -## Rails 5.0.0.beta2 (February 01, 2016) ## -* No changes. - - -## Rails 5.0.0.beta1 (December 18, 2015) ## - -* Add code of conduct to contributing guide - - *Jon Moss* - -* New section in Configuring: Configuring Active Job - - *Eliot Sykes* - -* New section in Active Record Association Basics: Single Table Inheritance - - *Andrey Nering* - -* New section in Active Record Querying: Understanding The Method Chaining - - *Andrey Nering* - -* New section in Configuring: Search Engines Indexing - - *Andrey Nering* - -Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/guides/CHANGELOG.md) for previous changes. +Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/guides/CHANGELOG.md) for previous changes. diff --git a/guides/Rakefile b/guides/Rakefile index 00577377d7..bf501f6a64 100644 --- a/guides/Rakefile +++ b/guides/Rakefile @@ -1,7 +1,7 @@ namespace :guides do desc 'Generate guides (for authors), use ONLY=foo to process just "foo.md"' - task :generate => 'generate:html' + task generate: "generate:html" namespace :generate do @@ -13,14 +13,14 @@ 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 0.1.1/ + unless `kindlerb -v 2> /dev/null` =~ /kindlerb 1.0.1/ abort "Please `gem install kindlerb` and make sure you have `kindlegen` in your PATH" end - unless `convert` =~ /convert/ + unless `convert` =~ /convert/ abort "Please install ImageMagick`" end - ENV['KINDLE'] = '1' - Rake::Task['guides:generate:html'].invoke + ENV["KINDLE"] = "1" + Rake::Task["guides:generate:html"].invoke end end @@ -76,4 +76,4 @@ Examples: end end -task :default => 'guides:help' +task default: "guides:help" diff --git a/guides/assets/images/favicon.ico b/guides/assets/images/favicon.ico Binary files differindex faa10b4580..87192a8a07 100644 --- a/guides/assets/images/favicon.ico +++ b/guides/assets/images/favicon.ico diff --git a/guides/assets/images/getting_started/template_is_missing_articles_new.png b/guides/assets/images/getting_started/template_is_missing_articles_new.png Binary files differindex 4e636d09ff..f4f054f3c6 100644 --- a/guides/assets/images/getting_started/template_is_missing_articles_new.png +++ b/guides/assets/images/getting_started/template_is_missing_articles_new.png diff --git a/guides/assets/javascripts/guides.js b/guides/assets/javascripts/guides.js index a9c7f0d016..e4d25dfb21 100644 --- a/guides/assets/javascripts/guides.js +++ b/guides/assets/javascripts/guides.js @@ -51,9 +51,3 @@ var guidesIndex = { window.location = url; } }; - -// Disable autolink inside example code blocks of guides. -$(document).ready(function() { - SyntaxHighlighter.defaults['auto-links'] = false; - SyntaxHighlighter.all(); -}); diff --git a/guides/assets/javascripts/syntaxhighlighter.js b/guides/assets/javascripts/syntaxhighlighter.js new file mode 100644 index 0000000000..584aaed716 --- /dev/null +++ b/guides/assets/javascripts/syntaxhighlighter.js @@ -0,0 +1,20 @@ +/*! + * SyntaxHighlighter + * https://github.com/syntaxhighlighter/syntaxhighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 4.0.1 (Sun, 03 Jul 2016 06:45:54 GMT) + * + * @copyright + * Copyright (C) 2004-2016 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ + + +!function(e){function t(r){if(n[r])return n[r].exports;var i=n[r]={exports:{},id:r,loaded:!1};return e[r].call(i.exports,i,i.exports,t),i.loaded=!0,i.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";function r(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t["default"]=e,t}function i(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(1);Object.keys(a).forEach(function(e){"default"!==e&&Object.defineProperty(t,e,{enumerable:!0,get:function(){return a[e]}})});var s=n(28),o=i(s),l=i(a),u=n(29),c=r(u);n(30),(0,o["default"])(function(){return l["default"].highlight(c.object(window.syntaxhighlighterConfig||{}))})},function(e,t,n){"use strict";function r(e){window.alert("SyntaxHighlighter\n\n"+e)}function i(e,t){var n=h.vars.discoveredBrushes,i=null;if(null==n){n={};for(var a in h.brushes){var s=h.brushes[a],o=s.aliases;if(null!=o){s.className=s.className||s.aliases[0],s.brushName=s.className||a.toLowerCase();for(var l=0,u=o.length;u>l;l++)n[o[l]]=a}}h.vars.discoveredBrushes=n}return i=h.brushes[n[e]],null==i&&t&&r(h.config.strings.noBrush+e),i}function a(e){var t="<![CDATA[",n="]]>",r=u.trim(e),i=!1,a=t.length,s=n.length;0==r.indexOf(t)&&(r=r.substring(a),i=!0);var o=r.length;return r.indexOf(n)==o-s&&(r=r.substring(0,o-s),i=!0),i?r:e}Object.defineProperty(t,"__esModule",{value:!0});var s=n(2),o=n(5),l=n(9)["default"],u=n(10),c=n(11),f=n(17),g=n(18),p=n(19),d=n(20),h={Match:o.Match,Highlighter:n(22),config:n(18),regexLib:n(3).commonRegExp,vars:{discoveredBrushes:null,highlighters:{}},brushes:{},findElements:function(e,t){var n=t?[t]:u.toArray(document.getElementsByTagName(h.config.tagName)),r=(h.config,[]);if(n=n.concat(f.getSyntaxHighlighterScriptTags()),0===n.length)return r;for(var i=0,a=n.length;a>i;i++){var o={target:n[i],params:s.defaults(s.parse(n[i].className),e)};null!=o.params.brush&&r.push(o)}return r},highlight:function(e,t){var n,r=h.findElements(e,t),u="innerHTML",m=null,x=h.config;if(0!==r.length)for(var v=0,y=r.length;y>v;v++){var m,w,b,t=r[v],E=t.target,S=t.params,C=S.brush;null!=C&&(m=i(C),m&&(S=s.defaults(S||{},p),S=s.defaults(S,g),1==S["html-script"]||1==p["html-script"]?(m=new d(i("xml"),m),C="htmlscript"):m=new m,b=E[u],x.useScriptTags&&(b=a(b)),""!=(E.title||"")&&(S.title=E.title),S.brush=C,b=c(b,S),w=o.applyRegexList(b,m.regexList,S),n=new l(b,w,S),t=f.create("div"),t.innerHTML=n.getHtml(),S.quickCode&&f.attachEvent(f.findElement(t,".code"),"dblclick",f.quickCodeHandler),""!=(E.id||"")&&(t.id=E.id),E.parentNode.replaceChild(t,E)))}}},m=0;t["default"]=h;var x=t.registerBrush=function(e){return h.brushes["brush"+m++]=e["default"]||e};t.clearRegisteredBrushes=function(){h.brushes={},m=0};x(n(23)),x(n(24)),x(n(25)),x(n(26)),x(n(27))},function(e,t,n){"use strict";function r(e){return e.replace(/-(\w+)/g,function(e,t){return t.charAt(0).toUpperCase()+t.substr(1)})}function i(e){var t=s[e];return null==t?e:t}var a=n(3).XRegExp,s={"true":!0,"false":!1};e.exports={defaults:function(e,t){for(var n in t||{})e.hasOwnProperty(n)||(e[n]=e[r(n)]=t[n]);return e},parse:function(e){for(var t,n={},s=a("^\\[(?<values>(.*?))\\]$"),o=0,l=a("(?<name>[\\w-]+)\\s*:\\s*(?<value>[\\w%#-]+|\\[.*?\\]|\".*?\"|'.*?')\\s*;?","g");null!=(t=a.exec(e,l,o));){var u=t.value.replace(/^['"]|['"]$/g,"");if(null!=u&&s.test(u)){var c=a.exec(u,s);u=c.values.length>0?c.values.split(/\s*,\s*/):[]}u=i(u),n[t.name]=n[r(t.name)]=u,o=t.index+t[0].length}return n}}},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(t,"__esModule",{value:!0}),t.commonRegExp=t.XRegExp=void 0;var i=n(4),a=r(i);t.XRegExp=a["default"];t.commonRegExp={multiLineCComments:(0,a["default"])("/\\*.*?\\*/","gs"),singleLineCComments:/\/\/.*$/gm,singleLinePerlComments:/#.*$/gm,doubleQuotedString:/"([^\\"\n]|\\.)*"/g,singleQuotedString:/'([^\\'\n]|\\.)*'/g,multiLineDoubleQuotedString:(0,a["default"])('"([^\\\\"]|\\\\.)*"',"gs"),multiLineSingleQuotedString:(0,a["default"])("'([^\\\\']|\\\\.)*'","gs"),xmlComments:(0,a["default"])("(<|<)!--.*?--(>|>)","gs"),url:/\w+:\/\/[\w-.\/?%&=:@;#]*/g,phpScriptTags:{left:/(<|<)\?(?:=|php)?/g,right:/\?(>|>)/g,eof:!0},aspScriptTags:{left:/(<|<)%=?/g,right:/%(>|>)/g},scriptScriptTags:{left:/(<|<)\s*script.*?(>|>)/gi,right:/(<|<)\/\s*script\s*(>|>)/gi}}},function(e,t){"use strict";function n(e,t,n,r,i){var a;if(e[b]={captureNames:t},i)return e;if(e.__proto__)e.__proto__=w.prototype;else for(a in w.prototype)e[a]=w.prototype[a];return e[b].source=n,e[b].flags=r?r.split("").sort().join(""):r,e}function r(e){return S.replace.call(e,/([\s\S])(?=[\s\S]*\1)/g,"")}function i(e,t){if(!w.isRegExp(e))throw new TypeError("Type RegExp expected");var i=e[b]||{},a=s(e),l="",u="",c=null,f=null;return t=t||{},t.removeG&&(u+="g"),t.removeY&&(u+="y"),u&&(a=S.replace.call(a,new RegExp("["+u+"]+","g"),"")),t.addG&&(l+="g"),t.addY&&(l+="y"),l&&(a=r(a+l)),t.isInternalOnly||(void 0!==i.source&&(c=i.source),null!=i.flags&&(f=l?r(i.flags+l):i.flags)),e=n(new RegExp(e.source,a),o(e)?i.captureNames.slice(0):null,c,f,t.isInternalOnly)}function a(e){return parseInt(e,16)}function s(e){return M?e.flags:S.exec.call(/\/([a-z]*)$/i,RegExp.prototype.toString.call(e))[1]}function o(e){return!(!e[b]||!e[b].captureNames)}function l(e){return parseInt(e,10).toString(16)}function u(e,t){var n,r=e.length;for(n=0;r>n;++n)if(e[n]===t)return n;return-1}function c(e,t){return $.call(e)==="[object "+t+"]"}function f(e,t,n){return S.test.call(n.indexOf("x")>-1?/^(?:\s+|#.*|\(\?#[^)]*\))*(?:[?*+]|{\d+(?:,\d*)?})/:/^(?:\(\?#[^)]*\))*(?:[?*+]|{\d+(?:,\d*)?})/,e.slice(t))}function g(e){for(;e.length<4;)e="0"+e;return e}function p(e,t){var n;if(r(t)!==t)throw new SyntaxError("Invalid duplicate regex flag "+t);for(e=S.replace.call(e,/^\(\?([\w$]+)\)/,function(e,n){if(S.test.call(/[gy]/,n))throw new SyntaxError("Cannot use flag g or y in mode modifier "+e);return t=r(t+n),""}),n=0;n<t.length;++n)if(!H[t.charAt(n)])throw new SyntaxError("Unknown regex flag "+t.charAt(n));return{pattern:e,flags:t}}function d(e){var t={};return c(e,"String")?(w.forEach(e,/[^\s,]+/,function(e){t[e]=!0}),t):e}function h(e){if(!/^[\w$]$/.test(e))throw new Error("Flag must be a single character A-Za-z0-9_$");H[e]=!0}function m(e,t,n,r,i){for(var a,s,o=L.length,l=e.charAt(n),u=null;o--;)if(s=L[o],!(s.leadChar&&s.leadChar!==l||s.scope!==r&&"all"!==s.scope||s.flag&&-1===t.indexOf(s.flag))&&(a=w.exec(e,s.regex,n,"sticky"))){u={matchLength:a[0].length,output:s.handler.call(i,a,r,t),reparse:s.reparse};break}return u}function x(e){E.astral=e}function v(e){RegExp.prototype.exec=(e?C:S).exec,RegExp.prototype.test=(e?C:S).test,String.prototype.match=(e?C:S).match,String.prototype.replace=(e?C:S).replace,String.prototype.split=(e?C:S).split,E.natives=e}function y(e){if(null==e)throw new TypeError("Cannot convert null or undefined to object");return e}function w(e,t){var r,a,s,o,l,u={hasNamedCapture:!1,captureNames:[]},c=R,f="",g=0;if(w.isRegExp(e)){if(void 0!==t)throw new TypeError("Cannot supply flags when copying a RegExp");return i(e)}if(e=void 0===e?"":String(e),t=void 0===t?"":String(t),w.isInstalled("astral")&&-1===t.indexOf("A")&&(t+="A"),k[e]||(k[e]={}),!k[e][t]){for(r=p(e,t),o=r.pattern,l=r.flags;g<o.length;){do r=m(o,l,g,c,u),r&&r.reparse&&(o=o.slice(0,g)+r.output+o.slice(g+r.matchLength));while(r&&r.reparse);r?(f+=r.output,g+=r.matchLength||1):(a=w.exec(o,_[c],g,"sticky")[0],f+=a,g+=a.length,"["===a&&c===R?c=T:"]"===a&&c===T&&(c=R))}k[e][t]={pattern:S.replace.call(f,/\(\?:\)(?:[*+?]|\{\d+(?:,\d*)?})?\??(?=\(\?:\))|^\(\?:\)(?:[*+?]|\{\d+(?:,\d*)?})?\??|\(\?:\)(?:[*+?]|\{\d+(?:,\d*)?})?\??$/g,""),flags:S.replace.call(l,/[^gimuy]+/g,""),captures:u.hasNamedCapture?u.captureNames:null}}return s=k[e][t],n(new RegExp(s.pattern,s.flags),s.captures,e,t)}var b="xregexp",E={astral:!1,natives:!1},S={exec:RegExp.prototype.exec,test:RegExp.prototype.test,match:String.prototype.match,replace:String.prototype.replace,split:String.prototype.split},C={},N={},k={},L=[],R="default",T="class",_={"default":/\\(?:0(?:[0-3][0-7]{0,2}|[4-7][0-7]?)?|[1-9]\d*|x[\dA-Fa-f]{2}|u(?:[\dA-Fa-f]{4}|{[\dA-Fa-f]+})|c[A-Za-z]|[\s\S])|\(\?[:=!]|[?*+]\?|{\d+(?:,\d*)?}\??|[\s\S]/,"class":/\\(?:[0-3][0-7]{0,2}|[4-7][0-7]?|x[\dA-Fa-f]{2}|u(?:[\dA-Fa-f]{4}|{[\dA-Fa-f]+})|c[A-Za-z]|[\s\S])|[\s\S]/},O=/\$(?:{([\w$]+)}|(\d\d?|[\s\S]))/g,j=void 0===S.exec.call(/()??/,"")[1],I=function(){var e=!0;try{new RegExp("","u")}catch(t){e=!1}return e}(),A=function(){var e=!0;try{new RegExp("","y")}catch(t){e=!1}return e}(),M=void 0!==/a/.flags,H={g:!0,i:!0,m:!0,u:I,y:A},$={}.toString;w.prototype=new RegExp,w.version="3.1.0-dev",w.addToken=function(e,t,n){n=n||{};var r,a=n.optionalFlags;if(n.flag&&h(n.flag),a)for(a=S.split.call(a,""),r=0;r<a.length;++r)h(a[r]);L.push({regex:i(e,{addG:!0,addY:A,isInternalOnly:!0}),handler:t,scope:n.scope||R,flag:n.flag,reparse:n.reparse,leadChar:n.leadChar}),w.cache.flush("patterns")},w.cache=function(e,t){return N[e]||(N[e]={}),N[e][t]||(N[e][t]=w(e,t))},w.cache.flush=function(e){"patterns"===e?k={}:N={}},w.escape=function(e){return S.replace.call(y(e),/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&")},w.exec=function(e,t,n,r){var a,s,o="g",l=!1;return l=A&&!!(r||t.sticky&&r!==!1),l&&(o+="y"),t[b]=t[b]||{},s=t[b][o]||(t[b][o]=i(t,{addG:!0,addY:l,removeY:r===!1,isInternalOnly:!0})),s.lastIndex=n=n||0,a=C.exec.call(s,e),r&&a&&a.index!==n&&(a=null),t.global&&(t.lastIndex=a?s.lastIndex:0),a},w.forEach=function(e,t,n){for(var r,i=0,a=-1;r=w.exec(e,t,i);)n(r,++a,e,t),i=r.index+(r[0].length||1)},w.globalize=function(e){return i(e,{addG:!0})},w.install=function(e){e=d(e),!E.astral&&e.astral&&x(!0),!E.natives&&e.natives&&v(!0)},w.isInstalled=function(e){return!!E[e]},w.isRegExp=function(e){return"[object RegExp]"===$.call(e)},w.match=function(e,t,n){var r,a,s=t.global&&"one"!==n||"all"===n,o=(s?"g":"")+(t.sticky?"y":"")||"noGY";return t[b]=t[b]||{},a=t[b][o]||(t[b][o]=i(t,{addG:!!s,addY:!!t.sticky,removeG:"one"===n,isInternalOnly:!0})),r=S.match.call(y(e),a),t.global&&(t.lastIndex="one"===n&&r?r.index+r[0].length:0),s?r||[]:r&&r[0]},w.matchChain=function(e,t){return function n(e,r){var i,a=t[r].regex?t[r]:{regex:t[r]},s=[],o=function(e){if(a.backref){if(!(e.hasOwnProperty(a.backref)||+a.backref<e.length))throw new ReferenceError("Backreference to undefined group: "+a.backref);s.push(e[a.backref]||"")}else s.push(e[0])};for(i=0;i<e.length;++i)w.forEach(e[i],a.regex,o);return r!==t.length-1&&s.length?n(s,r+1):s}([e],0)},w.replace=function(e,t,n,r){var a,s=w.isRegExp(t),o=t.global&&"one"!==r||"all"===r,l=(o?"g":"")+(t.sticky?"y":"")||"noGY",u=t;return s?(t[b]=t[b]||{},u=t[b][l]||(t[b][l]=i(t,{addG:!!o,addY:!!t.sticky,removeG:"one"===r,isInternalOnly:!0}))):o&&(u=new RegExp(w.escape(String(t)),"g")),a=C.replace.call(y(e),u,n),s&&t.global&&(t.lastIndex=0),a},w.replaceEach=function(e,t){var n,r;for(n=0;n<t.length;++n)r=t[n],e=w.replace(e,r[0],r[1],r[2]);return e},w.split=function(e,t,n){return C.split.call(y(e),t,n)},w.test=function(e,t,n,r){return!!w.exec(e,t,n,r)},w.uninstall=function(e){e=d(e),E.astral&&e.astral&&x(!1),E.natives&&e.natives&&v(!1)},w.union=function(e,t){var n,r,i,a,s=/(\()(?!\?)|\\([1-9]\d*)|\\[\s\S]|\[(?:[^\\\]]|\\[\s\S])*]/g,o=[],l=0,u=function(e,t,i){var a=r[l-n];if(t){if(++l,a)return"(?<"+a+">"}else if(i)return"\\"+(+i+n);return e};if(!c(e,"Array")||!e.length)throw new TypeError("Must provide a nonempty array of patterns to merge");for(a=0;a<e.length;++a)i=e[a],w.isRegExp(i)?(n=l,r=i[b]&&i[b].captureNames||[],o.push(S.replace.call(w(i.source).source,s,u))):o.push(w.escape(i));return w(o.join("|"),t)},C.exec=function(e){var t,n,r,a=this.lastIndex,s=S.exec.apply(this,arguments);if(s){if(!j&&s.length>1&&u(s,"")>-1&&(n=i(this,{removeG:!0,isInternalOnly:!0}),S.replace.call(String(e).slice(s.index),n,function(){var e,t=arguments.length;for(e=1;t-2>e;++e)void 0===arguments[e]&&(s[e]=void 0)})),this[b]&&this[b].captureNames)for(r=1;r<s.length;++r)t=this[b].captureNames[r-1],t&&(s[t]=s[r]);this.global&&!s[0].length&&this.lastIndex>s.index&&(this.lastIndex=s.index)}return this.global||(this.lastIndex=a),s},C.test=function(e){return!!C.exec.call(this,e)},C.match=function(e){var t;if(w.isRegExp(e)){if(e.global)return t=S.match.apply(this,arguments),e.lastIndex=0,t}else e=new RegExp(e);return C.exec.call(e,y(this))},C.replace=function(e,t){var n,r,i,a=w.isRegExp(e);return a?(e[b]&&(r=e[b].captureNames),n=e.lastIndex):e+="",i=c(t,"Function")?S.replace.call(String(this),e,function(){var n,i=arguments;if(r)for(i[0]=new String(i[0]),n=0;n<r.length;++n)r[n]&&(i[0][r[n]]=i[n+1]);return a&&e.global&&(e.lastIndex=i[i.length-2]+i[0].length),t.apply(void 0,i)}):S.replace.call(null==this?this:String(this),e,function(){var e=arguments;return S.replace.call(String(t),O,function(t,n,i){var a;if(n){if(a=+n,a<=e.length-3)return e[a]||"";if(a=r?u(r,n):-1,0>a)throw new SyntaxError("Backreference to undefined group "+t);return e[a+1]||""}if("$"===i)return"$";if("&"===i||0===+i)return e[0];if("`"===i)return e[e.length-1].slice(0,e[e.length-2]);if("'"===i)return e[e.length-1].slice(e[e.length-2]+e[0].length);if(i=+i,!isNaN(i)){if(i>e.length-3)throw new SyntaxError("Backreference to undefined group "+t);return e[i]||""}throw new SyntaxError("Invalid token "+t)})}),a&&(e.global?e.lastIndex=0:e.lastIndex=n),i},C.split=function(e,t){if(!w.isRegExp(e))return S.split.apply(this,arguments);var n,r=String(this),i=[],a=e.lastIndex,s=0;return t=(void 0===t?-1:t)>>>0,w.forEach(r,e,function(e){e.index+e[0].length>s&&(i.push(r.slice(s,e.index)),e.length>1&&e.index<r.length&&Array.prototype.push.apply(i,e.slice(1)),n=e[0].length,s=e.index+n)}),s===r.length?(!S.test.call(e,"")||n)&&i.push(""):i.push(r.slice(s)),e.lastIndex=a,i.length>t?i.slice(0,t):i},w.addToken(/\\([ABCE-RTUVXYZaeg-mopqyz]|c(?![A-Za-z])|u(?![\dA-Fa-f]{4}|{[\dA-Fa-f]+})|x(?![\dA-Fa-f]{2}))/,function(e,t){if("B"===e[1]&&t===R)return e[0];throw new SyntaxError("Invalid escape "+e[0])},{scope:"all",leadChar:"\\"}),w.addToken(/\\u{([\dA-Fa-f]+)}/,function(e,t,n){var r=a(e[1]);if(r>1114111)throw new SyntaxError("Invalid Unicode code point "+e[0]);if(65535>=r)return"\\u"+g(l(r));if(I&&n.indexOf("u")>-1)return e[0];throw new SyntaxError("Cannot use Unicode code point above \\u{FFFF} without flag u")},{scope:"all",leadChar:"\\"}),w.addToken(/\[(\^?)]/,function(e){return e[1]?"[\\s\\S]":"\\b\\B"},{leadChar:"["}),w.addToken(/\(\?#[^)]*\)/,function(e,t,n){return f(e.input,e.index+e[0].length,n)?"":"(?:)"},{leadChar:"("}),w.addToken(/\s+|#.*/,function(e,t,n){return f(e.input,e.index+e[0].length,n)?"":"(?:)"},{flag:"x"}),w.addToken(/\./,function(){return"[\\s\\S]"},{flag:"s",leadChar:"."}),w.addToken(/\\k<([\w$]+)>/,function(e){var t=isNaN(e[1])?u(this.captureNames,e[1])+1:+e[1],n=e.index+e[0].length;if(!t||t>this.captureNames.length)throw new SyntaxError("Backreference to undefined group "+e[0]);return"\\"+t+(n===e.input.length||isNaN(e.input.charAt(n))?"":"(?:)")},{leadChar:"\\"}),w.addToken(/\\(\d+)/,function(e,t){if(!(t===R&&/^[1-9]/.test(e[1])&&+e[1]<=this.captureNames.length)&&"0"!==e[1])throw new SyntaxError("Cannot use octal escape or backreference to undefined group "+e[0]);return e[0]},{scope:"all",leadChar:"\\"}),w.addToken(/\(\?P?<([\w$]+)>/,function(e){if(!isNaN(e[1]))throw new SyntaxError("Cannot use integer as capture name "+e[0]);if("length"===e[1]||"__proto__"===e[1])throw new SyntaxError("Cannot use reserved word as capture name "+e[0]);if(u(this.captureNames,e[1])>-1)throw new SyntaxError("Cannot use same name for multiple groups "+e[0]);return this.captureNames.push(e[1]),this.hasNamedCapture=!0,"("},{leadChar:"("}),w.addToken(/\((?!\?)/,function(e,t,n){return n.indexOf("n")>-1?"(?:":(this.captureNames.push(null),"(")},{optionalFlags:"n",leadChar:"("}),e.exports=w},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(6);Object.keys(r).forEach(function(e){"default"!==e&&Object.defineProperty(t,e,{enumerable:!0,get:function(){return r[e]}})});var i=n(7);Object.keys(i).forEach(function(e){"default"!==e&&Object.defineProperty(t,e,{enumerable:!0,get:function(){return i[e]}})})},function(e,t){"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();t.Match=function(){function e(t,r,i){n(this,e),this.value=t,this.index=r,this.length=t.length,this.css=i,this.brushName=null}return r(e,[{key:"toString",value:function(){return this.value}}]),e}()},function(e,t,n){"use strict";function r(e,t){var n=[];t=t||[];for(var r=0,s=t.length;s>r;r++)"object"===i(t[r])&&(n=n.concat((0,a.find)(e,t[r])));return n=(0,a.sort)(n),n=(0,a.removeNested)(n),n=(0,a.compact)(n)}Object.defineProperty(t,"__esModule",{value:!0});var i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol?"symbol":typeof e};t.applyRegexList=r;var a=n(8)},function(e,t,n){"use strict";function r(e,t){function n(e,t){return e[0]}for(var r=null,i=[],a=t.func?t.func:n,s=0;r=l.XRegExp.exec(e,t.regex,s);){var u=a(r,t);"string"==typeof u&&(u=[new o.Match(u,r.index,t.css)]),i=i.concat(u),s=r.index+r[0].length}return i}function i(e){function t(e,t){return e.index<t.index?-1:e.index>t.index?1:e.length<t.length?-1:e.length>t.length?1:0}return e.sort(t)}function a(e){var t,n,r=[];for(t=0,n=e.length;n>t;t++)e[t]&&r.push(e[t]);return r}function s(e){for(var t=0,n=e.length;n>t;t++)if(null!==e[t])for(var r=e[t],i=r.index+r.length,a=t+1,n=e.length;n>a&&null!==e[t];a++){var s=e[a];if(null!==s){if(s.index>i)break;s.index==r.index&&s.length>r.length?e[t]=null:s.index>=r.index&&s.index<i&&(e[a]=null)}}return e}Object.defineProperty(t,"__esModule",{value:!0}),t.find=r,t.sort=i,t.compact=a,t.removeNested=s;var o=n(6),l=n(3)},function(e,t){"use strict";function n(e,t){for(var n=e.toString();n.length<t;)n="0"+n;return n}function r(e){return e.split(/\r?\n/)}function i(e){var t,n,r,i={};for(t=e.highlight||[],"function"!=typeof t.push&&(t=[t]),r=0,n=t.length;n>r;r++)i[t[r]]=!0;return i}function a(e,t,n){var a=this;a.opts=n,a.code=e,a.matches=t,a.lines=r(e),a.linesToHighlight=i(n)}Object.defineProperty(t,"__esModule",{value:!0}),t["default"]=a,a.prototype={wrapLinesWithCode:function(e,t){if(null==e||0==e.length||"\n"==e||null==t)return e;var n,i,a,s,o,l=this,u=[];for(e=e.replace(/</g,"<"),e=e.replace(/ {2,}/g,function(e){for(a="",s=0,o=e.length;o-1>s;s++)a+=l.opts.space;return a+" "}),n=r(e),s=0,o=n.length;o>s;s++)i=n[s],a="",i.length>0&&(i=i.replace(/^( | )+/,function(e){return a=e,""}),i=0===i.length?a:a+'<code class="'+t+'">'+i+"</code>"),u.push(i);return u.join("\n")},processUrls:function(e){var t=/(.*)((>|<).*)/,n=/\w+:\/\/[\w-.\/?%&=:@;#]*/g;return e.replace(n,function(e){var n="",r=null;return(r=t.exec(e))&&(e=r[1],n=r[2]),'<a href="'+e+'">'+e+"</a>"+n})},figureOutLineNumbers:function(e){var t,n,r=[],i=this.lines,a=parseInt(this.opts.firstLine||1);for(t=0,n=i.length;n>t;t++)r.push(t+a);return r},wrapLine:function(e,t,n){var r=["line","number"+t,"index"+e,"alt"+(t%2==0?1:2).toString()];return this.linesToHighlight[t]&&r.push("highlighted"),0==t&&r.push("break"),'<div class="'+r.join(" ")+'">'+n+"</div>"},renderLineNumbers:function(e,t){var r,i,a=this,s=a.opts,o="",l=a.lines.length,u=parseInt(s.firstLine||1),c=s.padLineNumbers;for(1==c?c=(u+l-1).toString().length:1==isNaN(c)&&(c=0),i=0;l>i;i++)r=t?t[i]:u+i,e=0==r?s.space:n(r,c),o+=a.wrapLine(i,r,e);return o},getCodeLinesHtml:function(e,t){for(var n=this,i=n.opts,a=r(e),s=(i.padLineNumbers,parseInt(i.firstLine||1)),o=i.brush,e="",l=0,u=a.length;u>l;l++){var c=a[l],f=/^( |\s)+/.exec(c),g=null,p=t?t[l]:s+l;null!=f&&(g=f[0].toString(),c=c.substr(g.length),g=g.replace(" ",i.space)),0==c.length&&(c=i.space),e+=n.wrapLine(l,p,(null!=g?'<code class="'+o+' spaces">'+g+"</code>":"")+c)}return e},getTitleHtml:function(e){return e?"<caption>"+e+"</caption>":""},getMatchesHtml:function(e,t){function n(e){var t=e?e.brushName||c:c;return t?t+" ":""}var r,i,a,s,o=this,l=0,u="",c=o.opts.brush||"";for(a=0,s=t.length;s>a;a++)r=t[a],null!==r&&0!==r.length&&(i=n(r),u+=o.wrapLinesWithCode(e.substr(l,r.index-l),i+"plain")+o.wrapLinesWithCode(r.value,i+r.css),l=r.index+r.length+(r.offset||0));return u+=o.wrapLinesWithCode(e.substr(l),n()+"plain")},getHtml:function(){var e,t,n,r=this,i=r.opts,a=r.code,s=r.matches,o=["syntaxhighlighter"];return i.collapse===!0&&o.push("collapsed"),t=i.gutter!==!1,t||o.push("nogutter"),o.push(i.className),o.push(i.brush),t&&(e=r.figureOutLineNumbers(a)),n=r.getMatchesHtml(a,s),n=r.getCodeLinesHtml(n,e),i.autoLinks&&(n=r.processUrls(n)),n='\n <div class="'+o.join(" ")+'">\n <table border="0" cellpadding="0" cellspacing="0">\n '+r.getTitleHtml(i.title)+"\n <tbody>\n <tr>\n "+(t?'<td class="gutter">'+r.renderLineNumbers(a)+"</td>":"")+'\n <td class="code">\n <div class="container">'+n+"</div>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n "}}},function(e,t){"use strict";function n(e){return e.split(/\r?\n/)}function r(e,t){for(var r=n(e),i=0,a=r.length;a>i;i++)r[i]=t(r[i],i);return r.join("\n")}function i(e){return(e||"")+Math.round(1e6*Math.random()).toString()}function a(e,t){var n,r={};for(n in e)r[n]=e[n];for(n in t)r[n]=t[n];return r}function s(e){return e.replace(/^\s+|\s+$/g,"")}function o(e){return Array.prototype.slice.apply(e)}function l(e){var t={"true":!0,"false":!1}[e];return null==t?e:t}e.exports={splitLines:n,eachLine:r,guid:i,merge:a,trim:s,toArray:o,toBoolean:l}},function(e,t,n){"use strict";var r=n(12),i=n(13),a=n(14),s=n(15),o=n(16);e.exports=function(e,t){e=r(e,t),e=i(e,t),e=a(e,t),e=s.unindent(e,t);var n=t["tab-size"];return e=t["smart-tabs"]===!0?o.smart(e,n):o.regular(e,n)}},function(e,t){"use strict";e.exports=function(e,t){return e.replace(/^[ ]*[\n]+|[\n]*[ ]*$/g,"").replace(/\r/g," ")}},function(e,t){"use strict";e.exports=function(e,t){var n=/<br\s*\/?>|<br\s*\/?>/gi;return t.bloggerMode===!0&&(e=e.replace(n,"\n")),e}},function(e,t){"use strict";e.exports=function(e,t){var n=/<br\s*\/?>|<br\s*\/?>/gi;return t.stripBrs===!0&&(e=e.replace(n,"")),e}},function(e,t){"use strict";function n(e){return/^\s*$/.test(e)}e.exports={unindent:function(e){var t,r,i,a,s=e.split(/\r?\n/),o=/^\s*/,l=1e3;for(i=0,a=s.length;a>i&&l>0;i++)if(t=s[i],!n(t)){if(r=o.exec(t),null==r)return e;l=Math.min(r[0].length,l)}if(l>0)for(i=0,a=s.length;a>i;i++)n(s[i])||(s[i]=s[i].substr(l));return s.join("\n")}}},function(e,t){"use strict";function n(e,t,n){return e.substr(0,t)+r.substr(0,n)+e.substr(t+1,e.length)}for(var r="",i=0;50>i;i++)r+=" ";e.exports={smart:function(e,t){var r,i,a,s,o=e.split(/\r?\n/),l=" ";for(a=0,s=o.length;s>a;a++)if(r=o[a],-1!==r.indexOf(l)){for(i=0;-1!==(i=r.indexOf(l));)r=n(r,i,t-i%t);o[a]=r}return o.join("\n")},regular:function(e,t){return e.replace(/\t/g,r.substr(0,t))}}},function(e,t){"use strict";function n(){for(var e=document.getElementsByTagName("script"),t=[],n=0;n<e.length;n++)("text/syntaxhighlighter"==e[n].type||"syntaxhighlighter"==e[n].type)&&t.push(e[n]);return t}function r(e,t){return-1!=e.className.indexOf(t)}function i(e,t){r(e,t)||(e.className+=" "+t)}function a(e,t){e.className=e.className.replace(t,"")}function s(e,t,n,r){function i(e){e=e||window.event,e.target||(e.target=e.srcElement,e.preventDefault=function(){this.returnValue=!1}),n.call(r||window,e)}e.attachEvent?e.attachEvent("on"+t,i):e.addEventListener(t,i,!1)}function o(e,t,n){if(null==e)return null;var r,i,a=1!=n?e.childNodes:[e.parentNode],s={"#":"id",".":"className"}[t.substr(0,1)]||"nodeName";if(r="nodeName"!=s?t.substr(1):t.toUpperCase(),-1!=(e[s]||"").indexOf(r))return e;for(var l=0,u=a.length;a&&u>l&&null==i;l++)i=o(a[l],t,n);return i}function l(e,t){return o(e,t,!0)}function u(e,t,n,r,i){var a=(screen.width-n)/2,s=(screen.height-r)/2;i+=", left="+a+", top="+s+", width="+n+", height="+r,i=i.replace(/^,/,"");var o=window.open(e,t,i);return o.focus(),o}function c(e){return document.getElementsByTagName(e)}function f(e){var t,n,r=c(e.tagName);if(e.useScriptTags)for(t=c("script"),n=0;n<t.length;n++)t[n].type.match(/^(text\/)?syntaxhighlighter$/)&&r.push(t[n]);return r}function g(e){return document.createElement(e)}function p(e){var t=e.target,n=l(t,".syntaxhighlighter"),r=l(t,".container"),u=document.createElement("textarea");if(r&&n&&!o(r,"textarea")){i(n,"source");for(var c=r.childNodes,f=[],g=0,p=c.length;p>g;g++)f.push(c[g].innerText||c[g].textContent);f=f.join("\r"),f=f.replace(/\u00a0/g," "),u.readOnly=!0,u.appendChild(document.createTextNode(f)),r.appendChild(u),u.focus(),u.select(),s(u,"blur",function(e){u.parentNode.removeChild(u),a(n,"source")})}}e.exports={quickCodeHandler:p,create:g,popup:u,hasClass:r,addClass:i,removeClass:a,attachEvent:s,findElement:o,findParentElement:l,getSyntaxHighlighterScriptTags:n,findElementsToHighlight:f}},function(e,t){"use strict";e.exports={space:" ",useScriptTags:!0,bloggerMode:!1,stripBrs:!1,tagName:"pre"}},function(e,t){"use strict";e.exports={"class-name":"","first-line":1,"pad-line-numbers":!1,highlight:null,title:null,"smart-tabs":!0,"tab-size":4,gutter:!0,"quick-code":!0,collapse:!1,"auto-links":!0,unindent:!0,"html-script":!1}},function(e,t,n){(function(t){"use strict";function r(e,t){function n(e,t){for(var n=0,r=e.length;r>n;n++)e[n].index+=t}function r(e,r){function s(e){u=u.concat(e)}var o,l=e.code,u=[],c=a.regexList,f=e.index+e.left.length,g=a.htmlScript;o=i(l,c),n(o,f),s(o),null!=g.left&&null!=e.left&&(o=i(e.left,[g.left]),n(o,e.index),s(o)),null!=g.right&&null!=e.right&&(o=i(e.right,[g.right]),n(o,e.index+e[0].lastIndexOf(e.right)),s(o));for(var p=0,d=u.length;d>p;p++)u[p].brushName=t.brushName;return u}var a,s=new e;if(null!=t){if(a=new t,null==a.htmlScript)throw new Error("Brush wasn't configured for html-script option: "+t.brushName);s.regexList.push({regex:a.htmlScript.code,func:r}),this.regexList=s.regexList}}var i=n(5).applyRegexList;e.exports=r}).call(t,n(21))},function(e,t){"use strict";function n(){f&&u&&(f=!1,u.length?c=u.concat(c):g=-1,c.length&&r())}function r(){if(!f){var e=s(n);f=!0;for(var t=c.length;t;){for(u=c,c=[];++g<t;)u&&u[g].run();g=-1,t=c.length}u=null,f=!1,o(e)}}function i(e,t){this.fun=e,this.array=t}function a(){}var s,o,l=e.exports={};!function(){try{s=setTimeout}catch(e){s=function(){throw new Error("setTimeout is not defined")}}try{o=clearTimeout}catch(e){o=function(){throw new Error("clearTimeout is not defined")}}}();var u,c=[],f=!1,g=-1;l.nextTick=function(e){var t=new Array(arguments.length-1);if(arguments.length>1)for(var n=1;n<arguments.length;n++)t[n-1]=arguments[n];c.push(new i(e,t)),1!==c.length||f||s(r,0)},i.prototype.run=function(){this.fun.apply(null,this.array)},l.title="browser",l.browser=!0,l.env={},l.argv=[],l.version="",l.versions={},l.on=a,l.addListener=a,l.once=a,l.off=a,l.removeListener=a,l.removeAllListeners=a,l.emit=a,l.binding=function(e){throw new Error("process.binding is not supported")},l.cwd=function(){return"/"},l.chdir=function(e){throw new Error("process.chdir is not supported")},l.umask=function(){return 0}},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var a=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),s=n(9),o=r(s),l=n(3),u=n(5);e.exports=function(){function e(){i(this,e)}return a(e,[{key:"getKeywords",value:function(e){var t=e.replace(/^\s+|\s+$/g,"").replace(/\s+/g,"|");return"\\b(?:"+t+")\\b"}},{key:"forHtmlScript",value:function(e){var t={end:e.right.source};e.eof&&(t.end="(?:(?:"+t.end+")|$)"),this.htmlScript={left:{regex:e.left,css:"script"},right:{regex:e.right,css:"script"},code:(0,l.XRegExp)("(?<left>"+e.left.source+")(?<code>.*?)(?<right>"+t.end+")","sgi")}}},{key:"getHtml",value:function(e){var t=arguments.length<=1||void 0===arguments[1]?{}:arguments[1],n=(0,u.applyRegexList)(e,this.regexList),r=new o["default"](e,n,t);return r.getHtml()}}]),e}()},function(e,t,n){"use strict";function r(){var e="break case catch class continue default delete do else enum export extends false for from as function if implements import in instanceof interface let new null package private protected static return super switch this throw true try typeof var while with yield";this.regexList=[{regex:a.multiLineDoubleQuotedString,css:"string"},{regex:a.multiLineSingleQuotedString,css:"string"},{regex:a.singleLineCComments,css:"comments"},{regex:a.multiLineCComments,css:"comments"},{regex:/\s*#.*/gm,css:"preprocessor"},{regex:new RegExp(this.getKeywords(e),"gm"),css:"keyword"}],this.forHtmlScript(a.scriptScriptTags)}var i=n(22),a=n(3).commonRegExp;r.prototype=new i,r.aliases=["js","jscript","javascript","json"],e.exports=r},function(e,t,n){"use strict";function r(){var e="alias and BEGIN begin break case class def define_method defined do each else elsif END end ensure false for if in module new next nil not or raise redo rescue retry return self super then throw true undef unless until when while yield",t="Array Bignum Binding Class Continuation Dir Exception FalseClass File::Stat File Fixnum Fload Hash Integer IO MatchData Method Module NilClass Numeric Object Proc Range Regexp String Struct::TMS Symbol ThreadGroup Thread Time TrueClass";this.regexList=[{regex:a.singleLinePerlComments,css:"comments"},{regex:a.doubleQuotedString,css:"string"},{regex:a.singleQuotedString,css:"string"},{regex:/\b[A-Z0-9_]+\b/g,css:"constants"},{regex:/:[a-z][A-Za-z0-9_]*/g,css:"color2"},{regex:/(\$|@@|@)\w+/g,css:"variable bold"},{regex:new RegExp(this.getKeywords(e),"gm"),css:"keyword"},{regex:new RegExp(this.getKeywords(t),"gm"),css:"color1"}],this.forHtmlScript(a.aspScriptTags)}var i=n(22),a=n(3).commonRegExp;r.prototype=new i,r.aliases=["ruby","rails","ror","rb"],e.exports=r},function(e,t,n){"use strict";function r(){function e(e,t){var n=e[0],r=s.exec(n,s("(<|<)[\\s\\/\\?!]*(?<name>[:\\w-\\.]+)","xg")),i=[];if(null!=e.attributes)for(var a,l=0,u=s("(?<name> [\\w:.-]+)\\s*=\\s*(?<value> \".*?\"|'.*?'|\\w+)","xg");null!=(a=s.exec(n,u,l));)i.push(new o(a.name,e.index+a.index,"color1")),i.push(new o(a.value,e.index+a.index+a[0].indexOf(a.value),"string")),l=a.index+a[0].length;return null!=r&&i.push(new o(r.name,e.index+r[0].indexOf(r.name),"keyword")),i}this.regexList=[{regex:s("(\\<|<)\\!\\[[\\w\\s]*?\\[(.|\\s)*?\\]\\](\\>|>)","gm"),css:"color2"},{regex:a.xmlComments,css:"comments"},{regex:s("(<|<)[\\s\\/\\?!]*(\\w+)(?<attributes>.*?)[\\s\\/\\?]*(>|>)","sg"),func:e}]}var i=n(22),a=n(3).commonRegExp,s=n(3).XRegExp,o=n(5).Match;r.prototype=new i,r.aliases=["xml","xhtml","xslt","html","plist"],e.exports=r},function(e,t,n){"use strict";function r(){var e="abs avg case cast coalesce convert count current_timestamp current_user day isnull left lower month nullif replace right session_user space substring sum system_user upper user year",t="absolute action add after alter as asc at authorization begin bigint binary bit by cascade char character check checkpoint close collate column commit committed connect connection constraint contains continue create cube current current_date current_time cursor database date deallocate dec decimal declare default delete desc distinct double drop dynamic else end end-exec escape except exec execute false fetch first float for force foreign forward free from full function global goto grant group grouping having hour ignore index inner insensitive insert instead int integer intersect into is isolation key last level load local max min minute modify move name national nchar next no numeric of off on only open option order out output partial password precision prepare primary prior privileges procedure public read real references relative repeatable restrict return returns revoke rollback rollup rows rule schema scroll second section select sequence serializable set size smallint static statistics table temp temporary then time timestamp to top transaction translation trigger true truncate uncommitted union unique update values varchar varying view when where with work",n="all and any between cross in join like not null or outer some"; + this.regexList=[{regex:/--(.*)$/gm,css:"comments"},{regex:/\/\*([^\*][\s\S]*?)?\*\//gm,css:"comments"},{regex:a.multiLineDoubleQuotedString,css:"string"},{regex:a.multiLineSingleQuotedString,css:"string"},{regex:new RegExp(this.getKeywords(e),"gmi"),css:"color2"},{regex:new RegExp(this.getKeywords(n),"gmi"),css:"color1"},{regex:new RegExp(this.getKeywords(t),"gmi"),css:"keyword"}]}var i=n(22),a=n(3).commonRegExp;r.prototype=new i,r.aliases=["sql"],e.exports=r},function(e,t,n){"use strict";function r(){this.regexList=[]}var i=n(22);n(3).commonRegExp;r.prototype=new i,r.aliases=["text","plain"],e.exports=r},function(e,t,n){"use strict";"function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol?"symbol":typeof e};!function(t,n){e.exports=n()}("domready",function(){var e,t=[],n=document,r=n.documentElement.doScroll,i="DOMContentLoaded",a=(r?/^loaded|^c/:/^loaded|^i|^c/).test(n.readyState);return a||n.addEventListener(i,e=function(){for(n.removeEventListener(i,e),a=1;e=t.shift();)e()}),function(e){a?setTimeout(e,0):t.push(e)}})},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=t.string=function(e){return e.replace(/^([A-Z])/g,function(e,t){return t.toLowerCase()}).replace(/([A-Z])/g,function(e,t){return"-"+t.toLowerCase()})};t.object=function(e){var t={};return Object.keys(e).forEach(function(r){return t[n(r)]=e[r]}),t}},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}var i=n(1),a=r(i);window.SyntaxHighlighter=a["default"],"undefined"==typeof window.XRegExp&&(window.XRegExp=n(3).XRegExp)}]); diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushAS3.js b/guides/assets/javascripts/syntaxhighlighter/shBrushAS3.js deleted file mode 100644 index 8aa3ed2732..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushAS3.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - // Created by Peter Atoria @ http://iAtoria.com - - var inits = 'class interface function package'; - - var keywords = '-Infinity ...rest Array as AS3 Boolean break case catch const continue Date decodeURI ' + - 'decodeURIComponent default delete do dynamic each else encodeURI encodeURIComponent escape ' + - 'extends false final finally flash_proxy for get if implements import in include Infinity ' + - 'instanceof int internal is isFinite isNaN isXMLName label namespace NaN native new null ' + - 'Null Number Object object_proxy override parseFloat parseInt private protected public ' + - 'return set static String super switch this throw true try typeof uint undefined unescape ' + - 'use void while with' - ; - - this.regexList = [ - { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, // one line comments - { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, // multiline comments - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // double quoted strings - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // single quoted strings - { regex: /\b([\d]+(\.[\d]+)?|0x[a-f0-9]+)\b/gi, css: 'value' }, // numbers - { regex: new RegExp(this.getKeywords(inits), 'gm'), css: 'color3' }, // initializations - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, // keywords - { regex: new RegExp('var', 'gm'), css: 'variable' }, // variable - { regex: new RegExp('trace', 'gm'), css: 'color1' } // trace - ]; - - this.forHtmlScript(SyntaxHighlighter.regexLib.scriptScriptTags); - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['actionscript3', 'as3']; - - SyntaxHighlighter.brushes.AS3 = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushAppleScript.js b/guides/assets/javascripts/syntaxhighlighter/shBrushAppleScript.js deleted file mode 100644 index d40bbd7dd2..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushAppleScript.js +++ /dev/null @@ -1,75 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - // AppleScript brush by David Chambers - // http://davidchambersdesign.com/ - var keywords = 'after before beginning continue copy each end every from return get global in local named of set some that the then times to where whose with without'; - var ordinals = 'first second third fourth fifth sixth seventh eighth ninth tenth last front back middle'; - var specials = 'activate add alias AppleScript ask attachment boolean class constant delete duplicate empty exists false id integer list make message modal modified new no paragraph pi properties quit real record remove rest result reveal reverse run running save string true word yes'; - - this.regexList = [ - - { regex: /(--|#).*$/gm, - css: 'comments' }, - - { regex: /\(\*(?:[\s\S]*?\(\*[\s\S]*?\*\))*[\s\S]*?\*\)/gm, // support nested comments - css: 'comments' }, - - { regex: /"[\s\S]*?"/gm, - css: 'string' }, - - { regex: /(?:,|:|¬|'s\b|\(|\)|\{|\}|«|\b\w*»)/g, - css: 'color1' }, - - { regex: /(-)?(\d)+(\.(\d)?)?(E\+(\d)+)?/g, // numbers - css: 'color1' }, - - { regex: /(?:&(amp;|gt;|lt;)?|=|� |>|<|≥|>=|≤|<=|\*|\+|-|\/|÷|\^)/g, - css: 'color2' }, - - { regex: /\b(?:and|as|div|mod|not|or|return(?!\s&)(ing)?|equals|(is(n't| not)? )?equal( to)?|does(n't| not) equal|(is(n't| not)? )?(greater|less) than( or equal( to)?)?|(comes|does(n't| not) come) (after|before)|is(n't| not)?( in)? (back|front) of|is(n't| not)? behind|is(n't| not)?( (in|contained by))?|does(n't| not) contain|contain(s)?|(start|begin|end)(s)? with|((but|end) )?(consider|ignor)ing|prop(erty)?|(a )?ref(erence)?( to)?|repeat (until|while|with)|((end|exit) )?repeat|((else|end) )?if|else|(end )?(script|tell|try)|(on )?error|(put )?into|(of )?(it|me)|its|my|with (timeout( of)?|transaction)|end (timeout|transaction))\b/g, - css: 'keyword' }, - - { regex: /\b\d+(st|nd|rd|th)\b/g, // ordinals - css: 'keyword' }, - - { regex: /\b(?:about|above|against|around|at|below|beneath|beside|between|by|(apart|aside) from|(instead|out) of|into|on(to)?|over|since|thr(ough|u)|under)\b/g, - css: 'color3' }, - - { regex: /\b(?:adding folder items to|after receiving|choose( ((remote )?application|color|folder|from list|URL))?|clipboard info|set the clipboard to|(the )?clipboard|entire contents|display(ing| (alert|dialog|mode))?|document( (edited|file|nib name))?|file( (name|type))?|(info )?for|giving up after|(name )?extension|quoted form|return(ed)?|second(?! item)(s)?|list (disks|folder)|text item(s| delimiters)?|(Unicode )?text|(disk )?item(s)?|((current|list) )?view|((container|key) )?window|with (data|icon( (caution|note|stop))?|parameter(s)?|prompt|properties|seed|title)|case|diacriticals|hyphens|numeric strings|punctuation|white space|folder creation|application(s( folder)?| (processes|scripts position|support))?|((desktop )?(pictures )?|(documents|downloads|favorites|home|keychain|library|movies|music|public|scripts|sites|system|users|utilities|workflows) )folder|desktop|Folder Action scripts|font(s| panel)?|help|internet plugins|modem scripts|(system )?preferences|printer descriptions|scripting (additions|components)|shared (documents|libraries)|startup (disk|items)|temporary items|trash|on server|in AppleTalk zone|((as|long|short) )?user name|user (ID|locale)|(with )?password|in (bundle( with identifier)?|directory)|(close|open for) access|read|write( permission)?|(g|s)et eof|using( delimiters)?|starting at|default (answer|button|color|country code|entr(y|ies)|identifiers|items|name|location|script editor)|hidden( answer)?|open(ed| (location|untitled))?|error (handling|reporting)|(do( shell)?|load|run|store) script|administrator privileges|altering line endings|get volume settings|(alert|boot|input|mount|output|set) volume|output muted|(fax|random )?number|round(ing)?|up|down|toward zero|to nearest|as taught in school|system (attribute|info)|((AppleScript( Studio)?|system) )?version|(home )?directory|(IPv4|primary Ethernet) address|CPU (type|speed)|physical memory|time (stamp|to GMT)|replacing|ASCII (character|number)|localized string|from table|offset|summarize|beep|delay|say|(empty|multiple) selections allowed|(of|preferred) type|invisibles|showing( package contents)?|editable URL|(File|FTP|News|Media|Web) [Ss]ervers|Telnet hosts|Directory services|Remote applications|waiting until completion|saving( (in|to))?|path (for|to( (((current|frontmost) )?application|resource))?)|POSIX (file|path)|(background|RGB) color|(OK|cancel) button name|cancel button|button(s)?|cubic ((centi)?met(re|er)s|yards|feet|inches)|square ((kilo)?met(re|er)s|miles|yards|feet)|(centi|kilo)?met(re|er)s|miles|yards|feet|inches|lit(re|er)s|gallons|quarts|(kilo)?grams|ounces|pounds|degrees (Celsius|Fahrenheit|Kelvin)|print( (dialog|settings))?|clos(e(able)?|ing)|(de)?miniaturized|miniaturizable|zoom(ed|able)|attribute run|action (method|property|title)|phone|email|((start|end)ing|home) page|((birth|creation|current|custom|modification) )?date|((((phonetic )?(first|last|middle))|computer|host|maiden|related) |nick)?name|aim|icq|jabber|msn|yahoo|address(es)?|save addressbook|should enable action|city|country( code)?|formatte(r|d address)|(palette )?label|state|street|zip|AIM [Hh]andle(s)?|my card|select(ion| all)?|unsaved|(alpha )?value|entr(y|ies)|group|(ICQ|Jabber|MSN) handle|person|people|company|department|icon image|job title|note|organization|suffix|vcard|url|copies|collating|pages (across|down)|request print time|target( printer)?|((GUI Scripting|Script menu) )?enabled|show Computer scripts|(de)?activated|awake from nib|became (key|main)|call method|of (class|object)|center|clicked toolbar item|closed|for document|exposed|(can )?hide|idle|keyboard (down|up)|event( (number|type))?|launch(ed)?|load (image|movie|nib|sound)|owner|log|mouse (down|dragged|entered|exited|moved|up)|move|column|localization|resource|script|register|drag (info|types)|resigned (active|key|main)|resiz(e(d)?|able)|right mouse (down|dragged|up)|scroll wheel|(at )?index|should (close|open( untitled)?|quit( after last window closed)?|zoom)|((proposed|screen) )?bounds|show(n)?|behind|in front of|size (mode|to fit)|update(d| toolbar item)?|was (hidden|miniaturized)|will (become active|close|finish launching|hide|miniaturize|move|open|quit|(resign )?active|((maximum|minimum|proposed) )?size|show|zoom)|bundle|data source|movie|pasteboard|sound|tool(bar| tip)|(color|open|save) panel|coordinate system|frontmost|main( (bundle|menu|window))?|((services|(excluded from )?windows) )?menu|((executable|frameworks|resource|scripts|shared (frameworks|support)) )?path|(selected item )?identifier|data|content(s| view)?|character(s)?|click count|(command|control|option|shift) key down|context|delta (x|y|z)|key( code)?|location|pressure|unmodified characters|types|(first )?responder|playing|(allowed|selectable) identifiers|allows customization|(auto saves )?configuration|visible|image( name)?|menu form representation|tag|user(-| )defaults|associated file name|(auto|needs) display|current field editor|floating|has (resize indicator|shadow)|hides when deactivated|level|minimized (image|title)|opaque|position|release when closed|sheet|title(d)?)\b/g, - css: 'color3' }, - - { regex: new RegExp(this.getKeywords(specials), 'gm'), css: 'color3' }, - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, - { regex: new RegExp(this.getKeywords(ordinals), 'gm'), css: 'keyword' } - ]; - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['applescript']; - - SyntaxHighlighter.brushes.AppleScript = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushBash.js b/guides/assets/javascripts/syntaxhighlighter/shBrushBash.js deleted file mode 100644 index 8c296969ff..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushBash.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - var keywords = 'if fi then elif else for do done until while break continue case function return in eq ne ge le'; - var commands = 'alias apropos awk basename bash bc bg builtin bzip2 cal cat cd cfdisk chgrp chmod chown chroot' + - 'cksum clear cmp comm command cp cron crontab csplit cut date dc dd ddrescue declare df ' + - 'diff diff3 dig dir dircolors dirname dirs du echo egrep eject enable env ethtool eval ' + - 'exec exit expand export expr false fdformat fdisk fg fgrep file find fmt fold format ' + - 'free fsck ftp gawk getopts grep groups gzip hash head history hostname id ifconfig ' + - 'import install join kill less let ln local locate logname logout look lpc lpr lprint ' + - 'lprintd lprintq lprm ls lsof make man mkdir mkfifo mkisofs mknod more mount mtools ' + - 'mv netstat nice nl nohup nslookup open op passwd paste pathchk ping popd pr printcap ' + - 'printenv printf ps pushd pwd quota quotacheck quotactl ram rcp read readonly renice ' + - 'remsync rm rmdir rsync screen scp sdiff sed select seq set sftp shift shopt shutdown ' + - 'sleep sort source split ssh strace su sudo sum symlink sync tail tar tee test time ' + - 'times touch top traceroute trap tr true tsort tty type ulimit umask umount unalias ' + - 'uname unexpand uniq units unset unshar useradd usermod users uuencode uudecode v vdir ' + - 'vi watch wc whereis which who whoami Wget xargs yes' - ; - - this.regexList = [ - { regex: /^#!.*$/gm, css: 'preprocessor bold' }, - { regex: /\/[\w-\/]+/gm, css: 'plain' }, - { regex: SyntaxHighlighter.regexLib.singleLinePerlComments, css: 'comments' }, // one line comments - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // double quoted strings - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // single quoted strings - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, // keywords - { regex: new RegExp(this.getKeywords(commands), 'gm'), css: 'functions' } // commands - ]; - } - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['bash', 'shell']; - - SyntaxHighlighter.brushes.Bash = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushCSharp.js b/guides/assets/javascripts/syntaxhighlighter/shBrushCSharp.js deleted file mode 100644 index 079214efe1..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushCSharp.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - var keywords = 'abstract as base bool break byte case catch char checked class const ' + - 'continue decimal default delegate do double else enum event explicit ' + - 'extern false finally fixed float for foreach get goto if implicit in int ' + - 'interface internal is lock long namespace new null object operator out ' + - 'override params private protected public readonly ref return sbyte sealed set ' + - 'short sizeof stackalloc static string struct switch this throw true try ' + - 'typeof uint ulong unchecked unsafe ushort using virtual void while'; - - function fixComments(match, regexInfo) - { - var css = (match[0].indexOf("///") == 0) - ? 'color1' - : 'comments' - ; - - return [new SyntaxHighlighter.Match(match[0], match.index, css)]; - } - - this.regexList = [ - { regex: SyntaxHighlighter.regexLib.singleLineCComments, func : fixComments }, // one line comments - { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, // multiline comments - { regex: /@"(?:[^"]|"")*"/g, css: 'string' }, // @-quoted strings - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // strings - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // strings - { regex: /^\s*#.*/gm, css: 'preprocessor' }, // preprocessor tags like #region and #endregion - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, // c# keyword - { regex: /\bpartial(?=\s+(?:class|interface|struct)\b)/g, css: 'keyword' }, // contextual keyword: 'partial' - { regex: /\byield(?=\s+(?:return|break)\b)/g, css: 'keyword' } // contextual keyword: 'yield' - ]; - - this.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags); - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['c#', 'c-sharp', 'csharp']; - - SyntaxHighlighter.brushes.CSharp = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); - diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushColdFusion.js b/guides/assets/javascripts/syntaxhighlighter/shBrushColdFusion.js deleted file mode 100644 index 627dbb9b76..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushColdFusion.js +++ /dev/null @@ -1,100 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - // Contributed by Jen - // http://www.jensbits.com/2009/05/14/coldfusion-brush-for-syntaxhighlighter-plus - - var funcs = 'Abs ACos AddSOAPRequestHeader AddSOAPResponseHeader AjaxLink AjaxOnLoad ArrayAppend ArrayAvg ArrayClear ArrayDeleteAt ' + - 'ArrayInsertAt ArrayIsDefined ArrayIsEmpty ArrayLen ArrayMax ArrayMin ArraySet ArraySort ArraySum ArraySwap ArrayToList ' + - 'Asc ASin Atn BinaryDecode BinaryEncode BitAnd BitMaskClear BitMaskRead BitMaskSet BitNot BitOr BitSHLN BitSHRN BitXor ' + - 'Ceiling CharsetDecode CharsetEncode Chr CJustify Compare CompareNoCase Cos CreateDate CreateDateTime CreateObject ' + - 'CreateODBCDate CreateODBCDateTime CreateODBCTime CreateTime CreateTimeSpan CreateUUID DateAdd DateCompare DateConvert ' + - 'DateDiff DateFormat DatePart Day DayOfWeek DayOfWeekAsString DayOfYear DaysInMonth DaysInYear DE DecimalFormat DecrementValue ' + - 'Decrypt DecryptBinary DeleteClientVariable DeserializeJSON DirectoryExists DollarFormat DotNetToCFType Duplicate Encrypt ' + - 'EncryptBinary Evaluate Exp ExpandPath FileClose FileCopy FileDelete FileExists FileIsEOF FileMove FileOpen FileRead ' + - 'FileReadBinary FileReadLine FileSetAccessMode FileSetAttribute FileSetLastModified FileWrite Find FindNoCase FindOneOf ' + - 'FirstDayOfMonth Fix FormatBaseN GenerateSecretKey GetAuthUser GetBaseTagData GetBaseTagList GetBaseTemplatePath ' + - 'GetClientVariablesList GetComponentMetaData GetContextRoot GetCurrentTemplatePath GetDirectoryFromPath GetEncoding ' + - 'GetException GetFileFromPath GetFileInfo GetFunctionList GetGatewayHelper GetHttpRequestData GetHttpTimeString ' + - 'GetK2ServerDocCount GetK2ServerDocCountLimit GetLocale GetLocaleDisplayName GetLocalHostIP GetMetaData GetMetricData ' + - 'GetPageContext GetPrinterInfo GetProfileSections GetProfileString GetReadableImageFormats GetSOAPRequest GetSOAPRequestHeader ' + - 'GetSOAPResponse GetSOAPResponseHeader GetTempDirectory GetTempFile GetTemplatePath GetTickCount GetTimeZoneInfo GetToken ' + - 'GetUserRoles GetWriteableImageFormats Hash Hour HTMLCodeFormat HTMLEditFormat IIf ImageAddBorder ImageBlur ImageClearRect ' + - 'ImageCopy ImageCrop ImageDrawArc ImageDrawBeveledRect ImageDrawCubicCurve ImageDrawLine ImageDrawLines ImageDrawOval ' + - 'ImageDrawPoint ImageDrawQuadraticCurve ImageDrawRect ImageDrawRoundRect ImageDrawText ImageFlip ImageGetBlob ImageGetBufferedImage ' + - 'ImageGetEXIFTag ImageGetHeight ImageGetIPTCTag ImageGetWidth ImageGrayscale ImageInfo ImageNegative ImageNew ImageOverlay ImagePaste ' + - 'ImageRead ImageReadBase64 ImageResize ImageRotate ImageRotateDrawingAxis ImageScaleToFit ImageSetAntialiasing ImageSetBackgroundColor ' + - 'ImageSetDrawingColor ImageSetDrawingStroke ImageSetDrawingTransparency ImageSharpen ImageShear ImageShearDrawingAxis ImageTranslate ' + - 'ImageTranslateDrawingAxis ImageWrite ImageWriteBase64 ImageXORDrawingMode IncrementValue InputBaseN Insert Int IsArray IsBinary ' + - 'IsBoolean IsCustomFunction IsDate IsDDX IsDebugMode IsDefined IsImage IsImageFile IsInstanceOf IsJSON IsLeapYear IsLocalHost ' + - 'IsNumeric IsNumericDate IsObject IsPDFFile IsPDFObject IsQuery IsSimpleValue IsSOAPRequest IsStruct IsUserInAnyRole IsUserInRole ' + - 'IsUserLoggedIn IsValid IsWDDX IsXML IsXmlAttribute IsXmlDoc IsXmlElem IsXmlNode IsXmlRoot JavaCast JSStringFormat LCase Left Len ' + - 'ListAppend ListChangeDelims ListContains ListContainsNoCase ListDeleteAt ListFind ListFindNoCase ListFirst ListGetAt ListInsertAt ' + - 'ListLast ListLen ListPrepend ListQualify ListRest ListSetAt ListSort ListToArray ListValueCount ListValueCountNoCase LJustify Log ' + - 'Log10 LSCurrencyFormat LSDateFormat LSEuroCurrencyFormat LSIsCurrency LSIsDate LSIsNumeric LSNumberFormat LSParseCurrency LSParseDateTime ' + - 'LSParseEuroCurrency LSParseNumber LSTimeFormat LTrim Max Mid Min Minute Month MonthAsString Now NumberFormat ParagraphFormat ParseDateTime ' + - 'Pi PrecisionEvaluate PreserveSingleQuotes Quarter QueryAddColumn QueryAddRow QueryConvertForGrid QueryNew QuerySetCell QuotedValueList Rand ' + - 'Randomize RandRange REFind REFindNoCase ReleaseComObject REMatch REMatchNoCase RemoveChars RepeatString Replace ReplaceList ReplaceNoCase ' + - 'REReplace REReplaceNoCase Reverse Right RJustify Round RTrim Second SendGatewayMessage SerializeJSON SetEncoding SetLocale SetProfileString ' + - 'SetVariable Sgn Sin Sleep SpanExcluding SpanIncluding Sqr StripCR StructAppend StructClear StructCopy StructCount StructDelete StructFind ' + - 'StructFindKey StructFindValue StructGet StructInsert StructIsEmpty StructKeyArray StructKeyExists StructKeyList StructKeyList StructNew ' + - 'StructSort StructUpdate Tan TimeFormat ToBase64 ToBinary ToScript ToString Trim UCase URLDecode URLEncodedFormat URLSessionFormat Val ' + - 'ValueList VerifyClient Week Wrap Wrap WriteOutput XmlChildPos XmlElemNew XmlFormat XmlGetNodeType XmlNew XmlParse XmlSearch XmlTransform ' + - 'XmlValidate Year YesNoFormat'; - - var keywords = 'cfabort cfajaximport cfajaxproxy cfapplet cfapplication cfargument cfassociate cfbreak cfcache cfcalendar ' + - 'cfcase cfcatch cfchart cfchartdata cfchartseries cfcol cfcollection cfcomponent cfcontent cfcookie cfdbinfo ' + - 'cfdefaultcase cfdirectory cfdiv cfdocument cfdocumentitem cfdocumentsection cfdump cfelse cfelseif cferror ' + - 'cfexchangecalendar cfexchangeconnection cfexchangecontact cfexchangefilter cfexchangemail cfexchangetask ' + - 'cfexecute cfexit cffeed cffile cfflush cfform cfformgroup cfformitem cfftp cffunction cfgrid cfgridcolumn ' + - 'cfgridrow cfgridupdate cfheader cfhtmlhead cfhttp cfhttpparam cfif cfimage cfimport cfinclude cfindex ' + - 'cfinput cfinsert cfinterface cfinvoke cfinvokeargument cflayout cflayoutarea cfldap cflocation cflock cflog ' + - 'cflogin cfloginuser cflogout cfloop cfmail cfmailparam cfmailpart cfmenu cfmenuitem cfmodule cfNTauthenticate ' + - 'cfobject cfobjectcache cfoutput cfparam cfpdf cfpdfform cfpdfformparam cfpdfparam cfpdfsubform cfpod cfpop ' + - 'cfpresentation cfpresentationslide cfpresenter cfprint cfprocessingdirective cfprocparam cfprocresult ' + - 'cfproperty cfquery cfqueryparam cfregistry cfreport cfreportparam cfrethrow cfreturn cfsavecontent cfschedule ' + - 'cfscript cfsearch cfselect cfset cfsetting cfsilent cfslider cfsprydataset cfstoredproc cfswitch cftable ' + - 'cftextarea cfthread cfthrow cftimer cftooltip cftrace cftransaction cftree cftreeitem cftry cfupdate cfwddx ' + - 'cfwindow cfxml cfzip cfzipparam'; - - var operators = 'all and any between cross in join like not null or outer some'; - - this.regexList = [ - { regex: new RegExp('--(.*)$', 'gm'), css: 'comments' }, // one line and multiline comments - { regex: SyntaxHighlighter.regexLib.xmlComments, css: 'comments' }, // single quoted strings - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // double quoted strings - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // single quoted strings - { regex: new RegExp(this.getKeywords(funcs), 'gmi'), css: 'functions' }, // functions - { regex: new RegExp(this.getKeywords(operators), 'gmi'), css: 'color1' }, // operators and such - { regex: new RegExp(this.getKeywords(keywords), 'gmi'), css: 'keyword' } // keyword - ]; - } - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['coldfusion','cf']; - - SyntaxHighlighter.brushes.ColdFusion = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushCpp.js b/guides/assets/javascripts/syntaxhighlighter/shBrushCpp.js deleted file mode 100644 index 9f70d3aed6..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushCpp.js +++ /dev/null @@ -1,97 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - // Copyright 2006 Shin, YoungJin - - var datatypes = 'ATOM BOOL BOOLEAN BYTE CHAR COLORREF DWORD DWORDLONG DWORD_PTR ' + - 'DWORD32 DWORD64 FLOAT HACCEL HALF_PTR HANDLE HBITMAP HBRUSH ' + - 'HCOLORSPACE HCONV HCONVLIST HCURSOR HDC HDDEDATA HDESK HDROP HDWP ' + - 'HENHMETAFILE HFILE HFONT HGDIOBJ HGLOBAL HHOOK HICON HINSTANCE HKEY ' + - 'HKL HLOCAL HMENU HMETAFILE HMODULE HMONITOR HPALETTE HPEN HRESULT ' + - 'HRGN HRSRC HSZ HWINSTA HWND INT INT_PTR INT32 INT64 LANGID LCID LCTYPE ' + - 'LGRPID LONG LONGLONG LONG_PTR LONG32 LONG64 LPARAM LPBOOL LPBYTE LPCOLORREF ' + - 'LPCSTR LPCTSTR LPCVOID LPCWSTR LPDWORD LPHANDLE LPINT LPLONG LPSTR LPTSTR ' + - 'LPVOID LPWORD LPWSTR LRESULT PBOOL PBOOLEAN PBYTE PCHAR PCSTR PCTSTR PCWSTR ' + - 'PDWORDLONG PDWORD_PTR PDWORD32 PDWORD64 PFLOAT PHALF_PTR PHANDLE PHKEY PINT ' + - 'PINT_PTR PINT32 PINT64 PLCID PLONG PLONGLONG PLONG_PTR PLONG32 PLONG64 POINTER_32 ' + - 'POINTER_64 PSHORT PSIZE_T PSSIZE_T PSTR PTBYTE PTCHAR PTSTR PUCHAR PUHALF_PTR ' + - 'PUINT PUINT_PTR PUINT32 PUINT64 PULONG PULONGLONG PULONG_PTR PULONG32 PULONG64 ' + - 'PUSHORT PVOID PWCHAR PWORD PWSTR SC_HANDLE SC_LOCK SERVICE_STATUS_HANDLE SHORT ' + - 'SIZE_T SSIZE_T TBYTE TCHAR UCHAR UHALF_PTR UINT UINT_PTR UINT32 UINT64 ULONG ' + - 'ULONGLONG ULONG_PTR ULONG32 ULONG64 USHORT USN VOID WCHAR WORD WPARAM WPARAM WPARAM ' + - 'char bool short int __int32 __int64 __int8 __int16 long float double __wchar_t ' + - 'clock_t _complex _dev_t _diskfree_t div_t ldiv_t _exception _EXCEPTION_POINTERS ' + - 'FILE _finddata_t _finddatai64_t _wfinddata_t _wfinddatai64_t __finddata64_t ' + - '__wfinddata64_t _FPIEEE_RECORD fpos_t _HEAPINFO _HFILE lconv intptr_t ' + - 'jmp_buf mbstate_t _off_t _onexit_t _PNH ptrdiff_t _purecall_handler ' + - 'sig_atomic_t size_t _stat __stat64 _stati64 terminate_function ' + - 'time_t __time64_t _timeb __timeb64 tm uintptr_t _utimbuf ' + - 'va_list wchar_t wctrans_t wctype_t wint_t signed'; - - var keywords = 'break case catch class const __finally __exception __try ' + - 'const_cast continue private public protected __declspec ' + - 'default delete deprecated dllexport dllimport do dynamic_cast ' + - 'else enum explicit extern if for friend goto inline ' + - 'mutable naked namespace new noinline noreturn nothrow ' + - 'register reinterpret_cast return selectany ' + - 'sizeof static static_cast struct switch template this ' + - 'thread throw true false try typedef typeid typename union ' + - 'using uuid virtual void volatile whcar_t while'; - - var functions = 'assert isalnum isalpha iscntrl isdigit isgraph islower isprint' + - 'ispunct isspace isupper isxdigit tolower toupper errno localeconv ' + - 'setlocale acos asin atan atan2 ceil cos cosh exp fabs floor fmod ' + - 'frexp ldexp log log10 modf pow sin sinh sqrt tan tanh jmp_buf ' + - 'longjmp setjmp raise signal sig_atomic_t va_arg va_end va_start ' + - 'clearerr fclose feof ferror fflush fgetc fgetpos fgets fopen ' + - 'fprintf fputc fputs fread freopen fscanf fseek fsetpos ftell ' + - 'fwrite getc getchar gets perror printf putc putchar puts remove ' + - 'rename rewind scanf setbuf setvbuf sprintf sscanf tmpfile tmpnam ' + - 'ungetc vfprintf vprintf vsprintf abort abs atexit atof atoi atol ' + - 'bsearch calloc div exit free getenv labs ldiv malloc mblen mbstowcs ' + - 'mbtowc qsort rand realloc srand strtod strtol strtoul system ' + - 'wcstombs wctomb memchr memcmp memcpy memmove memset strcat strchr ' + - 'strcmp strcoll strcpy strcspn strerror strlen strncat strncmp ' + - 'strncpy strpbrk strrchr strspn strstr strtok strxfrm asctime ' + - 'clock ctime difftime gmtime localtime mktime strftime time'; - - this.regexList = [ - { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, // one line comments - { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, // multiline comments - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // strings - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // strings - { regex: /^ *#.*/gm, css: 'preprocessor' }, - { regex: new RegExp(this.getKeywords(datatypes), 'gm'), css: 'color1 bold' }, - { regex: new RegExp(this.getKeywords(functions), 'gm'), css: 'functions bold' }, - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword bold' } - ]; - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['cpp', 'c']; - - SyntaxHighlighter.brushes.Cpp = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushCss.js b/guides/assets/javascripts/syntaxhighlighter/shBrushCss.js deleted file mode 100644 index 4297a9a648..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushCss.js +++ /dev/null @@ -1,91 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - function getKeywordsCSS(str) - { - return '\\b([a-z_]|)' + str.replace(/ /g, '(?=:)\\b|\\b([a-z_\\*]|\\*|)') + '(?=:)\\b'; - }; - - function getValuesCSS(str) - { - return '\\b' + str.replace(/ /g, '(?!-)(?!:)\\b|\\b()') + '\:\\b'; - }; - - var keywords = 'ascent azimuth background-attachment background-color background-image background-position ' + - 'background-repeat background baseline bbox border-collapse border-color border-spacing border-style border-top ' + - 'border-right border-bottom border-left border-top-color border-right-color border-bottom-color border-left-color ' + - 'border-top-style border-right-style border-bottom-style border-left-style border-top-width border-right-width ' + - 'border-bottom-width border-left-width border-width border bottom cap-height caption-side centerline clear clip color ' + - 'content counter-increment counter-reset cue-after cue-before cue cursor definition-src descent direction display ' + - 'elevation empty-cells float font-size-adjust font-family font-size font-stretch font-style font-variant font-weight font ' + - 'height left letter-spacing line-height list-style-image list-style-position list-style-type list-style margin-top ' + - 'margin-right margin-bottom margin-left margin marker-offset marks mathline max-height max-width min-height min-width orphans ' + - 'outline-color outline-style outline-width outline overflow padding-top padding-right padding-bottom padding-left padding page ' + - 'page-break-after page-break-before page-break-inside pause pause-after pause-before pitch pitch-range play-during position ' + - 'quotes right richness size slope src speak-header speak-numeral speak-punctuation speak speech-rate stemh stemv stress ' + - 'table-layout text-align top text-decoration text-indent text-shadow text-transform unicode-bidi unicode-range units-per-em ' + - 'vertical-align visibility voice-family volume white-space widows width widths word-spacing x-height z-index'; - - var values = 'above absolute all always aqua armenian attr aural auto avoid baseline behind below bidi-override black blink block blue bold bolder '+ - 'both bottom braille capitalize caption center center-left center-right circle close-quote code collapse compact condensed '+ - 'continuous counter counters crop cross crosshair cursive dashed decimal decimal-leading-zero default digits disc dotted double '+ - 'embed embossed e-resize expanded extra-condensed extra-expanded fantasy far-left far-right fast faster fixed format fuchsia '+ - 'gray green groove handheld hebrew help hidden hide high higher icon inline-table inline inset inside invert italic '+ - 'justify landscape large larger left-side left leftwards level lighter lime line-through list-item local loud lower-alpha '+ - 'lowercase lower-greek lower-latin lower-roman lower low ltr marker maroon medium message-box middle mix move narrower '+ - 'navy ne-resize no-close-quote none no-open-quote no-repeat normal nowrap n-resize nw-resize oblique olive once open-quote outset '+ - 'outside overline pointer portrait pre print projection purple red relative repeat repeat-x repeat-y rgb ridge right right-side '+ - 'rightwards rtl run-in screen scroll semi-condensed semi-expanded separate se-resize show silent silver slower slow '+ - 'small small-caps small-caption smaller soft solid speech spell-out square s-resize static status-bar sub super sw-resize '+ - 'table-caption table-cell table-column table-column-group table-footer-group table-header-group table-row table-row-group teal '+ - 'text-bottom text-top thick thin top transparent tty tv ultra-condensed ultra-expanded underline upper-alpha uppercase upper-latin '+ - 'upper-roman url visible wait white wider w-resize x-fast x-high x-large x-loud x-low x-slow x-small x-soft xx-large xx-small yellow'; - - var fonts = '[mM]onospace [tT]ahoma [vV]erdana [aA]rial [hH]elvetica [sS]ans-serif [sS]erif [cC]ourier mono sans serif'; - - this.regexList = [ - { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, // multiline comments - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // double quoted strings - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // single quoted strings - { regex: /\#[a-fA-F0-9]{3,6}/g, css: 'value' }, // html colors - { regex: /(-?\d+)(\.\d+)?(px|em|pt|\:|\%|)/g, css: 'value' }, // sizes - { regex: /!important/g, css: 'color3' }, // !important - { regex: new RegExp(getKeywordsCSS(keywords), 'gm'), css: 'keyword' }, // keywords - { regex: new RegExp(getValuesCSS(values), 'g'), css: 'value' }, // values - { regex: new RegExp(this.getKeywords(fonts), 'g'), css: 'color1' } // fonts - ]; - - this.forHtmlScript({ - left: /(<|<)\s*style.*?(>|>)/gi, - right: /(<|<)\/\s*style\s*(>|>)/gi - }); - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['css']; - - SyntaxHighlighter.brushes.CSS = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushDelphi.js b/guides/assets/javascripts/syntaxhighlighter/shBrushDelphi.js deleted file mode 100644 index e1060d4468..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushDelphi.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - var keywords = 'abs addr and ansichar ansistring array as asm begin boolean byte cardinal ' + - 'case char class comp const constructor currency destructor div do double ' + - 'downto else end except exports extended false file finalization finally ' + - 'for function goto if implementation in inherited int64 initialization ' + - 'integer interface is label library longint longword mod nil not object ' + - 'of on or packed pansichar pansistring pchar pcurrency pdatetime pextended ' + - 'pint64 pointer private procedure program property pshortstring pstring ' + - 'pvariant pwidechar pwidestring protected public published raise real real48 ' + - 'record repeat set shl shortint shortstring shr single smallint string then ' + - 'threadvar to true try type unit until uses val var varirnt while widechar ' + - 'widestring with word write writeln xor'; - - this.regexList = [ - { regex: /\(\*[\s\S]*?\*\)/gm, css: 'comments' }, // multiline comments (* *) - { regex: /{(?!\$)[\s\S]*?}/gm, css: 'comments' }, // multiline comments { } - { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, // one line - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // strings - { regex: /\{\$[a-zA-Z]+ .+\}/g, css: 'color1' }, // compiler Directives and Region tags - { regex: /\b[\d\.]+\b/g, css: 'value' }, // numbers 12345 - { regex: /\$[a-zA-Z0-9]+\b/g, css: 'value' }, // numbers $F5D3 - { regex: new RegExp(this.getKeywords(keywords), 'gmi'), css: 'keyword' } // keyword - ]; - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['delphi', 'pascal', 'pas']; - - SyntaxHighlighter.brushes.Delphi = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushDiff.js b/guides/assets/javascripts/syntaxhighlighter/shBrushDiff.js deleted file mode 100644 index e9b14fc580..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushDiff.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - this.regexList = [ - { regex: /^\+\+\+.*$/gm, css: 'color2' }, - { regex: /^\-\-\-.*$/gm, css: 'color2' }, - { regex: /^\s.*$/gm, css: 'color1' }, - { regex: /^@@.*@@$/gm, css: 'variable' }, - { regex: /^\+[^\+]{1}.*$/gm, css: 'string' }, - { regex: /^\-[^\-]{1}.*$/gm, css: 'comments' } - ]; - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['diff', 'patch']; - - SyntaxHighlighter.brushes.Diff = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushErlang.js b/guides/assets/javascripts/syntaxhighlighter/shBrushErlang.js deleted file mode 100644 index 6ba7d9da87..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushErlang.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - // Contributed by Jean-Lou Dupont - // http://jldupont.blogspot.com/2009/06/erlang-syntax-highlighter.html - - // According to: http://erlang.org/doc/reference_manual/introduction.html#1.5 - var keywords = 'after and andalso band begin bnot bor bsl bsr bxor '+ - 'case catch cond div end fun if let not of or orelse '+ - 'query receive rem try when xor'+ - // additional - ' module export import define'; - - this.regexList = [ - { regex: new RegExp("[A-Z][A-Za-z0-9_]+", 'g'), css: 'constants' }, - { regex: new RegExp("\\%.+", 'gm'), css: 'comments' }, - { regex: new RegExp("\\?[A-Za-z0-9_]+", 'g'), css: 'preprocessor' }, - { regex: new RegExp("[a-z0-9_]+:[a-z0-9_]+", 'g'), css: 'functions' }, - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } - ]; - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['erl', 'erlang']; - - SyntaxHighlighter.brushes.Erland = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushGroovy.js b/guides/assets/javascripts/syntaxhighlighter/shBrushGroovy.js deleted file mode 100644 index 6ec5c18521..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushGroovy.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - // Contributed by Andres Almiray - // http://jroller.com/aalmiray/entry/nice_source_code_syntax_highlighter - - var keywords = 'as assert break case catch class continue def default do else extends finally ' + - 'if in implements import instanceof interface new package property return switch ' + - 'throw throws try while public protected private static'; - var types = 'void boolean byte char short int long float double'; - var constants = 'null'; - var methods = 'allProperties count get size '+ - 'collect each eachProperty eachPropertyName eachWithIndex find findAll ' + - 'findIndexOf grep inject max min reverseEach sort ' + - 'asImmutable asSynchronized flatten intersect join pop reverse subMap toList ' + - 'padRight padLeft contains eachMatch toCharacter toLong toUrl tokenize ' + - 'eachFile eachFileRecurse eachB yte eachLine readBytes readLine getText ' + - 'splitEachLine withReader append encodeBase64 decodeBase64 filterLine ' + - 'transformChar transformLine withOutputStream withPrintWriter withStream ' + - 'withStreams withWriter withWriterAppend write writeLine '+ - 'dump inspect invokeMethod print println step times upto use waitForOrKill '+ - 'getText'; - - this.regexList = [ - { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, // one line comments - { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, // multiline comments - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // strings - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // strings - { regex: /""".*"""/g, css: 'string' }, // GStrings - { regex: new RegExp('\\b([\\d]+(\\.[\\d]+)?|0x[a-f0-9]+)\\b', 'gi'), css: 'value' }, // numbers - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, // goovy keyword - { regex: new RegExp(this.getKeywords(types), 'gm'), css: 'color1' }, // goovy/java type - { regex: new RegExp(this.getKeywords(constants), 'gm'), css: 'constants' }, // constants - { regex: new RegExp(this.getKeywords(methods), 'gm'), css: 'functions' } // methods - ]; - - this.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags); - } - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['groovy']; - - SyntaxHighlighter.brushes.Groovy = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushJScript.js b/guides/assets/javascripts/syntaxhighlighter/shBrushJScript.js deleted file mode 100644 index ff98daba16..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushJScript.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - var keywords = 'break case catch continue ' + - 'default delete do else false ' + - 'for function if in instanceof ' + - 'new null return super switch ' + - 'this throw true try typeof var while with' - ; - - var r = SyntaxHighlighter.regexLib; - - this.regexList = [ - { regex: r.multiLineDoubleQuotedString, css: 'string' }, // double quoted strings - { regex: r.multiLineSingleQuotedString, css: 'string' }, // single quoted strings - { regex: r.singleLineCComments, css: 'comments' }, // one line comments - { regex: r.multiLineCComments, css: 'comments' }, // multiline comments - { regex: /\s*#.*/gm, css: 'preprocessor' }, // preprocessor tags like #region and #endregion - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } // keywords - ]; - - this.forHtmlScript(r.scriptScriptTags); - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['js', 'jscript', 'javascript']; - - SyntaxHighlighter.brushes.JScript = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushJava.js b/guides/assets/javascripts/syntaxhighlighter/shBrushJava.js deleted file mode 100644 index d692fd6382..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushJava.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - var keywords = 'abstract assert boolean break byte case catch char class const ' + - 'continue default do double else enum extends ' + - 'false final finally float for goto if implements import ' + - 'instanceof int interface long native new null ' + - 'package private protected public return ' + - 'short static strictfp super switch synchronized this throw throws true ' + - 'transient try void volatile while'; - - this.regexList = [ - { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, // one line comments - { regex: /\/\*([^\*][\s\S]*)?\*\//gm, css: 'comments' }, // multiline comments - { regex: /\/\*(?!\*\/)\*[\s\S]*?\*\//gm, css: 'preprocessor' }, // documentation comments - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // strings - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // strings - { regex: /\b([\d]+(\.[\d]+)?|0x[a-f0-9]+)\b/gi, css: 'value' }, // numbers - { regex: /(?!\@interface\b)\@[\$\w]+\b/g, css: 'color1' }, // annotation @anno - { regex: /\@interface\b/g, css: 'color2' }, // @interface keyword - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } // java keyword - ]; - - this.forHtmlScript({ - left : /(<|<)%[@!=]?/g, - right : /%(>|>)/g - }); - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['java']; - - SyntaxHighlighter.brushes.Java = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushJavaFX.js b/guides/assets/javascripts/syntaxhighlighter/shBrushJavaFX.js deleted file mode 100644 index 1a150a6ad3..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushJavaFX.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - // Contributed by Patrick Webster - // http://patrickwebster.blogspot.com/2009/04/javafx-brush-for-syntaxhighlighter.html - var datatypes = 'Boolean Byte Character Double Duration ' - + 'Float Integer Long Number Short String Void' - ; - - var keywords = 'abstract after and as assert at before bind bound break catch class ' - + 'continue def delete else exclusive extends false finally first for from ' - + 'function if import in indexof init insert instanceof into inverse last ' - + 'lazy mixin mod nativearray new not null on or override package postinit ' - + 'protected public public-init public-read replace return reverse sizeof ' - + 'step super then this throw true try tween typeof var where while with ' - + 'attribute let private readonly static trigger' - ; - - this.regexList = [ - { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, - { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, - { regex: /(-?\.?)(\b(\d*\.?\d+|\d+\.?\d*)(e[+-]?\d+)?|0x[a-f\d]+)\b\.?/gi, css: 'color2' }, // numbers - { regex: new RegExp(this.getKeywords(datatypes), 'gm'), css: 'variable' }, // datatypes - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } - ]; - this.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags); - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['jfx', 'javafx']; - - SyntaxHighlighter.brushes.JavaFX = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushPerl.js b/guides/assets/javascripts/syntaxhighlighter/shBrushPerl.js deleted file mode 100644 index d94a2e0ec5..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushPerl.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - // Contributed by David Simmons-Duffin and Marty Kube - - var funcs = - 'abs accept alarm atan2 bind binmode chdir chmod chomp chop chown chr ' + - 'chroot close closedir connect cos crypt defined delete each endgrent ' + - 'endhostent endnetent endprotoent endpwent endservent eof exec exists ' + - 'exp fcntl fileno flock fork format formline getc getgrent getgrgid ' + - 'getgrnam gethostbyaddr gethostbyname gethostent getlogin getnetbyaddr ' + - 'getnetbyname getnetent getpeername getpgrp getppid getpriority ' + - 'getprotobyname getprotobynumber getprotoent getpwent getpwnam getpwuid ' + - 'getservbyname getservbyport getservent getsockname getsockopt glob ' + - 'gmtime grep hex index int ioctl join keys kill lc lcfirst length link ' + - 'listen localtime lock log lstat map mkdir msgctl msgget msgrcv msgsnd ' + - 'oct open opendir ord pack pipe pop pos print printf prototype push ' + - 'quotemeta rand read readdir readline readlink readpipe recv rename ' + - 'reset reverse rewinddir rindex rmdir scalar seek seekdir select semctl ' + - 'semget semop send setgrent sethostent setnetent setpgrp setpriority ' + - 'setprotoent setpwent setservent setsockopt shift shmctl shmget shmread ' + - 'shmwrite shutdown sin sleep socket socketpair sort splice split sprintf ' + - 'sqrt srand stat study substr symlink syscall sysopen sysread sysseek ' + - 'system syswrite tell telldir time times tr truncate uc ucfirst umask ' + - 'undef unlink unpack unshift utime values vec wait waitpid warn write'; - - var keywords = - 'bless caller continue dbmclose dbmopen die do dump else elsif eval exit ' + - 'for foreach goto if import last local my next no our package redo ref ' + - 'require return sub tie tied unless untie until use wantarray while'; - - this.regexList = [ - { regex: new RegExp('#[^!].*$', 'gm'), css: 'comments' }, - { regex: new RegExp('^\\s*#!.*$', 'gm'), css: 'preprocessor' }, // shebang - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, - { regex: new RegExp('(\\$|@|%)\\w+', 'g'), css: 'variable' }, - { regex: new RegExp(this.getKeywords(funcs), 'gmi'), css: 'functions' }, - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } - ]; - - this.forHtmlScript(SyntaxHighlighter.regexLib.phpScriptTags); - } - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['perl', 'Perl', 'pl']; - - SyntaxHighlighter.brushes.Perl = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushPhp.js b/guides/assets/javascripts/syntaxhighlighter/shBrushPhp.js deleted file mode 100644 index 95e6e4325b..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushPhp.js +++ /dev/null @@ -1,88 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - var funcs = 'abs acos acosh addcslashes addslashes ' + - 'array_change_key_case array_chunk array_combine array_count_values array_diff '+ - 'array_diff_assoc array_diff_key array_diff_uassoc array_diff_ukey array_fill '+ - 'array_filter array_flip array_intersect array_intersect_assoc array_intersect_key '+ - 'array_intersect_uassoc array_intersect_ukey array_key_exists array_keys array_map '+ - 'array_merge array_merge_recursive array_multisort array_pad array_pop array_product '+ - 'array_push array_rand array_reduce array_reverse array_search array_shift '+ - 'array_slice array_splice array_sum array_udiff array_udiff_assoc '+ - 'array_udiff_uassoc array_uintersect array_uintersect_assoc '+ - 'array_uintersect_uassoc array_unique array_unshift array_values array_walk '+ - 'array_walk_recursive atan atan2 atanh base64_decode base64_encode base_convert '+ - 'basename bcadd bccomp bcdiv bcmod bcmul bindec bindtextdomain bzclose bzcompress '+ - 'bzdecompress bzerrno bzerror bzerrstr bzflush bzopen bzread bzwrite ceil chdir '+ - 'checkdate checkdnsrr chgrp chmod chop chown chr chroot chunk_split class_exists '+ - 'closedir closelog copy cos cosh count count_chars date decbin dechex decoct '+ - 'deg2rad delete ebcdic2ascii echo empty end ereg ereg_replace eregi eregi_replace error_log '+ - 'error_reporting escapeshellarg escapeshellcmd eval exec exit exp explode extension_loaded '+ - 'feof fflush fgetc fgetcsv fgets fgetss file_exists file_get_contents file_put_contents '+ - 'fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype '+ - 'floatval flock floor flush fmod fnmatch fopen fpassthru fprintf fputcsv fputs fread fscanf '+ - 'fseek fsockopen fstat ftell ftok getallheaders getcwd getdate getenv gethostbyaddr gethostbyname '+ - 'gethostbynamel getimagesize getlastmod getmxrr getmygid getmyinode getmypid getmyuid getopt '+ - 'getprotobyname getprotobynumber getrandmax getrusage getservbyname getservbyport gettext '+ - 'gettimeofday gettype glob gmdate gmmktime ini_alter ini_get ini_get_all ini_restore ini_set '+ - 'interface_exists intval ip2long is_a is_array is_bool is_callable is_dir is_double '+ - 'is_executable is_file is_finite is_float is_infinite is_int is_integer is_link is_long '+ - 'is_nan is_null is_numeric is_object is_readable is_real is_resource is_scalar is_soap_fault '+ - 'is_string is_subclass_of is_uploaded_file is_writable is_writeable mkdir mktime nl2br '+ - 'parse_ini_file parse_str parse_url passthru pathinfo print readlink realpath rewind rewinddir rmdir '+ - 'round str_ireplace str_pad str_repeat str_replace str_rot13 str_shuffle str_split '+ - 'str_word_count strcasecmp strchr strcmp strcoll strcspn strftime strip_tags stripcslashes '+ - 'stripos stripslashes stristr strlen strnatcasecmp strnatcmp strncasecmp strncmp strpbrk '+ - 'strpos strptime strrchr strrev strripos strrpos strspn strstr strtok strtolower strtotime '+ - 'strtoupper strtr strval substr substr_compare'; - - var keywords = 'abstract and array as break case catch cfunction class clone const continue declare default die do ' + - 'else elseif enddeclare endfor endforeach endif endswitch endwhile extends final for foreach ' + - 'function include include_once global goto if implements interface instanceof namespace new ' + - 'old_function or private protected public return require require_once static switch ' + - 'throw try use var while xor '; - - var constants = '__FILE__ __LINE__ __METHOD__ __FUNCTION__ __CLASS__'; - - this.regexList = [ - { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, // one line comments - { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, // multiline comments - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // double quoted strings - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // single quoted strings - { regex: /\$\w+/g, css: 'variable' }, // variables - { regex: new RegExp(this.getKeywords(funcs), 'gmi'), css: 'functions' }, // common functions - { regex: new RegExp(this.getKeywords(constants), 'gmi'), css: 'constants' }, // constants - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } // keyword - ]; - - this.forHtmlScript(SyntaxHighlighter.regexLib.phpScriptTags); - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['php']; - - SyntaxHighlighter.brushes.Php = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushPlain.js b/guides/assets/javascripts/syntaxhighlighter/shBrushPlain.js deleted file mode 100644 index 9f7d9e90c3..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushPlain.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['text', 'plain']; - - SyntaxHighlighter.brushes.Plain = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushPowerShell.js b/guides/assets/javascripts/syntaxhighlighter/shBrushPowerShell.js deleted file mode 100644 index 0be1752968..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushPowerShell.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - // Contributes by B.v.Zanten, Getronics - // http://confluence.atlassian.com/display/CONFEXT/New+Code+Macro - - var keywords = 'Add-Content Add-History Add-Member Add-PSSnapin Clear(-Content)? Clear-Item ' + - 'Clear-ItemProperty Clear-Variable Compare-Object ConvertFrom-SecureString Convert-Path ' + - 'ConvertTo-Html ConvertTo-SecureString Copy(-Item)? Copy-ItemProperty Export-Alias ' + - 'Export-Clixml Export-Console Export-Csv ForEach(-Object)? Format-Custom Format-List ' + - 'Format-Table Format-Wide Get-Acl Get-Alias Get-AuthenticodeSignature Get-ChildItem Get-Command ' + - 'Get-Content Get-Credential Get-Culture Get-Date Get-EventLog Get-ExecutionPolicy ' + - 'Get-Help Get-History Get-Host Get-Item Get-ItemProperty Get-Location Get-Member ' + - 'Get-PfxCertificate Get-Process Get-PSDrive Get-PSProvider Get-PSSnapin Get-Service ' + - 'Get-TraceSource Get-UICulture Get-Unique Get-Variable Get-WmiObject Group-Object ' + - 'Import-Alias Import-Clixml Import-Csv Invoke-Expression Invoke-History Invoke-Item ' + - 'Join-Path Measure-Command Measure-Object Move(-Item)? Move-ItemProperty New-Alias ' + - 'New-Item New-ItemProperty New-Object New-PSDrive New-Service New-TimeSpan ' + - 'New-Variable Out-Default Out-File Out-Host Out-Null Out-Printer Out-String Pop-Location ' + - 'Push-Location Read-Host Remove-Item Remove-ItemProperty Remove-PSDrive Remove-PSSnapin ' + - 'Remove-Variable Rename-Item Rename-ItemProperty Resolve-Path Restart-Service Resume-Service ' + - 'Select-Object Select-String Set-Acl Set-Alias Set-AuthenticodeSignature Set-Content ' + - 'Set-Date Set-ExecutionPolicy Set-Item Set-ItemProperty Set-Location Set-PSDebug ' + - 'Set-Service Set-TraceSource Set(-Variable)? Sort-Object Split-Path Start-Service ' + - 'Start-Sleep Start-Transcript Stop-Process Stop-Service Stop-Transcript Suspend-Service ' + - 'Tee-Object Test-Path Trace-Command Update-FormatData Update-TypeData Where(-Object)? ' + - 'Write-Debug Write-Error Write(-Host)? Write-Output Write-Progress Write-Verbose Write-Warning'; - var alias = 'ac asnp clc cli clp clv cpi cpp cvpa diff epal epcsv fc fl ' + - 'ft fw gal gc gci gcm gdr ghy gi gl gm gp gps group gsv ' + - 'gsnp gu gv gwmi iex ihy ii ipal ipcsv mi mp nal ndr ni nv oh rdr ' + - 'ri rni rnp rp rsnp rv rvpa sal sasv sc select si sl sleep sort sp ' + - 'spps spsv sv tee cat cd cp h history kill lp ls ' + - 'mount mv popd ps pushd pwd r rm rmdir echo cls chdir del dir ' + - 'erase rd ren type % \\?'; - - this.regexList = [ - { regex: /#.*$/gm, css: 'comments' }, // one line comments - { regex: /\$[a-zA-Z0-9]+\b/g, css: 'value' }, // variables $Computer1 - { regex: /\-[a-zA-Z]+\b/g, css: 'keyword' }, // Operators -not -and -eq - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // strings - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // strings - { regex: new RegExp(this.getKeywords(keywords), 'gmi'), css: 'keyword' }, - { regex: new RegExp(this.getKeywords(alias), 'gmi'), css: 'keyword' } - ]; - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['powershell', 'ps']; - - SyntaxHighlighter.brushes.PowerShell = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushPython.js b/guides/assets/javascripts/syntaxhighlighter/shBrushPython.js deleted file mode 100644 index ce77462975..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushPython.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - // Contributed by Gheorghe Milas and Ahmad Sherif - - var keywords = 'and assert break class continue def del elif else ' + - 'except exec finally for from global if import in is ' + - 'lambda not or pass print raise return try yield while'; - - var funcs = '__import__ abs all any apply basestring bin bool buffer callable ' + - 'chr classmethod cmp coerce compile complex delattr dict dir ' + - 'divmod enumerate eval execfile file filter float format frozenset ' + - 'getattr globals hasattr hash help hex id input int intern ' + - 'isinstance issubclass iter len list locals long map max min next ' + - 'object oct open ord pow print property range raw_input reduce ' + - 'reload repr reversed round set setattr slice sorted staticmethod ' + - 'str sum super tuple type type unichr unicode vars xrange zip'; - - var special = 'None True False self cls class_'; - - this.regexList = [ - { regex: SyntaxHighlighter.regexLib.singleLinePerlComments, css: 'comments' }, - { regex: /^\s*@\w+/gm, css: 'decorator' }, - { regex: /(['\"]{3})([^\1])*?\1/gm, css: 'comments' }, - { regex: /"(?!")(?:\.|\\\"|[^\""\n])*"/gm, css: 'string' }, - { regex: /'(?!')(?:\.|(\\\')|[^\''\n])*'/gm, css: 'string' }, - { regex: /\+|\-|\*|\/|\%|=|==/gm, css: 'keyword' }, - { regex: /\b\d+\.?\w*/g, css: 'value' }, - { regex: new RegExp(this.getKeywords(funcs), 'gmi'), css: 'functions' }, - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, - { regex: new RegExp(this.getKeywords(special), 'gm'), css: 'color1' } - ]; - - this.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags); - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['py', 'python']; - - SyntaxHighlighter.brushes.Python = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushRuby.js b/guides/assets/javascripts/syntaxhighlighter/shBrushRuby.js deleted file mode 100644 index ff82130a7a..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushRuby.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - // Contributed by Erik Peterson. - - var keywords = 'alias and BEGIN begin break case class def define_method defined do each else elsif ' + - 'END end ensure false for if in module new next nil not or raise redo rescue retry return ' + - 'self super then throw true undef unless until when while yield'; - - var builtins = 'Array Bignum Binding Class Continuation Dir Exception FalseClass File::Stat File Fixnum Fload ' + - 'Hash Integer IO MatchData Method Module NilClass Numeric Object Proc Range Regexp String Struct::TMS Symbol ' + - 'ThreadGroup Thread Time TrueClass'; - - this.regexList = [ - { regex: SyntaxHighlighter.regexLib.singleLinePerlComments, css: 'comments' }, // one line comments - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // double quoted strings - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // single quoted strings - { regex: /\b[A-Z0-9_]+\b/g, css: 'constants' }, // constants - { regex: /:[a-z][A-Za-z0-9_]*/g, css: 'color2' }, // symbols - { regex: /(\$|@@|@)\w+/g, css: 'variable bold' }, // $global, @instance, and @@class variables - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, // keywords - { regex: new RegExp(this.getKeywords(builtins), 'gm'), css: 'color1' } // builtins - ]; - - this.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags); - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['ruby', 'rails', 'ror', 'rb']; - - SyntaxHighlighter.brushes.Ruby = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushSass.js b/guides/assets/javascripts/syntaxhighlighter/shBrushSass.js deleted file mode 100644 index aa04da0996..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushSass.js +++ /dev/null @@ -1,94 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - function getKeywordsCSS(str) - { - return '\\b([a-z_]|)' + str.replace(/ /g, '(?=:)\\b|\\b([a-z_\\*]|\\*|)') + '(?=:)\\b'; - }; - - function getValuesCSS(str) - { - return '\\b' + str.replace(/ /g, '(?!-)(?!:)\\b|\\b()') + '\:\\b'; - }; - - var keywords = 'ascent azimuth background-attachment background-color background-image background-position ' + - 'background-repeat background baseline bbox border-collapse border-color border-spacing border-style border-top ' + - 'border-right border-bottom border-left border-top-color border-right-color border-bottom-color border-left-color ' + - 'border-top-style border-right-style border-bottom-style border-left-style border-top-width border-right-width ' + - 'border-bottom-width border-left-width border-width border bottom cap-height caption-side centerline clear clip color ' + - 'content counter-increment counter-reset cue-after cue-before cue cursor definition-src descent direction display ' + - 'elevation empty-cells float font-size-adjust font-family font-size font-stretch font-style font-variant font-weight font ' + - 'height left letter-spacing line-height list-style-image list-style-position list-style-type list-style margin-top ' + - 'margin-right margin-bottom margin-left margin marker-offset marks mathline max-height max-width min-height min-width orphans ' + - 'outline-color outline-style outline-width outline overflow padding-top padding-right padding-bottom padding-left padding page ' + - 'page-break-after page-break-before page-break-inside pause pause-after pause-before pitch pitch-range play-during position ' + - 'quotes right richness size slope src speak-header speak-numeral speak-punctuation speak speech-rate stemh stemv stress ' + - 'table-layout text-align top text-decoration text-indent text-shadow text-transform unicode-bidi unicode-range units-per-em ' + - 'vertical-align visibility voice-family volume white-space widows width widths word-spacing x-height z-index'; - - var values = 'above absolute all always aqua armenian attr aural auto avoid baseline behind below bidi-override black blink block blue bold bolder '+ - 'both bottom braille capitalize caption center center-left center-right circle close-quote code collapse compact condensed '+ - 'continuous counter counters crop cross crosshair cursive dashed decimal decimal-leading-zero digits disc dotted double '+ - 'embed embossed e-resize expanded extra-condensed extra-expanded fantasy far-left far-right fast faster fixed format fuchsia '+ - 'gray green groove handheld hebrew help hidden hide high higher icon inline-table inline inset inside invert italic '+ - 'justify landscape large larger left-side left leftwards level lighter lime line-through list-item local loud lower-alpha '+ - 'lowercase lower-greek lower-latin lower-roman lower low ltr marker maroon medium message-box middle mix move narrower '+ - 'navy ne-resize no-close-quote none no-open-quote no-repeat normal nowrap n-resize nw-resize oblique olive once open-quote outset '+ - 'outside overline pointer portrait pre print projection purple red relative repeat repeat-x repeat-y rgb ridge right right-side '+ - 'rightwards rtl run-in screen scroll semi-condensed semi-expanded separate se-resize show silent silver slower slow '+ - 'small small-caps small-caption smaller soft solid speech spell-out square s-resize static status-bar sub super sw-resize '+ - 'table-caption table-cell table-column table-column-group table-footer-group table-header-group table-row table-row-group teal '+ - 'text-bottom text-top thick thin top transparent tty tv ultra-condensed ultra-expanded underline upper-alpha uppercase upper-latin '+ - 'upper-roman url visible wait white wider w-resize x-fast x-high x-large x-loud x-low x-slow x-small x-soft xx-large xx-small yellow'; - - var fonts = '[mM]onospace [tT]ahoma [vV]erdana [aA]rial [hH]elvetica [sS]ans-serif [sS]erif [cC]ourier mono sans serif'; - - var statements = '!important !default'; - var preprocessor = '@import @extend @debug @warn @if @for @while @mixin @include'; - - var r = SyntaxHighlighter.regexLib; - - this.regexList = [ - { regex: r.multiLineCComments, css: 'comments' }, // multiline comments - { regex: r.singleLineCComments, css: 'comments' }, // singleline comments - { regex: r.doubleQuotedString, css: 'string' }, // double quoted strings - { regex: r.singleQuotedString, css: 'string' }, // single quoted strings - { regex: /\#[a-fA-F0-9]{3,6}/g, css: 'value' }, // html colors - { regex: /\b(-?\d+)(\.\d+)?(px|em|pt|\:|\%|)\b/g, css: 'value' }, // sizes - { regex: /\$\w+/g, css: 'variable' }, // variables - { regex: new RegExp(this.getKeywords(statements), 'g'), css: 'color3' }, // statements - { regex: new RegExp(this.getKeywords(preprocessor), 'g'), css: 'preprocessor' }, // preprocessor - { regex: new RegExp(getKeywordsCSS(keywords), 'gm'), css: 'keyword' }, // keywords - { regex: new RegExp(getValuesCSS(values), 'g'), css: 'value' }, // values - { regex: new RegExp(this.getKeywords(fonts), 'g'), css: 'color1' } // fonts - ]; - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['sass', 'scss']; - - SyntaxHighlighter.brushes.Sass = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushScala.js b/guides/assets/javascripts/syntaxhighlighter/shBrushScala.js deleted file mode 100644 index 4b0b6f04d2..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushScala.js +++ /dev/null @@ -1,51 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - // Contributed by Yegor Jbanov and David Bernard. - - var keywords = 'val sealed case def true trait implicit forSome import match object null finally super ' + - 'override try lazy for var catch throw type extends class while with new final yield abstract ' + - 'else do if return protected private this package false'; - - var keyops = '[_:=><%#@]+'; - - this.regexList = [ - { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, // one line comments - { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, // multiline comments - { regex: SyntaxHighlighter.regexLib.multiLineSingleQuotedString, css: 'string' }, // multi-line strings - { regex: SyntaxHighlighter.regexLib.multiLineDoubleQuotedString, css: 'string' }, // double-quoted string - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // strings - { regex: /0x[a-f0-9]+|\d+(\.\d+)?/gi, css: 'value' }, // numbers - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, // keywords - { regex: new RegExp(keyops, 'gm'), css: 'keyword' } // scala keyword - ]; - } - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['scala']; - - SyntaxHighlighter.brushes.Scala = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushSql.js b/guides/assets/javascripts/syntaxhighlighter/shBrushSql.js deleted file mode 100644 index 5c2cd8806f..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushSql.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - var funcs = 'abs avg case cast coalesce convert count current_timestamp ' + - 'current_user day isnull left lower month nullif replace right ' + - 'session_user space substring sum system_user upper user year'; - - var keywords = 'absolute action add after alter as asc at authorization begin bigint ' + - 'binary bit by cascade char character check checkpoint close collate ' + - 'column commit committed connect connection constraint contains continue ' + - 'create cube current current_date current_time cursor database date ' + - 'deallocate dec decimal declare default delete desc distinct double drop ' + - 'dynamic else end end-exec escape except exec execute false fetch first ' + - 'float for force foreign forward free from full function global goto grant ' + - 'group grouping having hour ignore index inner insensitive insert instead ' + - 'int integer intersect into is isolation key last level load local max min ' + - 'minute modify move name national nchar next no numeric of off on only ' + - 'open option order out output partial password precision prepare primary ' + - 'prior privileges procedure public read real references relative repeatable ' + - 'restrict return returns revoke rollback rollup rows rule schema scroll ' + - 'second section select sequence serializable set size smallint static ' + - 'statistics table temp temporary then time timestamp to top transaction ' + - 'translation trigger true truncate uncommitted union unique update values ' + - 'varchar varying view when where with work'; - - var operators = 'all and any between cross in join like not null or outer some'; - - this.regexList = [ - { regex: /--(.*)$/gm, css: 'comments' }, // one line and multiline comments - { regex: SyntaxHighlighter.regexLib.multiLineDoubleQuotedString, css: 'string' }, // double quoted strings - { regex: SyntaxHighlighter.regexLib.multiLineSingleQuotedString, css: 'string' }, // single quoted strings - { regex: new RegExp(this.getKeywords(funcs), 'gmi'), css: 'color2' }, // functions - { regex: new RegExp(this.getKeywords(operators), 'gmi'), css: 'color1' }, // operators and such - { regex: new RegExp(this.getKeywords(keywords), 'gmi'), css: 'keyword' } // keyword - ]; - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['sql']; - - SyntaxHighlighter.brushes.Sql = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); - diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushVb.js b/guides/assets/javascripts/syntaxhighlighter/shBrushVb.js deleted file mode 100644 index be845dc0b3..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushVb.js +++ /dev/null @@ -1,56 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - var keywords = 'AddHandler AddressOf AndAlso Alias And Ansi As Assembly Auto ' + - 'Boolean ByRef Byte ByVal Call Case Catch CBool CByte CChar CDate ' + - 'CDec CDbl Char CInt Class CLng CObj Const CShort CSng CStr CType ' + - 'Date Decimal Declare Default Delegate Dim DirectCast Do Double Each ' + - 'Else ElseIf End Enum Erase Error Event Exit False Finally For Friend ' + - 'Function Get GetType GoSub GoTo Handles If Implements Imports In ' + - 'Inherits Integer Interface Is Let Lib Like Long Loop Me Mod Module ' + - 'MustInherit MustOverride MyBase MyClass Namespace New Next Not Nothing ' + - 'NotInheritable NotOverridable Object On Option Optional Or OrElse ' + - 'Overloads Overridable Overrides ParamArray Preserve Private Property ' + - 'Protected Public RaiseEvent ReadOnly ReDim REM RemoveHandler Resume ' + - 'Return Select Set Shadows Shared Short Single Static Step Stop String ' + - 'Structure Sub SyncLock Then Throw To True Try TypeOf Unicode Until ' + - 'Variant When While With WithEvents WriteOnly Xor'; - - this.regexList = [ - { regex: /'.*$/gm, css: 'comments' }, // one line comments - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // strings - { regex: /^\s*#.*$/gm, css: 'preprocessor' }, // preprocessor tags like #region and #endregion - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } // vb keyword - ]; - - this.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags); - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['vb', 'vbnet']; - - SyntaxHighlighter.brushes.Vb = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/guides/assets/javascripts/syntaxhighlighter/shBrushXml.js b/guides/assets/javascripts/syntaxhighlighter/shBrushXml.js deleted file mode 100644 index 69d9fd0b1f..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shBrushXml.js +++ /dev/null @@ -1,69 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - function process(match, regexInfo) - { - var constructor = SyntaxHighlighter.Match, - code = match[0], - tag = new XRegExp('(<|<)[\\s\\/\\?]*(?<name>[:\\w-\\.]+)', 'xg').exec(code), - result = [] - ; - - if (match.attributes != null) - { - var attributes, - regex = new XRegExp('(?<name> [\\w:\\-\\.]+)' + - '\\s*=\\s*' + - '(?<value> ".*?"|\'.*?\'|\\w+)', - 'xg'); - - while ((attributes = regex.exec(code)) != null) - { - result.push(new constructor(attributes.name, match.index + attributes.index, 'color1')); - result.push(new constructor(attributes.value, match.index + attributes.index + attributes[0].indexOf(attributes.value), 'string')); - } - } - - if (tag != null) - result.push( - new constructor(tag.name, match.index + tag[0].indexOf(tag.name), 'keyword') - ); - - return result; - } - - this.regexList = [ - { regex: new XRegExp('(\\<|<)\\!\\[[\\w\\s]*?\\[(.|\\s)*?\\]\\](\\>|>)', 'gm'), css: 'color2' }, // <![ ... [ ... ]]> - { regex: SyntaxHighlighter.regexLib.xmlComments, css: 'comments' }, // <!-- ... --> - { regex: new XRegExp('(<|<)[\\s\\/\\?]*(\\w+)(?<attributes>.*?)[\\s\\/\\?]*(>|>)', 'sg'), func: process } - ]; - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['xml', 'xhtml', 'xslt', 'html']; - - SyntaxHighlighter.brushes.Xml = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/guides/assets/javascripts/syntaxhighlighter/shCore.js b/guides/assets/javascripts/syntaxhighlighter/shCore.js deleted file mode 100644 index b47b645472..0000000000 --- a/guides/assets/javascripts/syntaxhighlighter/shCore.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('K M;I(M)1S 2U("2a\'t 4k M 4K 2g 3l 4G 4H");(6(){6 r(f,e){I(!M.1R(f))1S 3m("3s 15 4R");K a=f.1w;f=M(f.1m,t(f)+(e||""));I(a)f.1w={1m:a.1m,19:a.19?a.19.1a(0):N};H f}6 t(f){H(f.1J?"g":"")+(f.4s?"i":"")+(f.4p?"m":"")+(f.4v?"x":"")+(f.3n?"y":"")}6 B(f,e,a,b){K c=u.L,d,h,g;v=R;5K{O(;c--;){g=u[c];I(a&g.3r&&(!g.2p||g.2p.W(b))){g.2q.12=e;I((h=g.2q.X(f))&&h.P===e){d={3k:g.2b.W(b,h,a),1C:h};1N}}}}5v(i){1S i}5q{v=11}H d}6 p(f,e,a){I(3b.Z.1i)H f.1i(e,a);O(a=a||0;a<f.L;a++)I(f[a]===e)H a;H-1}M=6(f,e){K a=[],b=M.1B,c=0,d,h;I(M.1R(f)){I(e!==1d)1S 3m("2a\'t 5r 5I 5F 5B 5C 15 5E 5p");H r(f)}I(v)1S 2U("2a\'t W 3l M 59 5m 5g 5x 5i");e=e||"";O(d={2N:11,19:[],2K:6(g){H e.1i(g)>-1},3d:6(g){e+=g}};c<f.L;)I(h=B(f,c,b,d)){a.U(h.3k);c+=h.1C[0].L||1}Y I(h=n.X.W(z[b],f.1a(c))){a.U(h[0]);c+=h[0].L}Y{h=f.3a(c);I(h==="[")b=M.2I;Y I(h==="]")b=M.1B;a.U(h);c++}a=15(a.1K(""),n.Q.W(e,w,""));a.1w={1m:f,19:d.2N?d.19:N};H a};M.3v="1.5.0";M.2I=1;M.1B=2;K C=/\\$(?:(\\d\\d?|[$&`\'])|{([$\\w]+)})/g,w=/[^5h]+|([\\s\\S])(?=[\\s\\S]*\\1)/g,A=/^(?:[?*+]|{\\d+(?:,\\d*)?})\\??/,v=11,u=[],n={X:15.Z.X,1A:15.Z.1A,1C:1r.Z.1C,Q:1r.Z.Q,1e:1r.Z.1e},x=n.X.W(/()??/,"")[1]===1d,D=6(){K f=/^/g;n.1A.W(f,"");H!f.12}(),y=6(){K f=/x/g;n.Q.W("x",f,"");H!f.12}(),E=15.Z.3n!==1d,z={};z[M.2I]=/^(?:\\\\(?:[0-3][0-7]{0,2}|[4-7][0-7]?|x[\\29-26-f]{2}|u[\\29-26-f]{4}|c[A-3o-z]|[\\s\\S]))/;z[M.1B]=/^(?:\\\\(?:0(?:[0-3][0-7]{0,2}|[4-7][0-7]?)?|[1-9]\\d*|x[\\29-26-f]{2}|u[\\29-26-f]{4}|c[A-3o-z]|[\\s\\S])|\\(\\?[:=!]|[?*+]\\?|{\\d+(?:,\\d*)?}\\??)/;M.1h=6(f,e,a,b){u.U({2q:r(f,"g"+(E?"y":"")),2b:e,3r:a||M.1B,2p:b||N})};M.2n=6(f,e){K a=f+"/"+(e||"");H M.2n[a]||(M.2n[a]=M(f,e))};M.3c=6(f){H r(f,"g")};M.5l=6(f){H f.Q(/[-[\\]{}()*+?.,\\\\^$|#\\s]/g,"\\\\$&")};M.5e=6(f,e,a,b){e=r(e,"g"+(b&&E?"y":""));e.12=a=a||0;f=e.X(f);H b?f&&f.P===a?f:N:f};M.3q=6(){M.1h=6(){1S 2U("2a\'t 55 1h 54 3q")}};M.1R=6(f){H 53.Z.1q.W(f)==="[2m 15]"};M.3p=6(f,e,a,b){O(K c=r(e,"g"),d=-1,h;h=c.X(f);){a.W(b,h,++d,f,c);c.12===h.P&&c.12++}I(e.1J)e.12=0};M.57=6(f,e){H 6 a(b,c){K d=e[c].1I?e[c]:{1I:e[c]},h=r(d.1I,"g"),g=[],i;O(i=0;i<b.L;i++)M.3p(b[i],h,6(k){g.U(d.3j?k[d.3j]||"":k[0])});H c===e.L-1||!g.L?g:a(g,c+1)}([f],0)};15.Z.1p=6(f,e){H J.X(e[0])};15.Z.W=6(f,e){H J.X(e)};15.Z.X=6(f){K e=n.X.1p(J,14),a;I(e){I(!x&&e.L>1&&p(e,"")>-1){a=15(J.1m,n.Q.W(t(J),"g",""));n.Q.W(f.1a(e.P),a,6(){O(K c=1;c<14.L-2;c++)I(14[c]===1d)e[c]=1d})}I(J.1w&&J.1w.19)O(K b=1;b<e.L;b++)I(a=J.1w.19[b-1])e[a]=e[b];!D&&J.1J&&!e[0].L&&J.12>e.P&&J.12--}H e};I(!D)15.Z.1A=6(f){(f=n.X.W(J,f))&&J.1J&&!f[0].L&&J.12>f.P&&J.12--;H!!f};1r.Z.1C=6(f){M.1R(f)||(f=15(f));I(f.1J){K e=n.1C.1p(J,14);f.12=0;H e}H f.X(J)};1r.Z.Q=6(f,e){K a=M.1R(f),b,c;I(a&&1j e.58()==="3f"&&e.1i("${")===-1&&y)H n.Q.1p(J,14);I(a){I(f.1w)b=f.1w.19}Y f+="";I(1j e==="6")c=n.Q.W(J,f,6(){I(b){14[0]=1f 1r(14[0]);O(K d=0;d<b.L;d++)I(b[d])14[0][b[d]]=14[d+1]}I(a&&f.1J)f.12=14[14.L-2]+14[0].L;H e.1p(N,14)});Y{c=J+"";c=n.Q.W(c,f,6(){K d=14;H n.Q.W(e,C,6(h,g,i){I(g)5b(g){24"$":H"$";24"&":H d[0];24"`":H d[d.L-1].1a(0,d[d.L-2]);24"\'":H d[d.L-1].1a(d[d.L-2]+d[0].L);5a:i="";g=+g;I(!g)H h;O(;g>d.L-3;){i=1r.Z.1a.W(g,-1)+i;g=1Q.3i(g/10)}H(g?d[g]||"":"$")+i}Y{g=+i;I(g<=d.L-3)H d[g];g=b?p(b,i):-1;H g>-1?d[g+1]:h}})})}I(a&&f.1J)f.12=0;H c};1r.Z.1e=6(f,e){I(!M.1R(f))H n.1e.1p(J,14);K a=J+"",b=[],c=0,d,h;I(e===1d||+e<0)e=5D;Y{e=1Q.3i(+e);I(!e)H[]}O(f=M.3c(f);d=f.X(a);){I(f.12>c){b.U(a.1a(c,d.P));d.L>1&&d.P<a.L&&3b.Z.U.1p(b,d.1a(1));h=d[0].L;c=f.12;I(b.L>=e)1N}f.12===d.P&&f.12++}I(c===a.L){I(!n.1A.W(f,"")||h)b.U("")}Y b.U(a.1a(c));H b.L>e?b.1a(0,e):b};M.1h(/\\(\\?#[^)]*\\)/,6(f){H n.1A.W(A,f.2S.1a(f.P+f[0].L))?"":"(?:)"});M.1h(/\\((?!\\?)/,6(){J.19.U(N);H"("});M.1h(/\\(\\?<([$\\w]+)>/,6(f){J.19.U(f[1]);J.2N=R;H"("});M.1h(/\\\\k<([\\w$]+)>/,6(f){K e=p(J.19,f[1]);H e>-1?"\\\\"+(e+1)+(3R(f.2S.3a(f.P+f[0].L))?"":"(?:)"):f[0]});M.1h(/\\[\\^?]/,6(f){H f[0]==="[]"?"\\\\b\\\\B":"[\\\\s\\\\S]"});M.1h(/^\\(\\?([5A]+)\\)/,6(f){J.3d(f[1]);H""});M.1h(/(?:\\s+|#.*)+/,6(f){H n.1A.W(A,f.2S.1a(f.P+f[0].L))?"":"(?:)"},M.1B,6(){H J.2K("x")});M.1h(/\\./,6(){H"[\\\\s\\\\S]"},M.1B,6(){H J.2K("s")})})();1j 2e!="1d"&&(2e.M=M);K 1v=6(){6 r(a,b){a.1l.1i(b)!=-1||(a.1l+=" "+b)}6 t(a){H a.1i("3e")==0?a:"3e"+a}6 B(a){H e.1Y.2A[t(a)]}6 p(a,b,c){I(a==N)H N;K d=c!=R?a.3G:[a.2G],h={"#":"1c",".":"1l"}[b.1o(0,1)]||"3h",g,i;g=h!="3h"?b.1o(1):b.5u();I((a[h]||"").1i(g)!=-1)H a;O(a=0;d&&a<d.L&&i==N;a++)i=p(d[a],b,c);H i}6 C(a,b){K c={},d;O(d 2g a)c[d]=a[d];O(d 2g b)c[d]=b[d];H c}6 w(a,b,c,d){6 h(g){g=g||1P.5y;I(!g.1F){g.1F=g.52;g.3N=6(){J.5w=11}}c.W(d||1P,g)}a.3g?a.3g("4U"+b,h):a.4y(b,h,11)}6 A(a,b){K c=e.1Y.2j,d=N;I(c==N){c={};O(K h 2g e.1U){K g=e.1U[h];d=g.4x;I(d!=N){g.1V=h.4w();O(g=0;g<d.L;g++)c[d[g]]=h}}e.1Y.2j=c}d=e.1U[c[a]];d==N&&b!=11&&1P.1X(e.13.1x.1X+(e.13.1x.3E+a));H d}6 v(a,b){O(K c=a.1e("\\n"),d=0;d<c.L;d++)c[d]=b(c[d],d);H c.1K("\\n")}6 u(a,b){I(a==N||a.L==0||a=="\\n")H a;a=a.Q(/</g,"&1y;");a=a.Q(/ {2,}/g,6(c){O(K d="",h=0;h<c.L-1;h++)d+=e.13.1W;H d+" "});I(b!=N)a=v(a,6(c){I(c.L==0)H"";K d="";c=c.Q(/^(&2s;| )+/,6(h){d=h;H""});I(c.L==0)H d;H d+\'<17 1g="\'+b+\'">\'+c+"</17>"});H a}6 n(a,b){a.1e("\\n");O(K c="",d=0;d<50;d++)c+=" ";H a=v(a,6(h){I(h.1i("\\t")==-1)H h;O(K g=0;(g=h.1i("\\t"))!=-1;)h=h.1o(0,g)+c.1o(0,b-g%b)+h.1o(g+1,h.L);H h})}6 x(a){H a.Q(/^\\s+|\\s+$/g,"")}6 D(a,b){I(a.P<b.P)H-1;Y I(a.P>b.P)H 1;Y I(a.L<b.L)H-1;Y I(a.L>b.L)H 1;H 0}6 y(a,b){6 c(k){H k[0]}O(K d=N,h=[],g=b.2D?b.2D:c;(d=b.1I.X(a))!=N;){K i=g(d,b);I(1j i=="3f")i=[1f e.2L(i,d.P,b.23)];h=h.1O(i)}H h}6 E(a){K b=/(.*)((&1G;|&1y;).*)/;H a.Q(e.3A.3M,6(c){K d="",h=N;I(h=b.X(c)){c=h[1];d=h[2]}H\'<a 2h="\'+c+\'">\'+c+"</a>"+d})}6 z(){O(K a=1E.36("1k"),b=[],c=0;c<a.L;c++)a[c].3s=="20"&&b.U(a[c]);H b}6 f(a){a=a.1F;K b=p(a,".20",R);a=p(a,".3O",R);K c=1E.4i("3t");I(!(!a||!b||p(a,"3t"))){B(b.1c);r(b,"1m");O(K d=a.3G,h=[],g=0;g<d.L;g++)h.U(d[g].4z||d[g].4A);h=h.1K("\\r");c.39(1E.4D(h));a.39(c);c.2C();c.4C();w(c,"4u",6(){c.2G.4E(c);b.1l=b.1l.Q("1m","")})}}I(1j 3F!="1d"&&1j M=="1d")M=3F("M").M;K e={2v:{"1g-27":"","2i-1s":1,"2z-1s-2t":11,1M:N,1t:N,"42-45":R,"43-22":4,1u:R,16:R,"3V-17":R,2l:11,"41-40":R,2k:11,"1z-1k":11},13:{1W:"&2s;",2M:R,46:11,44:11,34:"4n",1x:{21:"4o 1m",2P:"?",1X:"1v\\n\\n",3E:"4r\'t 4t 1D O: ",4g:"4m 4B\'t 51 O 1z-1k 4F: ",37:\'<!4T 1z 4S "-//4V//3H 4W 1.0 4Z//4Y" "1Z://2y.3L.3K/4X/3I/3H/3I-4P.4J"><1z 4I="1Z://2y.3L.3K/4L/5L"><3J><4N 1Z-4M="5G-5M" 6K="2O/1z; 6J=6I-8" /><1t>6L 1v</1t></3J><3B 1L="25-6M:6Q,6P,6O,6N-6F;6y-2f:#6x;2f:#6w;25-22:6v;2O-3D:3C;"><T 1L="2O-3D:3C;3w-32:1.6z;"><T 1L="25-22:6A-6E;">1v</T><T 1L="25-22:.6C;3w-6B:6R;"><T>3v 3.0.76 (72 73 3x)</T><T><a 2h="1Z://3u.2w/1v" 1F="38" 1L="2f:#3y">1Z://3u.2w/1v</a></T><T>70 17 6U 71.</T><T>6T 6X-3x 6Y 6D.</T></T><T>6t 61 60 J 1k, 5Z <a 2h="6u://2y.62.2w/63-66/65?64=5X-5W&5P=5O" 1L="2f:#3y">5R</a> 5V <2R/>5U 5T 5S!</T></T></3B></1z>\'}},1Y:{2j:N,2A:{}},1U:{},3A:{6n:/\\/\\*[\\s\\S]*?\\*\\//2c,6m:/\\/\\/.*$/2c,6l:/#.*$/2c,6k:/"([^\\\\"\\n]|\\\\.)*"/g,6o:/\'([^\\\\\'\\n]|\\\\.)*\'/g,6p:1f M(\'"([^\\\\\\\\"]|\\\\\\\\.)*"\',"3z"),6s:1f M("\'([^\\\\\\\\\']|\\\\\\\\.)*\'","3z"),6q:/(&1y;|<)!--[\\s\\S]*?--(&1G;|>)/2c,3M:/\\w+:\\/\\/[\\w-.\\/?%&=:@;]*/g,6a:{18:/(&1y;|<)\\?=?/g,1b:/\\?(&1G;|>)/g},69:{18:/(&1y;|<)%=?/g,1b:/%(&1G;|>)/g},6d:{18:/(&1y;|<)\\s*1k.*?(&1G;|>)/2T,1b:/(&1y;|<)\\/\\s*1k\\s*(&1G;|>)/2T}},16:{1H:6(a){6 b(i,k){H e.16.2o(i,k,e.13.1x[k])}O(K c=\'<T 1g="16">\',d=e.16.2x,h=d.2X,g=0;g<h.L;g++)c+=(d[h[g]].1H||b)(a,h[g]);c+="</T>";H c},2o:6(a,b,c){H\'<2W><a 2h="#" 1g="6e 6h\'+b+" "+b+\'">\'+c+"</a></2W>"},2b:6(a){K b=a.1F,c=b.1l||"";b=B(p(b,".20",R).1c);K d=6(h){H(h=15(h+"6f(\\\\w+)").X(c))?h[1]:N}("6g");b&&d&&e.16.2x[d].2B(b);a.3N()},2x:{2X:["21","2P"],21:{1H:6(a){I(a.V("2l")!=R)H"";K b=a.V("1t");H e.16.2o(a,"21",b?b:e.13.1x.21)},2B:6(a){a=1E.6j(t(a.1c));a.1l=a.1l.Q("47","")}},2P:{2B:6(){K a="68=0";a+=", 18="+(31.30-33)/2+", 32="+(31.2Z-2Y)/2+", 30=33, 2Z=2Y";a=a.Q(/^,/,"");a=1P.6Z("","38",a);a.2C();K b=a.1E;b.6W(e.13.1x.37);b.6V();a.2C()}}}},35:6(a,b){K c;I(b)c=[b];Y{c=1E.36(e.13.34);O(K d=[],h=0;h<c.L;h++)d.U(c[h]);c=d}c=c;d=[];I(e.13.2M)c=c.1O(z());I(c.L===0)H d;O(h=0;h<c.L;h++){O(K g=c[h],i=a,k=c[h].1l,j=3W 0,l={},m=1f M("^\\\\[(?<2V>(.*?))\\\\]$"),s=1f M("(?<27>[\\\\w-]+)\\\\s*:\\\\s*(?<1T>[\\\\w-%#]+|\\\\[.*?\\\\]|\\".*?\\"|\'.*?\')\\\\s*;?","g");(j=s.X(k))!=N;){K o=j.1T.Q(/^[\'"]|[\'"]$/g,"");I(o!=N&&m.1A(o)){o=m.X(o);o=o.2V.L>0?o.2V.1e(/\\s*,\\s*/):[]}l[j.27]=o}g={1F:g,1n:C(i,l)};g.1n.1D!=N&&d.U(g)}H d},1M:6(a,b){K c=J.35(a,b),d=N,h=e.13;I(c.L!==0)O(K g=0;g<c.L;g++){b=c[g];K i=b.1F,k=b.1n,j=k.1D,l;I(j!=N){I(k["1z-1k"]=="R"||e.2v["1z-1k"]==R){d=1f e.4l(j);j="4O"}Y I(d=A(j))d=1f d;Y 6H;l=i.3X;I(h.2M){l=l;K m=x(l),s=11;I(m.1i("<![6G[")==0){m=m.4h(9);s=R}K o=m.L;I(m.1i("]]\\>")==o-3){m=m.4h(0,o-3);s=R}l=s?m:l}I((i.1t||"")!="")k.1t=i.1t;k.1D=j;d.2Q(k);b=d.2F(l);I((i.1c||"")!="")b.1c=i.1c;i.2G.74(b,i)}}},2E:6(a){w(1P,"4k",6(){e.1M(a)})}};e.2E=e.2E;e.1M=e.1M;e.2L=6(a,b,c){J.1T=a;J.P=b;J.L=a.L;J.23=c;J.1V=N};e.2L.Z.1q=6(){H J.1T};e.4l=6(a){6 b(j,l){O(K m=0;m<j.L;m++)j[m].P+=l}K c=A(a),d,h=1f e.1U.5Y,g=J,i="2F 1H 2Q".1e(" ");I(c!=N){d=1f c;O(K k=0;k<i.L;k++)(6(){K j=i[k];g[j]=6(){H h[j].1p(h,14)}})();d.28==N?1P.1X(e.13.1x.1X+(e.13.1x.4g+a)):h.2J.U({1I:d.28.17,2D:6(j){O(K l=j.17,m=[],s=d.2J,o=j.P+j.18.L,F=d.28,q,G=0;G<s.L;G++){q=y(l,s[G]);b(q,o);m=m.1O(q)}I(F.18!=N&&j.18!=N){q=y(j.18,F.18);b(q,j.P);m=m.1O(q)}I(F.1b!=N&&j.1b!=N){q=y(j.1b,F.1b);b(q,j.P+j[0].5Q(j.1b));m=m.1O(q)}O(j=0;j<m.L;j++)m[j].1V=c.1V;H m}})}};e.4j=6(){};e.4j.Z={V:6(a,b){K c=J.1n[a];c=c==N?b:c;K d={"R":R,"11":11}[c];H d==N?c:d},3Y:6(a){H 1E.4i(a)},4c:6(a,b){K c=[];I(a!=N)O(K d=0;d<a.L;d++)I(1j a[d]=="2m")c=c.1O(y(b,a[d]));H J.4e(c.6b(D))},4e:6(a){O(K b=0;b<a.L;b++)I(a[b]!==N)O(K c=a[b],d=c.P+c.L,h=b+1;h<a.L&&a[b]!==N;h++){K g=a[h];I(g!==N)I(g.P>d)1N;Y I(g.P==c.P&&g.L>c.L)a[b]=N;Y I(g.P>=c.P&&g.P<d)a[h]=N}H a},4d:6(a){K b=[],c=2u(J.V("2i-1s"));v(a,6(d,h){b.U(h+c)});H b},3U:6(a){K b=J.V("1M",[]);I(1j b!="2m"&&b.U==N)b=[b];a:{a=a.1q();K c=3W 0;O(c=c=1Q.6c(c||0,0);c<b.L;c++)I(b[c]==a){b=c;1N a}b=-1}H b!=-1},2r:6(a,b,c){a=["1s","6i"+b,"P"+a,"6r"+(b%2==0?1:2).1q()];J.3U(b)&&a.U("67");b==0&&a.U("1N");H\'<T 1g="\'+a.1K(" ")+\'">\'+c+"</T>"},3Q:6(a,b){K c="",d=a.1e("\\n").L,h=2u(J.V("2i-1s")),g=J.V("2z-1s-2t");I(g==R)g=(h+d-1).1q().L;Y I(3R(g)==R)g=0;O(K i=0;i<d;i++){K k=b?b[i]:h+i,j;I(k==0)j=e.13.1W;Y{j=g;O(K l=k.1q();l.L<j;)l="0"+l;j=l}a=j;c+=J.2r(i,k,a)}H c},49:6(a,b){a=x(a);K c=a.1e("\\n");J.V("2z-1s-2t");K d=2u(J.V("2i-1s"));a="";O(K h=J.V("1D"),g=0;g<c.L;g++){K i=c[g],k=/^(&2s;|\\s)+/.X(i),j=N,l=b?b[g]:d+g;I(k!=N){j=k[0].1q();i=i.1o(j.L);j=j.Q(" ",e.13.1W)}i=x(i);I(i.L==0)i=e.13.1W;a+=J.2r(g,l,(j!=N?\'<17 1g="\'+h+\' 5N">\'+j+"</17>":"")+i)}H a},4f:6(a){H a?"<4a>"+a+"</4a>":""},4b:6(a,b){6 c(l){H(l=l?l.1V||g:g)?l+" ":""}O(K d=0,h="",g=J.V("1D",""),i=0;i<b.L;i++){K k=b[i],j;I(!(k===N||k.L===0)){j=c(k);h+=u(a.1o(d,k.P-d),j+"48")+u(k.1T,j+k.23);d=k.P+k.L+(k.75||0)}}h+=u(a.1o(d),c()+"48");H h},1H:6(a){K b="",c=["20"],d;I(J.V("2k")==R)J.1n.16=J.1n.1u=11;1l="20";J.V("2l")==R&&c.U("47");I((1u=J.V("1u"))==11)c.U("6S");c.U(J.V("1g-27"));c.U(J.V("1D"));a=a.Q(/^[ ]*[\\n]+|[\\n]*[ ]*$/g,"").Q(/\\r/g," ");b=J.V("43-22");I(J.V("42-45")==R)a=n(a,b);Y{O(K h="",g=0;g<b;g++)h+=" ";a=a.Q(/\\t/g,h)}a=a;a:{b=a=a;h=/<2R\\s*\\/?>|&1y;2R\\s*\\/?&1G;/2T;I(e.13.46==R)b=b.Q(h,"\\n");I(e.13.44==R)b=b.Q(h,"");b=b.1e("\\n");h=/^\\s*/;g=4Q;O(K i=0;i<b.L&&g>0;i++){K k=b[i];I(x(k).L!=0){k=h.X(k);I(k==N){a=a;1N a}g=1Q.4q(k[0].L,g)}}I(g>0)O(i=0;i<b.L;i++)b[i]=b[i].1o(g);a=b.1K("\\n")}I(1u)d=J.4d(a);b=J.4c(J.2J,a);b=J.4b(a,b);b=J.49(b,d);I(J.V("41-40"))b=E(b);1j 2H!="1d"&&2H.3S&&2H.3S.1C(/5s/)&&c.U("5t");H b=\'<T 1c="\'+t(J.1c)+\'" 1g="\'+c.1K(" ")+\'">\'+(J.V("16")?e.16.1H(J):"")+\'<3Z 5z="0" 5H="0" 5J="0">\'+J.4f(J.V("1t"))+"<3T><3P>"+(1u?\'<2d 1g="1u">\'+J.3Q(a)+"</2d>":"")+\'<2d 1g="17"><T 1g="3O">\'+b+"</T></2d></3P></3T></3Z></T>"},2F:6(a){I(a===N)a="";J.17=a;K b=J.3Y("T");b.3X=J.1H(a);J.V("16")&&w(p(b,".16"),"5c",e.16.2b);J.V("3V-17")&&w(p(b,".17"),"56",f);H b},2Q:6(a){J.1c=""+1Q.5d(1Q.5n()*5k).1q();e.1Y.2A[t(J.1c)]=J;J.1n=C(e.2v,a||{});I(J.V("2k")==R)J.1n.16=J.1n.1u=11},5j:6(a){a=a.Q(/^\\s+|\\s+$/g,"").Q(/\\s+/g,"|");H"\\\\b(?:"+a+")\\\\b"},5f:6(a){J.28={18:{1I:a.18,23:"1k"},1b:{1I:a.1b,23:"1k"},17:1f M("(?<18>"+a.18.1m+")(?<17>.*?)(?<1b>"+a.1b.1m+")","5o")}}};H e}();1j 2e!="1d"&&(2e.1v=1v);',62,441,'||||||function|||||||||||||||||||||||||||||||||||||return|if|this|var|length|XRegExp|null|for|index|replace|true||div|push|getParam|call|exec|else|prototype||false|lastIndex|config|arguments|RegExp|toolbar|code|left|captureNames|slice|right|id|undefined|split|new|class|addToken|indexOf|typeof|script|className|source|params|substr|apply|toString|String|line|title|gutter|SyntaxHighlighter|_xregexp|strings|lt|html|test|OUTSIDE_CLASS|match|brush|document|target|gt|getHtml|regex|global|join|style|highlight|break|concat|window|Math|isRegExp|throw|value|brushes|brushName|space|alert|vars|http|syntaxhighlighter|expandSource|size|css|case|font|Fa|name|htmlScript|dA|can|handler|gm|td|exports|color|in|href|first|discoveredBrushes|light|collapse|object|cache|getButtonHtml|trigger|pattern|getLineHtml|nbsp|numbers|parseInt|defaults|com|items|www|pad|highlighters|execute|focus|func|all|getDiv|parentNode|navigator|INSIDE_CLASS|regexList|hasFlag|Match|useScriptTags|hasNamedCapture|text|help|init|br|input|gi|Error|values|span|list|250|height|width|screen|top|500|tagName|findElements|getElementsByTagName|aboutDialog|_blank|appendChild|charAt|Array|copyAsGlobal|setFlag|highlighter_|string|attachEvent|nodeName|floor|backref|output|the|TypeError|sticky|Za|iterate|freezeTokens|scope|type|textarea|alexgorbatchev|version|margin|2010|005896|gs|regexLib|body|center|align|noBrush|require|childNodes|DTD|xhtml1|head|org|w3|url|preventDefault|container|tr|getLineNumbersHtml|isNaN|userAgent|tbody|isLineHighlighted|quick|void|innerHTML|create|table|links|auto|smart|tab|stripBrs|tabs|bloggerMode|collapsed|plain|getCodeLinesHtml|caption|getMatchesHtml|findMatches|figureOutLineNumbers|removeNestedMatches|getTitleHtml|brushNotHtmlScript|substring|createElement|Highlighter|load|HtmlScript|Brush|pre|expand|multiline|min|Can|ignoreCase|find|blur|extended|toLowerCase|aliases|addEventListener|innerText|textContent|wasn|select|createTextNode|removeChild|option|same|frame|xmlns|dtd|twice|1999|equiv|meta|htmlscript|transitional|1E3|expected|PUBLIC|DOCTYPE|on|W3C|XHTML|TR|EN|Transitional||configured|srcElement|Object|after|run|dblclick|matchChain|valueOf|constructor|default|switch|click|round|execAt|forHtmlScript|token|gimy|functions|getKeywords|1E6|escape|within|random|sgi|another|finally|supply|MSIE|ie|toUpperCase|catch|returnValue|definition|event|border|imsx|constructing|one|Infinity|from|when|Content|cellpadding|flags|cellspacing|try|xhtml|Type|spaces|2930402|hosted_button_id|lastIndexOf|donate|active|development|keep|to|xclick|_s|Xml|please|like|you|paypal|cgi|cmd|webscr|bin|highlighted|scrollbars|aspScriptTags|phpScriptTags|sort|max|scriptScriptTags|toolbar_item|_|command|command_|number|getElementById|doubleQuotedString|singleLinePerlComments|singleLineCComments|multiLineCComments|singleQuotedString|multiLineDoubleQuotedString|xmlComments|alt|multiLineSingleQuotedString|If|https|1em|000|fff|background|5em|xx|bottom|75em|Gorbatchev|large|serif|CDATA|continue|utf|charset|content|About|family|sans|Helvetica|Arial|Geneva|3em|nogutter|Copyright|syntax|close|write|2004|Alex|open|JavaScript|highlighter|July|02|replaceChild|offset|83'.split('|'),0,{})) diff --git a/guides/assets/stylesheets/main.css b/guides/assets/stylesheets/main.css index ed558e4793..b56699a0d0 100644 --- a/guides/assets/stylesheets/main.css +++ b/guides/assets/stylesheets/main.css @@ -16,7 +16,6 @@ .large {font-size: larger;} .hide {display: none;} -li ul, li ol { margin:0 1.5em; } ul, ol { margin: 0 1.5em 1.5em 1.5em; } ul { list-style-type: disc; } @@ -602,6 +601,8 @@ h6 { font-weight: normal; } +#subCol li ul, li ol { margin:0 1.5em; } + div.code_container { background: #EEE url(../images/tab_grey.gif) no-repeat left top; padding: 0.25em 1em 0.5em 48px; diff --git a/guides/assets/stylesheets/syntaxhighlighter/shCore.css b/guides/assets/stylesheets/syntaxhighlighter/shCore.css index 34f6864a15..7e1e199343 100644 --- a/guides/assets/stylesheets/syntaxhighlighter/shCore.css +++ b/guides/assets/stylesheets/syntaxhighlighter/shCore.css @@ -33,7 +33,7 @@ height: auto !important; left: auto !important; line-height: 1.1em !important; - margin: 0 !important; + margin: 0 0 0.5px 0 !important; outline: 0 !important; overflow: visible !important; padding: 0 !important; diff --git a/guides/assets/stylesheets/syntaxhighlighter/shCoreDefault.css b/guides/assets/stylesheets/syntaxhighlighter/shCoreDefault.css deleted file mode 100644 index 08f9e10e4e..0000000000 --- a/guides/assets/stylesheets/syntaxhighlighter/shCoreDefault.css +++ /dev/null @@ -1,328 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter a, -.syntaxhighlighter div, -.syntaxhighlighter code, -.syntaxhighlighter table, -.syntaxhighlighter table td, -.syntaxhighlighter table tr, -.syntaxhighlighter table tbody, -.syntaxhighlighter table thead, -.syntaxhighlighter table caption, -.syntaxhighlighter textarea { - -moz-border-radius: 0 0 0 0 !important; - -webkit-border-radius: 0 0 0 0 !important; - background: none !important; - border: 0 !important; - bottom: auto !important; - float: none !important; - height: auto !important; - left: auto !important; - line-height: 1.1em !important; - margin: 0 !important; - outline: 0 !important; - overflow: visible !important; - padding: 0 !important; - position: static !important; - right: auto !important; - text-align: left !important; - top: auto !important; - vertical-align: baseline !important; - width: auto !important; - box-sizing: content-box !important; - font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; - font-weight: normal !important; - font-style: normal !important; - font-size: 1em !important; - min-height: inherit !important; - min-height: auto !important; -} - -.syntaxhighlighter { - width: 100% !important; - margin: 1em 0 1em 0 !important; - position: relative !important; - overflow: auto !important; - font-size: 1em !important; -} -.syntaxhighlighter.source { - overflow: hidden !important; -} -.syntaxhighlighter .bold { - font-weight: bold !important; -} -.syntaxhighlighter .italic { - font-style: italic !important; -} -.syntaxhighlighter .line { - white-space: pre !important; -} -.syntaxhighlighter table { - width: 100% !important; -} -.syntaxhighlighter table caption { - text-align: left !important; - padding: .5em 0 0.5em 1em !important; -} -.syntaxhighlighter table td.code { - width: 100% !important; -} -.syntaxhighlighter table td.code .container { - position: relative !important; -} -.syntaxhighlighter table td.code .container textarea { - box-sizing: border-box !important; - position: absolute !important; - left: 0 !important; - top: 0 !important; - width: 100% !important; - height: 100% !important; - border: none !important; - background: white !important; - padding-left: 1em !important; - overflow: hidden !important; - white-space: pre !important; -} -.syntaxhighlighter table td.gutter .line { - text-align: right !important; - padding: 0 0.5em 0 1em !important; -} -.syntaxhighlighter table td.code .line { - padding: 0 1em !important; -} -.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line { - padding-left: 0em !important; -} -.syntaxhighlighter.show { - display: block !important; -} -.syntaxhighlighter.collapsed table { - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar { - padding: 0.1em 0.8em 0em 0.8em !important; - font-size: 1em !important; - position: static !important; - width: auto !important; - height: auto !important; -} -.syntaxhighlighter.collapsed .toolbar span { - display: inline !important; - margin-right: 1em !important; -} -.syntaxhighlighter.collapsed .toolbar span a { - padding: 0 !important; - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar span a.expandSource { - display: inline !important; -} -.syntaxhighlighter .toolbar { - position: absolute !important; - right: 1px !important; - top: 1px !important; - width: 11px !important; - height: 11px !important; - font-size: 10px !important; - z-index: 10 !important; -} -.syntaxhighlighter .toolbar span.title { - display: inline !important; -} -.syntaxhighlighter .toolbar a { - display: block !important; - text-align: center !important; - text-decoration: none !important; - padding-top: 1px !important; -} -.syntaxhighlighter .toolbar a.expandSource { - display: none !important; -} -.syntaxhighlighter.ie { - font-size: .9em !important; - padding: 1px 0 1px 0 !important; -} -.syntaxhighlighter.ie .toolbar { - line-height: 8px !important; -} -.syntaxhighlighter.ie .toolbar a { - padding-top: 0px !important; -} -.syntaxhighlighter.printing .line.alt1 .content, -.syntaxhighlighter.printing .line.alt2 .content, -.syntaxhighlighter.printing .line.highlighted .number, -.syntaxhighlighter.printing .line.highlighted.alt1 .content, -.syntaxhighlighter.printing .line.highlighted.alt2 .content { - background: none !important; -} -.syntaxhighlighter.printing .line .number { - color: #bbbbbb !important; -} -.syntaxhighlighter.printing .line .content { - color: black !important; -} -.syntaxhighlighter.printing .toolbar { - display: none !important; -} -.syntaxhighlighter.printing a { - text-decoration: none !important; -} -.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a { - color: black !important; -} -.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a { - color: #008200 !important; -} -.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a { - color: blue !important; -} -.syntaxhighlighter.printing .keyword { - color: #006699 !important; - font-weight: bold !important; -} -.syntaxhighlighter.printing .preprocessor { - color: gray !important; -} -.syntaxhighlighter.printing .variable { - color: #aa7700 !important; -} -.syntaxhighlighter.printing .value { - color: #009900 !important; -} -.syntaxhighlighter.printing .functions { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .constants { - color: #0066cc !important; -} -.syntaxhighlighter.printing .script { - font-weight: bold !important; -} -.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a { - color: gray !important; -} -.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a { - color: red !important; -} -.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a { - color: black !important; -} - -.syntaxhighlighter { - background-color: white !important; -} -.syntaxhighlighter .line.alt1 { - background-color: white !important; -} -.syntaxhighlighter .line.alt2 { - background-color: white !important; -} -.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { - background-color: #e0e0e0 !important; -} -.syntaxhighlighter .line.highlighted.number { - color: black !important; -} -.syntaxhighlighter table caption { - color: black !important; -} -.syntaxhighlighter .gutter { - color: #afafaf !important; -} -.syntaxhighlighter .gutter .line { - border-right: 3px solid #6ce26c !important; -} -.syntaxhighlighter .gutter .line.highlighted { - background-color: #6ce26c !important; - color: white !important; -} -.syntaxhighlighter.printing .line .content { - border: none !important; -} -.syntaxhighlighter.collapsed { - overflow: visible !important; -} -.syntaxhighlighter.collapsed .toolbar { - color: blue !important; - background: white !important; - border: 1px solid #6ce26c !important; -} -.syntaxhighlighter.collapsed .toolbar a { - color: blue !important; -} -.syntaxhighlighter.collapsed .toolbar a:hover { - color: red !important; -} -.syntaxhighlighter .toolbar { - color: white !important; - background: #6ce26c !important; - border: none !important; -} -.syntaxhighlighter .toolbar a { - color: white !important; -} -.syntaxhighlighter .toolbar a:hover { - color: black !important; -} -.syntaxhighlighter .plain, .syntaxhighlighter .plain a { - color: black !important; -} -.syntaxhighlighter .comments, .syntaxhighlighter .comments a { - color: #008200 !important; -} -.syntaxhighlighter .string, .syntaxhighlighter .string a { - color: blue !important; -} -.syntaxhighlighter .keyword { - color: #006699 !important; -} -.syntaxhighlighter .preprocessor { - color: gray !important; -} -.syntaxhighlighter .variable { - color: #aa7700 !important; -} -.syntaxhighlighter .value { - color: #009900 !important; -} -.syntaxhighlighter .functions { - color: #ff1493 !important; -} -.syntaxhighlighter .constants { - color: #0066cc !important; -} -.syntaxhighlighter .script { - font-weight: bold !important; - color: #006699 !important; - background-color: none !important; -} -.syntaxhighlighter .color1, .syntaxhighlighter .color1 a { - color: gray !important; -} -.syntaxhighlighter .color2, .syntaxhighlighter .color2 a { - color: #ff1493 !important; -} -.syntaxhighlighter .color3, .syntaxhighlighter .color3 a { - color: red !important; -} - -.syntaxhighlighter .keyword { - font-weight: bold !important; -} diff --git a/guides/assets/stylesheets/syntaxhighlighter/shCoreDjango.css b/guides/assets/stylesheets/syntaxhighlighter/shCoreDjango.css deleted file mode 100644 index 1db1f70cb0..0000000000 --- a/guides/assets/stylesheets/syntaxhighlighter/shCoreDjango.css +++ /dev/null @@ -1,331 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter a, -.syntaxhighlighter div, -.syntaxhighlighter code, -.syntaxhighlighter table, -.syntaxhighlighter table td, -.syntaxhighlighter table tr, -.syntaxhighlighter table tbody, -.syntaxhighlighter table thead, -.syntaxhighlighter table caption, -.syntaxhighlighter textarea { - -moz-border-radius: 0 0 0 0 !important; - -webkit-border-radius: 0 0 0 0 !important; - background: none !important; - border: 0 !important; - bottom: auto !important; - float: none !important; - height: auto !important; - left: auto !important; - line-height: 1.1em !important; - margin: 0 !important; - outline: 0 !important; - overflow: visible !important; - padding: 0 !important; - position: static !important; - right: auto !important; - text-align: left !important; - top: auto !important; - vertical-align: baseline !important; - width: auto !important; - box-sizing: content-box !important; - font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; - font-weight: normal !important; - font-style: normal !important; - font-size: 1em !important; - min-height: inherit !important; - min-height: auto !important; -} - -.syntaxhighlighter { - width: 100% !important; - margin: 1em 0 1em 0 !important; - position: relative !important; - overflow: auto !important; - font-size: 1em !important; -} -.syntaxhighlighter.source { - overflow: hidden !important; -} -.syntaxhighlighter .bold { - font-weight: bold !important; -} -.syntaxhighlighter .italic { - font-style: italic !important; -} -.syntaxhighlighter .line { - white-space: pre !important; -} -.syntaxhighlighter table { - width: 100% !important; -} -.syntaxhighlighter table caption { - text-align: left !important; - padding: .5em 0 0.5em 1em !important; -} -.syntaxhighlighter table td.code { - width: 100% !important; -} -.syntaxhighlighter table td.code .container { - position: relative !important; -} -.syntaxhighlighter table td.code .container textarea { - box-sizing: border-box !important; - position: absolute !important; - left: 0 !important; - top: 0 !important; - width: 100% !important; - height: 100% !important; - border: none !important; - background: white !important; - padding-left: 1em !important; - overflow: hidden !important; - white-space: pre !important; -} -.syntaxhighlighter table td.gutter .line { - text-align: right !important; - padding: 0 0.5em 0 1em !important; -} -.syntaxhighlighter table td.code .line { - padding: 0 1em !important; -} -.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line { - padding-left: 0em !important; -} -.syntaxhighlighter.show { - display: block !important; -} -.syntaxhighlighter.collapsed table { - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar { - padding: 0.1em 0.8em 0em 0.8em !important; - font-size: 1em !important; - position: static !important; - width: auto !important; - height: auto !important; -} -.syntaxhighlighter.collapsed .toolbar span { - display: inline !important; - margin-right: 1em !important; -} -.syntaxhighlighter.collapsed .toolbar span a { - padding: 0 !important; - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar span a.expandSource { - display: inline !important; -} -.syntaxhighlighter .toolbar { - position: absolute !important; - right: 1px !important; - top: 1px !important; - width: 11px !important; - height: 11px !important; - font-size: 10px !important; - z-index: 10 !important; -} -.syntaxhighlighter .toolbar span.title { - display: inline !important; -} -.syntaxhighlighter .toolbar a { - display: block !important; - text-align: center !important; - text-decoration: none !important; - padding-top: 1px !important; -} -.syntaxhighlighter .toolbar a.expandSource { - display: none !important; -} -.syntaxhighlighter.ie { - font-size: .9em !important; - padding: 1px 0 1px 0 !important; -} -.syntaxhighlighter.ie .toolbar { - line-height: 8px !important; -} -.syntaxhighlighter.ie .toolbar a { - padding-top: 0px !important; -} -.syntaxhighlighter.printing .line.alt1 .content, -.syntaxhighlighter.printing .line.alt2 .content, -.syntaxhighlighter.printing .line.highlighted .number, -.syntaxhighlighter.printing .line.highlighted.alt1 .content, -.syntaxhighlighter.printing .line.highlighted.alt2 .content { - background: none !important; -} -.syntaxhighlighter.printing .line .number { - color: #bbbbbb !important; -} -.syntaxhighlighter.printing .line .content { - color: black !important; -} -.syntaxhighlighter.printing .toolbar { - display: none !important; -} -.syntaxhighlighter.printing a { - text-decoration: none !important; -} -.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a { - color: black !important; -} -.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a { - color: #008200 !important; -} -.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a { - color: blue !important; -} -.syntaxhighlighter.printing .keyword { - color: #006699 !important; - font-weight: bold !important; -} -.syntaxhighlighter.printing .preprocessor { - color: gray !important; -} -.syntaxhighlighter.printing .variable { - color: #aa7700 !important; -} -.syntaxhighlighter.printing .value { - color: #009900 !important; -} -.syntaxhighlighter.printing .functions { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .constants { - color: #0066cc !important; -} -.syntaxhighlighter.printing .script { - font-weight: bold !important; -} -.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a { - color: gray !important; -} -.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a { - color: red !important; -} -.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a { - color: black !important; -} - -.syntaxhighlighter { - background-color: #0a2b1d !important; -} -.syntaxhighlighter .line.alt1 { - background-color: #0a2b1d !important; -} -.syntaxhighlighter .line.alt2 { - background-color: #0a2b1d !important; -} -.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { - background-color: #233729 !important; -} -.syntaxhighlighter .line.highlighted.number { - color: white !important; -} -.syntaxhighlighter table caption { - color: #f8f8f8 !important; -} -.syntaxhighlighter .gutter { - color: #497958 !important; -} -.syntaxhighlighter .gutter .line { - border-right: 3px solid #41a83e !important; -} -.syntaxhighlighter .gutter .line.highlighted { - background-color: #41a83e !important; - color: #0a2b1d !important; -} -.syntaxhighlighter.printing .line .content { - border: none !important; -} -.syntaxhighlighter.collapsed { - overflow: visible !important; -} -.syntaxhighlighter.collapsed .toolbar { - color: #96dd3b !important; - background: black !important; - border: 1px solid #41a83e !important; -} -.syntaxhighlighter.collapsed .toolbar a { - color: #96dd3b !important; -} -.syntaxhighlighter.collapsed .toolbar a:hover { - color: white !important; -} -.syntaxhighlighter .toolbar { - color: white !important; - background: #41a83e !important; - border: none !important; -} -.syntaxhighlighter .toolbar a { - color: white !important; -} -.syntaxhighlighter .toolbar a:hover { - color: #ffe862 !important; -} -.syntaxhighlighter .plain, .syntaxhighlighter .plain a { - color: #f8f8f8 !important; -} -.syntaxhighlighter .comments, .syntaxhighlighter .comments a { - color: #336442 !important; -} -.syntaxhighlighter .string, .syntaxhighlighter .string a { - color: #9df39f !important; -} -.syntaxhighlighter .keyword { - color: #96dd3b !important; -} -.syntaxhighlighter .preprocessor { - color: #91bb9e !important; -} -.syntaxhighlighter .variable { - color: #ffaa3e !important; -} -.syntaxhighlighter .value { - color: #f7e741 !important; -} -.syntaxhighlighter .functions { - color: #ffaa3e !important; -} -.syntaxhighlighter .constants { - color: #e0e8ff !important; -} -.syntaxhighlighter .script { - font-weight: bold !important; - color: #96dd3b !important; - background-color: none !important; -} -.syntaxhighlighter .color1, .syntaxhighlighter .color1 a { - color: #eb939a !important; -} -.syntaxhighlighter .color2, .syntaxhighlighter .color2 a { - color: #91bb9e !important; -} -.syntaxhighlighter .color3, .syntaxhighlighter .color3 a { - color: #edef7d !important; -} - -.syntaxhighlighter .comments { - font-style: italic !important; -} -.syntaxhighlighter .keyword { - font-weight: bold !important; -} diff --git a/guides/assets/stylesheets/syntaxhighlighter/shCoreEclipse.css b/guides/assets/stylesheets/syntaxhighlighter/shCoreEclipse.css deleted file mode 100644 index a45de9fd8e..0000000000 --- a/guides/assets/stylesheets/syntaxhighlighter/shCoreEclipse.css +++ /dev/null @@ -1,339 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter a, -.syntaxhighlighter div, -.syntaxhighlighter code, -.syntaxhighlighter table, -.syntaxhighlighter table td, -.syntaxhighlighter table tr, -.syntaxhighlighter table tbody, -.syntaxhighlighter table thead, -.syntaxhighlighter table caption, -.syntaxhighlighter textarea { - -moz-border-radius: 0 0 0 0 !important; - -webkit-border-radius: 0 0 0 0 !important; - background: none !important; - border: 0 !important; - bottom: auto !important; - float: none !important; - height: auto !important; - left: auto !important; - line-height: 1.1em !important; - margin: 0 !important; - outline: 0 !important; - overflow: visible !important; - padding: 0 !important; - position: static !important; - right: auto !important; - text-align: left !important; - top: auto !important; - vertical-align: baseline !important; - width: auto !important; - box-sizing: content-box !important; - font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; - font-weight: normal !important; - font-style: normal !important; - font-size: 1em !important; - min-height: inherit !important; - min-height: auto !important; -} - -.syntaxhighlighter { - width: 100% !important; - margin: 1em 0 1em 0 !important; - position: relative !important; - overflow: auto !important; - font-size: 1em !important; -} -.syntaxhighlighter.source { - overflow: hidden !important; -} -.syntaxhighlighter .bold { - font-weight: bold !important; -} -.syntaxhighlighter .italic { - font-style: italic !important; -} -.syntaxhighlighter .line { - white-space: pre !important; -} -.syntaxhighlighter table { - width: 100% !important; -} -.syntaxhighlighter table caption { - text-align: left !important; - padding: .5em 0 0.5em 1em !important; -} -.syntaxhighlighter table td.code { - width: 100% !important; -} -.syntaxhighlighter table td.code .container { - position: relative !important; -} -.syntaxhighlighter table td.code .container textarea { - box-sizing: border-box !important; - position: absolute !important; - left: 0 !important; - top: 0 !important; - width: 100% !important; - height: 100% !important; - border: none !important; - background: white !important; - padding-left: 1em !important; - overflow: hidden !important; - white-space: pre !important; -} -.syntaxhighlighter table td.gutter .line { - text-align: right !important; - padding: 0 0.5em 0 1em !important; -} -.syntaxhighlighter table td.code .line { - padding: 0 1em !important; -} -.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line { - padding-left: 0em !important; -} -.syntaxhighlighter.show { - display: block !important; -} -.syntaxhighlighter.collapsed table { - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar { - padding: 0.1em 0.8em 0em 0.8em !important; - font-size: 1em !important; - position: static !important; - width: auto !important; - height: auto !important; -} -.syntaxhighlighter.collapsed .toolbar span { - display: inline !important; - margin-right: 1em !important; -} -.syntaxhighlighter.collapsed .toolbar span a { - padding: 0 !important; - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar span a.expandSource { - display: inline !important; -} -.syntaxhighlighter .toolbar { - position: absolute !important; - right: 1px !important; - top: 1px !important; - width: 11px !important; - height: 11px !important; - font-size: 10px !important; - z-index: 10 !important; -} -.syntaxhighlighter .toolbar span.title { - display: inline !important; -} -.syntaxhighlighter .toolbar a { - display: block !important; - text-align: center !important; - text-decoration: none !important; - padding-top: 1px !important; -} -.syntaxhighlighter .toolbar a.expandSource { - display: none !important; -} -.syntaxhighlighter.ie { - font-size: .9em !important; - padding: 1px 0 1px 0 !important; -} -.syntaxhighlighter.ie .toolbar { - line-height: 8px !important; -} -.syntaxhighlighter.ie .toolbar a { - padding-top: 0px !important; -} -.syntaxhighlighter.printing .line.alt1 .content, -.syntaxhighlighter.printing .line.alt2 .content, -.syntaxhighlighter.printing .line.highlighted .number, -.syntaxhighlighter.printing .line.highlighted.alt1 .content, -.syntaxhighlighter.printing .line.highlighted.alt2 .content { - background: none !important; -} -.syntaxhighlighter.printing .line .number { - color: #bbbbbb !important; -} -.syntaxhighlighter.printing .line .content { - color: black !important; -} -.syntaxhighlighter.printing .toolbar { - display: none !important; -} -.syntaxhighlighter.printing a { - text-decoration: none !important; -} -.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a { - color: black !important; -} -.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a { - color: #008200 !important; -} -.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a { - color: blue !important; -} -.syntaxhighlighter.printing .keyword { - color: #006699 !important; - font-weight: bold !important; -} -.syntaxhighlighter.printing .preprocessor { - color: gray !important; -} -.syntaxhighlighter.printing .variable { - color: #aa7700 !important; -} -.syntaxhighlighter.printing .value { - color: #009900 !important; -} -.syntaxhighlighter.printing .functions { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .constants { - color: #0066cc !important; -} -.syntaxhighlighter.printing .script { - font-weight: bold !important; -} -.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a { - color: gray !important; -} -.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a { - color: red !important; -} -.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a { - color: black !important; -} - -.syntaxhighlighter { - background-color: white !important; -} -.syntaxhighlighter .line.alt1 { - background-color: white !important; -} -.syntaxhighlighter .line.alt2 { - background-color: white !important; -} -.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { - background-color: #c3defe !important; -} -.syntaxhighlighter .line.highlighted.number { - color: white !important; -} -.syntaxhighlighter table caption { - color: black !important; -} -.syntaxhighlighter .gutter { - color: #787878 !important; -} -.syntaxhighlighter .gutter .line { - border-right: 3px solid #d4d0c8 !important; -} -.syntaxhighlighter .gutter .line.highlighted { - background-color: #d4d0c8 !important; - color: white !important; -} -.syntaxhighlighter.printing .line .content { - border: none !important; -} -.syntaxhighlighter.collapsed { - overflow: visible !important; -} -.syntaxhighlighter.collapsed .toolbar { - color: #3f5fbf !important; - background: white !important; - border: 1px solid #d4d0c8 !important; -} -.syntaxhighlighter.collapsed .toolbar a { - color: #3f5fbf !important; -} -.syntaxhighlighter.collapsed .toolbar a:hover { - color: #aa7700 !important; -} -.syntaxhighlighter .toolbar { - color: #a0a0a0 !important; - background: #d4d0c8 !important; - border: none !important; -} -.syntaxhighlighter .toolbar a { - color: #a0a0a0 !important; -} -.syntaxhighlighter .toolbar a:hover { - color: red !important; -} -.syntaxhighlighter .plain, .syntaxhighlighter .plain a { - color: black !important; -} -.syntaxhighlighter .comments, .syntaxhighlighter .comments a { - color: #3f5fbf !important; -} -.syntaxhighlighter .string, .syntaxhighlighter .string a { - color: #2a00ff !important; -} -.syntaxhighlighter .keyword { - color: #7f0055 !important; -} -.syntaxhighlighter .preprocessor { - color: #646464 !important; -} -.syntaxhighlighter .variable { - color: #aa7700 !important; -} -.syntaxhighlighter .value { - color: #009900 !important; -} -.syntaxhighlighter .functions { - color: #ff1493 !important; -} -.syntaxhighlighter .constants { - color: #0066cc !important; -} -.syntaxhighlighter .script { - font-weight: bold !important; - color: #7f0055 !important; - background-color: none !important; -} -.syntaxhighlighter .color1, .syntaxhighlighter .color1 a { - color: gray !important; -} -.syntaxhighlighter .color2, .syntaxhighlighter .color2 a { - color: #ff1493 !important; -} -.syntaxhighlighter .color3, .syntaxhighlighter .color3 a { - color: red !important; -} - -.syntaxhighlighter .keyword { - font-weight: bold !important; -} -.syntaxhighlighter .xml .keyword { - color: #3f7f7f !important; - font-weight: normal !important; -} -.syntaxhighlighter .xml .color1, .syntaxhighlighter .xml .color1 a { - color: #7f007f !important; -} -.syntaxhighlighter .xml .string { - font-style: italic !important; - color: #2a00ff !important; -} diff --git a/guides/assets/stylesheets/syntaxhighlighter/shCoreEmacs.css b/guides/assets/stylesheets/syntaxhighlighter/shCoreEmacs.css deleted file mode 100644 index 706c77a0a8..0000000000 --- a/guides/assets/stylesheets/syntaxhighlighter/shCoreEmacs.css +++ /dev/null @@ -1,324 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter a, -.syntaxhighlighter div, -.syntaxhighlighter code, -.syntaxhighlighter table, -.syntaxhighlighter table td, -.syntaxhighlighter table tr, -.syntaxhighlighter table tbody, -.syntaxhighlighter table thead, -.syntaxhighlighter table caption, -.syntaxhighlighter textarea { - -moz-border-radius: 0 0 0 0 !important; - -webkit-border-radius: 0 0 0 0 !important; - background: none !important; - border: 0 !important; - bottom: auto !important; - float: none !important; - height: auto !important; - left: auto !important; - line-height: 1.1em !important; - margin: 0 !important; - outline: 0 !important; - overflow: visible !important; - padding: 0 !important; - position: static !important; - right: auto !important; - text-align: left !important; - top: auto !important; - vertical-align: baseline !important; - width: auto !important; - box-sizing: content-box !important; - font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; - font-weight: normal !important; - font-style: normal !important; - font-size: 1em !important; - min-height: inherit !important; - min-height: auto !important; -} - -.syntaxhighlighter { - width: 100% !important; - margin: 1em 0 1em 0 !important; - position: relative !important; - overflow: auto !important; - font-size: 1em !important; -} -.syntaxhighlighter.source { - overflow: hidden !important; -} -.syntaxhighlighter .bold { - font-weight: bold !important; -} -.syntaxhighlighter .italic { - font-style: italic !important; -} -.syntaxhighlighter .line { - white-space: pre !important; -} -.syntaxhighlighter table { - width: 100% !important; -} -.syntaxhighlighter table caption { - text-align: left !important; - padding: .5em 0 0.5em 1em !important; -} -.syntaxhighlighter table td.code { - width: 100% !important; -} -.syntaxhighlighter table td.code .container { - position: relative !important; -} -.syntaxhighlighter table td.code .container textarea { - box-sizing: border-box !important; - position: absolute !important; - left: 0 !important; - top: 0 !important; - width: 100% !important; - height: 100% !important; - border: none !important; - background: white !important; - padding-left: 1em !important; - overflow: hidden !important; - white-space: pre !important; -} -.syntaxhighlighter table td.gutter .line { - text-align: right !important; - padding: 0 0.5em 0 1em !important; -} -.syntaxhighlighter table td.code .line { - padding: 0 1em !important; -} -.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line { - padding-left: 0em !important; -} -.syntaxhighlighter.show { - display: block !important; -} -.syntaxhighlighter.collapsed table { - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar { - padding: 0.1em 0.8em 0em 0.8em !important; - font-size: 1em !important; - position: static !important; - width: auto !important; - height: auto !important; -} -.syntaxhighlighter.collapsed .toolbar span { - display: inline !important; - margin-right: 1em !important; -} -.syntaxhighlighter.collapsed .toolbar span a { - padding: 0 !important; - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar span a.expandSource { - display: inline !important; -} -.syntaxhighlighter .toolbar { - position: absolute !important; - right: 1px !important; - top: 1px !important; - width: 11px !important; - height: 11px !important; - font-size: 10px !important; - z-index: 10 !important; -} -.syntaxhighlighter .toolbar span.title { - display: inline !important; -} -.syntaxhighlighter .toolbar a { - display: block !important; - text-align: center !important; - text-decoration: none !important; - padding-top: 1px !important; -} -.syntaxhighlighter .toolbar a.expandSource { - display: none !important; -} -.syntaxhighlighter.ie { - font-size: .9em !important; - padding: 1px 0 1px 0 !important; -} -.syntaxhighlighter.ie .toolbar { - line-height: 8px !important; -} -.syntaxhighlighter.ie .toolbar a { - padding-top: 0px !important; -} -.syntaxhighlighter.printing .line.alt1 .content, -.syntaxhighlighter.printing .line.alt2 .content, -.syntaxhighlighter.printing .line.highlighted .number, -.syntaxhighlighter.printing .line.highlighted.alt1 .content, -.syntaxhighlighter.printing .line.highlighted.alt2 .content { - background: none !important; -} -.syntaxhighlighter.printing .line .number { - color: #bbbbbb !important; -} -.syntaxhighlighter.printing .line .content { - color: black !important; -} -.syntaxhighlighter.printing .toolbar { - display: none !important; -} -.syntaxhighlighter.printing a { - text-decoration: none !important; -} -.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a { - color: black !important; -} -.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a { - color: #008200 !important; -} -.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a { - color: blue !important; -} -.syntaxhighlighter.printing .keyword { - color: #006699 !important; - font-weight: bold !important; -} -.syntaxhighlighter.printing .preprocessor { - color: gray !important; -} -.syntaxhighlighter.printing .variable { - color: #aa7700 !important; -} -.syntaxhighlighter.printing .value { - color: #009900 !important; -} -.syntaxhighlighter.printing .functions { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .constants { - color: #0066cc !important; -} -.syntaxhighlighter.printing .script { - font-weight: bold !important; -} -.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a { - color: gray !important; -} -.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a { - color: red !important; -} -.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a { - color: black !important; -} - -.syntaxhighlighter { - background-color: black !important; -} -.syntaxhighlighter .line.alt1 { - background-color: black !important; -} -.syntaxhighlighter .line.alt2 { - background-color: black !important; -} -.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { - background-color: #2a3133 !important; -} -.syntaxhighlighter .line.highlighted.number { - color: white !important; -} -.syntaxhighlighter table caption { - color: #d3d3d3 !important; -} -.syntaxhighlighter .gutter { - color: #d3d3d3 !important; -} -.syntaxhighlighter .gutter .line { - border-right: 3px solid #990000 !important; -} -.syntaxhighlighter .gutter .line.highlighted { - background-color: #990000 !important; - color: black !important; -} -.syntaxhighlighter.printing .line .content { - border: none !important; -} -.syntaxhighlighter.collapsed { - overflow: visible !important; -} -.syntaxhighlighter.collapsed .toolbar { - color: #ebdb8d !important; - background: black !important; - border: 1px solid #990000 !important; -} -.syntaxhighlighter.collapsed .toolbar a { - color: #ebdb8d !important; -} -.syntaxhighlighter.collapsed .toolbar a:hover { - color: #ff7d27 !important; -} -.syntaxhighlighter .toolbar { - color: white !important; - background: #990000 !important; - border: none !important; -} -.syntaxhighlighter .toolbar a { - color: white !important; -} -.syntaxhighlighter .toolbar a:hover { - color: #9ccff4 !important; -} -.syntaxhighlighter .plain, .syntaxhighlighter .plain a { - color: #d3d3d3 !important; -} -.syntaxhighlighter .comments, .syntaxhighlighter .comments a { - color: #ff7d27 !important; -} -.syntaxhighlighter .string, .syntaxhighlighter .string a { - color: #ff9e7b !important; -} -.syntaxhighlighter .keyword { - color: aqua !important; -} -.syntaxhighlighter .preprocessor { - color: #aec4de !important; -} -.syntaxhighlighter .variable { - color: #ffaa3e !important; -} -.syntaxhighlighter .value { - color: #009900 !important; -} -.syntaxhighlighter .functions { - color: #81cef9 !important; -} -.syntaxhighlighter .constants { - color: #ff9e7b !important; -} -.syntaxhighlighter .script { - font-weight: bold !important; - color: aqua !important; - background-color: none !important; -} -.syntaxhighlighter .color1, .syntaxhighlighter .color1 a { - color: #ebdb8d !important; -} -.syntaxhighlighter .color2, .syntaxhighlighter .color2 a { - color: #ff7d27 !important; -} -.syntaxhighlighter .color3, .syntaxhighlighter .color3 a { - color: #aec4de !important; -} diff --git a/guides/assets/stylesheets/syntaxhighlighter/shCoreFadeToGrey.css b/guides/assets/stylesheets/syntaxhighlighter/shCoreFadeToGrey.css deleted file mode 100644 index 6101eba51f..0000000000 --- a/guides/assets/stylesheets/syntaxhighlighter/shCoreFadeToGrey.css +++ /dev/null @@ -1,328 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter a, -.syntaxhighlighter div, -.syntaxhighlighter code, -.syntaxhighlighter table, -.syntaxhighlighter table td, -.syntaxhighlighter table tr, -.syntaxhighlighter table tbody, -.syntaxhighlighter table thead, -.syntaxhighlighter table caption, -.syntaxhighlighter textarea { - -moz-border-radius: 0 0 0 0 !important; - -webkit-border-radius: 0 0 0 0 !important; - background: none !important; - border: 0 !important; - bottom: auto !important; - float: none !important; - height: auto !important; - left: auto !important; - line-height: 1.1em !important; - margin: 0 !important; - outline: 0 !important; - overflow: visible !important; - padding: 0 !important; - position: static !important; - right: auto !important; - text-align: left !important; - top: auto !important; - vertical-align: baseline !important; - width: auto !important; - box-sizing: content-box !important; - font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; - font-weight: normal !important; - font-style: normal !important; - font-size: 1em !important; - min-height: inherit !important; - min-height: auto !important; -} - -.syntaxhighlighter { - width: 100% !important; - margin: 1em 0 1em 0 !important; - position: relative !important; - overflow: auto !important; - font-size: 1em !important; -} -.syntaxhighlighter.source { - overflow: hidden !important; -} -.syntaxhighlighter .bold { - font-weight: bold !important; -} -.syntaxhighlighter .italic { - font-style: italic !important; -} -.syntaxhighlighter .line { - white-space: pre !important; -} -.syntaxhighlighter table { - width: 100% !important; -} -.syntaxhighlighter table caption { - text-align: left !important; - padding: .5em 0 0.5em 1em !important; -} -.syntaxhighlighter table td.code { - width: 100% !important; -} -.syntaxhighlighter table td.code .container { - position: relative !important; -} -.syntaxhighlighter table td.code .container textarea { - box-sizing: border-box !important; - position: absolute !important; - left: 0 !important; - top: 0 !important; - width: 100% !important; - height: 100% !important; - border: none !important; - background: white !important; - padding-left: 1em !important; - overflow: hidden !important; - white-space: pre !important; -} -.syntaxhighlighter table td.gutter .line { - text-align: right !important; - padding: 0 0.5em 0 1em !important; -} -.syntaxhighlighter table td.code .line { - padding: 0 1em !important; -} -.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line { - padding-left: 0em !important; -} -.syntaxhighlighter.show { - display: block !important; -} -.syntaxhighlighter.collapsed table { - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar { - padding: 0.1em 0.8em 0em 0.8em !important; - font-size: 1em !important; - position: static !important; - width: auto !important; - height: auto !important; -} -.syntaxhighlighter.collapsed .toolbar span { - display: inline !important; - margin-right: 1em !important; -} -.syntaxhighlighter.collapsed .toolbar span a { - padding: 0 !important; - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar span a.expandSource { - display: inline !important; -} -.syntaxhighlighter .toolbar { - position: absolute !important; - right: 1px !important; - top: 1px !important; - width: 11px !important; - height: 11px !important; - font-size: 10px !important; - z-index: 10 !important; -} -.syntaxhighlighter .toolbar span.title { - display: inline !important; -} -.syntaxhighlighter .toolbar a { - display: block !important; - text-align: center !important; - text-decoration: none !important; - padding-top: 1px !important; -} -.syntaxhighlighter .toolbar a.expandSource { - display: none !important; -} -.syntaxhighlighter.ie { - font-size: .9em !important; - padding: 1px 0 1px 0 !important; -} -.syntaxhighlighter.ie .toolbar { - line-height: 8px !important; -} -.syntaxhighlighter.ie .toolbar a { - padding-top: 0px !important; -} -.syntaxhighlighter.printing .line.alt1 .content, -.syntaxhighlighter.printing .line.alt2 .content, -.syntaxhighlighter.printing .line.highlighted .number, -.syntaxhighlighter.printing .line.highlighted.alt1 .content, -.syntaxhighlighter.printing .line.highlighted.alt2 .content { - background: none !important; -} -.syntaxhighlighter.printing .line .number { - color: #bbbbbb !important; -} -.syntaxhighlighter.printing .line .content { - color: black !important; -} -.syntaxhighlighter.printing .toolbar { - display: none !important; -} -.syntaxhighlighter.printing a { - text-decoration: none !important; -} -.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a { - color: black !important; -} -.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a { - color: #008200 !important; -} -.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a { - color: blue !important; -} -.syntaxhighlighter.printing .keyword { - color: #006699 !important; - font-weight: bold !important; -} -.syntaxhighlighter.printing .preprocessor { - color: gray !important; -} -.syntaxhighlighter.printing .variable { - color: #aa7700 !important; -} -.syntaxhighlighter.printing .value { - color: #009900 !important; -} -.syntaxhighlighter.printing .functions { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .constants { - color: #0066cc !important; -} -.syntaxhighlighter.printing .script { - font-weight: bold !important; -} -.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a { - color: gray !important; -} -.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a { - color: red !important; -} -.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a { - color: black !important; -} - -.syntaxhighlighter { - background-color: #121212 !important; -} -.syntaxhighlighter .line.alt1 { - background-color: #121212 !important; -} -.syntaxhighlighter .line.alt2 { - background-color: #121212 !important; -} -.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { - background-color: #2c2c29 !important; -} -.syntaxhighlighter .line.highlighted.number { - color: white !important; -} -.syntaxhighlighter table caption { - color: white !important; -} -.syntaxhighlighter .gutter { - color: #afafaf !important; -} -.syntaxhighlighter .gutter .line { - border-right: 3px solid #3185b9 !important; -} -.syntaxhighlighter .gutter .line.highlighted { - background-color: #3185b9 !important; - color: #121212 !important; -} -.syntaxhighlighter.printing .line .content { - border: none !important; -} -.syntaxhighlighter.collapsed { - overflow: visible !important; -} -.syntaxhighlighter.collapsed .toolbar { - color: #3185b9 !important; - background: black !important; - border: 1px solid #3185b9 !important; -} -.syntaxhighlighter.collapsed .toolbar a { - color: #3185b9 !important; -} -.syntaxhighlighter.collapsed .toolbar a:hover { - color: #d01d33 !important; -} -.syntaxhighlighter .toolbar { - color: white !important; - background: #3185b9 !important; - border: none !important; -} -.syntaxhighlighter .toolbar a { - color: white !important; -} -.syntaxhighlighter .toolbar a:hover { - color: #96daff !important; -} -.syntaxhighlighter .plain, .syntaxhighlighter .plain a { - color: white !important; -} -.syntaxhighlighter .comments, .syntaxhighlighter .comments a { - color: #696854 !important; -} -.syntaxhighlighter .string, .syntaxhighlighter .string a { - color: #e3e658 !important; -} -.syntaxhighlighter .keyword { - color: #d01d33 !important; -} -.syntaxhighlighter .preprocessor { - color: #435a5f !important; -} -.syntaxhighlighter .variable { - color: #898989 !important; -} -.syntaxhighlighter .value { - color: #009900 !important; -} -.syntaxhighlighter .functions { - color: #aaaaaa !important; -} -.syntaxhighlighter .constants { - color: #96daff !important; -} -.syntaxhighlighter .script { - font-weight: bold !important; - color: #d01d33 !important; - background-color: none !important; -} -.syntaxhighlighter .color1, .syntaxhighlighter .color1 a { - color: #ffc074 !important; -} -.syntaxhighlighter .color2, .syntaxhighlighter .color2 a { - color: #4a8cdb !important; -} -.syntaxhighlighter .color3, .syntaxhighlighter .color3 a { - color: #96daff !important; -} - -.syntaxhighlighter .functions { - font-weight: bold !important; -} diff --git a/guides/assets/stylesheets/syntaxhighlighter/shCoreMDUltra.css b/guides/assets/stylesheets/syntaxhighlighter/shCoreMDUltra.css deleted file mode 100644 index 2923ce7367..0000000000 --- a/guides/assets/stylesheets/syntaxhighlighter/shCoreMDUltra.css +++ /dev/null @@ -1,324 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter a, -.syntaxhighlighter div, -.syntaxhighlighter code, -.syntaxhighlighter table, -.syntaxhighlighter table td, -.syntaxhighlighter table tr, -.syntaxhighlighter table tbody, -.syntaxhighlighter table thead, -.syntaxhighlighter table caption, -.syntaxhighlighter textarea { - -moz-border-radius: 0 0 0 0 !important; - -webkit-border-radius: 0 0 0 0 !important; - background: none !important; - border: 0 !important; - bottom: auto !important; - float: none !important; - height: auto !important; - left: auto !important; - line-height: 1.1em !important; - margin: 0 !important; - outline: 0 !important; - overflow: visible !important; - padding: 0 !important; - position: static !important; - right: auto !important; - text-align: left !important; - top: auto !important; - vertical-align: baseline !important; - width: auto !important; - box-sizing: content-box !important; - font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; - font-weight: normal !important; - font-style: normal !important; - font-size: 1em !important; - min-height: inherit !important; - min-height: auto !important; -} - -.syntaxhighlighter { - width: 100% !important; - margin: 1em 0 1em 0 !important; - position: relative !important; - overflow: auto !important; - font-size: 1em !important; -} -.syntaxhighlighter.source { - overflow: hidden !important; -} -.syntaxhighlighter .bold { - font-weight: bold !important; -} -.syntaxhighlighter .italic { - font-style: italic !important; -} -.syntaxhighlighter .line { - white-space: pre !important; -} -.syntaxhighlighter table { - width: 100% !important; -} -.syntaxhighlighter table caption { - text-align: left !important; - padding: .5em 0 0.5em 1em !important; -} -.syntaxhighlighter table td.code { - width: 100% !important; -} -.syntaxhighlighter table td.code .container { - position: relative !important; -} -.syntaxhighlighter table td.code .container textarea { - box-sizing: border-box !important; - position: absolute !important; - left: 0 !important; - top: 0 !important; - width: 100% !important; - height: 100% !important; - border: none !important; - background: white !important; - padding-left: 1em !important; - overflow: hidden !important; - white-space: pre !important; -} -.syntaxhighlighter table td.gutter .line { - text-align: right !important; - padding: 0 0.5em 0 1em !important; -} -.syntaxhighlighter table td.code .line { - padding: 0 1em !important; -} -.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line { - padding-left: 0em !important; -} -.syntaxhighlighter.show { - display: block !important; -} -.syntaxhighlighter.collapsed table { - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar { - padding: 0.1em 0.8em 0em 0.8em !important; - font-size: 1em !important; - position: static !important; - width: auto !important; - height: auto !important; -} -.syntaxhighlighter.collapsed .toolbar span { - display: inline !important; - margin-right: 1em !important; -} -.syntaxhighlighter.collapsed .toolbar span a { - padding: 0 !important; - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar span a.expandSource { - display: inline !important; -} -.syntaxhighlighter .toolbar { - position: absolute !important; - right: 1px !important; - top: 1px !important; - width: 11px !important; - height: 11px !important; - font-size: 10px !important; - z-index: 10 !important; -} -.syntaxhighlighter .toolbar span.title { - display: inline !important; -} -.syntaxhighlighter .toolbar a { - display: block !important; - text-align: center !important; - text-decoration: none !important; - padding-top: 1px !important; -} -.syntaxhighlighter .toolbar a.expandSource { - display: none !important; -} -.syntaxhighlighter.ie { - font-size: .9em !important; - padding: 1px 0 1px 0 !important; -} -.syntaxhighlighter.ie .toolbar { - line-height: 8px !important; -} -.syntaxhighlighter.ie .toolbar a { - padding-top: 0px !important; -} -.syntaxhighlighter.printing .line.alt1 .content, -.syntaxhighlighter.printing .line.alt2 .content, -.syntaxhighlighter.printing .line.highlighted .number, -.syntaxhighlighter.printing .line.highlighted.alt1 .content, -.syntaxhighlighter.printing .line.highlighted.alt2 .content { - background: none !important; -} -.syntaxhighlighter.printing .line .number { - color: #bbbbbb !important; -} -.syntaxhighlighter.printing .line .content { - color: black !important; -} -.syntaxhighlighter.printing .toolbar { - display: none !important; -} -.syntaxhighlighter.printing a { - text-decoration: none !important; -} -.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a { - color: black !important; -} -.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a { - color: #008200 !important; -} -.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a { - color: blue !important; -} -.syntaxhighlighter.printing .keyword { - color: #006699 !important; - font-weight: bold !important; -} -.syntaxhighlighter.printing .preprocessor { - color: gray !important; -} -.syntaxhighlighter.printing .variable { - color: #aa7700 !important; -} -.syntaxhighlighter.printing .value { - color: #009900 !important; -} -.syntaxhighlighter.printing .functions { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .constants { - color: #0066cc !important; -} -.syntaxhighlighter.printing .script { - font-weight: bold !important; -} -.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a { - color: gray !important; -} -.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a { - color: red !important; -} -.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a { - color: black !important; -} - -.syntaxhighlighter { - background-color: #222222 !important; -} -.syntaxhighlighter .line.alt1 { - background-color: #222222 !important; -} -.syntaxhighlighter .line.alt2 { - background-color: #222222 !important; -} -.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { - background-color: #253e5a !important; -} -.syntaxhighlighter .line.highlighted.number { - color: white !important; -} -.syntaxhighlighter table caption { - color: lime !important; -} -.syntaxhighlighter .gutter { - color: #38566f !important; -} -.syntaxhighlighter .gutter .line { - border-right: 3px solid #435a5f !important; -} -.syntaxhighlighter .gutter .line.highlighted { - background-color: #435a5f !important; - color: #222222 !important; -} -.syntaxhighlighter.printing .line .content { - border: none !important; -} -.syntaxhighlighter.collapsed { - overflow: visible !important; -} -.syntaxhighlighter.collapsed .toolbar { - color: #428bdd !important; - background: black !important; - border: 1px solid #435a5f !important; -} -.syntaxhighlighter.collapsed .toolbar a { - color: #428bdd !important; -} -.syntaxhighlighter.collapsed .toolbar a:hover { - color: lime !important; -} -.syntaxhighlighter .toolbar { - color: #aaaaff !important; - background: #435a5f !important; - border: none !important; -} -.syntaxhighlighter .toolbar a { - color: #aaaaff !important; -} -.syntaxhighlighter .toolbar a:hover { - color: #9ccff4 !important; -} -.syntaxhighlighter .plain, .syntaxhighlighter .plain a { - color: lime !important; -} -.syntaxhighlighter .comments, .syntaxhighlighter .comments a { - color: #428bdd !important; -} -.syntaxhighlighter .string, .syntaxhighlighter .string a { - color: lime !important; -} -.syntaxhighlighter .keyword { - color: #aaaaff !important; -} -.syntaxhighlighter .preprocessor { - color: #8aa6c1 !important; -} -.syntaxhighlighter .variable { - color: aqua !important; -} -.syntaxhighlighter .value { - color: #f7e741 !important; -} -.syntaxhighlighter .functions { - color: #ff8000 !important; -} -.syntaxhighlighter .constants { - color: yellow !important; -} -.syntaxhighlighter .script { - font-weight: bold !important; - color: #aaaaff !important; - background-color: none !important; -} -.syntaxhighlighter .color1, .syntaxhighlighter .color1 a { - color: red !important; -} -.syntaxhighlighter .color2, .syntaxhighlighter .color2 a { - color: yellow !important; -} -.syntaxhighlighter .color3, .syntaxhighlighter .color3 a { - color: #ffaa3e !important; -} diff --git a/guides/assets/stylesheets/syntaxhighlighter/shCoreMidnight.css b/guides/assets/stylesheets/syntaxhighlighter/shCoreMidnight.css deleted file mode 100644 index e3733eed56..0000000000 --- a/guides/assets/stylesheets/syntaxhighlighter/shCoreMidnight.css +++ /dev/null @@ -1,324 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter a, -.syntaxhighlighter div, -.syntaxhighlighter code, -.syntaxhighlighter table, -.syntaxhighlighter table td, -.syntaxhighlighter table tr, -.syntaxhighlighter table tbody, -.syntaxhighlighter table thead, -.syntaxhighlighter table caption, -.syntaxhighlighter textarea { - -moz-border-radius: 0 0 0 0 !important; - -webkit-border-radius: 0 0 0 0 !important; - background: none !important; - border: 0 !important; - bottom: auto !important; - float: none !important; - height: auto !important; - left: auto !important; - line-height: 1.1em !important; - margin: 0 !important; - outline: 0 !important; - overflow: visible !important; - padding: 0 !important; - position: static !important; - right: auto !important; - text-align: left !important; - top: auto !important; - vertical-align: baseline !important; - width: auto !important; - box-sizing: content-box !important; - font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; - font-weight: normal !important; - font-style: normal !important; - font-size: 1em !important; - min-height: inherit !important; - min-height: auto !important; -} - -.syntaxhighlighter { - width: 100% !important; - margin: 1em 0 1em 0 !important; - position: relative !important; - overflow: auto !important; - font-size: 1em !important; -} -.syntaxhighlighter.source { - overflow: hidden !important; -} -.syntaxhighlighter .bold { - font-weight: bold !important; -} -.syntaxhighlighter .italic { - font-style: italic !important; -} -.syntaxhighlighter .line { - white-space: pre !important; -} -.syntaxhighlighter table { - width: 100% !important; -} -.syntaxhighlighter table caption { - text-align: left !important; - padding: .5em 0 0.5em 1em !important; -} -.syntaxhighlighter table td.code { - width: 100% !important; -} -.syntaxhighlighter table td.code .container { - position: relative !important; -} -.syntaxhighlighter table td.code .container textarea { - box-sizing: border-box !important; - position: absolute !important; - left: 0 !important; - top: 0 !important; - width: 100% !important; - height: 100% !important; - border: none !important; - background: white !important; - padding-left: 1em !important; - overflow: hidden !important; - white-space: pre !important; -} -.syntaxhighlighter table td.gutter .line { - text-align: right !important; - padding: 0 0.5em 0 1em !important; -} -.syntaxhighlighter table td.code .line { - padding: 0 1em !important; -} -.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line { - padding-left: 0em !important; -} -.syntaxhighlighter.show { - display: block !important; -} -.syntaxhighlighter.collapsed table { - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar { - padding: 0.1em 0.8em 0em 0.8em !important; - font-size: 1em !important; - position: static !important; - width: auto !important; - height: auto !important; -} -.syntaxhighlighter.collapsed .toolbar span { - display: inline !important; - margin-right: 1em !important; -} -.syntaxhighlighter.collapsed .toolbar span a { - padding: 0 !important; - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar span a.expandSource { - display: inline !important; -} -.syntaxhighlighter .toolbar { - position: absolute !important; - right: 1px !important; - top: 1px !important; - width: 11px !important; - height: 11px !important; - font-size: 10px !important; - z-index: 10 !important; -} -.syntaxhighlighter .toolbar span.title { - display: inline !important; -} -.syntaxhighlighter .toolbar a { - display: block !important; - text-align: center !important; - text-decoration: none !important; - padding-top: 1px !important; -} -.syntaxhighlighter .toolbar a.expandSource { - display: none !important; -} -.syntaxhighlighter.ie { - font-size: .9em !important; - padding: 1px 0 1px 0 !important; -} -.syntaxhighlighter.ie .toolbar { - line-height: 8px !important; -} -.syntaxhighlighter.ie .toolbar a { - padding-top: 0px !important; -} -.syntaxhighlighter.printing .line.alt1 .content, -.syntaxhighlighter.printing .line.alt2 .content, -.syntaxhighlighter.printing .line.highlighted .number, -.syntaxhighlighter.printing .line.highlighted.alt1 .content, -.syntaxhighlighter.printing .line.highlighted.alt2 .content { - background: none !important; -} -.syntaxhighlighter.printing .line .number { - color: #bbbbbb !important; -} -.syntaxhighlighter.printing .line .content { - color: black !important; -} -.syntaxhighlighter.printing .toolbar { - display: none !important; -} -.syntaxhighlighter.printing a { - text-decoration: none !important; -} -.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a { - color: black !important; -} -.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a { - color: #008200 !important; -} -.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a { - color: blue !important; -} -.syntaxhighlighter.printing .keyword { - color: #006699 !important; - font-weight: bold !important; -} -.syntaxhighlighter.printing .preprocessor { - color: gray !important; -} -.syntaxhighlighter.printing .variable { - color: #aa7700 !important; -} -.syntaxhighlighter.printing .value { - color: #009900 !important; -} -.syntaxhighlighter.printing .functions { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .constants { - color: #0066cc !important; -} -.syntaxhighlighter.printing .script { - font-weight: bold !important; -} -.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a { - color: gray !important; -} -.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a { - color: red !important; -} -.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a { - color: black !important; -} - -.syntaxhighlighter { - background-color: #0f192a !important; -} -.syntaxhighlighter .line.alt1 { - background-color: #0f192a !important; -} -.syntaxhighlighter .line.alt2 { - background-color: #0f192a !important; -} -.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { - background-color: #253e5a !important; -} -.syntaxhighlighter .line.highlighted.number { - color: #38566f !important; -} -.syntaxhighlighter table caption { - color: #d1edff !important; -} -.syntaxhighlighter .gutter { - color: #afafaf !important; -} -.syntaxhighlighter .gutter .line { - border-right: 3px solid #435a5f !important; -} -.syntaxhighlighter .gutter .line.highlighted { - background-color: #435a5f !important; - color: #0f192a !important; -} -.syntaxhighlighter.printing .line .content { - border: none !important; -} -.syntaxhighlighter.collapsed { - overflow: visible !important; -} -.syntaxhighlighter.collapsed .toolbar { - color: #428bdd !important; - background: black !important; - border: 1px solid #435a5f !important; -} -.syntaxhighlighter.collapsed .toolbar a { - color: #428bdd !important; -} -.syntaxhighlighter.collapsed .toolbar a:hover { - color: #1dc116 !important; -} -.syntaxhighlighter .toolbar { - color: #d1edff !important; - background: #435a5f !important; - border: none !important; -} -.syntaxhighlighter .toolbar a { - color: #d1edff !important; -} -.syntaxhighlighter .toolbar a:hover { - color: #8aa6c1 !important; -} -.syntaxhighlighter .plain, .syntaxhighlighter .plain a { - color: #d1edff !important; -} -.syntaxhighlighter .comments, .syntaxhighlighter .comments a { - color: #428bdd !important; -} -.syntaxhighlighter .string, .syntaxhighlighter .string a { - color: #1dc116 !important; -} -.syntaxhighlighter .keyword { - color: #b43d3d !important; -} -.syntaxhighlighter .preprocessor { - color: #8aa6c1 !important; -} -.syntaxhighlighter .variable { - color: #ffaa3e !important; -} -.syntaxhighlighter .value { - color: #f7e741 !important; -} -.syntaxhighlighter .functions { - color: #ffaa3e !important; -} -.syntaxhighlighter .constants { - color: #e0e8ff !important; -} -.syntaxhighlighter .script { - font-weight: bold !important; - color: #b43d3d !important; - background-color: none !important; -} -.syntaxhighlighter .color1, .syntaxhighlighter .color1 a { - color: #f8bb00 !important; -} -.syntaxhighlighter .color2, .syntaxhighlighter .color2 a { - color: white !important; -} -.syntaxhighlighter .color3, .syntaxhighlighter .color3 a { - color: #ffaa3e !important; -} diff --git a/guides/assets/stylesheets/syntaxhighlighter/shCoreRDark.css b/guides/assets/stylesheets/syntaxhighlighter/shCoreRDark.css deleted file mode 100644 index d09368384d..0000000000 --- a/guides/assets/stylesheets/syntaxhighlighter/shCoreRDark.css +++ /dev/null @@ -1,324 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter a, -.syntaxhighlighter div, -.syntaxhighlighter code, -.syntaxhighlighter table, -.syntaxhighlighter table td, -.syntaxhighlighter table tr, -.syntaxhighlighter table tbody, -.syntaxhighlighter table thead, -.syntaxhighlighter table caption, -.syntaxhighlighter textarea { - -moz-border-radius: 0 0 0 0 !important; - -webkit-border-radius: 0 0 0 0 !important; - background: none !important; - border: 0 !important; - bottom: auto !important; - float: none !important; - height: auto !important; - left: auto !important; - line-height: 1.1em !important; - margin: 0 !important; - outline: 0 !important; - overflow: visible !important; - padding: 0 !important; - position: static !important; - right: auto !important; - text-align: left !important; - top: auto !important; - vertical-align: baseline !important; - width: auto !important; - box-sizing: content-box !important; - font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; - font-weight: normal !important; - font-style: normal !important; - font-size: 1em !important; - min-height: inherit !important; - min-height: auto !important; -} - -.syntaxhighlighter { - width: 100% !important; - margin: 1em 0 1em 0 !important; - position: relative !important; - overflow: auto !important; - font-size: 1em !important; -} -.syntaxhighlighter.source { - overflow: hidden !important; -} -.syntaxhighlighter .bold { - font-weight: bold !important; -} -.syntaxhighlighter .italic { - font-style: italic !important; -} -.syntaxhighlighter .line { - white-space: pre !important; -} -.syntaxhighlighter table { - width: 100% !important; -} -.syntaxhighlighter table caption { - text-align: left !important; - padding: .5em 0 0.5em 1em !important; -} -.syntaxhighlighter table td.code { - width: 100% !important; -} -.syntaxhighlighter table td.code .container { - position: relative !important; -} -.syntaxhighlighter table td.code .container textarea { - box-sizing: border-box !important; - position: absolute !important; - left: 0 !important; - top: 0 !important; - width: 100% !important; - height: 100% !important; - border: none !important; - background: white !important; - padding-left: 1em !important; - overflow: hidden !important; - white-space: pre !important; -} -.syntaxhighlighter table td.gutter .line { - text-align: right !important; - padding: 0 0.5em 0 1em !important; -} -.syntaxhighlighter table td.code .line { - padding: 0 1em !important; -} -.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line { - padding-left: 0em !important; -} -.syntaxhighlighter.show { - display: block !important; -} -.syntaxhighlighter.collapsed table { - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar { - padding: 0.1em 0.8em 0em 0.8em !important; - font-size: 1em !important; - position: static !important; - width: auto !important; - height: auto !important; -} -.syntaxhighlighter.collapsed .toolbar span { - display: inline !important; - margin-right: 1em !important; -} -.syntaxhighlighter.collapsed .toolbar span a { - padding: 0 !important; - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar span a.expandSource { - display: inline !important; -} -.syntaxhighlighter .toolbar { - position: absolute !important; - right: 1px !important; - top: 1px !important; - width: 11px !important; - height: 11px !important; - font-size: 10px !important; - z-index: 10 !important; -} -.syntaxhighlighter .toolbar span.title { - display: inline !important; -} -.syntaxhighlighter .toolbar a { - display: block !important; - text-align: center !important; - text-decoration: none !important; - padding-top: 1px !important; -} -.syntaxhighlighter .toolbar a.expandSource { - display: none !important; -} -.syntaxhighlighter.ie { - font-size: .9em !important; - padding: 1px 0 1px 0 !important; -} -.syntaxhighlighter.ie .toolbar { - line-height: 8px !important; -} -.syntaxhighlighter.ie .toolbar a { - padding-top: 0px !important; -} -.syntaxhighlighter.printing .line.alt1 .content, -.syntaxhighlighter.printing .line.alt2 .content, -.syntaxhighlighter.printing .line.highlighted .number, -.syntaxhighlighter.printing .line.highlighted.alt1 .content, -.syntaxhighlighter.printing .line.highlighted.alt2 .content { - background: none !important; -} -.syntaxhighlighter.printing .line .number { - color: #bbbbbb !important; -} -.syntaxhighlighter.printing .line .content { - color: black !important; -} -.syntaxhighlighter.printing .toolbar { - display: none !important; -} -.syntaxhighlighter.printing a { - text-decoration: none !important; -} -.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a { - color: black !important; -} -.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a { - color: #008200 !important; -} -.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a { - color: blue !important; -} -.syntaxhighlighter.printing .keyword { - color: #006699 !important; - font-weight: bold !important; -} -.syntaxhighlighter.printing .preprocessor { - color: gray !important; -} -.syntaxhighlighter.printing .variable { - color: #aa7700 !important; -} -.syntaxhighlighter.printing .value { - color: #009900 !important; -} -.syntaxhighlighter.printing .functions { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .constants { - color: #0066cc !important; -} -.syntaxhighlighter.printing .script { - font-weight: bold !important; -} -.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a { - color: gray !important; -} -.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a { - color: red !important; -} -.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a { - color: black !important; -} - -.syntaxhighlighter { - background-color: #1b2426 !important; -} -.syntaxhighlighter .line.alt1 { - background-color: #1b2426 !important; -} -.syntaxhighlighter .line.alt2 { - background-color: #1b2426 !important; -} -.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { - background-color: #323e41 !important; -} -.syntaxhighlighter .line.highlighted.number { - color: #b9bdb6 !important; -} -.syntaxhighlighter table caption { - color: #b9bdb6 !important; -} -.syntaxhighlighter .gutter { - color: #afafaf !important; -} -.syntaxhighlighter .gutter .line { - border-right: 3px solid #435a5f !important; -} -.syntaxhighlighter .gutter .line.highlighted { - background-color: #435a5f !important; - color: #1b2426 !important; -} -.syntaxhighlighter.printing .line .content { - border: none !important; -} -.syntaxhighlighter.collapsed { - overflow: visible !important; -} -.syntaxhighlighter.collapsed .toolbar { - color: #5ba1cf !important; - background: black !important; - border: 1px solid #435a5f !important; -} -.syntaxhighlighter.collapsed .toolbar a { - color: #5ba1cf !important; -} -.syntaxhighlighter.collapsed .toolbar a:hover { - color: #5ce638 !important; -} -.syntaxhighlighter .toolbar { - color: white !important; - background: #435a5f !important; - border: none !important; -} -.syntaxhighlighter .toolbar a { - color: white !important; -} -.syntaxhighlighter .toolbar a:hover { - color: #e0e8ff !important; -} -.syntaxhighlighter .plain, .syntaxhighlighter .plain a { - color: #b9bdb6 !important; -} -.syntaxhighlighter .comments, .syntaxhighlighter .comments a { - color: #878a85 !important; -} -.syntaxhighlighter .string, .syntaxhighlighter .string a { - color: #5ce638 !important; -} -.syntaxhighlighter .keyword { - color: #5ba1cf !important; -} -.syntaxhighlighter .preprocessor { - color: #435a5f !important; -} -.syntaxhighlighter .variable { - color: #ffaa3e !important; -} -.syntaxhighlighter .value { - color: #009900 !important; -} -.syntaxhighlighter .functions { - color: #ffaa3e !important; -} -.syntaxhighlighter .constants { - color: #e0e8ff !important; -} -.syntaxhighlighter .script { - font-weight: bold !important; - color: #5ba1cf !important; - background-color: none !important; -} -.syntaxhighlighter .color1, .syntaxhighlighter .color1 a { - color: #e0e8ff !important; -} -.syntaxhighlighter .color2, .syntaxhighlighter .color2 a { - color: white !important; -} -.syntaxhighlighter .color3, .syntaxhighlighter .color3 a { - color: #ffaa3e !important; -} diff --git a/guides/assets/stylesheets/syntaxhighlighter/shThemeDefault.css b/guides/assets/stylesheets/syntaxhighlighter/shThemeDefault.css deleted file mode 100644 index 136541172d..0000000000 --- a/guides/assets/stylesheets/syntaxhighlighter/shThemeDefault.css +++ /dev/null @@ -1,117 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter { - background-color: white !important; -} -.syntaxhighlighter .line.alt1 { - background-color: white !important; -} -.syntaxhighlighter .line.alt2 { - background-color: white !important; -} -.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { - background-color: #e0e0e0 !important; -} -.syntaxhighlighter .line.highlighted.number { - color: black !important; -} -.syntaxhighlighter table caption { - color: black !important; -} -.syntaxhighlighter .gutter { - color: #afafaf !important; -} -.syntaxhighlighter .gutter .line { - border-right: 3px solid #6ce26c !important; -} -.syntaxhighlighter .gutter .line.highlighted { - background-color: #6ce26c !important; - color: white !important; -} -.syntaxhighlighter.printing .line .content { - border: none !important; -} -.syntaxhighlighter.collapsed { - overflow: visible !important; -} -.syntaxhighlighter.collapsed .toolbar { - color: blue !important; - background: white !important; - border: 1px solid #6ce26c !important; -} -.syntaxhighlighter.collapsed .toolbar a { - color: blue !important; -} -.syntaxhighlighter.collapsed .toolbar a:hover { - color: red !important; -} -.syntaxhighlighter .toolbar { - color: white !important; - background: #6ce26c !important; - border: none !important; -} -.syntaxhighlighter .toolbar a { - color: white !important; -} -.syntaxhighlighter .toolbar a:hover { - color: black !important; -} -.syntaxhighlighter .plain, .syntaxhighlighter .plain a { - color: black !important; -} -.syntaxhighlighter .comments, .syntaxhighlighter .comments a { - color: #008200 !important; -} -.syntaxhighlighter .string, .syntaxhighlighter .string a { - color: blue !important; -} -.syntaxhighlighter .keyword { - color: #006699 !important; -} -.syntaxhighlighter .preprocessor { - color: gray !important; -} -.syntaxhighlighter .variable { - color: #aa7700 !important; -} -.syntaxhighlighter .value { - color: #009900 !important; -} -.syntaxhighlighter .functions { - color: #ff1493 !important; -} -.syntaxhighlighter .constants { - color: #0066cc !important; -} -.syntaxhighlighter .script { - font-weight: bold !important; - color: #006699 !important; - background-color: none !important; -} -.syntaxhighlighter .color1, .syntaxhighlighter .color1 a { - color: gray !important; -} -.syntaxhighlighter .color2, .syntaxhighlighter .color2 a { - color: #ff1493 !important; -} -.syntaxhighlighter .color3, .syntaxhighlighter .color3 a { - color: red !important; -} - -.syntaxhighlighter .keyword { - font-weight: bold !important; -} diff --git a/guides/assets/stylesheets/syntaxhighlighter/shThemeDjango.css b/guides/assets/stylesheets/syntaxhighlighter/shThemeDjango.css deleted file mode 100644 index d8b4313433..0000000000 --- a/guides/assets/stylesheets/syntaxhighlighter/shThemeDjango.css +++ /dev/null @@ -1,120 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter { - background-color: #0a2b1d !important; -} -.syntaxhighlighter .line.alt1 { - background-color: #0a2b1d !important; -} -.syntaxhighlighter .line.alt2 { - background-color: #0a2b1d !important; -} -.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { - background-color: #233729 !important; -} -.syntaxhighlighter .line.highlighted.number { - color: white !important; -} -.syntaxhighlighter table caption { - color: #f8f8f8 !important; -} -.syntaxhighlighter .gutter { - color: #497958 !important; -} -.syntaxhighlighter .gutter .line { - border-right: 3px solid #41a83e !important; -} -.syntaxhighlighter .gutter .line.highlighted { - background-color: #41a83e !important; - color: #0a2b1d !important; -} -.syntaxhighlighter.printing .line .content { - border: none !important; -} -.syntaxhighlighter.collapsed { - overflow: visible !important; -} -.syntaxhighlighter.collapsed .toolbar { - color: #96dd3b !important; - background: black !important; - border: 1px solid #41a83e !important; -} -.syntaxhighlighter.collapsed .toolbar a { - color: #96dd3b !important; -} -.syntaxhighlighter.collapsed .toolbar a:hover { - color: white !important; -} -.syntaxhighlighter .toolbar { - color: white !important; - background: #41a83e !important; - border: none !important; -} -.syntaxhighlighter .toolbar a { - color: white !important; -} -.syntaxhighlighter .toolbar a:hover { - color: #ffe862 !important; -} -.syntaxhighlighter .plain, .syntaxhighlighter .plain a { - color: #f8f8f8 !important; -} -.syntaxhighlighter .comments, .syntaxhighlighter .comments a { - color: #336442 !important; -} -.syntaxhighlighter .string, .syntaxhighlighter .string a { - color: #9df39f !important; -} -.syntaxhighlighter .keyword { - color: #96dd3b !important; -} -.syntaxhighlighter .preprocessor { - color: #91bb9e !important; -} -.syntaxhighlighter .variable { - color: #ffaa3e !important; -} -.syntaxhighlighter .value { - color: #f7e741 !important; -} -.syntaxhighlighter .functions { - color: #ffaa3e !important; -} -.syntaxhighlighter .constants { - color: #e0e8ff !important; -} -.syntaxhighlighter .script { - font-weight: bold !important; - color: #96dd3b !important; - background-color: none !important; -} -.syntaxhighlighter .color1, .syntaxhighlighter .color1 a { - color: #eb939a !important; -} -.syntaxhighlighter .color2, .syntaxhighlighter .color2 a { - color: #91bb9e !important; -} -.syntaxhighlighter .color3, .syntaxhighlighter .color3 a { - color: #edef7d !important; -} - -.syntaxhighlighter .comments { - font-style: italic !important; -} -.syntaxhighlighter .keyword { - font-weight: bold !important; -} diff --git a/guides/assets/stylesheets/syntaxhighlighter/shThemeEclipse.css b/guides/assets/stylesheets/syntaxhighlighter/shThemeEclipse.css deleted file mode 100644 index 77377d9533..0000000000 --- a/guides/assets/stylesheets/syntaxhighlighter/shThemeEclipse.css +++ /dev/null @@ -1,128 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter { - background-color: white !important; -} -.syntaxhighlighter .line.alt1 { - background-color: white !important; -} -.syntaxhighlighter .line.alt2 { - background-color: white !important; -} -.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { - background-color: #c3defe !important; -} -.syntaxhighlighter .line.highlighted.number { - color: white !important; -} -.syntaxhighlighter table caption { - color: black !important; -} -.syntaxhighlighter .gutter { - color: #787878 !important; -} -.syntaxhighlighter .gutter .line { - border-right: 3px solid #d4d0c8 !important; -} -.syntaxhighlighter .gutter .line.highlighted { - background-color: #d4d0c8 !important; - color: white !important; -} -.syntaxhighlighter.printing .line .content { - border: none !important; -} -.syntaxhighlighter.collapsed { - overflow: visible !important; -} -.syntaxhighlighter.collapsed .toolbar { - color: #3f5fbf !important; - background: white !important; - border: 1px solid #d4d0c8 !important; -} -.syntaxhighlighter.collapsed .toolbar a { - color: #3f5fbf !important; -} -.syntaxhighlighter.collapsed .toolbar a:hover { - color: #aa7700 !important; -} -.syntaxhighlighter .toolbar { - color: #a0a0a0 !important; - background: #d4d0c8 !important; - border: none !important; -} -.syntaxhighlighter .toolbar a { - color: #a0a0a0 !important; -} -.syntaxhighlighter .toolbar a:hover { - color: red !important; -} -.syntaxhighlighter .plain, .syntaxhighlighter .plain a { - color: black !important; -} -.syntaxhighlighter .comments, .syntaxhighlighter .comments a { - color: #3f5fbf !important; -} -.syntaxhighlighter .string, .syntaxhighlighter .string a { - color: #2a00ff !important; -} -.syntaxhighlighter .keyword { - color: #7f0055 !important; -} -.syntaxhighlighter .preprocessor { - color: #646464 !important; -} -.syntaxhighlighter .variable { - color: #aa7700 !important; -} -.syntaxhighlighter .value { - color: #009900 !important; -} -.syntaxhighlighter .functions { - color: #ff1493 !important; -} -.syntaxhighlighter .constants { - color: #0066cc !important; -} -.syntaxhighlighter .script { - font-weight: bold !important; - color: #7f0055 !important; - background-color: none !important; -} -.syntaxhighlighter .color1, .syntaxhighlighter .color1 a { - color: gray !important; -} -.syntaxhighlighter .color2, .syntaxhighlighter .color2 a { - color: #ff1493 !important; -} -.syntaxhighlighter .color3, .syntaxhighlighter .color3 a { - color: red !important; -} - -.syntaxhighlighter .keyword { - font-weight: bold !important; -} -.syntaxhighlighter .xml .keyword { - color: #3f7f7f !important; - font-weight: normal !important; -} -.syntaxhighlighter .xml .color1, .syntaxhighlighter .xml .color1 a { - color: #7f007f !important; -} -.syntaxhighlighter .xml .string { - font-style: italic !important; - color: #2a00ff !important; -} diff --git a/guides/assets/stylesheets/syntaxhighlighter/shThemeEmacs.css b/guides/assets/stylesheets/syntaxhighlighter/shThemeEmacs.css deleted file mode 100644 index dae5053fea..0000000000 --- a/guides/assets/stylesheets/syntaxhighlighter/shThemeEmacs.css +++ /dev/null @@ -1,113 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter { - background-color: black !important; -} -.syntaxhighlighter .line.alt1 { - background-color: black !important; -} -.syntaxhighlighter .line.alt2 { - background-color: black !important; -} -.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { - background-color: #2a3133 !important; -} -.syntaxhighlighter .line.highlighted.number { - color: white !important; -} -.syntaxhighlighter table caption { - color: #d3d3d3 !important; -} -.syntaxhighlighter .gutter { - color: #d3d3d3 !important; -} -.syntaxhighlighter .gutter .line { - border-right: 3px solid #990000 !important; -} -.syntaxhighlighter .gutter .line.highlighted { - background-color: #990000 !important; - color: black !important; -} -.syntaxhighlighter.printing .line .content { - border: none !important; -} -.syntaxhighlighter.collapsed { - overflow: visible !important; -} -.syntaxhighlighter.collapsed .toolbar { - color: #ebdb8d !important; - background: black !important; - border: 1px solid #990000 !important; -} -.syntaxhighlighter.collapsed .toolbar a { - color: #ebdb8d !important; -} -.syntaxhighlighter.collapsed .toolbar a:hover { - color: #ff7d27 !important; -} -.syntaxhighlighter .toolbar { - color: white !important; - background: #990000 !important; - border: none !important; -} -.syntaxhighlighter .toolbar a { - color: white !important; -} -.syntaxhighlighter .toolbar a:hover { - color: #9ccff4 !important; -} -.syntaxhighlighter .plain, .syntaxhighlighter .plain a { - color: #d3d3d3 !important; -} -.syntaxhighlighter .comments, .syntaxhighlighter .comments a { - color: #ff7d27 !important; -} -.syntaxhighlighter .string, .syntaxhighlighter .string a { - color: #ff9e7b !important; -} -.syntaxhighlighter .keyword { - color: aqua !important; -} -.syntaxhighlighter .preprocessor { - color: #aec4de !important; -} -.syntaxhighlighter .variable { - color: #ffaa3e !important; -} -.syntaxhighlighter .value { - color: #009900 !important; -} -.syntaxhighlighter .functions { - color: #81cef9 !important; -} -.syntaxhighlighter .constants { - color: #ff9e7b !important; -} -.syntaxhighlighter .script { - font-weight: bold !important; - color: aqua !important; - background-color: none !important; -} -.syntaxhighlighter .color1, .syntaxhighlighter .color1 a { - color: #ebdb8d !important; -} -.syntaxhighlighter .color2, .syntaxhighlighter .color2 a { - color: #ff7d27 !important; -} -.syntaxhighlighter .color3, .syntaxhighlighter .color3 a { - color: #aec4de !important; -} diff --git a/guides/assets/stylesheets/syntaxhighlighter/shThemeFadeToGrey.css b/guides/assets/stylesheets/syntaxhighlighter/shThemeFadeToGrey.css deleted file mode 100644 index 8fbd871fb5..0000000000 --- a/guides/assets/stylesheets/syntaxhighlighter/shThemeFadeToGrey.css +++ /dev/null @@ -1,117 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter { - background-color: #121212 !important; -} -.syntaxhighlighter .line.alt1 { - background-color: #121212 !important; -} -.syntaxhighlighter .line.alt2 { - background-color: #121212 !important; -} -.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { - background-color: #2c2c29 !important; -} -.syntaxhighlighter .line.highlighted.number { - color: white !important; -} -.syntaxhighlighter table caption { - color: white !important; -} -.syntaxhighlighter .gutter { - color: #afafaf !important; -} -.syntaxhighlighter .gutter .line { - border-right: 3px solid #3185b9 !important; -} -.syntaxhighlighter .gutter .line.highlighted { - background-color: #3185b9 !important; - color: #121212 !important; -} -.syntaxhighlighter.printing .line .content { - border: none !important; -} -.syntaxhighlighter.collapsed { - overflow: visible !important; -} -.syntaxhighlighter.collapsed .toolbar { - color: #3185b9 !important; - background: black !important; - border: 1px solid #3185b9 !important; -} -.syntaxhighlighter.collapsed .toolbar a { - color: #3185b9 !important; -} -.syntaxhighlighter.collapsed .toolbar a:hover { - color: #d01d33 !important; -} -.syntaxhighlighter .toolbar { - color: white !important; - background: #3185b9 !important; - border: none !important; -} -.syntaxhighlighter .toolbar a { - color: white !important; -} -.syntaxhighlighter .toolbar a:hover { - color: #96daff !important; -} -.syntaxhighlighter .plain, .syntaxhighlighter .plain a { - color: white !important; -} -.syntaxhighlighter .comments, .syntaxhighlighter .comments a { - color: #696854 !important; -} -.syntaxhighlighter .string, .syntaxhighlighter .string a { - color: #e3e658 !important; -} -.syntaxhighlighter .keyword { - color: #d01d33 !important; -} -.syntaxhighlighter .preprocessor { - color: #435a5f !important; -} -.syntaxhighlighter .variable { - color: #898989 !important; -} -.syntaxhighlighter .value { - color: #009900 !important; -} -.syntaxhighlighter .functions { - color: #aaaaaa !important; -} -.syntaxhighlighter .constants { - color: #96daff !important; -} -.syntaxhighlighter .script { - font-weight: bold !important; - color: #d01d33 !important; - background-color: none !important; -} -.syntaxhighlighter .color1, .syntaxhighlighter .color1 a { - color: #ffc074 !important; -} -.syntaxhighlighter .color2, .syntaxhighlighter .color2 a { - color: #4a8cdb !important; -} -.syntaxhighlighter .color3, .syntaxhighlighter .color3 a { - color: #96daff !important; -} - -.syntaxhighlighter .functions { - font-weight: bold !important; -} diff --git a/guides/assets/stylesheets/syntaxhighlighter/shThemeMDUltra.css b/guides/assets/stylesheets/syntaxhighlighter/shThemeMDUltra.css deleted file mode 100755 index f4db39cd83..0000000000 --- a/guides/assets/stylesheets/syntaxhighlighter/shThemeMDUltra.css +++ /dev/null @@ -1,113 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter { - background-color: #222222 !important; -} -.syntaxhighlighter .line.alt1 { - background-color: #222222 !important; -} -.syntaxhighlighter .line.alt2 { - background-color: #222222 !important; -} -.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { - background-color: #253e5a !important; -} -.syntaxhighlighter .line.highlighted.number { - color: white !important; -} -.syntaxhighlighter table caption { - color: lime !important; -} -.syntaxhighlighter .gutter { - color: #38566f !important; -} -.syntaxhighlighter .gutter .line { - border-right: 3px solid #435a5f !important; -} -.syntaxhighlighter .gutter .line.highlighted { - background-color: #435a5f !important; - color: #222222 !important; -} -.syntaxhighlighter.printing .line .content { - border: none !important; -} -.syntaxhighlighter.collapsed { - overflow: visible !important; -} -.syntaxhighlighter.collapsed .toolbar { - color: #428bdd !important; - background: black !important; - border: 1px solid #435a5f !important; -} -.syntaxhighlighter.collapsed .toolbar a { - color: #428bdd !important; -} -.syntaxhighlighter.collapsed .toolbar a:hover { - color: lime !important; -} -.syntaxhighlighter .toolbar { - color: #aaaaff !important; - background: #435a5f !important; - border: none !important; -} -.syntaxhighlighter .toolbar a { - color: #aaaaff !important; -} -.syntaxhighlighter .toolbar a:hover { - color: #9ccff4 !important; -} -.syntaxhighlighter .plain, .syntaxhighlighter .plain a { - color: lime !important; -} -.syntaxhighlighter .comments, .syntaxhighlighter .comments a { - color: #428bdd !important; -} -.syntaxhighlighter .string, .syntaxhighlighter .string a { - color: lime !important; -} -.syntaxhighlighter .keyword { - color: #aaaaff !important; -} -.syntaxhighlighter .preprocessor { - color: #8aa6c1 !important; -} -.syntaxhighlighter .variable { - color: aqua !important; -} -.syntaxhighlighter .value { - color: #f7e741 !important; -} -.syntaxhighlighter .functions { - color: #ff8000 !important; -} -.syntaxhighlighter .constants { - color: yellow !important; -} -.syntaxhighlighter .script { - font-weight: bold !important; - color: #aaaaff !important; - background-color: none !important; -} -.syntaxhighlighter .color1, .syntaxhighlighter .color1 a { - color: red !important; -} -.syntaxhighlighter .color2, .syntaxhighlighter .color2 a { - color: yellow !important; -} -.syntaxhighlighter .color3, .syntaxhighlighter .color3 a { - color: #ffaa3e !important; -} diff --git a/guides/assets/stylesheets/syntaxhighlighter/shThemeMidnight.css b/guides/assets/stylesheets/syntaxhighlighter/shThemeMidnight.css deleted file mode 100644 index c49563cc9d..0000000000 --- a/guides/assets/stylesheets/syntaxhighlighter/shThemeMidnight.css +++ /dev/null @@ -1,113 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter { - background-color: #0f192a !important; -} -.syntaxhighlighter .line.alt1 { - background-color: #0f192a !important; -} -.syntaxhighlighter .line.alt2 { - background-color: #0f192a !important; -} -.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { - background-color: #253e5a !important; -} -.syntaxhighlighter .line.highlighted.number { - color: #38566f !important; -} -.syntaxhighlighter table caption { - color: #d1edff !important; -} -.syntaxhighlighter .gutter { - color: #afafaf !important; -} -.syntaxhighlighter .gutter .line { - border-right: 3px solid #435a5f !important; -} -.syntaxhighlighter .gutter .line.highlighted { - background-color: #435a5f !important; - color: #0f192a !important; -} -.syntaxhighlighter.printing .line .content { - border: none !important; -} -.syntaxhighlighter.collapsed { - overflow: visible !important; -} -.syntaxhighlighter.collapsed .toolbar { - color: #428bdd !important; - background: black !important; - border: 1px solid #435a5f !important; -} -.syntaxhighlighter.collapsed .toolbar a { - color: #428bdd !important; -} -.syntaxhighlighter.collapsed .toolbar a:hover { - color: #1dc116 !important; -} -.syntaxhighlighter .toolbar { - color: #d1edff !important; - background: #435a5f !important; - border: none !important; -} -.syntaxhighlighter .toolbar a { - color: #d1edff !important; -} -.syntaxhighlighter .toolbar a:hover { - color: #8aa6c1 !important; -} -.syntaxhighlighter .plain, .syntaxhighlighter .plain a { - color: #d1edff !important; -} -.syntaxhighlighter .comments, .syntaxhighlighter .comments a { - color: #428bdd !important; -} -.syntaxhighlighter .string, .syntaxhighlighter .string a { - color: #1dc116 !important; -} -.syntaxhighlighter .keyword { - color: #b43d3d !important; -} -.syntaxhighlighter .preprocessor { - color: #8aa6c1 !important; -} -.syntaxhighlighter .variable { - color: #ffaa3e !important; -} -.syntaxhighlighter .value { - color: #f7e741 !important; -} -.syntaxhighlighter .functions { - color: #ffaa3e !important; -} -.syntaxhighlighter .constants { - color: #e0e8ff !important; -} -.syntaxhighlighter .script { - font-weight: bold !important; - color: #b43d3d !important; - background-color: none !important; -} -.syntaxhighlighter .color1, .syntaxhighlighter .color1 a { - color: #f8bb00 !important; -} -.syntaxhighlighter .color2, .syntaxhighlighter .color2 a { - color: white !important; -} -.syntaxhighlighter .color3, .syntaxhighlighter .color3 a { - color: #ffaa3e !important; -} diff --git a/guides/assets/stylesheets/syntaxhighlighter/shThemeRDark.css b/guides/assets/stylesheets/syntaxhighlighter/shThemeRDark.css deleted file mode 100644 index 6305a10e4e..0000000000 --- a/guides/assets/stylesheets/syntaxhighlighter/shThemeRDark.css +++ /dev/null @@ -1,113 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter { - background-color: #1b2426 !important; -} -.syntaxhighlighter .line.alt1 { - background-color: #1b2426 !important; -} -.syntaxhighlighter .line.alt2 { - background-color: #1b2426 !important; -} -.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { - background-color: #323e41 !important; -} -.syntaxhighlighter .line.highlighted.number { - color: #b9bdb6 !important; -} -.syntaxhighlighter table caption { - color: #b9bdb6 !important; -} -.syntaxhighlighter .gutter { - color: #afafaf !important; -} -.syntaxhighlighter .gutter .line { - border-right: 3px solid #435a5f !important; -} -.syntaxhighlighter .gutter .line.highlighted { - background-color: #435a5f !important; - color: #1b2426 !important; -} -.syntaxhighlighter.printing .line .content { - border: none !important; -} -.syntaxhighlighter.collapsed { - overflow: visible !important; -} -.syntaxhighlighter.collapsed .toolbar { - color: #5ba1cf !important; - background: black !important; - border: 1px solid #435a5f !important; -} -.syntaxhighlighter.collapsed .toolbar a { - color: #5ba1cf !important; -} -.syntaxhighlighter.collapsed .toolbar a:hover { - color: #5ce638 !important; -} -.syntaxhighlighter .toolbar { - color: white !important; - background: #435a5f !important; - border: none !important; -} -.syntaxhighlighter .toolbar a { - color: white !important; -} -.syntaxhighlighter .toolbar a:hover { - color: #e0e8ff !important; -} -.syntaxhighlighter .plain, .syntaxhighlighter .plain a { - color: #b9bdb6 !important; -} -.syntaxhighlighter .comments, .syntaxhighlighter .comments a { - color: #878a85 !important; -} -.syntaxhighlighter .string, .syntaxhighlighter .string a { - color: #5ce638 !important; -} -.syntaxhighlighter .keyword { - color: #5ba1cf !important; -} -.syntaxhighlighter .preprocessor { - color: #435a5f !important; -} -.syntaxhighlighter .variable { - color: #ffaa3e !important; -} -.syntaxhighlighter .value { - color: #009900 !important; -} -.syntaxhighlighter .functions { - color: #ffaa3e !important; -} -.syntaxhighlighter .constants { - color: #e0e8ff !important; -} -.syntaxhighlighter .script { - font-weight: bold !important; - color: #5ba1cf !important; - background-color: none !important; -} -.syntaxhighlighter .color1, .syntaxhighlighter .color1 a { - color: #e0e8ff !important; -} -.syntaxhighlighter .color2, .syntaxhighlighter .color2 a { - color: white !important; -} -.syntaxhighlighter .color3, .syntaxhighlighter .color3 a { - color: #ffaa3e !important; -} diff --git a/guides/assets/stylesheets/syntaxhighlighter/shThemeRailsGuides.css b/guides/assets/stylesheets/syntaxhighlighter/shThemeRailsGuides.css index 6d2edb2eb8..bc7afd3898 100644 --- a/guides/assets/stylesheets/syntaxhighlighter/shThemeRailsGuides.css +++ b/guides/assets/stylesheets/syntaxhighlighter/shThemeRailsGuides.css @@ -90,7 +90,7 @@ } .syntaxhighlighter .script { color: #222 !important; - background-color: none !important; + background-color: transparent !important; } .syntaxhighlighter .color1, .syntaxhighlighter .color1 a { color: gray !important; diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb index 58ba708a39..960d269d90 100644 --- a/guides/bug_report_templates/action_controller_gem.rb +++ b/guides/bug_report_templates/action_controller_gem.rb @@ -1,30 +1,30 @@ begin - require 'bundler/inline' + require "bundler/inline" rescue LoadError => e - $stderr.puts 'Bundler version 1.10 or later is required. Please update your Bundler' + $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler" raise e end gemfile(true) do - source 'https://rubygems.org' + source "https://rubygems.org" # Activate the gem you are reporting the issue against. - gem 'rails', '4.2.0' + gem "rails", "5.0.0" end -require 'rack/test' -require 'action_controller/railtie' +require "rack/test" +require "action_controller/railtie" class TestApp < Rails::Application config.root = File.dirname(__FILE__) - config.session_store :cookie_store, key: 'cookie_store_key' - secrets.secret_token = 'secret_token' - secrets.secret_key_base = 'secret_key_base' + config.session_store :cookie_store, key: "cookie_store_key" + secrets.secret_token = "secret_token" + secrets.secret_key_base = "secret_key_base" config.logger = Logger.new($stdout) Rails.logger = config.logger routes.draw do - get '/' => 'test#index' + get "/" => "test#index" end end @@ -32,11 +32,11 @@ class TestController < ActionController::Base include Rails.application.routes.url_helpers def index - render plain: 'Home' + render plain: "Home" end end -require 'minitest/autorun' +require "minitest/autorun" # Ensure backward compatibility with Minitest 4 Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test) @@ -45,7 +45,7 @@ class BugTest < Minitest::Test include Rack::Test::Methods def test_returns_success - get '/' + get "/" assert last_response.ok? end diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb index 3f24aa3b4d..486c7243ad 100644 --- a/guides/bug_report_templates/action_controller_master.rb +++ b/guides/bug_report_templates/action_controller_master.rb @@ -1,33 +1,27 @@ begin - require 'bundler/inline' + require "bundler/inline" rescue LoadError => e - $stderr.puts 'Bundler version 1.10 or later is required. Please update your Bundler' + $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler" raise e end gemfile(true) do - source 'https://rubygems.org' - gem 'rails', github: 'rails/rails' - gem 'arel', github: 'rails/arel' - gem 'rack', github: 'rack/rack' - gem 'sprockets', github: 'rails/sprockets' - gem 'sprockets-rails', github: 'rails/sprockets-rails' - gem 'sass-rails', github: 'rails/sass-rails' + source "https://rubygems.org" + gem "rails", github: "rails/rails" end -require 'action_controller/railtie' +require "action_controller/railtie" class TestApp < Rails::Application config.root = File.dirname(__FILE__) - config.session_store :cookie_store, key: 'cookie_store_key' - secrets.secret_token = 'secret_token' - secrets.secret_key_base = 'secret_key_base' + secrets.secret_token = "secret_token" + secrets.secret_key_base = "secret_key_base" config.logger = Logger.new($stdout) Rails.logger = config.logger routes.draw do - get '/' => 'test#index' + get "/" => "test#index" end end @@ -35,18 +29,18 @@ class TestController < ActionController::Base include Rails.application.routes.url_helpers def index - render plain: 'Home' + render plain: "Home" end end -require 'minitest/autorun' -require 'rack/test' +require "minitest/autorun" +require "rack/test" class BugTest < Minitest::Test include Rack::Test::Methods def test_returns_success - get '/' + get "/" assert last_response.ok? end diff --git a/guides/bug_report_templates/active_job_gem.rb b/guides/bug_report_templates/active_job_gem.rb new file mode 100644 index 0000000000..debc46ad54 --- /dev/null +++ b/guides/bug_report_templates/active_job_gem.rb @@ -0,0 +1,32 @@ +begin + require "bundler/inline" +rescue LoadError => e + $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler" + raise e +end + +gemfile(true) do + source "https://rubygems.org" + # Activate the gem you are reporting the issue against. + gem "activejob", "5.0.0" +end + +require "minitest/autorun" +require "active_job" + +# Ensure backward compatibility with Minitest 4 +Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test) + +class BuggyJob < ActiveJob::Base + def perform + puts "performed" + end +end + +class BuggyJobTest < ActiveJob::TestCase + def test_stuff + assert_enqueued_with(job: BuggyJob) do + BuggyJob.perform_later + end + end +end diff --git a/guides/bug_report_templates/active_job_master.rb b/guides/bug_report_templates/active_job_master.rb new file mode 100644 index 0000000000..f61518713f --- /dev/null +++ b/guides/bug_report_templates/active_job_master.rb @@ -0,0 +1,31 @@ +begin + require "bundler/inline" +rescue LoadError => e + $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler" + raise e +end + +gemfile(true) do + source "https://rubygems.org" + gem "rails", github: "rails/rails" +end + +require "active_job" +require "minitest/autorun" + +# Ensure backward compatibility with Minitest 4 +Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test) + +class BuggyJob < ActiveJob::Base + def perform + puts "performed" + end +end + +class BuggyJobTest < ActiveJob::TestCase + def test_stuff + assert_enqueued_with(job: BuggyJob) do + BuggyJob.perform_later + end + end +end diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb index 09d6e7b331..e18302fe65 100644 --- a/guides/bug_report_templates/active_record_gem.rb +++ b/guides/bug_report_templates/active_record_gem.rb @@ -1,26 +1,26 @@ begin - require 'bundler/inline' + require "bundler/inline" rescue LoadError => e - $stderr.puts 'Bundler version 1.10 or later is required. Please update your Bundler' + $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler" raise e end gemfile(true) do - source 'https://rubygems.org' + source "https://rubygems.org" # Activate the gem you are reporting the issue against. - gem 'activerecord', '4.2.0' - gem 'sqlite3' + gem "activerecord", "5.0.0" + gem "sqlite3" end -require 'active_record' -require 'minitest/autorun' -require 'logger' +require "active_record" +require "minitest/autorun" +require "logger" # Ensure backward compatibility with Minitest 4 Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test) # This connection will do for database-independent bug reports. -ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') +ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") ActiveRecord::Base.logger = Logger.new(STDOUT) ActiveRecord::Schema.define do diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb index 5b742a9093..7265a671b0 100644 --- a/guides/bug_report_templates/active_record_master.rb +++ b/guides/bug_report_templates/active_record_master.rb @@ -1,34 +1,29 @@ begin - require 'bundler/inline' + require "bundler/inline" rescue LoadError => e - $stderr.puts 'Bundler version 1.10 or later is required. Please update your Bundler' + $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler" raise e end gemfile(true) do - source 'https://rubygems.org' - gem 'rails', github: 'rails/rails' - gem 'arel', github: 'rails/arel' - gem 'rack', github: 'rack/rack' - gem 'sprockets', github: 'rails/sprockets' - gem 'sprockets-rails', github: 'rails/sprockets-rails' - gem 'sass-rails', github: 'rails/sass-rails' - gem 'sqlite3' + source "https://rubygems.org" + gem "rails", github: "rails/rails" + gem "sqlite3" end -require 'active_record' -require 'minitest/autorun' -require 'logger' +require "active_record" +require "minitest/autorun" +require "logger" # This connection will do for database-independent bug reports. -ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') +ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") ActiveRecord::Base.logger = Logger.new(STDOUT) ActiveRecord::Schema.define do - create_table :posts, force: true do |t| + create_table :posts, force: true do |t| end - create_table :comments, force: true do |t| + create_table :comments, force: true do |t| t.integer :post_id end end diff --git a/guides/bug_report_templates/active_record_migrations_gem.rb b/guides/bug_report_templates/active_record_migrations_gem.rb new file mode 100644 index 0000000000..f568a111f6 --- /dev/null +++ b/guides/bug_report_templates/active_record_migrations_gem.rb @@ -0,0 +1,65 @@ +begin + require "bundler/inline" +rescue LoadError => e + $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler" + raise e +end + +gemfile(true) do + source "https://rubygems.org" + # Activate the gem you are reporting the issue against. + gem "activerecord", "5.0.0.1" + gem "sqlite3" +end + +require "active_record" +require "minitest/autorun" +require "logger" + +# Ensure backward compatibility with Minitest 4 +Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test) + +# This connection will do for database-independent bug reports. +ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") +ActiveRecord::Base.logger = Logger.new(STDOUT) + +ActiveRecord::Schema.define do + create_table :payments, force: true do |t| + t.decimal :amount, precision: 10, scale: 0, default: 0, null: false + end +end + +class Payment < ActiveRecord::Base +end + +class ChangeAmountToAddScale < ActiveRecord::Migration[5.0] + def change + reversible do |dir| + dir.up do + change_column :payments, :amount, :decimal, precision: 10, scale: 2, default: 0, null: false + end + + dir.down do + change_column :payments, :amount, :decimal, precision: 10, scale: 0, default: 0, null: false + end + end + end +end + +class BugTest < Minitest::Test + def test_migration_up + migrator = ActiveRecord::Migrator.new(:up, [ChangeAmountToAddScale]) + migrator.run + Payment.reset_column_information + + assert_equal "decimal(10,2)", Payment.columns.last.sql_type + end + + def test_migration_down + migrator = ActiveRecord::Migrator.new(:down, [ChangeAmountToAddScale]) + migrator.run + Payment.reset_column_information + + assert_equal "decimal(10,0)", Payment.columns.last.sql_type + end +end
\ No newline at end of file diff --git a/guides/bug_report_templates/active_record_migrations_master.rb b/guides/bug_report_templates/active_record_migrations_master.rb new file mode 100644 index 0000000000..ef7b42e0a6 --- /dev/null +++ b/guides/bug_report_templates/active_record_migrations_master.rb @@ -0,0 +1,64 @@ +begin + require "bundler/inline" +rescue LoadError => e + $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler" + raise e +end + +gemfile(true) do + source "https://rubygems.org" + gem "rails", github: "rails/rails" + gem "sqlite3" +end + +require "active_record" +require "minitest/autorun" +require "logger" + +# Ensure backward compatibility with Minitest 4 +Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test) + +# This connection will do for database-independent bug reports. +ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") +ActiveRecord::Base.logger = Logger.new(STDOUT) + +ActiveRecord::Schema.define do + create_table :payments, force: true do |t| + t.decimal :amount, precision: 10, scale: 0, default: 0, null: false + end +end + +class Payment < ActiveRecord::Base +end + +class ChangeAmountToAddScale < ActiveRecord::Migration[5.0] + def change + reversible do |dir| + dir.up do + change_column :payments, :amount, :decimal, precision: 10, scale: 2, default: 0, null: false + end + + dir.down do + change_column :payments, :amount, :decimal, precision: 10, scale: 0, default: 0, null: false + end + end + end +end + +class BugTest < Minitest::Test + def test_migration_up + migrator = ActiveRecord::Migrator.new(:up, [ChangeAmountToAddScale]) + migrator.run + Payment.reset_column_information + + assert_equal "decimal(10,2)", Payment.columns.last.sql_type + end + + def test_migration_down + migrator = ActiveRecord::Migrator.new(:down, [ChangeAmountToAddScale]) + migrator.run + Payment.reset_column_information + + assert_equal "decimal(10,0)", Payment.columns.last.sql_type + end +end
\ No newline at end of file diff --git a/guides/bug_report_templates/benchmark.rb b/guides/bug_report_templates/benchmark.rb new file mode 100644 index 0000000000..ae51d7027f --- /dev/null +++ b/guides/bug_report_templates/benchmark.rb @@ -0,0 +1,49 @@ +begin + require "bundler/inline" +rescue LoadError => e + $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler" + raise e +end + +gemfile(true) do + source "https://rubygems.org" + gem "rails", github: "rails/rails" + gem "benchmark-ips" +end + +require "active_support" +require "active_support/core_ext/object/blank" + +# Your patch goes here. +class String + def fast_blank? + true + end +end + +# Enumerate some representative scenarios here. +# +# It is very easy to make an optimization that improves performance for a +# specific scenario you care about but regresses on other common cases. +# Therefore, you should test your change against a list of representative +# scenarios. Ideally, they should be based on real-world scenarios extracted +# from production applications. +SCENARIOS = { + "Empty" => "", + "Single Space" => " ", + "Two Spaces" => " ", + "Mixed Whitspaces" => " \t\r\n", + "Very Long String" => " " * 100 +} + +SCENARIOS.each_pair do |name, value| + puts + puts " #{name} ".center(80, "=") + puts + + Benchmark.ips do |x| + x.report('blank?') { value.blank? } + x.report('fast_blank?') { value.fast_blank? } + x.compare! + end +end diff --git a/guides/bug_report_templates/generic_gem.rb b/guides/bug_report_templates/generic_gem.rb index a4fe51156d..a94848e25b 100644 --- a/guides/bug_report_templates/generic_gem.rb +++ b/guides/bug_report_templates/generic_gem.rb @@ -1,18 +1,18 @@ begin - require 'bundler/inline' + require "bundler/inline" rescue LoadError => e - $stderr.puts 'Bundler version 1.10 or later is required. Please update your Bundler' + $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler" raise e end gemfile(true) do - source 'https://rubygems.org' + source "https://rubygems.org" # Activate the gem you are reporting the issue against. - gem 'activesupport', '4.2.0' + gem "activesupport", "5.0.0" end -require 'active_support/core_ext/object/blank' -require 'minitest/autorun' +require "active_support/core_ext/object/blank" +require "minitest/autorun" # Ensure backward compatibility with Minitest 4 Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test) diff --git a/guides/bug_report_templates/generic_master.rb b/guides/bug_report_templates/generic_master.rb index fcc90fa503..d3a7ae4ac4 100644 --- a/guides/bug_report_templates/generic_master.rb +++ b/guides/bug_report_templates/generic_master.rb @@ -1,23 +1,18 @@ begin - require 'bundler/inline' + require "bundler/inline" rescue LoadError => e - $stderr.puts 'Bundler version 1.10 or later is required. Please update your Bundler' + $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler" raise e end gemfile(true) do - source 'https://rubygems.org' - gem 'rails', github: 'rails/rails' - gem 'arel', github: 'rails/arel' - gem 'rack', github: 'rack/rack' - gem 'sprockets', github: 'rails/sprockets' - gem 'sprockets-rails', github: 'rails/sprockets-rails' - gem 'sass-rails', github: 'rails/sass-rails' + source "https://rubygems.org" + gem "rails", github: "rails/rails" end -require 'active_support' -require 'active_support/core_ext/object/blank' -require 'minitest/autorun' +require "active_support" +require "active_support/core_ext/object/blank" +require "minitest/autorun" class BugTest < Minitest::Test def test_stuff diff --git a/guides/rails_guides.rb b/guides/rails_guides.rb index 367ed0b12e..557d23f78c 100644 --- a/guides/rails_guides.rb +++ b/guides/rails_guides.rb @@ -10,7 +10,7 @@ begin $:.unshift ap_lib if File.directory?(ap_lib) rescue LoadError # Guides generation from gems. - gem "actionpack", '>= 3.0' + gem "actionpack", ">= 3.0" end require "rails_guides/generator" diff --git a/guides/rails_guides/generator.rb b/guides/rails_guides/generator.rb index 7618fce2c8..39a57191eb 100644 --- a/guides/rails_guides/generator.rb +++ b/guides/rails_guides/generator.rb @@ -28,12 +28,12 @@ # enough: # # # generates only association_basics.html -# ONLY=assoc ruby rails_guides.rb +# ONLY=assoc rake guides:generate # # Separate many using commas: # -# # generates only association_basics.html and migrations.html -# ONLY=assoc,migrations ruby rails_guides.rb +# # 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. @@ -49,18 +49,18 @@ # # --------------------------------------------------------------------------- -require 'set' -require 'fileutils' +require "set" +require "fileutils" -require 'active_support/core_ext/string/output_safety' -require 'active_support/core_ext/object/blank' -require 'action_controller' -require 'action_view' +require "active_support/core_ext/string/output_safety" +require "active_support/core_ext/object/blank" +require "action_controller" +require "action_view" -require 'rails_guides/markdown' -require 'rails_guides/indexer' -require 'rails_guides/helpers' -require 'rails_guides/levenshtein' +require "rails_guides/markdown" +require "rails_guides/indexer" +require "rails_guides/helpers" +require "rails_guides/levenshtein" module RailsGuides class Generator @@ -81,12 +81,12 @@ module RailsGuides 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'] + @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 @@ -102,148 +102,148 @@ module RailsGuides private - def kindle? - @kindle - end + def kindle? + @kindle + end - def check_for_kindlegen - if `which kindlegen`.blank? - raise "Can't create a kindle version without `kindlegen`." + def check_for_kindlegen + if `which kindlegen`.blank? + raise "Can't create a kindle version without `kindlegen`." + end end - end - def generate_mobi - require 'rails_guides/kindle' - out = "#{output_dir}/kindlegen.out" - Kindle.generate(output_dir, mobi, out) - puts "(kindlegen log at #{out})." - end + def generate_mobi + require "rails_guides/kindle" + 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" : '') - end + def mobi + "ruby_on_rails_guides_#@version%s.mobi" % (@lang.present? ? ".#@lang" : "") + 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</$>, '') - 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</$>, "") + end - def create_output_dir_if_needed - FileUtils.mkdir_p(output_dir) - end + def create_output_dir_if_needed + FileUtils.mkdir_p(output_dir) + end - def generate_guides - guides_to_generate.each do |guide| - output_file = output_file_for(guide) - generate_guide(guide, output_file) if generate?(guide, output_file) + def generate_guides + guides_to_generate.each do |guide| + output_file = output_file_for(guide) + generate_guide(guide, output_file) if generate?(guide, output_file) + end end - end - def guides_to_generate - guides = Dir.entries(source_dir).grep(GUIDES_RE) + def guides_to_generate + guides = Dir.entries(source_dir).grep(GUIDES_RE) - if kindle? - Dir.entries("#{source_dir}/kindle").grep(GUIDES_RE).map do |entry| - next if entry == 'KINDLE.md' - guides << "kindle/#{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 end - ENV.key?('ONLY') ? select_only(guides) : guides - end + def select_only(guides) + prefixes = ENV["ONLY"].split(",").map(&:strip) + guides.select do |guide| + guide.start_with?("kindle".freeze, *prefixes) + end + end - def select_only(guides) - prefixes = ENV['ONLY'].split(",").map(&:strip) - guides.select do |guide| - guide.start_with?('kindle'.freeze, *prefixes) + def copy_assets + FileUtils.cp_r(Dir.glob("#{guides_dir}/assets/*"), output_dir) end - end - def copy_assets - FileUtils.cp_r(Dir.glob("#{guides_dir}/assets/*"), output_dir) - end + def output_file_for(guide) + if guide.end_with?(".md") + guide.sub(/md\z/, "html") + else + guide.sub(/\.erb\z/, "") + end + end - def output_file_for(guide) - if guide.end_with?('.md') - guide.sub(/md\z/, 'html') - else - guide.sub(/\.erb\z/, '') + def output_path_for(output_file) + File.join(output_dir, File.basename(output_file)) end - end - def output_path_for(output_file) - File.join(output_dir, File.basename(output_file)) - end + def generate?(source_file, output_file) + fin = File.join(source_dir, source_file) + fout = output_path_for(output_file) + all || !File.exist?(fout) || File.mtime(fout) < File.mtime(fin) + end - def generate?(source_file, output_file) - fin = File.join(source_dir, source_file) - fout = output_path_for(output_file) - 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" - def generate_guide(guide, output_file) - output_path = output_path_for(output_file) - puts "Generating #{guide} as #{output_file}" - 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.extend(Helpers) - File.open(output_path, 'w') do |f| - view = ActionView::Base.new(source_dir, :edge => @edge, :version => @version, :mobi => "kindle/#{mobi}", :lang => @lang) - view.extend(Helpers) + if guide =~ /\.(\w+)\.erb$/ + # Generate the special pages like the home. + # 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) - if guide =~ /\.(\w+)\.erb$/ - # Generate the special pages like the home. - # 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 + end - warn_about_broken_links(result) if @warnings + f.write(result) end - - f.write(result) end - end - def warn_about_broken_links(html) - anchors = extract_anchors(html) - check_fragment_identifiers(html, anchors) - end + def warn_about_broken_links(html) + anchors = extract_anchors(html) + check_fragment_identifiers(html, anchors) + end - def extract_anchors(html) - # Markdown generates headers with IDs computed from titles. - anchors = Set.new - html.scan(/<h\d\s+id="([^"]+)/).flatten.each do |anchor| - if anchors.member?(anchor) - puts "*** DUPLICATE ID: #{anchor}, please make sure that there're no headings with the same name at the same level." - else - anchors << anchor + def extract_anchors(html) + # Markdown generates headers with IDs computed from titles. + anchors = Set.new + html.scan(/<h\d\s+id="([^"]+)/).flatten.each do |anchor| + if anchors.member?(anchor) + puts "*** DUPLICATE ID: #{anchor}, please make sure that there're no headings with the same name at the same level." + else + anchors << anchor + end end - end - # 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 - end + # 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 + end - def check_fragment_identifiers(html, anchors) - html.scan(/<a\s+href="#([^"]+)/).flatten.each do |fragment_identifier| - next if fragment_identifier == 'mainCol' # in layout, jumps to some DIV - unless anchors.member?(fragment_identifier) - guess = anchors.min { |a, b| - Levenshtein.distance(fragment_identifier, a) <=> Levenshtein.distance(fragment_identifier, b) - } - puts "*** BROKEN LINK: ##{fragment_identifier}, perhaps you meant ##{guess}." + def check_fragment_identifiers(html, anchors) + html.scan(/<a\s+href="#([^"]+)/).flatten.each do |fragment_identifier| + next if fragment_identifier == "mainCol" # in layout, jumps to some DIV + unless anchors.member?(fragment_identifier) + guess = anchors.min { |a, b| + Levenshtein.distance(fragment_identifier, a) <=> Levenshtein.distance(fragment_identifier, b) + } + puts "*** BROKEN LINK: ##{fragment_identifier}, perhaps you meant ##{guess}." + end end end - end end end diff --git a/guides/rails_guides/helpers.rb b/guides/rails_guides/helpers.rb index 5bf73da16c..888b51b1ef 100644 --- a/guides/rails_guides/helpers.rb +++ b/guides/rails_guides/helpers.rb @@ -1,13 +1,13 @@ -require 'yaml' +require "yaml" module RailsGuides module Helpers def guide(name, url, options = {}, &block) - link = content_tag(:a, :href => url) { name } + link = content_tag(:a, href: url) { name } result = content_tag(:dt, link) if options[:work_in_progress] - result << content_tag(:dd, 'Work in progress', :class => 'work-in-progress') + result << content_tag(:dd, "Work in progress", class: "work-in-progress") end result << content_tag(:dd, capture(&block)) @@ -19,30 +19,30 @@ module RailsGuides end def documents_flat - documents_by_section.flat_map {|section| section['documents']} + documents_by_section.flat_map { |section| section["documents"] } end def finished_documents(documents) - documents.reject { |document| document['work_in_progress'] } + documents.reject { |document| document["work_in_progress"] } end def docs_for_menu(position=nil) if position.nil? documents_by_section - elsif position == 'L' + elsif position == "L" documents_by_section.to(3) else documents_by_section.from(4) end end - def author(name, nick, image = 'credits_pic_blank.gif', &block) + def author(name, nick, image = "credits_pic_blank.gif", &block) image = "images/#{image}" - result = tag(:img, :src => image, :class => 'left pic', :alt => name, :width => 91, :height => 91) + result = tag(:img, src: image, class: "left pic", alt: name, width: 91, height: 91) result << content_tag(:h3, name) result << content_tag(:p, capture(&block)) - content_tag(:div, result, :class => 'clearfix', :id => nick) + content_tag(:div, result, class: "clearfix", id: nick) end def code(&block) diff --git a/guides/rails_guides/indexer.rb b/guides/rails_guides/indexer.rb index 89fbccbb1d..56df6a5842 100644 --- a/guides/rails_guides/indexer.rb +++ b/guides/rails_guides/indexer.rb @@ -1,5 +1,5 @@ -require 'active_support/core_ext/object/blank' -require 'active_support/core_ext/string/inflections' +require "active_support/core_ext/object/blank" +require "active_support/core_ext/string/inflections" module RailsGuides class Indexer @@ -17,52 +17,52 @@ module RailsGuides private - def process(string, current_level=3, counters=[1]) - s = StringScanner.new(string) + def process(string, current_level=3, counters=[1]) + s = StringScanner.new(string) - level_hash = {} + level_hash = {} - while !s.eos? - re = %r{^h(\d)(?:\((#.*?)\))?\s*\.\s*(.*)$} - s.match?(re) - if matched = s.matched - matched =~ re - level, idx, title = $1.to_i, $2, $3.strip + while !s.eos? + re = %r{^h(\d)(?:\((#.*?)\))?\s*\.\s*(.*)$} + s.match?(re) + if matched = s.matched + matched =~ re + level, idx, title = $1.to_i, $2, $3.strip - if level < current_level - # This is needed. Go figure. - return level_hash - elsif level == current_level - index = counters.join(".") - idx ||= '#' + title_to_idx(title) + if level < current_level + # This is needed. Go figure. + return level_hash + elsif level == current_level + index = counters.join(".") + idx ||= "#" + title_to_idx(title) - raise "Parsing Fail" unless @result.sub!(matched, "h#{level}(#{idx}). #{index} #{title}") + raise "Parsing Fail" unless @result.sub!(matched, "h#{level}(#{idx}). #{index} #{title}") - key = { - :title => title, - :id => idx - } - # Recurse - counters << 1 - level_hash[key] = process(s.post_match, current_level + 1, counters) - counters.pop + key = { + title: title, + id: idx + } + # Recurse + counters << 1 + level_hash[key] = process(s.post_match, current_level + 1, counters) + counters.pop - # Increment the current level - last = counters.pop - counters << last + 1 + # Increment the current level + last = counters.pop + counters << last + 1 + end end + s.getch end - s.getch + level_hash end - level_hash - end - def title_to_idx(title) - idx = title.strip.parameterize.sub(/^\d+/, '') - if warnings && idx.blank? - puts "BLANK ID: please put an explicit ID for section #{title}, as in h5(#my-id)" + def title_to_idx(title) + idx = title.strip.parameterize.sub(/^\d+/, "") + if warnings && idx.blank? + puts "BLANK ID: please put an explicit ID for section #{title}, as in h5(#my-id)" + end + idx end - idx - end end end diff --git a/guides/rails_guides/kindle.rb b/guides/rails_guides/kindle.rb index 081afcb09f..6fb8183cb1 100644 --- a/guides/rails_guides/kindle.rb +++ b/guides/rails_guides/kindle.rb @@ -1,26 +1,26 @@ #!/usr/bin/env ruby -unless `which kindlerb` +unless `which kindlerb` abort "Please gem install kindlerb" end -require 'nokogiri' -require 'fileutils' -require 'yaml' -require 'date' +require "nokogiri" +require "fileutils" +require "yaml" +require "date" module Kindle extend self def generate(output_dir, mobi_outfile, logfile) output_dir = File.absolute_path(output_dir) - Dir.chdir output_dir do + Dir.chdir output_dir do puts "=> Using output dir: #{output_dir}" puts "=> Arranging html pages in document order" toc = File.read("toc.ncx") - doc = Nokogiri::XML(toc).xpath("//ncx:content", 'ncx' => "http://www.daisy.org/z3986/2005/ncx/") - html_pages = doc.select {|c| c[:src]}.map {|c| c[:src]}.uniq - + doc = Nokogiri::XML(toc).xpath("//ncx:content", "ncx" => "http://www.daisy.org/z3986/2005/ncx/") + html_pages = doc.select { |c| c[:src] }.map { |c| c[:src] }.uniq + generate_front_matter(html_pages) generate_sections(html_pages) @@ -37,26 +37,26 @@ module Kindle def generate_front_matter(html_pages) frontmatter = [] - html_pages.delete_if {|x| + html_pages.delete_if { |x| if x =~ /(toc|welcome|credits|copyright).html/ frontmatter << x unless x =~ /toc/ true end } - html = frontmatter.map {|x| + html = frontmatter.map { |x| Nokogiri::HTML(File.open(x)).at("body").inner_html }.join("\n") fdoc = Nokogiri::HTML(html) fdoc.search("h3").each do |h3| - h3.name = 'h4' + h3.name = "h4" end - fdoc.search("h2").each do |h2| - h2.name = 'h3' - h2['id'] = h2.inner_text.gsub(/\s/, '-') + fdoc.search("h2").each do |h2| + h2.name = "h3" + h2["id"] = h2.inner_text.gsub(/\s/, "-") end add_head_section fdoc, "Front Matter" - File.open("frontmatter.html",'w') {|f| f.puts fdoc.to_html} + File.open("frontmatter.html","w") { |f| f.puts fdoc.to_html } html_pages.unshift "frontmatter.html" end @@ -65,20 +65,20 @@ module Kindle html_pages.each_with_index do |page, section_idx| FileUtils::mkdir_p("sections/%03d" % section_idx) doc = Nokogiri::HTML(File.open(page)) - title = doc.at("title").inner_text.gsub("Ruby on Rails Guides: ", '') - title = page.capitalize.gsub('.html', '') if title.strip == '' - File.open("sections/%03d/_section.txt" % section_idx, 'w') {|f| f.puts title} + title = doc.at("title").inner_text.gsub("Ruby on Rails Guides: ", "") + title = page.capitalize.gsub(".html", "") if title.strip == "" + File.open("sections/%03d/_section.txt" % section_idx, "w") { |f| f.puts title } doc.xpath("//h3[@id]").each_with_index do |h3,item_idx| subsection = h3.inner_text - content = h3.xpath("./following-sibling::*").take_while {|x| x.name != "h3"}.map(&:to_html) + content = h3.xpath("./following-sibling::*").take_while { |x| x.name != "h3" }.map(&:to_html) item = Nokogiri::HTML(h3.to_html + content.join("\n")) - item_path = "sections/%03d/%03d.html" % [section_idx, item_idx] + item_path = "sections/%03d/%03d.html" % [section_idx, item_idx] add_head_section(item, subsection) item.search("img").each do |img| - img['src'] = "#{Dir.pwd}/#{img['src']}" + img["src"] = "#{Dir.pwd}/#{img['src']}" end - item.xpath("//li/p").each {|p| p.swap(p.children); p.remove} - File.open(item_path, 'w') {|f| f.puts item.to_html} + item.xpath("//li/p").each { |p| p.swap(p.children); p.remove } + File.open(item_path, "w") { |f| f.puts item.to_html } end end end @@ -87,21 +87,21 @@ module Kindle puts "=> Generating _document.yml" x = Nokogiri::XML(File.open("rails_guides.opf")).remove_namespaces! cover_jpg = "#{Dir.pwd}/images/rails_guides_kindle_cover.jpg" - cover_gif = cover_jpg.sub(/jpg$/, 'gif') + cover_gif = cover_jpg.sub(/jpg$/, "gif") puts `convert #{cover_jpg} #{cover_gif}` document = { - 'doc_uuid' => x.at("package")['unique-identifier'], - 'title' => x.at("title").inner_text.gsub(/\(.*$/, " v2"), - 'publisher' => x.at("publisher").inner_text, - 'author' => x.at("creator").inner_text, - 'subject' => x.at("subject").inner_text, - 'date' => x.at("date").inner_text, - 'cover' => cover_gif, - 'masthead' => nil, - 'mobi_outfile' => mobi_outfile + "doc_uuid" => x.at("package")["unique-identifier"], + "title" => x.at("title").inner_text.gsub(/\(.*$/, " v2"), + "publisher" => x.at("publisher").inner_text, + "author" => x.at("creator").inner_text, + "subject" => x.at("subject").inner_text, + "date" => x.at("date").inner_text, + "cover" => cover_gif, + "masthead" => nil, + "mobi_outfile" => mobi_outfile } puts document.to_yaml - File.open("_document.yml", 'w'){|f| f.puts document.to_yaml} + File.open("_document.yml", "w") { |f| f.puts document.to_yaml } end def add_head_section(doc, title) @@ -110,9 +110,9 @@ module Kindle title_node.content = title title_node.parent = head css = Nokogiri::XML::Node.new "link", doc - css['rel'] = 'stylesheet' - css['type'] = 'text/css' - css['href'] = "#{Dir.pwd}/stylesheets/kindle.css" + css["rel"] = "stylesheet" + css["type"] = "text/css" + css["href"] = "#{Dir.pwd}/stylesheets/kindle.css" css.parent = head doc.at("body").before head end diff --git a/guides/rails_guides/levenshtein.rb b/guides/rails_guides/levenshtein.rb index 049f633258..e947150364 100644 --- a/guides/rails_guides/levenshtein.rb +++ b/guides/rails_guides/levenshtein.rb @@ -1,8 +1,10 @@ module RailsGuides module Levenshtein - # This code is based directly on the Text gem implementation + # 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 self.distance str1, str2 + def self.distance(str1, str2) s = str1 t = str2 n = s.length diff --git a/guides/rails_guides/markdown.rb b/guides/rails_guides/markdown.rb index 69c7cd5136..009e5aff99 100644 --- a/guides/rails_guides/markdown.rb +++ b/guides/rails_guides/markdown.rb @@ -1,6 +1,6 @@ -require 'redcarpet' -require 'nokogiri' -require 'rails_guides/markdown/renderer' +require "redcarpet" +require "nokogiri" +require "rails_guides/markdown/renderer" module RailsGuides class Markdown @@ -8,7 +8,7 @@ module RailsGuides @view = view @layout = layout @index_counter = Hash.new(0) - @raw_header = '' + @raw_header = "" @node_ids = {} end @@ -47,21 +47,20 @@ module RailsGuides def dom_id_text(text) escaped_chars = Regexp.escape('\\/`*_{}[]()#+-.!:,;|&<>^~=\'"') - text.downcase.gsub(/\?/, '-questionmark') - .gsub(/!/, '-bang') - .gsub(/[#{escaped_chars}]+/, ' ').strip - .gsub(/\s+/, '-') + text.downcase.gsub(/\?/, "-questionmark") + .gsub(/!/, "-bang") + .gsub(/[#{escaped_chars}]+/, " ").strip + .gsub(/\s+/, "-") end def engine - @engine ||= Redcarpet::Markdown.new(Renderer, { + @engine ||= Redcarpet::Markdown.new(Renderer, no_intra_emphasis: true, fenced_code_blocks: true, autolink: true, strikethrough: true, superscript: true, - tables: true - }) + tables: true) end def extract_raw_header_and_body @@ -87,15 +86,15 @@ module RailsGuides doc.children.each do |node| if node.name =~ /^h[3-6]$/ case node.name - when 'h3' + when "h3" hierarchy = [node] @headings_for_index << [1, node, node.inner_html] - when 'h4' + when "h4" hierarchy = hierarchy[0, 1] + [node] @headings_for_index << [2, node, node.inner_html] - when 'h5' + when "h5" hierarchy = hierarchy[0, 2] + [node] - when 'h6' + when "h6" hierarchy = hierarchy[0, 3] + [node] end @@ -109,7 +108,7 @@ module RailsGuides def generate_index if @headings_for_index.present? - raw_index = '' + raw_index = "" @headings_for_index.each do |level, node, label| if level == 1 raw_index += "1. [#{label}](##{node[:id]})\n" @@ -119,7 +118,7 @@ module RailsGuides end @index = Nokogiri::HTML.fragment(engine.render(raw_index)).tap do |doc| - doc.at('ol')[:class] = 'chapters' + doc.at("ol")[:class] = "chapters" end.to_html @index = <<-INDEX.html_safe @@ -159,7 +158,7 @@ module RailsGuides @view.content_for(:header_section) { @header } @view.content_for(:page_title) { @title } @view.content_for(:index_section) { @index } - @view.render(:layout => @layout, :text => @body) + @view.render(layout: @layout, html: @body.html_safe) end end end diff --git a/guides/rails_guides/markdown/renderer.rb b/guides/rails_guides/markdown/renderer.rb index 73ca600361..eebd9ac730 100644 --- a/guides/rails_guides/markdown/renderer.rb +++ b/guides/rails_guides/markdown/renderer.rb @@ -25,7 +25,7 @@ HTML def paragraph(text) if text =~ /^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:]/ convert_notes(text) - elsif text.include?('DO NOT READ THIS FILE ON GITHUB') + elsif text.include?("DO NOT READ THIS FILE ON GITHUB") elsif text =~ /^\[<sup>(\d+)\]:<\/sup> (.+)$/ linkback = %(<a href="#footnote-#{$1}-ref"><sup>#{$1}</sup></a>) %(<p class="footnote" id="footnote-#{$1}">#{linkback} #{$2}</p>) @@ -46,14 +46,14 @@ HTML def brush_for(code_type) case code_type - when 'ruby', 'sql', 'plain' - code_type - when 'erb', 'html+erb' - 'ruby; html-script: true' - when 'html' - 'xml' # HTML is understood, but there are .xml rules in the CSS - else - 'plain' + when "ruby", "sql", "plain" + code_type + when "erb", "html+erb" + "ruby; html-script: true" + when "html" + "xml" # HTML is understood, but there are .xml rules in the CSS + else + "plain" end end @@ -67,14 +67,15 @@ HTML # as a list item, but as a paragraph starting with a plain # asterisk. body.gsub(/^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:](.*?)(\n(?=\n)|\Z)/m) do - css_class = case $1 - when 'CAUTION', 'IMPORTANT' - 'warning' - when 'TIP' - 'info' - else - $1.downcase - end + css_class = \ + case $1 + when "CAUTION", "IMPORTANT" + "warning" + when "TIP" + "info" + else + $1.downcase + end %(<div class="#{css_class}"><p>#{$2.strip}</p></div>) end end diff --git a/guides/source/2_2_release_notes.md b/guides/source/2_2_release_notes.md index be00087f63..ac5833e069 100644 --- a/guides/source/2_2_release_notes.md +++ b/guides/source/2_2_release_notes.md @@ -3,7 +3,7 @@ Ruby on Rails 2.2 Release Notes =============================== -Rails 2.2 delivers a number of new and improved features. This list covers the major upgrades, but doesn't include every little bug fix and change. If you want to see everything, check out the [list of commits](http://github.com/rails/rails/commits/2-2-stable) in the main Rails repository on GitHub. +Rails 2.2 delivers a number of new and improved features. This list covers the major upgrades, but doesn't include every little bug fix and change. If you want to see everything, check out the [list of commits](https://github.com/rails/rails/commits/2-2-stable) in the main Rails repository on GitHub. Along with Rails, 2.2 marks the launch of the [Ruby on Rails Guides](http://guides.rubyonrails.org/), the first results of the ongoing [Rails Guides hackfest](http://hackfest.rubyonrails.org/guide). This site will deliver high-quality documentation of the major features of Rails. @@ -21,8 +21,8 @@ Rails 2.2 supplies an easy system for internationalization (or i18n, for those o * Lead Contributors: Rails i18 Team * More information : * [Official Rails i18 website](http://rails-i18n.org) - * [Finally. Ruby on Rails gets internationalized](http://www.artweb-design.de/2008/7/18/finally-ruby-on-rails-gets-internationalized) - * [Localizing Rails : Demo application](http://github.com/clemens/i18n_demo_app) + * [Finally. Ruby on Rails gets internationalized](https://web.archive.org/web/20140407075019/http://www.artweb-design.de/2008/7/18/finally-ruby-on-rails-gets-internationalized) + * [Localizing Rails : Demo application](https://github.com/clemens/i18n_demo_app) ### Compatibility with Ruby 1.9 and JRuby @@ -34,7 +34,7 @@ Documentation The internal documentation of Rails, in the form of code comments, has been improved in numerous places. In addition, the [Ruby on Rails Guides](http://guides.rubyonrails.org/) project is the definitive source for information on major Rails components. In its first official release, the Guides page includes: * [Getting Started with Rails](getting_started.html) -* [Rails Database Migrations](migrations.html) +* [Rails Database Migrations](active_record_migrations.html) * [Active Record Associations](association_basics.html) * [Active Record Query Interface](active_record_querying.html) * [Layouts and Rendering in Rails](layouts_and_rendering.html) @@ -45,7 +45,6 @@ The internal documentation of Rails, in the form of code comments, has been impr * [A Guide to Testing Rails Applications](testing.html) * [Securing Rails Applications](security.html) * [Debugging Rails Applications](debugging_rails_applications.html) -* [Performance Testing Rails Applications](performance_testing.html) * [The Basics of Creating Rails Plugins](plugins.html) All told, the Guides provide tens of thousands of words of guidance for beginning and intermediate Rails developers. @@ -59,7 +58,7 @@ rake doc:guides This will put the guides inside `Rails.root/doc/guides` and you may start surfing straight away by opening `Rails.root/doc/guides/index.html` in your favourite browser. * Lead Contributors: [Rails Documentation Team](credits.html) -* Major contributions from [Xavier Noria":http://advogato.org/person/fxn/diary.html and "Hongli Lai](http://izumi.plan99.net/blog/.) +* Major contributions from [Xavier Noria](http://advogato.org/person/fxn/diary.html) and [Hongli Lai](http://izumi.plan99.net/blog/). * More information: * [Rails Guides hackfest](http://hackfest.rubyonrails.org/guide) * [Help improve Rails documentation on Git branch](http://weblog.rubyonrails.org/2008/5/2/help-improve-rails-documentation-on-git-branch) @@ -146,7 +145,7 @@ development: * Lead Contributor: [Nick Sieger](http://blog.nicksieger.com/) * More information: - * [What's New in Edge Rails: Connection Pools](http://ryandaigle.com/articles/2008/9/7/what-s-new-in-edge-rails-connection-pools) + * [What's New in Edge Rails: Connection Pools](http://archives.ryandaigle.com/articles/2008/9/7/what-s-new-in-edge-rails-connection-pools) ### Hashes for Join Table Conditions @@ -166,7 +165,7 @@ Product.all(:joins => :photos, :conditions => { :photos => { :copyright => false ``` * More information: - * [What's New in Edge Rails: Easy Join Table Conditions](http://ryandaigle.com/articles/2008/7/7/what-s-new-in-edge-rails-easy-join-table-conditions) + * [What's New in Edge Rails: Easy Join Table Conditions](http://archives.ryandaigle.com/articles/2008/7/7/what-s-new-in-edge-rails-easy-join-table-conditions) ### New Dynamic Finders @@ -239,7 +238,7 @@ This will enable recognition of (among others) these routes: * Lead Contributor: [S. Brent Faulkner](http://www.unwwwired.net/) * More information: * [Rails Routing from the Outside In](routing.html#nested-resources) - * [What's New in Edge Rails: Shallow Routes](http://ryandaigle.com/articles/2008/9/7/what-s-new-in-edge-rails-shallow-routes) + * [What's New in Edge Rails: Shallow Routes](http://archives.ryandaigle.com/articles/2008/9/7/what-s-new-in-edge-rails-shallow-routes) ### Method Arrays for Member or Collection Routes @@ -287,7 +286,7 @@ Action Mailer Action Mailer now supports mailer layouts. You can make your HTML emails as pretty as your in-browser views by supplying an appropriately-named layout - for example, the `CustomerMailer` class expects to use `layouts/customer_mailer.html.erb`. * More information: - * [What's New in Edge Rails: Mailer Layouts](http://ryandaigle.com/articles/2008/9/7/what-s-new-in-edge-rails-mailer-layouts) + * [What's New in Edge Rails: Mailer Layouts](http://archives.ryandaigle.com/articles/2008/9/7/what-s-new-in-edge-rails-mailer-layouts) Action Mailer now offers built-in support for GMail's SMTP servers, by turning on STARTTLS automatically. This requires Ruby 1.8.7 to be installed. @@ -321,7 +320,7 @@ Other features of memoization include `unmemoize`, `unmemoize_all`, and `memoize * Lead Contributor: [Josh Peek](http://joshpeek.com/) * More information: - * [What's New in Edge Rails: Easy Memoization](http://ryandaigle.com/articles/2008/7/16/what-s-new-in-edge-rails-memoization) + * [What's New in Edge Rails: Easy Memoization](http://archives.ryandaigle.com/articles/2008/7/16/what-s-new-in-edge-rails-memoization) * [Memo-what? A Guide to Memoization](http://www.railway.at/articles/2008/09/20/a-guide-to-memoization) ### each_with_object @@ -389,9 +388,9 @@ To avoid deployment issues and make Rails applications more self-contained, it's You can unpack or install a single gem by specifying `GEM=_gem_name_` on the command line. -* Lead Contributor: [Matt Jones](http://github.com/al2o3cr) +* Lead Contributor: [Matt Jones](https://github.com/al2o3cr) * More information: - * [What's New in Edge Rails: Gem Dependencies](http://ryandaigle.com/articles/2008/4/1/what-s-new-in-edge-rails-gem-dependencies) + * [What's New in Edge Rails: Gem Dependencies](http://archives.ryandaigle.com/articles/2008/4/1/what-s-new-in-edge-rails-gem-dependencies) * [Rails 2.1.2 and 2.2RC1: Update Your RubyGems](http://afreshcup.com/2008/10/25/rails-212-and-22rc1-update-your-rubygems/) * [Detailed discussion on Lighthouse](http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/1128) @@ -411,7 +410,7 @@ Deprecated A few pieces of older code are deprecated in this release: * `Rails::SecretKeyGenerator` has been replaced by `ActiveSupport::SecureRandom` -* `render_component` is deprecated. There's a [render_components plugin](http://github.com/rails/render_component/tree/master) available if you need this functionality. +* `render_component` is deprecated. There's a [render_components plugin](https://github.com/rails/render_component/tree/master) available if you need this functionality. * Implicit local assignments when rendering partials has been deprecated. ```ruby diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md index 0a62f34371..6976848e95 100644 --- a/guides/source/2_3_release_notes.md +++ b/guides/source/2_3_release_notes.md @@ -3,7 +3,7 @@ Ruby on Rails 2.3 Release Notes =============================== -Rails 2.3 delivers a variety of new and improved features, including pervasive Rack integration, refreshed support for Rails Engines, nested transactions for Active Record, dynamic and default scopes, unified rendering, more efficient routing, application templates, and quiet backtraces. This list covers the major upgrades, but doesn't include every little bug fix and change. If you want to see everything, check out the [list of commits](http://github.com/rails/rails/commits/2-3-stable) in the main Rails repository on GitHub or review the `CHANGELOG` files for the individual Rails components. +Rails 2.3 delivers a variety of new and improved features, including pervasive Rack integration, refreshed support for Rails Engines, nested transactions for Active Record, dynamic and default scopes, unified rendering, more efficient routing, application templates, and quiet backtraces. This list covers the major upgrades, but doesn't include every little bug fix and change. If you want to see everything, check out the [list of commits](https://github.com/rails/rails/commits/2-3-stable) in the main Rails repository on GitHub or review the `CHANGELOG` files for the individual Rails components. -------------------------------------------------------------------------------- @@ -54,7 +54,7 @@ Documentation The [Ruby on Rails guides](http://guides.rubyonrails.org/) project has published several additional guides for Rails 2.3. In addition, a [separate site](http://edgeguides.rubyonrails.org/) maintains updated copies of the Guides for Edge Rails. Other documentation efforts include a relaunch of the [Rails wiki](http://newwiki.rubyonrails.org/) and early planning for a Rails Book. -* More Information: [Rails Documentation Projects](http://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects.) +* More Information: [Rails Documentation Projects](http://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects) Ruby 1.9.1 Support ------------------ @@ -125,14 +125,14 @@ Order.scoped_by_customer_id(12).scoped_by_status("open") There's nothing to define to use dynamic scopes: they just work. * Lead Contributor: [Yaroslav Markin](http://evilmartians.com/) -* More Information: [What's New in Edge Rails: Dynamic Scope Methods](http://ryandaigle.com/articles/2008/12/29/what-s-new-in-edge-rails-dynamic-scope-methods.) +* More Information: [What's New in Edge Rails: Dynamic Scope Methods](http://archives.ryandaigle.com/articles/2008/12/29/what-s-new-in-edge-rails-dynamic-scope-methods) ### Default Scopes Rails 2.3 will introduce the notion of _default scopes_ similar to named scopes, but applying to all named scopes or find methods within the model. For example, you can write `default_scope :order => 'name ASC'` and any time you retrieve records from that model they'll come out sorted by name (unless you override the option, of course). * Lead Contributor: PaweÅ‚ Kondzior -* More Information: [What's New in Edge Rails: Default Scoping](http://ryandaigle.com/articles/2008/11/18/what-s-new-in-edge-rails-default-scoping) +* More Information: [What's New in Edge Rails: Default Scoping](http://archives.ryandaigle.com/articles/2008/11/18/what-s-new-in-edge-rails-default-scoping) ### Batch Processing @@ -158,7 +158,7 @@ Note that you should only use this method for batch processing: for small number * More Information (at that point the convenience method was called just `each`): * [Rails 2.3: Batch Finding](http://afreshcup.com/2009/02/23/rails-23-batch-finding/) - * [What's New in Edge Rails: Batched Find](http://ryandaigle.com/articles/2009/2/23/what-s-new-in-edge-rails-batched-find) + * [What's New in Edge Rails: Batched Find](http://archives.ryandaigle.com/articles/2009/2/23/what-s-new-in-edge-rails-batched-find) ### Multiple Conditions for Callbacks @@ -179,7 +179,7 @@ developers = Developer.find(:all, :group => "salary", :having => "sum(salary) > 10000", :select => "salary") ``` -* Lead Contributor: [Emilio Tagua](http://github.com/miloops) +* Lead Contributor: [Emilio Tagua](https://github.com/miloops) ### Reconnecting MySQL Connections @@ -235,7 +235,7 @@ If you're one of the people who has always been bothered by the special-case nam * More Information: * [The Death of Application.rb](http://afreshcup.com/2008/11/17/rails-2x-the-death-of-applicationrb/) - * [What's New in Edge Rails: Application.rb Duality is no More](http://ryandaigle.com/articles/2008/11/19/what-s-new-in-edge-rails-application-rb-duality-is-no-more) + * [What's New in Edge Rails: Application.rb Duality is no More](http://archives.ryandaigle.com/articles/2008/11/19/what-s-new-in-edge-rails-application-rb-duality-is-no-more) ### HTTP Digest Authentication Support @@ -261,7 +261,7 @@ end ``` * Lead Contributor: [Gregg Kellogg](http://www.kellogg-assoc.com/) -* More Information: [What's New in Edge Rails: HTTP Digest Authentication](http://ryandaigle.com/articles/2009/1/30/what-s-new-in-edge-rails-http-digest-authentication) +* More Information: [What's New in Edge Rails: HTTP Digest Authentication](http://archives.ryandaigle.com/articles/2009/1/30/what-s-new-in-edge-rails-http-digest-authentication) ### More Efficient Routing @@ -377,8 +377,8 @@ You can write this view in Rails 2.3: * Lead Contributor: [Eloy Duran](http://superalloy.nl/) * More Information: * [Nested Model Forms](http://weblog.rubyonrails.org/2009/1/26/nested-model-forms) - * [complex-form-examples](http://github.com/alloy/complex-form-examples) - * [What's New in Edge Rails: Nested Object Forms](http://ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes) + * [complex-form-examples](https://github.com/alloy/complex-form-examples) + * [What's New in Edge Rails: Nested Object Forms](http://archives.ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes) ### Smart Rendering of Partials @@ -394,7 +394,7 @@ render @article render @articles ``` -* More Information: [What's New in Edge Rails: render Stops Being High-Maintenance](http://ryandaigle.com/articles/2008/11/20/what-s-new-in-edge-rails-render-stops-being-high-maintenance) +* More Information: [What's New in Edge Rails: render Stops Being High-Maintenance](http://archives.ryandaigle.com/articles/2008/11/20/what-s-new-in-edge-rails-render-stops-being-high-maintenance) ### Prompts for Date Select Helpers @@ -421,7 +421,7 @@ You're likely familiar with Rails' practice of adding timestamps to static asset Asset hosts get more flexible in edge Rails with the ability to declare an asset host as a specific object that responds to a call. This allows you to implement any complex logic you need in your asset hosting. -* More Information: [asset-hosting-with-minimum-ssl](http://github.com/dhh/asset-hosting-with-minimum-ssl/tree/master) +* More Information: [asset-hosting-with-minimum-ssl](https://github.com/dhh/asset-hosting-with-minimum-ssl/tree/master) ### grouped_options_for_select Helper Method @@ -498,7 +498,7 @@ Active Support has a few interesting changes, including the introduction of `Obj A lot of folks have adopted the notion of using try() to attempt operations on objects. It's especially helpful in views where you can avoid nil-checking by writing code like `<%= @person.try(:name) %>`. Well, now it's baked right into Rails. As implemented in Rails, it raises `NoMethodError` on private methods and always returns `nil` if the object is nil. -* More Information: [try()](http://ozmm.org/posts/try.html.) +* More Information: [try()](http://ozmm.org/posts/try.html) ### Object#tap Backport @@ -533,7 +533,7 @@ If you look up the spec on the "json.org" site, you'll discover that all keys in ### Other Active Support Changes * You can use `Enumerable#none?` to check that none of the elements match the supplied block. -* If you're using Active Support [delegates](http://afreshcup.com/2008/10/19/coming-in-rails-22-delegate-prefixes/,) the new `:allow_nil` option lets you return `nil` instead of raising an exception when the target object is nil. +* If you're using Active Support [delegates](http://afreshcup.com/2008/10/19/coming-in-rails-22-delegate-prefixes/) the new `:allow_nil` option lets you return `nil` instead of raising an exception when the target object is nil. * `ActiveSupport::OrderedHash`: now implements `each_key` and `each_value`. * `ActiveSupport::MessageEncryptor` provides a simple way to encrypt information for storage in an untrusted location (like cookies). * Active Support's `from_xml` no longer depends on XmlSimple. Instead, Rails now includes its own XmlMini implementation, with just the functionality that it requires. This lets Rails dispense with the bundled copy of XmlSimple that it's been carting around. @@ -555,11 +555,11 @@ Rails Metal is a new mechanism that provides superfast endpoints inside of your * [Introducing Rails Metal](http://weblog.rubyonrails.org/2008/12/17/introducing-rails-metal) * [Rails Metal: a micro-framework with the power of Rails](http://soylentfoo.jnewland.com/articles/2008/12/16/rails-metal-a-micro-framework-with-the-power-of-rails-m) * [Metal: Super-fast Endpoints within your Rails Apps](http://www.railsinside.com/deployment/180-metal-super-fast-endpoints-within-your-rails-apps.html) - * [What's New in Edge Rails: Rails Metal](http://ryandaigle.com/articles/2008/12/18/what-s-new-in-edge-rails-rails-metal) + * [What's New in Edge Rails: Rails Metal](http://archives.ryandaigle.com/articles/2008/12/18/what-s-new-in-edge-rails-rails-metal) ### Application Templates -Rails 2.3 incorporates Jeremy McAnally's [rg](http://github.com/jeremymcanally/rg/tree/master) application generator. What this means is that we now have template-based application generation built right into Rails; if you have a set of plugins you include in every application (among many other use cases), you can just set up a template once and use it over and over again when you run the `rails` command. There's also a rake task to apply a template to an existing application: +Rails 2.3 incorporates Jeremy McAnally's [rg](https://github.com/jm/rg) application generator. What this means is that we now have template-based application generation built right into Rails; if you have a set of plugins you include in every application (among many other use cases), you can just set up a template once and use it over and over again when you run the `rails` command. There's also a rake task to apply a template to an existing application: ``` rake rails:template LOCATION=~/template.rb @@ -572,7 +572,7 @@ This will layer the changes from the template on top of whatever code the projec ### Quieter Backtraces -Building on Thoughtbot's [Quiet Backtrace](https://github.com/thoughtbot/quietbacktrace) plugin, which allows you to selectively remove lines from `Test::Unit` backtraces, Rails 2.3 implements `ActiveSupport::BacktraceCleaner` and `Rails::BacktraceCleaner` in core. This supports both filters (to perform regex-based substitutions on backtrace lines) and silencers (to remove backtrace lines entirely). Rails automatically adds silencers to get rid of the most common noise in a new application, and builds a `config/backtrace_silencers.rb` file to hold your own additions. This feature also enables prettier printing from any gem in the backtrace. +Building on thoughtbot's [Quiet Backtrace](https://github.com/thoughtbot/quietbacktrace) plugin, which allows you to selectively remove lines from `Test::Unit` backtraces, Rails 2.3 implements `ActiveSupport::BacktraceCleaner` and `Rails::BacktraceCleaner` in core. This supports both filters (to perform regex-based substitutions on backtrace lines) and silencers (to remove backtrace lines entirely). Rails automatically adds silencers to get rid of the most common noise in a new application, and builds a `config/backtrace_silencers.rb` file to hold your own additions. This feature also enables prettier printing from any gem in the backtrace. ### Faster Boot Time in Development Mode with Lazy Loading/Autoload @@ -605,8 +605,8 @@ Deprecated A few pieces of older code are deprecated in this release: -* If you're one of the (fairly rare) Rails developers who deploys in a fashion that depends on the inspector, reaper, and spawner scripts, you'll need to know that those scripts are no longer included in core Rails. If you need them, you'll be able to pick up copies via the [irs_process_scripts](http://github.com/rails/irs_process_scripts/tree) plugin. -* `render_component` goes from "deprecated" to "nonexistent" in Rails 2.3. If you still need it, you can install the [render_component plugin](http://github.com/rails/render_component/tree/master). +* If you're one of the (fairly rare) Rails developers who deploys in a fashion that depends on the inspector, reaper, and spawner scripts, you'll need to know that those scripts are no longer included in core Rails. If you need them, you'll be able to pick up copies via the [irs_process_scripts](https://github.com/rails/irs_process_scripts/tree) plugin. +* `render_component` goes from "deprecated" to "nonexistent" in Rails 2.3. If you still need it, you can install the [render_component plugin](https://github.com/rails/render_component/tree/master). * Support for Rails components has been removed. * If you were one of the people who got used to running `script/performance/request` to look at performance based on integration tests, you need to learn a new trick: that script has been removed from core Rails now. There's a new request_profiler plugin that you can install to get the exact same functionality back. * `ActionController::Base#session_enabled?` is deprecated because sessions are lazy-loaded now. @@ -614,7 +614,7 @@ A few pieces of older code are deprecated in this release: * Some integration test helpers have been removed. `response.headers["Status"]` and `headers["Status"]` will no longer return anything. Rack does not allow "Status" in its return headers. However you can still use the `status` and `status_message` helpers. `response.headers["cookie"]` and `headers["cookie"]` will no longer return any CGI cookies. You can inspect `headers["Set-Cookie"]` to see the raw cookie header or use the `cookies` helper to get a hash of the cookies sent to the client. * `formatted_polymorphic_url` is deprecated. Use `polymorphic_url` with `:format` instead. * The `:http_only` option in `ActionController::Response#set_cookie` has been renamed to `:httponly`. -* The `:connector` and `:skip_last_comma` options of `to_sentence` have been replaced by `:words_connnector`, `:two_words_connector`, and `:last_word_connector` options. +* The `:connector` and `:skip_last_comma` options of `to_sentence` have been replaced by `:words_connector`, `:two_words_connector`, and `:last_word_connector` options. * Posting a multipart form with an empty `file_field` control used to submit an empty string to the controller. Now it submits a nil, due to differences between Rack's multipart parser and the old Rails one. Credits diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md index 696493a3cf..517b38be07 100644 --- a/guides/source/3_0_release_notes.md +++ b/guides/source/3_0_release_notes.md @@ -17,7 +17,7 @@ Even if you don't give a hoot about any of our internal cleanups, Rails 3.0 is g On top of all that, we've tried our best to deprecate the old APIs with nice warnings. That means that you can move your existing application to Rails 3 without immediately rewriting all your old code to the latest best practices. -These release notes cover the major upgrades, but don't include every little bug fix and change. Rails 3.0 consists of almost 4,000 commits by more than 250 authors! If you want to see everything, check out the [list of commits](http://github.com/rails/rails/commits/3-0-stable) in the main Rails repository on GitHub. +These release notes cover the major upgrades, but don't include every little bug fix and change. Rails 3.0 consists of almost 4,000 commits by more than 250 authors! If you want to see everything, check out the [list of commits](https://github.com/rails/rails/commits/3-0-stable) in the main Rails repository on GitHub. -------------------------------------------------------------------------------- @@ -63,7 +63,7 @@ The `config.gem` method is gone and has been replaced by using `bundler` and a ` ### Upgrade Process -To help with the upgrade process, a plugin named [Rails Upgrade](http://github.com/rails/rails_upgrade) has been created to automate part of it. +To help with the upgrade process, a plugin named [Rails Upgrade](https://github.com/rails/rails_upgrade) has been created to automate part of it. Simply install the plugin, then run `rake rails:upgrade:check` to check your app for pieces that need to be updated (with links to information on how to update them). It also offers a task to generate a `Gemfile` based on your current `config.gem` calls and a task to generate a new routes file from your current one. To get the plugin, simply run the following: @@ -86,7 +86,7 @@ $ cd myapp ### Vendoring Gems -Rails now uses a `Gemfile` in the application root to determine the gems you require for your application to start. This `Gemfile` is processed by the [Bundler](http://github.com/carlhuda/bundler,) which then installs all your dependencies. It can even install all the dependencies locally to your application so that it doesn't depend on the system gems. +Rails now uses a `Gemfile` in the application root to determine the gems you require for your application to start. This `Gemfile` is processed by the [Bundler](https://github.com/bundler/bundler) which then installs all your dependencies. It can even install all the dependencies locally to your application so that it doesn't depend on the system gems. More information: - [bundler homepage](http://bundler.io/) @@ -138,14 +138,14 @@ More Information: - [Rails Edge Architecture](http://yehudakatz.com/2009/06/11/r ### Arel Integration -[Arel](http://github.com/brynary/arel) (or Active Relation) has been taken on as the underpinnings of Active Record and is now required for Rails. Arel provides an SQL abstraction that simplifies out Active Record and provides the underpinnings for the relation functionality in Active Record. +[Arel](https://github.com/brynary/arel) (or Active Relation) has been taken on as the underpinnings of Active Record and is now required for Rails. Arel provides an SQL abstraction that simplifies out Active Record and provides the underpinnings for the relation functionality in Active Record. More information: - [Why I wrote Arel](https://web.archive.org/web/20120718093140/http://magicscalingsprinkles.wordpress.com/2010/01/28/why-i-wrote-arel/) ### Mail Extraction -Action Mailer ever since its beginnings has had monkey patches, pre parsers and even delivery and receiver agents, all in addition to having TMail vendored in the source tree. Version 3 changes that with all email message related functionality abstracted out to the [Mail](http://github.com/mikel/mail) gem. This again reduces code duplication and helps create definable boundaries between Action Mailer and the email parser. +Action Mailer ever since its beginnings has had monkey patches, pre parsers and even delivery and receiver agents, all in addition to having TMail vendored in the source tree. Version 3 changes that with all email message related functionality abstracted out to the [Mail](https://github.com/mikel/mail) gem. This again reduces code duplication and helps create definable boundaries between Action Mailer and the email parser. More information: - [New Action Mailer API in Rails 3](http://lindsaar.net/2010/1/26/new-actionmailer-api-in-rails-3) @@ -155,13 +155,13 @@ Documentation The documentation in the Rails tree is being updated with all the API changes, additionally, the [Rails Edge Guides](http://edgeguides.rubyonrails.org/) are being updated one by one to reflect the changes in Rails 3.0. The guides at [guides.rubyonrails.org](http://guides.rubyonrails.org/) however will continue to contain only the stable version of Rails (at this point, version 2.3.5, until 3.0 is released). -More Information: - [Rails Documentation Projects](http://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects.) +More Information: - [Rails Documentation Projects](http://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects) Internationalization -------------------- -A large amount of work has been done with I18n support in Rails 3, including the latest [I18n](http://github.com/svenfuchs/i18n) gem supplying many speed improvements. +A large amount of work has been done with I18n support in Rails 3, including the latest [I18n](https://github.com/svenfuchs/i18n) gem supplying many speed improvements. * I18n for any object - I18n behavior can be added to any object by including `ActiveModel::Translation` and `ActiveModel::Validations`. There is also an `errors.messages` fallback for translations. * Attributes can have default translations. @@ -213,7 +213,6 @@ Railties now deprecates: More information: * [Discovering Rails 3 generators](http://blog.plataformatec.com.br/2010/01/discovering-rails-3-generators) -* [Making Generators for Rails 3 with Thor](http://caffeinedd.com/guides/331-making-generators-for-rails-3-with-thor) * [The Rails Module (in Rails 3)](http://litanyagainstfear.com/blog/2010/02/03/the-rails-module/) Action Pack @@ -250,7 +249,7 @@ Deprecations: More Information: -* [Render Options in Rails 3](http://www.engineyard.com/blog/2010/render-options-in-rails-3/) +* [Render Options in Rails 3](https://blog.engineyard.com/2010/render-options-in-rails-3) * [Three reasons to love ActionController::Responder](http://weblog.rubyonrails.org/2009/8/31/three-reasons-love-responder) @@ -310,7 +309,7 @@ More Information: Major re-write was done in the Action View helpers, implementing Unobtrusive JavaScript (UJS) hooks and removing the old inline AJAX commands. This enables Rails to use any compliant UJS driver to implement the UJS hooks in the helpers. -What this means is that all previous `remote_<method>` helpers have been removed from Rails core and put into the [Prototype Legacy Helper](http://github.com/rails/prototype_legacy_helper). To get UJS hooks into your HTML, you now pass `:remote => true` instead. For example: +What this means is that all previous `remote_<method>` helpers have been removed from Rails core and put into the [Prototype Legacy Helper](https://github.com/rails/prototype_legacy_helper). To get UJS hooks into your HTML, you now pass `:remote => true` instead. For example: ```ruby form_for @post, :remote => true @@ -576,7 +575,7 @@ The following methods have been removed because they are no longer used in the f Action Mailer ------------- -Action Mailer has been given a new API with TMail being replaced out with the new [Mail](http://github.com/mikel/mail) as the email library. Action Mailer itself has been given an almost complete re-write with pretty much every line of code touched. The result is that Action Mailer now simply inherits from Abstract Controller and wraps the Mail gem in a Rails DSL. This reduces the amount of code and duplication of other libraries in Action Mailer considerably. +Action Mailer has been given a new API with TMail being replaced out with the new [Mail](https://github.com/mikel/mail) as the email library. Action Mailer itself has been given an almost complete re-write with pretty much every line of code touched. The result is that Action Mailer now simply inherits from Abstract Controller and wraps the Mail gem in a Rails DSL. This reduces the amount of code and duplication of other libraries in Action Mailer considerably. * All mailers are now in `app/mailers` by default. * Can now send email using new API with three methods: `attachments`, `headers` and `mail`. diff --git a/guides/source/3_1_release_notes.md b/guides/source/3_1_release_notes.md index 327495704a..fd90cf9886 100644 --- a/guides/source/3_1_release_notes.md +++ b/guides/source/3_1_release_notes.md @@ -99,7 +99,7 @@ gem 'jquery-rails' # config.assets.manifest = YOUR_PATH # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) - # config.assets.precompile `= %w( search.js ) + # config.assets.precompile `= %w( admin.js admin.css ) # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. @@ -558,4 +558,4 @@ Credits See the [full list of contributors to Rails](http://contributors.rubyonrails.org/) for the many people who spent many hours making Rails, the stable and robust framework it is. Kudos to all of them. -Rails 3.1 Release Notes were compiled by [Vijay Dev](https://github.com/vijaydev.) +Rails 3.1 Release Notes were compiled by [Vijay Dev](https://github.com/vijaydev) diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md index b9444510ea..4615cf18e6 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -108,7 +108,7 @@ In Rails 4.0, several features have been extracted into gems. You can simply add * Mass assignment protection in Active Record models ([GitHub](https://github.com/rails/protected_attributes), [Pull Request](https://github.com/rails/rails/pull/7251)) * ActiveRecord::SessionStore ([GitHub](https://github.com/rails/activerecord-session_store), [Pull Request](https://github.com/rails/rails/pull/7436)) * Active Record Observers ([GitHub](https://github.com/rails/rails-observers), [Commit](https://github.com/rails/rails/commit/39e85b3b90c58449164673909a6f1893cba290b2)) -* Active Resource ([GitHub](https://github.com/rails/activeresource), [Pull Request](https://github.com/rails/rails/pull/572), [Blog](http://yetimedia.tumblr.com/post/35233051627/activeresource-is-dead-long-live-activeresource)) +* Active Resource ([GitHub](https://github.com/rails/activeresource), [Pull Request](https://github.com/rails/rails/pull/572), [Blog](http://yetimedia-blog-blog.tumblr.com/post/35233051627/activeresource-is-dead-long-live-activeresource)) * Action Caching ([GitHub](https://github.com/rails/actionpack-action_caching), [Pull Request](https://github.com/rails/rails/pull/7833)) * Page Caching ([GitHub](https://github.com/rails/actionpack-page_caching), [Pull Request](https://github.com/rails/rails/pull/7833)) * Sprockets ([GitHub](https://github.com/rails/sprockets-rails)) diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md index 73e6c2c05b..a30bfc458a 100644 --- a/guides/source/4_2_release_notes.md +++ b/guides/source/4_2_release_notes.md @@ -871,7 +871,7 @@ Please refer to the [Changelog][active-support] for detailed changes. `module Foo; extend ActiveSupport::Concern; end` boilerplate. ([Commit](https://github.com/rails/rails/commit/b16c36e688970df2f96f793a759365b248b582ad)) -* New [guide](constant_autoloading_and_reloading.html) about constant autoloading and reloading. +* New [guide](autoloading_and_reloading_constants.html) about constant autoloading and reloading. Credits ------- diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md index 4e8252f85b..a98f7be067 100644 --- a/guides/source/5_0_release_notes.md +++ b/guides/source/5_0_release_notes.md @@ -37,24 +37,141 @@ Major Features -------------- ### Action Cable -[Pull Request](https://github.com/rails/rails/pull/22586) -ToDo... +Action Cable is a new framework in Rails 5. It seamlessly integrates +[WebSockets](https://en.wikipedia.org/wiki/WebSocket) with the rest of your +Rails application. -### Rails API -[Pull Request](https://github.com/rails/rails/pull/19832) +Action Cable allows for real-time features to be written in Ruby in the +same style and form as the rest of your Rails application, while still being +performant and scalable. It's a full-stack offering that provides both a +client-side JavaScript framework and a server-side Ruby framework. You have +access to your full domain model written with Active Record or your ORM of +choice. -ToDo... +See the [Action Cable Overview](action_cable_overview.html) guide for more +information. + +### API Applications + +Rails can now be used to create slimmed down API only applications. +This is useful for creating and serving APIs similar to [Twitter](https://dev.twitter.com) or [GitHub](http://developer.github.com) API, +that can be used to serve public facing, as well as, for custom applications. + +You can generate a new api Rails app using: + +```bash +$ rails new my_api --api +``` + +This will do three main things: + +- Configure your application to start with a more limited set of middleware + than normal. Specifically, it will not include any middleware primarily useful + for browser applications (like cookies support) by default. +- Make `ApplicationController` inherit from `ActionController::API` instead of + `ActionController::Base`. As with middleware, this will leave out any Action + Controller modules that provide functionalities primarily used by browser + applications. +- Configure the generators to skip generating views, helpers and assets when + you generate a new resource. + +The application provides a base for APIs, +that can then be [configured to pull in functionality](api_app.html) as suitable for the application's needs. + +See the [Using Rails for API-only Applications](api_app.html) guide for more +information. ### Active Record attributes API -ToDo... +Defines an attribute with a type on a model. It will override the type of existing attributes if needed. +This allows control over how values are converted to and from SQL when assigned to a model. +It also changes the behavior of values passed to `ActiveRecord::Base.where`, which lets use our domain objects across much of Active Record, +without having to rely on implementation details or monkey patching. + +Some things that you can achieve with this: + +- The type detected by Active Record can be overridden. +- A default can also be provided. +- Attributes do not need to be backed by a database column. + +```ruby + +# db/schema.rb +create_table :store_listings, force: true do |t| + t.decimal :price_in_cents + t.string :my_string, default: "original default" +end + +# app/models/store_listing.rb +class StoreListing < ActiveRecord::Base +end + +store_listing = StoreListing.new(price_in_cents: '10.1') + +# before +store_listing.price_in_cents # => BigDecimal.new(10.1) +StoreListing.new.my_string # => "original default" + +class StoreListing < ActiveRecord::Base + attribute :price_in_cents, :integer # custom type + attribute :my_string, :string, default: "new default" # default value + attribute :my_default_proc, :datetime, default: -> { Time.now } # default value + attribute :field_without_db_column, :integer, array: true +end + +# after +store_listing.price_in_cents # => 10 +StoreListing.new.my_string # => "new default" +StoreListing.new.my_default_proc # => 2015-05-30 11:04:48 -0600 +model = StoreListing.new(field_without_db_column: ["1", "2", "3"]) +model.attributes # => {field_without_db_column: [1, 2, 3]} +``` + +**Creating Custom Types:** + +You can define your own custom types, as long as they respond +to the methods defined on the value type. The method `deserialize` or +`cast` will be called on your type object, with raw input from the +database or from your controllers. This is useful, for example, when doing custom conversion, +like Money data. + +**Querying:** + +When `ActiveRecord::Base.where` is called, it will +use the type defined by the model class to convert the value to SQL, +calling `serialize` on your type object. + +This gives the objects ability to specify, how to convert values when performing SQL queries. + +**Dirty Tracking:** + +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) +for a detailed write up. + ### Test Runner -[Pull Request](https://github.com/rails/rails/pull/19216) -ToDo... +A new test runner has been introduced to enhance the capabilities of running tests from Rails. +To use this test runner simply type `bin/rails test`. + +Test Runner is inspired from `RSpec`, `minitest-reporters`, `maxitest` and others. +It includes some of these notable advancements: +- Run a single test using line number of test. +- Run multiple tests pinpointing to line number of tests. +- Improved failure messages, which also add ease of re-running failed tests. +- Fail fast using `-f` option, to stop tests immediately on occurrence of failure, +instead of waiting for the suite to complete. +- Defer test output until the end of a full test run using the `-d` option. +- Complete exception backtrace output using `-b` option. +- Integration with `Minitest` to allow options like `-s` for test seed data, +`-n` for running specific test by name, `-v` for better verbose output and so forth. +- Colored test output. Railties -------- @@ -89,11 +206,15 @@ Please refer to the [Changelog][railties] for detailed changes. * Deprecated `config.static_cache_control` in favor of `config.public_file_server.headers`. - ([Pull Request](https://github.com/rails/rails/pull/22173)) + ([Pull Request](https://github.com/rails/rails/pull/19135)) * Deprecated `config.serve_static_files` in favor of `config.public_file_server.enabled`. ([Pull Request](https://github.com/rails/rails/pull/22173)) +* Deprecated the tasks in the `rails` task namespace in favor of the `app` namespace. + (e.g. `rails:update` and `rails:template` tasks are renamed to `app:update` and `app:template`.) + ([Pull Request](https://github.com/rails/rails/pull/23439)) + ### Notable changes * Added Rails test runner `bin/rails test`. @@ -120,6 +241,34 @@ Please refer to the [Changelog][railties] for detailed changes. ([Pull Request](https://github.com/rails/rails/pull/22457), [Pull Request](https://github.com/rails/rails/pull/22288)) +* New applications are generated with the evented file system monitor enabled + on Linux and Mac OS X. The feature can be opted out by passing + `--skip-listen` to the generator. + ([commit](https://github.com/rails/rails/commit/de6ad5665d2679944a9ee9407826ba88395a1003), + [commit](https://github.com/rails/rails/commit/94dbc48887bf39c241ee2ce1741ee680d773f202)) + +* Generate applications with an option to log to STDOUT in production + using the environment variable `RAILS_LOG_TO_STDOUT`. + ([Pull Request](https://github.com/rails/rails/pull/23734)) + +* Enable HSTS with IncludeSudomains header for new applications. + ([Pull Request](https://github.com/rails/rails/pull/23852)) + +* The application generator writes a new file `config/spring.rb`, which tells + Spring to watch additional common files. + ([commit](https://github.com/rails/rails/commit/b04d07337fd7bc17e88500e9d6bcd361885a45f8)) + +* Added `--skip-action-mailer` to skip Action Mailer while generating new app. + ([Pull Request](https://github.com/rails/rails/pull/18288)) + +* Removed `tmp/sessions` directory and the clear rake task associated with it. + ([Pull Request](https://github.com/rails/rails/pull/18314)) + +* Changed `_form.html.erb` generated by scaffold generator to use local variables. + ([Pull Request](https://github.com/rails/rails/pull/13434)) + +* Disabled autoloading of classes in production environment. + ([commit](https://github.com/rails/rails/commit/a71350cae0082193ad8c66d65ab62e8bb0b7853b)) Action Pack ----------- @@ -206,6 +355,21 @@ Please refer to the [Changelog][action-pack] for detailed changes. `RedirectBackError`. ([Pull Request](https://github.com/rails/rails/pull/22506)) +* `ActionDispatch::IntegrationTest` and `ActionController::TestCase` deprecate positional arguments in favor of + keyword arguments. ([Pull Request](https://github.com/rails/rails/pull/18323)) + +* Deprecated `:controller` and `:action` path parameters. + ([Pull Request](https://github.com/rails/rails/pull/23980)) + +* Deprecated env method on controller instances. + ([commit](https://github.com/rails/rails/commit/05934d24aff62d66fc62621aa38dae6456e276be)) + +* `ActionDispatch::ParamsParser` is deprecated and was removed from the + middleware stack. To configure the parameter parsers use + `ActionDispatch::Request.parameter_parsers=`. + ([commit](https://github.com/rails/rails/commit/38d2bf5fd1f3e014f2397898d371c339baa627b1), + [commit](https://github.com/rails/rails/commit/5ed38014811d4ce6d6f957510b9153938370173b)) + ### Notable changes * Added `ActionController::Renderer` to render arbitrary templates @@ -249,13 +413,50 @@ Please refer to the [Changelog][action-pack] for detailed changes. * Changed the `protect_from_forgery` prepend default to `false`. ([commit](https://github.com/rails/rails/commit/39794037817703575c35a75f1961b01b83791191)) -* `ActionController::TestCase` will be moved to it's own gem in Rails 5.1. Use +* `ActionController::TestCase` will be moved to its own gem in Rails 5.1. Use `ActionDispatch::IntegrationTest` instead. ([commit](https://github.com/rails/rails/commit/4414c5d1795e815b102571425974a8b1d46d932d)) -* Rails will only generate "weak", instead of strong ETags. +* Rails generates weak ETags by default. ([Pull Request](https://github.com/rails/rails/pull/17573)) +* Controller actions without an explicit `render` call and with no + corresponding templates will render `head :no_content` implicitly + instead of raising an error. + (Pull Request [1](https://github.com/rails/rails/pull/19377), + [2](https://github.com/rails/rails/pull/23827)) + +* Added an option for per-form CSRF tokens. + ([Pull Request](https://github.com/rails/rails/pull/22275)) + +* Added request encoding and response parsing to integration tests. + ([Pull Request](https://github.com/rails/rails/pull/21671)) + +* Add `ActionController#helpers` to get access to the view context + at the controller level. + ([Pull Request](https://github.com/rails/rails/pull/24866)) + +* Discarded flash messages get removed before storing into session. + ([Pull Request](https://github.com/rails/rails/pull/18721)) + +* Added support for passing collection of records to `fresh_when` and + `stale?`. + ([Pull Request](https://github.com/rails/rails/pull/18374)) + +* `ActionController::Live` became an `ActiveSupport::Concern`. That + means it can't be just included in other modules without extending + them with `ActiveSupport::Concern` or `ActionController::Live` + won't take effect in production. Some people may be using another + module to include some special `Warden`/`Devise` authentication + failure handling code as well since the middleware can't catch a + `:warden` thrown by a spawned thread which is the case when using + `ActionController::Live`. + ([More details in this issue](https://github.com/rails/rails/issues/25581)) + +* Introduce `Response#strong_etag=` and `#weak_etag=` and analogous + options for `fresh_when` and `stale?`. + ([Pull Request](https://github.com/rails/rails/pull/24387)) + Action View ------------- @@ -280,12 +481,9 @@ Please refer to the [Changelog][action-view] for detailed changes. * Changed the default template handler from `ERB` to `Raw`. ([commit](https://github.com/rails/rails/commit/4be859f0fdf7b3059a28d03c279f03f5938efc80)) -* Collection rendering automatically caches and fetches multiple partials. - ([Pull Request](https://github.com/rails/rails/pull/18948)) - -* Allow defining explicit collection caching using a `# Template Collection: ...` - directive inside templates. - ([Pull Request](https://github.com/rails/rails/pull/20781)) +* Collection rendering can cache and fetches multiple partials at once. + ([Pull Request](https://github.com/rails/rails/pull/18948), + [commit](https://github.com/rails/rails/commit/e93f0f0f133717f9b06b1eaefd3442bd0ff43985)) * Added wildcard matching to explicit dependencies. ([Pull Request](https://github.com/rails/rails/pull/20904)) @@ -294,6 +492,12 @@ Please refer to the [Changelog][action-view] for detailed changes. button on submit to prevent double submits. ([Pull Request](https://github.com/rails/rails/pull/21135)) +* Partial template name no longer has to be a valid Ruby identifier. + ([commit](https://github.com/rails/rails/commit/da9038e)) + +* The `datetime_tag` helper now generates an input tag with the type of + `datetime-local`. + ([Pull Request](https://github.com/rails/rails/pull/25469)) Action Mailer ------------- @@ -324,6 +528,11 @@ Please refer to the [Changelog][action-mailer] for detailed changes. the mailer queue name. ([Pull Request](https://github.com/rails/rails/pull/18587)) +* Added support for fragment caching in Action Mailer views. + Added new config option `config.action_mailer.perform_caching` to determine + whether your templates should perform caching or not. + ([Pull Request](https://github.com/rails/rails/pull/22825)) + Active Record ------------- @@ -375,8 +584,10 @@ Please refer to the [Changelog][active-record] for detailed changes. [activemodel-serializers-xml](https://github.com/rails/activemodel-serializers-xml) gem. ([Pull Request](https://github.com/rails/rails/pull/21161)) -* Removed support for the legacy `mysql` database adapter from core. It will - live on in a separate gem for now, but most users should just use `mysql2`. +* Removed support for the legacy `mysql` database adapter from core. Most users should + be able to use `mysql2`. It will be converted to a separate gem when when we find someone + to maintain it. ([Pull Request 1](https://github.com/rails/rails/pull/22642), + [Pull Request 2](https://github.com/rails/rails/pull/22715)) * Removed support for the `protected_attributes` gem. ([commit](https://github.com/rails/rails/commit/f4fbc0301021f13ae05c8e941c8efc4ae351fdf9)) @@ -384,6 +595,12 @@ Please refer to the [Changelog][active-record] for detailed changes. * Removed support for PostgreSQL versions below 9.1. ([Pull Request](https://github.com/rails/rails/pull/23434)) +* Removed support for `activerecord-deprecated_finders` gem. + ([commit](https://github.com/rails/rails/commit/78dab2a8569408658542e462a957ea5a35aa4679)) + +* Removed `ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES` constant. + ([commit](https://github.com/rails/rails/commit/a502703c3d2151d4d3b421b29fefdac5ad05df61)) + ### Deprecations * Deprecated passing a class as a value in a query. Users should pass strings @@ -396,10 +613,6 @@ Please refer to the [Changelog][active-record] for detailed changes. * Deprecated `ActiveRecord::Base.errors_in_transactional_callbacks=`. ([commit](https://github.com/rails/rails/commit/07d3d402341e81ada0214f2cb2be1da69eadfe72)) -* Deprecated passing of `start` value to `find_in_batches` and `find_each` - in favour of `begin_at` value. - ([Pull Request](https://github.com/rails/rails/pull/18961)) - * Deprecated `Relation#uniq` use `Relation#distinct` instead. ([commit](https://github.com/rails/rails/commit/adfab2dcf4003ca564d78d4425566dd2d9cd8b4f)) @@ -441,6 +654,22 @@ Please refer to the [Changelog][active-record] for detailed changes. `offset` method on relation instead. ([Pull Request](https://github.com/rails/rails/pull/22053)) +* Deprecated `{insert|update|delete}_sql` in `DatabaseStatements`. + Use the `{insert|update|delete}` public methods instead. + ([Pull Request](https://github.com/rails/rails/pull/23086)) + +* Deprecated `use_transactional_fixtures` in favor of + `use_transactional_tests` for more clarity. + ([Pull Request](https://github.com/rails/rails/pull/19282)) + +* Deprecated passing a column to `ActiveRecord::Connection#quote`. + ([commit](https://github.com/rails/rails/commit/7bb620869725ad6de603f6a5393ee17df13aa96c)) + +* Added an option `end` to `find_in_batches` that complements the `start` + parameter to specify where to stop batch processing. + ([Pull Request](https://github.com/rails/rails/pull/12257)) + + ### Notable changes * Added a `foreign_key` option to `references` while creating the table. @@ -449,19 +678,13 @@ Please refer to the [Changelog][active-record] for detailed changes. * New attributes API. ([commit](https://github.com/rails/rails/commit/8c752c7ac739d5a86d4136ab1e9d0142c4041e58)) -* Added `:enum_prefix`/`:enum_suffix` option to `enum` - definition. ([Pull Request](https://github.com/rails/rails/pull/19813)) +* Added `:_prefix`/`:_suffix` option to `enum` definition. + ([Pull Request](https://github.com/rails/rails/pull/19813), + [Pull Request](https://github.com/rails/rails/pull/20999)) * Added `#cache_key` to `ActiveRecord::Relation`. ([Pull Request](https://github.com/rails/rails/pull/20884)) -* Added `ActiveRecord::Relation#outer_joins`. - ([Pull Request](https://github.com/rails/rails/pull/12071)) - -* Require `belongs_to` by default. - ([Pull Request](https://github.com/rails/rails/pull/18937)) - Deprecate - `required` option in favor of `optional` for `belongs_to` - * Changed the default `null` value for `timestamps` to `false`. ([commit](https://github.com/rails/rails/commit/a939506f297b667291480f26fa32a373a18ae06a)) @@ -481,16 +704,14 @@ Please refer to the [Changelog][active-record] for detailed changes. operator to combine WHERE or HAVING clauses. ([commit](https://github.com/rails/rails/commit/b0b37942d729b6bdcd2e3178eda7fa1de203b3d0)) -* Added `:time` option added for `#touch`. - ([Pull Request](https://github.com/rails/rails/pull/18956)) - * Added `ActiveRecord::Base.suppress` to prevent the receiver from being saved during the given block. ([Pull Request](https://github.com/rails/rails/pull/18910)) * `belongs_to` will now trigger a validation error by default if the association is not present. You can turn this off on a per-association basis - with `optional: true`. + with `optional: true`. Also deprecate `required` option in favor of `optional` + for `belongs_to`. ([Pull Request](https://github.com/rails/rails/pull/18937)) * Added `config.active_record.dump_schemas` to configure the behavior of @@ -542,6 +763,54 @@ Please refer to the [Changelog][active-record] for detailed changes. model behavior. ([Pull Request](https://github.com/rails/rails/pull/22567)) +* Added ActiveRecord `#second_to_last` and `#third_to_last` methods. + ([Pull Request](https://github.com/rails/rails/pull/23583)) + +* Added ability to annotate database objects (tables, columns, indexes) + with comments stored in database metadata for PostgreSQL & MySQL. + ([Pull Request](https://github.com/rails/rails/pull/22911)) + +* Added prepared statements support to `mysql2` adapter, for mysql2 0.4.4+, + Previously this was only supported on the deprecated `mysql` legacy adapter. + To enable, set `prepared_statements: true` in config/database.yml. + ([Pull Request](https://github.com/rails/rails/pull/23461)) + +* Added ability to call `ActionRecord::Relation#update` on relation objects + which will run validations on callbacks on all objects in the relation. + ([Pull Request](https://github.com/rails/rails/pull/11898)) + +* Added `:touch` option to the `save` method so that records can be saved without + updating timestamps. + ([Pull Request](https://github.com/rails/rails/pull/18225)) + +* Added expression indexes and operator classes support for PostgreSQL. + ([commit](https://github.com/rails/rails/commit/edc2b7718725016e988089b5fb6d6fb9d6e16882)) + +* Added `:index_errors` option to add indexes to errors of nested attributes. + ([Pull Request](https://github.com/rails/rails/pull/19686)) + +* Added support for bidirectional destroy dependencies. + ([Pull Request](https://github.com/rails/rails/pull/18548)) + +* Added support for `after_commit` callbacks in transactional tests. + ([Pull Request](https://github.com/rails/rails/pull/18458)) + +* Added `foreign_key_exists?` method to see if a foreign key exists on a table + or not. + ([Pull Request](https://github.com/rails/rails/pull/18662)) + +* Added `:time` option to `touch` method to touch records with different time + than the current time. + ([Pull Request](https://github.com/rails/rails/pull/18956)) + +* Change transaction callbacks to not swallow errors. + Before this change any errors raised inside a transaction callback + were getting rescued and printed in the logs, unless you used + the (newly deprecated) `raise_in_transactional_callbacks = true` option. + + Now these errors are not rescued anymore and just bubble up, matching the + behavior of other callbacks. + ([commit](https://github.com/rails/rails/commit/07d3d402341e81ada0214f2cb2be1da69eadfe72)) Active Model ------------ @@ -558,6 +827,9 @@ Please refer to the [Changelog][active-model] for detailed changes. [activemodel-serializers-xml](https://github.com/rails/activemodel-serializers-xml) gem. ([Pull Request](https://github.com/rails/rails/pull/21161)) +* Removed `ActionController::ModelNaming` module. + ([Pull Request](https://github.com/rails/rails/pull/18194)) + ### Deprecations * Deprecated returning `false` as a way to halt Active Model and @@ -593,6 +865,9 @@ Please refer to the [Changelog][active-model] for detailed changes. * Validate multiple contexts on `valid?` and `invalid?` at once. ([Pull Request](https://github.com/rails/rails/pull/21069)) +* Change `validates_acceptance_of` to accept `true` as default value + apart from `1`. + ([Pull Request](https://github.com/rails/rails/pull/18439)) Active Job ----------- @@ -601,23 +876,32 @@ Please refer to the [Changelog][active-job] for detailed changes. ### Notable changes -* `ActiveJob::Base.deserialize` delegates to the job class. this allows jobs +* `ActiveJob::Base.deserialize` delegates to the job class. This allows jobs to attach arbitrary metadata when they get serialized and read it back when they get performed. ([Pull Request](https://github.com/rails/rails/pull/18260)) +* Add ability to configure the queue adapter on a per job basis without + affecting each other. + ([Pull Request](https://github.com/rails/rails/pull/16992)) + * A generated job now inherits from `app/jobs/application_job.rb` by default. ([Pull Request](https://github.com/rails/rails/pull/19034)) -* Allow `DelayedJob`, `Sidekiq`, `qu`, and `que` to report the job id back to - `ActiveJob::Base` as `provider_job_id`. +* Allow `DelayedJob`, `Sidekiq`, `qu`, `que`, and `queue_classic` to report + the job id back to `ActiveJob::Base` as `provider_job_id`. ([Pull Request](https://github.com/rails/rails/pull/20064), - [Pull Request](https://github.com/rails/rails/pull/20056)) + [Pull Request](https://github.com/rails/rails/pull/20056), + [commit](https://github.com/rails/rails/commit/68e3279163d06e6b04e043f91c9470e9259bbbe0)) * Implement a simple `AsyncJob` processor and associated `AsyncAdapter` that queue jobs to a `concurrent-ruby` thread pool. ([Pull Request](https://github.com/rails/rails/pull/21257)) +* Change the default adapter from inline to async. It's a better default as + tests will then not mistakenly come to rely on behavior happening + synchronously. + ([commit](https://github.com/rails/rails/commit/625baa69d14881ac49ba2e5c7d9cac4b222d7022)) Active Support -------------- @@ -655,6 +939,9 @@ Please refer to the [Changelog][active-support] for detailed changes. * Removed deprecated `ThreadSafe::Cache`. Use `Concurrent::Map` instead. ([Pull Request](https://github.com/rails/rails/pull/21679)) +* Removed `Object#itself` as it is implemented in Ruby 2.2. + ([Pull Request](https://github.com/rails/rails/pull/18244)) + ### Deprecations * Deprecated `MissingSourceFile` in favor of `LoadError`. @@ -682,10 +969,19 @@ Please refer to the [Changelog][active-support] for detailed changes. `ActiveSupport::Cache::MemCachedStore#escape_key`, and `ActiveSupport::Cache::FileStore#key_file_path`. Use `normalize_key` instead. + ([Pull Request](https://github.com/rails/rails/pull/22215), + [commit](https://github.com/rails/rails/commit/a8f773b0)) - Deprecated `ActiveSupport::Cache::LocaleCache#set_cache_value` in favor of `write_cache_value`. +* Deprecated `ActiveSupport::Cache::LocaleCache#set_cache_value` in favor of `write_cache_value`. ([Pull Request](https://github.com/rails/rails/pull/22215)) +* Deprecated passing arguments to `assert_nothing_raised`. + ([Pull Request](https://github.com/rails/rails/pull/23789)) + +* Deprecated `Module.local_constants` in favor of `Module.constants(false)`. + ([Pull Request](https://github.com/rails/rails/pull/23936)) + + ### Notable changes * Added `#verified` and `#valid_message?` methods to @@ -705,9 +1001,10 @@ Please refer to the [Changelog][active-support] for detailed changes. * Changed the default test order from `:sorted` to `:random`. ([commit](https://github.com/rails/rails/commit/5f777e4b5ee2e3e8e6fd0e2a208ec2a4d25a960d)) -* Added `#on_weekend?`, `#next_weekday`, `#prev_weekday` methods to `Date`, +* Added `#on_weekend?`, `#on_weekday?`, `#next_weekday`, `#prev_weekday` methods to `Date`, `Time`, and `DateTime`. - ([Pull Request](https://github.com/rails/rails/pull/18335)) + ([Pull Request](https://github.com/rails/rails/pull/18335), + [Pull Request](https://github.com/rails/rails/pull/23687)) * Added `same_time` option to `#next_week` and `#prev_week` for `Date`, `Time`, and `DateTime`. @@ -715,7 +1012,7 @@ Please refer to the [Changelog][active-support] for detailed changes. * Added `#prev_day` and `#next_day` counterparts to `#yesterday` and `#tomorrow` for `Date`, `Time`, and `DateTime`. - ([Pull Request](httpshttps://github.com/rails/rails/pull/18335)) + ([Pull Request](https://github.com/rails/rails/pull/18335)) * Added `SecureRandom.base58` for generation of random base58 strings. ([commit](https://github.com/rails/rails/commit/b1093977110f18ae0cafe56c3d99fc22a7d54d1b)) @@ -736,7 +1033,7 @@ Please refer to the [Changelog][active-support] for detailed changes. ([commit](https://github.com/rails/rails/commit/a5e507fa0b8180c3d97458a9b86c195e9857d8f6)) * Added `Integer#positive?` and `Integer#negative?` query methods - in the vein of `Fixnum#zero?`. + in the vein of `Integer#zero?`. ([commit](https://github.com/rails/rails/commit/e54277a45da3c86fecdfa930663d7692fd083daa)) * Added a bang version to `ActiveSupport::OrderedOptions` get methods which will raise @@ -755,6 +1052,27 @@ Please refer to the [Changelog][active-support] for detailed changes. class and module variables that live per-thread. ([Pull Request](https://github.com/rails/rails/pull/22630)) +* Added `Array#second_to_last` and `Array#third_to_last` methods. + ([Pull Request](https://github.com/rails/rails/pull/23583)) + +* Publish `ActiveSupport::Executor` and `ActiveSupport::Reloader` APIs to allow + components and libraries to manage, and participate in, the execution of + application code, and the application reloading process. + ([Pull Request](https://github.com/rails/rails/pull/23807)) + +* `ActiveSupport::Duration` now supports ISO8601 formatting and parsing. + ([Pull Request](https://github.com/rails/rails/pull/16917)) + +* `ActiveSupport::JSON.decode` now supports parsing ISO8601 local times when + `parse_json_times` is enabled. + ([Pull Request](https://github.com/rails/rails/pull/23011)) + +* `ActiveSupport::JSON.decode` now return `Date` objects for date strings. + ([Pull Request](https://github.com/rails/rails/pull/23011)) + +* Added ability to `TaggedLogging` to allow loggers to be instantiated multiple + times so that they don't share tags with each other. + ([Pull Request](https://github.com/rails/rails/pull/9065)) Credits ------- @@ -768,6 +1086,7 @@ framework it is. Kudos to all of them. [action-pack]: https://github.com/rails/rails/blob/5-0-stable/actionpack/CHANGELOG.md [action-view]: https://github.com/rails/rails/blob/5-0-stable/actionview/CHANGELOG.md [action-mailer]: https://github.com/rails/rails/blob/5-0-stable/actionmailer/CHANGELOG.md +[action-cable]: https://github.com/rails/rails/blob/5-0-stable/actioncable/CHANGELOG.md [active-record]: https://github.com/rails/rails/blob/5-0-stable/activerecord/CHANGELOG.md [active-model]: https://github.com/rails/rails/blob/5-0-stable/activemodel/CHANGELOG.md [active-support]: https://github.com/rails/rails/blob/5-0-stable/activesupport/CHANGELOG.md diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md new file mode 100644 index 0000000000..3716aa0ecb --- /dev/null +++ b/guides/source/action_cable_overview.md @@ -0,0 +1,670 @@ +Action Cable Overview +===================== + +In this guide you will learn how Action Cable works and how to use WebSockets to +incorporate real-time features into your Rails application. + +After reading this guide, you will know: + +* What Action Cable is and its integration on backend and frontend +* How to setup Action Cable +* How to setup channels +* Deployment and Architecture setup for running Action Cable + +-------------------------------------------------------------------------------- + +Introduction +------------ + +Action Cable seamlessly integrates +[WebSockets](https://en.wikipedia.org/wiki/WebSocket) with the rest of your +Rails application. It allows for real-time features to be written in Ruby in the +same style and form as the rest of your Rails application, while still being +performant and scalable. It's a full-stack offering that provides both a +client-side JavaScript framework and a server-side Ruby framework. You have +access to your full domain model written with Active Record or your ORM of +choice. + +What is Pub/Sub +--------------- + +[Pub/Sub](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern), or +Publish-Subscribe, refers to a message queue paradigm whereby senders of +information (publishers), send data to an abstract class of recipients +(subscribers), without specifying individual recipients. Action Cable uses this +approach to communicate between the server and many clients. + +## Server-Side Components + +### Connections + +*Connections* form the foundation of the client-server relationship. For every +WebSocket accepted by the server, a connection object is instantiated. This +object becomes the parent of all the *channel subscriptions* that are created +from there on. The connection itself does not deal with any specific application +logic beyond authentication and authorization. The client of a WebSocket +connection is called the connection *consumer*. An individual user will create +one consumer-connection pair per browser tab, window, or device they have open. + +Connections are instances of `ApplicationCable::Connection`. In this class, you +authorize the incoming connection, and proceed to establish it if the user can +be identified. + +#### Connection Setup + +```ruby +# app/channels/application_cable/connection.rb +module ApplicationCable + class Connection < ActionCable::Connection::Base + identified_by :current_user + + def connect + self.current_user = find_verified_user + end + + protected + def find_verified_user + if current_user = User.find_by(id: cookies.signed[:user_id]) + current_user + else + reject_unauthorized_connection + end + end + end +end +``` + +Here `identified_by` is a connection identifier that can be used to find the +specific connection later. Note that anything marked as an identifier will automatically +create a delegate by the same name on any channel instances created off the connection. + +This example relies on the fact that you will already have handled authentication of the user +somewhere else in your application, and that a successful authentication sets a signed +cookie with the user ID. + +The cookie is then automatically sent to the connection instance when a new connection +is attempted, and you use that to set the `current_user`. By identifying the connection +by this same current user, you're also ensuring that you can later retrieve all open +connections by a given user (and potentially disconnect them all if the user is deleted +or unauthorized). + +### Channels + +A *channel* encapsulates a logical unit of work, similar to what a controller does in a +regular MVC setup. By default, Rails creates a parent `ApplicationCable::Channel` class +for encapsulating shared logic between your channels. + +#### Parent Channel Setup + +```ruby +# app/channels/application_cable/channel.rb +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end +``` + +Then you would create your own channel classes. For example, you could have a +`ChatChannel` and an `AppearanceChannel`: + +```ruby +# app/channels/chat_channel.rb +class ChatChannel < ApplicationCable::Channel +end + +# app/channels/appearance_channel.rb +class AppearanceChannel < ApplicationCable::Channel +end +``` + +A consumer could then be subscribed to either or both of these channels. + +#### Subscriptions + +Consumers subscribe to channels, acting as *subscribers*. Their connection is +called a *subscription*. Produced messages are then routed to these channel +subscriptions based on an identifier sent by the cable consumer. + +```ruby +# app/channels/chat_channel.rb +class ChatChannel < ApplicationCable::Channel + # Called when the consumer has successfully + # become a subscriber of this channel. + def subscribed + end +end +``` + +## Client-Side Components + +### Connections + +Consumers require an instance of the connection on their side. This can be +established using the following JavaScript, which is generated by default by Rails: + +#### Connect Consumer + +```js +// app/assets/javascripts/cable.js +//= require action_cable +//= require_self +//= require_tree ./channels + +(function() { + this.App || (this.App = {}); + + App.cable = ActionCable.createConsumer(); +}).call(this); +``` + +This will ready a consumer that'll connect against `/cable` on your server by default. +The connection won't be established until you've also specified at least one subscription +you're interested in having. + +#### Subscriber + +A consumer becomes a subscriber by creating a subscription to a given channel: + +```coffeescript +# app/assets/javascripts/cable/subscriptions/chat.coffee +App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" } + +# app/assets/javascripts/cable/subscriptions/appearance.coffee +App.cable.subscriptions.create { channel: "AppearanceChannel" } +``` + +While this creates the subscription, the functionality needed to respond to +received data will be described later on. + +A consumer can act as a subscriber to a given channel any number of times. For +example, a consumer could subscribe to multiple chat rooms at the same time: + +```coffeescript +App.cable.subscriptions.create { channel: "ChatChannel", room: "1st Room" } +App.cable.subscriptions.create { channel: "ChatChannel", room: "2nd Room" } +``` + +## Client-Server Interactions + +### Streams + +*Streams* provide the mechanism by which channels route published content +(broadcasts) to their subscribers. + +```ruby +# app/channels/chat_channel.rb +class ChatChannel < ApplicationCable::Channel + def subscribed + stream_from "chat_#{params[:room]}" + end +end +``` + +If you have a stream that is related to a model, then the broadcasting used +can be generated from the model and channel. The following example would +subscribe to a broadcasting like `comments:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE` + +```ruby +class CommentsChannel < ApplicationCable::Channel + def subscribed + post = Post.find(params[:id]) + stream_for post + end +end +``` + +You can then broadcast to this channel like this: + +```ruby +CommentsChannel.broadcast_to(@post, @comment) +``` + +### Broadcasting + +A *broadcasting* is a pub/sub link where anything transmitted by a publisher +is routed directly to the channel subscribers who are streaming that named +broadcasting. Each channel can be streaming zero or more broadcastings. + +Broadcastings are purely an online queue and time dependent. If a consumer is +not streaming (subscribed to a given channel), they'll not get the broadcast +should they connect later. + +Broadcasts are called elsewhere in your Rails application: + +```ruby +WebNotificationsChannel.broadcast_to( + current_user, + title: 'New things!', + body: 'All the news fit to print' +) +``` + +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. +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 +`web_notifications:1` directly to the client by invoking the `received` +callback. + +### Subscriptions + +When a consumer is subscribed to a channel, they act as a subscriber. This +connection is called a subscription. Incoming messages are then routed to +these channel subscriptions based on an identifier sent by the cable consumer. + +```coffeescript +# app/assets/javascripts/cable/subscriptions/chat.coffee +# Assumes you've already requested the right to send web notifications +App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" }, + received: (data) -> + @appendLine(data) + + appendLine: (data) -> + html = @createLine(data) + $("[data-chat-room='Best Room']").append(html) + + createLine: (data) -> + """ + <article class="chat-line"> + <span class="speaker">#{data["sent_by"]}</span> + <span class="body">#{data["body"]}</span> + </article> + """ +``` + +### Passing Parameters to Channels + +You can pass parameters from the client side to the server side when creating a +subscription. For example: + +```ruby +# app/channels/chat_channel.rb +class ChatChannel < ApplicationCable::Channel + def subscribed + stream_from "chat_#{params[:room]}" + end +end +``` + +An object passed as the first argument to `subscriptions.create` becomes the +params hash in the cable channel. The keyword `channel` is required: + +```coffeescript +# app/assets/javascripts/cable/subscriptions/chat.coffee +App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" }, + received: (data) -> + @appendLine(data) + + appendLine: (data) -> + html = @createLine(data) + $("[data-chat-room='Best Room']").append(html) + + createLine: (data) -> + """ + <article class="chat-line"> + <span class="speaker">#{data["sent_by"]}</span> + <span class="body">#{data["body"]}</span> + </article> + """ +``` + +```ruby +# Somewhere in your app this is called, perhaps +# from a NewCommentJob. +ActionCable.server.broadcast( + "chat_#{room}", + sent_by: 'Paul', + body: 'This is a cool chat app.' +) +``` + +### Rebroadcasting a Message + +A common use case is to *rebroadcast* a message sent by one client to any +other connected clients. + +```ruby +# app/channels/chat_channel.rb +class ChatChannel < ApplicationCable::Channel + def subscribed + stream_from "chat_#{params[:room]}" + end + + def receive(data) + ActionCable.server.broadcast("chat_#{params[:room]}", data) + end +end +``` + +```coffeescript +# app/assets/javascripts/cable/subscriptions/chat.coffee +App.chatChannel = App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" }, + received: (data) -> + # data => { sent_by: "Paul", body: "This is a cool chat app." } + +App.chatChannel.send({ sent_by: "Paul", body: "This is a cool chat app." }) +``` + +The rebroadcast will be received by all connected clients, _including_ the +client that sent the message. Note that params are the same as they were when +you subscribed to the channel. + +## Full-Stack Examples + +The following setup steps are common to both examples: + + 1. [Setup your connection](#connection-setup). + 2. [Setup your parent channel](#parent-channel-setup). + 3. [Connect your consumer](#connect-consumer). + +### Example 1: User Appearances + +Here's a simple example of a channel that tracks whether a user is online or not +and what page they're on. (This is useful for creating presence features like showing +a green dot next to a user name if they're online). + +Create the server-side appearance channel: + +```ruby +# app/channels/appearance_channel.rb +class AppearanceChannel < ApplicationCable::Channel + def subscribed + current_user.appear + end + + def unsubscribed + current_user.disappear + end + + def appear(data) + current_user.appear(on: data['appearing_on']) + end + + def away + current_user.away + end +end +``` + +When a subscription is initiated the `subscribed` callback gets fired and we +take that opportunity to say "the current user has indeed appeared". That +appear/disappear API could be backed by Redis, a database, or whatever else. + +Create the client-side appearance channel subscription: + +```coffeescript +# app/assets/javascripts/cable/subscriptions/appearance.coffee +App.cable.subscriptions.create "AppearanceChannel", + # Called when the subscription is ready for use on the server. + connected: -> + @install() + @appear() + + # Called when the WebSocket connection is closed. + disconnected: -> + @uninstall() + + # Called when the subscription is rejected by the server. + rejected: -> + @uninstall() + + appear: -> + # Calls `AppearanceChannel#appear(data)` on the server. + @perform("appear", appearing_on: $("main").data("appearing-on")) + + away: -> + # Calls `AppearanceChannel#away` on the server. + @perform("away") + + + buttonSelector = "[data-behavior~=appear_away]" + + install: -> + $(document).on "turbolinks:load.appearance", => + @appear() + + $(document).on "click.appearance", buttonSelector, => + @away() + false + + $(buttonSelector).show() + + uninstall: -> + $(document).off(".appearance") + $(buttonSelector).hide() +``` + +##### Client-Server Interaction + +1. **Client** connects to the **Server** via `App.cable = +ActionCable.createConsumer("ws://cable.example.com")`. (`cable.js`). The +**Server** identifies this connection by `current_user`. + +2. **Client** subscribes to the appearance channel via +`App.cable.subscriptions.create(channel: "AppearanceChannel")`. (`appearance.coffee`) + +3. **Server** recognizes a new subscription has been initiated for the +appearance channel and runs its `subscribed` callback, calling the `appear` +method on `current_user`. (`appearance_channel.rb`) + +4. **Client** recognizes that a subscription has been established and calls +`connected` (`appearance.coffee`) which in turn calls `@install` and `@appear`. +`@appear` calls `AppearanceChannel#appear(data)` on the server, and supplies a +data hash of `{ appearing_on: $("main").data("appearing-on") }`. This is +possible because the server-side channel instance automatically exposes all +public methods declared on the class (minus the callbacks), so that these can be +reached as remote procedure calls via a subscription's `perform` method. + +5. **Server** receives the request for the `appear` action on the appearance +channel for the connection identified by `current_user` +(`appearance_channel.rb`). **Server** retrieves the data with the +`:appearing_on` key from the data hash and sets it as the value for the `:on` +key being passed to `current_user.appear`. + +### Example 2: Receiving New Web Notifications + +The appearance example was all about exposing server functionality to +client-side invocation over the WebSocket connection. But the great thing +about WebSockets is that it's a two-way street. So now let's show an example +where the server invokes an action on the client. + +This is a web notification channel that allows you to trigger client-side +web notifications when you broadcast to the right streams: + +Create the server-side web notifications channel: + +```ruby +# app/channels/web_notifications_channel.rb +class WebNotificationsChannel < ApplicationCable::Channel + def subscribed + stream_for current_user + end +end +``` + +Create the client-side web notifications channel subscription: + +```coffeescript +# app/assets/javascripts/cable/subscriptions/web_notifications.coffee +# Client-side which assumes you've already requested +# the right to send web notifications. +App.cable.subscriptions.create "WebNotificationsChannel", + received: (data) -> + new Notification data["title"], body: data["body"] +``` + +Broadcast content to a web notification channel instance from elsewhere in your +application: + +```ruby +# Somewhere in your app this is called, perhaps from a NewCommentJob +WebNotificationsChannel.broadcast_to( + current_user, + title: 'New things!', + body: 'All the news fit to print' +) +``` + +The `WebNotificationsChannel.broadcast_to` call places a message in the current +subscription adapter'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 +`web_notifications:1` directly to the client by invoking the `received` +callback. The data passed as argument is the hash sent as the second parameter +to the server-side broadcast call, JSON encoded for the trip across the wire, +and unpacked for the data argument arriving to `received`. + +### More Complete Examples + +See the [rails/actioncable-examples](https://github.com/rails/actioncable-examples) +repository for a full example of how to setup Action Cable in a Rails app and adding channels. + +## Configuration + +Action Cable has two required configurations: a subscription adapter and allowed request origins. + +### 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 +[Dependencies](#dependencies) section for additional information on adapters. + +```yaml +development: + adapter: async + +test: + adapter: async + +production: + adapter: redis + url: redis://10.10.3.153:6381 +``` + +### Allowed Request Origins + +Action Cable will only accept requests from specified origins, which are +passed to the server config as an array. The origins can be instances of +strings or regular expressions, against which a check for match will be performed. + +```ruby +config.action_cable.allowed_request_origins = ['http://rubyonrails.com', %r{http://ruby.*}] +``` + +To disable and allow requests from any origin: + +```ruby +config.action_cable.disable_request_forgery_protection = true +``` + +By default, Action Cable allows all requests from localhost:3000 when running +in the development environment. + +### Consumer Configuration + +To configure the URL, add a call to `action_cable_meta_tag` in your HTML layout +HEAD. This uses a URL or path typically set via `config.action_cable.url` in the +environment configuration files. + +### Other Configurations + +The other common option to configure, is the log tags applied to the +per-connection logger. Here's an example that uses +the user account id if available, else "no-account" while tagging: + +```ruby +config.action_cable.log_tags = [ + -> request { request.env['user_account_id'] || "no-account" }, + :action_cable, + -> request { request.uuid } +] +``` + +For a full list of all configuration options, see the +`ActionCable::Server::Configuration` class. + +Also note that your server must provide at least the same number of database +connections as you have workers. The default worker pool size is set to 4, so +that means you have to make at least that available. You can change that in +`config/database.yml` through the `pool` attribute. + +## Running Standalone Cable Servers + +### In App + +Action Cable can run alongside your Rails application. For example, to +listen for WebSocket requests on `/websocket`, specify that path to +`config.action_cable.mount_path`: + +```ruby +# config/application.rb +class Application < Rails::Application + config.action_cable.mount_path = '/websocket' +end +``` + +You can use `App.cable = ActionCable.createConsumer()` to connect to the cable +server if `action_cable_meta_tag` is invoked in the layout. A custom path is +specified as first argument to `createConsumer` (e.g. `App.cable = +ActionCable.createConsumer("/websocket")`). + +For every instance of your server you create and for every worker your server +spawns, you will also have a new instance of Action Cable, but the use of Redis +keeps messages synced across connections. + +### Standalone + +The cable servers can be separated from your normal application server. It's +still a Rack application, but it is its own Rack application. The recommended +basic setup is as follows: + +```ruby +# cable/config.ru +require_relative '../config/environment' +Rails.application.eager_load! + +run ActionCable.server +``` + +Then you start the server using a binstub in `bin/cable` ala: + +``` +#!/bin/bash +bundle exec puma -p 28080 cable/config.ru +``` + +The above will start a cable server on port 28080. + +### Notes + +The WebSocket server doesn't have access to the session, but it has +access to the cookies. This can be used when you need to handle +authentication. You can see one way of doing that with Devise in this [article](http://www.rubytutorial.io/actioncable-devise-authentication). + +## Dependencies + +Action Cable provides a subscription adapter interface to process its +pubsub internals. By default, asynchronous, inline, PostgreSQL, evented +Redis, and non-evented Redis adapters are included. The default adapter +in new Rails applications is the asynchronous (`async`) adapter. + +The Ruby side of things is built on top of [websocket-driver](https://github.com/faye/websocket-driver-ruby), +[nio4r](https://github.com/celluloid/nio4r), and [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby). + +## Deployment + +Action Cable is powered by a combination of WebSockets and threads. Both the +framework plumbing and user-specified channel work are handled internally by +utilizing Ruby's native thread support. This means you can use all your regular +Rails models with no problem, as long as you haven't committed any thread-safety sins. + +The Action Cable server implements the Rack socket hijacking API, +thereby allowing the use of a multithreaded pattern for managing connections +internally, irrespective of whether the application server is multi-threaded or not. + +Accordingly, Action Cable works with popular servers like Unicorn, Puma, and +Passenger. diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 9ef2c1a441..7b1138c7d4 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -21,7 +21,7 @@ After reading this guide, you will know: What Does a Controller Do? -------------------------- -Action Controller is the C in MVC. After routing has determined which controller to use for a request, the controller is responsible for making sense of the request and producing the appropriate output. Luckily, Action Controller does most of the groundwork for you and uses smart conventions to make this as straightforward as possible. +Action Controller is the C in MVC. After the router has determined which controller to use for a request, the controller is responsible for making sense of the request and producing the appropriate output. Luckily, Action Controller does most of the groundwork for you and uses smart conventions to make this as straightforward as possible. For most conventional [RESTful](http://en.wikipedia.org/wiki/Representational_state_transfer) applications, the controller will receive the request (this is invisible to you as the developer), fetch or save data from a model and use a view to create HTML output. If your controller needs to do things a little differently, that's not a problem, this is just the most common way for a controller to work. @@ -145,7 +145,7 @@ So for example, if you are sending this JSON content: Your controller will receive `params[:company]` as `{ "name" => "acme", "address" => "123 Carrot Street" }`. -Also, if you've turned on `config.wrap_parameters` in your initializer or called `wrap_parameters` in your controller, you can safely omit the root element in the JSON parameter. In this case, the parameters will be cloned and wrapped with a key chosen based on your controller's name. So the above JSON POST can be written as: +Also, if you've turned on `config.wrap_parameters` in your initializer or called `wrap_parameters` in your controller, you can safely omit the root element in the JSON parameter. In this case, the parameters will be cloned and wrapped with a key chosen based on your controller's name. So the above JSON request can be written as: ```json { "name": "acme", "address": "123 Carrot Street" } @@ -199,11 +199,12 @@ practice to help prevent accidentally allowing users to update sensitive model attributes. In addition, parameters can be marked as required and will flow through a -predefined raise/rescue flow to end up as a 400 Bad Request. +predefined raise/rescue flow that will result in a 400 Bad Request being +returned if not all required parameters are passed in. ```ruby class PeopleController < ActionController::Base - # This will raise an ActiveModel::ForbiddenAttributes exception + # This will raise an ActiveModel::ForbiddenAttributesError exception # because it's using mass assignment without an explicit permit # step. def create @@ -213,8 +214,8 @@ class PeopleController < ActionController::Base # This will pass with flying colors as long as there's a person key # in the parameters, otherwise it'll raise a # ActionController::ParameterMissing exception, which will get - # caught by ActionController::Base and turned into that 400 Bad - # Request reply. + # caught by ActionController::Base and turned into a 400 Bad + # Request error. def update person = current_account.people.find(params[:id]) person.update!(person_params) @@ -361,7 +362,7 @@ If your user sessions don't store critical data or don't need to be around for l Read more about session storage in the [Security Guide](security.html). -If you need a different session storage mechanism, you can change it in the `config/initializers/session_store.rb` file: +If you need a different session storage mechanism, you can change it in an initializer: ```ruby # Use the database for sessions instead of the cookie-based default, @@ -370,7 +371,7 @@ If you need a different session storage mechanism, you can change it in the `con # Rails.application.config.session_store :active_record_store ``` -Rails sets up a session key (the name of the cookie) when signing the session data. These can also be changed in `config/initializers/session_store.rb`: +Rails sets up a session key (the name of the cookie) when signing the session data. These can also be changed in an initializer: ```ruby # Be sure to restart your server when you modify this file. @@ -700,7 +701,7 @@ class LoginsController < ApplicationController end ``` -Now, the `LoginsController`'s `new` and `create` actions will work as before without requiring the user to be logged in. The `:only` option is used to only skip this filter for these actions, and there is also an `:except` option which works the other way. These options can be used when adding filters too, so you can add a filter which only runs for selected actions in the first place. +Now, the `LoginsController`'s `new` and `create` actions will work as before without requiring the user to be logged in. The `:only` option is used to skip this filter only for these actions, and there is also an `:except` option which works the other way. These options can be used when adding filters too, so you can add a filter which only runs for selected actions in the first place. ### After Filters and Around Filters @@ -814,7 +815,7 @@ In every controller there are two accessor methods pointing to the request and t ### The `request` Object -The request object contains a lot of useful information about the request coming in from the client. To get a full list of the available methods, refer to the [API documentation](http://api.rubyonrails.org/classes/ActionDispatch/Request.html). Among the properties that you can access on this object are: +The request object contains a lot of useful information about the request coming in from the client. To get a full list of the available methods, refer to the [Rails API documentation](http://api.rubyonrails.org/classes/ActionDispatch/Request.html) and [Rack Documentation](http://www.rubydoc.info/github/rack/rack/Rack/Request). Among the properties that you can access on this object are: | Property of `request` | Purpose | | ----------------------------------------- | -------------------------------------------------------------------------------- | @@ -836,7 +837,7 @@ Rails collects all of the parameters sent along with the request in the `params` ### The `response` Object -The response object is not usually used directly, but is built up during the execution of the action and rendering of the data that is being sent back to the user, but sometimes - like in an after filter - it can be useful to access the response directly. Some of these accessor methods also have setters, allowing you to change their values. +The response object is not usually used directly, but is built up during the execution of the action and rendering of the data that is being sent back to the user, but sometimes - like in an after filter - it can be useful to access the response directly. Some of these accessor methods also have setters, allowing you to change their values. To get a full list of the available methods, refer to the [Rails API documentation](http://api.rubyonrails.org/classes/ActionDispatch/Response.html) and [Rack Documentation](http://www.rubydoc.info/github/rack/rack/Rack/Response). | Property of `response` | Purpose | | ---------------------- | --------------------------------------------------------------------------------------------------- | @@ -1088,6 +1089,8 @@ You can filter out sensitive request parameters from your log files by appending config.filter_parameters << :password ``` +NOTE: Provided parameters will be filtered out by partial matching regular expression. Rails adds default `:password` in the appropriate initializer (`initializers/filter_parameter_logging.rb`) and cares about typical application parameters `password` and `password_confirmation`. + ### Redirects Filtering Sometimes it's desirable to filter out from log files some sensitive locations your application is redirecting to. diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index ec175cbdad..34847832fd 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -204,10 +204,14 @@ class UsersController < ApplicationController end ``` -NOTE: Active Job's default behavior is to execute jobs ':inline'. So, you can use -`deliver_later` now to send emails, and when you later decide to start sending -them from a background job, you'll only need to set up Active Job to use a queueing -backend (Sidekiq, Resque, etc). +NOTE: Active Job's default behavior is to execute jobs via the `:async` adapter. So, you can use +`deliver_later` now to send emails asynchronously. +Active Job's default adapter runs jobs with an in-process thread pool. +It's well-suited for the development/test environments, since it doesn't require +any external infrastructure, but it's a poor fit for production since it drops +pending jobs on restart. +If you need a persistent backend, you will need to use an Active Job adapter +that has a persistent backend (Sidekiq, Resque, etc). If you want to send emails right away (from a cronjob for example) just call `deliver_now`: @@ -278,7 +282,7 @@ different, encode your content and pass in the encoded content and encoding in a ```ruby encoded_content = SpecialEncode(File.read('/path/to/filename.jpg')) attachments['filename.jpg'] = { - mime_type: 'application/x-gzip', + mime_type: 'application/gzip', encoding: 'SpecialEncoding', content: encoded_content } @@ -407,6 +411,22 @@ use the rendered text for the text part. The render command is the same one used inside of Action Controller, so you can use all the same options, such as `:text`, `:inline` etc. +#### Caching mailer view + +You can do cache in mailer views like in application views using `cache` method. + +``` +<% cache do %> + <%= @company.name %> +<% end %> +``` + +And in order to use this feature, you need to configure your application with this: + +``` + config.action_mailer.perform_caching = true +``` + ### Action Mailer Layouts Just like controller views, you can also have mailer layouts. The layout name @@ -714,8 +734,8 @@ files (environment.rb, production.rb, etc...) | Configuration | Description | |---------------|-------------| |`logger`|Generates information on the mailing run if available. Can be set to `nil` for no logging. Compatible with both Ruby's own `Logger` and `Log4r` loggers.| -|`smtp_settings`|Allows detailed configuration for `:smtp` delivery method:<ul><li>`:address` - Allows you to use a remote mail server. Just change it from its default `"localhost"` setting.</li><li>`:port` - On the off chance that your mail server doesn't run on port 25, you can change it.</li><li>`:domain` - If you need to specify a HELO domain, you can do it here.</li><li>`:user_name` - If your mail server requires authentication, set the username in this setting.</li><li>`:password` - If your mail server requires authentication, set the password in this setting.</li><li>`:authentication` - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of `:plain` (will send the password in the clear), `:login` (will send password Base64 encoded) or `:cram_md5` (combines a Challenge/Response mechanism to exchange information and a cryptographic Message Digest 5 algorithm to hash important information)</li><li>`:enable_starttls_auto` - Detects if STARTTLS is enabled in your SMTP server and starts to use it. Defaults to `true`.</li><li>`:openssl_verify_mode` - When using TLS, you can set how OpenSSL checks the certificate. This is really useful if you need to validate a self-signed and/or a wildcard certificate. You can use the name of an OpenSSL verify constant ('none', 'peer', 'client_once', 'fail_if_no_peer_cert') or directly the constant (`OpenSSL::SSL::VERIFY_NONE`, `OpenSSL::SSL::VERIFY_PEER`, ...).</li></ul>| -|`sendmail_settings`|Allows you to override options for the `:sendmail` delivery method.<ul><li>`:location` - The location of the sendmail executable. Defaults to `/usr/sbin/sendmail`.</li><li>`:arguments` - The command line arguments to be passed to sendmail. Defaults to `-i -t`.</li></ul>| +|`smtp_settings`|Allows detailed configuration for `:smtp` delivery method:<ul><li>`:address` - Allows you to use a remote mail server. Just change it from its default `"localhost"` setting.</li><li>`:port` - On the off chance that your mail server doesn't run on port 25, you can change it.</li><li>`:domain` - If you need to specify a HELO domain, you can do it here.</li><li>`:user_name` - If your mail server requires authentication, set the username in this setting.</li><li>`:password` - If your mail server requires authentication, set the password in this setting.</li><li>`:authentication` - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of `:plain` (will send the password in the clear), `:login` (will send password Base64 encoded) or `:cram_md5` (combines a Challenge/Response mechanism to exchange information and a cryptographic Message Digest 5 algorithm to hash important information)</li><li>`:enable_starttls_auto` - Detects if STARTTLS is enabled in your SMTP server and starts to use it. Defaults to `true`.</li><li>`:openssl_verify_mode` - When using TLS, you can set how OpenSSL checks the certificate. This is really useful if you need to validate a self-signed and/or a wildcard certificate. You can use the name of an OpenSSL verify constant ('none' or 'peer') or directly the constant (`OpenSSL::SSL::VERIFY_NONE` or `OpenSSL::SSL::VERIFY_PEER`).</li></ul>| +|`sendmail_settings`|Allows you to override options for the `:sendmail` delivery method.<ul><li>`:location` - The location of the sendmail executable. Defaults to `/usr/sbin/sendmail`.</li><li>`:arguments` - The command line arguments to be passed to sendmail. Defaults to `-i`.</li></ul>| |`raise_delivery_errors`|Whether or not errors should be raised if the email fails to be delivered. This only works if the external email server is configured for immediate delivery.| |`delivery_method`|Defines a delivery method. Possible values are:<ul><li>`:smtp` (default), can be configured by using `config.action_mailer.smtp_settings`.</li><li>`:sendmail`, can be configured by using `config.action_mailer.sendmail_settings`.</li><li>`:file`: save emails to files; can be configured by using `config.action_mailer.file_settings`.</li><li>`:test`: save emails to `ActionMailer::Base.deliveries` array.</li></ul>See [API docs](http://api.rubyonrails.org/classes/ActionMailer/Base.html) for more info.| |`perform_deliveries`|Determines whether deliveries are actually carried out when the `deliver` method is invoked on the Mail message. By default they are, but this can be turned off to help functional testing.| @@ -736,7 +756,7 @@ config.action_mailer.delivery_method = :sendmail # Defaults to: # config.action_mailer.sendmail_settings = { # location: '/usr/sbin/sendmail', -# arguments: '-i -t' +# arguments: '-i' # } config.action_mailer.perform_deliveries = true config.action_mailer.raise_delivery_errors = true diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md index 5e6eae1071..ff0127522b 100644 --- a/guides/source/action_view_overview.md +++ b/guides/source/action_view_overview.md @@ -15,7 +15,7 @@ After reading this guide, you will know: What is Action View? -------------------- -In Rails, web requests are handled by [Action Controller](action_controller_overview.html) and Action View. Typically, Action Controller will be concerned with communicating with the database and performing CRUD actions where necessary. Action View is then responsible for compiling the response. +In Rails, web requests are handled by [Action Controller](action_controller_overview.html) and Action View. Typically, Action Controller is concerned with communicating with the database and performing CRUD actions where necessary. Action View is then responsible for compiling the response. Action View templates are written using embedded Ruby in tags mingled with HTML. To avoid cluttering the templates with boilerplate code, a number of helper classes provide common behavior for forms, dates, and strings. It's also easy to add new helpers to your application as it evolves. @@ -173,11 +173,11 @@ would produce: ```json { "name": "Alex", - "email: "alex@example.com" + "email": "alex@example.com" } ``` -See the [Jbuilder documention](https://github.com/rails/jbuilder#jbuilder) for +See the [Jbuilder documentation](https://github.com/rails/jbuilder#jbuilder) for more examples and information. #### Template Caching @@ -254,13 +254,7 @@ as if we had written: <%= render partial: "product", locals: { product: @product } %> ``` -With the `as` option we can specify a different name for the local variable. For example, if we wanted it to be `item` instead of `product` we would do: - -```erb -<%= render partial: "product", as: "item" %> -``` - -The `object` option can be used to directly specify which object is rendered into the partial; useful when the template's object is elsewhere (eg. in a different instance variable or in a local variable). +The `object` option can be used to directly specify which object is rendered into the partial; useful when the template's object is elsewhere (e.g. in a different instance variable or in a local variable). For example, instead of: @@ -274,12 +268,18 @@ we would do: <%= render partial: "product", object: @item %> ``` -The `object` and `as` options can also be used together: +With the `as` option we can specify a different name for the said local variable. For example, if we wanted it to be `item` instead of `product` we would do: ```erb <%= render partial: "product", object: @item, as: "item" %> ``` +This is equivalent to + +```erb +<%= render partial: "product", locals: { item: @item } %> +``` + #### Rendering Collections It is very common that a template will need to iterate over a collection and render a sub-template for each of the elements. This pattern has been implemented as a single method that accepts an array and renders a partial for each one of the elements in the array. @@ -442,7 +442,7 @@ image_path("edit.png") # => /assets/edit-2d1a2db63fc738690021fedb5a65b68e.png #### image_url -Computes the url to an image asset in the `app/assets/images` directory. This will call `image_path` internally and merge with your current host or your asset host. +Computes the URL to an image asset in the `app/assets/images` directory. This will call `image_path` internally and merge with your current host or your asset host. ```ruby image_url("edit.png") # => http://www.example.com/assets/edit.png @@ -493,7 +493,7 @@ javascript_path "common" # => /assets/common.js #### javascript_url -Computes the url to a JavaScript asset in the `app/assets/javascripts` directory. This will call `javascript_path` internally and merge with your current host or your asset host. +Computes the URL to a JavaScript asset in the `app/assets/javascripts` directory. This will call `javascript_path` internally and merge with your current host or your asset host. ```ruby javascript_url "common" # => http://www.example.com/assets/common.js @@ -530,7 +530,7 @@ stylesheet_path "application" # => /assets/application.css #### stylesheet_url -Computes the url to a stylesheet asset in the `app/assets/stylesheets` directory. This will call `stylesheet_path` internally and merge with your current host or your asset host. +Computes the URL to a stylesheet asset in the `app/assets/stylesheets` directory. This will call `stylesheet_path` internally and merge with your current host or your asset host. ```ruby stylesheet_url "application" # => http://www.example.com/assets/application.css @@ -599,7 +599,7 @@ This would add something like "Process data files (0.34523)" to the log, which y #### cache -A method for caching fragments of a view rather than an entire action or page. This technique is useful for caching pieces like menus, lists of news topics, static HTML fragments, and so on. This method takes a block that contains the content you wish to cache. See `ActionController::Caching::Fragments` for more information. +A method for caching fragments of a view rather than an entire action or page. This technique is useful for caching pieces like menus, lists of news topics, static HTML fragments, and so on. This method takes a block that contains the content you wish to cache. See `AbstractController::Caching::Fragments` for more information. ```erb <% cache do %> @@ -1247,7 +1247,7 @@ file_field_tag 'attachment' #### form_tag -Starts a form tag that points the action to a url configured with `url_for_options` just like `ActionController::Base#url_for`. +Starts a form tag that points the action to a URL configured with `url_for_options` just like `ActionController::Base#url_for`. ```html+erb <%= form_tag '/articles' do %> @@ -1419,7 +1419,7 @@ number_to_percentage(100, precision: 0) # => 100% #### number_to_phone -Formats a number into a US phone number. +Formats a number into a phone number (US by default). ```ruby number_to_phone(1235551234) # => 123-555-1234 @@ -1439,7 +1439,7 @@ Formats a number with the specified level of `precision`, which defaults to 3. ```ruby number_with_precision(111.2345) # => 111.235 -number_with_precision(111.2345, 2) # => 111.23 +number_with_precision(111.2345, precision: 2) # => 111.23 ``` ### SanitizeHelper diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md index d8ea1ee079..c65d1e6de5 100644 --- a/guides/source/active_job_basics.md +++ b/guides/source/active_job_basics.md @@ -34,8 +34,9 @@ Delayed Job and Resque. Picking your queuing backend becomes more of an operatio concern, then. And you'll be able to switch between them without having to rewrite your jobs. -NOTE: Rails by default comes with an "immediate runner" queuing implementation. -That means that each job that has been enqueued will run immediately. +NOTE: Rails by default comes with an asynchronous queuing implementation that +runs jobs with an in-process thread pool. Jobs will run asynchronously, but any +jobs in the queue will be dropped upon restart. Creating a Job @@ -62,12 +63,12 @@ $ bin/rails generate job guests_cleanup --queue urgent ``` If you don't want to use a generator, you could create your own file inside of -`app/jobs`, just make sure that it inherits from `ActiveJob::Base`. +`app/jobs`, just make sure that it inherits from `ApplicationJob`. Here's what a job looks like: ```ruby -class GuestsCleanupJob < ActiveJob::Base +class GuestsCleanupJob < ApplicationJob queue_as :default def perform(*guests) @@ -109,7 +110,7 @@ That's it! Job Execution ------------- -For enqueuing and executing jobs in production you need to set up a queuing backend, +For enqueuing and executing jobs in production you need to set up a queuing backend, that is to say you need to decide for a 3rd-party queuing library that Rails should use. Rails itself only provides an in-process queuing system, which only keeps the jobs in RAM. If the process crashes or the machine is reset, then all outstanding jobs are lost with the @@ -138,6 +139,18 @@ module YourApp end ``` +You can also configure your backend on a per job basis. + +```ruby +class GuestsCleanupJob < ApplicationJob + self.queue_adapter = :resque + #.... +end + +# Now your job will use `resque` as it's backend queue adapter overriding what +# was configured in `config.active_job.queue_adapter`. +``` + ### Starting the Backend Since jobs run in parallel to your Rails application, most queuing libraries @@ -159,7 +172,7 @@ Most of the adapters support multiple queues. With Active Job you can schedule the job to run on a specific queue: ```ruby -class GuestsCleanupJob < ActiveJob::Base +class GuestsCleanupJob < ApplicationJob queue_as :low_priority #.... end @@ -177,7 +190,7 @@ module YourApp end # app/jobs/guests_cleanup_job.rb -class GuestsCleanupJob < ActiveJob::Base +class GuestsCleanupJob < ApplicationJob queue_as :low_priority #.... end @@ -200,7 +213,7 @@ module YourApp end # app/jobs/guests_cleanup_job.rb -class GuestsCleanupJob < ActiveJob::Base +class GuestsCleanupJob < ApplicationJob queue_as :low_priority #.... end @@ -222,7 +235,7 @@ block will be executed in the job context (so you can access `self.arguments`) and you must return the queue name: ```ruby -class ProcessVideoJob < ActiveJob::Base +class ProcessVideoJob < ApplicationJob queue_as do video = self.arguments.first if video.owner.premium? @@ -262,7 +275,7 @@ trigger logic during the life cycle of a job. ### Usage ```ruby -class GuestsCleanupJob < ActiveJob::Base +class GuestsCleanupJob < ApplicationJob queue_as :default before_enqueue do |job| @@ -319,7 +332,7 @@ Active Record objects to your job instead of class/id pairs, which you then have to manually deserialize. Before, jobs would look like this: ```ruby -class TrashableCleanupJob < ActiveJob::Base +class TrashableCleanupJob < ApplicationJob def perform(trashable_class, trashable_id, depth) trashable = trashable_class.constantize.find(trashable_id) trashable.cleanup(depth) @@ -330,7 +343,7 @@ end Now you can simply do: ```ruby -class TrashableCleanupJob < ActiveJob::Base +class TrashableCleanupJob < ApplicationJob def perform(trashable, depth) trashable.cleanup(depth) end @@ -348,7 +361,7 @@ Active Job provides a way to catch exceptions raised during the execution of the job: ```ruby -class GuestsCleanupJob < ActiveJob::Base +class GuestsCleanupJob < ApplicationJob queue_as :default rescue_from(ActiveRecord::RecordNotFound) do |exception| diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md index a8199e5d02..732e553c62 100644 --- a/guides/source/active_model_basics.md +++ b/guides/source/active_model_basics.md @@ -13,7 +13,7 @@ After reading this guide, you will know: * How an Active Record model behaves. * How Callbacks and validations work. * How serializers work. -* The Rails internationalization (i18n) framework. +* How Active Model integrates with the Rails internationalization (i18n) framework. -------------------------------------------------------------------------------- @@ -416,7 +416,6 @@ the Active Model API. ```ruby class Person include ActiveModel::Model - end ``` @@ -467,7 +466,7 @@ In order to make this work, the model must have an accessor named `password_dige The `has_secure_password` will add the following validations on the `password` accessor: 1. Password should be present. -2. Password should be equal to its confirmation. +2. Password should be equal to its confirmation (provided +password_confirmation+ is passed along). 3. The maximum length of a password is 72 (required by `bcrypt` on which ActiveModel::SecurePassword depends) #### Examples @@ -493,6 +492,10 @@ person.valid? # => false person.password = person.password_confirmation = 'a' * 100 person.valid? # => false +# When only password is supplied with no password_confirmation. +person.password = 'aditya' +person.valid? # => true + # When all validations are passed. person.password = person.password_confirmation = 'aditya' person.valid? # => true diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index fba89f9d13..6b3aa471f9 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -104,7 +104,7 @@ depending on the purpose of these columns. your models. * **Primary keys** - By default, Active Record will use an integer column named `id` as the table's primary key. When using [Active Record - Migrations](migrations.html) to create your tables, this column will be + Migrations](active_record_migrations.html) to create your tables, this column will be automatically created. There are also some optional column names that will add additional features @@ -361,7 +361,7 @@ class CreatePublications < ActiveRecord::Migration[5.0] t.string :publisher_type t.boolean :single_issue - t.timestamps null: false + t.timestamps end add_index :publications, :publication_type_id end @@ -374,4 +374,4 @@ and to roll it back, `rails db:rollback`. Note that the above code is database-agnostic: it will run in MySQL, PostgreSQL, Oracle and others. You can learn more about migrations in the -[Active Record Migrations guide](migrations.html). +[Active Record Migrations guide](active_record_migrations.html). diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md index fb5d2065d3..2a1c960887 100644 --- a/guides/source/active_record_callbacks.md +++ b/guides/source/active_record_callbacks.md @@ -399,7 +399,7 @@ By using the `after_commit` callback we can account for this case. ```ruby class PictureFile < ApplicationRecord - after_commit :delete_picture_file_from_disk, on: [:destroy] + after_commit :delete_picture_file_from_disk, on: :destroy def delete_picture_file_from_disk if File.exist?(filepath) @@ -409,7 +409,7 @@ class PictureFile < ApplicationRecord end ``` -NOTE: the `:on` option specifies when a callback will be fired. If you +NOTE: The `:on` option specifies when a callback will be fired. If you don't supply the `:on` option the callback will fire for every action. Since using `after_commit` callback only on create, update or delete is @@ -431,4 +431,4 @@ class PictureFile < ApplicationRecord end ``` -WARNING. The `after_commit` and `after_rollback` callbacks are guaranteed to be called for all models created, updated, or destroyed within a transaction block. If any exceptions are raised within one of these callbacks, they will be ignored so that they don't interfere with the other callbacks. As such, if your callback code could raise an exception, you'll need to rescue it and handle it appropriately within the callback. +WARNING. The `after_commit` and `after_rollback` callbacks are called for all models created, updated, or destroyed within a transaction block. However, if an exception is raised within one of these callbacks, the exception will bubble up and any remaining `after_commit` or `after_rollback` methods will _not_ be executed. As such, if your callback code could raise an exception, you'll need to rescue it and handle it within the callback in order to allow other callbacks to run. diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md index bd7dbd0f11..d91c9bd606 100644 --- a/guides/source/active_record_migrations.md +++ b/guides/source/active_record_migrations.md @@ -41,7 +41,7 @@ class CreateProducts < ActiveRecord::Migration[5.0] t.string :name t.text :description - t.timestamps null: false + t.timestamps end end end @@ -241,12 +241,13 @@ generates ```ruby class AddUserRefToProducts < ActiveRecord::Migration[5.0] def change - add_reference :products, :user, index: true, foreign_key: true + add_reference :products, :user, foreign_key: true end end ``` This migration will create a `user_id` column and appropriate index. +For more `add_reference` options, visit the [API documentation](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_reference). There is also a generator which will produce join tables if `JoinTable` is part of the name: @@ -287,7 +288,7 @@ class CreateProducts < ActiveRecord::Migration[5.0] t.string :name t.text :description - t.timestamps null: false + t.timestamps end end end @@ -312,7 +313,7 @@ will produce a migration that looks like this class AddDetailsToProducts < ActiveRecord::Migration[5.0] def change add_column :products, :price, :decimal, precision: 5, scale: 2 - add_reference :products, :supplier, polymorphic: true, index: true + add_reference :products, :supplier, polymorphic: true end end ``` @@ -353,7 +354,14 @@ end ``` will append `ENGINE=BLACKHOLE` to the SQL statement used to create the table -(when using MySQL, the default is `ENGINE=InnoDB`). +(when using MySQL or MariaDB, the default is `ENGINE=InnoDB`). + +Also you can pass the `:comment` option with any description for the table +that will be stored in database itself and can be viewed with database administration +tools, such as MySQL Workbench or PgAdmin III. It's highly recommended to specify +comments in migrations for applications with large databases as it helps people +to understand data model and generate documentation. +Currently only the MySQL and PostgreSQL adapters support comments. ### Creating a Join Table @@ -454,10 +462,13 @@ number of digits after the decimal point. are using a dynamic value (such as a date), the default will only be calculated the first time (i.e. on the date the migration is applied). * `index` Adds an index for the column. +* `comment` Adds a comment for the column. Some adapters may support additional options; see the adapter specific API docs for further information. +NOTE: `null` and `default` cannot be specified via command line. + ### Foreign Keys While it's not required you might want to add foreign key constraints to @@ -847,7 +858,7 @@ class CreateProducts < ActiveRecord::Migration[5.0] create_table :products do |t| t.string :name t.text :description - t.timestamps null: false + t.timestamps end end @@ -970,7 +981,7 @@ this, then you should set the schema format to `:sql`. Instead of using Active Record's schema dumper, the database's structure will be dumped using a tool specific to the database (via the `db:structure:dump` rails task) into `db/structure.sql`. For example, for PostgreSQL, the `pg_dump` -utility is used. For MySQL, this file will contain the output of +utility is used. For MySQL and MariaDB, this file will contain the output of `SHOW CREATE TABLE` for the various tables. Loading these schemas is simply a question of executing the SQL statements they @@ -1009,10 +1020,10 @@ such features, the `execute` method can be used to execute arbitrary SQL. Migrations and Seed Data ------------------------ -The main purpose of Rails' migration feature is to issue commands that modify the -schema using a consistent process. Migrations can also be used -to add or modify data. This is useful in an existing database that can't be destroyed -and recreated, such as a production database. +The main purpose of Rails' migration feature is to issue commands that modify the +schema using a consistent process. Migrations can also be used +to add or modify data. This is useful in an existing database that can't be destroyed +and recreated, such as a production database. ```ruby class AddInitialProducts < ActiveRecord::Migration[5.0] @@ -1028,10 +1039,10 @@ class AddInitialProducts < ActiveRecord::Migration[5.0] end ``` -To add initial data after a database is created, Rails has a built-in -'seeds' feature that makes the process quick and easy. This is especially -useful when reloading the database frequently in development and test environments. -It's easy to get started with this feature: just fill up `db/seeds.rb` with some +To add initial data after a database is created, Rails has a built-in +'seeds' feature that makes the process quick and easy. This is especially +useful when reloading the database frequently in development and test environments. +It's easy to get started with this feature: just fill up `db/seeds.rb` with some Ruby code, and run `rails db:seed`: ```ruby diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md index 5eb19f5214..d7e35490ef 100644 --- a/guides/source/active_record_postgresql.md +++ b/guides/source/active_record_postgresql.md @@ -435,7 +435,7 @@ create_table :documents do |t| t.string 'body' end -execute "CREATE INDEX documents_idx ON documents USING gin(to_tsvector('english', title || ' ' || body));" +add_index :documents, "to_tsvector('english', title || ' ' || body)", using: :gin, name: 'documents_idx' # app/models/document.rb class Document < ApplicationRecord @@ -503,9 +503,9 @@ second = Article.create! title: "Brace yourself", status: "draft", published_at: 1.month.ago -Article.count # => 1 -first.archive! Article.count # => 2 +first.archive! +Article.count # => 1 ``` NOTE: This application only cares about non-archived `Articles`. A view also diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 1235c04c50..38b1ffc4c8 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -11,7 +11,7 @@ After reading this guide, you will know: * How to specify the order, retrieved attributes, grouping, and other properties of the found records. * How to use eager loading to reduce the number of database queries needed for data retrieval. * How to use dynamic finder methods. -* How to use method chaining to use multiple ActiveRecord methods together. +* How to use method chaining to use multiple Active Record methods together. * How to check for the existence of particular records. * How to perform various calculations on Active Record models. * How to run EXPLAIN on relations. @@ -50,7 +50,7 @@ class Role < ApplicationRecord end ``` -Active Record will perform queries on the database for you and is compatible with most database systems (MySQL, PostgreSQL and SQLite to name a few). Regardless of which database system you're using, the Active Record method format will always be the same. +Active Record will perform queries on the database for you and is compatible with most database systems, including MySQL, MariaDB, PostgreSQL, and SQLite. Regardless of which database system you're using, the Active Record method format will always be the same. Retrieving Objects from the Database ------------------------------------ @@ -81,10 +81,9 @@ The methods are: * `reorder` * `reverse_order` * `select` -* `distinct` * `where` -All of the above methods return an instance of `ActiveRecord::Relation`. +Finder methods that return a collection, such as `where` and `group`, return an instance of `ActiveRecord::Relation`. Methods that find a single entity, such as `find` and `first`, return a single instance of the model. The primary operation of `Model.find(options)` can be summarized as: @@ -153,9 +152,9 @@ You can pass in a numerical argument to the `take` method to return up to that n ```ruby client = Client.take(2) # => [ - #<Client id: 1, first_name: "Lifo">, - #<Client id: 220, first_name: "Sara"> -] +# #<Client id: 1, first_name: "Lifo">, +# #<Client id: 220, first_name: "Sara"> +# ] ``` The SQL equivalent of the above is: @@ -192,10 +191,10 @@ You can pass in a numerical argument to the `first` method to return up to that ```ruby client = Client.first(3) # => [ - #<Client id: 1, first_name: "Lifo">, - #<Client id: 2, first_name: "Fifo">, - #<Client id: 3, first_name: "Filo"> -] +# #<Client id: 1, first_name: "Lifo">, +# #<Client id: 2, first_name: "Fifo">, +# #<Client id: 3, first_name: "Filo"> +# ] ``` The SQL equivalent of the above is: @@ -204,7 +203,7 @@ The SQL equivalent of the above is: SELECT * FROM clients ORDER BY clients.id ASC LIMIT 3 ``` -On a collection that is ordered using `order`, `first` will return the first record ordered by the specified attribute for `order`. +On a collection that is ordered using `order`, `first` will return the first record ordered by the specified attribute for `order`. ```ruby client = Client.order(:first_name).first @@ -243,10 +242,10 @@ You can pass in a numerical argument to the `last` method to return up to that n ```ruby client = Client.last(3) # => [ - #<Client id: 219, first_name: "James">, - #<Client id: 220, first_name: "Sara">, - #<Client id: 221, first_name: "Russel"> -] +# #<Client id: 219, first_name: "James">, +# #<Client id: 220, first_name: "Sara">, +# #<Client id: 221, first_name: "Russel"> +# ] ``` The SQL equivalent of the above is: @@ -255,7 +254,7 @@ The SQL equivalent of the above is: SELECT * FROM clients ORDER BY clients.id DESC LIMIT 3 ``` -On a collection that is ordered using `order`, `last` will return the last record ordered by the specified attribute for `order`. +On a collection that is ordered using `order`, `last` will return the last record ordered by the specified attribute for `order`. ```ruby client = Client.order(:first_name).last @@ -314,7 +313,7 @@ We often need to iterate over a large set of records, as when we send a newslett This may appear straightforward: ```ruby -# This is very inefficient when the users table has thousands of rows. +# This may consume too much memory if the table is big. User.all.each do |user| NewsMailer.weekly(user).deliver_now end @@ -328,7 +327,7 @@ TIP: The `find_each` and `find_in_batches` methods are intended for use in the b #### `find_each` -The `find_each` method retrieves a batch of records and then yields _each_ record to the block individually as a model. In the following example, `find_each` will retrieve 1000 records (the current default for both `find_each` and `find_in_batches`) and then yield each record individually to the block as a model. This process is repeated until all of the records have been processed: +The `find_each` method retrieves records in batches and then yields _each_ one to the block. In the following example, `find_each` retrieves users in batches of 1000 and yields them to the block one by one: ```ruby User.find_each do |user| @@ -336,7 +335,9 @@ User.find_each do |user| end ``` -To add conditions to a `find_each` operation you can chain other Active Record methods such as `where`: +This process is repeated, fetching more batches as needed, until all of the records have been processed. + +`find_each` works on model classes, as seen above, and also on relations: ```ruby User.where(weekly_subscriber: true).find_each do |user| @@ -344,11 +345,16 @@ User.where(weekly_subscriber: true).find_each do |user| end ``` -##### Options for `find_each` +as long as they have no ordering, since the method needs to force an order +internally to iterate. -The `find_each` method accepts most of the options allowed by the regular `find` method, except for `:order` and `:limit`, which are reserved for internal use by `find_each`. +If an order is present in the receiver the behaviour depends on the flag +`config.active_record.error_on_ignored_order`. If true, `ArgumentError` is +raised, otherwise the order is ignored and a warning issued, which is the +default. This can be overridden with the option `:error_on_ignore`, explained +below. -Three additional options, `:batch_size`, `:start` and `:finish`, are available as well. +##### Options for `find_each` **`:batch_size`** @@ -364,10 +370,10 @@ end By default, records are fetched in ascending order of the primary key, which must be an integer. The `:start` option allows you to configure the first ID of the sequence whenever the lowest ID is not the one you need. This would be useful, for example, if you wanted to resume an interrupted batch process, provided you saved the last processed ID as a checkpoint. -For example, to send newsletters only to users with the primary key starting from 2000, and to retrieve them in batches of 5000: +For example, to send newsletters only to users with the primary key starting from 2000: ```ruby -User.find_each(start: 2000, batch_size: 5000) do |user| +User.find_each(start: 2000) do |user| NewsMailer.weekly(user).deliver_now end ``` @@ -375,12 +381,12 @@ end **`:finish`** Similar to the `:start` option, `:finish` allows you to configure the last ID of the sequence whenever the highest ID is not the one you need. -This would be useful, for example, if you wanted to run a batch process, using a subset of records based on `:start` and `:finish` +This would be useful, for example, if you wanted to run a batch process using a subset of records based on `:start` and `:finish`. -For example, to send newsletters only to users with the primary key starting from 2000 up to 10000 and to retrieve them in batches of 5000: +For example, to send newsletters only to users with the primary key starting from 2000 up to 10000: ```ruby -User.find_each(start: 2000, finish: 10000, batch_size: 5000) do |user| +User.find_each(start: 2000, finish: 10000) do |user| NewsMailer.weekly(user).deliver_now end ``` @@ -389,20 +395,36 @@ Another example would be if you wanted multiple workers handling the same processing queue. You could have each worker handle 10000 records by setting the appropriate `:start` and `:finish` options on each worker. +**`:error_on_ignore`** + +Overrides the application config to specify if an error should be raised when an +order is present in the relation. + #### `find_in_batches` The `find_in_batches` method is similar to `find_each`, since both retrieve batches of records. The difference is that `find_in_batches` yields _batches_ to the block as an array of models, instead of individually. The following example will yield to the supplied block an array of up to 1000 invoices at a time, with the final block containing any remaining invoices: ```ruby -# Give add_invoices an array of 1000 invoices at a time +# Give add_invoices an array of 1000 invoices at a time. Invoice.find_in_batches do |invoices| export.add_invoices(invoices) end ``` +`find_in_batches` works on model classes, as seen above, and also on relations: + +```ruby +Invoice.pending.find_in_batches do |invoice| + pending_invoices_export.add_invoices(invoices) +end +``` + +as long as they have no ordering, since the method needs to force an order +internally to iterate. + ##### Options for `find_in_batches` -The `find_in_batches` method accepts the same `:batch_size`, `:start` and `:finish` options as `find_each`. +The `find_in_batches` method accepts the same options as `find_each`. Conditions ---------- @@ -472,6 +494,12 @@ NOTE: Only equality, range and subset checking are possible with Hash conditions Client.where(locked: true) ``` +This will generate SQL like this: + +```sql +SELECT * FROM clients WHERE (clients.locked = 1) +``` + The field name can also be a string: ```ruby @@ -517,13 +545,17 @@ SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5)) ### NOT Conditions -`NOT` SQL queries can be built by `where.not`. +`NOT` SQL queries can be built by `where.not`: ```ruby -Article.where.not(author: author) +Client.where.not(locked: true) ``` -In other words, this query can be generated by calling `where` with no argument, then immediately chain with `not` passing `where` conditions. +In other words, this query can be generated by calling `where` with no argument, then immediately chain with `not` passing `where` conditions. This will generate SQL like this: + +```sql +SELECT * FROM clients WHERE (clients.locked != 1) +``` Ordering -------- @@ -568,6 +600,7 @@ If you want to call `order` multiple times, subsequent orders will be appended t Client.order("orders_count ASC").order("created_at DESC") # SELECT * FROM clients ORDER BY orders_count ASC, created_at DESC ``` +WARNING: If you are using **MySQL 5.7.5** and above, then on selecting fields from a result set using methods like `select`, `pluck` and `ids`; the `order` method will raise an `ActiveRecord::StatementInvalid` exception unless the field(s) used in `order` clause are included in the select list. See the next section for selecting fields from the result set. Selecting Specific Fields ------------------------- @@ -980,13 +1013,13 @@ There are multiple ways to use the `joins` method. You can just supply the raw SQL specifying the `JOIN` clause to `joins`: ```ruby -Author.joins("INNER JOIN posts ON posts.author_id = author.id AND posts.published = 't'") +Author.joins("INNER JOIN posts ON posts.author_id = authors.id AND posts.published = 't'") ``` This will result in the following SQL: ```sql -SELECT clients.* FROM clients INNER JOIN posts ON posts.author_id = author.id AND posts.published = 't' +SELECT authors.* FROM authors INNER JOIN posts ON posts.author_id = authors.id AND posts.published = 't' ``` #### Using Array/Hash of Named Associations @@ -1111,7 +1144,7 @@ If you want to select a set of records whether or not they have associated records you can use the `left_outer_joins` method. ```ruby -Author.left_outer_joins(:posts).uniq.select('authors.*, COUNT(posts.*) AS posts_count').group('authors.id') +Author.left_outer_joins(:posts).distinct.select('authors.*, COUNT(posts.*) AS posts_count').group('authors.id') ``` Which produces: @@ -1217,7 +1250,8 @@ articles, all the articles would still be loaded. By using `joins` (an INNER JOIN), the join conditions **must** match, otherwise no records will be returned. - +NOTE: If an association is eager loaded as part of a join, any fields from a custom select clause will not present be on the loaded models. +This is because it is ambiguous whether they should appear on the parent record, or the child. Scopes ------ @@ -1613,7 +1647,7 @@ now want the client named 'Nick': ```ruby nick = Client.find_or_initialize_by(first_name: 'Nick') -# => <Client id: nil, first_name: "Nick", orders_count: 0, locked: true, created_at: "2011-08-30 06:09:27", updated_at: "2011-08-30 06:09:27"> +# => #<Client id: nil, first_name: "Nick", orders_count: 0, locked: true, created_at: "2011-08-30 06:09:27", updated_at: "2011-08-30 06:09:27"> nick.persisted? # => false @@ -1645,10 +1679,10 @@ Client.find_by_sql("SELECT * FROM clients INNER JOIN orders ON clients.id = orders.client_id ORDER BY clients.created_at desc") # => [ - #<Client id: 1, first_name: "Lucas" >, - #<Client id: 2, first_name: "Jan" >, - # ... -] +# #<Client id: 1, first_name: "Lucas" >, +# #<Client id: 2, first_name: "Jan" >, +# ... +# ] ``` `find_by_sql` provides you with a simple way of making custom calls to the database and retrieving instantiated objects. @@ -1660,9 +1694,9 @@ Client.find_by_sql("SELECT * FROM clients ```ruby Client.connection.select_all("SELECT first_name, created_at FROM clients WHERE id = '1'") # => [ - {"first_name"=>"Rafael", "created_at"=>"2012-11-10 23:23:45.281189"}, - {"first_name"=>"Eileen", "created_at"=>"2013-12-09 11:22:35.221282"} -] +# {"first_name"=>"Rafael", "created_at"=>"2012-11-10 23:23:45.281189"}, +# {"first_name"=>"Eileen", "created_at"=>"2013-12-09 11:22:35.221282"} +# ] ``` ### `pluck` @@ -1915,7 +1949,7 @@ EXPLAIN for: SELECT `users`.* FROM `users` INNER JOIN `articles` ON `articles`.` 2 rows in set (0.00 sec) ``` -under MySQL. +under MySQL and MariaDB. Active Record performs a pretty printing that emulates that of the corresponding database shell. So, the same query running with the @@ -1975,7 +2009,7 @@ EXPLAIN for: SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` IN 1 row in set (0.00 sec) ``` -under MySQL. +under MySQL and MariaDB. ### Interpreting EXPLAIN @@ -1986,4 +2020,6 @@ following pointers may be helpful: * MySQL: [EXPLAIN Output Format](http://dev.mysql.com/doc/refman/5.7/en/explain-output.html) +* MariaDB: [EXPLAIN](https://mariadb.com/kb/en/mariadb/explain/) + * PostgreSQL: [Using EXPLAIN](http://www.postgresql.org/docs/current/static/using-explain.html) diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index dd7adf09a2..665e97c470 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -149,7 +149,7 @@ false` as an argument. This technique should be used with caution. ### `valid?` and `invalid?` -Before saving an ActiveRecord object, Rails runs your validations. +Before saving an Active Record object, Rails runs your validations. If these validations produce any errors, Rails does not save the object. You can also run these validations on your own. `valid?` triggers your validations @@ -278,12 +278,6 @@ form was submitted. This is typically used when the user needs to agree to your application's terms of service, confirm that some text is read, or any similar concept. -This validation is very specific to web applications and this -'acceptance' does not need to be recorded anywhere in your database. If you -don't have a field for it, the helper will just create a virtual attribute. If -the field does exist in your database, the `accept` option must be set to -`true` or else the validation will not run. - ```ruby class Person < ApplicationRecord validates :terms_of_service, acceptance: true @@ -292,16 +286,31 @@ end This check is performed only if `terms_of_service` is not `nil`. The default error message for this helper is _"must be accepted"_. +You can also pass custom message via the `message` option. -It can receive an `:accept` option, which determines the value that will be -considered acceptance. It defaults to "1" and can be easily changed. +```ruby +class Person < ApplicationRecord + validates :terms_of_service, acceptance: { message: 'must be abided' } +end +``` + +It can also receive an `:accept` option, which determines the allowed values +that will be considered as accepted. It defaults to `['1', true]` and can be +easily changed. ```ruby class Person < ApplicationRecord validates :terms_of_service, acceptance: { accept: 'yes' } + validates :eula, acceptance: { accept: ['TRUE', 'accepted'] } end ``` +This validation is very specific to web applications and this +'acceptance' does not need to be recorded anywhere in your database. If you +don't have a field for it, the helper will just create a virtual attribute. If +the field does exist in your database, the `accept` option must be set to +or include `true` or else the validation will not run. + ### `validates_associated` You should use this helper when your model has associations with other models @@ -383,7 +392,8 @@ The `exclusion` helper has an option `:in` that receives the set of values that will not be accepted for the validated attributes. The `:in` option has an alias called `:within` that you can use for the same purpose, if you'd like to. This example uses the `:message` option to show how you can include the -attribute's value. +attribute's value. For full options to the message argument please see the +[message documentation](#message). The default error message is _"is reserved"_. @@ -418,7 +428,8 @@ end The `inclusion` helper has an option `:in` that receives the set of values that will be accepted. The `:in` option has an alias called `:within` that you can use for the same purpose, if you'd like to. The previous example uses the -`:message` option to show how you can include the attribute's value. +`:message` option to show how you can include the attribute's value. For full +options please see the [message documentation](#message). The default error message for this helper is _"is not included in the list"_. @@ -505,6 +516,8 @@ constraints to acceptable values: * `:less_than_or_equal_to` - Specifies the value must be less than or equal to the supplied value. The default error message for this option is _"must be less than or equal to %{count}"_. +* `:other_than` - Specifies the value must be other than the supplied value. + The default error message for this option is _"must be other than %{count}"_. * `:odd` - Specifies the value must be an odd number if set to true. The default error message for this option is _"must be odd"_. * `:even` - Specifies the value must be an even number if set to true. The @@ -757,6 +770,9 @@ class Coffee < ApplicationRecord end ``` +For full options to the message argument please see the +[message documentation](#message). + ### `:allow_blank` The `:allow_blank` option is similar to the `:allow_nil` option. This option @@ -781,9 +797,10 @@ for each validation helper. The `:message` option accepts a `String` or `Proc`. A `String` `:message` value can optionally contain any/all of `%{value}`, `%{attribute}`, and `%{model}` which will be dynamically replaced when -validation fails. +validation fails. This replacement is done using the I18n gem, and the +placeholders must match exactly, no spaces are allowed. -A `Proc` `:message` value is given two arguments: a message key for i18n, and +A `Proc` `:message` value is given two arguments: the object being validated, and a hash with `:model`, `:attribute`, and `:value` key-value pairs. ```ruby @@ -799,10 +816,10 @@ class Person < ApplicationRecord # Proc validates :username, uniqueness: { - # key = "activerecord.errors.models.person.attributes.username.taken" + # object = person object being validated # data = { model: "Person", attribute: "Username", value: <username> } - message: ->(key, data) do - "#{data[:value]} taken! Try again #{Time.zone.tomorrow}" + message: ->(object, data) do + "Hey #{object.name}!, #{data[:value]} is taken already! Try again #{Time.zone.tomorrow}" end } end @@ -830,6 +847,25 @@ class Person < ApplicationRecord end ``` +You can also use `on:` to define custom context. +Custom contexts need to be triggered explicitly +by passing name of the context to `valid?`, `invalid?` or `save`. + +```ruby +class Person < ApplicationRecord + validates :email, uniqueness: true, on: :account_setup + validates :age, numericality: true, on: :account_setup +end + +person = Person.new +``` + +`person.valid?(:account_setup)` executes both the validations +without saving the model. And `person.save(context: :account_setup)` +validates `person` in `account_setup` context before saving. +On explicit triggers, model is validated by +validations of only that context and validations without context. + Strict Validations ------------------ @@ -1196,9 +1232,9 @@ person.errors[:name] person.errors.clear person.errors.empty? # => true -p.save # => false +person.save # => false -p.errors[:name] +person.errors[:name] # => ["can't be blank", "is too short (minimum is 3 characters)"] ``` diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 10122629b2..70b04a9695 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -252,7 +252,7 @@ Note that `try` will swallow no-method errors, returning nil instead. If you wan ```ruby @number.try(:nest) # => nil -@number.try!(:nest) # NoMethodError: undefined method `nest' for 1:Fixnum +@number.try!(:nest) # NoMethodError: undefined method `nest' for 1:Integer ``` NOTE: Defined in `active_support/core_ext/object/try.rb`. @@ -368,7 +368,7 @@ account.to_query('company[name]') so its output is ready to be used in a query string. -Arrays return the result of applying `to_query` to each element with `_key_[]` as key, and join the result with "&": +Arrays return the result of applying `to_query` to each element with `key[]` as key, and join the result with "&": ```ruby [3.4, -45.6].to_query('sample') @@ -632,8 +632,6 @@ module ActiveSupport mattr_accessor :load_once_paths mattr_accessor :autoloaded_constants mattr_accessor :explicitly_unloadable_constants - mattr_accessor :logger - mattr_accessor :log_activity mattr_accessor :constant_watch_stack mattr_accessor :constant_watch_stack_mutex end @@ -709,87 +707,6 @@ M.parents # => [X::Y, X, Object] NOTE: Defined in `active_support/core_ext/module/introspection.rb`. -### Constants - -The method `local_constants` returns the names of the constants that have been -defined in the receiver module: - -```ruby -module X - X1 = 1 - X2 = 2 - module Y - Y1 = :y1 - X1 = :overrides_X1_above - end -end - -X.local_constants # => [:X1, :X2, :Y] -X::Y.local_constants # => [:Y1, :X1] -``` - -The names are returned as symbols. - -NOTE: Defined in `active_support/core_ext/module/introspection.rb`. - -#### Qualified Constant Names - -The standard methods `const_defined?`, `const_get`, and `const_set` accept -bare constant names. Active Support extends this API to be able to pass -relative qualified constant names. - -The new methods are `qualified_const_defined?`, `qualified_const_get`, and -`qualified_const_set`. Their arguments are assumed to be qualified constant -names relative to their receiver: - -```ruby -Object.qualified_const_defined?("Math::PI") # => true -Object.qualified_const_get("Math::PI") # => 3.141592653589793 -Object.qualified_const_set("Math::Phi", 1.618034) # => 1.618034 -``` - -Arguments may be bare constant names: - -```ruby -Math.qualified_const_get("E") # => 2.718281828459045 -``` - -These methods are analogous to their built-in counterparts. In particular, -`qualified_constant_defined?` accepts an optional second argument to be -able to say whether you want the predicate to look in the ancestors. -This flag is taken into account for each constant in the expression while -walking down the path. - -For example, given - -```ruby -module M - X = 1 -end - -module N - class C - include M - end -end -``` - -`qualified_const_defined?` behaves this way: - -```ruby -N.qualified_const_defined?("C::X", false) # => false -N.qualified_const_defined?("C::X", true) # => true -N.qualified_const_defined?("C::X") # => true -``` - -As the last example implies, the second argument defaults to true, -as in `const_defined?`. - -For coherence with the built-in methods only relative paths are accepted. -Absolute qualified constant names like `::Math::PI` raise `NameError`. - -NOTE: Defined in `active_support/core_ext/module/qualified_const.rb`. - ### Reachable A named module is reachable if it is stored in its corresponding constant. It means you can reach the module object via the constant. @@ -1037,7 +954,8 @@ class A class_attribute :x, instance_reader: false end -A.new.x = 1 # NoMethodError +A.new.x = 1 +A.new.x # NoMethodError ``` For convenience `class_attribute` also defines an instance predicate which is the double negation of what the instance reader returns. In the examples above it would be called `x?`. @@ -1686,19 +1604,6 @@ Given a string with a qualified constant reference expression, `deconstantize` r "Admin::Hotel::ReservationUtils".deconstantize # => "Admin::Hotel" ``` -Active Support for example uses this method in `Module#qualified_const_set`: - -```ruby -def qualified_const_set(path, value) - QualifiedConstUtils.raise_if_absolute(path) - - const_name = path.demodulize - mod_name = path.deconstantize - mod = mod_name.empty? ? self : qualified_const_get(mod_name) - mod.const_set(const_name, value) -end -``` - NOTE: Defined in `active_support/core_ext/string/inflections.rb`. #### `parameterize` @@ -1767,7 +1672,7 @@ NOTE: Defined in `active_support/core_ext/string/inflections.rb`. The method `constantize` resolves the constant reference expression in its receiver: ```ruby -"Fixnum".constantize # => Fixnum +"Integer".constantize # => Integer module M X = 1 @@ -2131,7 +2036,7 @@ Addition only assumes the elements respond to `+`: ```ruby [[1, 2], [2, 3], [3, 4]].sum # => [1, 2, 2, 3, 3, 4] %w(foo bar baz).sum # => "foobarbaz" -{a: 1, b: 2, c: 3}.sum # => [:b, 2, :c, 3, :a, 1] +{a: 1, b: 2, c: 3}.sum # => [:b, 2, :c, 3, :a, 1] ``` The sum of an empty collection is zero by default, but this is customizable: @@ -2374,7 +2279,7 @@ Contributor.limit(2).order(:rank).to_xml To do so it sends `to_xml` to every item in turn, and collects the results under a root node. All items must respond to `to_xml`, an exception is raised otherwise. -By default, the name of the root element is the underscorized and dasherized plural of the name of the class of the first item, provided the rest of elements belong to that type (checked with `is_a?`) and they are not hashes. In the example above that's "contributors". +By default, the name of the root element is the underscored and dasherized plural of the name of the class of the first item, provided the rest of elements belong to that type (checked with `is_a?`) and they are not hashes. In the example above that's "contributors". If there's any element that does not belong to the type of the first one the root node becomes "objects": @@ -2636,8 +2541,7 @@ To do so, the method loops over the pairs and builds nodes that depend on the _v ```ruby XML_TYPE_NAMES = { "Symbol" => "symbol", - "Fixnum" => "integer", - "Bignum" => "integer", + "Integer" => "integer", "BigDecimal" => "decimal", "Float" => "float", "TrueClass" => "boolean", @@ -2757,7 +2661,7 @@ The method `transform_keys` accepts a block and returns a hash that has applied ```ruby {nil => nil, 1 => 1, a: :a}.transform_keys { |key| key.to_s.upcase } -# => {"" => nil, "A" => :a, "1" => 1} +# => {"" => nil, "1" => 1, "A" => :a} ``` In case of key collision, one of the values will be chosen. The chosen value may not always be the same given the same hash: @@ -2799,7 +2703,7 @@ The method `stringify_keys` returns a hash that has a stringified version of the ```ruby {nil => nil, 1 => 1, a: :a}.stringify_keys -# => {"" => nil, "a" => :a, "1" => 1} +# => {"" => nil, "1" => 1, "a" => :a} ``` In case of key collision, one of the values will be chosen. The chosen value may not always be the same given the same hash: @@ -2841,7 +2745,7 @@ The method `symbolize_keys` returns a hash that has a symbolized version of the ```ruby {nil => nil, 1 => 1, "a" => "a"}.symbolize_keys -# => {1=>1, nil=>nil, :a=>"a"} +# => {nil=>nil, 1=>1, :a=>"a"} ``` WARNING. Note in the previous example only one key was symbolized. @@ -2918,7 +2822,7 @@ Ruby has built-in support for taking slices out of strings and arrays. Active Su ```ruby {a: 1, b: 2, c: 3}.slice(:a, :c) -# => {:c=>3, :a=>1} +# => {:a=>1, :c=>3} {a: 1, b: 2, c: 3}.slice(:b, :X) # => {:b=>2} # non-existing keys are ignored @@ -3012,6 +2916,24 @@ end NOTE: Defined in `active_support/core_ext/regexp.rb`. +### `match?` + +Rails implements `Regexp#match?` for Ruby versions prior to 2.4: + +```ruby +/oo/.match?('foo') # => true +/oo/.match?('bar') # => false +/oo/.match?('foo', 1) # => true +``` + +The backport has the same interface and lack of side-effects in the caller like +not setting `$1` and friends, but it does not have the speed benefits. Its +purpose is to be able to write 2.4 compatible code. Rails itself uses this +predicate internally for example. + +Active Support defines `Regexp#match?` only if not present, so code running +under 2.4 or later does run the original one and gets the performance boost. + Extensions to `Range` --------------------- @@ -3078,7 +3000,7 @@ INFO: The following calculation methods have edge cases in October 1582, since d #### `Date.current` -Active Support defines `Date.current` to be today in the current time zone. That's like `Date.today`, except that it honors the user time zone, if defined. It also defines `Date.yesterday` and `Date.tomorrow`, and the instance predicates `past?`, `today?`, and `future?`, all of them relative to `Date.current`. +Active Support defines `Date.current` to be today in the current time zone. That's like `Date.today`, except that it honors the user time zone, if defined. It also defines `Date.yesterday` and `Date.tomorrow`, and the instance predicates `past?`, `today?`, `future?`, `on_weekday?` and `on_weekend?`, all of them relative to `Date.current`. When making Date comparisons using methods which honor the user time zone, make sure to use `Date.current` and not `Date.today`. There are cases where the user time zone might be in the future compared to the system time zone, which `Date.today` uses by default. This means `Date.today` may equal `Date.yesterday`. @@ -3467,6 +3389,8 @@ years_ago years_since prev_year (last_year) next_year +on_weekday? +on_weekend? ``` The following methods are reimplemented so you do **not** need to load `active_support/core_ext/date/calculations.rb` for these ones: @@ -3653,6 +3577,8 @@ years_ago years_since prev_year (last_year) next_year +on_weekday? +on_weekend? ``` They are analogous. Please refer to their documentation above and take into account the following differences: diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md index 0fd0112c9f..3fc9d9bfa9 100644 --- a/guides/source/active_support_instrumentation.md +++ b/guides/source/active_support_instrumentation.md @@ -112,6 +112,7 @@ Action Controller | `:controller` | The controller name | | `:action` | The action | | `:params` | Hash of request parameters without any filtered parameter | +| `:headers` | Request headers | | `:format` | html/js/json/xml etc | | `:method` | HTTP request verb | | `:path` | Request path | @@ -121,6 +122,7 @@ Action Controller controller: "PostsController", action: "new", params: { "action" => "new", "controller" => "posts" }, + headers: #<ActionDispatch::Http::Headers:0x0055a67a519b88>, format: :html, method: "GET", path: "/posts/new" @@ -134,6 +136,7 @@ Action Controller | `:controller` | The controller name | | `:action` | The action | | `:params` | Hash of request parameters without any filtered parameter | +| `:headers` | Request headers | | `:format` | html/js/json/xml etc | | `:method` | HTTP request verb | | `:path` | Request path | @@ -146,6 +149,7 @@ Action Controller controller: "PostsController", action: "index", params: {"action" => "index", "controller" => "posts"}, + headers: #<ActionDispatch::Http::Headers:0x0055a67a519b88>, format: :html, method: "GET", path: "/posts", @@ -227,12 +231,13 @@ Active Record ### sql.active_record -| Key | Value | -| ---------------- | --------------------- | -| `:sql` | SQL statement | -| `:name` | Name of the operation | -| `:connection_id` | `self.object_id` | -| `:binds` | Bind parameters | +| Key | Value | +| ---------------- | ---------------------------------------- | +| `:sql` | SQL statement | +| `:name` | Name of the operation | +| `:connection_id` | `self.object_id` | +| `:binds` | Bind parameters | +| `:cached` | `true` is added when cached queries used | INFO. The adapters will add their own data as well. diff --git a/guides/source/api_app.md b/guides/source/api_app.md index 563214896a..f373d313cc 100644 --- a/guides/source/api_app.md +++ b/guides/source/api_app.md @@ -13,8 +13,8 @@ In this guide you will learn: -------------------------------------------------------------------------------- -What is an API app? -------------------- +What is an API Application? +--------------------------- Traditionally, when people said that they used Rails as an "API", they meant providing a programmatically accessible API alongside their web application. @@ -28,15 +28,14 @@ applications. For example, Twitter uses its [public API](https://dev.twitter.com) in its web application, which is built as a static site that consumes JSON resources. -Instead of using Rails to generate dynamic HTML that will communicate with the -server through forms and links, many developers are treating their web application -as just another client, delivered as static HTML, CSS and JavaScript consuming -a simple JSON API. +Instead of using Rails to generate HTML that communicates with the server +through forms and links, many developers are treating their web application as +just an API client delivered as HTML with JavaScript that consumes a JSON API. This guide covers building a Rails application that serves JSON resources to an -API client **or** a client-side framework. +API client, including client-side frameworks. -Why use Rails for JSON APIs? +Why Use Rails for JSON APIs? ---------------------------- The first question a lot of people have when thinking about building a JSON API @@ -75,13 +74,11 @@ Handled at the middleware layer: URL-encoded String? No problem. Rails will decode the JSON for you and make it available in `params`. Want to use nested URL-encoded parameters? That works too. -- Conditional GETs: Rails handles conditional `GET`, (`ETag` and `Last-Modified`), +- Conditional GETs: Rails handles conditional `GET` (`ETag` and `Last-Modified`) processing request headers and returning the correct response headers and status code. All you need to do is use the [`stale?`](http://api.rubyonrails.org/classes/ActionController/ConditionalGet.html#method-i-stale-3F) check in your controller, and Rails will handle all of the HTTP details for you. -- Caching: If you use `dirty?` with public cache control, Rails will automatically - cache your responses. You can easily configure the cache store. - HEAD requests: Rails will transparently convert `HEAD` requests into `GET` ones, and return just the headers on the way out. This makes `HEAD` work reliably in all Rails APIs. @@ -104,21 +101,21 @@ Handled at the Action Pack layer: add the response headers, but why? - Caching: Rails provides page, action and fragment caching. Fragment caching is especially helpful when building up a nested JSON object. -- Basic, Digest and Token Authentication: Rails comes with out-of-the-box support +- Basic, Digest, and Token Authentication: Rails comes with out-of-the-box support for three kinds of HTTP authentication. -- Instrumentation: Rails has an instrumentation API that will trigger registered +- Instrumentation: Rails has an instrumentation API that triggers registered handlers for a variety of events, such as action processing, sending a file or data, redirection, and database queries. The payload of each event comes with relevant information (for the action processing event, the payload includes the controller, action, parameters, request format, request method and the request's full path). -- Generators: This may be passé for advanced Rails users, but it can be nice to - generate a resource and get your model, controller, test stubs, and routes - created for you in a single command. +- Generators: It is often handy to generate a resource and get your model, + controller, test stubs, and routes created for you in a single command for + further tweaking. Same for migrations and others. - Plugins: Many third-party libraries come with support for Rails that reduce or eliminate the cost of setting up and gluing together the library and the web framework. This includes things like overriding default generators, adding - rake tasks, and honoring Rails choices (like the logger and cache back-end). + Rake tasks, and honoring Rails choices (like the logger and cache back-end). Of course, the Rails boot process also glues together all registered components. For example, the Rails boot process is what uses your `config/database.yml` file @@ -167,14 +164,23 @@ class definition: config.api_only = true ``` -Optionally, in `config/environments/development.rb` add the following line -to render error responses using the API format (JSON by default) when it -is a local request: +In `config/environments/development.rb`, set `config.debug_exception_response_format` +to configure the format used in responses when errors occur in development mode. + +To render an HTML page with debugging information, use the value `:default`. + +```ruby +config.debug_exception_response_format = :default +``` + +To render debugging information preserving the response format, use the value `:api`. ```ruby config.debug_exception_response_format = :api ``` +By default, `config.debug_exception_response_format` is set to `:api`, when `config.api_only` is set to true. + Finally, inside `app/controllers/application_controller.rb`, instead of: ```ruby @@ -196,7 +202,7 @@ An API application comes with the following middleware by default: - `Rack::Sendfile` - `ActionDispatch::Static` -- `ActionDispatch::LoadInterlock` +- `ActionDispatch::Executor` - `ActiveSupport::Cache::Strategy::LocalCache::Middleware` - `Rack::Runtime` - `ActionDispatch::RequestId` @@ -206,6 +212,7 @@ An API application comes with the following middleware by default: - `ActionDispatch::RemoteIp` - `ActionDispatch::Reloader` - `ActionDispatch::Callbacks` +- `ActiveRecord::Migration::CheckPending` - `Rack::Head` - `Rack::ConditionalGet` - `Rack::ETag` @@ -283,7 +290,7 @@ You can learn more about how to use `Rack::Sendfile` with popular front-ends in [the Rack::Sendfile documentation](http://rubydoc.info/github/rack/rack/master/Rack/Sendfile). -Here are some values for popular servers, once they are configured, to support +Here are some values for this header for some popular servers, once these servers are configured to support accelerated file sending: ```ruby @@ -333,7 +340,7 @@ API application, especially if one of your API clients is the browser: - `Rack::MethodOverride` - `ActionDispatch::Cookies` - `ActionDispatch::Flash` -- For sessions management +- For session management * `ActionDispatch::Session::CacheStore` * `ActionDispatch::Session::CookieStore` * `ActionDispatch::Session::MemCacheStore` @@ -367,10 +374,8 @@ controller modules by default: - `AbstractController::Rendering` and `ActionController::ApiRendering`: Basic support for rendering. - `ActionController::Renderers::All`: Support for `render :json` and friends. - `ActionController::ConditionalGet`: Support for `stale?`. -- `ActionController::BasicImplicitRender`: Makes sure to return an empty response - if there's not an explicit one. -- `ActionController::StrongParameters`: Support for parameters white-listing in - combination with Active Model mass assignment. +- `ActionController::BasicImplicitRender`: Makes sure to return an empty response, if there isn't an explicit one. +- `ActionController::StrongParameters`: Support for parameters white-listing in combination with Active Model mass assignment. - `ActionController::ForceSSL`: Support for `force_ssl`. - `ActionController::DataStreaming`: Support for `send_file` and `send_data`. - `AbstractController::Callbacks`: Support for `before_action` and @@ -380,8 +385,8 @@ controller modules by default: hooks defined by Action Controller (see [the instrumentation guide](active_support_instrumentation.html#action-controller) for more information regarding this). -- `ActionController::ParamsWrapper`: Wraps the parameters hash into a nested hash - so you don't have to specify root elements sending POST requests for instance. +- `ActionController::ParamsWrapper`: Wraps the parameters hash into a nested hash, + so that you don't have to specify root elements sending POST requests for instance. Other plugins may add additional modules. You can get a list of all modules included into `ActionController::API` in the rails console: @@ -389,6 +394,13 @@ included into `ActionController::API` in the rails console: ```bash $ bin/rails c >> ActionController::API.ancestors - ActionController::Metal.ancestors +=> [ActionController::API, + ActiveRecord::Railties::ControllerRuntime, + ActionDispatch::Routing::RouteSet::MountedHelpers, + ActionController::ParamsWrapper, + ... , + AbstractController::Rendering, + ActionView::ViewPaths] ``` ### Adding Other Modules @@ -403,7 +415,7 @@ Some common modules you might want to add: and translation methods. - `ActionController::HttpAuthentication::Basic` (or `Digest` or `Token`): Support for basic, digest or token HTTP authentication. -- `AbstractController::Layouts`: Support for layouts when rendering. +- `ActionView::Layouts`: Support for layouts when rendering. - `ActionController::MimeResponds`: Support for `respond_to`. - `ActionController::Cookies`: Support for `cookies`, which includes support for signed and encrypted cookies. This requires the cookies middleware. diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md index cd208c2e13..34b9c0d2ca 100644 --- a/guides/source/api_documentation_guidelines.md +++ b/guides/source/api_documentation_guidelines.md @@ -20,7 +20,7 @@ The [Rails API documentation](http://api.rubyonrails.org) is generated with in the rails root directory, run `bundle install` and execute: ```bash - ./bin/rails rdoc + bundle exec rake rdoc ``` Resulting HTML files can be found in the ./doc/rdoc directory. @@ -120,7 +120,7 @@ On the other hand, big chunks of structured documentation may have a separate "E The results of expressions follow them and are introduced by "# => ", vertically aligned: ```ruby -# For checking if a fixnum is even or odd. +# For checking if an integer is even or odd. # # 1.even? # => false # 1.odd? # => true diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 5dd54bf8ad..41dfeea84d 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -25,13 +25,9 @@ It allows assets in your application to be automatically combined with assets from other gems. For example, jquery-rails includes a copy of jquery.js and enables AJAX features in Rails. -The asset pipeline is technically no longer a core feature from Rails 4 onwards -- it has -been extracted out of the framework into the -[sprockets-rails](https://github.com/rails/sprockets-rails) gem. - -The asset pipeline is enabled by default. - -You can disable the asset pipeline while creating a new application by +The asset pipeline is implemented by the +[sprockets-rails](https://github.com/rails/sprockets-rails) gem, +and is enabled by default. You can disable it while creating a new application by passing the `--skip-sprockets` option. ```bash @@ -335,8 +331,8 @@ include the 'data-turbolinks-track' option which causes turbolinks to check if an asset has been updated and if so loads it into the page: ```erb -<%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %> -<%= javascript_include_tag "application", "data-turbolinks-track" => true %> +<%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => "reload" %> +<%= javascript_include_tag "application", "data-turbolinks-track" => "reload" %> ``` In regular views you can access images in the `public/assets/images` directory @@ -439,11 +435,11 @@ Sprockets uses manifest files to determine which assets to include and serve. These manifest files contain _directives_ - instructions that tell Sprockets which files to require in order to build a single CSS or JavaScript file. With these directives, Sprockets loads the files specified, processes them if -necessary, concatenates them into one single file and then compresses them (if -`Rails.application.config.assets.compress` is true). By serving one file rather -than many, the load time of pages can be greatly reduced because the browser -makes fewer requests. Compression also reduces file size, enabling the -browser to download them faster. +necessary, concatenates them into one single file and then compresses them +(based on value of `Rails.application.config.assets.js_compressor`). By serving +one file rather than many, the load time of pages can be greatly reduced because +the browser makes fewer requests. Compression also reduces file size, enabling +the browser to download them faster. For example, a new Rails application includes a default @@ -489,7 +485,7 @@ which contains these lines: Rails creates both `app/assets/javascripts/application.js` and `app/assets/stylesheets/application.css` regardless of whether the ---skip-sprockets option is used when creating a new rails application. This is +--skip-sprockets option is used when creating a new Rails application. This is so you can easily add asset pipelining later if you like. The directives that work in JavaScript files also work in stylesheets @@ -590,6 +586,19 @@ in your application are included in the `config.assets.precompile` list. If `config.assets.digest` is also true, the asset pipeline will require that all requests for assets include digests. +### Raise an Error When an Asset is Not Found + +If you are using sprockets-rails >= 3.2.0 you can configure what happens +when an asset lookup is performed and nothing is found. If you turn off "asset fallback" +then an error will be raised when an asset cannot be found. + +```ruby +config.assets.unknown_asset_fallback = false +``` + +If "asset fallback" is enabled then when an asset cannot be found the path will be +output instead and no error raised. The asset fallback behavior is enabled by default. + ### Turning Digests Off You can turn off digests by updating `config/environments/development.rb` to @@ -670,7 +679,7 @@ anymore, delete these options from the `javascript_include_tag` and `stylesheet_link_tag`. The fingerprinting behavior is controlled by the `config.assets.digest` -initialization option (which defaults to `true` for production and development). +initialization option (which defaults to `true`). NOTE: Under normal circumstances the default `config.assets.digest` option should not be changed. If there are no digests in the filenames, and far-future @@ -728,7 +737,7 @@ If you have other manifests or individual stylesheets and JavaScript files to include, you can add them to the `precompile` array in `config/initializers/assets.rb`: ```ruby -Rails.application.config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js'] +Rails.application.config.assets.precompile += %w( admin.js admin.css ) ``` NOTE. Always specify an expected compiled filename that ends with .js or .css, @@ -789,7 +798,6 @@ location ~ ^/assets/ { add_header Cache-Control public; add_header ETag ""; - break; } ``` @@ -1029,7 +1037,7 @@ to tell our CDN (and browser) that the asset is "public", that means any cache can store the request. Also we commonly want to set `max-age` which is how long the cache will store the object before invalidating the cache. The `max-age` value is set to seconds with a maximum possible value of `31536000` which is one -year. You can do this in your rails application by setting +year. You can do this in your Rails application by setting ``` config.public_file_server.headers = { @@ -1110,11 +1118,17 @@ NOTE: You will need an [ExecJS](https://github.com/rails/execjs#readme) supported runtime in order to use `uglifier`. If you are using Mac OS X or Windows you have a JavaScript runtime installed in your operating system. -NOTE: The `config.assets.compress` initialization option is no longer used in -Rails to enable either CSS or JavaScript compression. Setting it will have no -effect on the application. Instead, setting `config.assets.css_compressor` and -`config.assets.js_compressor` will control compression of CSS and JavaScript -assets. + + +### Serving GZipped version of assets + +By default, gzipped version of compiled assets will be generated, along with +the non-gzipped version of assets. Gzipped assets help reduce the transmission +of data over the wire. You can configure this by setting the `gzip` flag. + +```ruby +config.assets.gzip = false # disable gzipped assets generation +``` ### Using Your Own Compressor @@ -1285,12 +1299,12 @@ config.assets.js_compressor = :uglifier # Don't fallback to assets pipeline if a precompiled asset is missed config.assets.compile = false -# Generate digests for assets URLs. This is planned for deprecation. +# Generate digests for assets URLs. config.assets.digest = true # Precompile additional assets (application.js, application.css, and all # non-JS/CSS are already added) -# config.assets.precompile += %w( search.js ) +# config.assets.precompile += %w( admin.js admin.css ) ``` Rails 4 and above no longer set default config values for Sprockets in `test.rb`, so diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index 09ab64837a..03d3daecc8 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -105,13 +105,13 @@ class CreateBooks < ActiveRecord::Migration[5.0] def change create_table :authors do |t| t.string :name - t.timestamps null: false + t.timestamps end create_table :books do |t| t.belongs_to :author, index: true t.datetime :published_at - t.timestamps null: false + t.timestamps end end end @@ -136,13 +136,13 @@ class CreateSuppliers < ActiveRecord::Migration[5.0] def change create_table :suppliers do |t| t.string :name - t.timestamps null: false + t.timestamps end create_table :accounts do |t| t.belongs_to :supplier, index: true t.string :account_number - t.timestamps null: false + t.timestamps end end end @@ -180,13 +180,13 @@ class CreateAuthors < ActiveRecord::Migration[5.0] def change create_table :authors do |t| t.string :name - t.timestamps null: false + t.timestamps end create_table :books do |t| t.belongs_to :author, index: true t.datetime :published_at - t.timestamps null: false + t.timestamps end end end @@ -222,19 +222,19 @@ class CreateAppointments < ActiveRecord::Migration[5.0] def change create_table :physicians do |t| t.string :name - t.timestamps null: false + t.timestamps end create_table :patients do |t| t.string :name - t.timestamps null: false + t.timestamps end create_table :appointments do |t| t.belongs_to :physician, index: true t.belongs_to :patient, index: true t.datetime :appointment_date - t.timestamps null: false + t.timestamps end end end @@ -308,19 +308,19 @@ class CreateAccountHistories < ActiveRecord::Migration[5.0] def change create_table :suppliers do |t| t.string :name - t.timestamps null: false + t.timestamps end create_table :accounts do |t| t.belongs_to :supplier, index: true t.string :account_number - t.timestamps null: false + t.timestamps end create_table :account_histories do |t| t.belongs_to :account, index: true t.integer :credit_rating - t.timestamps null: false + t.timestamps end end end @@ -349,12 +349,12 @@ class CreateAssembliesAndParts < ActiveRecord::Migration[5.0] def change create_table :assemblies do |t| t.string :name - t.timestamps null: false + t.timestamps end create_table :parts do |t| t.string :part_number - t.timestamps null: false + t.timestamps end create_table :assemblies_parts, id: false do |t| @@ -387,14 +387,14 @@ The corresponding migration might look like this: class CreateSuppliers < ActiveRecord::Migration[5.0] def change create_table :suppliers do |t| - t.string :name - t.timestamps null: false + t.string :name + t.timestamps end create_table :accounts do |t| t.integer :supplier_id t.string :account_number - t.timestamps null: false + t.timestamps end add_index :accounts, :supplier_id @@ -472,7 +472,7 @@ class CreatePictures < ActiveRecord::Migration[5.0] t.string :name t.integer :imageable_id t.string :imageable_type - t.timestamps null: false + t.timestamps end add_index :pictures, [:imageable_type, :imageable_id] @@ -488,7 +488,7 @@ class CreatePictures < ActiveRecord::Migration[5.0] create_table :pictures do |t| t.string :name t.references :imageable, polymorphic: true, index: true - t.timestamps null: false + t.timestamps end end end @@ -518,7 +518,7 @@ class CreateEmployees < ActiveRecord::Migration[5.0] def change create_table :employees do |t| t.references :manager, index: true - t.timestamps null: false + t.timestamps end end end @@ -545,13 +545,13 @@ author.books.size # uses the cached copy of books author.books.empty? # uses the cached copy of books ``` -But what if you want to reload the cache, because data might have been changed by some other part of the application? Just pass `true` to the association call: +But what if you want to reload the cache, because data might have been changed by some other part of the application? Just call `reload` on the association: ```ruby author.books # retrieves books from the database author.books.size # uses the cached copy of books -author.books(true).empty? # discards the cached copy of books - # and goes back to the database +author.books.reload.empty? # discards the cached copy of books + # and goes back to the database ``` ### Avoiding Name Collisions @@ -734,7 +734,7 @@ end With these changes, Active Record will only load one copy of the author object, preventing inconsistencies and making your application more efficient: ```ruby -a = author.first +a = Author.first b = a.books.first a.first_name == b.author.first_name # => true a.first_name = 'Manny' @@ -932,15 +932,13 @@ side of the association. Counter cache columns are added to the containing model's list of read-only attributes through `attr_readonly`. ##### `:dependent` -If you set the `:dependent` option to: +Controls what happens to associated objects when their owner is destroyed: -* `:destroy`, when the object is destroyed, `destroy` will be called on its -associated objects. -* `:delete_all`, when the object is destroyed, all its associated objects will be -deleted directly from the database without calling their `destroy` method. -* `:nullify`, causes the foreign key to be set to `NULL`. Callbacks are not executed. -* `:restrict_with_exception`, causes an exception to be raised if there is an associated record -* `:restrict_with_error`, causes an error to be added to the owner if there is an associated object +* `:destroy` causes the associated objects to also be destroyed. +* `:delete_all` causes the associated objects to be deleted directly from the database (callbacks are not executed). +* `:nullify` causes the foreign keys to be set to `NULL` (callbacks are not executed). +* `:restrict_with_exception` causes an exception to be raised if there are associated records. +* `:restrict_with_error` causes an error to be added to the owner if there are associated objects. WARNING: You should not specify this option on a `belongs_to` association that is connected with a `has_many` association on the other class. Doing so can lead to orphaned records in your database. @@ -1009,7 +1007,7 @@ class Author < ApplicationRecord end ``` -In this case, saving or destroying an book will update the timestamp on the associated author. You can also specify a particular timestamp attribute to update: +In this case, saving or destroying a book will update the timestamp on the associated author. You can also specify a particular timestamp attribute to update: ```ruby class Book < ApplicationRecord @@ -1479,7 +1477,7 @@ WARNING: Objects will _always_ be removed from the database, ignoring the `:depe ##### `collection=(objects)` -The `collection=` method makes the collection contain only the supplied objects, by adding and deleting as appropriate. +The `collection=` method makes the collection contain only the supplied objects, by adding and deleting as appropriate. The changes are persisted to the database. ##### `collection_singular_ids` @@ -1491,7 +1489,7 @@ The `collection_singular_ids` method returns an array of the ids of the objects ##### `collection_singular_ids=(ids)` -The `collection_singular_ids=` method makes the collection contain only the objects identified by the supplied primary key values, by adding and deleting as appropriate. +The `collection_singular_ids=` method makes the collection contain only the objects identified by the supplied primary key values, by adding and deleting as appropriate. The changes are persisted to the database. ##### `collection.clear` @@ -1843,7 +1841,7 @@ article = Article.create(name: 'a1') person.articles << article person.articles << article person.articles.inspect # => [#<Article id: 5, name: "a1">, #<Article id: 5, name: "a1">] -Reading.all.inspect # => [#<Reading id: 12, person_id: 5, article_id: 5>, #<Reading id: 13, person_id: 5, article_id: 5>] +Reading.all.inspect # => [#<Reading id: 12, person_id: 5, article_id: 5>, #<Reading id: 13, person_id: 5, article_id: 5>] ``` In the above case there are two readings and `person.articles` brings out both of @@ -1862,7 +1860,7 @@ article = Article.create(name: 'a1') person.articles << article person.articles << article person.articles.inspect # => [#<Article id: 7, name: "a1">] -Reading.all.inspect # => [#<Reading id: 16, person_id: 7, article_id: 7>, #<Reading id: 17, person_id: 7, article_id: 7>] +Reading.all.inspect # => [#<Reading id: 16, person_id: 7, article_id: 7>, #<Reading id: 17, person_id: 7, article_id: 7>] ``` In the above case there are still two readings. However `person.articles` shows @@ -1996,11 +1994,9 @@ The `collection.delete` method removes one or more objects from the collection b @part.assemblies.delete(@assembly1) ``` -WARNING: This does not trigger callbacks on the join records. - ##### `collection.destroy(object, ...)` -The `collection.destroy` method removes one or more objects from the collection by running `destroy` on each record in the join table, including running callbacks. This does not destroy the objects. +The `collection.destroy` method removes one or more objects from the collection by deleting records in the join table. This does not destroy the objects. ```ruby @part.assemblies.destroy(@assembly1) @@ -2008,7 +2004,7 @@ The `collection.destroy` method removes one or more objects from the collection ##### `collection=(objects)` -The `collection=` method makes the collection contain only the supplied objects, by adding and deleting as appropriate. +The `collection=` method makes the collection contain only the supplied objects, by adding and deleting as appropriate. The changes are persisted to the database. ##### `collection_singular_ids` @@ -2020,7 +2016,7 @@ The `collection_singular_ids` method returns an array of the ids of the objects ##### `collection_singular_ids=(ids)` -The `collection_singular_ids=` method makes the collection contain only the objects identified by the supplied primary key values, by adding and deleting as appropriate. +The `collection_singular_ids=` method makes the collection contain only the objects identified by the supplied primary key values, by adding and deleting as appropriate. The changes are persisted to the database. ##### `collection.clear` diff --git a/guides/source/autoloading_and_reloading_constants.md b/guides/source/autoloading_and_reloading_constants.md index de0fa2fdc0..61657023e7 100644 --- a/guides/source/autoloading_and_reloading_constants.md +++ b/guides/source/autoloading_and_reloading_constants.md @@ -449,9 +449,10 @@ Alright, Rails has a collection of directories similar to `$LOAD_PATH` in which to look up `post.rb`. That collection is called `autoload_paths` and by default it contains: -* All subdirectories of `app` in the application and engines. For example, - `app/controllers`. They do not need to be the default ones, any custom - directories like `app/workers` belong automatically to `autoload_paths`. +* All subdirectories of `app` in the application and engines present at boot + time. For example, `app/controllers`. They do not need to be the default + ones, any custom directories like `app/workers` belong automatically to + `autoload_paths`. * Any existing second level directories called `app/*/concerns` in the application and engines. @@ -524,7 +525,7 @@ On the contrary, if `ApplicationController` is unknown, the constant is considered missing and an autoload is going to be attempted by Rails. In order to load `ApplicationController`, Rails iterates over `autoload_paths`. -First checks if `app/assets/application_controller.rb` exists. If it does not, +First it checks if `app/assets/application_controller.rb` exists. If it does not, which is normally the case, it continues and finds `app/controllers/application_controller.rb`. @@ -624,7 +625,7 @@ file is loaded. If the file actually defines `Post` all is fine, otherwise ### Qualified References When a qualified constant is missing Rails does not look for it in the parent -namespaces. But there is a caveat: When a constant is missing, Rails is +namespaces. But there is a caveat: when a constant is missing, Rails is unable to tell if the trigger was a relative reference or a qualified one. For example, consider diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index 3a1a1ccfe6..fd7626250c 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -100,8 +100,8 @@ called key-based expiration. Cache fragments will also be expired when the view fragment changes (e.g., the HTML in the view changes). The string of characters at the end of the key is a -template tree digest. It is an md5 hash computed based on the contents of the -view fragment you are caching. If you change the view fragment, the md5 hash +template tree digest. It is an MD5 hash computed based on the contents of the +view fragment you are caching. If you change the view fragment, the MD5 hash will change, expiring the existing file. TIP: Cache stores like Memcached will automatically delete old cache files. @@ -119,25 +119,16 @@ If you want to cache a fragment under certain conditions, you can use The `render` helper can also cache individual templates rendered for a collection. It can even one up the previous example with `each` by reading all cache -templates at once instead of one by one. This is done automatically if the template -rendered by the collection includes a `cache` call. Take a collection that renders -a `products/_product.html.erb` partial for each element: - -```ruby -render products -``` - -If `products/_product.html.erb` starts with a `cache` call like so: +templates at once instead of one by one. This is done by passing `cached: true` when rendering the collection: ```html+erb -<% cache product do %> - <%= product.name %> -<% end %> +<%= render partial: 'products/product', collection: @products, cached: true %> ``` -All the cached templates from previous renders will be fetched at once with much -greater speed. There's more info on how to make your templates [eligible for -collection caching](http://api.rubyonrails.org/classes/ActionView/Template/Handlers/ERB.html#method-i-resource_cache_call_pattern). +All cached templates from previous renders will be fetched at once with much +greater speed. Additionally, the templates that haven't yet been cached will be +written to cache and multi fetched on the next render. + ### Russian Doll Caching @@ -207,11 +198,11 @@ render "comments/comments" render 'comments/comments' render('comments/comments') -render "header" => render("comments/header") +render "header" translates to render("comments/header") -render(@topic) => render("topics/topic") -render(topics) => render("topics/topic") -render(message.topics) => render("topics/topic") +render(@topic) translates to render("topics/topic") +render(topics) translates to render("topics/topic") +render(message.topics) translates to render("topics/topic") ``` On the other hand, some calls need to be changed to make caching work properly. @@ -267,7 +258,7 @@ comment format anywhere in the template, like: If you use a helper method, for example, inside a cached block and you then update that helper, you'll have to bump the cache as well. It doesn't really matter how -you do it, but the md5 of the template file must change. One recommendation is to +you do it, but the MD5 of the template file must change. One recommendation is to simply be explicit in a comment, like: ```html+erb @@ -279,7 +270,7 @@ simply be explicit in a comment, like: Sometimes you need to cache a particular value or query result instead of caching view fragments. Rails' caching mechanism works great for storing __any__ kind of information. -The most efficient way to implement low-level caching is using the `Rails.cache.fetch` method. This method does both reading and writing to the cache. When passed only a single argument, the key is fetched and value from the cache is returned. If a block is passed, the result of the block will be cached to the given key and the result is returned. +The most efficient way to implement low-level caching is using the `Rails.cache.fetch` method. This method does both reading and writing to the cache. When passed only a single argument, the key is fetched and value from the cache is returned. If a block is passed, that block will be executed in the event of a cache miss. The return value of the block will be written to the cache under the given cache key, and that return value will be returned. In case of cache hit, the cached value will be returned without executing the block. Consider the following example. An application has a `Product` model with an instance method that looks up the product’s price on a competing website. The data returned by this method would be perfect for low-level caching: @@ -390,7 +381,7 @@ config.cache_store = :memory_store, { size: 64.megabytes } ``` If you're running multiple Ruby on Rails server processes (which is the case -if you're using mongrel_cluster or Phusion Passenger), then your Rails server +if you're using Phusion Passenger or puma clustered mode), then your Rails server process instances won't be able to share cache data with each other. This cache store is not appropriate for large application deployments. However, it can work well for small, low traffic sites with only a couple of server processes, @@ -521,6 +512,64 @@ class ProductsController < ApplicationController end ``` +Sometimes we want to cache response, for example a static page, that never gets +expired. To achieve this, we can use `http_cache_forever` helper and by doing +so browser and proxies will cache it indefinitely. + +By default cached responses will be private, cached only on the user's web +browser. To allow proxies to cache the response, set `public: true` to indicate +that they can serve the cached response to all users. + +Using this helper, `last_modified` header is set to `Time.new(2011, 1, 1).utc` +and `expires` header is set to a 100 years. + +WARNING: Use this method carefully as browser/proxy won't be able to invalidate +the cached response unless browser cache is forcefully cleared. + +```ruby +class HomeController < ApplicationController + def index + http_cache_forever(public: true) do + render + end + end +end +``` + +### Strong v/s Weak ETags + +Rails generates weak ETags by default. Weak ETags allow semantically equivalent +responses to have the same ETags, even if their bodies do not match exactly. +This is useful when we don't want the page to be regenerated for minor changes in +response body. + +Weak ETags have a leading `W/` to differentiate them from strong ETags. + +``` + W/"618bbc92e2d35ea1945008b42799b0e7" → Weak ETag + "618bbc92e2d35ea1945008b42799b0e7" → Strong ETag +``` + +Unlike weak ETag, strong ETag implies that response should be exactly the same +and byte by byte identical. Useful when doing Range requests within a +large video or PDF file. Some CDNs support only strong ETags, like Akamai. +If you absolutely need to generate a strong ETag, it can be done as follows. + +```ruby + class ProductsController < ApplicationController + def show + @product = Product.find(params[:id]) + fresh_when last_modified: @product.published_at.utc, strong_etag: @product + end + end +``` + +You can also set the strong ETag directly on the response. + +```ruby + response.strong_etag = response.body # => "618bbc92e2d35ea1945008b42799b0e7" +``` + References ---------- diff --git a/guides/source/command_line.md b/guides/source/command_line.md index e865a02cbd..9d7ecce947 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -65,11 +65,12 @@ $ bin/rails server => Booting Puma => Rails 5.0.0 application starting in development on http://0.0.0.0:3000 => Run `rails server -h` for more startup options -=> Ctrl-C to shutdown server -Puma 2.15.3 starting... -* Min threads: 0, max threads: 16 +Puma starting in single mode... +* Version 3.0.2 (ruby 2.3.0-p0), codename: Plethora of Penguin Pinatas +* Min threads: 5, max threads: 5 * Environment: development * Listening on tcp://localhost:3000 +Use Ctrl-C to stop ``` With just three commands we whipped up a Rails server listening on port 3000. Go to your browser and open [http://localhost:3000](http://localhost:3000), you will see a basic Rails app running. @@ -208,7 +209,7 @@ Description: Create rails files for model generator. ``` -NOTE: For a list of available field types, refer to the [API documentation](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html#method-i-column) for the column method for the `TableDefinition` class. +NOTE: For a list of available field types for the `type` parameter, refer to the [API documentation](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_column) for the add_column method for the `SchemaStatements` module. The `index` parameter generates a corresponding index for the column. But instead of generating a model directly (which we'll be doing later), let's set up a scaffold. A **scaffold** in Rails is a full set of model, database migration for that model, controller to manipulate it, views to view and manipulate the data, and a test suite for each of the above. @@ -325,7 +326,7 @@ With the `helper` method it is possible to access Rails and your application's h ### `rails dbconsole` -`rails dbconsole` figures out which database you're using and drops you into whichever command line interface you would use with it (and figures out the command line parameters to give to it, too!). It supports MySQL, PostgreSQL and SQLite3. +`rails dbconsole` figures out which database you're using and drops you into whichever command line interface you would use with it (and figures out the command line parameters to give to it, too!). It supports MySQL (including MariaDB), PostgreSQL and SQLite3. INFO: You can also use the alias "db" to invoke the dbconsole: `rails db`. @@ -432,7 +433,7 @@ Ruby version 2.2.2 (x86_64-linux) RubyGems version 2.4.6 Rack version 1.6 JavaScript Runtime Node.js (V8) -Middleware Rack::Sendfile, ActionDispatch::Static, ActionDispatch::LoadInterlock, #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007ffd131a7c88>, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, Rack::Head, Rack::ConditionalGet, Rack::ETag +Middleware Rack::Sendfile, ActionDispatch::Static, ActionDispatch::Executor, #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007ffd131a7c88>, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, Rack::Head, Rack::ConditionalGet, Rack::ETag Application root /home/foobar/commandsapp Environment development Database adapter sqlite3 @@ -496,7 +497,13 @@ app/models/article.rb: NOTE. When using specific annotations and custom annotations, the annotation name (FIXME, BUG etc) is not displayed in the output lines. -By default, `rails notes` will look in the `app`, `config`, `db`, `lib` and `test` directories. If you would like to search other directories, you can provide them as a comma separated list in an environment variable `SOURCE_ANNOTATION_DIRECTORIES`. +By default, `rails notes` will look in the `app`, `config`, `db`, `lib` and `test` directories. If you would like to search other directories, you can configure them using `config.annotations.register_directories` option. + +```ruby +config.annotations.register_directories("spec", "vendor") +``` + +You can also provide them as a comma separated list in the environment variable `SOURCE_ANNOTATION_DIRECTORIES`. ```bash $ export SOURCE_ANNOTATION_DIRECTORIES='spec,vendor' diff --git a/guides/source/configuring.md b/guides/source/configuring.md index a5fb396f15..c625cf67f6 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -60,22 +60,22 @@ These configuration methods are to be called on a `Rails::Railtie` object, such * `config.asset_host` sets the host for the assets. Useful when CDNs are used for hosting assets, or when you want to work around the concurrency constraints built-in in browsers using different domain aliases. Shorter version of `config.action_controller.asset_host`. -* `config.autoload_once_paths` accepts an array of paths from which Rails will autoload constants that won't be wiped per request. Relevant if `config.cache_classes` is false, which is the case in development mode by default. Otherwise, all autoloading happens only once. All elements of this array must also be in `autoload_paths`. Default is an empty array. +* `config.autoload_once_paths` accepts an array of paths from which Rails will autoload constants that won't be wiped per request. Relevant if `config.cache_classes` is `false`, which is the case in development mode by default. Otherwise, all autoloading happens only once. All elements of this array must also be in `autoload_paths`. Default is an empty array. * `config.autoload_paths` accepts an array of paths from which Rails will autoload constants. Default is all directories under `app`. -* `config.cache_classes` controls whether or not application classes and modules should be reloaded on each request. Defaults to false in development mode, and true in test and production modes. +* `config.cache_classes` controls whether or not application classes and modules should be reloaded on each request. Defaults to `false` in development mode, and `true` in test and production modes. * `config.action_view.cache_template_loading` controls whether or not templates should be reloaded on each request. Defaults to whatever is set for `config.cache_classes`. * `config.beginning_of_week` sets the default beginning of week for the application. Accepts a valid week day symbol (e.g. `:monday`). -* `config.cache_store` configures which cache store to use for Rails caching. Options include one of the symbols `:memory_store`, `:file_store`, `:mem_cache_store`, `:null_store`, or an object that implements the cache API. Defaults to `:file_store` if the directory `tmp/cache` exists, and to `:memory_store` otherwise. +* `config.cache_store` configures which cache store to use for Rails caching. Options include one of the symbols `:memory_store`, `:file_store`, `:mem_cache_store`, `:null_store`, or an object that implements the cache API. Defaults to `:file_store`. -* `config.colorize_logging` specifies whether or not to use ANSI color codes when logging information. Defaults to true. +* `config.colorize_logging` specifies whether or not to use ANSI color codes when logging information. Defaults to `true`. -* `config.consider_all_requests_local` is a flag. If true then any error will cause detailed debugging information to be dumped in the HTTP response, and the `Rails::Info` controller will show the application runtime context in `/rails/info/properties`. True by default in development and test environments, and false in production mode. For finer-grained control, set this to false and implement `local_request?` in controllers to specify which requests should provide debugging information on errors. +* `config.consider_all_requests_local` is a flag. If `true` then any error will cause detailed debugging information to be dumped in the HTTP response, and the `Rails::Info` controller will show the application runtime context in `/rails/info/properties`. `true` by default in development and test environments, and `false` in production mode. For finer-grained control, set this to `false` and implement `local_request?` in controllers to specify which requests should provide debugging information on errors. * `config.console` allows you to set class that will be used as console you run `rails console`. It's best to run it in `console` block: @@ -88,25 +88,29 @@ application. Accepts a valid week day symbol (e.g. `:monday`). end ``` -* `config.eager_load` when true, eager loads all registered `config.eager_load_namespaces`. This includes your application, engines, Rails frameworks and any other registered namespace. +* `config.eager_load` when `true`, eager loads all registered `config.eager_load_namespaces`. This includes your application, engines, Rails frameworks and any other registered namespace. -* `config.eager_load_namespaces` registers namespaces that are eager loaded when `config.eager_load` is true. All namespaces in the list must respond to the `eager_load!` method. +* `config.eager_load_namespaces` registers namespaces that are eager loaded when `config.eager_load` is `true`. All namespaces in the list must respond to the `eager_load!` method. * `config.eager_load_paths` accepts an array of paths from which Rails will eager load on boot if cache classes is enabled. Defaults to every folder in the `app` directory of the application. +* `config.enable_dependency_loading`: when true, enables autoloading, even if the application is eager loaded and `config.cache_classes` is set as true. Defaults to false. + * `config.encoding` sets up the application-wide encoding. Defaults to UTF-8. * `config.exceptions_app` sets the exceptions application invoked by the ShowException middleware when an exception happens. Defaults to `ActionDispatch::PublicExceptions.new(Rails.public_path)`. -* `config.file_watcher` is the class used to detect file updates in the file system when `config.reload_classes_only_on_change` is true. Rails ships with `ActiveSupport::FileUpdateChecker`, the default, and `ActiveSupport::EventedFileUpdateChecker` (this one depends on the [listen](https://github.com/guard/listen) gem). Custom classes must conform to the `ActiveSupport::FileUpdateChecker` API. +* `config.debug_exception_response_format` sets the format used in responses when errors occur in development mode. Defaults to `:api` for API only apps and `:default` for normal apps. + +* `config.file_watcher` is the class used to detect file updates in the file system when `config.reload_classes_only_on_change` is `true`. Rails ships with `ActiveSupport::FileUpdateChecker`, the default, and `ActiveSupport::EventedFileUpdateChecker` (this one depends on the [listen](https://github.com/guard/listen) gem). Custom classes must conform to the `ActiveSupport::FileUpdateChecker` API. * `config.filter_parameters` used for filtering out the parameters that you don't want shown in the logs, such as passwords or credit card -numbers. New applications filter out passwords by adding the following `config.filter_parameters+=[:password]` in `config/initializers/filter_parameter_logging.rb`. +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. 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://edgeapi.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 except production, where it defaults to `Logger::Formatter`. +* `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. * `config.log_level` defines the verbosity of the Rails logger. This option defaults to `:debug` for all environments. The available log levels are: `:debug`, @@ -114,17 +118,31 @@ defaults to `:debug` for all environments. The available log levels are: `:debug * `config.log_tags` accepts a list of: methods that the `request` object responds to, a `Proc` that accepts the `request` object, or something that responds to `to_s`. This makes it easy to tag log lines with debug information like subdomain and request id - both very helpful in debugging multi-user production applications. -* `config.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to an instance of `ActiveSupport::Logger`. +* `config.logger` is the logger that will be used for `Rails.logger` and any related Rails logging such as `ActiveRecord::Base.logger`. It defaults to an instance of `ActiveSupport::TaggedLogging` that wraps an instance of `ActiveSupport::Logger` which outputs a log to the `log/` directory. You can supply a custom logger, to get full compatibility you must follow these guidelines: + * To support a formatter, you must manually assign a formatter from the `config.log_formatter` value to the logger. + * To support tagged logs, the log instance must be wrapped with `ActiveSupport::TaggedLogging`. + * To support silencing, the logger must include `LoggerSilence` and `ActiveSupport::LoggerThreadSafeLevel` modules. The `ActiveSupport::Logger` class already includes these modules. + + ```ruby + class MyLogger < ::Logger + include ActiveSupport::LoggerThreadSafeLevel + include LoggerSilence + end + + mylogger = MyLogger.new(STDOUT) + mylogger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(mylogger) + ``` * `config.middleware` allows you to configure the application's middleware. This is covered in depth in the [Configuring Middleware](#configuring-middleware) section below. -* `config.reload_classes_only_on_change` enables or disables reloading of classes only when tracked files change. By default tracks everything on autoload paths and is set to true. If `config.cache_classes` is true, this option is ignored. +* `config.reload_classes_only_on_change` enables or disables reloading of classes only when tracked files change. By default tracks everything on autoload paths and is set to `true`. If `config.cache_classes` is `true`, this option is ignored. * `secrets.secret_key_base` is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `secrets.secret_key_base` initialized to a random key present in `config/secrets.yml`. -* `config.public_file_server.enabled` configures Rails to serve static files from the public directory. This option defaults to true, but in the production environment it is set to false because the server software (e.g. NGINX or Apache) used to run the application should serve static files instead. If you are running or testing your app in production mode using WEBrick (it is not recommended to use WEBrick in production) set the option to true. Otherwise, you won't be able to use page caching and request for files that exist under the public directory. +* `config.public_file_server.enabled` configures Rails to serve static files from the public directory. This option defaults to `true`, but in the production environment it is set to `false` because the server software (e.g. NGINX or Apache) used to run the application should serve static files instead. If you are running or testing your app in production mode using WEBrick (it is not recommended to use WEBrick in production) set the option to `true.` Otherwise, you won't be able to use page caching and request for files that exist under the public directory. -* `config.session_store` is usually set up in `config/initializers/session_store.rb` and specifies what class to use to store the session. Possible values are `:cookie_store` which is the default, `:mem_cache_store`, and `:disabled`. The last one tells Rails not to deal with sessions. Custom session stores can also be specified: +* `config.session_store` specifies what class to use to store the session. Possible values are `:cookie_store` which is the default, `:mem_cache_store`, and `:disabled`. The last one tells Rails not to deal with sessions. Defaults to a cookie store with application name as the session key. Custom session stores can also be specified: ```ruby config.session_store :my_custom_store @@ -137,31 +155,37 @@ defaults to `:debug` for all environments. The available log levels are: `:debug ### Configuring Assets * `config.assets.enabled` a flag that controls whether the asset -pipeline is enabled. It is set to true by default. +pipeline is enabled. It is set to `true` by default. * `config.assets.raise_runtime_errors` Set this flag to `true` to enable additional runtime error checking. Recommended in `config/environments/development.rb` to minimize unexpected behavior when deploying to `production`. -* `config.assets.compress` a flag that enables the compression of compiled assets. It is explicitly set to true in `config/environments/production.rb`. - * `config.assets.css_compressor` defines the CSS compressor to use. It is set by default by `sass-rails`. The unique alternative value at the moment is `:yui`, which uses the `yui-compressor` gem. * `config.assets.js_compressor` defines the JavaScript compressor to use. Possible values are `:closure`, `:uglifier` and `:yui` which require the use of the `closure-compiler`, `uglifier` or `yui-compressor` gems respectively. +* `config.assets.gzip` a flag that enables the creation of gzipped version of compiled assets, along with non-gzipped assets. Set to `true` by default. + * `config.assets.paths` contains the paths which are used to look for assets. Appending paths to this configuration option will cause those paths to be used in the search for assets. * `config.assets.precompile` allows you to specify additional assets (other than `application.css` and `application.js`) which are to be precompiled when `rake assets:precompile` is run. +* `config.assets.unknown_asset_fallback` allows you to modify the behavior of the asset pipeline when an asset is not in the pipeline, if you use sprockets-rails 3.2.0 or newer. Defaults to `true`. + * `config.assets.prefix` defines the prefix where assets are served from. Defaults to `/assets`. * `config.assets.manifest` defines the full path to be used for the asset precompiler's manifest file. Defaults to a file named `manifest-<random>.json` in the `config.assets.prefix` directory within the public folder. -* `config.assets.digest` enables the use of MD5 fingerprints in asset names. Set to `true` by default in `production.rb` and `development.rb`. +* `config.assets.digest` enables the use of MD5 fingerprints in asset names. Set to `true` by default. * `config.assets.debug` disables the concatenation and compression of assets. Set to `true` by default in `development.rb`. +* `config.assets.version` is an option string that is used in MD5 hash generation. This can be changed to force all files to be recompiled. + * `config.assets.compile` is a boolean that can be used to turn on live Sprockets compilation in production. -* `config.assets.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to the same configured at `config.logger`. Setting `config.assets.logger` to false will turn off served assets logging. +* `config.assets.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to the same configured at `config.logger`. Setting `config.assets.logger` to `false` will turn off served assets logging. + +* `config.assets.quiet` disables logging of assets requests. Set to `true` by default in `development.rb`. ### Configuring Generators @@ -189,6 +213,7 @@ The full set of methods that can be used in this block are as follows: * `scaffold_controller` different from `resource_controller`, defines which generator to use for generating a _scaffolded_ controller when using `rails generate scaffold`. Defaults to `:scaffold_controller`. * `stylesheets` turns on the hook for stylesheets in generators. Used in Rails for when the `scaffold` generator is run, but this hook can be used in other generates as well. Defaults to `true`. * `stylesheet_engine` configures the stylesheet engine (for eg. sass) to be used when generating assets. Defaults to `:css`. +* `scaffold_stylesheet` creates `scaffold.css` when generating a scaffolded resource. Defaults to `true`. * `test_framework` defines which test framework to use. Defaults to `false` and will use Minitest by default. * `template_engine` defines which template engine to use, such as ERB or Haml. Defaults to `:erb`. @@ -198,7 +223,7 @@ Every Rails application comes with a standard set of middleware which it uses in * `ActionDispatch::SSL` forces every request to be served using HTTPS. Enabled if `config.force_ssl` is set to `true`. Options passed to this can be configured by setting `config.ssl_options`. * `ActionDispatch::Static` is used to serve static assets. Disabled if `config.public_file_server.enabled` is `false`. Set `config.public_file_server.index_name` if you need to serve a static directory index file that is not named `index`. For example, to serve `main.html` instead of `index.html` for directory requests, set `config.public_file_server.index_name` to `"main"`. -* `ActionDispatch::LoadInterlock` allows thread safe code reloading. Disabled if `config.allow_concurrency` is `false`, which causes `Rack::Lock` to be loaded. `Rack::Lock` wraps the app in mutex so it can only be called by a single thread at a time. +* `ActionDispatch::Executor` allows thread safe code reloading. Disabled if `config.allow_concurrency` is `false`, which causes `Rack::Lock` to be loaded. `Rack::Lock` wraps the app in mutex so it can only be called by a single thread at a time. * `ActiveSupport::Cache::Strategy::LocalCache` serves as a basic memory backed cache. This cache is not thread safe and is intended only for serving as a temporary memory cache for a single thread. * `Rack::Runtime` sets an `X-Runtime` header, containing the time (in seconds) taken to execute the request. * `Rails::Rack::Logger` notifies the logs that the request has begun. After request is complete, flushes all the logs. @@ -207,8 +232,6 @@ Every Rails application comes with a standard set of middleware which it uses in * `ActionDispatch::RemoteIp` checks for IP spoofing attacks and gets valid `client_ip` from request headers. Configurable with the `config.action_dispatch.ip_spoofing_check`, and `config.action_dispatch.trusted_proxies` options. * `Rack::Sendfile` intercepts responses whose body is being served from a file and replaces it with a server specific X-Sendfile header. Configurable with `config.action_dispatch.x_sendfile_header`. * `ActionDispatch::Callbacks` runs the prepare callbacks before serving the request. -* `ActiveRecord::ConnectionAdapters::ConnectionManagement` cleans active connections after each request, unless the `rack.test` key in the request environment is set to `true`. -* `ActiveRecord::QueryCache` caches all SELECT queries generated in a request. If any INSERT or UPDATE takes place then the cache is cleaned. * `ActionDispatch::Cookies` sets cookies for the request. * `ActionDispatch::Session::CookieStore` is responsible for storing the session in cookies. An alternate middleware can be used for this by changing the `config.action_controller.session_store` to an alternate value. Additionally, options passed to this can be configured by using `config.action_controller.session_options`. * `ActionDispatch::Flash` sets up the `flash` keys. Only available if `config.action_controller.session_store` is set to a value. @@ -227,6 +250,12 @@ This will put the `Magical::Unicorns` middleware on the end of the stack. You ca config.middleware.insert_before Rack::Head, Magical::Unicorns ``` +Or you can insert a middleware to exact position by using indexes. For example, if you want to insert `Magical::Unicorns` middleware on top of the stack, you can do it, like so: + +```ruby +config.middleware.insert_before 0, Magical::Unicorns +``` + There's also `insert_after` which will insert a middleware after another: ```ruby @@ -257,6 +286,28 @@ All these configuration options are delegated to the `I18n` library. * `config.i18n.load_path` sets the path Rails uses to look for locale files. Defaults to `config/locales/*.{yml,rb}`. +* `config.i18n.fallbacks` sets fallback behavior for missing translations. Here are 3 usage examples for this option: + + * You can set the option to `true` for using default locale as fallback, like so: + + ```ruby + config.i18n.fallbacks = true + ``` + + * Or you can set an array of locales as fallback, like so: + + ```ruby + config.i18n.fallbacks = [:tr, :en] + ``` + + * Or you can set different fallbacks for locales individually. For example, if you want to use `:tr` for `:az` and `:de`, `:en` for `:da` as fallbacks, you can do it, like so: + + ```ruby + config.i18n.fallbacks = { az: :tr, da: [:de, :en] } + #or + config.i18n.fallbacks.map = { az: :tr, da: [:de, :en] } + ``` + ### Configuring Active Record `config.active_record` includes a variety of configuration options: @@ -264,8 +315,8 @@ All these configuration options are delegated to the `I18n` library. * `config.active_record.logger` accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then passed on to any new database connections made. You can retrieve this logger by calling `logger` on either an Active Record model class or an Active Record model instance. Set to `nil` to disable logging. * `config.active_record.primary_key_prefix_type` lets you adjust the naming for primary key columns. By default, Rails assumes that primary key columns are named `id` (and this configuration option doesn't need to be set.) There are two other choices: - * `:table_name` would make the primary key for the Customer class `customerid` - * `:table_name_with_underscore` would make the primary key for the Customer class `customer_id` + * `:table_name` would make the primary key for the Customer class `customerid`. + * `:table_name_with_underscore` would make the primary key for the Customer class `customer_id`. * `config.active_record.table_name_prefix` lets you set a global string to be prepended to table names. If you set this to `northwest_`, then the Customer class will look for `northwest_customers` as its table. The default is an empty string. @@ -273,15 +324,17 @@ All these configuration options are delegated to the `I18n` library. * `config.active_record.schema_migrations_table_name` lets you set a string to be used as the name of the schema migrations table. -* `config.active_record.pluralize_table_names` specifies whether Rails will look for singular or plural table names in the database. If set to true (the default), then the Customer class will use the `customers` table. If set to false, then the Customer class will use the `customer` table. +* `config.active_record.pluralize_table_names` specifies whether Rails will look for singular or plural table names in the database. If set to `true` (the default), then the Customer class will use the `customers` table. If set to false, then the Customer class will use the `customer` table. * `config.active_record.default_timezone` determines whether to use `Time.local` (if set to `:local`) or `Time.utc` (if set to `:utc`) when pulling dates and times from the database. The default is `:utc`. * `config.active_record.schema_format` controls the format for dumping the database schema to a file. The options are `:ruby` (the default) for a database-independent version that depends on migrations, or `:sql` for a set of (potentially database-dependent) SQL statements. -* `config.active_record.timestamped_migrations` controls whether migrations are numbered with serial integers or with timestamps. The default is true, to use timestamps, which are preferred if there are multiple developers working on the same application. +* `config.active_record.error_on_ignored_order` specifies if an error should be raised if the order of a query is ignored during a batch query. The options are `true` (raise error) or `false` (warn). Default is `false`. + +* `config.active_record.timestamped_migrations` controls whether migrations are numbered with serial integers or with timestamps. The default is `true`, to use timestamps, which are preferred if there are multiple developers working on the same application. -* `config.active_record.lock_optimistically` controls whether Active Record will use optimistic locking and is true by default. +* `config.active_record.lock_optimistically` controls whether Active Record will use optimistic locking and is `true` by default. * `config.active_record.cache_timestamp_format` controls the format of the timestamp value in the cache key. Default is `:nsec`. @@ -289,13 +342,13 @@ All these configuration options are delegated to the `I18n` library. * `config.active_record.partial_writes` is a boolean value and controls whether or not partial writes are used (i.e. whether updates only set attributes that are dirty). Note that when using partial writes, you should also use optimistic locking `config.active_record.lock_optimistically` since concurrent updates may write attributes based on a possibly stale read state. The default value is `true`. -* `config.active_record.maintain_test_schema` is a boolean value which controls whether Active Record should try to keep your test database schema up-to-date with `db/schema.rb` (or `db/structure.sql`) when you run your tests. The default is true. +* `config.active_record.maintain_test_schema` is a boolean value which controls whether Active Record should try to keep your test database schema up-to-date with `db/schema.rb` (or `db/structure.sql`) when you run your tests. The default is `true`. * `config.active_record.dump_schema_after_migration` is a flag which controls whether or not schema dump should happen (`db/schema.rb` or - `db/structure.sql`) when you run migrations. This is set to false in + `db/structure.sql`) when you run migrations. This is set to `false` in `config/environments/production.rb` which is generated by Rails. The - default value is true if this configuration is not set. + default value is `true` if this configuration is not set. * `config.active_record.dump_schemas` controls which database schemas will be dumped when calling db:structure:dump. The options are `:schema_search_path` (the default) which dumps any schemas listed in schema_search_path, @@ -313,11 +366,11 @@ All these configuration options are delegated to the `I18n` library. * `config.active_record.index_nested_attribute_errors` allows errors for nested has_many relationships to be displayed with an index as well as the error. - Defaults to false. + Defaults to `false`. The MySQL adapter adds one additional configuration option: -* `ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns in a MySQL database to be booleans and is true by default. +* `ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns as booleans. Defaults to `true`. The schema dumper adds one additional configuration option: @@ -329,11 +382,11 @@ The schema dumper adds one additional configuration option: * `config.action_controller.asset_host` sets the host for the assets. Useful when CDNs are used for hosting assets rather than the application server itself. -* `config.action_controller.perform_caching` configures whether the application should perform the caching features provided by the Action Controller component or not. Set to false in development mode, true in production. +* `config.action_controller.perform_caching` configures whether the application should perform the caching features provided by the Action Controller component or not. Set to `false` in development mode, `true` in production. * `config.action_controller.default_static_extension` configures the extension used for cached pages. Defaults to `.html`. -* `config.action_controller.include_all_helpers` configures whether all view helpers are available everywhere or are scoped to the corresponding controller. If set to `false`, `UsersHelper` methods are only available for views rendered as part of `UsersController`. If `true`, `UsersHelper` methods are available everywhere. The default is `true`. +* `config.action_controller.include_all_helpers` configures whether all view helpers are available everywhere or are scoped to the corresponding controller. If set to `false`, `UsersHelper` methods are only available for views rendered as part of `UsersController`. If `true`, `UsersHelper` methods are available everywhere. The default configuration behavior (when this option is not explicitly set to `true` or `false`) is that all view helpers are available to each controller. * `config.action_controller.logger` accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action Controller. Set to `nil` to disable logging. @@ -353,6 +406,22 @@ The schema dumper adds one additional configuration option: * `config.action_controller.always_permitted_parameters` sets a list of whitelisted parameters that are permitted by default. The default values are `['controller', 'action']`. +* `config.action_controller.enable_fragment_cache_logging` determines whether to log fragment cache reads and writes in verbose format as follows: + + ``` + Read fragment views/v1/2914079/v1/2914079/recordings/70182313-20160225015037000000/d0bdf2974e1ef6d31685c3b392ad0b74 (0.6ms) + Rendered messages/_message.html.erb in 1.2 ms [cache hit] + Write fragment views/v1/2914079/v1/2914079/recordings/70182313-20160225015037000000/3b4e249ac9d168c617e32e84b99218b5 (1.1ms) + Rendered recordings/threads/_thread.html.erb in 1.5 ms [cache miss] + ``` + + By default it is set to `false` which results in following output: + + ``` + Rendered messages/_message.html.erb in 1.2 ms [cache hit] + Rendered recordings/threads/_thread.html.erb in 1.5 ms [cache miss] + ``` + ### Configuring Action Dispatch * `config.action_dispatch.session_store` sets the name of the store for session data. The default is `:cookie_store`; other valid options include `:active_record_store`, `:mem_cache_store` or the name of your own custom class. @@ -371,6 +440,10 @@ The schema dumper adds one additional configuration option: * `config.action_dispatch.tld_length` sets the TLD (top-level domain) length for the application. Defaults to `1`. +* `config.action_dispatch.ignore_accept_header` is used to determine whether to ignore accept headers from a request. Defaults to `false`. + +* `config.action_dispatch.x_sendfile_header` specifies server specific X-Sendfile header. This is useful for accelerated file sending from server. For example it can be set to 'X-Sendfile' for Apache. + * `config.action_dispatch.http_auth_salt` sets the HTTP Auth salt value. Defaults to `'http authentication'`. @@ -385,27 +458,29 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`. * `config.action_dispatch.perform_deep_munge` configures whether `deep_munge` method should be performed on the parameters. See [Security Guide](security.html#unsafe-query-generation) - for more information. It defaults to true. + for more information. It defaults to `true`. * `config.action_dispatch.rescue_responses` configures what exceptions are assigned to an HTTP status. It accepts a hash and you can specify pairs of exception/status. By default, this is defined as: ```ruby config.action_dispatch.rescue_responses = { - 'ActionController::RoutingError' => :not_found, - 'AbstractController::ActionNotFound' => :not_found, - 'ActionController::MethodNotAllowed' => :method_not_allowed, - 'ActionController::UnknownHttpMethod' => :method_not_allowed, - 'ActionController::NotImplemented' => :not_implemented, - 'ActionController::UnknownFormat' => :not_acceptable, - 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity, - 'ActionController::InvalidCrossOriginRequest' => :unprocessable_entity, - 'ActionDispatch::ParamsParser::ParseError' => :bad_request, - 'ActionController::BadRequest' => :bad_request, - 'ActionController::ParameterMissing' => :bad_request, - 'ActiveRecord::RecordNotFound' => :not_found, - 'ActiveRecord::StaleObjectError' => :conflict, - 'ActiveRecord::RecordInvalid' => :unprocessable_entity, - 'ActiveRecord::RecordNotSaved' => :unprocessable_entity + 'ActionController::RoutingError' => :not_found, + 'AbstractController::ActionNotFound' => :not_found, + 'ActionController::MethodNotAllowed' => :method_not_allowed, + 'ActionController::UnknownHttpMethod' => :method_not_allowed, + 'ActionController::NotImplemented' => :not_implemented, + 'ActionController::UnknownFormat' => :not_acceptable, + 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity, + 'ActionController::InvalidCrossOriginRequest' => :unprocessable_entity, + 'ActionDispatch::Http::Parameters::ParseError' => :bad_request, + 'ActionController::BadRequest' => :bad_request, + 'ActionController::ParameterMissing' => :bad_request, + 'Rack::QueryParser::ParameterTypeError' => :bad_request, + 'Rack::QueryParser::InvalidParameterError' => :bad_request, + 'ActiveRecord::RecordNotFound' => :not_found, + 'ActiveRecord::StaleObjectError' => :conflict, + 'ActiveRecord::RecordInvalid' => :unprocessable_entity, + 'ActiveRecord::RecordNotSaved' => :unprocessable_entity } ``` @@ -440,7 +515,7 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`. * `config.action_view.embed_authenticity_token_in_remote_forms` allows you to set the default behavior for `authenticity_token` in forms with `remote: - true`. By default it's set to false, which means that remote forms will not + true`. By default it's set to `false`, which means that remote forms will not include `authenticity_token`, which is helpful when you're fragment-caching the form. Remote forms get the authenticity from the `meta` tag, so embedding is unnecessary unless you support browsers without JavaScript. In such case @@ -459,9 +534,9 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`. error should be raised for missing translations. * `config.action_view.automatically_disable_submit_tag` determines whether - submit_tag should automatically disable on click, this defaults to true. + submit_tag should automatically disable on click, this defaults to `true`. -* `config.action_view.debug_missing_translation` determins whether to wrap the missing translations key in a `<span>` tag or not. This defaults to true. +* `config.action_view.debug_missing_translation` determines whether to wrap the missing translations key in a `<span>` tag or not. This defaults to `true`. ### Configuring Action Mailer @@ -476,16 +551,19 @@ There are a number of settings available on `config.action_mailer`: * `:user_name` - If your mail server requires authentication, set the username in this setting. * `:password` - If your mail server requires authentication, set the password in this setting. * `:authentication` - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of `:plain`, `:login`, `:cram_md5`. + * `:enable_starttls_auto` - Detects if STARTTLS is enabled in your SMTP server and starts to use it. It defaults to `true`. + * `:openssl_verify_mode` - When using TLS, you can set how OpenSSL checks the certificate. This is useful if you need to validate a self-signed and/or a wildcard certificate. This can be one of the OpenSSL verify constants, `:none` or `:peer` -- or the constant directly `OpenSSL::SSL::VERIFY_NONE` or `OpenSSL::SSL::VERIFY_PEER`, respectively. + * `:ssl/:tls` - Enables the SMTP connection to use SMTP/TLS (SMTPS: SMTP over direct TLS connection). * `config.action_mailer.sendmail_settings` allows detailed configuration for the `sendmail` delivery method. It accepts a hash of options, which can include any of these options: * `:location` - The location of the sendmail executable. Defaults to `/usr/sbin/sendmail`. - * `:arguments` - The command line arguments. Defaults to `-i -t`. + * `:arguments` - The command line arguments. Defaults to `-i`. -* `config.action_mailer.raise_delivery_errors` specifies whether to raise an error if email delivery cannot be completed. It defaults to true. +* `config.action_mailer.raise_delivery_errors` specifies whether to raise an error if email delivery cannot be completed. It defaults to `true`. * `config.action_mailer.delivery_method` defines the delivery method and defaults to `:smtp`. See the [configuration section in the Action Mailer guide](action_mailer_basics.html#action-mailer-configuration) for more info. -* `config.action_mailer.perform_deliveries` specifies whether mail will actually be delivered and is true by default. It can be convenient to set it to false for testing. +* `config.action_mailer.perform_deliveries` specifies whether mail will actually be delivered and is true by default. It can be convenient to set it to `false` for testing. * `config.action_mailer.default_options` configures Action Mailer defaults. Use to set options like `from` or `reply_to` for every mailer. These default to: @@ -531,13 +609,16 @@ There are a number of settings available on `config.action_mailer`: * `config.action_mailer.deliver_later_queue_name` specifies the queue name for mailers. By default this is `mailers`. +* `config.action_mailer.perform_caching` specifies whether the mailer templates should perform fragment caching or not. By default this is `false` in all environments. + + ### Configuring Active Support There are a few configuration options available in Active Support: * `config.active_support.bare` enables or disables the loading of `active_support/all` when booting Rails. Defaults to `nil`, which means `active_support/all` is loaded. -* `config.active_support.test_order` sets the order that test cases are executed. Possible values are `:random` and `:sorted`. This option is set to `:random` in `config/environments/test.rb` in newly-generated applications. If you have an application that does not specify a `test_order`, it will default to `:sorted`, *until* Rails 5.0, when the default will become `:random`. +* `config.active_support.test_order` sets the order in which the test cases are executed. Possible values are `:random` and `:sorted`. Defaults to `:random`. * `config.active_support.escape_html_entities_in_json` enables or disables the escaping of HTML entities in JSON serialization. Defaults to `true`. @@ -545,7 +626,7 @@ 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 `callback_terminator.rb` which sets the value to `false`. This file is *not* added when running `rake rails: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.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`. @@ -561,7 +642,7 @@ There are a few configuration options available in Active Support: `config.active_job` provides the following configuration options: -* `config.active_job.queue_adapter` sets the adapter for the queueing backend. The default adapter is `:inline` which will perform jobs immediately. For an up-to-date list of built-in adapters see the [ActiveJob::QueueAdapters API documentation](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html). +* `config.active_job.queue_adapter` sets the adapter for the queueing backend. The default adapter is `:async`. For an up-to-date list of built-in adapters see the [ActiveJob::QueueAdapters API documentation](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html). ```ruby # Be sure to have the adapter's gem in your Gemfile @@ -610,6 +691,17 @@ There are a few configuration options available in Active Support: * `config.active_job.logger` accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Active Job. You can retrieve this logger by calling `logger` on either an Active Job class or an Active Job instance. Set to `nil` to disable logging. +### Configuring Action Cable + +* `config.action_cable.url` accepts a string for the URL for where + you are hosting your Action Cable server. You would use this option +if you are running Action Cable servers that are separated from your +main application. +* `config.action_cable.mount_path` accepts a string for where to mount Action + Cable, as part of the main server process. Defaults to `/cable`. +You can set this as nil to not mount Action Cable as part of your +normal Rails server. + ### Configuring a Database Just about every Rails application will interact with a database. You can connect to the database by setting an environment variable `ENV['DATABASE_URL']` or by using a configuration file called `config/database.yml`. @@ -751,11 +843,11 @@ development: timeout: 5000 ``` -NOTE: Rails uses an SQLite3 database for data storage by default because it is a zero configuration database that just works. Rails also supports MySQL and PostgreSQL "out of the box", and has plugins for many database systems. If you are using a database in a production environment Rails most likely has an adapter for it. +NOTE: Rails uses an SQLite3 database for data storage by default because it is a zero configuration database that just works. Rails also supports MySQL (including MariaDB) and PostgreSQL "out of the box", and has plugins for many database systems. If you are using a database in a production environment Rails most likely has an adapter for it. -#### Configuring a MySQL Database +#### Configuring a MySQL or MariaDB Database -If you choose to use MySQL instead of the shipped SQLite3 database, your `config/database.yml` will look a little different. Here's the development section: +If you choose to use MySQL or MariaDB instead of the shipped SQLite3 database, your `config/database.yml` will look a little different. Here's the development section: ```yaml development: @@ -768,7 +860,7 @@ development: socket: /tmp/mysql.sock ``` -If your development computer's MySQL installation includes a root user with an empty password, this configuration should work for you. Otherwise, change the username and password in the `development` section as appropriate. +If your development database has a root user with an empty password, this configuration should work for you. Otherwise, change the username and password in the `development` section as appropriate. #### Configuring a PostgreSQL Database @@ -810,9 +902,9 @@ development: database: db/development.sqlite3 ``` -#### Configuring a MySQL Database for JRuby Platform +#### Configuring a MySQL or MariaDB Database for JRuby Platform -If you choose to use MySQL and are using JRuby, your `config/database.yml` will look a little different. Here's the development section: +If you choose to use MySQL or MariaDB and are using JRuby, your `config/database.yml` will look a little different. Here's the development section: ```yaml development: @@ -865,7 +957,7 @@ Rails will now prepend "/app1" when generating links. #### Using Passenger -Passenger makes it easy to run your application in a subdirectory. You can find the relevant configuration in the [Passenger manual](http://www.modrails.com/documentation/Users%20guide%20Apache.html#deploying_rails_to_sub_uri). +Passenger makes it easy to run your application in a subdirectory. You can find the relevant configuration in the [Passenger manual](https://www.phusionpassenger.com/library/deploy/apache/deploy/ruby/#deploying-an-app-to-a-sub-uri-or-subdirectory). #### Using a Reverse Proxy @@ -990,110 +1082,110 @@ Because `Rails::Application` inherits from `Rails::Railtie` (indirectly), you ca Below is a comprehensive list of all the initializers found in Rails in the order that they are defined (and therefore run in, unless otherwise stated). -* `load_environment_hook` Serves as a placeholder so that `:load_environment_config` can be defined to run before it. +* `load_environment_hook`: Serves as a placeholder so that `:load_environment_config` can be defined to run before it. -* `load_active_support` Requires `active_support/dependencies` which sets up the basis for Active Support. Optionally requires `active_support/all` if `config.active_support.bare` is un-truthful, which is the default. +* `load_active_support`: Requires `active_support/dependencies` which sets up the basis for Active Support. Optionally requires `active_support/all` if `config.active_support.bare` is un-truthful, which is the default. -* `initialize_logger` Initializes the logger (an `ActiveSupport::Logger` object) for the application and makes it accessible at `Rails.logger`, provided that no initializer inserted before this point has defined `Rails.logger`. +* `initialize_logger`: Initializes the logger (an `ActiveSupport::Logger` object) for the application and makes it accessible at `Rails.logger`, provided that no initializer inserted before this point has defined `Rails.logger`. -* `initialize_cache` If `Rails.cache` isn't set yet, initializes the cache by referencing the value in `config.cache_store` and stores the outcome as `Rails.cache`. If this object responds to the `middleware` method, its middleware is inserted before `Rack::Runtime` in the middleware stack. +* `initialize_cache`: If `Rails.cache` isn't set yet, initializes the cache by referencing the value in `config.cache_store` and stores the outcome as `Rails.cache`. If this object responds to the `middleware` method, its middleware is inserted before `Rack::Runtime` in the middleware stack. -* `set_clear_dependencies_hook` This initializer - which runs only if `cache_classes` is set to `false` - uses `ActionDispatch::Callbacks.after` to remove the constants which have been referenced during the request from the object space so that they will be reloaded during the following request. +* `set_clear_dependencies_hook`: This initializer - which runs only if `cache_classes` is set to `false` - uses `ActionDispatch::Callbacks.after` to remove the constants which have been referenced during the request from the object space so that they will be reloaded during the following request. -* `initialize_dependency_mechanism` If `config.cache_classes` is true, configures `ActiveSupport::Dependencies.mechanism` to `require` dependencies rather than `load` them. +* `initialize_dependency_mechanism`: If `config.cache_classes` is true, configures `ActiveSupport::Dependencies.mechanism` to `require` dependencies rather than `load` them. -* `bootstrap_hook` Runs all configured `before_initialize` blocks. +* `bootstrap_hook`: Runs all configured `before_initialize` blocks. -* `i18n.callbacks` In the development environment, sets up a `to_prepare` callback which will call `I18n.reload!` if any of the locales have changed since the last request. In production mode this callback will only run on the first request. +* `i18n.callbacks`: In the development environment, sets up a `to_prepare` callback which will call `I18n.reload!` if any of the locales have changed since the last request. In production mode this callback will only run on the first request. -* `active_support.deprecation_behavior` Sets up deprecation reporting for environments, defaulting to `:log` for development, `:notify` for production and `:stderr` for test. If a value isn't set for `config.active_support.deprecation` then this initializer will prompt the user to configure this line in the current environment's `config/environments` file. Can be set to an array of values. +* `active_support.deprecation_behavior`: Sets up deprecation reporting for environments, defaulting to `:log` for development, `:notify` for production and `:stderr` for test. If a value isn't set for `config.active_support.deprecation` then this initializer will prompt the user to configure this line in the current environment's `config/environments` file. Can be set to an array of values. -* `active_support.initialize_time_zone` Sets the default time zone for the application based on the `config.time_zone` setting, which defaults to "UTC". +* `active_support.initialize_time_zone`: Sets the default time zone for the application based on the `config.time_zone` setting, which defaults to "UTC". -* `active_support.initialize_beginning_of_week` Sets the default beginning of week for the application based on `config.beginning_of_week` setting, which defaults to `:monday`. +* `active_support.initialize_beginning_of_week`: Sets the default beginning of week for the application based on `config.beginning_of_week` setting, which defaults to `:monday`. -* `active_support.set_configs` Sets up Active Support by using the settings in `config.active_support` by `send`'ing the method names as setters to `ActiveSupport` and passing the values through. +* `active_support.set_configs`: Sets up Active Support by using the settings in `config.active_support` by `send`'ing the method names as setters to `ActiveSupport` and passing the values through. -* `action_dispatch.configure` Configures the `ActionDispatch::Http::URL.tld_length` to be set to the value of `config.action_dispatch.tld_length`. +* `action_dispatch.configure`: Configures the `ActionDispatch::Http::URL.tld_length` to be set to the value of `config.action_dispatch.tld_length`. -* `action_view.set_configs` Sets up Action View by using the settings in `config.action_view` by `send`'ing the method names as setters to `ActionView::Base` and passing the values through. +* `action_view.set_configs`: Sets up Action View by using the settings in `config.action_view` by `send`'ing the method names as setters to `ActionView::Base` and passing the values through. -* `action_controller.assets_config` Initializes the `config.actions_controller.assets_dir` to the app's public directory if not explicitly configured +* `action_controller.assets_config`: Initializes the `config.actions_controller.assets_dir` to the app's public directory if not explicitly configured. -* `action_controller.set_helpers_path` Sets Action Controller's helpers_path to the application's helpers_path +* `action_controller.set_helpers_path`: Sets Action Controller's `helpers_path` to the application's `helpers_path`. -* `action_controller.parameters_config` Configures strong parameters options for `ActionController::Parameters` +* `action_controller.parameters_config`: Configures strong parameters options for `ActionController::Parameters`. -* `action_controller.set_configs` Sets up Action Controller by using the settings in `config.action_controller` by `send`'ing the method names as setters to `ActionController::Base` and passing the values through. +* `action_controller.set_configs`: Sets up Action Controller by using the settings in `config.action_controller` by `send`'ing the method names as setters to `ActionController::Base` and passing the values through. -* `action_controller.compile_config_methods` Initializes methods for the config settings specified so that they are quicker to access. +* `action_controller.compile_config_methods`: Initializes methods for the config settings specified so that they are quicker to access. -* `active_record.initialize_timezone` Sets `ActiveRecord::Base.time_zone_aware_attributes` to true, as well as setting `ActiveRecord::Base.default_timezone` to UTC. When attributes are read from the database, they will be converted into the time zone specified by `Time.zone`. +* `active_record.initialize_timezone`: Sets `ActiveRecord::Base.time_zone_aware_attributes` to `true`, as well as setting `ActiveRecord::Base.default_timezone` to UTC. When attributes are read from the database, they will be converted into the time zone specified by `Time.zone`. -* `active_record.logger` Sets `ActiveRecord::Base.logger` - if it's not already set - to `Rails.logger`. +* `active_record.logger`: Sets `ActiveRecord::Base.logger` - if it's not already set - to `Rails.logger`. -* `active_record.migration_error` Configures middleware to check for pending migrations +* `active_record.migration_error`: Configures middleware to check for pending migrations. -* `active_record.check_schema_cache_dump` Loads the schema cache dump if configured and available +* `active_record.check_schema_cache_dump`: Loads the schema cache dump if configured and available. -* `active_record.warn_on_records_fetched_greater_than` Enables warnings when queries return large numbers of records +* `active_record.warn_on_records_fetched_greater_than`: Enables warnings when queries return large numbers of records. -* `active_record.set_configs` Sets up Active Record by using the settings in `config.active_record` by `send`'ing the method names as setters to `ActiveRecord::Base` and passing the values through. +* `active_record.set_configs`: Sets up Active Record by using the settings in `config.active_record` by `send`'ing the method names as setters to `ActiveRecord::Base` and passing the values through. -* `active_record.initialize_database` Loads the database configuration (by default) from `config/database.yml` and establishes a connection for the current environment. +* `active_record.initialize_database`: Loads the database configuration (by default) from `config/database.yml` and establishes a connection for the current environment. -* `active_record.log_runtime` Includes `ActiveRecord::Railties::ControllerRuntime` which is responsible for reporting the time taken by Active Record calls for the request back to the logger. +* `active_record.log_runtime`: Includes `ActiveRecord::Railties::ControllerRuntime` which is responsible for reporting the time taken by Active Record calls for the request back to the logger. -* `active_record.set_reloader_hooks` Resets all reloadable connections to the database if `config.cache_classes` is set to `false`. +* `active_record.set_reloader_hooks`: Resets all reloadable connections to the database if `config.cache_classes` is set to `false`. -* `active_record.add_watchable_files` Adds `schema.rb` and `structure.sql` files to watchable files +* `active_record.add_watchable_files`: Adds `schema.rb` and `structure.sql` files to watchable files. -* `active_job.logger` Sets `ActiveJob::Base.logger` - if it's not already set - +* `active_job.logger`: Sets `ActiveJob::Base.logger` - if it's not already set - to `Rails.logger`. -* `active_job.set_configs` Sets up Active Job by using the settings in `config.active_job` by `send`'ing the method names as setters to `ActiveJob::Base` and passing the values through. +* `active_job.set_configs`: Sets up Active Job by using the settings in `config.active_job` by `send`'ing the method names as setters to `ActiveJob::Base` and passing the values through. -* `action_mailer.logger` Sets `ActionMailer::Base.logger` - if it's not already set - to `Rails.logger`. +* `action_mailer.logger`: Sets `ActionMailer::Base.logger` - if it's not already set - to `Rails.logger`. -* `action_mailer.set_configs` Sets up Action Mailer by using the settings in `config.action_mailer` by `send`'ing the method names as setters to `ActionMailer::Base` and passing the values through. +* `action_mailer.set_configs`: Sets up Action Mailer by using the settings in `config.action_mailer` by `send`'ing the method names as setters to `ActionMailer::Base` and passing the values through. -* `action_mailer.compile_config_methods` Initializes methods for the config settings specified so that they are quicker to access. +* `action_mailer.compile_config_methods`: Initializes methods for the config settings specified so that they are quicker to access. -* `set_load_path` This initializer runs before `bootstrap_hook`. Adds paths specified by `config.load_paths` and all autoload paths to `$LOAD_PATH`. +* `set_load_path`: This initializer runs before `bootstrap_hook`. Adds paths specified by `config.load_paths` and all autoload paths to `$LOAD_PATH`. -* `set_autoload_paths` This initializer runs before `bootstrap_hook`. Adds all sub-directories of `app` and paths specified by `config.autoload_paths`, `config.eager_load_paths` and `config.autoload_once_paths` to `ActiveSupport::Dependencies.autoload_paths`. +* `set_autoload_paths`: This initializer runs before `bootstrap_hook`. Adds all sub-directories of `app` and paths specified by `config.autoload_paths`, `config.eager_load_paths` and `config.autoload_once_paths` to `ActiveSupport::Dependencies.autoload_paths`. -* `add_routing_paths` Loads (by default) all `config/routes.rb` files (in the application and railties, including engines) and sets up the routes for the application. +* `add_routing_paths`: Loads (by default) all `config/routes.rb` files (in the application and railties, including engines) and sets up the routes for the application. -* `add_locales` Adds the files in `config/locales` (from the application, railties and engines) to `I18n.load_path`, making available the translations in these files. +* `add_locales`: Adds the files in `config/locales` (from the application, railties and engines) to `I18n.load_path`, making available the translations in these files. -* `add_view_paths` Adds the directory `app/views` from the application, railties and engines to the lookup path for view files for the application. +* `add_view_paths`: Adds the directory `app/views` from the application, railties and engines to the lookup path for view files for the application. -* `load_environment_config` Loads the `config/environments` file for the current environment. +* `load_environment_config`: Loads the `config/environments` file for the current environment. -* `prepend_helpers_path` Adds the directory `app/helpers` from the application, railties and engines to the lookup path for helpers for the application. +* `prepend_helpers_path`: Adds the directory `app/helpers` from the application, railties and engines to the lookup path for helpers for the application. -* `load_config_initializers` Loads all Ruby files from `config/initializers` in the application, railties and engines. The files in this directory can be used to hold configuration settings that should be made after all of the frameworks are loaded. +* `load_config_initializers`: Loads all Ruby files from `config/initializers` in the application, railties and engines. The files in this directory can be used to hold configuration settings that should be made after all of the frameworks are loaded. -* `engines_blank_point` Provides a point-in-initialization to hook into if you wish to do anything before engines are loaded. After this point, all railtie and engine initializers are run. +* `engines_blank_point`: Provides a point-in-initialization to hook into if you wish to do anything before engines are loaded. After this point, all railtie and engine initializers are run. -* `add_generator_templates` Finds templates for generators at `lib/templates` for the application, railties and engines and adds these to the `config.generators.templates` setting, which will make the templates available for all generators to reference. +* `add_generator_templates`: Finds templates for generators at `lib/templates` for the application, railties and engines and adds these to the `config.generators.templates` setting, which will make the templates available for all generators to reference. -* `ensure_autoload_once_paths_as_subset` Ensures that the `config.autoload_once_paths` only contains paths from `config.autoload_paths`. If it contains extra paths, then an exception will be raised. +* `ensure_autoload_once_paths_as_subset`: Ensures that the `config.autoload_once_paths` only contains paths from `config.autoload_paths`. If it contains extra paths, then an exception will be raised. -* `add_to_prepare_blocks` The block for every `config.to_prepare` call in the application, a railtie or engine is added to the `to_prepare` callbacks for Action Dispatch which will be run per request in development, or before the first request in production. +* `add_to_prepare_blocks`: The block for every `config.to_prepare` call in the application, a railtie or engine is added to the `to_prepare` callbacks for Action Dispatch which will be run per request in development, or before the first request in production. -* `add_builtin_route` If the application is running under the development environment then this will append the route for `rails/info/properties` to the application routes. This route provides the detailed information such as Rails and Ruby version for `public/index.html` in a default Rails application. +* `add_builtin_route`: If the application is running under the development environment then this will append the route for `rails/info/properties` to the application routes. This route provides the detailed information such as Rails and Ruby version for `public/index.html` in a default Rails application. -* `build_middleware_stack` Builds the middleware stack for the application, returning an object which has a `call` method which takes a Rack environment object for the request. +* `build_middleware_stack`: Builds the middleware stack for the application, returning an object which has a `call` method which takes a Rack environment object for the request. -* `eager_load!` If `config.eager_load` is true, runs the `config.before_eager_load` hooks and then calls `eager_load!` which will load all `config.eager_load_namespaces`. +* `eager_load!`: If `config.eager_load` is `true`, runs the `config.before_eager_load` hooks and then calls `eager_load!` which will load all `config.eager_load_namespaces`. -* `finisher_hook` Provides a hook for after the initialization of process of the application is complete, as well as running all the `config.after_initialize` blocks for the application, railties and engines. +* `finisher_hook`: Provides a hook for after the initialization of process of the application is complete, as well as running all the `config.after_initialize` blocks for the application, railties and engines. -* `set_routes_reloader` Configures Action Dispatch to reload the routes file using `ActionDispatch::Callbacks.to_prepare`. +* `set_routes_reloader_hook`: Configures Action Dispatch to reload the routes file using `ActionDispatch::Callbacks.to_prepare`. -* `disable_dependency_loading` Disables the automatic dependency loading if the `config.eager_load` is set to true. +* `disable_dependency_loading`: Disables the automatic dependency loading if the `config.eager_load` is set to `true`. Database pooling ---------------- @@ -1108,7 +1200,7 @@ development: timeout: 5000 ``` -Since the connection pooling is handled inside of Active Record by default, all application servers (Thin, mongrel, Unicorn etc.) should behave the same. The database connection pool is initially empty. As demand for connections increases it will create them until it reaches the connection pool limit. +Since the connection pooling is handled inside of Active Record by default, all application servers (Thin, Puma, Unicorn etc.) should behave the same. The database connection pool is initially empty. As demand for connections increases it will create them until it reaches the connection pool limit. Any one request will check out a connection the first time it requires access to the database. At the end of the request it will check the connection back in. This means that the additional connection slot will be available again for the next request in the queue. @@ -1129,21 +1221,25 @@ NOTE. If you are running in a multi-threaded environment, there could be a chanc Custom configuration -------------------- -You can configure your own code through the Rails configuration object with custom configuration. It works like this: +You can configure your own code through the Rails configuration object with +custom configuration under either the `config.x` namespace, or `config` directly. +The key difference between these two is that you should be using `config.x` if you +are defining _nested_ configuration (ex: `config.x.nested.nested.hi`), and just +`config` for _single level_ configuration (ex: `config.hello`). ```ruby - config.payment_processing.schedule = :daily - config.payment_processing.retries = 3 + config.x.payment_processing.schedule = :daily + config.x.payment_processing.retries = 3 config.super_debugger = true ``` These configuration points are then available through the configuration object: ```ruby - Rails.configuration.payment_processing.schedule # => :daily - Rails.configuration.payment_processing.retries # => 3 - Rails.configuration.super_debugger # => true - Rails.configuration.super_debugger.not_set # => nil + Rails.configuration.x.payment_processing.schedule # => :daily + Rails.configuration.x.payment_processing.retries # => 3 + Rails.configuration.x.payment_processing.not_set # => nil + Rails.configuration.super_debugger # => true ``` You can also use `Rails::Application.config_for` to load whole configuration files: @@ -1198,11 +1294,11 @@ Evented File System Monitor If the [listen gem](https://github.com/guard/listen) is loaded Rails uses an evented file system monitor to detect changes when `config.cache_classes` is -false: +`false`: ```ruby group :development do - gem 'listen', '~> 3.0.4' + gem 'listen', '>= 3.0.5', '< 3.2' end ``` diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 0f98d12217..830a546570 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -25,7 +25,7 @@ Reporting an Issue Ruby on Rails uses [GitHub Issue Tracking](https://github.com/rails/rails/issues) to track issues (primarily bugs and contributions of new code). If you've found a bug in Ruby on Rails, this is the place to start. You'll need to create a (free) GitHub account in order to submit an issue, to comment on them or to create pull requests. -NOTE: Bugs in the most recent released version of Ruby on Rails are likely to get the most attention. Also, the Rails core team is always interested in feedback from those who can take the time to test _edge Rails_ (the code for the version of Rails that is currently under development). Later in this guide you'll find out how to get edge Rails for testing. +NOTE: Bugs in the most recent released version of Ruby on Rails are likely to get the most attention. Also, the Rails core team is always interested in feedback from those who can take the time to test _edge Rails_ (the code for the version of Rails that is currently under development). Later in this guide, you'll find out how to get edge Rails for testing. ### Creating a Bug Report @@ -40,7 +40,9 @@ Then, don't get your hopes up! Unless you have a "Code Red, Mission Critical, th Having a way to reproduce your issue will be very helpful for others to help confirm, investigate and ultimately fix your issue. You can do this by providing an executable test case. To make this process easier, we have prepared several bug report templates for you to use as a starting point: * Template for Active Record (models, database) issues: [gem](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_gem.rb) / [master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_master.rb) +* Template for testing Active Record (migration) issues: [gem](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_migrations_gem.rb) / [master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_migrations_master.rb) * Template for Action Pack (controllers, routing) issues: [gem](https://github.com/rails/rails/blob/master/guides/bug_report_templates/action_controller_gem.rb) / [master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/action_controller_master.rb) +* Template for Active Job issues: [gem](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_job_gem.rb) / [master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_job_master.rb) * Generic template for other issues: [gem](https://github.com/rails/rails/blob/master/guides/bug_report_templates/generic_gem.rb) / [master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/generic_master.rb) These templates include the boilerplate code to set up a test case against either a released version of Rails (`*_gem.rb`) or edge Rails (`*_master.rb`). @@ -58,7 +60,7 @@ WARNING: Please do not report security vulnerabilities with public GitHub issue Please don't put "feature request" items into GitHub Issues. If there's a new feature that you want to see added to Ruby on Rails, you'll need to write the code yourself - or convince someone else to partner with you to write the code. -Later in this guide you'll find detailed instructions for proposing a patch to +Later in this guide, you'll find detailed instructions for proposing a patch to Ruby on Rails. If you enter a wish list item in GitHub Issues with no code, you can expect it to be marked "invalid" as soon as it's reviewed. @@ -130,11 +132,11 @@ learn about Ruby on Rails, and the API, which serves as a reference. You can help improve the Rails guides by making them more coherent, consistent or readable, adding missing information, correcting factual errors, fixing typos, or bringing them up to date with the latest edge Rails. -You can either open a pull request to [Rails](http://github.com/rails/rails) or -ask the [Rails core team](http://rubyonrails.org/core) for commit access on +You can either open a pull request to [Rails](https://github.com/rails/rails) or +ask the [Rails core team](http://rubyonrails.org/community/#core) for commit access on docrails if you contribute regularly. Please do not open pull requests in docrails, if you'd like to get feedback on your -change, ask for it in [Rails](http://github.com/rails/rails) instead. +change, ask for it in [Rails](https://github.com/rails/rails) instead. Docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation. @@ -174,8 +176,8 @@ Translation efforts we know about (various versions): * **Italian**: [https://github.com/rixlabs/docrails](https://github.com/rixlabs/docrails) * **Spanish**: [http://wiki.github.com/gramos/docrails](http://wiki.github.com/gramos/docrails) -* **Polish**: [http://github.com/apohllo/docrails/tree/master](http://github.com/apohllo/docrails/tree/master) -* **French** : [http://github.com/railsfrance/docrails](http://github.com/railsfrance/docrails) +* **Polish**: [https://github.com/apohllo/docrails/tree/master](https://github.com/apohllo/docrails/tree/master) +* **French** : [https://github.com/railsfrance/docrails](https://github.com/railsfrance/docrails) * **Czech** : [https://github.com/rubyonrails-cz/docrails/tree/czech](https://github.com/rubyonrails-cz/docrails/tree/czech) * **Turkish** : [https://github.com/ujk/docrails/tree/master](https://github.com/ujk/docrails/tree/master) * **Korean** : [https://github.com/rorlakr/rails-guides](https://github.com/rorlakr/rails-guides) @@ -189,7 +191,7 @@ Contributing to the Rails Code ### Setting Up a Development Environment -To move on from submitting bugs to helping resolve existing issues or contributing your own code to Ruby on Rails, you _must_ be able to run its test suite. In this section of the guide you'll learn how to setup the tests on your own computer. +To move on from submitting bugs to helping resolve existing issues or contributing your own code to Ruby on Rails, you _must_ be able to run its test suite. In this section of the guide, you'll learn how to setup the tests on your own computer. #### The Easy Way @@ -268,40 +270,31 @@ The above are guidelines - please use your best judgment in using them. ### Benchmark Your Code -If your change has an impact on the performance of Rails, please use the -[benchmark-ips](https://github.com/evanphx/benchmark-ips) gem to provide -benchmark results for comparison. - -Here's an example of using benchmark-ips: - -```ruby -require 'benchmark/ips' - -Benchmark.ips do |x| - x.report('addition') { 1 + 2 } - x.report('addition with send') { 1.send(:+, 2) } -end -``` - -This will generate a report with the following information: - -``` -Calculating ------------------------------------- - addition 132.013k i/100ms - addition with send 125.413k i/100ms -------------------------------------------------- - addition 9.677M (± 1.7%) i/s - 48.449M - addition with send 6.794M (± 1.1%) i/s - 33.987M -``` - -Please see the benchmark/ips [README](https://github.com/evanphx/benchmark-ips/blob/master/README.md) for more information. +For changes that might have an impact on performance, please benchmark your +code and measure the impact. Please share the benchmark script you used as well +as the results. You should consider including this information in your commit +message, which allows future contributors to easily verify your findings and +determine if they are still relevant. (For example, future optimizations in the +Ruby VM might render certain optimizations unnecessary.) + +It is very easy to make an optimization that improves performance for a +specific scenario you care about but regresses on other common cases. +Therefore, you should test your change against a list of representative +scenarios. Ideally, they should be based on real-world scenarios extracted +from production applications. + +You can use the [benchmark template](https://github.com/rails/rails/blob/master/guides/bug_report_templates/benchmark.rb) +as a starting point. It includes the boilerplate code to setup a benchmark +using the [benchmark-ips](https://github.com/evanphx/benchmark-ips) gem. The +template is designed for testing relatively self-contained changes that can be +inlined into the script. ### Running Tests It is not customary in Rails to run the full test suite before pushing -changes. The railties test suite in particular takes a long time, and even -more if the source code is mounted in `/vagrant` as happens in the recommended -workflow with the [rails-dev-box](https://github.com/rails/rails-dev-box). +changes. The railties test suite in particular takes a long time, and takes an +especially long time if the source code is mounted in `/vagrant` as happens in +the recommended workflow with the [rails-dev-box](https://github.com/rails/rails-dev-box). As a compromise, test what your code obviously affects, and if the change is not in railties, run the whole test suite of the affected component. If all @@ -367,7 +360,7 @@ Finally, $ bundle exec rake test ``` -will now run the four of them in turn. +will now run the three of them in turn. You can also run any single test separately: @@ -662,7 +655,7 @@ Changes that are merged into master are intended for the next major release of R For simple fixes, the easiest way to backport your changes is to [extract a diff from your changes in master and apply them to the target branch](http://ariejan.net/2009/10/26/how-to-create-and-apply-a-patch-with-git). -First make sure your changes are the only difference between your current branch and master: +First, make sure your changes are the only difference between your current branch and master: ```bash $ git log master..HEAD @@ -677,7 +670,7 @@ $ git format-patch master --stdout > ~/my_changes.patch Switch over to the target branch and apply your changes: ```bash -$ git checkout -b my_backport_branch 3-2-stable +$ git checkout -b my_backport_branch 4-2-stable $ git apply ~/my_changes.patch ``` diff --git a/guides/source/credits.html.erb b/guides/source/credits.html.erb index 1d995581fa..5adbd12ac0 100644 --- a/guides/source/credits.html.erb +++ b/guides/source/credits.html.erb @@ -22,7 +22,7 @@ Ruby on Rails Guides: Credits <h3 class="section">Rails Guides Designers</h3> <%= author('Jason Zimdars', 'jz') do %> - Jason Zimdars is an experienced creative director and web designer who has lead UI and UX design for numerous websites and web applications. You can see more of his design and writing at <a href="http://www.thinkcage.com/">Thinkcage.com</a> or follow him on <a href="http://twitter.com/JZ">Twitter</a>. + Jason Zimdars is an experienced creative director and web designer who has lead UI and UX design for numerous websites and web applications. You can see more of his design and writing at <a href="http://www.thinkcage.com/">Thinkcage.com</a> or follow him on <a href="https://twitter.com/jasonzimdars">Twitter</a>. <% end %> <h3 class="section">Rails Guides Authors</h3> @@ -32,7 +32,7 @@ Ruby on Rails Guides: Credits <% end %> <%= author('Oscar Del Ben', 'oscardelben', 'oscardelben.jpg') do %> -Oscar Del Ben is a software engineer at <a href="http://www.wildfireapp.com/">Wildfire</a>. He's a regular open source contributor (<a href="https://github.com/oscardelben">GitHub account</a>) and tweets regularly at <a href="https://twitter.com/oscardelben">@oscardelben</a>. +Oscar Del Ben is a software engineer at <a href="http://www.businessinsider.com/google-buys-wildfire-2012-8">Wildfire</a>. He's a regular open source contributor (<a href="https://github.com/oscardelben">GitHub account</a>) and tweets regularly at <a href="https://twitter.com/oscardelben">@oscardelben</a>. <% end %> <%= author('Frederick Cheung', 'fcheung') do %> @@ -64,7 +64,7 @@ Oscar Del Ben is a software engineer at <a href="http://www.wildfireapp.com/">Wi <% end %> <%= author('Pratik Naik', 'lifo') do %> - Pratik Naik is a Ruby on Rails developer at <a href="https://basecamp.com/">Basecamp</a> and also a member of the <a href="http://rubyonrails.org/core">Rails core team</a>. He maintains a blog at <a href="http://m.onkey.org">has_many :bugs, :through => :rails</a> and has a semi-active <a href="http://twitter.com/lifo">twitter account</a>. + Pratik Naik is a Ruby on Rails developer at <a href="https://basecamp.com/">Basecamp</a> and maintains a blog at <a href="http://m.onkey.org">has_many :bugs, :through => :rails</a>. He also has a semi-active <a href="http://twitter.com/lifo">twitter account</a>. <% end %> <%= author('Emilio Tagua', 'miloops') do %> diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md index 35ad6eb705..df3003a6a8 100644 --- a/guides/source/debugging_rails_applications.md +++ b/guides/source/debugging_rails_applications.md @@ -109,18 +109,18 @@ It can also be useful to save information to log files at runtime. Rails maintai Rails makes use of the `ActiveSupport::Logger` class to write log information. Other loggers, such as `Log4r`, may also be substituted. -You can specify an alternative logger in `environment.rb` or any other environment file, for example: +You can specify an alternative logger in `config/application.rb` or any other environment file, for example: ```ruby -Rails.logger = Logger.new(STDOUT) -Rails.logger = Log4r::Logger.new("Application Log") +config.logger = Logger.new(STDOUT) +config.logger = Log4r::Logger.new("Application Log") ``` Or in the `Initializer` section, add _any_ of the following ```ruby -config.logger = Logger.new(STDOUT) -config.logger = Log4r::Logger.new("Application Log") +Rails.logger = Logger.new(STDOUT) +Rails.logger = Log4r::Logger.new("Application Log") ``` TIP: By default, each log is created under `Rails.root/log/` and the log file is named after the environment in which the application is running. @@ -255,7 +255,8 @@ is your best companion. The debugger can also help you if you want to learn about the Rails source code but don't know where to start. Just debug any request to your application and -use this guide to learn how to move from the code you have written into the underlying Rails code. +use this guide to learn how to move from the code you have written into the +underlying Rails code. ### Setup @@ -314,13 +315,12 @@ For example: => Booting Puma => Rails 5.0.0 application starting in development on http://0.0.0.0:3000 => Run `rails server -h` for more startup options -=> Ctrl-C to shutdown server -Puma 2.15.3 starting... -* Min threads: 0, max threads: 16 +Puma starting in single mode... +* Version 3.4.0 (ruby 2.3.1-p112), codename: Owl Bowl Brawl +* Min threads: 5, max threads: 5 * Environment: development * Listening on tcp://localhost:3000 - - +Use Ctrl-C to stop Started GET "/" for 127.0.0.1 at 2014-04-11 13:11:48 +0200 ActiveRecord::SchemaMigration Load (0.2ms) SELECT "schema_migrations".* FROM "schema_migrations" Processing by ArticlesController#index as HTML @@ -336,7 +336,6 @@ Processing by ArticlesController#index as HTML 10: respond_to do |format| 11: format.html # index.html.erb 12: format.json { render json: @articles } - (byebug) ``` @@ -346,11 +345,45 @@ by asking the debugger for help. Type: `help` ``` (byebug) help - h[elp][ <cmd>[ <subcmd>]] + break -- Sets breakpoints in the source code + catch -- Handles exception catchpoints + condition -- Sets conditions on breakpoints + continue -- Runs until program ends, hits a breakpoint or reaches a line + debug -- Spawns a subdebugger + delete -- Deletes breakpoints + disable -- Disables breakpoints or displays + display -- Evaluates expressions every time the debugger stops + down -- Moves to a lower frame in the stack trace + edit -- Edits source files + enable -- Enables breakpoints or displays + finish -- Runs the program until frame returns + frame -- Moves to a frame in the call stack + help -- Helps you using byebug + history -- Shows byebug's history of commands + info -- Shows several informations about the program being debugged + interrupt -- Interrupts the program + irb -- Starts an IRB session + kill -- Sends a signal to the current process + list -- Lists lines of source code + method -- Shows methods of an object, class or module + next -- Runs one or more lines of code + pry -- Starts a Pry session + quit -- Exits byebug + restart -- Restarts the debugged program + save -- Saves current byebug session to a file + set -- Modifies byebug settings + show -- Shows byebug settings + source -- Restores a previously saved byebug session + step -- Steps into blocks or methods one or more times + thread -- Commands to manipulate threads + tracevar -- Enables tracing of a global variable + undisplay -- Stops displaying all or some expressions when program stops + untracevar -- Stops tracing a global variable + up -- Moves to a higher frame in the stack trace + var -- Shows variables and its values + where -- Displays the backtrace - help -- prints this help. - help <cmd> -- prints help on command <cmd>. - help <cmd> <subcmd> -- prints help on <cmd>'s subcommand <subcmd>. +(byebug) ``` To see the previous ten lines you should type `list-` (or `l-`). @@ -369,12 +402,11 @@ To see the previous ten lines you should type `list-` (or `l-`). 8 @articles = Article.find_recent 9 10 respond_to do |format| - ``` -This way you can move inside the file and see the code above -the line where you added the `byebug` call. Finally, to see where you are in -the code again you can type `list=` +This way you can move inside the file and see the code above the line where you +added the `byebug` call. Finally, to see where you are in the code again you can +type `list=` ``` (byebug) list= @@ -390,7 +422,6 @@ the code again you can type `list=` 10: respond_to do |format| 11: format.html # index.html.erb 12: format.json { render json: @articles } - (byebug) ``` @@ -412,46 +443,45 @@ then `backtrace` will supply the answer. ``` (byebug) where --> #0 ArticlesController.index - at /PathTo/project/test_app/app/controllers/articles_controller.rb:8 - #1 ActionController::ImplicitRender.send_action(method#String, *args#Array) - at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/implicit_render.rb:4 + at /PathToProject/app/controllers/articles_controller.rb:8 + #1 ActionController::BasicImplicitRender.send_action(method#String, *args#Array) + at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/basic_implicit_render.rb:4 #2 AbstractController::Base.process_action(action#NilClass, *args#Array) - at /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb:189 - #3 ActionController::Rendering.process_action(action#NilClass, *args#NilClass) - at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/rendering.rb:10 + at /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb:181 + #3 ActionController::Rendering.process_action(action, *args) + at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/rendering.rb:30 ... ``` The current frame is marked with `-->`. You can move anywhere you want in this -trace (thus changing the context) by using the `frame _n_` command, where _n_ is +trace (thus changing the context) by using the `frame n` command, where _n_ is the specified frame number. If you do that, `byebug` will display your new context. ``` (byebug) frame 2 -[184, 193] in /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb - 184: # is the intended way to override action dispatching. - 185: # - 186: # Notice that the first argument is the method to be dispatched - 187: # which is *not* necessarily the same as the action name. - 188: def process_action(method_name, *args) -=> 189: send_action(method_name, *args) - 190: end - 191: - 192: # Actually call the method associated with the action. Override - 193: # this method if you wish to change how action methods are called, - +[176, 185] in /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb + 176: # is the intended way to override action dispatching. + 177: # + 178: # Notice that the first argument is the method to be dispatched + 179: # which is *not* necessarily the same as the action name. + 180: def process_action(method_name, *args) +=> 181: send_action(method_name, *args) + 182: end + 183: + 184: # Actually call the method associated with the action. Override + 185: # this method if you wish to change how action methods are called, (byebug) ``` The available variables are the same as if you were running the code line by line. After all, that's what debugging is. -You can also use `up [n]` (`u` for abbreviated) and `down [n]` commands in order -to change the context _n_ frames up or down the stack respectively. _n_ defaults -to one. Up in this case is towards higher-numbered stack frames, and down is -towards lower-numbered stack frames. +You can also use `up [n]` and `down [n]` commands in order to change the context +_n_ frames up or down the stack respectively. _n_ defaults to one. Up in this +case is towards higher-numbered stack frames, and down is towards lower-numbered +stack frames. ### Threads @@ -460,11 +490,11 @@ the `thread` command (or the abbreviated `th`). This command has a handful of options: * `thread`: shows the current thread. -* `thread list`: is used to list all threads and their statuses. The plus + -character and the number indicates the current thread of execution. -* `thread stop _n_`: stop thread _n_. -* `thread resume _n_`: resumes thread _n_. -* `thread switch _n_`: switches the current thread context to _n_. +* `thread list`: is used to list all threads and their statuses. The current +thread is marked with a plus (+) sign. +* `thread stop n`: stops thread _n_. +* `thread resume n`: resumes thread _n_. +* `thread switch n`: switches the current thread context to _n_. This command is very helpful when you are debugging concurrent threads and need to verify that there are no race conditions in your code. @@ -491,9 +521,9 @@ current context: 12: format.json { render json: @articles } (byebug) instance_variables -[:@_action_has_layout, :@_routes, :@_headers, :@_status, :@_request, - :@_response, :@_prefixes, :@_lookup_context, :@_action_name, - :@_response_body, :@marked_for_same_origin_verification, :@_config] +[:@_action_has_layout, :@_routes, :@_request, :@_response, :@_lookup_context, + :@_action_name, :@_response_body, :@marked_for_same_origin_verification, + :@_config] ``` As you may have figured out, all of the variables that you can access from a @@ -503,6 +533,7 @@ command later in this guide). ``` (byebug) next + [5, 14] in /PathTo/project/app/controllers/articles_controller.rb 5 # GET /articles.json 6 def index @@ -522,29 +553,35 @@ And then ask again for the instance_variables: ``` (byebug) instance_variables -[:@_action_has_layout, :@_routes, :@_headers, :@_status, :@_request, - :@_response, :@_prefixes, :@_lookup_context, :@_action_name, - :@_response_body, :@marked_for_same_origin_verification, :@_config, - :@articles] +[:@_action_has_layout, :@_routes, :@_request, :@_response, :@_lookup_context, + :@_action_name, :@_response_body, :@marked_for_same_origin_verification, + :@_config, :@articles] ``` -Now `@articles` is included in the instance variables, because the line defining it -was executed. +Now `@articles` is included in the instance variables, because the line defining +it was executed. TIP: You can also step into **irb** mode with the command `irb` (of course!). -This will start an irb session within the context you invoked it. But -be warned: this is an experimental feature. +This will start an irb session within the context you invoked it. The `var` method is the most convenient way to show variables and their values. Let's have `byebug` help us with it. ``` (byebug) help var -v[ar] cl[ass] show class variables of self -v[ar] const <object> show constants of object -v[ar] g[lobal] show global variables -v[ar] i[nstance] <object> show instance variables of object -v[ar] l[ocal] show local variables + + [v]ar <subcommand> + + Shows variables and its values + + + var all -- Shows local, global and instance variables of self. + var args -- Information about arguments of the current scope + var const -- Shows constants of an object. + var global -- Shows global variables. + var instance -- Shows instance variables of self or a specific object. + var local -- Shows local variables in current scope. + ``` This is a great way to inspect the values of the current context variables. For @@ -562,16 +599,17 @@ You can also inspect for an object method this way: @_start_transaction_state = {} @aggregation_cache = {} @association_cache = {} -@attributes = {"id"=>nil, "created_at"=>nil, "updated_at"=>nil} -@attributes_cache = {} -@changed_attributes = nil -... +@attributes = #<ActiveRecord::AttributeSet:0x007fd0682a9b18 @attributes={"id"=>#<ActiveRecord::Attribute::FromDatabase:0x007fd0682a9a00 @name="id", @value_be... +@destroyed = false +@destroyed_by_association = nil +@marked_for_destruction = false +@new_record = true +@readonly = false +@transaction_state = nil +@txn = nil ``` -TIP: The commands `p` (print) and `pp` (pretty print) can be used to evaluate -Ruby expressions and display the value of variables to the console. - -You can use also `display` to start watching variables. This is a good way of +You can also use `display` to start watching variables. This is a good way of tracking the values of a variable while the execution goes on. ``` @@ -580,7 +618,7 @@ tracking the values of a variable while the execution goes on. ``` The variables inside the displayed list will be printed with their values after -you move in the stack. To stop displaying a variable use `undisplay _n_` where +you move in the stack. To stop displaying a variable use `undisplay n` where _n_ is the variable number (1 in the last example). ### Step by Step @@ -590,32 +628,23 @@ available variables. But let's continue and move on with the application execution. Use `step` (abbreviated `s`) to continue running your program until the next -logical stopping point and return control to the debugger. - -You may also use `next` which is similar to step, but function or method calls -that appear within the line of code are executed without stopping. - -TIP: You can also use `step n` or `next n` to move forwards `n` steps at once. - -The difference between `next` and `step` is that `step` stops at the next line -of code executed, doing just a single step, while `next` moves to the next line -without descending inside methods. +logical stopping point and return control to the debugger. `next` is similar to +`step`, but while `step` stops at the next line of code executed, doing just a +single step, `next` moves to the next line without descending inside methods. For example, consider the following situation: -```ruby +``` Started GET "/" for 127.0.0.1 at 2014-04-11 13:39:23 +0200 Processing by ArticlesController#index as HTML -[1, 8] in /home/davidr/Proyectos/test_app/app/models/article.rb +[1, 6] in /PathToProject/app/models/article.rb 1: class Article < ApplicationRecord - 2: - 3: def self.find_recent(limit = 10) - 4: byebug -=> 5: where('created_at > ?', 1.week.ago).limit(limit) - 6: end - 7: - 8: end + 2: def self.find_recent(limit = 10) + 3: byebug +=> 4: where('created_at > ?', 1.week.ago).limit(limit) + 5: end + 6: end (byebug) ``` @@ -627,11 +656,7 @@ method. ``` (byebug) next - -Next advances to the next line (line 6: `end`), which returns to the next line -of the caller method: - -[4, 13] in /PathTo/project/test_app/app/controllers/articles_controller.rb +[4, 13] in /PathToProject/app/controllers/articles_controller.rb 4: # GET /articles 5: # GET /articles.json 6: def index @@ -652,23 +677,24 @@ Ruby instruction to be executed -- in this case, Active Support's `week` method. ``` (byebug) step -[50, 59] in /PathToGems/activesupport-5.0.0/lib/active_support/core_ext/numeric/time.rb - 50: ActiveSupport::Duration.new(self * 24.hours, [[:days, self]]) - 51: end - 52: alias :day :days - 53: - 54: def weeks -=> 55: ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]]) - 56: end - 57: alias :week :weeks - 58: - 59: def fortnights - +[49, 58] in /PathToGems/activesupport-5.0.0/lib/active_support/core_ext/numeric/time.rb + 49: + 50: # Returns a Duration instance matching the number of weeks provided. + 51: # + 52: # 2.weeks # => 14 days + 53: def weeks +=> 54: ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]]) + 55: end + 56: alias :week :weeks + 57: + 58: # Returns a Duration instance matching the number of fortnights provided. (byebug) ``` This is one of the best ways to find bugs in your code. +TIP: You can also use `step n` or `next n` to move forward `n` steps at once. + ### Breakpoints A breakpoint makes your application stop whenever a certain point in the program @@ -677,19 +703,18 @@ is reached. The debugger shell is invoked in that line. You can add breakpoints dynamically with the command `break` (or just `b`). There are 3 possible ways of adding breakpoints manually: -* `break line`: set breakpoint in the _line_ in the current source file. -* `break file:line [if expression]`: set breakpoint in the _line_ number inside -the _file_. If an _expression_ is given it must evaluated to _true_ to fire up -the debugger. +* `break n`: set breakpoint in line number _n_ in the current source file. +* `break file:n [if expression]`: set breakpoint in line number _n_ inside +file named _file_. If an _expression_ is given it must evaluated to _true_ to +fire up the debugger. * `break class(.|\#)method [if expression]`: set breakpoint in _method_ (. and \# for class and instance method respectively) defined in _class_. The -_expression_ works the same way as with file:line. - +_expression_ works the same way as with file:n. For example, in the previous situation ``` -[4, 13] in /PathTo/project/app/controllers/articles_controller.rb +[4, 13] in /PathToProject/app/controllers/articles_controller.rb 4: # GET /articles 5: # GET /articles.json 6: def index @@ -702,20 +727,20 @@ For example, in the previous situation 13: end (byebug) break 11 -Created breakpoint 1 at /PathTo/project/app/controllers/articles_controller.rb:11 +Successfully created breakpoint with id 1 ``` -Use `info breakpoints _n_` or `info break _n_` to list breakpoints. If you -supply a number, it lists that breakpoint. Otherwise it lists all breakpoints. +Use `info breakpoints` to list breakpoints. If you supply a number, it lists +that breakpoint. Otherwise it lists all breakpoints. ``` (byebug) info breakpoints Num Enb What -1 y at /PathTo/project/app/controllers/articles_controller.rb:11 +1 y at /PathToProject/app/controllers/articles_controller.rb:11 ``` -To delete breakpoints: use the command `delete _n_` to remove the breakpoint +To delete breakpoints: use the command `delete n` to remove the breakpoint number _n_. If no number is specified, it deletes all breakpoints that are currently active. @@ -727,10 +752,11 @@ No breakpoints. You can also enable or disable breakpoints: -* `enable breakpoints`: allow a _breakpoints_ list or all of them if no list is -specified, to stop your program. This is the default state when you create a +* `enable breakpoints [n [m [...]]]`: allows a specific breakpoint list or all +breakpoints to stop your program. This is the default state when you create a breakpoint. -* `disable breakpoints`: the _breakpoints_ will have no effect on your program. +* `disable breakpoints [n [m [...]]]`: make certain (or all) breakpoints have +no effect on your program. ### Catching Exceptions @@ -745,24 +771,22 @@ To list all active catchpoints use `catch`. There are two ways to resume execution of an application that is stopped in the debugger: -* `continue [line-specification]` \(or `c`): resume program execution, at the -address where your script last stopped; any breakpoints set at that address are -bypassed. The optional argument line-specification allows you to specify a line -number to set a one-time breakpoint which is deleted when that breakpoint is -reached. -* `finish [frame-number]` \(or `fin`): execute until the selected stack frame -returns. If no frame number is given, the application will run until the -currently selected frame returns. The currently selected frame starts out the -most-recent frame or 0 if no frame positioning (e.g up, down or frame) has been -performed. If a frame number is given it will run until the specified frame -returns. +* `continue [n]`: resumes program execution at the address where your script last +stopped; any breakpoints set at that address are bypassed. The optional argument +`n` allows you to specify a line number to set a one-time breakpoint which is +deleted when that breakpoint is reached. +* `finish [n]`: execute until the selected stack frame returns. If no frame +number is given, the application will run until the currently selected frame +returns. The currently selected frame starts out the most-recent frame or 0 if +no frame positioning (e.g up, down or frame) has been performed. If a frame +number is given it will run until the specified frame returns. ### Editing Two commands allow you to open code from the debugger into an editor: -* `edit [file:line]`: edit _file_ using the editor specified by the EDITOR -environment variable. A specific _line_ can also be given. +* `edit [file:n]`: edit file named _file_ using the editor specified by the +EDITOR environment variable. A specific line _n_ can also be given. ### Quitting @@ -776,21 +800,43 @@ will be stopped and you will have to start it again. `byebug` has a few available options to tweak its behavior: -* `set autoreload`: Reload source code when changed (defaults: true). -* `set autolist`: Execute `list` command on every breakpoint (defaults: true). -* `set listsize _n_`: Set number of source lines to list by default to _n_ -(defaults: 10) -* `set forcestep`: Make sure the `next` and `step` commands always move to a new -line. +``` +(byebug) help set + + set <setting> <value> -You can see the full list by using `help set`. Use `help set _subcommand_` to -learn about a particular `set` command. + Modifies byebug settings + + Boolean values take "on", "off", "true", "false", "1" or "0". If you + don't specify a value, the boolean setting will be enabled. Conversely, + you can use "set no<setting>" to disable them. + + You can see these environment settings with the "show" command. + + List of supported settings: + + autosave -- Automatically save command history record on exit + autolist -- Invoke list command on every stop + width -- Number of characters per line in byebug's output + autoirb -- Invoke IRB on every stop + basename -- <file>:<line> information after every stop uses short paths + linetrace -- Enable line execution tracing + autopry -- Invoke Pry on every stop + stack_on_error -- Display stack trace when `eval` raises an exception + fullpath -- Display full file names in backtraces + histfile -- File where cmd history is saved to. Default: ./.byebug_history + listsize -- Set number of source lines to list by default + post_mortem -- Enable/disable post-mortem mode + callstyle -- Set how you want method call parameters to be displayed + histsize -- Maximum number of commands that can be stored in byebug history + savefile -- File where settings are saved to. Default: ~/.byebug_save +``` TIP: You can save these settings in an `.byebugrc` file in your home directory. The debugger reads these global settings when it starts. For example: ```bash -set forcestep +set callstyle short set listsize 25 ``` @@ -900,16 +946,10 @@ development that will end your tailing of development.log. Have all information about your Rails app requests in the browser — in the Developer Tools panel. Provides insight to db/rendering/total times, parameter list, rendered views and more. +* [Pry](https://github.com/pry/pry) An IRB alternative and runtime developer console. References ---------- -* [ruby-debug Homepage](http://bashdb.sourceforge.net/ruby-debug/home-page.html) -* [debugger Homepage](https://github.com/cldwalker/debugger) * [byebug Homepage](https://github.com/deivid-rodriguez/byebug) * [web-console Homepage](https://github.com/rails/web-console) -* [Article: Debugging a Rails application with ruby-debug](http://www.sitepoint.com/debug-rails-app-ruby-debug/) -* [Ryan Bates' debugging ruby (revised) screencast](http://railscasts.com/episodes/54-debugging-ruby-revised) -* [Ryan Bates' stack trace screencast](http://railscasts.com/episodes/24-the-stack-trace) -* [Ryan Bates' logger screencast](http://railscasts.com/episodes/56-the-logger) -* [Debugging with ruby-debug](http://bashdb.sourceforge.net/ruby-debug.html) diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md index 7beb8f72a9..20cd34c182 100644 --- a/guides/source/development_dependencies_install.md +++ b/guides/source/development_dependencies_install.md @@ -30,7 +30,6 @@ Ruby on Rails uses Git for source code control. The [Git homepage](http://git-sc * [Try Git course](http://try.github.io/) is an interactive course that will teach you the basics. * The [official Documentation](http://git-scm.com/documentation) is pretty comprehensive and also contains some videos with the basics of Git. * [Everyday Git](http://schacon.github.io/git/everyday.html) will teach you just enough about Git to get by. -* The [PeepCode screencast](https://peepcode.com/products/git) on Git is easier to follow. * [GitHub](http://help.github.com) offers links to a variety of Git resources. * [Pro Git](http://git-scm.com/book) is an entire book about Git with a Creative Commons license. @@ -289,3 +288,46 @@ NOTE: Using the rake task to create the test databases ensures they have the cor NOTE: You'll see the following warning (or localized warning) during activating HStore extension in PostgreSQL 9.1.x or earlier: "WARNING: => is deprecated as an operator". If you're using another database, check the file `activerecord/test/config.yml` or `activerecord/test/config.example.yml` for default connection information. You can edit `activerecord/test/config.yml` to provide different credentials on your machine if you must, but obviously you should not push any such changes back to Rails. + +### Action Cable Setup + +Action Cable uses Redis as its default subscriptions adapter ([read more](action_cable_overview.html#broadcasting)). Thus, in order to have Action Cable's tests passing you need to install and have Redis running. + +#### Install Redis From Source + +Redis' documentation discourage installations with package managers as those are usually outdated. Installing from source and bringing the server up is straight forward and well documented on [Redis' documentation](http://redis.io/download#installation). + +#### Install Redis From Package Manager + +On OS X, you can run: + +```bash +$ brew install redis +``` + +Follow the instructions given by Homebrew to start these. + +In Ubuntu just run: + +```bash +$ sudo apt-get install redis-server +``` + +On Fedora or CentOS (requires EPEL enabled), just run: + +```bash +$ sudo yum install redis +``` + +If you are running Arch Linux just run: + +```bash +$ sudo pacman -S redis +$ sudo systemctl start redis +``` + +FreeBSD users will have to run the following: + +```bash +# portmaster databases/redis +``` diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml index 2cf613f47f..2925fb4b58 100644 --- a/guides/source/documents.yaml +++ b/guides/source/documents.yaml @@ -85,9 +85,8 @@ description: This guide provides you with all you need to get started creating, enqueuing, and executing background jobs. - name: Testing Rails Applications - work_in_progress: true url: testing.html - description: This is a rather comprehensive guide to the various testing facilities in Rails. It covers everything from 'What is a test?' to the testing APIs. Enjoy. + description: This is a rather comprehensive guide to the various testing facilities in Rails. It covers everything from 'What is a test?' to Integration Testing. Enjoy. - name: Securing Rails Applications url: security.html @@ -101,9 +100,9 @@ url: configuring.html description: This guide covers the basic configuration settings for a Rails application. - - name: Rails Command Line Tools and Rake Tasks + name: The Rails Command Line url: command_line.html - description: This guide covers the command line tools and rake tasks provided by Rails. + description: This guide covers the command line tools provided by Rails. - name: Asset Pipeline url: asset_pipeline.html @@ -116,7 +115,7 @@ name: The Rails Initialization Process work_in_progress: true url: initialization.html - description: This guide explains the internals of the Rails initialization process as of Rails 4. + description: This guide explains the internals of the Rails initialization process. - name: Autoloading and Reloading Constants url: autoloading_and_reloading_constants.html @@ -139,6 +138,10 @@ name: Using Rails for API-only Applications url: api_app.html description: This guide explains how to effectively use Rails to develop a JSON API application. + - + name: Action Cable Overview + url: action_cable_overview.html + description: This guide explains how Action Cable works, and how to use WebSockets to create real-time features. - name: Extending Rails @@ -191,6 +194,10 @@ url: upgrading_ruby_on_rails.html description: This guide helps in upgrading applications to latest Ruby on Rails versions. - + name: Ruby on Rails 5.0 Release Notes + url: 5_0_release_notes.html + description: Release notes for Rails 5.0. + - name: Ruby on Rails 4.2 Release Notes url: 4_2_release_notes.html description: Release notes for Rails 4.2. diff --git a/guides/source/engines.md b/guides/source/engines.md index db50ad278f..0020112a1c 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -11,9 +11,9 @@ After reading this guide, you will know: * What makes an engine. * How to generate an engine. -* Building features for the engine. -* Hooking the engine into an application. -* Overriding engine functionality in the application. +* How to build features for the engine. +* How to hook the engine into an application. +* How to override engine functionality in the application. -------------------------------------------------------------------------------- @@ -25,7 +25,7 @@ their host applications. A Rails application is actually just a "supercharged" engine, with the `Rails::Application` class inheriting a lot of its behavior from `Rails::Engine`. -Therefore, engines and applications can be thought of almost the same thing, +Therefore, engines and applications can be thought of as almost the same thing, just with subtle differences, as you'll see throughout this guide. Engines and applications also share a common structure. @@ -46,7 +46,7 @@ see how to hook it into an application. Engines can also be isolated from their host applications. This means that an application is able to have a path provided by a routing helper such as -`articles_path` and use an engine also that provides a path also called +`articles_path` and use an engine that also provides a path also called `articles_path`, and the two would not clash. Along with this, controllers, models and table names are also namespaced. You'll see how to do this later in this guide. @@ -184,7 +184,7 @@ end By inheriting from the `Rails::Engine` class, this gem notifies Rails that there's an engine at the specified path, and will correctly mount the engine inside the application, performing tasks such as adding the `app` directory of -the engine to the load path for models, mailers, controllers and views. +the engine to the load path for models, mailers, controllers, and views. The `isolate_namespace` method here deserves special notice. This call is responsible for isolating the controllers, models, routes and other things into @@ -402,8 +402,8 @@ module Blorgh end ``` -NOTE: The `ApplicationController` class being inherited from here is the -`Blorgh::ApplicationController`, not an application's `ApplicationController`. +NOTE: The `ArticlesController` class inherits from +`Blorgh::ApplicationController`, not the application's `ApplicationController`. The helper inside `app/helpers/blorgh/articles_helper.rb` is also namespaced: @@ -799,7 +799,7 @@ before the article is saved. It will also need to have an `attr_accessor` set up for this field, so that the setter and getter methods are defined for it. To do all this, you'll need to add the `attr_accessor` for `author_name`, the -association for the author and the `before_save` call into +association for the author and the `before_validation` call into `app/models/blorgh/article.rb`. The `author` association will be hard-coded to the `User` class for the time being. @@ -807,7 +807,7 @@ association for the author and the `before_save` call into attr_accessor :author_name belongs_to :author, class_name: "User" -before_save :set_author +before_validation :set_author private def set_author @@ -1209,7 +1209,7 @@ module Blorgh::Concerns::Models::Article attr_accessor :author_name belongs_to :author, class_name: "User" - before_save :set_author + before_validation :set_author private def set_author @@ -1364,7 +1364,7 @@ You can define assets for precompilation in `engine.rb`: ```ruby initializer "blorgh.assets.precompile" do |app| - app.config.assets.precompile += %w(admin.css admin.js) + app.config.assets.precompile += %w( admin.js admin.css ) end ``` diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md index 2a289dd33a..048fe190e8 100644 --- a/guides/source/form_helpers.md +++ b/guides/source/form_helpers.md @@ -174,7 +174,6 @@ URL fields, email fields, number fields and range fields: <%= search_field(:user, :name) %> <%= telephone_field(:user, :phone) %> <%= date_field(:user, :born_on) %> -<%= datetime_field(:user, :meeting_time) %> <%= datetime_local_field(:user, :graduation_day) %> <%= month_field(:user, :birthday_month) %> <%= week_field(:user, :birthday_week) %> @@ -195,7 +194,6 @@ Output: <input id="user_name" name="user[name]" type="search" /> <input id="user_phone" name="user[phone]" type="tel" /> <input id="user_born_on" name="user[born_on]" type="date" /> -<input id="user_meeting_time" name="user[meeting_time]" type="datetime" /> <input id="user_graduation_day" name="user[graduation_day]" type="datetime-local" /> <input id="user_birthday_month" name="user[birthday_month]" type="month" /> <input id="user_birthday_week" name="user[birthday_week]" type="week" /> @@ -317,7 +315,7 @@ The Article model is directly available to users of the application, so - follow resources :articles ``` -TIP: Declaring a resource has a number of side-affects. See [Rails Routing From the Outside In](routing.html#resource-routing-the-rails-default) for more information on setting up and using resources. +TIP: Declaring a resource has a number of side effects. See [Rails Routing From the Outside In](routing.html#resource-routing-the-rails-default) for more information on setting up and using resources. When dealing with RESTful resources, calls to `form_for` can get significantly easier if you rely on **record identification**. In short, you can just pass the model instance and have Rails figure out model name and the rest: diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 8eb3b6190f..31d5c4f71d 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -68,7 +68,7 @@ The Rails philosophy includes two major guiding principles: again, our code is more maintainable, more extensible, and less buggy. * **Convention Over Configuration:** Rails has opinions about the best way to do many things in a web application, and defaults to this set of conventions, rather than - require that you specify every minutiae through endless configuration files. + require that you specify minutiae through endless configuration files. Creating a New Rails Project ---------------------------- @@ -93,7 +93,7 @@ current version of Ruby installed: ```bash $ ruby -v -ruby 2.3.0p0 +ruby 2.3.1p112 ``` TIP: A number of tools exist to help you quickly install Ruby and Ruby @@ -148,6 +148,10 @@ This will create a Rails application called Blog in a `blog` directory and install the gem dependencies that are already mentioned in `Gemfile` using `bundle install`. +NOTE: If you're using Windows Subsystem for Linux then there are currently some +limitations on file system notifications that mean you should disable the `spring` +and `listen` gems which you can do by running `rails new blog --skip-spring --skip-listen`. + TIP: You can see all of the command line options that the Rails application builder accepts by running `rails new -h`. @@ -164,7 +168,7 @@ of the files and folders that Rails created by default: | File/Folder | Purpose | | ----------- | ------- | -|app/|Contains the controllers, models, views, helpers, mailers and assets for your application. You'll focus on this folder for the remainder of this guide.| +|app/|Contains the controllers, models, views, helpers, mailers, channels, jobs and assets for your application. You'll focus on this folder for the remainder of this guide.| |bin/|Contains the rails script that starts your app and can contain other scripts you use to setup, update, deploy or run your application.| |config/|Configure your application's routes, database, and more. This is covered in more detail in [Configuring Rails Applications](configuring.html).| |config.ru|Rack configuration for Rack based servers used to start the application.| @@ -178,6 +182,7 @@ of the files and folders that Rails created by default: |test/|Unit tests, fixtures, and other test apparatus. These are covered in [Testing Rails Applications](testing.html).| |tmp/|Temporary files (like cache and pid files).| |vendor/|A place for all third-party code. In a typical Rails application this includes vendored gems.| +|.gitignore|This file tells git which files (or patterns) it should ignore. See [Github - Ignoring files](https://help.github.com/articles/ignoring-files) for more info about ignoring files. Hello, Rails! ------------- @@ -223,8 +228,7 @@ the server. The "Welcome aboard" page is the _smoke test_ for a new Rails application: it makes sure that you have your software configured correctly enough to serve a -page. You can also click on the _About your application's environment_ link to -see a summary of your application's environment. +page. ### Say "Hello", Rails @@ -245,11 +249,11 @@ Ruby) which is processed by the request cycle in Rails before being sent to the user. To create a new controller, you will need to run the "controller" generator and -tell it you want a controller called "welcome" with an action called "index", +tell it you want a controller called "Welcome" with an action called "index", just like this: ```bash -$ bin/rails generate controller welcome index +$ bin/rails generate controller Welcome index ``` Rails will create several files and a route for you. @@ -264,6 +268,7 @@ invoke test_unit create test/controllers/welcome_controller_test.rb invoke helper create app/helpers/welcome_helper.rb +invoke test_unit invoke assets invoke coffee create app/assets/javascripts/welcome.coffee @@ -298,33 +303,30 @@ Open the file `config/routes.rb` in your editor. Rails.application.routes.draw do get 'welcome/index' - # The priority is based upon order of creation: - # first created -> highest priority. - # See how all your routes lay out with "bin/rails routes". - # - # You can have the root of your site routed with "root" - # root 'welcome#index' - # - # ... + # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html +end ``` This is your application's _routing file_ which holds entries in a special [DSL (domain-specific language)](http://en.wikipedia.org/wiki/Domain-specific_language) that tells Rails how to connect incoming requests to -controllers and actions. This file contains many sample routes on commented -lines, and one of them actually shows you how to connect the root of your site -to a specific controller and action. Find the line beginning with `root` and -uncomment it. It should look something like the following: +controllers and actions. +Edit this file by adding the line of code `root 'welcome#index'`. +It should look something like the following: ```ruby -root 'welcome#index' +Rails.application.routes.draw do + get 'welcome/index' + + root 'welcome#index' +end ``` `root 'welcome#index'` tells Rails to map requests to the root of the application to the welcome controller's index action and `get 'welcome/index'` tells Rails to map requests to <http://localhost:3000/welcome/index> to the welcome controller's index action. This was created earlier when you ran the -controller generator (`bin/rails generate controller welcome index`). +controller generator (`bin/rails generate controller Welcome index`). Launch the web server again if you stopped it to generate the controller (`bin/rails server`) and navigate to <http://localhost:3000> in your browser. You'll see the @@ -348,10 +350,11 @@ operations are referred to as _CRUD_ operations. Rails provides a `resources` method which can be used to declare a standard REST resource. You need to add the _article resource_ to the -`config/routes.rb` as follows: +`config/routes.rb` so the file will look as follows: ```ruby Rails.application.routes.draw do + get 'welcome/index' resources :articles @@ -387,7 +390,7 @@ create and read. The form for doing this will look like this: It will look a little basic for now, but that's ok. We'll look at improving the styling for it afterwards. -### Laying down the ground work +### Laying down the groundwork Firstly, you need a place within the application to create a new article. A great place for that would be at `/articles/new`. With the route already @@ -403,7 +406,7 @@ a controller called `ArticlesController`. You can do this by running this command: ```bash -$ bin/rails generate controller articles +$ bin/rails generate controller Articles ``` If you open up the newly generated `app/controllers/articles_controller.rb` @@ -458,7 +461,7 @@ available, Rails will raise an exception. In the above image, the bottom line has been truncated. Let's see what the full error message looks like: ->Missing template articles/new, application/new with {locale:[:en], formats:[:html], handlers:[:erb, :builder, :coffee]}. Searched in: * "/path/to/blog/app/views" +>ArticlesController#new is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: [] NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not… nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot. That's quite a lot of text! Let's quickly go through and understand what each part of it means. @@ -468,27 +471,24 @@ The first part identifies which template is missing. In this case, it's the then it will attempt to load a template called `application/new`. It looks for one here because the `ArticlesController` inherits from `ApplicationController`. -The next part of the message contains a hash. The `:locale` key in this hash -simply indicates which spoken language template should be retrieved. By default, -this is the English - or "en" - template. The next key, `:formats` specifies the -format of template to be served in response. The default format is `:html`, and -so Rails is looking for an HTML template. The final key, `:handlers`, is telling -us what _template handlers_ could be used to render our template. `:erb` is most -commonly used for HTML templates, `:builder` is used for XML templates, and -`:coffee` uses CoffeeScript to build JavaScript templates. - -The final part of this message tells us where Rails has looked for the templates. -Templates within a basic Rails application like this are kept in a single -location, but in more complex applications it could be many different paths. +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 +the response and helps Rails determine which template to use in the response. +It is empty because no information has been provided. The simplest template that would work in this case would be one located at `app/views/articles/new.html.erb`. The extension of this file name is important: the first extension is the _format_ of the template, and the second extension -is the _handler_ that will be used. Rails is attempting to find a template -called `articles/new` within `app/views` for the application. The format for -this template can only be `html` and the handler must be one of `erb`, -`builder` or `coffee`. Because you want to create a new HTML form, you will be -using the `ERB` language which is designed to embed Ruby in HTML. +is the _handler_ that will be used to render the template. Rails is attempting +to find a template called `articles/new` within `app/views` for the +application. The format for this template can only be `html` and the default +handler for HTML is `erb`. Rails uses other handlers for other formats. +`builder` handler is used to build XML templates and `coffee` handler uses +CoffeeScript to build JavaScript templates. Since you want to create a new +HTML form, you will be using the `ERB` language which is designed to embed Ruby +in HTML. Therefore the file should be called `articles/new.html.erb` and needs to be located inside the `app/views` directory of the application. @@ -529,7 +529,7 @@ method called `form_for`. To use this method, add this code into <% end %> ``` -If you refresh the page now, you'll see the exact same form as in the example. +If you refresh the page now, you'll see the exact same form from our example above. Building forms in Rails is really just that easy! When you call `form_for`, you pass it an identifying object for this @@ -607,9 +607,11 @@ class ArticlesController < ApplicationController end ``` -If you re-submit the form now, you'll see another familiar error: a template is -missing. That's ok, we can ignore that for now. What the `create` action should -be doing is saving our new article to the database. +If you re-submit the form now, you may not see any change on the page. Don't worry! +This is because Rails by default returns `204 No Content` response for an action if +we don't specify what the response should be. We just added the `create` action +but didn't specify anything about how the response should be. In this case, the +`create` action should save our new article to the database. When a form is submitted, the fields of the form are sent to Rails as _parameters_. These parameters can then be referenced inside the controller @@ -625,17 +627,16 @@ end The `render` method here is taking a very simple hash with a key of `:plain` and value of `params[:article].inspect`. The `params` method is the object which represents the parameters (or fields) coming in from the form. The `params` -method returns an `ActiveSupport::HashWithIndifferentAccess` object, which +method returns an `ActionController::Parameters` object, which allows you to access the keys of the hash using either strings or symbols. In this situation, the only parameters that matter are the ones from the form. TIP: Ensure you have a firm grasp of the `params` method, as you'll use it fairly regularly. Let's consider an example URL: **http://www.example.com/?username=dhh&email=dhh@email.com**. In this URL, `params[:username]` would equal "dhh" and `params[:email]` would equal "dhh@email.com". -If you re-submit the form one more time you'll now no longer get the missing -template error. Instead, you'll see something that looks like the following: +If you re-submit the form one more time, you'll see something that looks like the following: ```ruby -{"title"=>"First article!", "text"=>"This is my first article."} +<ActionController::Parameters {"title"=>"First Article!", "text"=>"This is my first article."} permitted: false> ``` This action is now displaying the parameters for the article that are coming in @@ -686,7 +687,7 @@ class CreateArticles < ActiveRecord::Migration[5.0] t.string :title t.text :text - t.timestamps null: false + t.timestamps end end end @@ -699,8 +700,8 @@ in case you want to reverse it later. When you run this migration it will create an `articles` table with one string column and a text column. It also creates two timestamp fields to allow Rails to track article creation and update times. -TIP: For more information about migrations, refer to [Rails Database Migrations] -(migrations.html). +TIP: For more information about migrations, refer to [Active Record Migrations] +(active_record_migrations.html). At this point, you can use a bin/rails command to run the migration: @@ -1154,7 +1155,7 @@ new articles. Create a file called `app/views/articles/edit.html.erb` and make it look as follows: ```html+erb -<h1>Editing article</h1> +<h1>Edit article</h1> <%= form_for :article, url: article_path(@article), method: :patch do |f| %> @@ -1554,9 +1555,9 @@ class CreateComments < ActiveRecord::Migration[5.0] create_table :comments do |t| t.string :commenter t.text :body - t.references :article, index: true, foreign_key: true + t.references :article, foreign_key: true - t.timestamps null: false + t.timestamps end end end diff --git a/guides/source/i18n.md b/guides/source/i18n.md index 5bbd4048b9..887774961a 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -25,7 +25,7 @@ After reading this guide, you will know: * How I18n works in Ruby on Rails * How to correctly use I18n into a RESTful application in various ways -* How to use I18n to translate ActiveRecord errors or ActionMailer E-mail subjects +* How to use I18n to translate Active Record errors or Action Mailer E-mail subjects * Some other tools to go further with the translation process of your application -------------------------------------------------------------------------------- @@ -109,12 +109,11 @@ The **translations load path** (`I18n.load_path`) is an array of paths to files NOTE: The backend lazy-loads these translations when a translation is looked up for the first time. This backend can be swapped with something else even after translations have already been announced. -The default `config/application.rb` file has instructions on how to add locales from another directory and how to set a different default locale. +You can change the default locale as well as configure the translations load paths in `config/application.rb` as follows: ```ruby -# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. -# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] -# config.i18n.default_locale = :de + config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] + config.i18n.default_locale = :de ``` The load path must be specified before any translations are looked up. To change the default locale from an initializer instead of `config/application.rb`: @@ -867,7 +866,7 @@ This way you can provide special translations for various error messages at diff #### Error Message Interpolation -The translated model name, translated attribute name, and value are always available for interpolation. +The translated model name, translated attribute name, and value are always available for interpolation as `model`, `attribute` and `value` respectively. So, for example, instead of the default error message `"cannot be blank"` you could use the attribute name like this : `"Please fill in your %{attribute}"`. @@ -889,6 +888,7 @@ So, for example, instead of the default error message `"cannot be blank"` you co | inclusion | - | :inclusion | - | | exclusion | - | :exclusion | - | | associated | - | :invalid | - | +| non-optional association | - | :required | - | | numericality | - | :not_a_number | - | | numericality | :greater_than | :greater_than | count | | numericality | :greater_than_or_equal_to | :greater_than_or_equal_to | count | @@ -1113,7 +1113,7 @@ Conclusion At this point you should have a good overview about how I18n support in Ruby on Rails works and are ready to start translating your project. -If you find anything missing or wrong in this guide, please file a ticket on our [issue tracker](http://i18n.lighthouseapp.com/projects/14948-rails-i18n/overview). If you want to discuss certain portions or have questions, please sign up to our [mailing list](http://groups.google.com/group/rails-i18n). +If you want to discuss certain portions or have questions, please sign up to the [rails-i18n mailing list](http://groups.google.com/group/rails-i18n). Contributing to Rails I18n @@ -1121,19 +1121,17 @@ Contributing to Rails I18n I18n support in Ruby on Rails was introduced in the release 2.2 and is still evolving. The project follows the good Ruby on Rails development tradition of evolving solutions in gems and real applications first, and only then cherry-picking the best-of-breed of most widely useful features for inclusion in the core. -Thus we encourage everybody to experiment with new ideas and features in gems or other libraries and make them available to the community. (Don't forget to announce your work on our [mailing list](http://groups.google.com/group/rails-i18n!)) +Thus we encourage everybody to experiment with new ideas and features in gems or other libraries and make them available to the community. (Don't forget to announce your work on our [mailing list](http://groups.google.com/group/rails-i18n)!) -If you find your own locale (language) missing from our [example translations data](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) repository for Ruby on Rails, please [_fork_](https://github.com/guides/fork-a-project-and-submit-your-modifications) the repository, add your data and send a [pull request](https://github.com/guides/pull-requests). +If you find your own locale (language) missing from our [example translations data](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) repository for Ruby on Rails, please [_fork_](https://github.com/guides/fork-a-project-and-submit-your-modifications) the repository, add your data and send a [pull request](https://help.github.com/articles/about-pull-requests/). Resources --------- * [Google group: rails-i18n](http://groups.google.com/group/rails-i18n) - The project's mailing list. -* [GitHub: rails-i18n](https://github.com/svenfuchs/rails-i18n/tree/master) - Code repository for the rails-i18n project. Most importantly you can find lots of [example translations](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) for Rails that should work for your application in most cases. -* [GitHub: i18n](https://github.com/svenfuchs/i18n/tree/master) - Code repository for the i18n gem. -* [Lighthouse: rails-i18n](http://i18n.lighthouseapp.com/projects/14948-rails-i18n/overview) - Issue tracker for the rails-i18n project. -* [Lighthouse: i18n](http://i18n.lighthouseapp.com/projects/14947-ruby-i18n/overview) - Issue tracker for the i18n gem. +* [GitHub: rails-i18n](https://github.com/svenfuchs/rails-i18n) - Code repository and issue tracker for the rails-i18n project. Most importantly you can find lots of [example translations](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) for Rails that should work for your application in most cases. +* [GitHub: i18n](https://github.com/svenfuchs/i18n) - Code repository and issue tracker for the i18n gem. Authors diff --git a/guides/source/initialization.md b/guides/source/initialization.md index 156f9c92b4..57ed35d0d8 100644 --- a/guides/source/initialization.md +++ b/guides/source/initialization.md @@ -3,8 +3,8 @@ The Rails Initialization Process ================================ -This guide explains the internals of the initialization process in Rails -as of Rails 4. It is an extremely in-depth guide and recommended for advanced Rails developers. +This guide explains the internals of the initialization process in Rails. +It is an extremely in-depth guide and recommended for advanced Rails developers. After reading this guide, you will know: @@ -16,7 +16,7 @@ After reading this guide, you will know: -------------------------------------------------------------------------------- This guide goes through every method call that is -required to boot up the Ruby on Rails stack for a default Rails 4 +required to boot up the Ruby on Rails stack for a default Rails application, explaining each part in detail along the way. For this guide, we will be focusing on what happens when you execute `rails server` to boot your app. @@ -318,7 +318,7 @@ def parse!(args) args, options = args.dup, {} opt_parser = OptionParser.new do |opts| - opts.banner = "Usage: rails server [mongrel, thin, etc] [options]" + 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 } ... @@ -356,8 +356,6 @@ private def print_boot_information ... puts "=> Run `rails server -h` for more startup options" - ... - puts "=> Ctrl-C to shutdown server" unless options[:daemonize] end def create_tmp_directories @@ -466,7 +464,7 @@ The `options[:config]` value defaults to `config.ru` which contains this: ```ruby # This file is used by Rack-based servers to start the application. -require ::File.expand_path('../config/environment', __FILE__) +require_relative 'config/environment' run <%= app_const %> ``` @@ -487,7 +485,7 @@ end The `initialize` method of `Rack::Builder` will take the block here and execute it within an instance of `Rack::Builder`. This is where the majority of the initialization process of Rails happens. The `require` line for `config/environment.rb` in `config.ru` is the first to run: ```ruby -require ::File.expand_path('../config/environment', __FILE__) +require_relative 'config/environment' ``` ### `config/environment.rb` @@ -497,7 +495,7 @@ This file is the common file required by `config.ru` (`rails server`) and Passen This file begins with requiring `config/application.rb`: ```ruby -require File.expand_path('../application', __FILE__) +require_relative 'application' ``` ### `config/application.rb` @@ -505,7 +503,7 @@ require File.expand_path('../application', __FILE__) This file requires `config/boot.rb`: ```ruby -require File.expand_path('../boot', __FILE__) +require_relative 'boot' ``` But only if it hasn't been required before, which would be the case in `rails server` @@ -665,7 +663,7 @@ DEFAULT_OPTIONS = { } def self.run(app, options = {}) - options = DEFAULT_OPTIONS.merge(options) + options = DEFAULT_OPTIONS.merge(options) if options[:Verbose] app = Rack::CommonLogger.new(app, STDOUT) diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb index 1f81ea4694..bb50761b30 100644 --- a/guides/source/layout.html.erb +++ b/guides/source/layout.html.erb @@ -24,7 +24,17 @@ <% end %> <div id="topNav"> <div class="wrapper"> - <strong class="more-info-label">â†<a href="http://rubyonrails.org/">Back to rubyonrails.org:</a> </strong> + <strong class="more-info-label">More at <a href="http://rubyonrails.org/">rubyonrails.org:</a> </strong> + <span class="red-button more-info-button"> + More Ruby on Rails + </span> + <ul class="more-info-links s-hidden"> + <li class="more-info"><a href="http://weblog.rubyonrails.org/">Blog</a></li> + <li class="more-info"><a href="http://guides.rubyonrails.org/">Guides</a></li> + <li class="more-info"><a href="http://api.rubyonrails.org/">API</a></li> + <li class="more-info"><a href="http://stackoverflow.com/questions/tagged/ruby-on-rails">Ask for help</a></li> + <li class="more-info"><a href="https://github.com/rails/rails">Contribute on GitHub</a></li> + </ul> </div> </div> <div id="header"> @@ -78,7 +88,7 @@ <div id="container"> <div class="wrapper"> <div id="mainCol"> - <%= yield.html_safe %> + <%= yield %> <h3>Feedback</h3> <p> @@ -101,7 +111,7 @@ <%= link_to 'open an issue', 'https://github.com/rails/rails/issues' %>. </p> <p>And last but not least, any kind of discussion regarding Ruby on Rails - documentation is very welcome in the <%= link_to 'rubyonrails-docs mailing list', 'http://groups.google.com/group/rubyonrails-docs' %>. + documentation is very welcome in the <%= link_to 'rubyonrails-docs mailing list', 'https://groups.google.com/forum/#!forum/rubyonrails-docs' %>. </p> </div> </div> @@ -117,13 +127,11 @@ <script type="text/javascript" src="javascripts/jquery.min.js"></script> <script type="text/javascript" src="javascripts/responsive-tables.js"></script> <script type="text/javascript" src="javascripts/guides.js"></script> - <script type="text/javascript" src="javascripts/syntaxhighlighter/shCore.js"></script> - <script type="text/javascript" src="javascripts/syntaxhighlighter/shBrushRuby.js"></script> - <script type="text/javascript" src="javascripts/syntaxhighlighter/shBrushXml.js"></script> - <script type="text/javascript" src="javascripts/syntaxhighlighter/shBrushSql.js"></script> - <script type="text/javascript" src="javascripts/syntaxhighlighter/shBrushPlain.js"></script> + <script type="text/javascript" src="javascripts/syntaxhighlighter.js"></script> <script type="text/javascript"> - SyntaxHighlighter.all(); + syntaxhighlighterConfig = { + autoLinks: false, + }; $(guidesIndex.bind); </script> </body> diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index 83173e8d75..7e4ec5ba7e 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -149,23 +149,22 @@ render template: "products/show" #### Rendering an Arbitrary File -The `render` method can also use a view that's entirely outside of your application (perhaps you're sharing views between two Rails applications): - -```ruby -render "/u/apps/warehouse_app/current/app/views/products/show" -``` - -Rails determines that this is a file render because of the leading slash character. To be explicit, you can use the `:file` option (which was required on Rails 2.2 and earlier): +The `render` method can also use a view that's entirely outside of your application: ```ruby render file: "/u/apps/warehouse_app/current/app/views/products/show" ``` -The `:file` option takes an absolute file-system path. Of course, you need to have rights to the view that you're using to render the content. +The `:file` option takes an absolute file-system path. Of course, you need to have rights +to the view that you're using to render the content. + +NOTE: Using the `:file` option in combination with users input can lead to security problems +since an attacker could use this action to access security sensitive files in your file system. NOTE: By default, the file is rendered using the current layout. -TIP: If you're running Rails on Microsoft Windows, you should use the `:file` option to render a file, because Windows filenames do not have the same format as Unix filenames. +TIP: If you're running Rails on Microsoft Windows, you should use the `:file` option to +render a file, because Windows filenames do not have the same format as Unix filenames. #### Wrapping it up @@ -238,7 +237,7 @@ TIP: This is useful when you're rendering a small snippet of HTML code. However, you might want to consider moving it to a template file if the markup is complex. -NOTE: When using `html:` option, HTML entities will be escaped if the string is not marked as HTML safe by using `html_safe` method. +NOTE: When using `html:` option, HTML entities will be escaped if the string is not marked as HTML safe by using `html_safe` method. #### Rendering JSON @@ -631,6 +630,8 @@ to use in this case. redirect_back(fallback_location: root_path) ``` +NOTE: `redirect_to` and `redirect_back` do not halt and return immediately from method execution, but simply set HTTP responses. Statements occurring after them in a method will be executed. You can halt by an explicit `return` or some other halting mechanism, if needed. + #### Getting a Different Redirect Status Code Rails uses HTTP status code 302, a temporary redirect, when you call `redirect_to`. If you'd like to use a different status code, perhaps 301, a permanent redirect, you can use the `:status` option: @@ -700,7 +701,7 @@ This would detect that there are no books with the specified ID, populate the `@ ### Using `head` To Build Header-Only Responses -The `head` method can be used to send responses with only headers to the browser. The `head` method accepts a number or symbol (see [reference table](#the-status-option)) representing a HTTP status code. The options argument is interpreted as a hash of header names and values. For example, you can return only an error header: +The `head` method can be used to send responses with only headers to the browser. The `head` method accepts a number or symbol (see [reference table](#the-status-option)) representing an HTTP status code. The options argument is interpreted as a hash of header names and values. For example, you can return only an error header: ```ruby head :bad_request @@ -750,7 +751,7 @@ When Rails renders a view as a response, it does so by combining the view with t ### Asset Tag Helpers -Asset tag helpers provide methods for generating HTML that link views to feeds, JavaScript, stylesheets, images, videos and audios. There are six asset tag helpers available in Rails: +Asset tag helpers provide methods for generating HTML that link views to feeds, JavaScript, stylesheets, images, videos, and audios. There are six asset tag helpers available in Rails: * `auto_discovery_link_tag` * `javascript_include_tag` diff --git a/guides/source/maintenance_policy.md b/guides/source/maintenance_policy.md index f99b6ebd31..7ced3eab1c 100644 --- a/guides/source/maintenance_policy.md +++ b/guides/source/maintenance_policy.md @@ -44,7 +44,7 @@ from. In special situations, where someone from the Core Team agrees to support more series, they are included in the list of supported series. -**Currently included series:** `5.0.Z`. +**Currently included series:** `5.0.Z`, `4.2.Z`. Security Issues --------------- diff --git a/guides/source/plugins.md b/guides/source/plugins.md index 8f055f8fe3..760ff431c0 100644 --- a/guides/source/plugins.md +++ b/guides/source/plugins.md @@ -30,7 +30,7 @@ Setup ----- Currently, Rails plugins are built as gems, _gemified plugins_. They can be shared across -different rails applications using RubyGems and Bundler if desired. +different Rails applications using RubyGems and Bundler if desired. ### Generate a gemified plugin. @@ -67,7 +67,7 @@ This will tell you that everything got generated properly and you are ready to s Extending Core Classes ---------------------- -This section will explain how to add a method to String that will be available anywhere in your rails application. +This section will explain how to add a method to String that will be available anywhere in your Rails application. In this example you will add a method to String named `to_squawk`. To begin, create a new test file with a few assertions: diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md index 5a46baff2d..3e99ee7021 100644 --- a/guides/source/rails_application_templates.md +++ b/guides/source/rails_application_templates.md @@ -15,18 +15,18 @@ After reading this guide, you will know: Usage ----- -To apply a template, you need to provide the Rails generator with the location of the template you wish to apply using the -m option. This can either be a path to a file or a URL. +To apply a template, you need to provide the Rails generator with the location of the template you wish to apply using the `-m` option. This can either be a path to a file or a URL. ```bash $ rails new blog -m ~/template.rb $ rails new blog -m http://example.com/template.rb ``` -You can use the rake task `rails:template` to apply templates to an existing Rails application. The location of the template needs to be passed in to an environment variable named LOCATION. Again, this can either be path to a file or a URL. +You can use the `app:template` Rake task to apply templates to an existing Rails application. The location of the template needs to be passed in via the LOCATION environment variable. Again, this can either be path to a file or a URL. ```bash -$ bin/rails rails:template LOCATION=~/template.rb -$ bin/rails rails:template LOCATION=http://example.com/template.rb +$ bin/rails app:template LOCATION=~/template.rb +$ bin/rails app:template LOCATION=http://example.com/template.rb ``` Template API @@ -38,7 +38,7 @@ The Rails templates API is easy to understand. Here's an example of a typical Ra # template.rb generate(:scaffold, "person name:string") route "root to: 'people#index'" -rake("db:migrate") +rails_command("db:migrate") after_bundle do git :init @@ -175,18 +175,24 @@ Executes an arbitrary command. Just like the backticks. Let's say you want to re run "rm README.rdoc" ``` -### rake(command, options = {}) +### rails_command(command, options = {}) -Runs the supplied rake tasks in the Rails application. Let's say you want to migrate the database: +Runs the supplied task in the Rails application. Let's say you want to migrate the database: ```ruby -rake "db:migrate" +rails_command "db:migrate" ``` -You can also run rake tasks with a different Rails environment: +You can also run tasks with a different Rails environment: ```ruby -rake "db:migrate", env: 'production' +rails_command "db:migrate", env: 'production' +``` + +You can also run tasks as a super-user: + +```ruby +rails_command "log:clear", sudo: true ``` ### route(routing_code) @@ -226,7 +232,7 @@ CODE These methods let you ask questions from templates and decide the flow based on the user's answer. Let's say you want to Freeze Rails only if the user wants to: ```ruby -rake("rails:freeze:gems") if yes?("Freeze rails gems?") +rails_command("rails:freeze:gems") if yes?("Freeze rails gems?") # no?(question) acts just the opposite. ``` diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md index 3b61d65df5..ed935e1008 100644 --- a/guides/source/rails_on_rack.md +++ b/guides/source/rails_on_rack.md @@ -64,7 +64,7 @@ To use `rackup` instead of Rails' `rails server`, you can put the following insi ```ruby # Rails.root/config.ru -require ::File.expand_path('../config/environment', __FILE__) +require_relative 'config/environment' run Rails.application ``` @@ -93,7 +93,7 @@ NOTE: `ActionDispatch::MiddlewareStack` is Rails equivalent of `Rack::Builder`, ### Inspecting Middleware Stack -Rails has a handy rake task for inspecting the middleware stack in use: +Rails has a handy task for inspecting the middleware stack in use: ```bash $ bin/rails middleware @@ -104,20 +104,19 @@ For a freshly generated Rails application, this might produce something like: ```ruby use Rack::Sendfile use ActionDispatch::Static -use ActionDispatch::LoadInterlock -use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x000000029a0838> +use ActionDispatch::Executor +use ActiveSupport::Cache::Strategy::LocalCache::Middleware use Rack::Runtime use Rack::MethodOverride use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions +use WebConsole::Middleware use ActionDispatch::DebugExceptions use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::Migration::CheckPending -use ActiveRecord::ConnectionAdapters::ConnectionManagement -use ActiveRecord::QueryCache use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash @@ -149,9 +148,9 @@ You can add a new middleware to the middleware stack using any of the following # Push Rack::BounceFavicon at the bottom config.middleware.use Rack::BounceFavicon -# Add Lifo::Cache after ActiveRecord::QueryCache. +# Add Lifo::Cache after ActionDispatch::Executor. # Pass { page_cache: false } argument to Lifo::Cache. -config.middleware.insert_after ActiveRecord::QueryCache, Lifo::Cache, page_cache: false +config.middleware.insert_after ActionDispatch::Executor, Lifo::Cache, page_cache: false ``` #### Swapping a Middleware @@ -219,7 +218,7 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol * Sets `env["rack.multithread"]` flag to `false` and wraps the application within a Mutex. -**`ActionDispatch::LoadInterlock`** +**`ActionDispatch::Executor`** * Used for thread safe code reloading during development. @@ -267,14 +266,6 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol * Checks pending migrations and raises `ActiveRecord::PendingMigrationError` if any migrations are pending. -**`ActiveRecord::ConnectionAdapters::ConnectionManagement`** - -* Cleans active connections after each request, unless the `rack.test` key in the request environment is set to `true`. - -**`ActiveRecord::QueryCache`** - -* Enables the Active Record query cache. - **`ActionDispatch::Cookies`** * Sets cookies for the request. diff --git a/guides/source/routing.md b/guides/source/routing.md index bd3e236a2b..937e313663 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -9,16 +9,16 @@ After reading this guide, you will know: * How to interpret the code in `config/routes.rb`. * How to construct your own routes, using either the preferred resourceful style or the `match` method. -* What parameters to expect an action to receive. +* How to declare route parameters, which are passed onto controller actions. * How to automatically create paths and URLs using route helpers. -* Advanced techniques such as constraints and Rack endpoints. +* Advanced techniques such as creating constraints and mounting Rack endpoints. -------------------------------------------------------------------------------- The Purpose of the Rails Router ------------------------------- -The Rails router recognizes URLs and dispatches them to a controller's action. It can also generate paths and URLs, avoiding the need to hardcode strings in your views. +The Rails router recognizes URLs and dispatches them to a controller's action, or to a Rack application. It can also generate paths and URLs, avoiding the need to hardcode strings in your views. ### Connecting URLs to Code @@ -553,29 +553,23 @@ In particular, simple routing makes it very easy to map legacy URLs to new Rails ### Bound Parameters -When you set up a regular route, you supply a series of symbols that Rails maps to parts of an incoming HTTP request. Two of these symbols are special: `:controller` maps to the name of a controller in your application, and `:action` maps to the name of an action within that controller. For example, consider this route: +When you set up a regular route, you supply a series of symbols that Rails maps to parts of an incoming HTTP request. For example, consider this route: ```ruby -get ':controller(/:action(/:id))' +get 'photos(/:id)', to: :display ``` -If an incoming request of `/photos/show/1` is processed by this route (because it hasn't matched any previous route in the file), then the result will be to invoke the `show` action of the `PhotosController`, and to make the final parameter `"1"` available as `params[:id]`. This route will also route the incoming request of `/photos` to `PhotosController#index`, since `:action` and `:id` are optional parameters, denoted by parentheses. +If an incoming request of `/photos/1` is processed by this route (because it hasn't matched any previous route in the file), then the result will be to invoke the `display` action of the `PhotosController`, and to make the final parameter `"1"` available as `params[:id]`. This route will also route the incoming request of `/photos` to `PhotosController#display`, since `:id` is an optional parameter, denoted by parentheses. ### Dynamic Segments -You can set up as many dynamic segments within a regular route as you like. Anything other than `:controller` or `:action` will be available to the action as part of `params`. If you set up this route: +You can set up as many dynamic segments within a regular route as you like. Any segment will be available to the action as part of `params`. If you set up this route: ```ruby -get ':controller/:action/:id/:user_id' +get 'photos/:id/:user_id', to: 'photos#show' ``` -An incoming path of `/photos/show/1/2` will be dispatched to the `show` action of the `PhotosController`. `params[:id]` will be `"1"`, and `params[:user_id]` will be `"2"`. - -NOTE: You can't use `:namespace` or `:module` with a `:controller` path segment. If you need to do this then use a constraint on :controller that matches the namespace you require. e.g: - -```ruby -get ':controller(/:action(/:id))', controller: /admin\/[^\/]+/ -``` +An incoming path of `/photos/1/2` will be dispatched to the `show` action of the `PhotosController`. `params[:id]` will be `"1"`, and `params[:user_id]` will be `"2"`. TIP: By default, dynamic segments don't accept dots - this is because the dot is used as a separator for formatted routes. If you need to use a dot within a dynamic segment, add a constraint that overrides this – for example, `id: /[^\/]+/` allows anything except a slash. @@ -584,32 +578,24 @@ TIP: By default, dynamic segments don't accept dots - this is because the dot is You can specify static segments when creating a route by not prepending a colon to a fragment: ```ruby -get ':controller/:action/:id/with_user/:user_id' +get 'photos/:id/with_user/:user_id', to: 'photos#show' ``` -This route would respond to paths such as `/photos/show/1/with_user/2`. In this case, `params` would be `{ controller: 'photos', action: 'show', id: '1', user_id: '2' }`. +This route would respond to paths such as `/photos/1/with_user/2`. In this case, `params` would be `{ controller: 'photos', action: 'show', id: '1', user_id: '2' }`. ### The Query String The `params` will also include any parameters from the query string. For example, with this route: ```ruby -get ':controller/:action/:id' +get 'photos/:id', to: 'photos#show' ``` -An incoming path of `/photos/show/1?user_id=2` will be dispatched to the `show` action of the `Photos` controller. `params` will be `{ controller: 'photos', action: 'show', id: '1', user_id: '2' }`. +An incoming path of `/photos/1?user_id=2` will be dispatched to the `show` action of the `Photos` controller. `params` will be `{ controller: 'photos', action: 'show', id: '1', user_id: '2' }`. ### Defining Defaults -You do not need to explicitly use the `:controller` and `:action` symbols within a route. You can supply them as defaults: - -```ruby -get 'photos/:id', to: 'photos#show' -``` - -With this route, Rails will match an incoming path of `/photos/12` to the `show` action of `PhotosController`. - -You can also define other defaults in a route by supplying a hash for the `:defaults` option. This even applies to parameters that you do not specify as dynamic segments. For example: +You can define defaults in a route by supplying a hash for the `:defaults` option. This even applies to parameters that you do not specify as dynamic segments. For example: ```ruby get 'photos/:id', to: 'photos#show', defaults: { format: 'jpg' } @@ -812,10 +798,10 @@ In all of these cases, if you don't provide the leading host (`http://www.exampl Instead of a String like `'articles#index'`, which corresponds to the `index` action in the `ArticlesController`, you can specify any [Rack application](rails_on_rack.html) as the endpoint for a matcher: ```ruby -match '/application.js', to: Sprockets, via: :all +match '/application.js', to: MyRackApp, via: :all ``` -As long as `Sprockets` responds to `call` and returns a `[status, headers, body]`, the router won't know the difference between the Rack application and an action. This is an appropriate use of `via: :all`, as you will want to allow your Rack application to handle all verbs as it considers appropriate. +As long as `MyRackApp` responds to `call` and returns a `[status, headers, body]`, the router won't know the difference between the Rack application and an action. This is an appropriate use of `via: :all`, as you will want to allow your Rack application to handle all verbs as it considers appropriate. NOTE: For the curious, `'articles#index'` actually expands out to `ArticlesController.action(:index)`, which returns a valid Rack application. diff --git a/guides/source/security.md b/guides/source/security.md index 98324141cc..aea9728c10 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -41,24 +41,24 @@ NOTE: _HTTP is a stateless protocol. Sessions make it stateful._ Most applications need to keep track of certain state of a particular user. This could be the contents of a shopping basket or the user id of the currently logged in user. Without the idea of sessions, the user would have to identify, and probably authenticate, on every request. Rails will create a new session automatically if a new user accesses the application. It will load an existing session if the user has already used the application. -A session usually consists of a hash of values and a session id, usually a 32-character string, to identify the hash. Every cookie sent to the client's browser includes the session id. And the other way round: the browser will send it to the server on every request from the client. In Rails you can save and retrieve values using the session method: +A session usually consists of a hash of values and a session ID, usually a 32-character string, to identify the hash. Every cookie sent to the client's browser includes the session ID. And the other way round: the browser will send it to the server on every request from the client. In Rails you can save and retrieve values using the session method: ```ruby session[:user_id] = @current_user.id User.find(session[:user_id]) ``` -### Session id +### Session ID -NOTE: _The session id is a 32 byte long MD5 hash value._ +NOTE: _The session ID is a 32-character random hex string._ -A session id consists of the hash value of a random string. The random string is the current time, a random number between 0 and 1, the process id number of the Ruby interpreter (also basically a random number) and a constant string. Currently it is not feasible to brute-force Rails' session ids. To date MD5 is uncompromised, but there have been collisions, so it is theoretically possible to create another input text with the same hash value. But this has had no security impact to date. +The session ID is generated using `SecureRandom.hex` which generates a random hex string using platform specific methods (such as OpenSSL, /dev/urandom or Win32) for generating cryptographically secure random numbers. Currently it is not feasible to brute-force Rails' session IDs. ### Session Hijacking -WARNING: _Stealing a user's session id lets an attacker use the web application in the victim's name._ +WARNING: _Stealing a user's session ID lets an attacker use the web application in the victim's name._ -Many web applications have an authentication system: a user provides a user name and password, the web application checks them and stores the corresponding user id in the session hash. From now on, the session is valid. On every request the application will load the user, identified by the user id in the session, without the need for new authentication. The session id in the cookie identifies the session. +Many web applications have an authentication system: a user provides a user name and password, the web application checks them and stores the corresponding user id in the session hash. From now on, the session is valid. On every request the application will load the user, identified by the user id in the session, without the need for new authentication. The session ID in the cookie identifies the session. Hence, the cookie serves as temporary authentication for the web application. Anyone who seizes a cookie from someone else, may use the web application as this user - with possibly severe consequences. Here are some ways to hijack a session, and their countermeasures: @@ -89,7 +89,7 @@ This will also be a good idea, if you modify the structure of an object and old NOTE: _Rails provides several storage mechanisms for the session hashes. The most important is `ActionDispatch::Session::CookieStore`._ -Rails 2 introduced a new default session storage, CookieStore. CookieStore saves the session hash directly in a cookie on the client-side. The server retrieves the session hash from the cookie and eliminates the need for a session id. That will greatly increase the speed of the application, but it is a controversial storage option and you have to think about the security implications of it: +Rails 2 introduced a new default session storage, CookieStore. CookieStore saves the session hash directly in a cookie on the client-side. The server retrieves the session hash from the cookie and eliminates the need for a session ID. That will greatly increase the speed of the application, but it is a controversial storage option and you have to think about the security implications of it: * Cookies imply a strict size limit of 4kB. This is fine as you should not store large amounts of data in a session anyway, as described before. _Storing the current user's database id in a session is usually ok_. @@ -131,22 +131,22 @@ It works like this: * The user takes the cookie from the first step (which they previously copied) and replaces the current cookie in the browser. * The user has their original credit back. -Including a nonce (a random value) in the session solves replay attacks. A nonce is valid only once, and the server has to keep track of all the valid nonces. It gets even more complicated if you have several application servers (mongrels). Storing nonces in a database table would defeat the entire purpose of CookieStore (avoiding accessing the database). +Including a nonce (a random value) in the session solves replay attacks. A nonce is valid only once, and the server has to keep track of all the valid nonces. It gets even more complicated if you have several application servers. Storing nonces in a database table would defeat the entire purpose of CookieStore (avoiding accessing the database). The best _solution against it is not to store this kind of data in a session, but in the database_. In this case store the credit in the database and the logged_in_user_id in the session. ### Session Fixation -NOTE: _Apart from stealing a user's session id, the attacker may fix a session id known to them. This is called session fixation._ +NOTE: _Apart from stealing a user's session ID, the attacker may fix a session ID known to them. This is called session fixation._  -This attack focuses on fixing a user's session id known to the attacker, and forcing the user's browser into using this id. It is therefore not necessary for the attacker to steal the session id afterwards. Here is how this attack works: +This attack focuses on fixing a user's session ID known to the attacker, and forcing the user's browser into using this ID. It is therefore not necessary for the attacker to steal the session ID afterwards. Here is how this attack works: -* The attacker creates a valid session id: They load the login page of the web application where they want to fix the session, and take the session id in the cookie from the response (see number 1 and 2 in the image). +* The attacker creates a valid session ID: They load the login page of the web application where they want to fix the session, and take the session ID in the cookie from the response (see number 1 and 2 in the image). * They maintain the session by accessing the web application periodically in order to keep an expiring session alive. -* The attacker forces the user's browser into using this session id (see number 3 in the image). As you may not change a cookie of another domain (because of the same origin policy), the attacker has to run a JavaScript from the domain of the target web application. Injecting the JavaScript code into the application by XSS accomplishes this attack. Here is an example: `<script>document.cookie="_session_id=16d5b78abb28e3d6206b60f22a03c8d9";</script>`. Read more about XSS and injection later on. -* The attacker lures the victim to the infected page with the JavaScript code. By viewing the page, the victim's browser will change the session id to the trap session id. +* The attacker forces the user's browser into using this session ID (see number 3 in the image). As you may not change a cookie of another domain (because of the same origin policy), the attacker has to run a JavaScript from the domain of the target web application. Injecting the JavaScript code into the application by XSS accomplishes this attack. Here is an example: `<script>document.cookie="_session_id=16d5b78abb28e3d6206b60f22a03c8d9";</script>`. Read more about XSS and injection later on. +* The attacker lures the victim to the infected page with the JavaScript code. By viewing the page, the victim's browser will change the session ID to the trap session ID. * As the new trap session is unused, the web application will require the user to authenticate. * From now on, the victim and the attacker will co-use the web application with the same session: The session became valid and the victim didn't notice the attack. @@ -160,7 +160,7 @@ The most effective countermeasure is to _issue a new session identifier_ and dec reset_session ``` -If you use the popular RestfulAuthentication plugin for user management, add reset_session to the SessionsController#create action. Note that this removes any value from the session, _you have to transfer them to the new session_. +If you use the popular [Devise](https://rubygems.org/gems/devise) gem for user management, it will automatically expire sessions on sign in and sign out for you. If you roll your own, remember to expire the session after your sign in action (when the session is created). This will remove values from the session, therefore _you will have to transfer them to the new session_. Another countermeasure is to _save user-specific properties in the session_, verify them every time a request comes in, and deny access, if the information does not match. Such properties could be the remote IP address or the user agent (the web browser name), though the latter is less user-specific. When saving the IP address, you have to bear in mind that there are Internet service providers or large organizations that put their users behind proxies. _These might change over the course of a session_, so these users will not be able to use your application, or only in a limited way. @@ -168,7 +168,7 @@ Another countermeasure is to _save user-specific properties in the session_, ver NOTE: _Sessions that never expire extend the time-frame for attacks such as cross-site request forgery (CSRF), session hijacking and session fixation._ -One possibility is to set the expiry time-stamp of the cookie with the session id. However the client can edit cookies that are stored in the web browser so expiring sessions on the server is safer. Here is an example of how to _expire sessions in a database table_. Call `Session.sweep("20 minutes")` to expire sessions that were used longer than 20 minutes ago. +One possibility is to set the expiry time-stamp of the cookie with the session ID. However the client can edit cookies that are stored in the web browser so expiring sessions on the server is safer. Here is an example of how to _expire sessions in a database table_. Call `Session.sweep("20 minutes")` to expire sessions that were used longer than 20 minutes ago. ```ruby class Session < ApplicationRecord @@ -196,11 +196,11 @@ This attack method works by including malicious code or a link in a page that ac  -In the [session chapter](#sessions) you have learned that most Rails applications use cookie-based sessions. Either they store the session id in the cookie and have a server-side session hash, or the entire session hash is on the client-side. In either case the browser will automatically send along the cookie on every request to a domain, if it can find a cookie for that domain. The controversial point is that if the request comes from a site of a different domain, it will also send the cookie. Let's start with an example: +In the [session chapter](#sessions) you have learned that most Rails applications use cookie-based sessions. Either they store the session ID in the cookie and have a server-side session hash, or the entire session hash is on the client-side. In either case the browser will automatically send along the cookie on every request to a domain, if it can find a cookie for that domain. The controversial point is that if the request comes from a site of a different domain, it will also send the cookie. Let's start with an example: * Bob browses a message board and views a post from a hacker where there is a crafted HTML image element. The element references a command in Bob's project management application, rather than an image file: `<img src="http://www.webapp.com/project/1/destroy">` * Bob's session at `www.webapp.com` is still alive, because he didn't log out a few minutes ago. -* By viewing the post, the browser finds an image tag. It tries to load the suspected image from `www.webapp.com`. As explained before, it will also send along the cookie with the valid session id. +* By viewing the post, the browser finds an image tag. It tries to load the suspected image from `www.webapp.com`. As explained before, it will also send along the cookie with the valid session ID. * The web application at `www.webapp.com` verifies the user information in the corresponding session hash and destroys the project with the ID 1. It then returns a result page which is an unexpected result for the browser, so it will not display the image. * Bob doesn't notice the attack - but a few days later he finds out that project number one is gone. @@ -249,7 +249,7 @@ There are many other possibilities, like using a `<script>` tag to make a cross- Note: We can't distinguish a `<script>` tag's origin—whether it's a tag on your own site or on some other malicious site—so we must block all `<script>` across the board, even if it's actually a safe same-origin script served from your own site. In these cases, explicitly skip CSRF protection on actions that serve JavaScript meant for a `<script>` tag. -To protect against all other forged requests, we introduce a _required security token_ that our site knows but other sites don't know. We include the security token in requests and verify it on the server. This is a one-liner in your application controller, and is the default for newly created rails applications: +To protect against all other forged requests, we introduce a _required security token_ that our site knows but other sites don't know. We include the security token in requests and verify it on the server. This is a one-liner in your application controller, and is the default for newly created Rails applications: ```ruby protect_from_forgery with: :exception @@ -287,7 +287,7 @@ Another class of security vulnerabilities surrounds the use of redirection and f WARNING: _Redirection in a web application is an underestimated cracker tool: Not only can the attacker forward the user to a trap web site, they may also create a self-contained attack._ -Whenever the user is allowed to pass (parts of) the URL for redirection, it is possibly vulnerable. The most obvious attack would be to redirect users to a fake web application which looks and feels exactly as the original one. This so-called phishing attack works by sending an unsuspicious link in an email to the users, injecting the link by XSS in the web application or putting the link into an external site. It is unsuspicious, because the link starts with the URL to the web application and the URL to the malicious site is hidden in the redirection parameter: http://www.example.com/site/redirect?to= www.attacker.com. Here is an example of a legacy action: +Whenever the user is allowed to pass (parts of) the URL for redirection, it is possibly vulnerable. The most obvious attack would be to redirect users to a fake web application which looks and feels exactly as the original one. This so-called phishing attack works by sending an unsuspicious link in an email to the users, injecting the link by XSS in the web application or putting the link into an external site. It is unsuspicious, because the link starts with the URL to the web application and the URL to the malicious site is hidden in the redirection parameter: http://www.example.com/site/redirect?to=www.attacker.com. Here is an example of a legacy action: ```ruby def legacy @@ -381,7 +381,7 @@ Refer to the Injection section for countermeasures against XSS. It is _recommend **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. -A real-world example is a [router reconfiguration by CSRF](http://www.h-online.com/security/news/item/Symantec-reports-first-active-attack-on-a-DSL-router-735883.html). The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for the user, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had their credentials stolen. +A real-world example is a [router reconfiguration by CSRF](http://www.h-online.com/security/news/item/Symantec-reports-first-active-attack-on-a-DSL-router-735883.html). The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for the user, but it also contained an image tag that resulted in an HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had their credentials stolen. Another example changed Google Adsense's e-mail address and password. If the victim was logged into Google Adsense, the administration interface for Google advertisement campaigns, an attacker could change the credentials of the victim.
 @@ -453,7 +453,7 @@ However, the attacker may also take over the account by changing the e-mail addr #### Other -Depending on your web application, there may be more ways to hijack the user's account. In many cases CSRF and XSS will help to do so. For example, as in a CSRF vulnerability in [Google Mail](http://www.gnucitizen.org/blog/google-gmail-e-mail-hijack-technique/). In this proof-of-concept attack, the victim would have been lured to a web site controlled by the attacker. On that site is a crafted IMG-tag which results in a HTTP GET request that changes the filter settings of Google Mail. If the victim was logged in to Google Mail, the attacker would change the filters to forward all e-mails to their e-mail address. This is nearly as harmful as hijacking the entire account. As a countermeasure, _review your application logic and eliminate all XSS and CSRF vulnerabilities_. +Depending on your web application, there may be more ways to hijack the user's account. In many cases CSRF and XSS will help to do so. For example, as in a CSRF vulnerability in [Google Mail](http://www.gnucitizen.org/blog/google-gmail-e-mail-hijack-technique/). In this proof-of-concept attack, the victim would have been lured to a web site controlled by the attacker. On that site is a crafted IMG-tag which results in an HTTP GET request that changes the filter settings of Google Mail. If the victim was logged in to Google Mail, the attacker would change the filters to forward all e-mails to their e-mail address. This is nearly as harmful as hijacking the entire account. As a countermeasure, _review your application logic and eliminate all XSS and CSRF vulnerabilities_. ### CAPTCHAs @@ -466,7 +466,7 @@ The problem with CAPTCHAs is that they have a negative impact on the user experi Most bots are really dumb. They crawl the web and put their spam into every form's field they can find. Negative CAPTCHAs take advantage of that and include a "honeypot" field in the form which will be hidden from the human user by CSS or JavaScript. -Note that negative CAPTCHAs are only effective against dumb bots and won't suffice to protect critical applications from targeted bots. Still, the negative and positive CAPTCHAs can be combined to increase the performance, e.g., if the "honeypot" field is not empty (bot detected), you won't need to verify the positive CAPTCHA, which would require a HTTPS request to Google ReCaptcha before computing the response. +Note that negative CAPTCHAs are only effective against dumb bots and won't suffice to protect critical applications from targeted bots. Still, the negative and positive CAPTCHAs can be combined to increase the performance, e.g., if the "honeypot" field is not empty (bot detected), you won't need to verify the positive CAPTCHA, which would require an HTTPS request to Google ReCaptcha before computing the response. Here are some ideas how to hide honeypot fields by JavaScript and/or CSS: @@ -494,6 +494,8 @@ By default, Rails logs all requests being made to the web application. But log f config.filter_parameters << :password ``` +NOTE: Provided parameters will be filtered out by partial matching regular expression. Rails adds default `:password` in the appropriate initializer (`initializers/filter_parameter_logging.rb`) and cares about typical application parameters `password` and `password_confirmation`. + ### Good Passwords INFO: _Do you find it hard to remember all your passwords? Don't write them down, but use the initial letters of each word in an easy to remember sentence._ @@ -565,7 +567,7 @@ This is alright for some web applications, but certainly not if the user is not Depending on your web application, there will be many more parameters the user can tamper with. As a rule of thumb, _no user input data is secure, until proven otherwise, and every parameter from the user is potentially manipulated_. -Don't be fooled by security by obfuscation and JavaScript security. The Web Developer Toolbar for Mozilla Firefox lets you review and change every form's hidden fields. _JavaScript can be used to validate user input data, but certainly not to prevent attackers from sending malicious requests with unexpected values_. The Live Http Headers plugin for Mozilla Firefox logs every request and may repeat and change them. That is an easy way to bypass any JavaScript validations. And there are even client-side proxies that allow you to intercept any request and response from and to the Internet. +Don't be fooled by security by obfuscation and JavaScript security. Developer tools let you review and change every form's hidden fields. _JavaScript can be used to validate user input data, but certainly not to prevent attackers from sending malicious requests with unexpected values_. The Firebug addon for Mozilla Firefox logs every request and may repeat and change them. That is an easy way to bypass any JavaScript validations. And there are even client-side proxies that allow you to intercept any request and response from and to the Internet. Injection --------- @@ -675,14 +677,12 @@ INFO: _The most widespread, and one of the most devastating security vulnerabili An entry point is a vulnerable URL and its parameters where an attacker can start an attack. -The most common entry points are message posts, user comments, and guest books, but project titles, document names and search result pages have also been vulnerable - just about everywhere where the user can input data. But the input does not necessarily have to come from input boxes on web sites, it can be in any URL parameter - obvious, hidden or internal. Remember that the user may intercept any traffic. Applications, such as the [Live HTTP Headers Firefox plugin](http://livehttpheaders.mozdev.org/), or client-site proxies make it easy to change requests. +The most common entry points are message posts, user comments, and guest books, but project titles, document names and search result pages have also been vulnerable - just about everywhere where the user can input data. But the input does not necessarily have to come from input boxes on web sites, it can be in any URL parameter - obvious, hidden or internal. Remember that the user may intercept any traffic. Applications or client-site proxies make it easy to change requests. There are also other attack vectors like banner advertisements. XSS attacks work like this: An attacker injects some code, the web application saves it and displays it on a page, later presented to a victim. Most XSS examples simply display an alert box, but it is more powerful than that. XSS can steal the cookie, hijack the session, redirect the victim to a fake website, display advertisements for the benefit of the attacker, change elements on the web site to get confidential information or install malicious software through security holes in the web browser. During the second half of 2007, there were 88 vulnerabilities reported in Mozilla browsers, 22 in Safari, 18 in IE, and 12 in Opera. The [Symantec Global Internet Security threat report](http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf) also documented 239 browser plug-in vulnerabilities in the last six months of 2007. [Mpack](http://pandalabs.pandasecurity.com/mpack-uncovered/) is a very active and up-to-date attack framework which exploits these vulnerabilities. For criminal hackers, it is very attractive to exploit an SQL-Injection vulnerability in a web application framework and insert malicious code in every textual table column. In April 2008 more than 510,000 sites were hacked like this, among them the British government, United Nations, and many more high targets. -A relatively new, and unusual, form of entry points are banner advertisements. In earlier 2008, malicious code appeared in banner ads on popular sites, such as MySpace and Excite, according to [Trend Micro](http://blog.trendmicro.com/myspace-excite-and-blick-serve-up-malicious-banner-ads/). - #### HTML/JavaScript Injection The most common XSS language is of course the most popular client-side scripting language JavaScript, often in combination with HTML. _Escaping user input is essential_. @@ -720,7 +720,7 @@ The log files on www.attacker.com will read like this: GET http://www.attacker.com/_app_session=836c1c25278e5b321d6bea4f19cb57e2 ``` -You can mitigate these attacks (in the obvious way) by adding the **httpOnly** flag to cookies, so that document.cookie may not be read by JavaScript. Http only cookies can be used from IE v6.SP1, Firefox v2.0.0.5 and Opera 9.5. Safari is still considering, it ignores the option. But other, older browsers (such as WebTV and IE 5.5 on Mac) can actually cause the page to fail to load. Be warned that cookies [will still be visible using Ajax](https://www.owasp.org/index.php/HTTPOnly#Browsers_Supporting_HttpOnly), though. +You can mitigate these attacks (in the obvious way) by adding the **httpOnly** flag to cookies, so that document.cookie may not be read by JavaScript. HTTP only cookies can be used from IE v6.SP1, Firefox v2.0.0.5, Opera 9.5, Safari 4 and Chrome 1.0.154 onwards. But other, older browsers (such as WebTV and IE 5.5 on Mac) can actually cause the page to fail to load. Be warned that cookies [will still be visible using Ajax](https://www.owasp.org/index.php/HTTPOnly#Browsers_Supporting_HttpOnly), though. ##### Defacement @@ -965,7 +965,7 @@ When `params[:token]` is one of: `[nil]`, `[nil, nil, ...]` or `['foo', nil]` it will bypass the test for `nil`, but `IS NULL` or `IN ('foo', NULL)` where clauses still will be added to the SQL query. -To keep rails secure by default, `deep_munge` replaces some of the values with +To keep Rails secure by default, `deep_munge` replaces some of the values with `nil`. Below table shows what the parameters look like based on `JSON` sent in request: diff --git a/guides/source/testing.md b/guides/source/testing.md index 13f4446751..0ac5121b12 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -37,10 +37,12 @@ controllers/ helpers/ mailers/ test_helper.rb fixtures/ integration/ models/ ``` -The `models` directory is meant to hold tests for your models, the `controllers` directory is meant to hold tests for your controllers and the `integration` directory is meant to hold tests that involve any number of controllers interacting. There is also a directory for testing your mailers and one for testing view helpers. +The `helpers`, `mailers`, and `models` directories are meant to hold tests for view helpers, mailers, and models, respectively. The `controllers` directory is meant to hold tests for controllers, routes, and views. The `integration` directory is meant to hold tests for interactions between controllers. Fixtures are a way of organizing test data; they reside in the `fixtures` directory. +A `jobs` directory will also be created when an associated test is first generated. + The `test_helper.rb` file holds the default configuration for your tests. @@ -146,18 +148,28 @@ Let us run this newly added test (where `6` is the number of line where the test ```bash $ bin/rails test test/models/article_test.rb:6 +Run options: --seed 44656 + +# Running: + F -Finished tests in 0.044632s, 22.4054 tests/s, 22.4054 assertions/s. +Failure: +ArticleTest#test_should_not_save_article_without_title [/path/to/blog/test/models/article_test.rb:6]: +Expected true to be nil or false + + +bin/rails test test/models/article_test.rb:6 - 1) Failure: -test_should_not_save_article_without_title(ArticleTest) [test/models/article_test.rb:6]: -Failed assertion, no message given. -1 tests, 1 assertions, 1 failures, 0 errors, 0 skips + +Finished in 0.023918s, 41.8090 runs/s, 41.8090 assertions/s. + +1 runs, 1 assertions, 1 failures, 0 errors, 0 skips + ``` -In the output, `F` denotes a failure. You can see the corresponding trace shown under `1)` along with the name of the failing test. The next few lines contain the stack trace followed by a message that mentions the actual value and the expected value by the assertion. The default assertion messages provide just enough information to help pinpoint the error. To make the assertion failure message more readable, every assertion provides an optional message parameter, as shown here: +In the output, `F` denotes a failure. You can see the corresponding trace shown under `Failure` along with the name of the failing test. The next few lines contain the stack trace followed by a message that mentions the actual value and the expected value by the assertion. The default assertion messages provide just enough information to help pinpoint the error. To make the assertion failure message more readable, every assertion provides an optional message parameter, as shown here: ```ruby test "should not save article without title" do @@ -169,8 +181,8 @@ end Running this test shows the friendlier assertion message: ```bash - 1) Failure: -test_should_not_save_article_without_title(ArticleTest) [test/models/article_test.rb:6]: +Failure: +ArticleTest#test_should_not_save_article_without_title [/path/to/blog/test/models/article_test.rb:6]: Saved the article without a title ``` @@ -186,11 +198,15 @@ Now the test should pass. Let us verify by running the test again: ```bash $ bin/rails test test/models/article_test.rb:6 +Run options: --seed 31252 + +# Running: + . -Finished tests in 0.047721s, 20.9551 tests/s, 20.9551 assertions/s. +Finished in 0.027476s, 36.3952 runs/s, 36.3952 assertions/s. -1 tests, 1 assertions, 0 failures, 0 errors, 0 skips +1 runs, 1 assertions, 0 failures, 0 errors, 0 skips ``` Now, if you noticed, we first wrote a test which fails for a desired @@ -215,16 +231,25 @@ Now you can see even more output in the console from running the tests: ```bash $ bin/rails test test/models/article_test.rb -E +Run options: --seed 1808 + +# Running: + +.E + +Error: +ArticleTest#test_should_report_error: +NameError: undefined local variable or method 'some_undefined_variable' for #<ArticleTest:0x007fee3aa71798> + test/models/article_test.rb:11:in 'block in <class:ArticleTest>' + -Finished tests in 0.030974s, 32.2851 tests/s, 0.0000 assertions/s. +bin/rails test test/models/article_test.rb:9 - 1) Error: -test_should_report_error(ArticleTest): -NameError: undefined local variable or method `some_undefined_variable' for #<ArticleTest:0x007fe32e24afe0> - test/models/article_test.rb:10:in `block in <class:ArticleTest>' -1 tests, 0 assertions, 0 failures, 1 errors, 0 skips + +Finished in 0.040609s, 49.2500 runs/s, 24.6250 assertions/s. + +2 runs, 1 assertions, 0 failures, 1 errors, 0 skips ``` Notice the 'E' in the output. It denotes a test with error. @@ -287,7 +312,6 @@ specify to make your test failure messages clearer. | `assert_not_in_delta( expected, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are not within `delta` of each other.| | `assert_throws( symbol, [msg] ) { block }` | Ensures that the given block throws the symbol.| | `assert_raises( exception1, exception2, ... ) { block }` | Ensures that the given block raises one of the given exceptions.| -| `assert_nothing_raised( exception1, exception2, ... ) { block }` | Ensures that the given block doesn't raise one of the given exceptions.| | `assert_instance_of( class, obj, [msg] )` | Ensures that `obj` is an instance of `class`.| | `assert_not_instance_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class`.| | `assert_kind_of( class, obj, [msg] )` | Ensures that `obj` is an instance of `class` or is descending from it.| @@ -318,6 +342,7 @@ Rails adds some custom assertions of its own to the `minitest` framework: | --------------------------------------------------------------------------------- | ------- | | [`assert_difference(expressions, difference = 1, message = nil) {...}`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_difference) | Test numeric difference between the return value of an expression as a result of what is evaluated in the yielded block.| | [`assert_no_difference(expressions, message = nil, &block)`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_no_difference) | Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block.| +| [`assert_nothing_raised { block }`](http://api.rubyonrails.org/classes/ActiveSupport/TestCase.html#method-i-assert_nothing_raised) | Ensures that the given block doesn't raise any exceptions.| | [`assert_recognizes(expected_options, path, extras={}, message=nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html#method-i-assert_recognizes) | Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options.| | [`assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html#method-i-assert_generates) | Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes. The extras parameter is used to tell the request the names and values of additional request parameters that would be in a query string. The message parameter allows you to specify a custom error message for assertion failures.| | [`assert_response(type, message = nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/ResponseAssertions.html#method-i-assert_response) | Asserts that the response comes with a specific status code. You can specify `:success` to indicate 200-299, `:redirect` to indicate 300-399, `:missing` to indicate 404, or `:error` to match the 500-599 range. You can also pass an explicit status number or its symbolic equivalent. For more information, see [full list of status codes](http://rubydoc.info/github/rack/rack/master/Rack/Utils#HTTP_STATUS_CODES-constant) and how their [mapping](http://rubydoc.info/github/rack/rack/master/Rack/Utils#SYMBOL_TO_STATUS_CODE-constant) works.| @@ -342,17 +367,21 @@ documentation](http://docs.seattlerb.org/minitest). ### The Rails Test Runner -We can run all of our tests at once by using the `rails test` command. +We can run all of our tests at once by using the `bin/rails test` command. -Or we can run a single test by passing the `rails test` command the filename containing the test cases. +Or we can run a single test file by passing the `bin/rails test` command the filename containing the test cases. ```bash $ bin/rails test test/models/article_test.rb -. +Run options: --seed 1559 -Finished tests in 0.009262s, 107.9680 tests/s, 107.9680 assertions/s. +# Running: -1 tests, 1 assertions, 0 failures, 0 errors, 0 skips +.. + +Finished in 0.027034s, 73.9810 runs/s, 110.9715 assertions/s. + +2 runs, 3 assertions, 0 failures, 0 errors, 0 skips ``` This will run all test methods from the test case. @@ -362,6 +391,10 @@ You can also run a particular test method from the test case by providing the ```bash $ bin/rails test test/models/article_test.rb -n test_the_truth +Run options: -n test_the_truth --seed 43583 + +# Running: + . Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s. @@ -372,7 +405,7 @@ Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s. You can also run a test at a specific line by providing the line number. ```bash -$ bin/rails test test/models/post_test.rb:44 # run specific test and line +$ bin/rails test test/models/article_test.rb:6 # run specific test and line ``` You can also run an entire directory of tests by providing the path to the directory. @@ -381,6 +414,38 @@ You can also run an entire directory of tests by providing the path to the direc $ bin/rails test test/controllers # run all tests from specific directory ``` +The test runner provides lot of other features too like failing fast, deferring test output +at the end of test run and so on. Check the documentation of the test runner as follows: + +```bash +$ bin/rails test -h +minitest options: + -h, --help Display this help. + -s, --seed SEED Sets random seed. Also via env. Eg: SEED=n rake + -v, --verbose Verbose. Show progress processing files. + -n, --name PATTERN Filter run on /regexp/ or string. + --exclude PATTERN Exclude /regexp/ or string from run. + +Known extensions: rails, pride + +Usage: bin/rails test [options] [files or directories] +You can run a single test by appending a line number to a filename: + + bin/rails test test/models/user_test.rb:27 + +You can run multiple files and directories at the same time: + + bin/rails test test/controllers test/integration/login_test.rb + +By default test failures and errors are reported inline during a run. + +Rails options: + -e, --environment ENV Run tests in the ENV environment + -b, --backtrace Show the complete backtrace + -d, --defer-output Output test failures and errors after the test run + -f, --fail-fast Abort test run on first failure or error + -c, --[no-]color Enable color in the output +``` The Test Database ----------------- @@ -495,7 +560,8 @@ users(:david) users(:david).id # one can also access methods available on the User class -email(david.partner.email, david.location_tonight) +david = users(:david) +david.call(david.partner) ``` To get multiple fixtures at once, you can pass in a list of fixture names. For example: @@ -528,7 +594,7 @@ Integration Testing Integration tests are used to test how various parts of your application interact. They are generally used to test important workflows within our application. -For creating Rails integration tests, we use the 'test/integration' directory for our application. Rails provides a generator to create an integration test skeleton for us. +For creating Rails integration tests, we use the `test/integration` directory for our application. Rails provides a generator to create an integration test skeleton for us. ```bash $ bin/rails generate integration_test user_flows @@ -684,9 +750,8 @@ Let's take a look at one such test, `test_should_get_index` from the file `artic # articles_controller_test.rb class ArticlesControllerTest < ActionDispatch::IntegrationTest test "should get index" do - get '/articles' + get articles_url assert_response :success - assert_includes @response.body, 'Articles' end end ``` @@ -694,30 +759,29 @@ end In the `test_should_get_index` test, Rails simulates a request on the action called `index`, making sure the request was successful and also ensuring that the right response body has been generated. -The `get` method kicks off the web request and populates the results into the `@response`. It accepts 4 arguments: - -* The action of the controller you are requesting. - This can be in the form of a string or a route (i.e. `articles_url`). +The `get` method kicks off the web request and populates the results into the `@response`. It can accept up to 6 arguments: +* The URI of the controller action you are requesting. + This can be in the form of a string or a route helper (e.g. `articles_url`). * `params`: option with a hash of request parameters to pass into the action (e.g. query string parameters or article variables). - -* `session`: option with a hash of session variables to pass along with the request. - -* `flash`: option with a hash of flash values. +* `headers`: for setting the headers that will be passed with the request. +* `env`: for customizing the request environment as needed. +* `xhr`: whether the request is Ajax request or not. Can be set to true for marking the request as Ajax. +* `as`: for encoding the request with different content type. Supports `:json` by default. All of these keyword arguments are optional. -Example: Calling the `:show` action, passing an `id` of 12 as the `params` and setting a `user_id` of 5 in the session: +Example: Calling the `:show` action, passing an `id` of 12 as the `params` and setting `HTTP_REFERER` header: ```ruby -get(:show, params: { id: 12 }, session: { user_id: 5 }) +get article_url, params: { id: 12 }, headers: { "HTTP_REFERER" => "http://example.com/home" } ``` -Another example: Calling the `:view` action, passing an `id` of 12 as the `params`, this time with no session, but with a flash message. +Another example: Calling the `:update` action, passing an `id` of 12 as the `params` as an Ajax request. ```ruby -get(view_url, params: { id: 12 }, flash: { message: 'booya!' }) +patch article_url, params: { id: 12 }, xhr: true ``` NOTE: If you try running `test_should_create_article` test from `articles_controller_test.rb` it will fail on account of the newly added model level validation and rightly so. @@ -727,7 +791,7 @@ Let us modify `test_should_create_article` test in `articles_controller_test.rb` ```ruby test "should create article" do assert_difference('Article.count') do - post '/article', params: { article: { title: 'Some title' } } + post articles_url, params: { article: { body: 'Rails is awesome!', title: 'Hello Rails' } } end assert_redirected_to article_path(Article.last) @@ -758,7 +822,7 @@ To test AJAX requests, you can specify the `xhr: true` option to `get`, `post`, ```ruby test "ajax request" do - article = articles(:first) + article = articles(:one) get article_url(article), xhr: true assert_equal 'hello world', @response.body @@ -784,27 +848,38 @@ cookies["are_good_for_u"] cookies[:are_good_for_u] ### Instance Variables Available -You also have access to three instance variables in your functional tests: +You also have access to three instance variables in your functional tests, after a request is made: * `@controller` - The controller processing the request * `@request` - The request object * `@response` - The response object + +```ruby +class ArticlesControllerTest < ActionDispatch::IntegrationTest + test "should get index" do + get articles_url + + assert_equal "index", @controller.action_name + assert_equal "application/x-www-form-urlencoded", @request.media_type + assert_match "Articles", @response.body + end +end +``` + ### Setting Headers and CGI variables [HTTP headers](http://tools.ietf.org/search/rfc2616#section-5.3) and [CGI variables](http://tools.ietf.org/search/rfc3875#section-4.1) -can be set directly on the `@request` instance variable: +can be passed as headers: ```ruby -# setting a HTTP Header -@request.headers["Accept"] = "text/plain, text/html" -get articles_url # simulate the request with custom header +# setting an HTTP Header +get articles_url, headers: { "Content-Type": "text/plain" } # simulate the request with custom header # setting a CGI variable -@request.headers["HTTP_REFERER"] = "http://example.com/home" -post article_url # simulate the request with custom env variable +get articles_url, headers: { "HTTP_REFERER": "http://example.com/home" } # simulate the request with custom env variable ``` ### Testing `flash` notices @@ -840,7 +915,7 @@ F Finished in 0.114870s, 8.7055 runs/s, 34.8220 assertions/s. 1) Failure: -ArticlesControllerTest#test_should_create_article [/Users/zzak/code/bench/sharedapp/test/controllers/articles_controller_test.rb:16]: +ArticlesControllerTest#test_should_create_article [/test/controllers/articles_controller_test.rb:16]: --- expected +++ actual @@ -1 +1 @@ @@ -889,7 +964,7 @@ Let's write a test for the `:show` action: ```ruby test "should show article" do article = articles(:one) - get '/article', params: { id: article.id } + get article_url(article) assert_response :success end ``` @@ -915,7 +990,7 @@ We can also add a test for updating an existing Article. test "should update article" do article = articles(:one) - patch '/article', params: { id: article.id, article: { title: "updated" } } + patch article_url(article), params: { article: { title: "updated" } } assert_redirected_to article_path(article) # Reload association to fetch updated data and assert that title is updated. @@ -958,7 +1033,7 @@ class ArticlesControllerTest < ActionDispatch::IntegrationTest end test "should update article" do - patch '/article', params: { id: @article.id, article: { title: "updated" } } + patch article_url(@article), params: { article: { title: "updated" } } assert_redirected_to article_path(@article) # Reload association to fetch updated data and assert that title is updated. @@ -976,11 +1051,11 @@ To avoid code duplication, you can add your own test helpers. Sign in helper can be a good example: ```ruby -#test/test_helper.rb +# test/test_helper.rb module SignInHelper - def sign_in(user) - session[:user_id] = user.id + def sign_in_as(user) + post sign_in_url(email: user.email, password: user.password) end end @@ -996,7 +1071,7 @@ class ProfileControllerTest < ActionDispatch::IntegrationTest test "should show profile" do # helper is now reusable from any controller test case - sign_in users(:david) + sign_in_as users(:david) get profile_url assert_response :success @@ -1007,7 +1082,7 @@ end Testing Routes -------------- -Like everything else in your Rails application, you can test your routes. +Like everything else in your Rails application, you can test your routes. Route tests reside in `test/controllers/` or are part of controller tests. NOTE: If your application has complex routes, Rails provides a number of useful helpers to test them. @@ -1191,9 +1266,9 @@ testing) but instead it will be appended to an array (`ActionMailer::Base.deliveries`). NOTE: The `ActionMailer::Base.deliveries` array is only reset automatically in -`ActionMailer::TestCase` tests. If you want to have a clean slate outside Action -Mailer tests, you can reset it manually with: -`ActionMailer::Base.deliveries.clear` +`ActionMailer::TestCase` and `ActionDispatch::IntegrationTest` tests. +If you want to have a clean slate outside these test cases, you can reset it +manually with: `ActionMailer::Base.deliveries.clear` ### Functional Testing @@ -1282,11 +1357,11 @@ Here is an example using the [`travel_to`](http://api.rubyonrails.org/classes/Ac user = User.create(name: 'Gaurish', activation_date: Date.new(2004, 10, 24)) assert_not user.applicable_for_gifting? travel_to Date.new(2004, 11, 24) do - assert_equal Date.new(2004, 10, 24), user.activation_date # inside the travel_to block `Date.current` is mocked + assert_equal Date.new(2004, 10, 24), user.activation_date # inside the `travel_to` block `Date.current` is mocked assert user.applicable_for_gifting? end assert_equal Date.new(2004, 10, 24), user.activation_date # The change was visible only inside the `travel_to` block. ``` -Please see [`ActiveSupport::TimeHelpers` API Documentation](http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html) +Please see [`ActiveSupport::Testing::TimeHelpers` API Documentation](http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html) for in-depth information about the available time helpers. diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 0dfa4f1cb8..dda2b12a3a 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -22,12 +22,12 @@ When changing Rails versions, it's best to move slowly, one minor version at a t The process should go as follows: -1. Write tests and make sure they pass -2. Move to the latest patch version after your current version -3. Fix tests and deprecated features -4. Move to the latest patch version of the next minor version +1. Write tests and make sure they pass. +2. Move to the latest patch version after your current version. +3. Fix tests and deprecated features. +4. Move to the latest patch version of the next minor version. -Repeat this process until you reach your target Rails version. Each time you move versions, you will need to change the Rails version number in the Gemfile (and possibly other gem versions) and run `bundle update`. Then run the Update rake task mentioned below to update configuration files, then run your tests. +Repeat this process until you reach your target Rails version. Each time you move versions, you will need to change the Rails version number in the Gemfile (and possibly other gem versions) and run `bundle update`. Then run the Update task mentioned below to update configuration files, then run your tests. You can find a list of all released Rails versions [here](https://rubygems.org/gems/rails/versions). @@ -42,15 +42,15 @@ Rails generally stays close to the latest released Ruby version when it's releas TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition has these fixed since the release of 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump straight to 1.9.3 for smooth sailing. -### The Rake Task +### The Update Task -Rails provides the `rails:update` rake task. After updating the Rails version -in the Gemfile, run this rake task. +Rails provides the `app:update` task (`rake rails:update` on 4.2 and earlier). After updating the Rails version +in the Gemfile, run this task. This will help you with the creation of new files and changes of old files in an interactive session. ```bash -$ rake rails:update +$ rails app:update identical config/boot.rb exist config conflict config/routes.rb @@ -68,13 +68,16 @@ Don't forget to review the difference, to see if there were any unexpected chang Upgrading from Rails 4.2 to Rails 5.0 ------------------------------------- -### Ruby 2.2.2+ +For more information on changes made to Rails 5.0 please see the [release notes](5_0_release_notes.html). -ToDo... +### Ruby 2.2.2+ required -### Active Record models now inherit from ApplicationRecord by default +From Ruby on Rails 5.0 onwards, Ruby 2.2.2+ is the only supported Ruby version. +Make sure you are on Ruby 2.2.2 version or greater, before you proceed. -In Rails 4.2 an Active Record model inherits from `ActiveRecord::Base`. In Rails 5.0, +### Active Record Models Now Inherit from ApplicationRecord by Default + +In Rails 4.2, an Active Record model inherits from `ActiveRecord::Base`. In Rails 5.0, all models inherit from `ApplicationRecord`. `ApplicationRecord` is a new superclass for all app models, analogous to app @@ -82,7 +85,7 @@ controllers subclassing `ApplicationController` instead of `ActionController::Base`. This gives apps a single spot to configure app-wide model behavior. -When upgrading from Rails 4.2 to Rails 5.0 you need to create an +When upgrading from Rails 4.2 to Rails 5.0, you need to create an `application_record.rb` file in `app/models/` and add the following content: ``` @@ -91,7 +94,9 @@ class ApplicationRecord < ActiveRecord::Base end ``` -### Halting callback chains via `throw(:abort)` +Then make sure that all your models inherit from it. + +### Halting Callback Chains via `throw(:abort)` In Rails 4.2, when a 'before' callback returns `false` in Active Record and Active Model, then the entire callback chain is halted. In other words, @@ -116,12 +121,12 @@ halted the chain when any value was returned. See [#17227](https://github.com/rails/rails/pull/17227) for more details. -### ActiveJob jobs now inherit from ApplicationJob by default +### ActiveJob Now Inherits from ApplicationJob by Default -In Rails 4.2 an ActiveJob inherits from `ActiveJob::Base`. In Rails 5.0 this +In Rails 4.2, an Active Job inherits from `ActiveJob::Base`. In Rails 5.0, this behavior has changed to now inherit from `ApplicationJob`. -When upgrading from Rails 4.2 to Rails 5.0 you need to create an +When upgrading from Rails 4.2 to Rails 5.0, you need to create an `application_job.rb` file in `app/jobs/` and add the following content: ``` @@ -133,6 +138,210 @@ Then make sure that all your job classes inherit from it. See [#19034](https://github.com/rails/rails/pull/19034) for more details. +### 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. + +If you are using Rspec for testing, please see the extra configuration required in the gem's +documentation. + +### Autoloading is Disabled After Booting in the Production Environment + +Autoloading is now disabled after booting in the production environment by +default. + +Eager loading the application is part of the boot process, so top-level +constants are fine and are still autoloaded, no need to require their files. + +Constants in deeper places only executed at runtime, like regular method bodies, +are also fine because the file defining them will have been eager loaded while booting. + +For the vast majority of applications this change needs no action. But in the +very rare event that your application needs autoloading while running in +production mode, set `Rails.application.config.enable_dependency_loading` to +true. + +### XML Serialization + +`ActiveModel::Serializers::Xml` has been extracted from Rails to the `activemodel-serializers-xml` +gem. To continue using XML serialization in your application, add `gem 'activemodel-serializers-xml'` +to your Gemfile. + +### Removed Support for Legacy `mysql` Database Adapter + +Rails 5 removes support for the legacy `mysql` database adapter. Most users should be able to +use `mysql2` instead. It will be converted to a separate gem when we find someone to maintain +it. + +### Removed Support for Debugger + +`debugger` is not supported by Ruby 2.2 which is required by Rails 5. Use `byebug` instead. + +### Use bin/rails for running tasks and tests + +Rails 5 adds the ability to run tasks and tests through `bin/rails` instead of rake. Generally +these changes are in parallel with rake, but some were ported over altogether. + +To use the new test runner simply type `bin/rails test`. + +`rake dev:cache` is now `rails dev:cache`. + +Run `bin/rails` to see the list of commands available. + +### `ActionController::Parameters` No Longer Inherits from `HashWithIndifferentAccess` + +Calling `params` in your application will now return an object instead of a hash. If your +parameters are already permitted, then you will not need to make any changes. If you are using `slice` +and other methods that depend on being able to read the hash regardless of `permitted?` you will +need to upgrade your application to first permit and then convert to a hash. + + params.permit([:proceed_to, :return_to]).to_h + +### `protect_from_forgery` Now Defaults to `prepend: false` + +`protect_from_forgery` defaults to `prepend: false` which means that it will be inserted into +the callback chain at the point in which you call it in your application. If you want +`protect_from_forgery` to always run first, then you should change your application to use +`protect_from_forgery prepend: true`. + +### Default Template Handler is Now RAW + +Files without a template handler in their extension will be rendered using the raw handler. +Previously Rails would render files using the ERB template handler. + +If you do not want your file to be handled via the raw handler, you should add an extension +to your file that can be parsed by the appropriate template handler. + +### Added Wildcard Matching for Template Dependencies + +You can now use wildcard matching for your template dependencies. For example, if you were +defining your templates as such: + +```erb +<% # Template Dependency: recordings/threads/events/subscribers_changed %> +<% # Template Dependency: recordings/threads/events/completed %> +<% # Template Dependency: recordings/threads/events/uncompleted %> +``` + +You can now just call the dependency once with a wildcard. + +```erb +<% # Template Dependency: recordings/threads/events/* %> +``` + +### Removed Support for `protected_attributes` Gem + +The `protected_attributes` gem is no longer supported in Rails 5. + +### Removed support for `activerecord-deprecated_finders` gem + +The `activerecord-deprecated_finders` gem is no longer supported in Rails 5. + +### `ActiveSupport::TestCase` Default Test Order is Now Random + +When tests are run in your application, the default order is now `:random` +instead of `:sorted`. Use the following config option to set it back to `:sorted`. + +```ruby +# config/environments/test.rb +Rails.application.configure do + config.active_support.test_order = :sorted +end +``` + +### `ActionController::Live` became a `Concern` + +If you include `ActionController::Live` in another module that is included in your controller, then you +should also extend the module with `ActiveSupport::Concern`. Alternatively, you can use the `self.included` hook +to include `ActionController::Live` directly to the controller once the `StreamingSupport` is included. + +This means that if your application used to have its own streaming module, the following code +would break in production mode: + +```ruby +# This is a work-around for streamed controllers performing authentication with Warden/Devise. +# See https://github.com/plataformatec/devise/issues/2332 +# Authenticating in the router is another solution as suggested in that issue +class StreamingSupport + include ActionController::Live # this won't work in production for Rails 5 + # extend ActiveSupport::Concern # unless you uncomment this line. + + def process(name) + super(name) + rescue ArgumentError => e + if e.message == 'uncaught throw :warden' + throw :warden + else + raise e + end + end +end +``` + +### New Framework Defaults + +#### Active Record `belongs_to` Required by Default Option + +`belongs_to` will now trigger a validation error by default if the association is not present. + +This can be turned off per-association with `optional: true`. + +This default will be automatically configured in new applications. If existing application +want to add this feature it will need to be turned on in an initializer. + + config.active_record.belongs_to_required_by_default = true + +#### Per-form CSRF Tokens + +Rails 5 now supports per-form CSRF tokens to mitigate against code-injection attacks with forms +created by JavaScript. With this option turned on, forms in your application will each have their +own CSRF token that is specified to the action and method for that form. + + config.action_controller.per_form_csrf_tokens = true + +#### Forgery Protection with Origin Check + +You can now configure your application to check if the HTTP `Origin` header should be checked +against the site's origin as an additional CSRF defense. Set the following in your config to +true: + + config.action_controller.forgery_protection_origin_check = true + +#### Allow Configuration of Action Mailer Queue Name + +The default mailer queue name is `mailers`. This configuration option allows you to globally change +the queue name. Set the following in your config: + + config.action_mailer.deliver_later_queue_name = :new_queue_name + +#### Support Fragment Caching in Action Mailer Views + +Set `config.action_mailer.perform_caching` in your config to determine whether your Action Mailer views +should support caching. + + config.action_mailer.perform_caching = true + +#### Configure the Output of `db:structure:dump` + +If you're using `schema_search_path` or other PostgreSQL extensions, you can control how the schema is +dumped. Set to `:all` to generate all dumps, or to `:schema_search_path` to generate from schema search path. + + config.active_record.dump_schemas = :all + +#### Configure SSL Options to Enable HSTS with Subdomains + +Set the following in your config to enable HSTS when using subdomains: + + config.ssl_options = { hsts: { subdomains: true } } + +#### Preserve Timezone of the Receiver + +When using Ruby 2.4, you can preserve the timezone of the receiver when calling `to_time`. + + ActiveSupport.to_time_preserves_timezone = false + Upgrading from Rails 4.1 to Rails 4.2 ------------------------------------- @@ -417,7 +626,7 @@ secrets, you need to: 3. Remove the `secret_token.rb` initializer. -4. Use `rails secret` to generate new keys for the `development` and `test` sections. +4. Use `rake secret` to generate new keys for the `development` and `test` sections. 5. Restart your server. @@ -880,7 +1089,7 @@ Rails 4.0 no longer supports loading plugins from `vendor/plugins`. You must rep * Rails 4.0 has removed the identity map from Active Record, due to [some inconsistencies with associations](https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6). If you have manually enabled it in your application, you will have to remove the following config that has no effect anymore: `config.active_record.identity_map`. -* The `delete` method in collection associations can now receive `Fixnum` or `String` arguments as record ids, besides records, pretty much like the `destroy` method does. Previously it raised `ActiveRecord::AssociationTypeMismatch` for such arguments. From Rails 4.0 on `delete` automatically tries to find the records matching the given ids before deleting them. +* The `delete` method in collection associations can now receive `Integer` or `String` arguments as record ids, besides records, pretty much like the `destroy` method does. Previously it raised `ActiveRecord::AssociationTypeMismatch` for such arguments. From Rails 4.0 on `delete` automatically tries to find the records matching the given ids before deleting them. * In Rails 4.0 when a column or a table is renamed the related indexes are also renamed. If you have migrations which rename the indexes, they are no longer needed. @@ -978,7 +1187,7 @@ Please read [Pull Request #9978](https://github.com/rails/rails/pull/9978) for d * Rails 4.0 has deprecated `ActionController::Base.page_cache_extension` option. Use `ActionController::Base.default_static_extension` instead. -* Rails 4.0 has removed Action and Page caching from Action Pack. You will need to add the `actionpack-action_caching` gem in order to use `caches_action` and the `actionpack-page_caching` to use `caches_pages` in your controllers. +* Rails 4.0 has removed Action and Page caching from Action Pack. You will need to add the `actionpack-action_caching` gem in order to use `caches_action` and the `actionpack-page_caching` to use `caches_page` in your controllers. * Rails 4.0 has removed the XML parameters parser. You will need to add the `actionpack-xml_parser` gem if you require this feature. @@ -1217,7 +1426,7 @@ config.assets.digest = true # config.assets.manifest = YOUR_PATH # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) -# config.assets.precompile += %w( search.js ) +# config.assets.precompile += %w( admin.js admin.css ) # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # config.force_ssl = true diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index 26ff5da7a3..c1dfcab6f3 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -328,7 +328,7 @@ this: respond_to do |format| if @user.save format.html { redirect_to @user, notice: 'User was successfully created.' } - format.js {} + format.js format.json { render json: @user, status: :created, location: @user } else format.html { render action: "new" } @@ -350,8 +350,8 @@ $("<%= escape_javascript(render @user) %>").appendTo("#users"); Turbolinks ---------- -Rails 4 ships with the [Turbolinks gem](https://github.com/turbolinks/turbolinks). -This gem uses Ajax to speed up page rendering in most applications. +Rails ships with the [Turbolinks library](https://github.com/turbolinks/turbolinks), +which uses Ajax to speed up page rendering in most applications. ### How Turbolinks Works @@ -364,14 +364,14 @@ will then use PushState to change the URL to the correct one, preserving refresh semantics and giving you pretty URLs. The only thing you have to do to enable Turbolinks is have it in your Gemfile, -and put `//= require turbolinks` in your CoffeeScript manifest, which is usually +and put `//= require turbolinks` in your JavaScript manifest, which is usually `app/assets/javascripts/application.js`. -If you want to disable Turbolinks for certain links, add a `data-no-turbolink` +If you want to disable Turbolinks for certain links, add a `data-turbolinks="false"` attribute to the tag: ```html -<a href="..." data-no-turbolink>No turbolinks here</a>. +<a href="..." data-turbolinks="false">No turbolinks here</a>. ``` ### Page Change Events @@ -389,7 +389,7 @@ event that this relies on will not be fired. If you have code that looks like this, you must change your code to do this instead: ```coffeescript -$(document).on "page:change", -> +$(document).on "turbolinks:load", -> alert "page has loaded!" ``` diff --git a/guides/w3c_validator.rb b/guides/w3c_validator.rb index 71f044b9c4..2ce27e2e16 100644 --- a/guides/w3c_validator.rb +++ b/guides/w3c_validator.rb @@ -21,17 +21,16 @@ # # Separate many using commas: # -# # validates only association_basics.html and migrations.html -# rake guides:validate ONLY=assoc,migrations +# # validates only association_basics.html and command_line.html +# rake guides:validate ONLY=assoc,command # # --------------------------------------------------------------------------- -require 'w3c_validators' +require "w3c_validators" include W3CValidators module RailsGuides class Validator - def validate validator = MarkupValidator.new STDOUT.sync = true @@ -57,40 +56,39 @@ module RailsGuides end private - def guides_to_validate - guides = Dir["./output/*.html"] - guides.delete("./output/layout.html") - guides.delete("./output/_license.html") - guides.delete("./output/_welcome.html") - ENV.key?('ONLY') ? select_only(guides) : guides - end + def guides_to_validate + guides = Dir["./output/*.html"] + guides.delete("./output/layout.html") + guides.delete("./output/_license.html") + guides.delete("./output/_welcome.html") + ENV.key?("ONLY") ? select_only(guides) : guides + end - def select_only(guides) - prefixes = ENV['ONLY'].split(",").map(&:strip) - guides.select do |guide| - prefixes.any? {|p| guide.start_with?("./output/#{p}")} + def select_only(guides) + prefixes = ENV["ONLY"].split(",").map(&:strip) + guides.select do |guide| + prefixes.any? { |p| guide.start_with?("./output/#{p}") } + end end - end - def show_results(error_list) - if error_list.size == 0 - puts "\n\nAll checked guides validate OK!" - else - error_summary = error_detail = "" + def show_results(error_list) + if error_list.size == 0 + puts "\n\nAll checked guides validate OK!" + else + error_summary = error_detail = "" - error_list.each_pair do |name, errors| - error_summary += "\n #{name}" - error_detail += "\n\n #{name} has #{errors.size} validation error(s):\n" - errors.each do |error| - error_detail += "\n "+error.to_s.delete("\n") + error_list.each_pair do |name, errors| + error_summary += "\n #{name}" + error_detail += "\n\n #{name} has #{errors.size} validation error(s):\n" + errors.each do |error| + error_detail += "\n "+error.to_s.delete("\n") + end end - end - puts "\n\nThere are #{error_list.size} guides with validation errors:\n" + error_summary - puts "\nHere are the detailed errors for each guide:" + error_detail + puts "\n\nThere are #{error_list.size} guides with validation errors:\n" + error_summary + puts "\nHere are the detailed errors for each guide:" + error_detail + end end - end - end end diff --git a/load_paths.rb b/load_paths.rb deleted file mode 100644 index 0ad8fcfeda..0000000000 --- a/load_paths.rb +++ /dev/null @@ -1,3 +0,0 @@ -# bust gem prelude -require 'bundler' -Bundler.setup diff --git a/rails.gemspec b/rails.gemspec index d4e78b6aa1..2d5be58c17 100644 --- a/rails.gemspec +++ b/rails.gemspec @@ -1,33 +1,33 @@ -version = File.read(File.expand_path('../RAILS_VERSION', __FILE__)).strip +version = File.read(File.expand_path("../RAILS_VERSION", __FILE__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY - s.name = 'rails' + s.name = "rails" s.version = version - s.summary = 'Full-stack web application framework.' - s.description = 'Ruby on Rails is a full-stack web framework optimized for programmer happiness and sustainable productivity. It encourages beautiful code by favoring convention over configuration.' + s.summary = "Full-stack web application framework." + s.description = "Ruby on Rails is a full-stack web framework optimized for programmer happiness and sustainable productivity. It encourages beautiful code by favoring convention over configuration." - s.required_ruby_version = '>= 2.2.2' - s.required_rubygems_version = '>= 1.8.11' + s.required_ruby_version = ">= 2.2.2" + s.required_rubygems_version = ">= 1.8.11" - s.license = 'MIT' + s.license = "MIT" - s.author = 'David Heinemeier Hansson' - s.email = 'david@loudthinking.com' - s.homepage = 'http://www.rubyonrails.org' + s.author = "David Heinemeier Hansson" + s.email = "david@loudthinking.com" + s.homepage = "http://rubyonrails.org" - s.files = ['README.md'] + s.files = ["README.md"] - s.add_dependency 'activesupport', version - s.add_dependency 'actionpack', version - s.add_dependency 'actionview', version - s.add_dependency 'activemodel', version - s.add_dependency 'activerecord', version - s.add_dependency 'actionmailer', version - s.add_dependency 'activejob', version - s.add_dependency 'actioncable', version - s.add_dependency 'railties', version + s.add_dependency "activesupport", version + s.add_dependency "actionpack", version + s.add_dependency "actionview", version + s.add_dependency "activemodel", version + s.add_dependency "activerecord", version + s.add_dependency "actionmailer", version + s.add_dependency "activejob", version + s.add_dependency "actioncable", version + s.add_dependency "railties", version - s.add_dependency 'bundler', '>= 1.3.0', '< 2.0' - s.add_dependency 'sprockets-rails', '>= 2.0.0' + s.add_dependency "bundler", ">= 1.3.0", "< 2.0" + s.add_dependency "sprockets-rails", ">= 2.0.0" end diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 2506baac16..b488e4ed8e 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,473 +1,67 @@ -* The Gemfiles of new applications include spring-watcher-listen on Linux and - Mac OS X (unless --skip-spring). +* Allow the use of listen's 3.1.x branch - *Xavier Noria* + *Esteban Santana Santana* -* New applications are generated with the evented file system monitor enabled - on Linux and Mac OS X. +* Run `Minitest.after_run` hooks when running `rails test`. - *Xavier Noria* + *Michael Grosser* -* Add dummy files for apple-touch-icon.png and apple-touch-icon.png. GH#23427 +* Run `before_configuration` callbacks as soon as application constant + inherits from `Rails::Application`. - *Alexey Zabelin* - -## Rails 5.0.0.beta2 (February 01, 2016) ## - -* Add `after_bundle` callbacks in Rails plugin templates. Useful for allowing - templates to perform actions that are dependent upon `bundle install`. - - *Ryan Manuel* - -* Bring back `TEST=` env for `rake test` task. - - *Yves Senn* - -* Specify log file names or all logs to clear `rake log:clear` - - Specify which logs to clear when using the `rake log:clear` task, e.g. `rake log:clear LOGS=test,staging` - - Clear all logs from log/*.log e.g. `rake log:clear LOGS=all` - - By default `rake log:clear` clears standard environment log files i.e. 'development,test,production' - - *Pramod Shinde* - -* Fix using `add_source` with a block after using `gem` in a custom generator. - - *Will Fisher* - - -## Rails 5.0.0.beta1 (December 18, 2015) ## - -* Newly generated plugins get a `README.md` in Markdown. - - *Yuji Yaginuma* - -* The generated config file for the development environment includes a new - config line, commented out, showing how to enable the evented file watcher. - - *Xavier Noria* - -* `config.debug_exception_response_format` configures the format used - in responses when errors occur in development mode. - - Set `config.debug_exception_response_format` to render an HTML page with - debug info (using the value `:default`) or render debug info preserving - the response format (using the value `:api`). - - *Jorge Bejar* - -* Fix setting exit status code for rake test tasks. The exit status code - was not set when tests were fired with `rake`. Now, it is being set and it matches - behavior of running tests via `rails` command (`rails test`), so no matter if - `rake test` or `rails test` command is used the exit code will be set. - - *Arkadiusz Fal* - -* Add Command infrastructure to replace rake. - - Also move `rake dev:cache` to new infrastructure. You'll need to use - `rails dev:cache` to toggle development caching from now on. - - *Chuck Callebs* - -* Allow use of minitest-rails gem with Rails test runner. - - Fixes #22455. - - *Chris Kottom* - -* Add `bin/test` script to rails plugin. - - `bin/test` can use the same API as `bin/rails test`. - - *Yuji Yaginuma* - -* Make `static_index` part of the `config.public_file_server` config and - call it `public_file_server.index_name`. - - *Yuki Nishijima* - -* Deprecate `serve_static_files` in favor of `public_file_server.enabled`. - - Unifies the static asset options under `public_file_server`. - - To upgrade, replace occurrences of: - - ``` - config.serve_static_files = # false or true - ``` - - in your environment files, with: - - ``` - config.public_file_server.enabled = # false or true - ``` - - *Kasper Timm Hansen* - -* Deprecate `config.static_cache_control` in favor of - `config.public_file_server.headers`. - - To upgrade, replace occurrences of: - - ``` - config.static_cache_control = 'public, max-age=60' - ``` - - in your environment files, with: - - ``` - config.public_file_server.headers = { - 'Cache-Control' => 'public, max-age=60' - } - ``` - - `config.public_file_server.headers` can set arbitrary headers, sent along when - a response is delivered. - - *Yuki Nishijima* - -* Route generator should be idempotent - running generators several times no longer require you to cleanup routes.rb - - *Thiago Pinto* - -* Allow passing an environment to `config_for`. - - *Simon Eskildsen* - -* Allow rake:stats to account for rake tasks in lib/tasks - - *Kevin Deisz* - -* Added javascript to update the URL on mailer previews with the currently - selected email format. Reloading the page now keeps you on your selected - format rather than going back to the default html version. - - *James Kerr* - -* Add fail fast to `bin/rails test` - - Adding `--fail-fast` or `-f` when running tests will interrupt the run on - the first failure: - - ``` - # Running: - - ................................................S......E - - ArgumentError: Wups! Bet you didn't expect this! - test/models/bunny_test.rb:19:in `block in <class:BunnyTest>' - - bin/rails test test/models/bunny_test.rb:18 - - ....................................F - - This failed - - bin/rails test test/models/bunny_test.rb:14 - - Interrupted. Exiting... - - - Finished in 0.051427s, 1808.3872 runs/s, 1769.4972 assertions/s. - - ``` - - Note that any unexpected errors don't abort the run. - - *Kasper Timm Hansen* - -* Add inline output to `bin/rails test` - - Any failures or errors (and skips if running in verbose mode) are output - during a test run: - - ``` - # Running: - - .....S..........................................F - - This failed - - bin/rails test test/models/bunny_test.rb:14 - - .................................E - - ArgumentError: Wups! Bet you didn't expect this! - test/models/bunny_test.rb:19:in `block in <class:BunnyTest>' - - bin/rails test test/models/bunny_test.rb:18 - - .................... - - Finished in 0.069708s, 1477.6019 runs/s, 1448.9106 assertions/s. - ``` - - Output can be deferred to after a run with the `--defer-output` option. - - *Kasper Timm Hansen* - -* Fix displaying mailer previews on non local requests when config - `action_mailer.show_previews` is set - - *Wojciech WnÄ™trzak* - -* `rails server` will now honour the `PORT` environment variable - - *David Cornu* - -* Plugins generated using `rails plugin new` are now generated with the - version number set to 0.1.0. - - *Daniel Morris* - -* `I18n.load_path` is now reloaded under development so there's no need to - restart the server to make new locale files available. Also, I18n will no - longer raise for deleted locale files. - - *Kir Shatrov* - -* Add `bin/update` script to update development environment automatically. - - *Mehmet Emin İNAÇ* - -* Fix STATS_DIRECTORIES already defined warning when running rake from within - the top level directory of an engine that has a test app. - - Fixes #20510. - - *Ersin Akinci* - -* Make enabling or disabling caching in development mode possible with - rake dev:cache. - - Running rake dev:cache will create or remove tmp/caching-dev.txt. When this - file exists config.action_controller.perform_caching will be set to true in - config/environments/development.rb. - - Additionally, a server can be started with either --dev-caching or - --no-dev-caching included to toggle caching on startup. - - *Jussi Mertanen*, *Chuck Callebs* - -* Add a `--api` option in order to generate plugins that can be added - inside an API application. - - *Robin Dupret* - -* Fix `NoMethodError` when generating a scaffold inside a full engine. + Fixes #19880. *Yuji Yaginuma* -* Adding support for passing a block to the `add_source` action of a custom generator - - *Mike Dalton*, *Hirofumi Wakasugi* +* A generated app should not include Uglifier with `--skip-javascript` option. -* `assert_file` understands paths with special characters - (eg. `v0.1.4~alpha+nightly`). + *Ben Pickles* - *Diego Carrion* +* Set session store to cookie store internally and remove the initializer from + the generated app. -* Remove ContentLength middleware from the defaults. If you want it, just - add it as a middleware in your config. + *Prathamesh Sonpatki* - *Egg McMuffin* +* Set the server host using the `HOST` environment variable. -* Make it possible to customize the executable inside rerun snippets. + *mahnunchik* - *Yves Senn* - -* Add support for API only apps. - Middleware stack was slimmed down and it has only the needed - middleware for API apps & generators generates the right files, - folders and configurations. - - *Santiago Pastorino*, *Jorge Bejar* - -* Make generated scaffold functional tests work inside engines. - - *Yuji Yaginuma* - -* Generate a `.keep` file in the `tmp` folder by default as many scripts - assume the existence of this folder and most would fail if it is absent. - - See #20299. - - *Yoong Kang Lim*, *Sunny Juneja* - -* `config.static_index` configures directory `index.html` filename - - Set `config.static_index` to serve a static directory index file not named - `index`. E.g. to serve `main.html` instead of `index.html` for directory - requests, set `config.static_index` to `"main"`. - - *Eliot Sykes* - -* `bin/setup` uses built-in rake tasks (`log:clear`, `tmp:clear`). - - *Mohnish Thallavajhula* - -* Fix mailer previews with attachments by using the mail gem's own API to - locate the first part of the correct mime type. - - Fixes #14435. - - *Andrew White* - -* Remove sqlite support from `rails dbconsole`. - - *Andrew White* - -* Rename `railties/bin` to `railties/exe` to match the new Bundler executables - convention. - - *Islam Wazery* - -* Print `bundle install` output in `rails new` as soon as it's available. - - Running `rails new` will now print the output of `bundle install` as - it is available, instead of waiting until all gems finish installing. - - *Max Holder* - -* Respect `pluralize_table_names` when generating fixture file. - - Fixes #19519. - - *Yuji Yaginuma* +* Add public API to register new folders for `rake notes`: -* Add a new-line to the end of route method generated code. + config.annotations.register_directories('spec', 'features') - We need to add a `\n`, because we cannot have two routes - in the same line. + *John Meehan* - *arthurnn* +* Display name of the class defining the initializer along with the initializer + name in the output of `rails initializers`. -* Add `rake initializers`. + Before: + disable_dependency_loading - This task prints out all defined initializers in the order they are invoked - by Rails. This is helpful for debugging issues related to the initialization - process. + After: + DemoApp::Application.disable_dependency_loading - *Naoto Kaneko* + *ta1kt0me* -* Created rake restart task. Restarts your Rails app by touching the - `tmp/restart.txt`. +* Do not run `bundle install` when generating a new plugin. - See #18876. - - *Hyonjee Joo* - -* Add `config/initializers/active_record_belongs_to_required_by_default.rb`. - - Newly generated Rails apps have a new initializer called - `active_record_belongs_to_required_by_default.rb` which sets the value of - the configuration option `config.active_record.belongs_to_required_by_default` - to `true` when ActiveRecord is not skipped. - - As a result, new Rails apps require `belongs_to` association on model - to be valid. - - This initializer is *not* added when running `rake rails:update`, so - old apps ported to Rails 5 will work without any change. - - *Josef Å imánek* - -* `delete` operations in configurations are run last in order to eliminate - 'No such middleware' errors when `insert_before` or `insert_after` are added - after the `delete` operation for the middleware being deleted. - - Fixes #16433. - - *Guo Xiang Tan* - -* Newly generated applications get a `README.md` in Markdown. - - *Xavier Noria* - -* Remove the documentation tasks `doc:app`, `doc:rails`, and `doc:guides`. - - *Xavier Noria* - -* Force generated routes to be inserted into `config/routes.rb`. - - *Andrew White* - -* Don't remove all line endings from `config/routes.rb` when revoking scaffold. - - Fixes #15913. - - *Andrew White* - -* Rename `--skip-test-unit` option to `--skip-test` in app generator - - *Melanie Gilman* - -* Add the `method_source` gem to the default Gemfile for apps. - - *Sean Griffin* - -* Drop old test locations from `rake stats`: - - - test/functional - - test/unit - - *Ravil Bayramgalin* - -* Update `rake stats` to correctly count declarative tests - as methods in `_test.rb` files. - - *Ravil Bayramgalin* - -* Remove deprecated `test:all` and `test:all:db` tasks. - - *Rafael Mendonça França* - -* Remove deprecated `Rails::Rack::LogTailer`. - - *Rafael Mendonça França* - -* Remove deprecated `RAILS_CACHE` constant. - - *Rafael Mendonça França* - -* Remove deprecated `serve_static_assets` configuration. + Since bundler 1.12.0, the gemspec is validated so the `bundle install` + command will fail just after the gem is created causing confusion to the + users. This change was a bug fix to correctly validate gemspecs. *Rafael Mendonça França* -* Use local variables in `_form.html.erb` partial generated by scaffold. - - *Andrew Kozlov* - -* Add `config/initializers/callback_terminator.rb`. - - Newly generated Rails apps have a new initializer called - `callback_terminator.rb` which sets the value of the configuration option - `ActiveSupport.halt_callback_chains_on_return_false` to `false`. - - As a result, new Rails apps do not halt Active Record and Active Model - callback chains when a callback returns `false`; only when they are - explicitly halted with `throw(:abort)`. - - The terminator is *not* added when running `rake rails:update`, so returning - `false` will still work on old apps ported to Rails 5, displaying a - deprecation warning to prompt users to update their code to the new syntax. - - *claudiob* - -* Generated fixtures won't use the id when generated with references attributes. +* Default `config.assets.quiet = true` in the development environment. Suppress + logging of assets requests by default. - *Pablo Olmos de Aguilera Corradini* + *Kevin McPhillips* -* Add `--skip-action-mailer` option to the app generator. +* Ensure `/rails/info` routes match in development for apps with a catch-all globbing route. - *claudiob* + *Nicholas Firth-McCoy* -* Autoload any second level directories called `app/*/concerns`. +* Added a shared section to `config/secrets.yml` that will be loaded for all environments. - *Alex Robbin* + *DHH* -Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/railties/CHANGELOG.md) for previous changes. +Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/railties/CHANGELOG.md) for previous changes. diff --git a/railties/RDOC_MAIN.rdoc b/railties/RDOC_MAIN.rdoc index 26a25ee9dc..ef9bbf3d7e 100644 --- a/railties/RDOC_MAIN.rdoc +++ b/railties/RDOC_MAIN.rdoc @@ -35,17 +35,17 @@ can read more about Action Pack in its {README}[link:files/actionpack/README_rdo 1. Install \Rails at the command prompt if you haven't yet: - gem install rails + $ gem install rails 2. At the command prompt, create a new \Rails application: - rails new myapp + $ rails new myapp where "myapp" is the application name. 3. Change directory to +myapp+ and start the web server: - cd myapp; rails server + $ cd myapp; rails server Run with <tt>--help</tt> or <tt>-h</tt> for options. diff --git a/railties/Rakefile b/railties/Rakefile index 3421d9b464..202644fb26 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -1,12 +1,11 @@ -require 'rake/testtask' +require "rake/testtask" -task :default => :test +task default: :test task :package -task "package:clean" desc "Run all unit tests" -task :test => 'test:isolated' +task test: "test:isolated" namespace :test do task :isolated do @@ -15,8 +14,8 @@ namespace :test do Dir[*test_files].each do |file| next true if file.include?("fixtures") dash_i = [ - 'test', - 'lib', + "test", + "lib", "#{File.dirname(__FILE__)}/../activesupport/lib", "#{File.dirname(__FILE__)}/../actionpack/lib", "#{File.dirname(__FILE__)}/../activemodel/lib" @@ -26,9 +25,9 @@ namespace :test do end end -Rake::TestTask.new('test:regular') do |t| - t.libs << 'test' << "#{File.dirname(__FILE__)}/../activesupport/lib" - t.pattern = 'test/**/*_test.rb' +Rake::TestTask.new("test:regular") do |t| + t.libs << "test" << "#{File.dirname(__FILE__)}/../activesupport/lib" + t.pattern = "test/**/*_test.rb" t.warning = false t.verbose = true t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) diff --git a/railties/exe/rails b/railties/exe/rails index 82c17cabce..7e791c1f99 100755 --- a/railties/exe/rails +++ b/railties/exe/rails @@ -1,9 +1,9 @@ #!/usr/bin/env ruby -git_path = File.expand_path('../../../.git', __FILE__) +git_path = File.expand_path("../../../.git", __FILE__) if File.exist?(git_path) - railties_path = File.expand_path('../../lib', __FILE__) + railties_path = File.expand_path("../../lib", __FILE__) $:.unshift(railties_path) end require "rails/cli" diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb index fe789f3c2a..5d862e3fec 100644 --- a/railties/lib/rails.rb +++ b/railties/lib/rails.rb @@ -1,18 +1,18 @@ -require 'rails/ruby_version_check' +require "rails/ruby_version_check" -require 'pathname' +require "pathname" -require 'active_support' -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" +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 'rails/application' -require 'rails/version' +require "rails/application" +require "rails/version" -require 'active_support/railtie' -require 'action_dispatch/railtie' +require "active_support/railtie" +require "action_dispatch/railtie" # UTF-8 is the default internal and external encoding. silence_warnings do @@ -46,13 +46,13 @@ module Rails def backtrace_cleaner @backtrace_cleaner ||= begin - # Relies on Active Support, so we have to lazy load to postpone definition until AS has been loaded - require 'rails/backtrace_cleaner' + # Relies on Active Support, so we have to lazy load to postpone definition until Active Support has been loaded + require "rails/backtrace_cleaner" Rails::BacktraceCleaner.new end end - # Returns a Pathname object of the current rails project, + # Returns a Pathname object of the current Rails project, # otherwise it returns nil if there is no project: # # Rails.root @@ -77,7 +77,7 @@ module Rails @_env = ActiveSupport::StringInquirer.new(environment) end - # Returns all rails groups for loading based on: + # Returns all Rails groups for loading based on: # # * The Rails environment; # * The environment variable RAILS_GROUPS; @@ -100,7 +100,7 @@ module Rails end # Returns a Pathname object of the public folder of the current - # rails project, otherwise it returns nil if there is no project: + # Rails project, otherwise it returns nil if there is no project: # # Rails.public_path # # => #<Pathname:/Users/someuser/some/path/project/public> diff --git a/railties/lib/rails/all.rb b/railties/lib/rails/all.rb index 11f4d5c4bc..7606ea0e46 100644 --- a/railties/lib/rails/all.rb +++ b/railties/lib/rails/all.rb @@ -11,7 +11,7 @@ require "rails" sprockets/railtie ).each do |railtie| begin - require "#{railtie}" + require railtie rescue LoadError end end diff --git a/railties/lib/rails/api/task.rb b/railties/lib/rails/api/task.rb index d478bbf9e8..bc670b1d75 100644 --- a/railties/lib/rails/api/task.rb +++ b/railties/lib/rails/api/task.rb @@ -1,33 +1,33 @@ -require 'rdoc/task' +require "rdoc/task" module Rails module API class Task < RDoc::Task RDOC_FILES = { - 'activesupport' => { - :include => %w( + "activesupport" => { + include: %w( README.rdoc lib/active_support/**/*.rb ), - :exclude => 'lib/active_support/vendor/*' + exclude: "lib/active_support/vendor/*" }, - 'activerecord' => { - :include => %w( + "activerecord" => { + include: %w( README.rdoc lib/active_record/**/*.rb ) }, - 'activemodel' => { - :include => %w( + "activemodel" => { + include: %w( README.rdoc lib/active_model/**/*.rb ) }, - 'actionpack' => { - :include => %w( + "actionpack" => { + include: %w( README.rdoc lib/abstract_controller/**/*.rb lib/action_controller/**/*.rb @@ -35,41 +35,41 @@ module Rails ) }, - 'actionview' => { - :include => %w( + "actionview" => { + include: %w( README.rdoc lib/action_view/**/*.rb ), - :exclude => 'lib/action_view/vendor/*' + exclude: "lib/action_view/vendor/*" }, - 'actionmailer' => { - :include => %w( + "actionmailer" => { + include: %w( README.rdoc lib/action_mailer/**/*.rb ) }, - 'activejob' => { - :include => %w( + "activejob" => { + include: %w( README.md lib/active_job/**/*.rb ) }, - 'actioncable' => { - :include => %w( + "actioncable" => { + include: %w( README.md lib/action_cable/**/*.rb ) }, - 'railties' => { - :include => %w( + "railties" => { + include: %w( README.rdoc lib/**/*.rb ), - :exclude => 'lib/rails/generators/rails/**/templates/**/*.rb' + exclude: "lib/rails/generators/rails/**/templates/**/*.rb" } } @@ -92,16 +92,16 @@ module Rails end def load_and_configure_sdoc - require 'sdoc' + require "sdoc" - self.title = 'Ruby on Rails API' + self.title = "Ruby on Rails API" self.rdoc_dir = api_dir - options << '-m' << api_main - options << '-e' << 'UTF-8' + options << "-m" << api_main + options << "-e" << "UTF-8" - options << '-f' << 'sdoc' - options << '-T' << 'rails' + options << "-f" << "sdoc" + options << "-T" << "rails" rescue LoadError $stderr.puts %(Unable to load SDoc, please add\n\n gem 'sdoc', require: false\n\nto the Gemfile.) exit 1 @@ -121,22 +121,35 @@ module Rails rdoc_files.exclude("#{cdr}/#{pattern}") end end + + # Only generate documentation for files that have been + # changed since the API was generated. + if Dir.exist?("doc/rdoc") && !ENV["ALL"] + last_generation = DateTime.rfc2822(File.open("doc/rdoc/created.rid", &:readline)) + + rdoc_files.keep_if do |file| + File.mtime(file).to_datetime > last_generation + end + + # Nothing to do + exit(0) if rdoc_files.empty? + end end def setup_horo_variables - ENV['HORO_PROJECT_NAME'] = 'Ruby on Rails' - ENV['HORO_PROJECT_VERSION'] = rails_version + ENV["HORO_PROJECT_NAME"] = "Ruby on Rails" + ENV["HORO_PROJECT_VERSION"] = rails_version end def api_main - component_root_dir('railties') + '/RDOC_MAIN.rdoc' + component_root_dir("railties") + "/RDOC_MAIN.rdoc" end end class RepoTask < Task def load_and_configure_sdoc super - options << '-g' # link to GitHub, SDoc flag + options << "-g" # link to GitHub, SDoc flag end def component_root_dir(component) @@ -144,7 +157,7 @@ module Rails end def api_dir - 'doc/rdoc' + "doc/rdoc" end end @@ -156,7 +169,7 @@ module Rails class StableTask < RepoTask def rails_version - File.read('RAILS_VERSION').strip + File.read("RAILS_VERSION").strip end end end diff --git a/railties/lib/rails/app_loader.rb b/railties/lib/rails/app_loader.rb index a9fe21824e..525d5f0161 100644 --- a/railties/lib/rails/app_loader.rb +++ b/railties/lib/rails/app_loader.rb @@ -1,12 +1,12 @@ -require 'pathname' -require 'rails/version' +require "pathname" +require "rails/version" module Rails module AppLoader # :nodoc: extend self RUBY = Gem.ruby - EXECUTABLES = ['bin/rails', 'script/rails'] + EXECUTABLES = ["bin/rails", "script/rails"] BUNDLER_WARNING = <<EOS Looks like your app's ./bin/rails is a stub that was generated by Bundler. @@ -16,7 +16,7 @@ like any other source code, rather than stubs that are generated on demand. Here's how to upgrade: bundle config --delete bin # Turn off Bundler's stub generator - rake rails:update:bin # Use the new Rails 4 executables + rails app:update:bin # Use the new Rails 5 executables git add bin # Add bin/ to source control You may need to remove bin/ from your .gitignore as well. @@ -39,21 +39,21 @@ EOS if contents =~ /(APP|ENGINE)_PATH/ exec RUBY, exe, *ARGV break # non reachable, hack to be able to stub exec in the test suite - elsif exe.end_with?('bin/rails') && contents.include?('This file was generated by Bundler') + elsif exe.end_with?("bin/rails") && contents.include?("This file was generated by Bundler") $stderr.puts(BUNDLER_WARNING) - Object.const_set(:APP_PATH, File.expand_path('config/application', Dir.pwd)) - require File.expand_path('../boot', APP_PATH) - require 'rails/commands' + Object.const_set(:APP_PATH, File.expand_path("config/application", Dir.pwd)) + require File.expand_path("../boot", APP_PATH) + require "rails/commands" break end end # If we exhaust the search there is no executable, this could be a # call to generate a new application, so restore the original cwd. - Dir.chdir(original_cwd) and return if Pathname.new(Dir.pwd).root? + Dir.chdir(original_cwd) && return if Pathname.new(Dir.pwd).root? # Otherwise keep moving upwards in search of an executable. - Dir.chdir('..') + Dir.chdir("..") end end diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index bff33ff42e..b01196e3ed 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -1,10 +1,9 @@ -require 'fileutils' -require 'yaml' -require 'active_support/core_ext/hash/keys' -require 'active_support/core_ext/object/blank' -require 'active_support/key_generator' -require 'active_support/message_verifier' -require 'rails/engine' +require "yaml" +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/object/blank" +require "active_support/key_generator" +require "active_support/message_verifier" +require "rails/engine" module Rails # An Engine with the responsibility of coordinating the whole boot process. @@ -74,21 +73,21 @@ module Rails # the configuration. # # If you decide to define rake tasks, runners, or initializers in an - # application other than +Rails.application+, then you must run those - # these manually. + # application other than +Rails.application+, then you must run them manually. class Application < Engine - autoload :Bootstrap, 'rails/application/bootstrap' - autoload :Configuration, 'rails/application/configuration' - autoload :DefaultMiddlewareStack, 'rails/application/default_middleware_stack' - autoload :Finisher, 'rails/application/finisher' - autoload :Railties, 'rails/engine/railties' - autoload :RoutesReloader, 'rails/application/routes_reloader' + autoload :Bootstrap, "rails/application/bootstrap" + autoload :Configuration, "rails/application/configuration" + autoload :DefaultMiddlewareStack, "rails/application/default_middleware_stack" + autoload :Finisher, "rails/application/finisher" + autoload :Railties, "rails/engine/railties" + autoload :RoutesReloader, "rails/application/routes_reloader" class << self def inherited(base) super Rails.app_class = base add_lib_to_load_path!(find_root(base.called_from)) + ActiveSupport.run_load_hooks(:before_configuration, base) end def instance @@ -113,7 +112,7 @@ module Rails attr_accessor :assets, :sandbox alias_method :sandbox?, :sandbox - attr_reader :reloaders + attr_reader :reloaders, :reloader, :executor delegate :default_url_options, :default_url_options=, to: :routes @@ -131,6 +130,10 @@ module Rails @message_verifiers = {} @ran_load_hooks = false + @executor = Class.new(ActiveSupport::Executor) + @reloader = Class.new(ActiveSupport::Reloader) + @reloader.executor = @executor + # are these actually used? @initial_variable_values = initial_variable_values @block = block @@ -144,7 +147,6 @@ module Rails def run_load_hooks! # :nodoc: return self if @ran_load_hooks @ran_load_hooks = true - ActiveSupport.run_load_hooks(:before_configuration, self) @initial_variable_values.each do |variable_name, value| if INITIAL_VARIABLES.include?(variable_name) @@ -243,7 +245,7 @@ module Rails @app_env_config ||= begin validate_secret_key_config! - super.merge({ + super.merge( "action_dispatch.parameter_filter" => config.filter_parameters, "action_dispatch.redirect_filter" => config.filter_redirect, "action_dispatch.secret_token" => secrets.secret_token, @@ -259,7 +261,7 @@ module Rails "action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt, "action_dispatch.cookies_serializer" => config.action_dispatch.cookies_serializer, "action_dispatch.cookies_digest" => config.action_dispatch.cookies_digest - }) + ) end end @@ -315,7 +317,7 @@ module Rails # Rails application, you will need to add lib to $LOAD_PATH on your own in case # you need to load files in lib/ during the application configuration as well. def self.add_lib_to_load_path!(root) #:nodoc: - path = File.join root, 'lib' + path = File.join root, "lib" if File.exist?(path) && !$LOAD_PATH.include?(path) $LOAD_PATH.unshift(path) end @@ -383,11 +385,16 @@ module Rails def secrets @secrets ||= begin secrets = ActiveSupport::OrderedOptions.new - yaml = config.paths["config/secrets"].first + yaml = config.paths["config/secrets"].first + if File.exist?(yaml) require "erb" - all_secrets = YAML.load(ERB.new(IO.read(yaml)).result) || {} - env_secrets = all_secrets[Rails.env] + + all_secrets = YAML.load(ERB.new(IO.read(yaml)).result) || {} + shared_secrets = all_secrets["shared"] + env_secrets = all_secrets[Rails.env] + + secrets.merge!(shared_secrets.symbolize_keys) if shared_secrets secrets.merge!(env_secrets.symbolize_keys) if env_secrets end @@ -515,15 +522,15 @@ module Rails private - def build_request(env) - req = super - env["ORIGINAL_FULLPATH"] = req.fullpath - env["ORIGINAL_SCRIPT_NAME"] = req.script_name - req - end + def build_request(env) + req = super + env["ORIGINAL_FULLPATH"] = req.fullpath + env["ORIGINAL_SCRIPT_NAME"] = req.script_name + req + end - def build_middleware - config.app_middleware + super - end + def build_middleware + config.app_middleware + super + end end end diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb index 9baf8aa742..11da271501 100644 --- a/railties/lib/rails/application/bootstrap.rb +++ b/railties/lib/rails/application/bootstrap.rb @@ -1,3 +1,4 @@ +require "fileutils" require "active_support/notifications" require "active_support/dependencies" require "active_support/descendants_tracker" @@ -35,7 +36,7 @@ INFO FileUtils.mkdir_p File.dirname path end - f = File.open path, 'a' + f = File.open path, "a" f.binmode f.sync = config.autoflush_log # if true make sure every write flushes diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 65cff1561a..810750ed35 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -1,10 +1,10 @@ -require 'active_support/core_ext/kernel/reporting' -require 'active_support/file_update_checker' -require 'rails/engine/configuration' -require 'rails/source_annotation_extractor' +require "active_support/core_ext/kernel/reporting" +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 +require "active_support/deprecation" +require "active_support/core_ext/string/strip" # for strip_heredoc module Rails class Application @@ -16,7 +16,7 @@ module Rails :railties_order, :relative_url_root, :secret_key_base, :secret_token, :ssl_options, :public_file_server, :session_options, :time_zone, :reload_classes_only_on_change, - :beginning_of_week, :filter_redirect, :x + :beginning_of_week, :filter_redirect, :x, :enable_dependency_loading attr_writer :log_level attr_reader :encoding, :api_only, :static_cache_control @@ -34,8 +34,7 @@ module Rails @public_file_server.index_name = "index" @force_ssl = false @ssl_options = {} - @session_store = :cookie_store - @session_options = {} + @session_store = nil @time_zone = "UTC" @beginning_of_week = :monday @log_level = nil @@ -54,11 +53,12 @@ module Rails @api_only = false @debug_exception_response_format = nil @x = Custom.new + @enable_dependency_loading = false end def static_cache_control=(value) ActiveSupport::Deprecation.warn <<-eow.strip_heredoc - `static_cache_control` is deprecated and will be removed in Rails 5.1. + `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. @@ -69,8 +69,8 @@ module Rails def serve_static_files ActiveSupport::Deprecation.warn <<-eow.strip_heredoc - `serve_static_files` is deprecated and will be removed in Rails 5.1. - Please use `public_file_server.enabled` instead. + `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 @@ -78,8 +78,8 @@ module Rails def serve_static_files=(value) ActiveSupport::Deprecation.warn <<-eow.strip_heredoc - `serve_static_files` is deprecated and will be removed in Rails 5.1. - Please use `public_file_server.enabled = #{value}` instead. + `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 @@ -134,7 +134,7 @@ module Rails require "yaml" require "erb" YAML.load(ERB.new(yaml.read).result) || {} - elsif ENV['DATABASE_URL'] + elsif ENV["DATABASE_URL"] # Value from ENV['DATABASE_URL'] is set to default database connection # by Active Record. {} @@ -164,29 +164,37 @@ module Rails self.generators.colorize_logging = val end - def session_store(*args) - if args.empty? - case @session_store - when :disabled - nil - when :active_record_store + def session_store(new_session_store = nil, **options) + if new_session_store + if new_session_store == :active_record_store begin ActionDispatch::Session::ActiveRecordStore rescue NameError raise "`ActiveRecord::SessionStore` is extracted out of Rails into a gem. " \ "Please add `activerecord-session_store` to your Gemfile to use it." end + end + + @session_store = new_session_store + @session_options = options || {} + else + case @session_store + when :disabled + nil + when :active_record_store + ActionDispatch::Session::ActiveRecordStore when Symbol ActionDispatch::Session.const_get(@session_store.to_s.camelize) else @session_store end - else - @session_store = args.shift - @session_options = args.shift || {} end end + def session_store? #:nodoc: + @session_store + end + def annotations SourceAnnotationExtractor::Annotation end @@ -205,6 +213,10 @@ module Rails } end end + + def respond_to_missing?(symbol, *) + true + end end end end diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb index 4f1cc0703d..14c0a8cbe4 100644 --- a/railties/lib/rails/application/default_middleware_stack.rb +++ b/railties/lib/rails/application/default_middleware_stack.rb @@ -19,7 +19,7 @@ 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 + 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 @@ -34,22 +34,10 @@ module Rails # handling: presumably their code is not threadsafe middleware.use ::Rack::Lock - - elsif config.allow_concurrency == :unsafe - # Do nothing, even if we know this is dangerous. This is the - # historical behaviour for true. - - else - # Default concurrency setting: enabled, but safe - - unless config.cache_classes && config.eager_load - # Without cache_classes + eager_load, the load interlock - # is required for proper operation - - middleware.use ::ActionDispatch::LoadInterlock - end end + middleware.use ::ActionDispatch::Executor, app.executor + middleware.use ::Rack::Runtime middleware.use ::Rack::MethodOverride unless config.api_only middleware.use ::ActionDispatch::RequestId @@ -61,7 +49,7 @@ module Rails middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies unless config.cache_classes - middleware.use ::ActionDispatch::Reloader, lambda { reload_dependencies? } + middleware.use ::ActionDispatch::Reloader, app.reloader end middleware.use ::ActionDispatch::Callbacks @@ -83,18 +71,14 @@ module Rails private - def reload_dependencies? - config.reload_classes_only_on_change != true || app.reloaders.map(&:updated?).any? - end - def load_rack_cache rack_cache = config.action_dispatch.rack_cache return unless rack_cache begin - require 'rack/cache' + require "rack/cache" rescue LoadError => error - error.message << ' Be sure to add rack-cache to your Gemfile' + error.message << " Be sure to add rack-cache to your Gemfile" raise end diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index 411cdbad19..a855e8fab0 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -21,15 +21,26 @@ module Rails initializer :add_builtin_route do |app| if Rails.env.development? + app.routes.prepend do + get "/rails/info/properties" => "rails/info#properties", internal: true + get "/rails/info/routes" => "rails/info#routes", internal: true + get "/rails/info" => "rails/info#index", internal: true + end + app.routes.append do - get '/rails/info/properties' => "rails/info#properties" - get '/rails/info/routes' => "rails/info#routes" - get '/rails/info' => "rails/info#index" - get '/' => "rails/welcome#index" + get "/" => "rails/welcome#index", internal: true end end end + # Setup default session store if not already set in config/application.rb + initializer :setup_default_session_store, before: :build_middleware_stack do |app| + unless app.config.session_store? + app_name = app.class.name ? app.railtie_name.chomp("_application") : "" + app.config.session_store :cookie_store, key: "_#{app_name}_session" + end + end + initializer :build_middleware_stack do build_middleware_stack end @@ -38,16 +49,16 @@ module Rails app.routes.define_mounted_helper(:main_app) end - initializer :add_to_prepare_blocks do + initializer :add_to_prepare_blocks do |app| config.to_prepare_blocks.each do |block| - ActionDispatch::Reloader.to_prepare(&block) + app.reloader.to_prepare(&block) end end # This needs to happen before eager load so it happens # in exactly the same point regardless of config.cache_classes - initializer :run_prepare_callbacks do - ActionDispatch::Reloader.prepare! + initializer :run_prepare_callbacks do |app| + app.reloader.prepare! end initializer :eager_load! do @@ -62,13 +73,60 @@ module Rails ActiveSupport.run_load_hooks(:after_initialize, self) end + class MutexHook + def initialize(mutex = Mutex.new) + @mutex = mutex + end + + def run + @mutex.lock + end + + def complete(_state) + @mutex.unlock + end + end + + module InterlockHook + def self.run + ActiveSupport::Dependencies.interlock.start_running + end + + def self.complete(_state) + ActiveSupport::Dependencies.interlock.done_running + end + end + + initializer :configure_executor_for_concurrency do |app| + if config.allow_concurrency == false + # User has explicitly opted out of concurrent request + # handling: presumably their code is not threadsafe + + app.executor.register_hook(MutexHook.new, outer: true) + + elsif config.allow_concurrency == :unsafe + # Do nothing, even if we know this is dangerous. This is the + # historical behavior for true. + + else + # Default concurrency setting: enabled, but safe + + unless config.cache_classes && config.eager_load + # Without cache_classes + eager_load, the load interlock + # is required for proper operation + + app.executor.register_hook(InterlockHook, outer: true) + end + end + end + # Set routes reload after the finisher hook to ensure routes added in # the hook are taken into account. - initializer :set_routes_reloader_hook do + initializer :set_routes_reloader_hook do |app| reloader = routes_reloader reloader.execute_if_updated - self.reloaders << reloader - ActionDispatch::Reloader.to_prepare do + reloaders << reloader + app.reloader.to_run do # We configure #execute rather than #execute_if_updated because if # autoloaded constants are cleared we need to reload routes also in # case any was used there, as in @@ -78,42 +136,55 @@ module Rails # This means routes are also reloaded if i18n is updated, which # might not be necessary, but in order to be more precise we need # some sort of reloaders dependency support, to be added. + require_unload_lock! reloader.execute end end # Set clearing dependencies after the finisher hook to ensure paths # added in the hook are taken into account. - initializer :set_clear_dependencies_hook, group: :all do + initializer :set_clear_dependencies_hook, group: :all do |app| callback = lambda do - ActiveSupport::Dependencies.interlock.unloading do - ActiveSupport::DescendantsTracker.clear - ActiveSupport::Dependencies.clear + ActiveSupport::DescendantsTracker.clear + ActiveSupport::Dependencies.clear + end + + if config.cache_classes + app.reloader.check = lambda { false } + elsif config.reload_classes_only_on_change + app.reloader.check = lambda do + app.reloaders.map(&:updated?).any? end + else + app.reloader.check = lambda { true } end if config.reload_classes_only_on_change reloader = config.file_watcher.new(*watchable_args, &callback) - self.reloaders << reloader + reloaders << reloader # Prepend this callback to have autoloaded constants cleared before # any other possible reloading, in case they need to autoload fresh # constants. - ActionDispatch::Reloader.to_prepare(prepend: true) do + app.reloader.to_run(prepend: true) do # In addition to changes detected by the file watcher, if routes # or i18n have been updated we also need to clear constants, # that's why we run #execute rather than #execute_if_updated, this # callback has to clear autoloaded constants after any update. - reloader.execute + class_unload! do + reloader.execute + end end else - ActionDispatch::Reloader.to_cleanup(&callback) + app.reloader.to_complete do + class_unload!(&callback) + end end end # Disable dependency loading during request cycle initializer :disable_dependency_loading do - if config.eager_load && config.cache_classes + if config.eager_load && config.cache_classes && !config.enable_dependency_loading ActiveSupport::Dependencies.unhook! end end diff --git a/railties/lib/rails/application_controller.rb b/railties/lib/rails/application_controller.rb index 618a09a5b3..f8394b8e0a 100644 --- a/railties/lib/rails/application_controller.rb +++ b/railties/lib/rails/application_controller.rb @@ -1,16 +1,16 @@ class Rails::ApplicationController < ActionController::Base # :nodoc: - self.view_paths = File.expand_path('../templates', __FILE__) - layout 'application' + self.view_paths = File.expand_path("../templates", __FILE__) + layout "application" protected - def require_local! - unless local_request? - render html: '<p>For security purposes, this information is only available to local requests.</p>'.html_safe, status: :forbidden + def require_local! + unless local_request? + render html: "<p>For security purposes, this information is only available to local requests.</p>".html_safe, status: :forbidden + end end - end - def local_request? - Rails.application.config.consider_all_requests_local || request.local? - end + def local_request? + Rails.application.config.consider_all_requests_local || request.local? + end end diff --git a/railties/lib/rails/backtrace_cleaner.rb b/railties/lib/rails/backtrace_cleaner.rb index 5276eb33c9..5c833e12ba 100644 --- a/railties/lib/rails/backtrace_cleaner.rb +++ b/railties/lib/rails/backtrace_cleaner.rb @@ -1,12 +1,12 @@ -require 'active_support/backtrace_cleaner' +require "active_support/backtrace_cleaner" module Rails class BacktraceCleaner < ActiveSupport::BacktraceCleaner - APP_DIRS_PATTERN = /^\/?(app|config|lib|test)/ + APP_DIRS_PATTERN = /^\/?(app|config|lib|test|\(\w*\))/ RENDER_TEMPLATE_PATTERN = /:in `_render_template_\w*'/ - EMPTY_STRING = ''.freeze - SLASH = '/'.freeze - DOT_SLASH = './'.freeze + EMPTY_STRING = "".freeze + SLASH = "/".freeze + DOT_SLASH = "./".freeze def initialize super diff --git a/railties/lib/rails/cli.rb b/railties/lib/rails/cli.rb index a8794bc0de..973b746068 100644 --- a/railties/lib/rails/cli.rb +++ b/railties/lib/rails/cli.rb @@ -1,15 +1,17 @@ -require 'rails/app_loader' +require "rails/app_loader" # If we are inside a Rails application this method performs an exec and thus # the rest of this script is not run. Rails::AppLoader.exec_app -require 'rails/ruby_version_check' +require "rails/ruby_version_check" Signal.trap("INT") { puts; exit(1) } -if ARGV.first == 'plugin' +require "rails/command" + +if ARGV.first == "plugin" ARGV.shift - require 'rails/commands/plugin' + Rails::Command.invoke :plugin, ARGV else - require 'rails/commands/application' + Rails::Command.invoke :application, ARGV end diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb index fc8717c752..b3d88147a5 100644 --- a/railties/lib/rails/code_statistics.rb +++ b/railties/lib/rails/code_statistics.rb @@ -1,15 +1,15 @@ -require 'rails/code_statistics_calculator' +require "rails/code_statistics_calculator" +require "active_support/core_ext/enumerable" class CodeStatistics #:nodoc: + TEST_TYPES = ["Controller tests", + "Helper tests", + "Model tests", + "Mailer tests", + "Job tests", + "Integration tests"] - TEST_TYPES = ['Controller tests', - 'Helper tests', - 'Model tests', - 'Mailer tests', - 'Job tests', - 'Integration tests'] - - HEADERS = {lines: ' Lines', code_lines: ' LOC', classes: 'Classes', methods: 'Methods'} + HEADERS = { lines: " Lines", code_lines: " LOC", classes: "Classes", methods: "Methods" } def initialize(*pairs) @pairs = pairs @@ -32,7 +32,7 @@ class CodeStatistics #:nodoc: private def calculate_statistics - Hash[@pairs.map{|pair| [pair.first, calculate_directory_statistics(pair.last)]}] + Hash[@pairs.map { |pair| [pair.first, calculate_directory_statistics(pair.last)] }] end def calculate_directory_statistics(directory, pattern = /^(?!\.).*?\.(rb|js|coffee|rake)$/) @@ -70,25 +70,25 @@ class CodeStatistics #:nodoc: end def width_for(label) - [@statistics.values.sum {|s| s.send(label) }.to_s.size, HEADERS[label].length].max + [@statistics.values.sum { |s| s.send(label) }.to_s.size, HEADERS[label].length].max end def print_header print_splitter - print '| Name ' + print "| Name " HEADERS.each do |k, v| print " | #{v.rjust(width_for(k))}" end - puts ' | M/C | LOC/M |' + puts " | M/C | LOC/M |" print_splitter end def print_splitter - print '+----------------------' + print "+----------------------" HEADERS.each_key do |k| print "+#{'-' * (width_for(k) + 2)}" end - puts '+-----+-------+' + puts "+-----+-------+" end def print_line(name, statistics) diff --git a/railties/lib/rails/code_statistics_calculator.rb b/railties/lib/rails/code_statistics_calculator.rb index fad13e8517..d0194af197 100644 --- a/railties/lib/rails/code_statistics_calculator.rb +++ b/railties/lib/rails/code_statistics_calculator.rb @@ -43,7 +43,7 @@ class CodeStatisticsCalculator #:nodoc: def add_by_file_path(file_path) File.open(file_path) do |f| - self.add_by_io(f, file_type(file_path)) + add_by_io(f, file_type(file_path)) end end @@ -77,10 +77,10 @@ class CodeStatisticsCalculator #:nodoc: private def file_type(file_path) - if file_path.end_with? '_test.rb' + if file_path.end_with? "_test.rb" :minitest else - File.extname(file_path).sub(/\A\./, '').downcase.to_sym + File.extname(file_path).sub(/\A\./, "").downcase.to_sym end end end diff --git a/railties/lib/rails/command.rb b/railties/lib/rails/command.rb index f7753cbb83..6065e78fd1 100644 --- a/railties/lib/rails/command.rb +++ b/railties/lib/rails/command.rb @@ -1,70 +1,99 @@ -require 'rails/commands/commands_tasks' +require "active_support" +require "active_support/dependencies/autoload" +require "active_support/core_ext/enumerable" +require "active_support/core_ext/object/blank" +require "active_support/core_ext/hash/transform_values" + +require "thor" module Rails - class Command #:nodoc: - attr_reader :argv + module Command + extend ActiveSupport::Autoload - def initialize(argv = []) - @argv = argv + autoload :Behavior + autoload :Base - @option_parser = build_option_parser - @options = {} - end + include Behavior - def self.run(task_name, argv) - command_name = command_name_for(task_name) + class << self + def hidden_commands # :nodoc: + @hidden_commands ||= [] + end - if command = command_for(command_name) - command.new(argv).run(command_name) - else - Rails::CommandsTasks.new(argv).run_command!(task_name) + def environment # :nodoc: + ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development" end - end - def run(command_name) - parse_options_for(command_name) - @option_parser.parse! @argv + # 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 = "version" if %w( -v --version ).include? namespace - public_send(command_name) - end - - def self.options_for(command_name, &options_to_parse) - @@command_options[command_name] = options_to_parse - end + if command = find_by_namespace(namespace) + command.perform(namespace, args, config) + else + find_by_namespace("rake").perform(namespace, args, config) + end + end - def self.set_banner(command_name, banner) - options_for(command_name) { |opts, _| opts.banner = banner } - end + # 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. + # + # find_by_namespace :webrat, :rails, :integration + # + # Will search for the following commands: + # + # "rails:webrat", "webrat:integration", "webrat" + # + # Notice that "rails:commands:webrat" could be loaded as well, what + # Rails looks for is the first and last parts of the namespace. + def find_by_namespace(name) # :nodoc: + lookups = [ name, "rails:#{name}" ] - private - @@commands = [] - @@command_options = {} + lookup(lookups) - def parse_options_for(command_name) - @@command_options.fetch(command_name, proc {}).call(@option_parser, @options) + namespaces = subclasses.index_by(&:namespace) + namespaces[(lookups & namespaces.keys).first] end - def build_option_parser - OptionParser.new do |opts| - opts.on('-h', '--help', 'Show this help.') do - puts opts - exit - end + # Returns the root of the Rails engine or app running the command. + def root + if defined?(ENGINE_ROOT) + Pathname.new(ENGINE_ROOT) + elsif defined?(APP_PATH) + Pathname.new(File.expand_path("../..", APP_PATH)) end end - def self.inherited(command) - @@commands << command + def print_commands # :nodoc: + sorted_groups.each { |b, n| print_list(b, n) } end - def self.command_name_for(task_name) - task_name.gsub(':', '_').to_sym + def sorted_groups # :nodoc: + lookup! + + groups = (subclasses - hidden_commands).group_by { |c| c.namespace.split(":").first } + groups.transform_values! { |commands| commands.flat_map(&:printing_commands).sort } + + rails = groups.delete("rails") + [[ "rails", rails ]] + groups.sort.to_a end - def self.command_for(command_name) - @@commands.find do |command| - command.public_instance_methods.include?(command_name) + protected + def command_type + @command_type ||= "command" end - end + + def lookup_paths + @lookup_paths ||= %w( rails/commands commands ) + end + + def file_lookup_paths + @file_lookup_paths ||= [ "{#{lookup_paths.join(',')}}", "**", "*_command.rb" ] + end + end end end diff --git a/railties/lib/rails/command/actions.rb b/railties/lib/rails/command/actions.rb new file mode 100644 index 0000000000..31b656ec31 --- /dev/null +++ b/railties/lib/rails/command/actions.rb @@ -0,0 +1,42 @@ +module Rails + module Command + module Actions + # Change to the application's path if there is no config.ru file in current directory. + # This allows us to run `rails server` from other directories, but still get + # the main config.ru and properly set the tmp directory. + def set_application_directory! + Dir.chdir(File.expand_path("../../", APP_PATH)) unless File.exist?(File.expand_path("config.ru")) + end + + if defined?(ENGINE_PATH) + def require_application_and_environment! + require ENGINE_PATH + end + + def load_tasks + Rake.application.init("rails") + Rake.application.load_rakefile + end + + def load_generators + engine = ::Rails::Engine.find(ENGINE_ROOT) + Rails::Generators.namespace = engine.railtie_namespace + engine.load_generators + end + else + def require_application_and_environment! + require APP_PATH + Rails.application.require_environment! + end + + def load_tasks + Rails.application.load_tasks + end + + def load_generators + Rails.application.load_generators + end + end + end + end +end diff --git a/railties/lib/rails/command/base.rb b/railties/lib/rails/command/base.rb new file mode 100644 index 0000000000..1efcd69e63 --- /dev/null +++ b/railties/lib/rails/command/base.rb @@ -0,0 +1,135 @@ +require "thor" +require "erb" + +require "active_support/core_ext/string/filters" +require "active_support/core_ext/string/inflections" + +require "rails/command/actions" + +module Rails + module Command + class Base < Thor + class Error < Thor::Error # :nodoc: + end + + include Actions + + class << self + # Returns true when the app is a Rails engine. + def engine? + defined?(ENGINE_ROOT) + end + + # Tries to get the description from a USAGE file one folder above the command + # root. + def desc(usage = nil, description = nil) + if usage + super + else + @desc ||= ERB.new(File.read(usage_path)).result(binding) if usage_path + end + end + + # Convenience method to get the namespace from the class name. It's the + # same as Thor default except that the Command at the end of the class + # is removed. + def namespace(name = nil) + if name + super + else + @namespace ||= super.chomp("_command").sub(/:command:/, ":") + end + end + + # Convenience method to hide this command from the available ones when + # running rails command. + def hide_command! + Rails::Command.hidden_commands << self + end + + def inherited(base) #:nodoc: + super + + if base.name && base.name !~ /Base$/ + Rails::Command.subclasses << base + end + end + + def perform(command, args, config) # :nodoc: + command = nil if Thor::HELP_MAPPINGS.include?(args.first) + + dispatch(command, args.dup, nil, config) + end + + def printing_commands + namespace.sub(/^rails:/, "") + end + + def executable + "bin/rails #{command_name}" + end + + # Use Rails' default banner. + def banner(*) + "#{executable} #{arguments.map(&:usage).join(' ')} [options]".squish! + end + + # Sets the base_name taking into account the current class namespace. + # + # Rails::Command::TestCommand.base_name # => 'rails' + def base_name + @base_name ||= begin + if base = name.to_s.split("::").first + base.underscore + end + end + end + + # Return command name without namespaces. + # + # Rails::Command::TestCommand.command_name # => 'test' + def command_name + @command_name ||= begin + if command = name.to_s.split("::").last + command.chomp!("Command") + command.underscore + end + end + end + + # Path to lookup a USAGE description in a file. + def usage_path + if default_command_root + path = File.join(default_command_root, "USAGE") + path if File.exist?(path) + end + end + + # Default file root to place extra files a command might need, placed + # one folder above the command file. + # + # For a `Rails::Command::TestCommand` placed in `rails/command/test_command.rb` + # would return `rails/test`. + def default_command_root + path = File.expand_path(File.join("../commands", command_name), __dir__) + path if File.exist?(path) + end + + private + # Allow the command method to be called perform. + def create_command(meth) + if meth == "perform" + alias_method command_name, meth + else + # Prevent exception about command without usage. + # Some commands define their documentation differently. + @usage ||= "" + @desc ||= "" + + super + end + end + end + end + end +end diff --git a/railties/lib/rails/command/behavior.rb b/railties/lib/rails/command/behavior.rb new file mode 100644 index 0000000000..ce994746a4 --- /dev/null +++ b/railties/lib/rails/command/behavior.rb @@ -0,0 +1,123 @@ +require "active_support" + +module Rails + module Command + module Behavior #:nodoc: + extend ActiveSupport::Concern + + class_methods do + # Remove the color from output. + def no_color! + Thor::Base.shell = Thor::Shell::Basic + end + + # Track all command subclasses. + def subclasses + @subclasses ||= [] + end + + protected + + # 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) + s = str1 + t = str2 + n = s.length + m = t.length + + return m if (0 == n) + return n if (0 == m) + + d = (0..m).to_a + x = nil + + # avoid duplicating an enumerable object in the loop + str2_codepoint_enumerable = str2.each_codepoint + + str1.each_codepoint.with_index do |char1, i| + e = i+1 + + str2_codepoint_enumerable.with_index do |char2, j| + cost = (char1 == char2) ? 0 : 1 + x = [ + d[j+1] + 1, # insertion + e + 1, # deletion + d[j] + cost # substitution + ].min + d[j] = e + e = x + end + + d[m] = x + end + + x + end + + # Prints a list of generators. + def print_list(base, namespaces) #:nodoc: + return if namespaces.empty? + puts "#{base.camelize}:" + + namespaces.each do |namespace| + puts(" #{namespace}") + end + + puts + end + + # Receives namespaces in an array and tries to find matching generators + # in the load path. + def lookup(namespaces) #:nodoc: + paths = namespaces_to_paths(namespaces) + + paths.each do |raw_path| + lookup_paths.each do |base| + path = "#{base}/#{raw_path}_#{command_type}" + + begin + require path + return + rescue LoadError => e + raise unless e.message =~ /#{Regexp.escape(path)}$/ + rescue Exception => e + warn "[WARNING] Could not load #{command_type} #{path.inspect}. Error: #{e.message}.\n#{e.backtrace.join("\n")}" + end + end + end + end + + # This will try to load any command in the load path to show in help. + def lookup! #:nodoc: + $LOAD_PATH.each do |base| + Dir[File.join(base, *file_lookup_paths)].each do |path| + begin + path = path.sub("#{base}/", "") + require path + rescue Exception + # No problem + end + end + end + end + + # 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: + paths = [] + namespaces.each do |namespace| + pieces = namespace.split(":") + paths << pieces.dup.push(pieces.last).join("/") + paths << pieces.join("/") + end + paths.uniq! + paths + end + end + end + end +end diff --git a/railties/lib/rails/command/environment_argument.rb b/railties/lib/rails/command/environment_argument.rb new file mode 100644 index 0000000000..05eac34155 --- /dev/null +++ b/railties/lib/rails/command/environment_argument.rb @@ -0,0 +1,34 @@ +require "active_support" + +module Rails + module Command + module EnvironmentArgument #:nodoc: + extend ActiveSupport::Concern + + included do + argument :environment, optional: true, banner: "environment" + end + + private + def extract_environment_option_from_argument + if environment + self.options = options.merge(environment: acceptable_environment(environment)) + elsif !options[:environment] + self.options = options.merge(environment: Rails::Command.environment) + end + end + + def acceptable_environment(env = nil) + if available_environments.include? env + env + else + %w( production development test ).detect { |e| e =~ /^#{env}/ } || env + end + end + + def available_environments + Dir["config/environments/*.rb"].map { |fname| File.basename(fname, ".*") } + end + end + end +end diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb index fa47c52b96..fff0119c65 100644 --- a/railties/lib/rails/commands.rb +++ b/railties/lib/rails/commands.rb @@ -1,4 +1,4 @@ -ARGV << '--help' if ARGV.empty? +require "rails/command" aliases = { "g" => "generate", @@ -13,6 +13,4 @@ aliases = { command = ARGV.shift command = aliases[command] || command -require 'rails/command' - -Rails::Command.run(command, ARGV) +Rails::Command.invoke command, ARGV diff --git a/railties/lib/rails/commands/application.rb b/railties/lib/rails/commands/application.rb deleted file mode 100644 index c998e6b6a8..0000000000 --- a/railties/lib/rails/commands/application.rb +++ /dev/null @@ -1,17 +0,0 @@ -require 'rails/generators' -require 'rails/generators/rails/app/app_generator' - -module Rails - module Generators - class AppGenerator # :nodoc: - # We want to exit on failure to be kind to other libraries - # This is only when accessing via CLI - def self.exit_on_failure? - true - end - end - end -end - -args = Rails::Generators::ARGVScrubber.new(ARGV).prepare! -Rails::Generators::AppGenerator.start args diff --git a/railties/lib/rails/commands/application/application_command.rb b/railties/lib/rails/commands/application/application_command.rb new file mode 100644 index 0000000000..7675d3b3d1 --- /dev/null +++ b/railties/lib/rails/commands/application/application_command.rb @@ -0,0 +1,29 @@ +require "rails/generators" +require "rails/generators/rails/app/app_generator" + +module Rails + module Generators + class AppGenerator # :nodoc: + # We want to exit on failure to be kind to other libraries + # This is only when accessing via CLI + def self.exit_on_failure? + true + end + end + end + + module Command + class ApplicationCommand < Base # :nodoc: + hide_command! + + def help + perform # Punt help output to the generator. + end + + def perform(*args) + Rails::Generators::AppGenerator.start \ + Rails::Generators::ARGVScrubber.new(args).prepare! + end + end + end +end diff --git a/railties/lib/rails/commands/commands_tasks.rb b/railties/lib/rails/commands/commands_tasks.rb deleted file mode 100644 index da3b9452d5..0000000000 --- a/railties/lib/rails/commands/commands_tasks.rb +++ /dev/null @@ -1,180 +0,0 @@ -require 'rails/commands/rake_proxy' - -module Rails - # This is a class which takes in a rails command and initiates the appropriate - # initiation sequence. - # - # Warning: This class mutates ARGV because some commands require manipulating - # it before they are run. - class CommandsTasks # :nodoc: - include Rails::RakeProxy - - attr_reader :argv - - HELP_MESSAGE = <<-EOT -Usage: rails COMMAND [ARGS] - -The most common rails commands are: - generate Generate new code (short-cut alias: "g") - console Start the Rails console (short-cut alias: "c") - server Start the Rails server (short-cut alias: "s") - test Run tests (short-cut alias: "t") - dbconsole Start a console for the database specified in config/database.yml - (short-cut alias: "db") - new Create a new Rails application. "rails new my_app" creates a - new application called MyApp in "./my_app" - -All commands can be run with -h (or --help) for more information. - -In addition to those commands, there are: -EOT - - ADDITIONAL_COMMANDS = [ - [ 'destroy', 'Undo code generated with "generate" (short-cut alias: "d")' ], - [ 'plugin new', 'Generates skeleton for developing a Rails plugin' ], - [ 'runner', - 'Run a piece of code in the application environment (short-cut alias: "r")' ] - ] - - COMMAND_WHITELIST = %w(plugin generate destroy console server dbconsole runner new version help test) - - def initialize(argv) - @argv = argv - end - - def run_command!(command) - command = parse_command(command) - - if COMMAND_WHITELIST.include?(command) - send(command) - else - run_rake_task(command) - end - end - - def plugin - require_command!("plugin") - end - - def generate - generate_or_destroy(:generate) - end - - def destroy - generate_or_destroy(:destroy) - end - - def console - require_command!("console") - options = Rails::Console.parse_arguments(argv) - - # RAILS_ENV needs to be set before config/application is required - ENV['RAILS_ENV'] = options[:environment] if options[:environment] - - # shift ARGV so IRB doesn't freak - shift_argv! - - require_application_and_environment! - Rails::Console.start(Rails.application, options) - 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 - end - end - - def test - require_command!("test") - end - - def dbconsole - require_command!("dbconsole") - Rails::DBConsole.start - end - - def runner - require_command!("runner") - end - - def new - if %w(-h --help).include?(argv.first) - require_command!("application") - else - exit_with_initialization_warning! - end - end - - def version - argv.unshift '--version' - require_command!("application") - end - - def help - write_help_message - write_commands ADDITIONAL_COMMANDS + formatted_rake_tasks - end - - private - - def exit_with_initialization_warning! - puts "Can't initialize a new Rails application within the directory of another, please change to a non-Rails directory first.\n" - puts "Type 'rails' for help." - exit(1) - end - - def shift_argv! - argv.shift if argv.first && argv.first[0] != '-' - end - - def require_command!(command) - require "rails/commands/#{command}" - end - - def generate_or_destroy(command) - require 'rails/generators' - require_application_and_environment! - Rails.application.load_generators - require_command!(command) - end - - # Change to the application's path if there is no config.ru file in current directory. - # This allows us to run `rails server` from other directories, but still get - # the main config.ru and properly set the tmp directory. - def set_application_directory! - Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exist?(File.expand_path("config.ru")) - end - - def require_application_and_environment! - require APP_PATH - Rails.application.require_environment! - end - - def write_help_message - puts HELP_MESSAGE - end - - def write_commands(commands) - width = commands.map { |name, _| name.size }.max || 10 - commands.each { |command| printf(" %-#{width}s %s\n", *command) } - end - - def parse_command(command) - case command - when '--version', '-v' - 'version' - when '--help', '-h' - 'help' - else - command - end - end - end -end diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb deleted file mode 100644 index ea5d20ea24..0000000000 --- a/railties/lib/rails/commands/console.rb +++ /dev/null @@ -1,68 +0,0 @@ -require 'optparse' -require 'irb' -require 'irb/completion' -require 'rails/commands/console_helper' - -module Rails - class Console - include ConsoleHelper - - class << self - def parse_arguments(arguments) - options = {} - - OptionParser.new do |opt| - opt.banner = "Usage: rails console [environment] [options]" - opt.on('-s', '--sandbox', 'Rollback database modifications on exit.') { |v| options[:sandbox] = v } - opt.on("-e", "--environment=name", String, - "Specifies the environment to run this console under (test/development/production).", - "Default: development") { |v| options[:environment] = v.strip } - opt.parse!(arguments) - end - - set_options_env(arguments, options) - end - end - - attr_reader :options, :app, :console - - def initialize(app, options={}) - @app = app - @options = options - - app.sandbox = sandbox? - app.load_console - - @console = app.config.console || IRB - end - - def sandbox? - options[:sandbox] - end - - def environment - options[:environment] ||= super - end - alias_method :environment?, :environment - - def set_environment! - Rails.env = environment - end - - def start - set_environment! if environment? - - if sandbox? - puts "Loading #{Rails.env} environment in sandbox (Rails #{Rails.version})" - puts "Any modifications you make will be rolled back on exit" - else - puts "Loading #{Rails.env} environment (Rails #{Rails.version})" - end - - if defined?(console::ExtendCommandBundle) - console::ExtendCommandBundle.include(Rails::ConsoleMethods) - end - console.start - end - end -end diff --git a/railties/lib/rails/commands/console/console_command.rb b/railties/lib/rails/commands/console/console_command.rb new file mode 100644 index 0000000000..62e3aa19df --- /dev/null +++ b/railties/lib/rails/commands/console/console_command.rb @@ -0,0 +1,89 @@ +require "irb" +require "irb/completion" + +require "rails/command/environment_argument" + +module Rails + class Console + module BacktraceCleaner + def filter_backtrace(bt) + if result = super + Rails.backtrace_cleaner.filter([result]).first + end + end + end + + def self.start(*args) + new(*args).start + end + + attr_reader :options, :app, :console + + def initialize(app, options = {}) + @app = app + @options = options + + app.sandbox = sandbox? + app.load_console + + @console = app.config.console || IRB + + if @console == IRB + IRB::WorkSpace.prepend(BacktraceCleaner) + end + end + + def sandbox? + options[:sandbox] + end + + def environment + options[:environment] + end + alias_method :environment?, :environment + + def set_environment! + Rails.env = environment + end + + def start + set_environment! if environment? + + if sandbox? + puts "Loading #{Rails.env} environment in sandbox (Rails #{Rails.version})" + puts "Any modifications you make will be rolled back on exit" + else + puts "Loading #{Rails.env} environment (Rails #{Rails.version})" + end + + if defined?(console::ExtendCommandBundle) + console::ExtendCommandBundle.include(Rails::ConsoleMethods) + end + console.start + end + end + + module Command + class ConsoleCommand < Base # :nodoc: + include EnvironmentArgument + + class_option :sandbox, aliases: "-s", type: :boolean, default: false, + desc: "Rollback database modifications on exit." + + class_option :environment, aliases: "-e", type: :string, + desc: "Specifies the environment to run this console under (test/development/production)." + + def perform + extract_environment_option_from_argument + + # RAILS_ENV needs to be set before config/application is required. + ENV["RAILS_ENV"] = options[:environment] + + ARGV.clear # Clear ARGV so IRB doesn't freak. + + require_application_and_environment! + Rails::Console.start(Rails.application, options) + end + end + end +end diff --git a/railties/lib/rails/commands/console_helper.rb b/railties/lib/rails/commands/console_helper.rb deleted file mode 100644 index 8ee0b60012..0000000000 --- a/railties/lib/rails/commands/console_helper.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'active_support/concern' - -module Rails - module ConsoleHelper # :nodoc: - extend ActiveSupport::Concern - - module ClassMethods - def start(*args) - new(*args).start - end - - private - def set_options_env(arguments, options) - if arguments.first && arguments.first[0] != '-' - env = arguments.first - if available_environments.include? env - options[:environment] = env - else - options[:environment] = %w(production development test).detect { |e| e =~ /^#{env}/ } || env - end - end - options - end - - def available_environments - Dir['config/environments/*.rb'].map { |fname| File.basename(fname, '.*') } - end - end - - def environment - ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development" - end - end -end
\ No newline at end of file diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb deleted file mode 100644 index 2c36edfa3f..0000000000 --- a/railties/lib/rails/commands/dbconsole.rb +++ /dev/null @@ -1,173 +0,0 @@ -require 'erb' -require 'yaml' -require 'optparse' -require 'rails/commands/console_helper' - -module Rails - class DBConsole - include ConsoleHelper - - attr_reader :arguments - - class << self - def parse_arguments(arguments) - options = {} - - OptionParser.new do |opt| - opt.banner = "Usage: rails dbconsole [environment] [options]" - opt.on("-p", "--include-password", "Automatically provide the password from database.yml") do |v| - options['include_password'] = true - end - - opt.on("--mode [MODE]", ['html', 'list', 'line', 'column'], - "Automatically put the sqlite3 database in the specified mode (html, list, line, column).") do |mode| - options['mode'] = mode - end - - opt.on("--header") do |h| - options['header'] = h - end - - opt.on("-h", "--help", "Show this help message.") do - puts opt - exit - end - - opt.on("-e", "--environment=name", String, - "Specifies the environment to run this console under (test/development/production).", - "Default: development" - ) { |v| options[:environment] = v.strip } - - opt.parse!(arguments) - abort opt.to_s unless (0..1).include?(arguments.size) - end - - set_options_env(arguments, options) - end - end - - def initialize(arguments = ARGV) - @arguments = arguments - end - - def start - options = self.class.parse_arguments(arguments) - ENV['RAILS_ENV'] = options[:environment] || environment - - case config["adapter"] - when /^(jdbc)?mysql/ - args = { - 'host' => '--host', - 'port' => '--port', - 'socket' => '--socket', - 'username' => '--user', - 'encoding' => '--default-character-set', - 'sslca' => '--ssl-ca', - 'sslcert' => '--ssl-cert', - 'sslcapath' => '--ssl-capath', - 'sslcipher' => '--ssl-cipher', - 'sslkey' => '--ssl-key' - }.map { |opt, arg| "#{arg}=#{config[opt]}" if config[opt] }.compact - - if config['password'] && options['include_password'] - args << "--password=#{config['password']}" - elsif config['password'] && !config['password'].to_s.empty? - args << "-p" - end - - args << config['database'] - - find_cmd_and_exec(['mysql', 'mysql5'], *args) - - when /^postgres|^postgis/ - ENV['PGUSER'] = config["username"] if config["username"] - ENV['PGHOST'] = config["host"] if config["host"] - ENV['PGPORT'] = config["port"].to_s if config["port"] - ENV['PGPASSWORD'] = config["password"].to_s if config["password"] && options['include_password'] - find_cmd_and_exec('psql', config["database"]) - - when "sqlite3" - args = [] - - args << "-#{options['mode']}" if options['mode'] - args << "-header" if options['header'] - args << File.expand_path(config['database'], Rails.respond_to?(:root) ? Rails.root : nil) - - find_cmd_and_exec('sqlite3', *args) - - when "oracle", "oracle_enhanced" - logon = "" - - if config['username'] - logon = config['username'] - logon << "/#{config['password']}" if config['password'] && options['include_password'] - logon << "@#{config['database']}" if config['database'] - end - - find_cmd_and_exec('sqlplus', logon) - - when "sqlserver" - args = [] - - args += ["-D", "#{config['database']}"] if config['database'] - args += ["-U", "#{config['username']}"] if config['username'] - args += ["-P", "#{config['password']}"] if config['password'] - - if config['host'] - host_arg = "#{config['host']}" - host_arg << ":#{config['port']}" if config['port'] - args += ["-S", host_arg] - end - - find_cmd_and_exec("sqsh", *args) - - else - abort "Unknown command-line client for #{config['database']}." - end - end - - def config - @config ||= begin - if configurations[environment].blank? - raise ActiveRecord::AdapterNotSpecified, "'#{environment}' database is not configured. Available configuration: #{configurations.inspect}" - else - configurations[environment] - end - end - end - - def environment - Rails.respond_to?(:env) ? Rails.env : super - end - - protected - def configurations - require APP_PATH - ActiveRecord::Base.configurations = Rails.application.config.database_configuration - ActiveRecord::Base.configurations - end - - def find_cmd_and_exec(commands, *args) - commands = Array(commands) - - dirs_on_path = ENV['PATH'].to_s.split(File::PATH_SEPARATOR) - unless (ext = RbConfig::CONFIG['EXEEXT']).empty? - commands = commands.map{|cmd| "#{cmd}#{ext}"} - end - - full_path_command = nil - found = commands.detect do |cmd| - dirs_on_path.detect do |path| - full_path_command = File.join(path, cmd) - File.file?(full_path_command) && File.executable?(full_path_command) - end - end - - if found - exec full_path_command, *args - else - abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.") - end - end - end -end diff --git a/railties/lib/rails/commands/dbconsole/dbconsole_command.rb b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb new file mode 100644 index 0000000000..54457cf78b --- /dev/null +++ b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb @@ -0,0 +1,158 @@ +require "erb" +require "yaml" + +require "rails/command/environment_argument" + +module Rails + class DBConsole + def self.start(*args) + new(*args).start + end + + def initialize(options = {}) + @options = options + end + + def start + ENV["RAILS_ENV"] = @options[:environment] || environment + + case config["adapter"] + when /^(jdbc)?mysql/ + args = { + "host" => "--host", + "port" => "--port", + "socket" => "--socket", + "username" => "--user", + "encoding" => "--default-character-set", + "sslca" => "--ssl-ca", + "sslcert" => "--ssl-cert", + "sslcapath" => "--ssl-capath", + "sslcipher" => "--ssl-cipher", + "sslkey" => "--ssl-key" + }.map { |opt, arg| "#{arg}=#{config[opt]}" if config[opt] }.compact + + if config["password"] && @options["include_password"] + args << "--password=#{config['password']}" + elsif config["password"] && !config["password"].to_s.empty? + args << "-p" + end + + args << config["database"] + + find_cmd_and_exec(["mysql", "mysql5"], *args) + + when /^postgres|^postgis/ + ENV["PGUSER"] = config["username"] if config["username"] + ENV["PGHOST"] = config["host"] if config["host"] + ENV["PGPORT"] = config["port"].to_s if config["port"] + ENV["PGPASSWORD"] = config["password"].to_s if config["password"] && @options["include_password"] + find_cmd_and_exec("psql", config["database"]) + + when "sqlite3" + args = [] + + args << "-#{@options['mode']}" if @options["mode"] + args << "-header" if @options["header"] + args << File.expand_path(config["database"], Rails.respond_to?(:root) ? Rails.root : nil) + + find_cmd_and_exec("sqlite3", *args) + + when "oracle", "oracle_enhanced" + logon = "" + + if config["username"] + logon = config["username"] + logon << "/#{config['password']}" if config["password"] && @options["include_password"] + logon << "@#{config['database']}" if config["database"] + end + + find_cmd_and_exec("sqlplus", logon) + + when "sqlserver" + args = [] + + args += ["-D", "#{config['database']}"] if config["database"] + args += ["-U", "#{config['username']}"] if config["username"] + args += ["-P", "#{config['password']}"] if config["password"] + + if config["host"] + host_arg = "#{config['host']}" + host_arg << ":#{config['port']}" if config["port"] + args += ["-S", host_arg] + end + + find_cmd_and_exec("sqsh", *args) + + else + abort "Unknown command-line client for #{config['database']}." + end + end + + def config + @config ||= begin + if configurations[environment].blank? + raise ActiveRecord::AdapterNotSpecified, "'#{environment}' database is not configured. Available configuration: #{configurations.inspect}" + else + configurations[environment] + end + end + end + + def environment + Rails.respond_to?(:env) ? Rails.env : Rails::Command.environment + end + + protected + def configurations + require APP_PATH + ActiveRecord::Base.configurations = Rails.application.config.database_configuration + ActiveRecord::Base.configurations + end + + def find_cmd_and_exec(commands, *args) + commands = Array(commands) + + dirs_on_path = ENV["PATH"].to_s.split(File::PATH_SEPARATOR) + unless (ext = RbConfig::CONFIG["EXEEXT"]).empty? + commands = commands.map { |cmd| "#{cmd}#{ext}" } + end + + full_path_command = nil + found = commands.detect do |cmd| + dirs_on_path.detect do |path| + full_path_command = File.join(path, cmd) + File.file?(full_path_command) && File.executable?(full_path_command) + end + end + + if found + exec full_path_command, *args + else + abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.") + end + end + end + + module Command + class DbconsoleCommand < Base # :nodoc: + include EnvironmentArgument + + class_option :include_password, aliases: "-p", type: :boolean, + desc: "Automatically provide the password from database.yml" + + class_option :mode, enum: %w( html list line column ), type: :string, + desc: "Automatically put the sqlite3 database in the specified mode (html, list, line, column)." + + class_option :header, type: :string + + class_option :environment, aliases: "-e", type: :string, + desc: "Specifies the environment to run this console under (test/development/production)." + + def perform + extract_environment_option_from_argument + + Rails::DBConsole.start(options) + end + end + end +end diff --git a/railties/lib/rails/commands/destroy.rb b/railties/lib/rails/commands/destroy.rb deleted file mode 100644 index ce26cc3fde..0000000000 --- a/railties/lib/rails/commands/destroy.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'rails/generators' - -#if no argument/-h/--help is passed to rails destroy command, then -#it generates the help associated. -if [nil, "-h", "--help"].include?(ARGV.first) - Rails::Generators.help 'destroy' - exit -end - -name = ARGV.shift -Rails::Generators.invoke name, ARGV, behavior: :revoke, destination_root: Rails.root diff --git a/railties/lib/rails/commands/destroy/destroy_command.rb b/railties/lib/rails/commands/destroy/destroy_command.rb new file mode 100644 index 0000000000..5b552b2070 --- /dev/null +++ b/railties/lib/rails/commands/destroy/destroy_command.rb @@ -0,0 +1,21 @@ +require "rails/generators" + +module Rails + module Command + class DestroyCommand < Base # :nodoc: + def help + Rails::Generators.help self.class.command_name + end + + def perform(*) + generator = args.shift + return help unless generator + + require_application_and_environment! + Rails.application.load_generators + + Rails::Generators.invoke generator, args, behavior: :revoke, destination_root: Rails.root + end + end + end +end diff --git a/railties/lib/rails/commands/generate.rb b/railties/lib/rails/commands/generate.rb deleted file mode 100644 index 926c36b967..0000000000 --- a/railties/lib/rails/commands/generate.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'rails/generators' - -#if no argument/-h/--help is passed to rails generate command, then -#it generates the help associated. -if [nil, "-h", "--help"].include?(ARGV.first) - Rails::Generators.help 'generate' - exit -end - -name = ARGV.shift - -root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root -Rails::Generators.invoke name, ARGV, behavior: :invoke, destination_root: root diff --git a/railties/lib/rails/commands/generate/generate_command.rb b/railties/lib/rails/commands/generate/generate_command.rb new file mode 100644 index 0000000000..59b2febc43 --- /dev/null +++ b/railties/lib/rails/commands/generate/generate_command.rb @@ -0,0 +1,21 @@ +require "rails/generators" + +module Rails + module Command + class GenerateCommand < Base # :nodoc: + def help + Rails::Generators.help self.class.command_name + end + + def perform(*) + generator = args.shift + return help unless generator + + require_application_and_environment! + load_generators + + Rails::Generators.invoke generator, args, behavior: :invoke, destination_root: Rails::Command.root + end + end + end +end diff --git a/railties/lib/rails/commands/help/USAGE b/railties/lib/rails/commands/help/USAGE new file mode 100644 index 0000000000..348f41861f --- /dev/null +++ b/railties/lib/rails/commands/help/USAGE @@ -0,0 +1,27 @@ +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") + server Start the Rails server (short-cut alias: "s") + test Run tests (short-cut alias: "t") + dbconsole Start a console for the database specified in config/database.yml + (short-cut alias: "db") + new Create a new Rails application. "rails new my_app" creates a + new application called MyApp in "./my_app" + +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/help/help_command.rb b/railties/lib/rails/commands/help/help_command.rb new file mode 100644 index 0000000000..90d37217fc --- /dev/null +++ b/railties/lib/rails/commands/help/help_command.rb @@ -0,0 +1,13 @@ +module Rails + module Command + class HelpCommand < Base # :nodoc: + hide_command! + + def help(*) + puts self.class.desc + + Rails::Command.print_commands + end + end + end +end diff --git a/railties/lib/rails/commands/new/new_command.rb b/railties/lib/rails/commands/new/new_command.rb new file mode 100644 index 0000000000..74d1fa5021 --- /dev/null +++ b/railties/lib/rails/commands/new/new_command.rb @@ -0,0 +1,15 @@ +module Rails + module Command + class NewCommand < Base # :nodoc: + def help + Rails::Command.invoke :application, [ "--help" ] + end + + def perform(*) + puts "Can't initialize a new Rails application within the directory of another, please change to a non-Rails directory first.\n" + puts "Type 'rails' for help." + exit 1 + end + end + end +end diff --git a/railties/lib/rails/commands/plugin.rb b/railties/lib/rails/commands/plugin.rb deleted file mode 100644 index 52d8966ead..0000000000 --- a/railties/lib/rails/commands/plugin.rb +++ /dev/null @@ -1,23 +0,0 @@ -if ARGV.first != "new" - ARGV[0] = "--help" -else - ARGV.shift - unless ARGV.delete("--no-rc") - customrc = ARGV.index{ |x| x.include?("--rc=") } - railsrc = if customrc - File.expand_path(ARGV.delete_at(customrc).gsub(/--rc=/, "")) - else - File.join(File.expand_path("~"), '.railsrc') - end - if File.exist?(railsrc) - extra_args_string = File.read(railsrc) - extra_args = extra_args_string.split(/\n+/).flat_map(&:split) - puts "Using #{extra_args.join(" ")} from #{railsrc}" - ARGV.insert(1, *extra_args) - end - end -end - -require 'rails/generators' -require 'rails/generators/rails/plugin/plugin_generator' -Rails::Generators::PluginGenerator.start diff --git a/railties/lib/rails/commands/plugin/plugin_command.rb b/railties/lib/rails/commands/plugin/plugin_command.rb new file mode 100644 index 0000000000..16587ce067 --- /dev/null +++ b/railties/lib/rails/commands/plugin/plugin_command.rb @@ -0,0 +1,43 @@ +module Rails + module Command + class PluginCommand < Base # :nodoc: + hide_command! + + def help + run_plugin_generator %w( --help ) + end + + def self.banner(*) # :nodoc: + "#{executable} new [options]" + end + + class_option :rc, type: :boolean, 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." + + def perform(type = nil, *plugin_args) + plugin_args << "--help" unless type == "new" + + unless options.key?("no_rc") # Thor's not so indifferent access hash. + railsrc = File.expand_path(options[:rc]) + + if File.exist?(railsrc) + extra_args = File.read(railsrc).split(/\n+/).flat_map(&:split) + puts "Using #{extra_args.join(" ")} from #{railsrc}" + plugin_args.insert(1, *extra_args) + end + end + + run_plugin_generator plugin_args + end + + private + def run_plugin_generator(plugin_args) + require "rails/generators" + require "rails/generators/rails/plugin/plugin_generator" + Rails::Generators::PluginGenerator.start plugin_args + end + end + end +end diff --git a/railties/lib/rails/commands/rake/rake_command.rb b/railties/lib/rails/commands/rake/rake_command.rb new file mode 100644 index 0000000000..f03dc81117 --- /dev/null +++ b/railties/lib/rails/commands/rake/rake_command.rb @@ -0,0 +1,51 @@ +module Rails + module Command + class RakeCommand < Base # :nodoc: + extend Rails::Command::Actions + + namespace "rake" + + class << self + def printing_commands + formatted_rake_tasks.map(&:first) + end + + def perform(task, *) + require_rake + + ARGV.unshift(task) # Prepend the task, so Rake knows how to run it. + + Rake.application.standard_exception_handling do + Rake.application.init("rails") + Rake.application.load_rakefile + Rake.application.top_level + end + end + + private + def rake_tasks + require_rake + + return @rake_tasks if defined?(@rake_tasks) + + ActiveSupport::Deprecation.silence do + require_application_and_environment! + end + + Rake::TaskManager.record_task_metadata = true + Rake.application.instance_variable_set(:@name, "rails") + load_tasks + @rake_tasks = Rake.application.tasks.select(&:comment) + end + + def formatted_rake_tasks + rake_tasks.map { |t| [ t.name_with_args, t.comment ] } + end + + def require_rake + require "rake" # Defer booting Rake until we know it's needed. + end + end + end + end +end diff --git a/railties/lib/rails/commands/rake_proxy.rb b/railties/lib/rails/commands/rake_proxy.rb deleted file mode 100644 index f7d5df6b2f..0000000000 --- a/railties/lib/rails/commands/rake_proxy.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'rake' -require 'active_support' - -module Rails - module RakeProxy #:nodoc: - private - def run_rake_task(command) - ARGV.unshift(command) # Prepend the command, so Rake knows how to run it. - - Rake.application.standard_exception_handling do - Rake.application.init('rails') - Rake.application.load_rakefile - Rake.application.top_level - end - end - - def rake_tasks - return @rake_tasks if defined?(@rake_tasks) - - ActiveSupport::Deprecation.silence do - require_application_and_environment! - end - - Rake::TaskManager.record_task_metadata = true - Rake.application.instance_variable_set(:@name, 'rails') - Rails.application.load_tasks - @rake_tasks = Rake.application.tasks.select(&:comment) - end - - def formatted_rake_tasks - rake_tasks.map { |t| [ t.name_with_args, t.comment ] } - end - end -end diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb deleted file mode 100644 index 5844e9037c..0000000000 --- a/railties/lib/rails/commands/runner.rb +++ /dev/null @@ -1,68 +0,0 @@ -require 'optparse' - -options = { environment: (ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development").dup } -code_or_file = nil - -if ARGV.first.nil? - ARGV.push "-h" -end - -ARGV.clone.options do |opts| - opts.banner = "Usage: rails runner [options] [<'Some.ruby(code)'> | <filename.rb>]" - - opts.separator "" - - opts.on("-e", "--environment=name", String, - "Specifies the environment for the runner to operate under (test/development/production).", - "Default: development") { |v| options[:environment] = v } - - opts.separator "" - - opts.on("-h", "--help", - "Show this help message.") { $stdout.puts opts; exit } - - opts.separator "" - opts.separator "Examples: " - - opts.separator " rails runner 'puts Rails.env'" - opts.separator " This runs the code `puts Rails.env` after loading the app" - opts.separator "" - opts.separator " rails runner path/to/filename.rb" - opts.separator " This runs the Ruby file located at `path/to/filename.rb` after loading the app" - - if RbConfig::CONFIG['host_os'] !~ /mswin|mingw/ - opts.separator "" - opts.separator "You can also use runner as a shebang line for your executables:" - opts.separator " -------------------------------------------------------------" - opts.separator " #!/usr/bin/env #{File.expand_path($0)} runner" - opts.separator "" - opts.separator " Product.all.each { |p| p.price *= 2 ; p.save! }" - opts.separator " -------------------------------------------------------------" - end - - opts.order! { |o| code_or_file ||= o } rescue retry -end - -ARGV.delete(code_or_file) - -ENV["RAILS_ENV"] = options[:environment] - -require APP_PATH -Rails.application.require_environment! -Rails.application.load_runner - -if code_or_file.nil? - $stderr.puts "Run '#{$0} -h' for help." - exit 1 -elsif File.exist?(code_or_file) - $0 = code_or_file - Kernel.load code_or_file -else - begin - eval(code_or_file, binding, __FILE__, __LINE__) - rescue SyntaxError, NameError - $stderr.puts "Please specify a valid ruby command or the path of a script to run." - $stderr.puts "Run '#{$0} -h' for help." - exit 1 - end -end diff --git a/railties/lib/rails/commands/runner/USAGE b/railties/lib/rails/commands/runner/USAGE new file mode 100644 index 0000000000..dc47a35ff3 --- /dev/null +++ b/railties/lib/rails/commands/runner/USAGE @@ -0,0 +1,17 @@ +Examples: + +Run `puts Rails.env` after loading the app: + + <%= executable %> 'puts Rails.env' + +Run the Ruby file located at `path/to/filename.rb` after loading the app: + + <%= executable %> path/to/filename.rb + +<% if RbConfig::CONFIG['host_os'] !~ /mswin|mingw/ %> +You can also use the runner command as a shebang line for your executables: + + #!/usr/bin/env <%= File.expand_path(executable) %> + + Product.all.each { |p| p.price *= 2 ; p.save! } +<% end %> diff --git a/railties/lib/rails/commands/runner/runner_command.rb b/railties/lib/rails/commands/runner/runner_command.rb new file mode 100644 index 0000000000..27666c76b7 --- /dev/null +++ b/railties/lib/rails/commands/runner/runner_command.rb @@ -0,0 +1,45 @@ +module Rails + module Command + class RunnerCommand < Base # :nodoc: + class_option :environment, aliases: "-e", type: :string, + default: Rails::Command.environment.dup, + desc: "The environment for the runner to operate under (test/development/production)" + + def help + super + puts self.class.desc + end + + def self.banner(*) + "#{super} [<'Some.ruby(code)'> | <filename.rb>]" + end + + def perform(code_or_file = nil) + unless code_or_file + help + exit 1 + end + + ENV["RAILS_ENV"] = options[:environment] + + require_application_and_environment! + Rails.application.load_runner + + if File.exist?(code_or_file) + $0 = code_or_file + Kernel.load code_or_file + else + begin + eval(code_or_file, binding, __FILE__, __LINE__) + rescue SyntaxError, NameError => error + $stderr.puts "Please specify a valid ruby command or the path of a script to run." + $stderr.puts "Run '#{self.class.executable} -h' for help." + $stderr.puts + $stderr.puts error + exit 1 + end + end + end + end + end +end diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server/server_command.rb index 27cbaf360a..e9538b804c 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server/server_command.rb @@ -1,7 +1,8 @@ -require 'fileutils' -require 'optparse' -require 'action_dispatch' -require 'rails' +require "fileutils" +require "optparse" +require "action_dispatch" +require "rails" +require "rails/dev_caching" module Rails class Server < ::Rack::Server @@ -18,11 +19,13 @@ module Rails options end - private - - def option_parser(options) + def option_parser(options) # :nodoc: OptionParser.new do |opts| - opts.banner = "Usage: rails server [mongrel, thin etc] [options]" + 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, @@ -79,7 +82,7 @@ module Rails ensure # The '-h' option calls exit before @options is set. # If we call 'options' with it unset, we get double help banners. - puts 'Exiting' unless @options && options[:daemonize] + puts "Exiting" unless @options && options[:daemonize] end def middleware @@ -87,25 +90,21 @@ module Rails end def default_options - super.merge({ - Port: ENV.fetch('PORT', 3000).to_i, + 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, + environment: (ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development").dup, daemonize: false, - caching: false, - pid: Options::DEFAULT_PID_PATH - }) + caching: nil, + pid: Options::DEFAULT_PID_PATH, + restart_cmd: restart_command) end private - def setup_dev_caching - return unless options[:environment] == "development" - - if options[:caching] == false - delete_cache_file - elsif options[:caching] - create_cache_file + if options[:environment] == "development" + Rails::DevCaching.enable_by_argument(options[:caching]) end end @@ -114,21 +113,11 @@ module Rails puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}" puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}" puts "=> Run `rails server -h` for more startup options" - - puts "=> Ctrl-C to shutdown server" unless options[:daemonize] - end - - def create_cache_file - FileUtils.touch("tmp/caching-dev.txt") - end - - def delete_cache_file - FileUtils.rm("tmp/caching-dev.txt") if File.exist?("tmp/caching-dev.txt") end def create_tmp_directories %w(cache pids sockets).each do |dir_to_make| - FileUtils.mkdir_p(File.join(Rails.root, 'tmp', dir_to_make)) + FileUtils.mkdir_p(File.join(Rails.root, "tmp", dir_to_make)) end end @@ -143,5 +132,29 @@ module Rails Rails.logger.extend(ActiveSupport::Logger.broadcast(console)) end end + + def restart_command + "bin/rails server #{ARGV.join(' ')}" + end + end + + module Command + class ServerCommand < Base # :nodoc: + def help + puts Rails::Server::Options.new.option_parser(Hash.new) + end + + 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 diff --git a/railties/lib/rails/commands/test.rb b/railties/lib/rails/commands/test.rb deleted file mode 100644 index dd069f081f..0000000000 --- a/railties/lib/rails/commands/test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require "rails/test_unit/minitest_plugin" - -if defined?(ENGINE_ROOT) - $: << File.expand_path('test', ENGINE_ROOT) -else - $: << File.expand_path('../../test', APP_PATH) -end - -exit Minitest.run(ARGV) diff --git a/railties/lib/rails/commands/test/test_command.rb b/railties/lib/rails/commands/test/test_command.rb new file mode 100644 index 0000000000..7bf8f61137 --- /dev/null +++ b/railties/lib/rails/commands/test/test_command.rb @@ -0,0 +1,20 @@ +require "rails/command" +require "rails/test_unit/minitest_plugin" + +module Rails + module Command + class TestCommand < Base # :nodoc: + def help + perform # Hand over help printing to minitest. + end + + def perform(*) + $LOAD_PATH << Rails::Command.root.join("test") + + Minitest.run_via[:rails] = true + + require "active_support/testing/autorun" + end + end + end +end diff --git a/railties/lib/rails/commands/version/version_command.rb b/railties/lib/rails/commands/version/version_command.rb new file mode 100644 index 0000000000..ac745594ee --- /dev/null +++ b/railties/lib/rails/commands/version/version_command.rb @@ -0,0 +1,9 @@ +module Rails + module Command + class VersionCommand < Base # :nodoc: + def perform + Rails::Command.invoke :application, [ "--version" ] + end + end + end +end diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index 30eafd59f2..7dfab969e8 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -1,7 +1,7 @@ -require 'active_support/ordered_options' -require 'active_support/core_ext/object' -require 'rails/paths' -require 'rails/rack' +require "active_support/ordered_options" +require "active_support/core_ext/object" +require "rails/paths" +require "rails/rack" module Rails module Configuration @@ -112,7 +112,7 @@ module Rails end def method_missing(method, *args) - method = method.to_s.sub(/=$/, '').to_sym + method = method.to_s.sub(/=$/, "").to_sym return @options[method] if args.empty? diff --git a/railties/lib/rails/console/app.rb b/railties/lib/rails/console/app.rb index ac5836a588..541d5e3dad 100644 --- a/railties/lib/rails/console/app.rb +++ b/railties/lib/rails/console/app.rb @@ -1,5 +1,5 @@ -require 'active_support/all' -require 'action_controller' +require "active_support/all" +require "action_controller" module Rails module ConsoleMethods @@ -29,8 +29,7 @@ module Rails # reloads the environment def reload!(print=true) puts "Reloading..." if print - ActionDispatch::Reloader.cleanup! - ActionDispatch::Reloader.prepare! + Rails.application.reloader.reload! true end end diff --git a/railties/lib/rails/dev_caching.rb b/railties/lib/rails/dev_caching.rb new file mode 100644 index 0000000000..f69275a34a --- /dev/null +++ b/railties/lib/rails/dev_caching.rb @@ -0,0 +1,43 @@ +require "fileutils" + +module Rails + module DevCaching # :nodoc: + class << self + FILE = "tmp/caching-dev.txt" + + def enable_by_file + FileUtils.mkdir_p("tmp") + + if File.exist?(FILE) + delete_cache_file + puts "Development mode is no longer being cached." + else + create_cache_file + puts "Development mode is now being cached." + end + + FileUtils.touch "tmp/restart.txt" + FileUtils.rm_f("tmp/pids/server.pid") + end + + def enable_by_argument(caching) + FileUtils.mkdir_p("tmp") + + if caching + create_cache_file + elsif caching == false && File.exist?(FILE) + delete_cache_file + end + end + + private + def create_cache_file + FileUtils.touch FILE + end + + def delete_cache_file + File.delete FILE + end + end + end +end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 5757d235d2..90e7c04d7c 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -1,8 +1,8 @@ -require 'rails/railtie' -require 'rails/engine/railties' -require 'active_support/core_ext/module/delegation' -require 'pathname' -require 'thread' +require "rails/railtie" +require "rails/engine/railties" +require "active_support/core_ext/module/delegation" +require "pathname" +require "thread" module Rails # <tt>Rails::Engine</tt> allows you to wrap a specific Rails application or subset of @@ -219,7 +219,7 @@ module Rails # The next thing that changes in isolated engines is the behavior of routes. # Normally, when you namespace your controllers, you also need to namespace # the related routes. With an isolated engine, the engine's namespace is - # automatically applied, so you don't need to specify it explicity in your + # automatically applied, so you don't need to specify it explicitly in your # routes: # # MyEngine::Engine.routes.draw do @@ -337,7 +337,7 @@ module Rails # == Loading priority # # In order to change engine's priority you can use +config.railties_order+ in the main application. - # It will affect the priority of loading views, helpers, assets and all the other files + # It will affect the priority of loading views, helpers, assets, and all the other files # related to engine or application. # # # load Blog::Engine with highest priority, followed by application and other railties @@ -402,7 +402,7 @@ module Rails end unless mod.respond_to?(:railtie_routes_url_helpers) - define_method(:railtie_routes_url_helpers) {|include_path_helpers = true| railtie.routes.url_helpers(include_path_helpers) } + define_method(:railtie_routes_url_helpers) { |include_path_helpers = true| railtie.routes.url_helpers(include_path_helpers) } end end end @@ -590,8 +590,8 @@ module Rails initializer :add_view_paths do views = paths["app/views"].existent unless views.empty? - ActiveSupport.on_load(:action_controller){ prepend_view_path(views) if respond_to?(:prepend_view_path) } - ActiveSupport.on_load(:action_mailer){ prepend_view_path(views) } + ActiveSupport.on_load(:action_controller) { prepend_view_path(views) if respond_to?(:prepend_view_path) } + ActiveSupport.on_load(:action_mailer) { prepend_view_path(views) } end end @@ -619,7 +619,7 @@ module Rails end rake_tasks do - next if self.is_a?(Rails::Application) + next if is_a?(Rails::Application) next unless has_migrations? namespace railtie_name do @@ -643,62 +643,62 @@ module Rails protected - def load_config_initializer(initializer) - ActiveSupport::Notifications.instrument('load_config_initializer.railties', initializer: initializer) do - load(initializer) + def load_config_initializer(initializer) + ActiveSupport::Notifications.instrument("load_config_initializer.railties", initializer: initializer) do + load(initializer) + end end - end - def run_tasks_blocks(*) #:nodoc: - super - paths["lib/tasks"].existent.sort.each { |ext| load(ext) } - end + def run_tasks_blocks(*) #:nodoc: + super + paths["lib/tasks"].existent.sort.each { |ext| load(ext) } + end - def has_migrations? #:nodoc: - paths["db/migrate"].existent.any? - end + def has_migrations? #:nodoc: + paths["db/migrate"].existent.any? + end - def self.find_root_with_flag(flag, root_path, default=nil) #:nodoc: + 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 - end + while root_path && File.directory?(root_path) && !File.exist?("#{root_path}/#{flag}") + parent = File.dirname(root_path) + root_path = parent != root_path && parent + end - root = File.exist?("#{root_path}/#{flag}") ? root_path : default - raise "Could not find root path for #{self}" unless root + root = File.exist?("#{root_path}/#{flag}") ? root_path : default + raise "Could not find root path for #{self}" unless root - Pathname.new File.realpath root - end + Pathname.new File.realpath root + end - def default_middleware_stack #:nodoc: - ActionDispatch::MiddlewareStack.new - end + def default_middleware_stack #:nodoc: + ActionDispatch::MiddlewareStack.new + end - def _all_autoload_once_paths #:nodoc: - config.autoload_once_paths - end + def _all_autoload_once_paths #:nodoc: + config.autoload_once_paths + end - def _all_autoload_paths #:nodoc: - @_all_autoload_paths ||= (config.autoload_paths + config.eager_load_paths + config.autoload_once_paths).uniq - end + def _all_autoload_paths #:nodoc: + @_all_autoload_paths ||= (config.autoload_paths + config.eager_load_paths + config.autoload_once_paths).uniq + end - def _all_load_paths #:nodoc: - @_all_load_paths ||= (config.paths.load_paths + _all_autoload_paths).uniq - end + def _all_load_paths #:nodoc: + @_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 - req.routes = routes - req.engine_script_name = req.script_name - req - end + def build_request(env) + env.merge!(env_config) + req = ActionDispatch::Request.new env + req.routes = routes + req.engine_script_name = req.script_name + req + end - def build_middleware - config.middleware - end + def build_middleware + config.middleware + end end end diff --git a/railties/lib/rails/engine/commands.rb b/railties/lib/rails/engine/commands.rb index 7bbd9ef744..a23ae44b0b 100644 --- a/railties/lib/rails/engine/commands.rb +++ b/railties/lib/rails/engine/commands.rb @@ -1,6 +1,4 @@ -require 'rails/engine/commands_tasks' - -ARGV << '--help' if ARGV.empty? +require "rails/command" aliases = { "g" => "generate", @@ -11,4 +9,4 @@ aliases = { command = ARGV.shift command = aliases[command] || command -Rails::Engine::CommandsTasks.new(ARGV).run_command!(command) +Rails::Command.invoke command, ARGV diff --git a/railties/lib/rails/engine/commands_tasks.rb b/railties/lib/rails/engine/commands_tasks.rb deleted file mode 100644 index fa3ee59b7d..0000000000 --- a/railties/lib/rails/engine/commands_tasks.rb +++ /dev/null @@ -1,116 +0,0 @@ -require 'rails/commands/rake_proxy' - -module Rails - class Engine - class CommandsTasks # :nodoc: - include Rails::RakeProxy - - attr_reader :argv - - HELP_MESSAGE = <<-EOT -Usage: rails COMMAND [ARGS] - -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 `rails server` or `rails console`, -you should do it from application's directory (typically test/dummy). - -In addition to those commands, there are: - EOT - - COMMAND_WHITELIST = %w(generate destroy version help test) - - def initialize(argv) - @argv = argv - end - - def run_command!(command) - command = parse_command(command) - - if COMMAND_WHITELIST.include?(command) - send(command) - else - run_rake_task(command) - end - end - - def generate - generate_or_destroy(:generate) - end - - def destroy - generate_or_destroy(:destroy) - end - - def test - require_command!("test") - end - - def version - argv.unshift '--version' - require_command!("application") - end - - def help - write_help_message - write_commands(formatted_rake_tasks) - end - - private - - def require_command!(command) - require "rails/commands/#{command}" - end - - def generate_or_destroy(command) - load_generators - require_command!(command) - end - - def load_generators - require 'rails/generators' - require ENGINE_PATH - - engine = ::Rails::Engine.find(ENGINE_ROOT) - Rails::Generators.namespace = engine.railtie_namespace - engine.load_generators - end - - def write_help_message - puts HELP_MESSAGE - end - - def write_commands(commands) - width = commands.map { |name, _| name.size }.max || 10 - commands.each { |command| printf(" %-#{width}s %s\n", *command) } - end - - def parse_command(command) - case command - when '--version', '-v' - 'version' - when '--help', '-h' - 'help' - else - command - end - end - - def rake_tasks - return @rake_tasks if defined?(@rake_tasks) - - load_generators - Rake::TaskManager.record_task_metadata = true - Rake.application.init('rails') - Rake.application.load_rakefile - @rake_tasks = Rake.application.tasks.select(&:comment) - end - end - end -end diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index 8cadbc3ddd..147b904679 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -1,4 +1,4 @@ -require 'rails/railtie/configuration' +require "rails/railtie/configuration" module Rails class Engine @@ -39,6 +39,7 @@ module Rails paths.add "app", eager_load: true, glob: "{*,*/concerns}" paths.add "app/assets", glob: "*" paths.add "app/controllers", eager_load: true + paths.add "app/channels", eager_load: true, glob: "**/*_channel.rb" paths.add "app/helpers", eager_load: true paths.add "app/models", eager_load: true paths.add "app/mailers", eager_load: true diff --git a/railties/lib/rails/gem_version.rb b/railties/lib/rails/gem_version.rb index 93e0151602..9c49e0655a 100644 --- a/railties/lib/rails/gem_version.rb +++ b/railties/lib/rails/gem_version.rb @@ -6,9 +6,9 @@ module Rails module VERSION MAJOR = 5 - MINOR = 0 + MINOR = 1 TINY = 0 - PRE = "beta2" + PRE = "alpha" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index e3d79521e7..dd16b44786 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -1,45 +1,48 @@ -activesupport_path = File.expand_path('../../../../activesupport/lib', __FILE__) +activesupport_path = File.expand_path("../../../../activesupport/lib", __FILE__) $:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) -require 'thor/group' +require "thor/group" +require "rails/command" -require 'active_support' -require 'active_support/core_ext/object/blank' -require 'active_support/core_ext/kernel/singleton_class' -require 'active_support/core_ext/array/extract_options' -require 'active_support/core_ext/hash/deep_merge' -require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/core_ext/string/inflections' +require "active_support" +require "active_support/core_ext/object/blank" +require "active_support/core_ext/kernel/singleton_class" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/hash/deep_merge" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/string/inflections" module Rails module Generators - autoload :Actions, 'rails/generators/actions' - autoload :ActiveModel, 'rails/generators/active_model' - autoload :Base, 'rails/generators/base' - autoload :Migration, 'rails/generators/migration' - autoload :NamedBase, 'rails/generators/named_base' - autoload :ResourceHelpers, 'rails/generators/resource_helpers' - autoload :TestCase, 'rails/generators/test_case' + include Rails::Command::Behavior + + autoload :Actions, "rails/generators/actions" + autoload :ActiveModel, "rails/generators/active_model" + autoload :Base, "rails/generators/base" + autoload :Migration, "rails/generators/migration" + autoload :NamedBase, "rails/generators/named_base" + autoload :ResourceHelpers, "rails/generators/resource_helpers" + autoload :TestCase, "rails/generators/test_case" mattr_accessor :namespace DEFAULT_ALIASES = { rails: { - actions: '-a', - orm: '-o', - javascripts: '-j', - javascript_engine: '-je', - resource_controller: '-c', - scaffold_controller: '-c', - stylesheets: '-y', - stylesheet_engine: '-se', - scaffold_stylesheet: '-ss', - template_engine: '-e', - test_framework: '-t' + actions: "-a", + orm: "-o", + javascripts: "-j", + javascript_engine: "-je", + resource_controller: "-c", + scaffold_controller: "-c", + stylesheets: "-y", + stylesheet_engine: "-se", + scaffold_stylesheet: "-ss", + template_engine: "-e", + test_framework: "-t" }, test_unit: { - fixture_replacement: '-r', + fixture_replacement: "-r", } } @@ -105,7 +108,7 @@ module Rails # 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 views, helpers and assets + # 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" @@ -116,6 +119,10 @@ module Rails helper: false, template_engine: nil ) + + if ARGV.first == "mailer" + options[:rails].merge!(template_engine: :erb) + end end # Remove the color from output. @@ -123,67 +130,6 @@ module Rails Thor::Base.shell = Thor::Shell::Basic end - # Track all generators subclasses. - def self.subclasses - @subclasses ||= [] - end - - # 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}" - end - - lookup(lookups) - - namespaces = Hash[subclasses.map { |klass| [klass.namespace, klass] }] - - lookups.each do |namespace| - klass = namespaces[namespace] - return klass if klass - end - - invoke_fallbacks_for(name, base) || invoke_fallbacks_for(context, name) - 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 - end - 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 @@ -228,7 +174,7 @@ module Rails end # Show help message with available generators. - def self.help(command = 'generate') + def self.help(command = "generate") puts "Usage: rails #{command} GENERATOR [args] [options]" puts puts "General options:" @@ -256,74 +202,83 @@ module Rails def self.sorted_groups namespaces = public_namespaces namespaces.sort! + groups = Hash.new { |h,k| h[k] = [] } namespaces.each do |namespace| - base = namespace.split(':').first + base = namespace.split(":").first groups[base] << namespace end + rails = groups.delete("rails") - rails.map! { |n| n.sub(/^rails:/, '') } + rails.map! { |n| n.sub(/^rails:/, "") } rails.delete("app") rails.delete("plugin") hidden_namespaces.each { |n| groups.delete(n.to_s) } - [["rails", rails]] + groups.sort.to_a + [[ "rails", rails ]] + groups.sort.to_a end - protected + # 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 - # This code is based directly on the Text gem implementation - # Returns a value representing the "cost" of transforming str1 into str2 - def self.levenshtein_distance str1, str2 - s = str1 - t = str2 - n = s.length - m = t.length - - return m if (0 == n) - return n if (0 == m) - - d = (0..m).to_a - x = nil - - # avoid duplicating an enumerable object in the loop - str2_codepoint_enumerable = str2.each_codepoint - - str1.each_codepoint.with_index do |char1, i| - e = i+1 - - str2_codepoint_enumerable.with_index do |char2, j| - cost = (char1 == char2) ? 0 : 1 - x = [ - d[j+1] + 1, # insertion - e + 1, # deletion - d[j] + cost # substitution - ].min - d[j] = e - e = x - end - - d[m] = x + unless base || context + unless name.to_s.include?(?:) + lookups << "#{name}:#{name}" + lookups << "rails:#{name}" end - - x + lookups << "#{name}" end - # Prints a list of generators. - def self.print_list(base, namespaces) #:nodoc: - namespaces = namespaces.reject do |n| - hidden_namespaces.include?(n) - end + lookup(lookups) - return if namespaces.empty? - puts "#{base.camelize}:" + namespaces = Hash[subclasses.map { |klass| [klass.namespace, klass] }] + lookups.each do |namespace| - namespaces.each do |namespace| - puts(" #{namespace}") - end + klass = namespaces[namespace] + return klass if klass + end + + invoke_fallbacks_for(name, base) || invoke_fallbacks_for(context, name) + end - puts + # 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 + end + end + + protected + def self.print_list(base, namespaces) + namespaces = namespaces.reject { |n| hidden_namespaces.include?(n) } + super end # Try fallbacks for the given base. @@ -342,53 +297,16 @@ module Rails nil end - # Receives namespaces in an array and tries to find matching generators - # in the load path. - def self.lookup(namespaces) #:nodoc: - paths = namespaces_to_paths(namespaces) - - paths.each do |raw_path| - ["rails/generators", "generators"].each do |base| - path = "#{base}/#{raw_path}_generator" - - begin - require path - return - rescue LoadError => e - raise unless e.message =~ /#{Regexp.escape(path)}$/ - rescue Exception => e - warn "[WARNING] Could not load generator #{path.inspect}. Error: #{e.message}.\n#{e.backtrace.join("\n")}" - end - end - end + def self.command_type + @command_type ||= "generator" end - # This will try to load any generator in the load path to show in help. - def self.lookup! #:nodoc: - $LOAD_PATH.each do |base| - Dir[File.join(base, "{rails/generators,generators}", "**", "*_generator.rb")].each do |path| - begin - path = path.sub("#{base}/", "") - require path - rescue Exception - # No problem - end - end - end + def self.lookup_paths + @lookup_paths ||= %w( rails/generators generators ) end - # 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 self.namespaces_to_paths(namespaces) #:nodoc: - paths = [] - namespaces.each do |namespace| - pieces = namespace.split(":") - paths << pieces.dup.push(pieces.last).join("/") - paths << pieces.join("/") - end - paths.uniq! - paths + def self.file_lookup_paths + @file_lookup_paths ||= [ "{#{lookup_paths.join(',')}}", "**", "*_generator.rb" ] end end end diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index 9ca731347a..ab9dc019e2 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -1,5 +1,3 @@ -require 'open-uri' - module Rails module Generators module Actions @@ -105,7 +103,7 @@ module Rails in_root do if options[:env].nil? - inject_into_file 'config/application.rb', "\n #{data}", after: sentinel, verbose: false + inject_into_file "config/application.rb", "\n #{data}", after: sentinel, verbose: false else Array(options[:env]).each do |env| inject_into_file "config/environments/#{env}.rb", "\n #{data}", after: env_file_sentinel, verbose: false @@ -207,16 +205,22 @@ module Rails in_root { run_ruby_script("bin/rails generate #{what} #{argument}", verbose: false) } end - # Runs the supplied rake task + # Runs the supplied rake task (invoked with 'rake ...') # # rake("db:migrate") # rake("db:migrate", env: "production") # rake("gems:install", sudo: true) def rake(command, options={}) - log :rake, command - env = options[:env] || ENV["RAILS_ENV"] || 'development' - sudo = options[:sudo] && RbConfig::CONFIG['host_os'] !~ /mswin|mingw/ ? 'sudo ' : '' - in_root { run("#{sudo}#{extify(:rake)} #{command} RAILS_ENV=#{env}", verbose: false) } + execute_command :rake, command, options + end + + # Runs the supplied rake task (invoked with 'rails ...') + # + # rails("db:migrate") + # rails("db:migrate", env: "production") + # rails("gems:install", sudo: true) + def rails_command(command, options={}) + execute_command :rails, command, options end # Just run the capify command in root @@ -235,7 +239,7 @@ module Rails sentinel = /\.routes\.draw do\s*\n/m in_root do - inject_into_file 'config/routes.rb', " #{routing_code}\n", { after: sentinel, verbose: false, force: false } + inject_into_file "config/routes.rb", " #{routing_code}\n", after: sentinel, verbose: false, force: false end end @@ -270,9 +274,18 @@ module Rails end end + # Runs the supplied command using either "rake ..." or "rails ..." + # based on the executor parameter provided. + def execute_command(executor, command, options={}) + log executor, command + env = options[:env] || ENV["RAILS_ENV"] || "development" + sudo = options[:sudo] && RbConfig::CONFIG["host_os"] !~ /mswin|mingw/ ? "sudo " : "" + in_root { run("#{sudo}#{extify(executor)} #{command} RAILS_ENV=#{env}", verbose: false) } + end + # Add an extension to the given name based on the platform. def extify(name) - if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ + if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ "#{name}.bat" else name diff --git a/railties/lib/rails/generators/actions/create_migration.rb b/railties/lib/rails/generators/actions/create_migration.rb index d664b07652..587c61fd42 100644 --- a/railties/lib/rails/generators/actions/create_migration.rb +++ b/railties/lib/rails/generators/actions/create_migration.rb @@ -1,10 +1,10 @@ -require 'thor/actions' +require "fileutils" +require "thor/actions" module Rails module Generators module Actions class CreateMigration < Thor::Actions::CreateFile #:nodoc: - def migration_dir File.dirname(@destination) end @@ -39,30 +39,30 @@ module Rails protected - def on_conflict_behavior - options = base.options.merge(config) - if identical? - say_status :identical, :blue, relative_existing_migration - elsif options[:force] - say_status :remove, :green, relative_existing_migration - say_status :create, :green - unless pretend? - ::FileUtils.rm_rf(existing_migration) - yield + def on_conflict_behavior + options = base.options.merge(config) + if identical? + say_status :identical, :blue, relative_existing_migration + elsif options[:force] + say_status :remove, :green, relative_existing_migration + say_status :create, :green + unless pretend? + ::FileUtils.rm_rf(existing_migration) + yield + end + elsif options[:skip] + 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 " + + "or --skip to ignore conflicted file." end - elsif options[:skip] - 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 " + - "or --skip to ignore conflicted file." end - end - def say_status(status, color, message = relative_destination) - base.shell.say_status(status, message, color) if config[:verbose] - end + def say_status(status, color, message = relative_destination) + base.shell.say_status(status, message, color) if config[:verbose] + end end end end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 8f8c2ec9e1..91342c592c 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -1,15 +1,16 @@ -require 'digest/md5' -require 'active_support/core_ext/string/strip' -require 'rails/version' unless defined?(Rails::VERSION) -require 'open-uri' -require 'uri' -require 'rails/generators' -require 'active_support/core_ext/array/extract_options' +require "fileutils" +require "digest/md5" +require "active_support/core_ext/string/strip" +require "rails/version" unless defined?(Rails::VERSION) +require "open-uri" +require "uri" +require "rails/generators" +require "active_support/core_ext/array/extract_options" module Rails module Generators class AppBase < Base # :nodoc: - DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db sqlserver ) + DATABASES = %w( mysql postgresql sqlite3 oracle frontbase ibm_db sqlserver ) JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 jdbcpostgresql jdbc ) DATABASES.concat(JDBC_DATABASES) @@ -23,54 +24,57 @@ module Rails end def self.add_shared_options_for(name) - class_option :template, type: :string, aliases: '-m', + class_option :template, type: :string, aliases: "-m", desc: "Path to some #{name} template (can be a filesystem path or URL)" - class_option :database, type: :string, aliases: '-d', default: 'sqlite3', + class_option :database, type: :string, aliases: "-d", default: "sqlite3", desc: "Preconfigure for selected database (options: #{DATABASES.join('/')})" - class_option :javascript, type: :string, aliases: '-j', default: 'jquery', - desc: 'Preconfigure for selected JavaScript library' + class_option :javascript, type: :string, aliases: "-j", default: "jquery", + desc: "Preconfigure for selected JavaScript library" class_option :skip_gemfile, type: :boolean, default: false, desc: "Don't create a Gemfile" - class_option :skip_bundle, type: :boolean, aliases: '-B', default: false, + 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' + class_option :skip_git, type: :boolean, aliases: "-G", default: false, + desc: "Skip .gitignore file" class_option :skip_keeps, type: :boolean, default: false, - desc: 'Skip source control .keep files' + desc: "Skip source control .keep files" class_option :skip_action_mailer, type: :boolean, aliases: "-M", default: false, desc: "Skip Action Mailer files" - class_option :skip_active_record, type: :boolean, aliases: '-O', default: false, - desc: 'Skip Active Record files' + class_option :skip_active_record, type: :boolean, aliases: "-O", default: false, + desc: "Skip Active Record files" - class_option :skip_puma, type: :boolean, aliases: '-P', default: false, - desc: 'Skip Puma related files' + class_option :skip_puma, type: :boolean, aliases: "-P", default: false, + desc: "Skip Puma related files" - class_option :skip_action_cable, type: :boolean, aliases: '-C', default: false, - desc: 'Skip Action Cable files' + class_option :skip_action_cable, type: :boolean, aliases: "-C", default: false, + desc: "Skip Action Cable files" - class_option :skip_sprockets, type: :boolean, aliases: '-S', default: false, - desc: 'Skip Sprockets files' + class_option :skip_sprockets, type: :boolean, aliases: "-S", default: false, + desc: "Skip Sprockets files" class_option :skip_spring, type: :boolean, default: false, desc: "Don't install Spring application preloader" - class_option :skip_javascript, type: :boolean, aliases: '-J', default: false, - desc: 'Skip JavaScript files' + class_option :skip_listen, type: :boolean, default: false, + desc: "Don't generate configuration that depends on the listen gem" + + class_option :skip_javascript, type: :boolean, aliases: "-J", default: false, + desc: "Skip JavaScript files" class_option :skip_turbolinks, type: :boolean, default: false, - desc: 'Skip turbolinks gem' + desc: "Skip turbolinks gem" - class_option :skip_test, type: :boolean, aliases: '-T', default: false, - desc: 'Skip test files' + class_option :skip_test, type: :boolean, aliases: "-T", default: false, + desc: "Skip test files" class_option :dev, type: :boolean, default: false, desc: "Setup the #{name} with Gemfile pointing to your Rails checkout" @@ -82,10 +86,10 @@ module Rails desc: "Path to file containing extra configuration options for rails command" class_option :no_rc, type: :boolean, default: false, - desc: 'Skip loading of extra configuration options from .railsrc file' + desc: "Skip loading of extra configuration options from .railsrc file" - class_option :help, type: :boolean, aliases: '-h', group: :rails, - desc: 'Show this help message and quit' + class_option :help, type: :boolean, aliases: "-h", group: :rails, + desc: "Show this help message and quit" end def initialize(*args) @@ -146,7 +150,7 @@ module Rails def create_root valid_const? - empty_directory '.' + empty_directory "." FileUtils.cd(destination_root) unless options[:pretend] end @@ -158,14 +162,15 @@ module Rails def set_default_accessors! self.destination_root = File.expand_path(app_path, destination_root) - self.rails_template = case options[:template] + self.rails_template = \ + case options[:template] when /^https?:\/\// options[:template] when String File.expand_path(options[:template], Dir.pwd) else options[:template] - end + end end def database_gemfile_entry @@ -177,8 +182,8 @@ module Rails def webserver_gemfile_entry return [] if options[:skip_puma] - comment = 'Use Puma as the app server' - GemfileEntry.new('puma', nil, comment) + comment = "Use Puma as the app server" + GemfileEntry.new("puma", "~> 3.0", comment) end def include_all_railties? @@ -186,7 +191,7 @@ module Rails end def comment_if(value) - options[value] ? '# ' : '' + options[value] ? "# " : "" end def keeps? @@ -194,7 +199,7 @@ module Rails end def sqlite3? - !options[:skip_active_record] && options[:database] == 'sqlite3' + !options[:skip_active_record] && options[:database] == "sqlite3" end class GemfileEntry < Struct.new(:name, :version, :comment, :options, :commented_out) @@ -234,14 +239,14 @@ module Rails ] if options.dev? [ - GemfileEntry.path('rails', Rails::Generators::RAILS_DEV_PATH) + GemfileEntry.path("rails", Rails::Generators::RAILS_DEV_PATH) ] + dev_edge_common elsif options.edge? [ - GemfileEntry.github('rails', 'rails/rails') + GemfileEntry.github("rails", "rails/rails") ] + dev_edge_common else - [GemfileEntry.version('rails', + [GemfileEntry.version("rails", rails_version_specifier, "Bundle edge Rails instead: gem 'rails', github: 'rails/rails'")] end @@ -262,12 +267,12 @@ module Rails end def gem_for_database - # %w( mysql oracle postgresql sqlite3 frontbase ibm_db sqlserver jdbcmysql jdbcsqlite3 jdbcpostgresql ) + # %w( mysql postgresql sqlite3 oracle frontbase ibm_db sqlserver jdbcmysql jdbcsqlite3 jdbcpostgresql ) case options[:database] - when "oracle" then ["ruby-oci8", nil] + when "mysql" then ["mysql2", [">= 0.3.18", "< 0.5"]] when "postgresql" then ["pg", ["~> 0.18"]] + when "oracle" then ["ruby-oci8", nil] when "frontbase" then ["ruby-frontbase", nil] - when "mysql" then ["mysql2", [">= 0.3.18", "< 0.5"]] when "sqlserver" then ["activerecord-sqlserver-adapter", nil] when "jdbcmysql" then ["activerecord-jdbcmysql-adapter", nil] when "jdbcsqlite3" then ["activerecord-jdbcsqlite3-adapter", nil] @@ -280,9 +285,9 @@ module Rails def convert_database_option_for_jruby if defined?(JRUBY_VERSION) case options[:database] - when "oracle" then options[:database].replace "jdbc" when "postgresql" then options[:database].replace "jdbcpostgresql" when "mysql" then options[:database].replace "jdbcmysql" + when "oracle" then options[:database].replace "jdbc" when "sqlite3" then options[:database].replace "jdbcsqlite3" end end @@ -292,28 +297,25 @@ module Rails return [] if options[:skip_sprockets] gems = [] - gems << GemfileEntry.version('sass-rails', '~> 5.0', - 'Use SCSS for stylesheets') + gems << GemfileEntry.github("sass-rails", "rails/sass-rails", nil, + "Use SCSS for stylesheets") - gems << GemfileEntry.version('uglifier', - '>= 1.3.0', - 'Use Uglifier as compressor for JavaScript assets') + if !options[:skip_javascript] + gems << GemfileEntry.version("uglifier", + ">= 1.3.0", + "Use Uglifier as compressor for JavaScript assets") + end gems end def jbuilder_gemfile_entry - comment = 'Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder' - GemfileEntry.new 'jbuilder', '~> 2.0', comment, {}, options[:api] + comment = "Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder" + GemfileEntry.new "jbuilder", "~> 2.5", comment, {}, options[:api] end def coffee_gemfile_entry - comment = 'Use CoffeeScript for .coffee assets and views' - if options.dev? || options.edge? - GemfileEntry.github 'coffee-rails', 'rails/coffee-rails', nil, comment - else - GemfileEntry.version 'coffee-rails', '~> 4.1.0', comment - end + GemfileEntry.version "coffee-rails", "~> 4.2", "Use CoffeeScript for .coffee assets and views" end def javascript_gemfile_entry @@ -325,8 +327,8 @@ module Rails "Use #{options[:javascript]} as the JavaScript library") unless options[:skip_turbolinks] - gems << GemfileEntry.version("turbolinks", nil, - "Turbolinks makes following links in your web application faster. Read more: https://github.com/turbolinks/turbolinks") + gems << GemfileEntry.version("turbolinks", "~> 5", + "Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks") end gems @@ -334,27 +336,27 @@ module Rails end def javascript_runtime_gemfile_entry - comment = 'See https://github.com/rails/execjs#readme for more supported runtimes' + comment = "See https://github.com/rails/execjs#readme for more supported runtimes" if defined?(JRUBY_VERSION) - GemfileEntry.version 'therubyrhino', nil, comment + GemfileEntry.version "therubyrhino", nil, comment else - GemfileEntry.new 'therubyracer', nil, comment, { platforms: :ruby }, true + GemfileEntry.new "therubyracer", nil, comment, { platforms: :ruby }, true end end def psych_gemfile_entry return [] unless defined?(Rubinius) - comment = 'Use Psych as the YAML engine, instead of Syck, so serialized ' \ - 'data can be read safely from different rubies (see http://git.io/uuLVag)' - GemfileEntry.new('psych', '~> 2.0', comment, platforms: :rbx) + comment = "Use Psych as the YAML engine, instead of Syck, so serialized " \ + "data can be read safely from different rubies (see http://git.io/uuLVag)" + GemfileEntry.new("psych", "~> 2.0", comment, platforms: :rbx) end def cable_gemfile_entry return [] if options[:skip_action_cable] - comment = 'Use Redis adapter to run Action Cable in production' + comment = "Use Redis adapter to run Action Cable in production" gems = [] - gems << GemfileEntry.new("redis", '~> 3.0', comment, {}, true) + gems << GemfileEntry.new("redis", "~> 3.0", comment, {}, true) gems end @@ -369,9 +371,9 @@ module Rails # We unset temporary bundler variables to load proper bundler and Gemfile. # # Thanks to James Tucker for the Gem tricks involved in this call. - _bundle_command = Gem.bin_path('bundler', 'bundle') + _bundle_command = Gem.bin_path("bundler", "bundle") - require 'bundler' + require "bundler" Bundler.with_clean_env do full_command = %Q["#{Gem.ruby}" "#{_bundle_command}" #{command}] if options[:quiet] @@ -390,12 +392,16 @@ module Rails !options[:skip_spring] && !options.dev? && Process.respond_to?(:fork) && !RUBY_PLATFORM.include?("cygwin") end + def depend_on_listen? + !options[:skip_listen] && os_supports_listen_out_of_the_box? + end + def os_supports_listen_out_of_the_box? - RbConfig::CONFIG['host_os'] =~ /darwin|linux/ + RbConfig::CONFIG["host_os"] =~ /darwin|linux/ end def run_bundle - bundle_command('install') if bundle_install? + bundle_command("install") if bundle_install? end def generate_spring_binstubs diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb index c72ec400a0..1a0420c769 100644 --- a/railties/lib/rails/generators/base.rb +++ b/railties/lib/rails/generators/base.rb @@ -1,5 +1,5 @@ begin - require 'thor/group' + require "thor/group" rescue LoadError puts "Thor is not available.\nIf you ran this command from a git checkout " \ "of Rails, please make sure thor is installed,\nand run this command " \ @@ -42,13 +42,13 @@ module Rails # is removed. def self.namespace(name=nil) return super if name - @namespace ||= super.sub(/_generator$/, '').sub(/:generators:/, ':') + @namespace ||= super.sub(/_generator$/, "").sub(/:generators:/, ":") end # Convenience method to hide this generator from the available ones when # running rails generator command. def self.hide! - Rails::Generators.hide_namespace self.namespace + Rails::Generators.hide_namespace(namespace) end # Invoke a generator based on the value supplied by the user to the @@ -168,7 +168,7 @@ module Rails names.each do |name| unless class_options.key?(name) defaults = if options[:type] == :boolean - { } + {} elsif [true, false].include?(default_value_for_option(name, options)) { banner: "" } else @@ -208,7 +208,7 @@ module Rails def self.default_source_root return unless base_name && generator_name return unless default_generator_root - path = File.join(default_generator_root, 'templates') + path = File.join(default_generator_root, "templates") path if File.exist?(path) end @@ -230,7 +230,7 @@ module Rails Rails::Generators.subclasses << base Rails::Generators.templates_path.each do |path| - if base.name.include?('::') + if base.name.include?("::") base.source_paths << File.join(path, base.base_name, base.generator_name) else base.source_paths << File.join(path, base.generator_name) @@ -251,7 +251,7 @@ module Rails next if class_name.strip.empty? # Split the class from its module nesting - nesting = class_name.split('::') + nesting = class_name.split("::") last_name = nesting.pop last = extract_last_module(nesting) @@ -273,13 +273,13 @@ module Rails # Use Rails default banner. def self.banner - "rails generate #{namespace.sub(/^rails:/,'')} #{self.arguments.map(&:usage).join(' ')} [options]".gsub(/\s+/, ' ') + "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 @base_name ||= begin - if base = name.to_s.split('::').first + if base = name.to_s.split("::").first base.underscore end end @@ -289,8 +289,8 @@ module Rails # Rails::Generators::ModelGenerator will return "model" as generator name. def self.generator_name @generator_name ||= begin - if generator = name.to_s.split('::').last - generator.sub!(/Generator$/, '') + if generator = name.to_s.split("::").last + generator.sub!(/Generator$/, "") generator.underscore end end @@ -310,9 +310,9 @@ module Rails # Returns default for the option name given doing a lookup in config. def self.default_for_option(config, name, options, default) - if generator_name and c = config[generator_name.to_sym] and c.key?(name) + if generator_name && (c = config[generator_name.to_sym]) && c.key?(name) c[name] - elsif base_name and c = config[base_name.to_sym] and c.key?(name) + elsif base_name && (c = config[base_name.to_sym]) && c.key?(name) c[name] elsif config[:rails].key?(name) config[:rails][name] @@ -373,7 +373,6 @@ module Rails path = File.expand_path(File.join(base_name, generator_name), base_root) path if File.exist?(path) end - end end end diff --git a/railties/lib/rails/generators/css/assets/assets_generator.rb b/railties/lib/rails/generators/css/assets/assets_generator.rb index e4a305f4b3..20baf31a34 100644 --- a/railties/lib/rails/generators/css/assets/assets_generator.rb +++ b/railties/lib/rails/generators/css/assets/assets_generator.rb @@ -6,7 +6,7 @@ module Css # :nodoc: source_root File.expand_path("../templates", __FILE__) def copy_stylesheet - copy_file "stylesheet.css", File.join('app/assets/stylesheets', class_path, "#{file_name}.css") + copy_file "stylesheet.css", File.join("app/assets/stylesheets", class_path, "#{file_name}.css") end end end diff --git a/railties/lib/rails/generators/erb.rb b/railties/lib/rails/generators/erb.rb index 0755ac335c..d01502002f 100644 --- a/railties/lib/rails/generators/erb.rb +++ b/railties/lib/rails/generators/erb.rb @@ -1,25 +1,25 @@ -require 'rails/generators/named_base' +require "rails/generators/named_base" module Erb # :nodoc: module Generators # :nodoc: class Base < Rails::Generators::NamedBase #:nodoc: protected - def formats - [format] - end + def formats + [format] + end - def format - :html - end + def format + :html + end - def handler - :erb - end + def handler + :erb + end - def filename_with_extensions(name, format = self.format) - [name, format, handler].compact.join(".") - end + def filename_with_extensions(name, format = self.format) + [name, format, handler].compact.join(".") + end end end end diff --git a/railties/lib/rails/generators/erb/controller/controller_generator.rb b/railties/lib/rails/generators/erb/controller/controller_generator.rb index 94c1b835d1..36ecfea09b 100644 --- a/railties/lib/rails/generators/erb/controller/controller_generator.rb +++ b/railties/lib/rails/generators/erb/controller/controller_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/erb' +require "rails/generators/erb" module Erb # :nodoc: module Generators # :nodoc: diff --git a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb index bc249aa5e5..f150240908 100644 --- a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb +++ b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/erb' +require "rails/generators/erb" module Erb # :nodoc: module Generators # :nodoc: @@ -6,9 +6,16 @@ module Erb # :nodoc: argument :actions, type: :array, default: [], banner: "method method" def copy_view_files - view_base_path = File.join("app/views", class_path, file_name + '_mailer') + view_base_path = File.join("app/views", class_path, file_name + "_mailer") empty_directory view_base_path + if self.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 + end + end + actions.each do |action| @action = action @@ -21,13 +28,13 @@ module Erb # :nodoc: protected - def formats - [:text, :html] - end + def formats + [:text, :html] + end - def file_name - @_file_name ||= super.gsub(/\_mailer/i, '') - end + def file_name + @_file_name ||= super.gsub(/_mailer/i, "") + end end end end diff --git a/railties/lib/rails/generators/erb/mailer/templates/layout.html.erb.tt b/railties/lib/rails/generators/erb/mailer/templates/layout.html.erb.tt new file mode 100644 index 0000000000..55f3675d49 --- /dev/null +++ b/railties/lib/rails/generators/erb/mailer/templates/layout.html.erb.tt @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <style> + /* Email styles need to be inline */ + </style> + </head> + + <body> + <%%= yield %> + </body> +</html> diff --git a/railties/lib/rails/generators/erb/mailer/templates/layout.text.erb.tt b/railties/lib/rails/generators/erb/mailer/templates/layout.text.erb.tt new file mode 100644 index 0000000000..6363733e6e --- /dev/null +++ b/railties/lib/rails/generators/erb/mailer/templates/layout.text.erb.tt @@ -0,0 +1 @@ +<%%= yield %> diff --git a/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb index c94829a0ae..154d85f381 100644 --- a/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb @@ -1,5 +1,5 @@ -require 'rails/generators/erb' -require 'rails/generators/resource_helpers' +require "rails/generators/erb" +require "rails/generators/resource_helpers" module Erb # :nodoc: module Generators # :nodoc: diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb index 7e437e7344..61181b7b97 100644 --- a/railties/lib/rails/generators/generated_attribute.rb +++ b/railties/lib/rails/generators/generated_attribute.rb @@ -1,4 +1,4 @@ -require 'active_support/time' +require "active_support/time" module Rails module Generators @@ -12,7 +12,7 @@ module Rails class << self def parse(column_definition) - name, type, has_index = column_definition.split(':') + name, type, has_index = column_definition.split(":") # if user provided "name:index" instead of "name:string:index" # type should be set blank so GeneratedAttribute's constructor @@ -66,40 +66,40 @@ module Rails def field_type @field_type ||= case type - when :integer then :number_field - when :float, :decimal then :text_field - when :time then :time_select - when :datetime, :timestamp then :datetime_select - when :date then :date_select - when :text then :text_area - when :boolean then :check_box + when :integer then :number_field + when :float, :decimal then :text_field + when :time then :time_select + when :datetime, :timestamp then :datetime_select + when :date then :date_select + when :text then :text_area + when :boolean then :check_box else - :text_field + :text_field end end def default @default ||= case type - when :integer then 1 - when :float then 1.5 - when :decimal then "9.99" - when :datetime, :timestamp, :time then Time.now.to_s(:db) - when :date then Date.today.to_s(:db) - when :string then name == "type" ? "" : "MyString" - when :text then "MyText" - when :boolean then false - when :references, :belongs_to then nil + when :integer then 1 + when :float then 1.5 + when :decimal then "9.99" + when :datetime, :timestamp, :time then Time.now.to_s(:db) + when :date then Date.today.to_s(:db) + when :string then name == "type" ? "" : "MyString" + when :text then "MyText" + when :boolean then false + when :references, :belongs_to then nil else - "" + "" end end def plural_name - name.sub(/_id$/, '').pluralize + name.sub(/_id$/, "").pluralize end def singular_name - name.sub(/_id$/, '').singularize + name.sub(/_id$/, "").singularize end def human_name @@ -127,11 +127,11 @@ module Rails end def polymorphic? - self.attr_options[:polymorphic] + attr_options[:polymorphic] end def required? - self.attr_options[:required] + attr_options[:required] end def has_index? @@ -143,7 +143,7 @@ module Rails end def password_digest? - name == 'password' && type == :digest + name == "password" && type == :digest end def token? diff --git a/railties/lib/rails/generators/js/assets/assets_generator.rb b/railties/lib/rails/generators/js/assets/assets_generator.rb index 1e925b2cd2..64d706ec91 100644 --- a/railties/lib/rails/generators/js/assets/assets_generator.rb +++ b/railties/lib/rails/generators/js/assets/assets_generator.rb @@ -6,7 +6,7 @@ module Js # :nodoc: source_root File.expand_path("../templates", __FILE__) def copy_javascript - copy_file "javascript.js", File.join('app/assets/javascripts', class_path, "#{file_name}.js") + copy_file "javascript.js", File.join("app/assets/javascripts", class_path, "#{file_name}.js") end end end diff --git a/railties/lib/rails/generators/migration.rb b/railties/lib/rails/generators/migration.rb index 87f2e1d42b..7290e235a1 100644 --- a/railties/lib/rails/generators/migration.rb +++ b/railties/lib/rails/generators/migration.rb @@ -1,5 +1,5 @@ -require 'active_support/concern' -require 'rails/generators/actions/create_migration' +require "active_support/concern" +require "rails/generators/actions/create_migration" module Rails module Generators @@ -39,7 +39,7 @@ module Rails migration_dir = File.dirname(destination) @migration_number = self.class.next_migration_number(migration_dir) - @migration_file_name = File.basename(destination, '.rb') + @migration_file_name = File.basename(destination, ".rb") @migration_class_name = @migration_file_name.camelize end @@ -55,13 +55,13 @@ module Rails source = File.expand_path(find_in_source_paths(source.to_s)) set_migration_assigns!(destination) - context = instance_eval('binding') + context = instance_eval("binding") dir, base = File.split(destination) - numbered_destination = File.join(dir, ["%migration_number%", base].join('_')) + numbered_destination = File.join(dir, ["%migration_number%", base].join("_")) create_migration numbered_destination, nil, config do - ERB.new(::File.binread(source), nil, '-', '@output_buffer').result(context) + ERB.new(::File.binread(source), nil, "-", "@output_buffer").result(context) end end end diff --git a/railties/lib/rails/generators/model_helpers.rb b/railties/lib/rails/generators/model_helpers.rb index 42c646543e..6f87a18660 100644 --- a/railties/lib/rails/generators/model_helpers.rb +++ b/railties/lib/rails/generators/model_helpers.rb @@ -1,4 +1,4 @@ -require 'rails/generators/active_model' +require "rails/generators/active_model" module Rails module Generators @@ -8,7 +8,7 @@ module Rails mattr_accessor :skip_warn def self.included(base) #:nodoc: - base.class_option :force_plural, type: :boolean, default: false, desc: 'Forces the use of the given model name' + base.class_option :force_plural, type: :boolean, default: false, desc: "Forces the use of the given model name" end def initialize(args, *_options) diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index efbf51ddfb..c39ea24935 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -1,6 +1,6 @@ -require 'active_support/core_ext/module/introspection' -require 'rails/generators/base' -require 'rails/generators/generated_attribute' +require "active_support/core_ext/module/introspection" +require "rails/generators/base" +require "rails/generators/generated_attribute" module Rails module Generators @@ -14,7 +14,7 @@ module Rails # Unfreeze name in case it's given as a frozen string args[0] = args[0].dup if args[0].is_a?(String) && args[0].frozen? super - assign_names!(self.name) + assign_names!(name) parse_attributes! if respond_to?(:attributes) end @@ -26,6 +26,10 @@ module Rails super end end + + def js_template(source, destination) + template(source + ".js", destination + ".js") + end end protected @@ -47,7 +51,7 @@ module Rails def indent(content, multiplier = 2) spaces = " " * multiplier - content.each_line.map {|line| line.blank? ? line : "#{spaces}#{line}" }.join + content.each_line.map { |line| line.blank? ? line : "#{spaces}#{line}" }.join end def wrap_with_namespace(content) @@ -75,7 +79,7 @@ module Rails end def file_path - @file_path ||= (class_path + [file_name]).join('/') + @file_path ||= (class_path + [file_name]).join("/") end def class_path @@ -99,7 +103,7 @@ module Rails end def class_name - (class_path + [file_name]).map!(&:camelize).join('::') + (class_path + [file_name]).map!(&:camelize).join("::") end def human_name @@ -111,13 +115,13 @@ module Rails end def i18n_scope - @i18n_scope ||= file_path.tr('/', '.') + @i18n_scope ||= file_path.tr("/", ".") end def table_name @table_name ||= begin base = pluralize_table_names? ? plural_name : singular_name - (class_path + [base]).join('_') + (class_path + [base]).join("_") end end @@ -158,24 +162,24 @@ module Rails end def route_url - @route_url ||= class_path.collect {|dname| "/" + dname }.join + "/" + plural_file_name + @route_url ||= class_path.collect { |dname| "/" + dname }.join + "/" + plural_file_name end def url_helper_prefix - @url_helper_prefix ||= (class_path + [file_name]).join('_') + @url_helper_prefix ||= (class_path + [file_name]).join("_") end # Tries to retrieve the application name or simply return application. def application_name if defined?(Rails) && Rails.application - Rails.application.class.name.split('::').first.underscore + Rails.application.class.name.split("::").first.underscore else "application" end end def assign_names!(name) #:nodoc: - @class_path = name.include?('/') ? name.split('/') : name.split('::') + @class_path = name.include?("/") ? name.split("/") : name.split("::") @class_path.map!(&:underscore) @file_name = @class_path.pop end @@ -190,7 +194,7 @@ module Rails def attributes_names @attributes_names ||= attributes.each_with_object([]) do |a, names| names << a.column_name - names << 'password_confirmation' if a.password_digest? + names << "password_confirmation" if a.password_digest? names << "#{a.name}_type" if a.polymorphic? end end diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 248ad20019..03573b274d 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/app_base' +require "rails/generators/app_base" module Rails module ActionMethods # :nodoc: @@ -54,13 +54,13 @@ module Rails end def app - directory 'app' + directory "app" - keep_file 'app/assets/images' - empty_directory_with_keep_file 'app/assets/javascripts/channels' unless options[:skip_action_cable] + keep_file "app/assets/images" + empty_directory_with_keep_file "app/assets/javascripts/channels" unless options[:skip_action_cable] - keep_file 'app/controllers/concerns' - keep_file 'app/models/concerns' + keep_file "app/controllers/concerns" + keep_file "app/models/concerns" end def bin @@ -80,6 +80,7 @@ module Rails template "secrets.yml" template "cable.yml" unless options[:skip_action_cable] template "puma.rb" unless options[:skip_puma] + template "spring.rb" if spring_install? directory "environments" directory "initializers" @@ -88,22 +89,24 @@ module Rails end def config_when_updating - cookie_serializer_config_exist = File.exist?('config/initializers/cookies_serializer.rb') - callback_terminator_config_exist = File.exist?('config/initializers/callback_terminator.rb') - active_record_belongs_to_required_by_default_config_exist = File.exist?('config/initializers/active_record_belongs_to_required_by_default.rb') + cookie_serializer_config_exist = File.exist?("config/initializers/cookies_serializer.rb") + action_cable_config_exist = File.exist?("config/cable.yml") + rack_cors_config_exist = File.exist?("config/initializers/cors.rb") config - unless callback_terminator_config_exist - remove_file 'config/initializers/callback_terminator.rb' - end + gsub_file "config/environments/development.rb", /^(\s+)config\.file_watcher/, '\1# config.file_watcher' unless cookie_serializer_config_exist - gsub_file 'config/initializers/cookies_serializer.rb', /json/, 'marshal' + gsub_file "config/initializers/cookies_serializer.rb", /json(?!,)/, "marshal" + end + + unless action_cable_config_exist + template "config/cable.yml" end - unless active_record_belongs_to_required_by_default_config_exist - remove_file 'config/initializers/active_record_belongs_to_required_by_default.rb' + unless rack_cors_config_exist + remove_file "config/initializers/cors.rb" end end @@ -116,13 +119,13 @@ module Rails end def lib - empty_directory 'lib' - empty_directory_with_keep_file 'lib/tasks' - empty_directory_with_keep_file 'lib/assets' + empty_directory "lib" + empty_directory_with_keep_file "lib/tasks" + empty_directory_with_keep_file "lib/assets" end def log - empty_directory_with_keep_file 'log' + empty_directory_with_keep_file "log" end def public_directory @@ -130,15 +133,15 @@ module Rails end def test - empty_directory_with_keep_file 'test/fixtures' - empty_directory_with_keep_file 'test/fixtures/files' - empty_directory_with_keep_file 'test/controllers' - empty_directory_with_keep_file 'test/mailers' - empty_directory_with_keep_file 'test/models' - empty_directory_with_keep_file 'test/helpers' - empty_directory_with_keep_file 'test/integration' - - template 'test/test_helper.rb' + empty_directory_with_keep_file "test/fixtures" + empty_directory_with_keep_file "test/fixtures/files" + empty_directory_with_keep_file "test/controllers" + empty_directory_with_keep_file "test/mailers" + empty_directory_with_keep_file "test/models" + empty_directory_with_keep_file "test/helpers" + empty_directory_with_keep_file "test/integration" + + template "test/test_helper.rb" end def tmp @@ -154,12 +157,12 @@ module Rails def vendor_javascripts unless options[:skip_javascript] - empty_directory_with_keep_file 'vendor/assets/javascripts' + empty_directory_with_keep_file "vendor/assets/javascripts" end end def vendor_stylesheets - empty_directory_with_keep_file 'vendor/assets/stylesheets' + empty_directory_with_keep_file "vendor/assets/stylesheets" end end @@ -223,6 +226,11 @@ module Rails end remove_task :update_config_files + def display_upgrade_guide_info + say "\nAfter this, check Rails upgrade guide at http://guides.rubyonrails.org/upgrading_ruby_on_rails.html for more details about upgrading your app." + end + remove_task :display_upgrade_guide_info + def create_boot_file template "config/boot.rb" end @@ -233,6 +241,7 @@ module Rails end def create_db_files + return if options[:skip_active_record] build(:db) end @@ -262,78 +271,82 @@ module Rails def delete_app_assets_if_api_option if options[:api] - remove_dir 'app/assets' - remove_dir 'lib/assets' - remove_dir 'tmp/cache/assets' - remove_dir 'vendor/assets' + remove_dir "app/assets" + remove_dir "lib/assets" + remove_dir "tmp/cache/assets" + remove_dir "vendor/assets" end end def delete_app_helpers_if_api_option if options[:api] - remove_dir 'app/helpers' - remove_dir 'test/helpers' + remove_dir "app/helpers" + remove_dir "test/helpers" + end + end + + def delete_application_layout_file_if_api_option + if options[:api] + remove_file "app/views/layouts/application.html.erb" end end - def delete_app_views_if_api_option + def delete_public_files_if_api_option if options[:api] - remove_dir 'app/views' + remove_file "public/404.html" + remove_file "public/422.html" + remove_file "public/500.html" + remove_file "public/apple-touch-icon-precomposed.png" + remove_file "public/apple-touch-icon.png" + remove_file "public/favicon.ico" end end def delete_js_folder_skipping_javascript if options[:skip_javascript] - remove_dir 'app/assets/javascripts' + remove_dir "app/assets/javascripts" end end def delete_assets_initializer_skipping_sprockets if options[:skip_sprockets] - remove_file 'config/initializers/assets.rb' + remove_file "config/initializers/assets.rb" end end def delete_application_record_skipping_active_record if options[:skip_active_record] - remove_file 'app/models/application_record.rb' + remove_file "app/models/application_record.rb" end end 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' - end - end - - def delete_active_record_initializers_skipping_active_record - if options[:skip_active_record] - remove_file 'config/initializers/active_record_belongs_to_required_by_default.rb' + 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" + remove_dir "test/mailers" end end def delete_action_cable_files_skipping_action_cable if options[:skip_action_cable] - remove_file 'config/cable.yml' - remove_file 'app/assets/javascripts/cable.coffee' - remove_dir 'app/channels' + remove_file "config/cable.yml" + remove_file "app/assets/javascripts/cable.js" + remove_dir "app/channels" end end def delete_non_api_initializers_if_api_option if options[:api] - remove_file 'config/initializers/session_store.rb' - remove_file 'config/initializers/cookies_serializer.rb' - remove_file 'config/initializers/request_forgery_protection.rb' - remove_file 'config/initializers/per_form_csrf_tokens.rb' + remove_file "config/initializers/cookies_serializer.rb" end end def delete_api_initializers unless options[:api] - remove_file 'config/initializers/cors.rb' + remove_file "config/initializers/cors.rb" end end @@ -351,7 +364,7 @@ module Rails protected def self.banner - "rails new #{self.arguments.map(&:usage).join(' ')} [options]" + "rails new #{arguments.map(&:usage).join(' ')} [options]" end # Define file as an alias to create_file for backwards compatibility. @@ -360,7 +373,7 @@ module Rails end def app_name - @app_name ||= (defined_app_const_base? ? defined_app_name : File.basename(destination_root)).tr('\\', '').tr(". ", "_") + @app_name ||= (defined_app_const_base? ? defined_app_name : File.basename(destination_root)).tr('\\', "").tr(". ", "_") end def defined_app_name @@ -375,7 +388,7 @@ module Rails alias :defined_app_const_base? :defined_app_const_base def app_const_base - @app_const_base ||= defined_app_const_base || app_name.gsub(/\W/, '_').squeeze('_').camelize + @app_const_base ||= defined_app_const_base || app_name.gsub(/\W/, "_").squeeze("_").camelize end alias :camelized :app_const_base @@ -410,7 +423,7 @@ module Rails "/opt/local/var/run/mysql4/mysqld.sock", # mac + darwinports + mysql4 "/opt/local/var/run/mysql5/mysqld.sock", # mac + darwinports + mysql5 "/opt/lampp/var/mysql/mysql.sock" # xampp for linux - ].find { |f| File.exist?(f) } unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ + ].find { |f| File.exist?(f) } unless RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ end def get_builder_class @@ -438,14 +451,14 @@ module Rails end def self.default_rc_file - File.expand_path('~/.railsrc') + File.expand_path("~/.railsrc") end private def handle_version_request!(argument) - if ['--version', '-v'].include?(argument) - require 'rails/version' + if ["--version", "-v"].include?(argument) + require "rails/version" puts "Rails #{Rails::VERSION::STRING}" exit(0) end @@ -455,20 +468,20 @@ module Rails if argument == "new" yield else - ['--help'] + argv.drop(1) + ["--help"] + argv.drop(1) end end def handle_rails_rc!(argv) - if argv.find { |arg| arg == '--no-rc' } - argv.reject { |arg| arg == '--no-rc' } + if argv.find { |arg| arg == "--no-rc" } + argv.reject { |arg| arg == "--no-rc" } else railsrc(argv) { |rc_argv, rc| insert_railsrc_into_argv!(rc_argv, rc) } end end def railsrc(argv) - if (customrc = argv.index{ |x| x.include?("--rc=") }) + if (customrc = argv.index { |x| x.include?("--rc=") }) fname = File.expand_path(argv[customrc].gsub(/--rc=/, "")) yield(argv.take(customrc) + argv.drop(customrc + 1), fname) else diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index c3fad31f23..422217286c 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -26,25 +26,25 @@ source 'https://rubygems.org' <% if RUBY_ENGINE == 'ruby' -%> group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console - gem 'byebug' + gem 'byebug', platform: :mri end group :development do <%- unless options.api? -%> - # Access an IRB console on exception pages or by using <%%= console %> in views + # Access an IRB console on exception pages or by using <%%= console %> anywhere in the code. <%- if options.dev? || options.edge? -%> gem 'web-console', github: 'rails/web-console' <%- else -%> - gem 'web-console', '~> 3.0' + gem 'web-console', '>= 3.3.0' <%- end -%> <%- end -%> -<% if os_supports_listen_out_of_the_box? -%> - gem 'listen', '~> 3.0.5' +<% if depend_on_listen? -%> + gem 'listen', '>= 3.0.5', '< 3.2' <% end -%> <% if spring_install? -%> # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' -<% if os_supports_listen_out_of_the_box? -%> +<% if depend_on_listen? -%> gem 'spring-watcher-listen', '~> 2.0.0' <% end -%> <% end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/README.md b/railties/lib/rails/generators/rails/app/templates/README.md index 55e144da18..7db80e4ca1 100644 --- a/railties/lib/rails/generators/rails/app/templates/README.md +++ b/railties/lib/rails/generators/rails/app/templates/README.md @@ -1,4 +1,4 @@ -## README +# README This README would normally document whatever steps are necessary to get the application up and running. diff --git a/railties/lib/rails/generators/rails/app/templates/Rakefile b/railties/lib/rails/generators/rails/app/templates/Rakefile index ba6b733dd2..e85f913914 100644 --- a/railties/lib/rails/generators/rails/app/templates/Rakefile +++ b/railties/lib/rails/generators/rails/app/templates/Rakefile @@ -1,6 +1,6 @@ # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. -require File.expand_path('../config/application', __FILE__) +require_relative 'config/application' Rails.application.load_tasks diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/config/manifest.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/config/manifest.js.tt index f4ee1409af..70b579d10e 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/assets/config/manifest.js.tt +++ b/railties/lib/rails/generators/rails/app/templates/app/assets/config/manifest.js.tt @@ -1,6 +1,4 @@ -<% unless options.api? -%> //= link_tree ../images -<% end -%> <% unless options.skip_javascript -%> //= link_directory ../javascripts .js <% end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee deleted file mode 100644 index 07934d026f..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee +++ /dev/null @@ -1,11 +0,0 @@ -# Action Cable provides the framework to deal with WebSockets in Rails. -# You can generate new channels where WebSocket features live using the rails generate channel command. -# -# Turn on the cable connection by removing the comments after the require statements (and ensure it's also on in config/routes.rb). -# -#= require action_cable -#= require_self -#= require_tree ./channels -# -# @App ||= {} -# App.cable = ActionCable.createConsumer() diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js new file mode 100644 index 0000000000..739aa5f022 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js @@ -0,0 +1,13 @@ +// Action Cable provides the framework to deal with WebSockets in Rails. +// You can generate new channels where WebSocket features live using the `rails generate channel` command. +// +//= require action_cable +//= require_self +//= require_tree ./channels + +(function() { + this.App || (this.App = {}); + + App.cable = ActionCable.createConsumer(); + +}).call(this); diff --git a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb index d56fa30f4d..d672697283 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb +++ b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb @@ -1,4 +1,3 @@ -# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading. module ApplicationCable class Channel < ActionCable::Channel::Base end diff --git a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb index b4f41389ad..0ff5442f47 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb +++ b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb @@ -1,4 +1,3 @@ -# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading. module ApplicationCable class Connection < ActionCable::Connection::Base end diff --git a/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt index f726fd6305..413354186d 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt @@ -1,7 +1,5 @@ class ApplicationController < ActionController::<%= options[:api] ? "API" : "Base" %> <%- unless options[:api] -%> - # Prevent CSRF attacks by raising an exception. - # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception <%- end -%> end diff --git a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt index 68b5c051b2..d51f79bd49 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt +++ b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt @@ -3,16 +3,13 @@ <head> <title><%= camelized %></title> <%%= csrf_meta_tags %> - <%- unless options[:skip_action_cable] -%> - <%%= action_cable_meta_tag %> - <%- end -%> <%- if options[:skip_javascript] -%> <%%= stylesheet_link_tag 'application', media: 'all' %> <%- else -%> <%- if gemfile_entries.any? { |m| m.name == 'turbolinks' } -%> - <%%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> - <%%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> + <%%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> + <%%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> <%- else -%> <%%= stylesheet_link_tag 'application', media: 'all' %> <%%= javascript_include_tag 'application' %> diff --git a/railties/lib/rails/generators/rails/app/templates/bin/rails b/railties/lib/rails/generators/rails/app/templates/bin/rails index 80ec8080ab..513a2e0183 100644 --- a/railties/lib/rails/generators/rails/app/templates/bin/rails +++ b/railties/lib/rails/generators/rails/app/templates/bin/rails @@ -1,3 +1,3 @@ -APP_PATH = File.expand_path('../../config/application', __FILE__) +APP_PATH = File.expand_path('../config/application', __dir__) require_relative '../config/boot' require 'rails/commands' diff --git a/railties/lib/rails/generators/rails/app/templates/bin/setup b/railties/lib/rails/generators/rails/app/templates/bin/setup index df88bfd3bc..acae810c1a 100644 --- a/railties/lib/rails/generators/rails/app/templates/bin/setup +++ b/railties/lib/rails/generators/rails/app/templates/bin/setup @@ -15,7 +15,7 @@ chdir APP_ROOT do puts '== Installing dependencies ==' system! 'gem install bundler --conservative' - system('bundle check') or system!('bundle install') + system('bundle check') || system!('bundle install') # puts "\n== Copying sample files ==" # unless File.exist?('config/database.yml') diff --git a/railties/lib/rails/generators/rails/app/templates/bin/update b/railties/lib/rails/generators/rails/app/templates/bin/update index c6ed3ae64b..770a605fed 100644 --- a/railties/lib/rails/generators/rails/app/templates/bin/update +++ b/railties/lib/rails/generators/rails/app/templates/bin/update @@ -15,7 +15,7 @@ chdir APP_ROOT do puts '== Installing dependencies ==' system! 'gem install bundler --conservative' - system 'bundle check' or system! 'bundle install' + system('bundle check') || system!('bundle install') puts "\n== Updating database ==" system! 'bin/rails db:migrate' diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru b/railties/lib/rails/generators/rails/app/templates/config.ru new file mode 100644 index 0000000000..f7ba0b527b --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config.ru @@ -0,0 +1,5 @@ +# This file is used by Rack-based servers to start the application. + +require_relative 'config/environment' + +run Rails.application diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru.tt b/railties/lib/rails/generators/rails/app/templates/config.ru.tt deleted file mode 100644 index 343c0833d7..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/config.ru.tt +++ /dev/null @@ -1,10 +0,0 @@ -# This file is used by Rack-based servers to start the application. - -require ::File.expand_path('../config/environment', __FILE__) -<%- unless options[:skip_action_cable] -%> - -# Action Cable requires that all classes are loaded in advance -Rails.application.eager_load! -<%- end -%> - -run Rails.application diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index cb83364360..c0a0bd0a3e 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -1,4 +1,4 @@ -require File.expand_path('../boot', __FILE__) +require_relative 'boot' <% if include_all_railties? -%> require 'rails/all' diff --git a/railties/lib/rails/generators/rails/app/templates/config/boot.rb b/railties/lib/rails/generators/rails/app/templates/config/boot.rb index 6b750f00b1..30f5120df6 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/boot.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/boot.rb @@ -1,3 +1,3 @@ -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. 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 aa4e832748..0bbde6f74f 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/cable.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/cable.yml @@ -1,10 +1,9 @@ -# Action Cable uses Redis by default to administer connections, channels, and sending/receiving messages over the WebSocket. -production: - adapter: redis - url: redis://localhost:6379/1 - development: adapter: async test: adapter: async + +production: + adapter: redis + url: redis://localhost:6379/1 diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml index 34fc0e3465..917b52e535 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml @@ -8,6 +8,7 @@ # default: &default adapter: frontbase + pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> host: localhost username: <%= app_name %> password: '' diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml index 187ff01bac..d40117a27f 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml @@ -34,6 +34,7 @@ # default: &default adapter: ibm_db + pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: db2inst1 password: #schema: db2inst1 diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml index db0a429753..563be77710 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml @@ -38,6 +38,7 @@ default: &default adapter: jdbc + pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: <%= app_name %> password: driver: 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 f2c4922e7d..a2b2a64ba6 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 @@ -11,6 +11,7 @@ # default: &default adapter: mysql + pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: host: localhost diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml index 80ceb9df92..70df04079d 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml @@ -6,6 +6,7 @@ default: &default adapter: postgresql encoding: unicode + pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> development: <<: *default diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml index 28c36eb82f..371415e6a8 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml @@ -6,6 +6,7 @@ # default: &default adapter: sqlite3 + pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> development: <<: *default 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 193423e84a..d987cf303b 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 @@ -12,7 +12,7 @@ default: &default adapter: mysql2 encoding: utf8 - pool: 5 + pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: <% if mysql_socket -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml index 9aedcc15cb..d2499ea4fb 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml @@ -18,6 +18,7 @@ # default: &default adapter: oracle + pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: <%= app_name %> password: diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml index bd5c0b10f6..145cfb7f74 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml @@ -17,7 +17,7 @@ default: &default adapter: postgresql encoding: unicode - # For details on connection pooling, see rails configuration guide + # For details on connection pooling, see Rails configuration guide # http://guides.rubyonrails.org/configuring.html#database-pooling pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml index 1c1a37ca8d..9510568124 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml @@ -6,7 +6,7 @@ # default: &default adapter: sqlite3 - pool: 5 + pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> timeout: 5000 development: 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 30b0df34a8..c223d6bc62 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 @@ -25,6 +25,7 @@ default: &default adapter: sqlserver encoding: utf8 + pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> reconnect: false username: <%= app_name %> password: diff --git a/railties/lib/rails/generators/rails/app/templates/config/environment.rb b/railties/lib/rails/generators/rails/app/templates/config/environment.rb index ee8d90dc65..426333bb46 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environment.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/environment.rb @@ -1,5 +1,5 @@ # Load the Rails application. -require File.expand_path('../application', __FILE__) +require_relative 'application' # Initialize the Rails application. Rails.application.initialize! diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt index 3451ade158..511b4a82eb 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -15,18 +15,22 @@ Rails.application.configure do # Enable/disable caching. By default caching is disabled. if Rails.root.join('tmp/caching-dev.txt').exist? config.action_controller.perform_caching = true + config.cache_store = :memory_store config.public_file_server.headers = { - 'Cache-Control' => 'public, max-age=172800' + 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}" } else config.action_controller.perform_caching = false + config.cache_store = :null_store end <%- unless options.skip_action_mailer? -%> # Don't care if the mailer can't send. config.action_mailer.raise_delivery_errors = false + + config.action_mailer.perform_caching = false <%- end -%> # Print deprecation notices to the Rails logger. @@ -43,14 +47,8 @@ Rails.application.configure do # number of complex assets. config.assets.debug = true - # Asset digests allow you to set far-future HTTP expiration dates on all assets, - # yet still be able to expire them through the digest params. - config.assets.digest = true - - # Adds additional error checking when serving assets at runtime. - # Checks for improperly declared sprockets dependencies. - # Raises helpful error messages. - config.assets.raise_runtime_errors = true + # Suppress logger output for asset requests. + config.assets.quiet = true <%- end -%> # Raises error for missing translations @@ -58,5 +56,5 @@ Rails.application.configure do # Use an evented file watcher to asynchronously detect changes in source code, # routes, locales, etc. This feature depends on the listen gem. - <%= '# ' unless os_supports_listen_out_of_the_box? %>config.file_watcher = ActiveSupport::EventedFileUpdateChecker + <%= '# ' unless depend_on_listen? %>config.file_watcher = ActiveSupport::EventedFileUpdateChecker end 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 82509f5ef5..7deab5dbb1 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 @@ -19,17 +19,17 @@ Rails.application.configure do config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? <%- unless options.skip_sprockets? -%> + <%- if options.skip_javascript? -%> + # Compress CSS. + <%- else -%> # Compress JavaScripts and CSS. config.assets.js_compressor = :uglifier + <%- end -%> # config.assets.css_compressor = :sass # Do not fallback to assets pipeline if a precompiled asset is missed. config.assets.compile = false - # Asset digests allow you to set far-future HTTP expiration dates on all assets, - # yet still be able to expire them through the digest params. - config.assets.digest = true - # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb <%- end -%> @@ -41,7 +41,8 @@ Rails.application.configure do # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX <%- unless options[:skip_action_cable] -%> - # Action Cable endpoint configuration + # Mount Action Cable outside main process or domain + # config.action_cable.mount_path = nil # config.action_cable.url = 'wss://example.com/cable' # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] <%- end -%> @@ -56,10 +57,6 @@ Rails.application.configure do # Prepend all log lines with the following tags. config.log_tags = [ :request_id ] - # Use a different logger for distributed setups. - # require 'syslog/logger' - # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') - # Use a different cache store in production. # config.cache_store = :mem_cache_store @@ -67,6 +64,7 @@ Rails.application.configure do # config.active_job.queue_adapter = :resque # config.active_job.queue_name_prefix = "<%= app_name %>_#{Rails.env}" <%- unless options.skip_action_mailer? -%> + config.action_mailer.perform_caching = false # Ignore bad email addresses and do not raise email delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors. @@ -82,6 +80,16 @@ Rails.application.configure do # Use default logging formatter so that PID and timestamp are not suppressed. config.log_formatter = ::Logger::Formatter.new + + # Use a different logger for distributed setups. + # require 'syslog/logger' + # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') + + if ENV["RAILS_LOG_TO_STDOUT"].present? + logger = ActiveSupport::Logger.new(STDOUT) + logger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(logger) + end <%- unless options.skip_active_record? -%> # Do not dump schema after migrations. diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt index 8133917591..56416b3075 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt @@ -15,7 +15,7 @@ Rails.application.configure do # Configure public file server for tests with Cache-Control for performance. config.public_file_server.enabled = true config.public_file_server.headers = { - 'Cache-Control' => 'public, max-age=3600' + 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}" } # Show full error reports and disable caching. @@ -28,6 +28,7 @@ Rails.application.configure do # Disable request forgery protection in test environment. config.action_controller.allow_forgery_protection = false <%- unless options.skip_action_mailer? -%> + config.action_mailer.perform_caching = false # Tell Action Mailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the @@ -35,9 +36,6 @@ Rails.application.configure do config.action_mailer.delivery_method = :test <%- end -%> - # Randomize the order test cases are executed. - config.active_support.test_order = :random - # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb deleted file mode 100644 index f613b40f80..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb +++ /dev/null @@ -1,6 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# Require `belongs_to` associations by default. This is a new Rails 5.0 -# default, so it is introduced as a configuration option to ensure that apps -# made on earlier versions of Rails are not affected when upgrading. -Rails.application.config.active_record.belongs_to_required_by_default = true 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 01ef3e6630..2318cf59ff 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 @@ -7,5 +7,6 @@ Rails.application.config.assets.version = '1.0' # Rails.application.config.assets.paths << Emoji.images_path # Precompile additional assets. -# application.js, application.css, and all non-JS/CSS in app/assets folder are already added. -# Rails.application.config.assets.precompile += %w( search.js ) +# application.js, application.css, and all non-JS/CSS in the app/assets +# folder are already added. +# Rails.application.config.assets.precompile += %w( admin.js admin.css ) diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb deleted file mode 100644 index 649e82280e..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb +++ /dev/null @@ -1,6 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# Do not halt callback chains when a callback returns false. This is a new -# Rails 5.0 default, so it is introduced as a configuration option to ensure -# that apps made with earlier versions of Rails are not affected when upgrading. -ActiveSupport.halt_callback_chains_on_return_false = false 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 new file mode 100644 index 0000000000..3ad3eba98a --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt @@ -0,0 +1,40 @@ +# Be sure to restart your server when you modify this file. +# +# This file contains migration options to ease your Rails 5.0 upgrade. +# +<%- if options[:update] -%> +# Once upgraded flip defaults one by one to migrate to the new default. +# +<%- end -%> +# Read the Guide for Upgrading Ruby on Rails for more info on each option. +<%- unless options[:api] -%> + +# Enable per-form CSRF tokens. Previous versions had false. +Rails.application.config.action_controller.per_form_csrf_tokens = <%= options[:update] ? false : true %> + +# Enable origin-checking CSRF mitigation. Previous versions had false. +Rails.application.config.action_controller.forgery_protection_origin_check = <%= options[:update] ? false : true %> +<%- end -%> + +# Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`. +# Previous versions had false. +ActiveSupport.to_time_preserves_timezone = <%= options[:update] ? false : true %> +<%- unless options[:skip_active_record] -%> + +# 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. +Rails.application.config.ssl_options = { hsts: { subdomains: true } } +<%- end -%> +<%- unless options[:skip_sprockets] -%> + +# Unknown asset fallback will return the path passed in when the given +# asset is not present in the asset pipeline. +Rails.application.config.assets.unknown_asset_fallback = <%= options[:update] ? true : false %> +<%- end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/per_form_csrf_tokens.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/per_form_csrf_tokens.rb deleted file mode 100644 index 1f569dedfd..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/per_form_csrf_tokens.rb +++ /dev/null @@ -1,4 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# Enable per-form CSRF tokens. -Rails.application.config.action_controller.per_form_csrf_tokens = true diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/request_forgery_protection.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/request_forgery_protection.rb deleted file mode 100644 index 3eab78a885..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/request_forgery_protection.rb +++ /dev/null @@ -1,4 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# Enable origin-checking CSRF mitigation. -Rails.application.config.action_controller.forgery_protection_origin_check = true diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt deleted file mode 100644 index 2bb9b82c61..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt +++ /dev/null @@ -1,3 +0,0 @@ -# Be sure to restart your server when you modify this file. - -Rails.application.config.session_store :cookie_store, key: <%= "'_#{app_name}_session'" %> 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 1bf274bc66..7ee948002e 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/puma.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/puma.rb @@ -4,7 +4,7 @@ # 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. # -threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i +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. @@ -32,6 +32,14 @@ environment ENV.fetch("RAILS_ENV") { "development" } # # preload_app! +# If you are preloading your application and using Active Record, it's +# recommended that you close any connections to the database before workers +# are forked to prevent connection leakage. +# +# before_fork do +# ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord) +# end + # 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!` @@ -42,3 +50,7 @@ environment ENV.fetch("RAILS_ENV") { "development" } # on_worker_boot do # ActiveRecord::Base.establish_connection if defined?(ActiveRecord) # end +# + +# Allow puma to be restarted by `rails restart` command. +plugin :tmp_restart diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb index 8293c8a483..787824f888 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb @@ -1,6 +1,3 @@ Rails.application.routes.draw do # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html - - # Serve websocket cable requests in-process - # mount ActionCable.server => '/cable' end diff --git a/railties/lib/rails/generators/rails/app/templates/config/secrets.yml b/railties/lib/rails/generators/rails/app/templates/config/secrets.yml index cdea2fd060..8e995a5df1 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/secrets.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/secrets.yml @@ -10,6 +10,13 @@ # Make sure the secrets in this file are kept private # if you're sharing your code publicly. +# Shared secrets are available across all environments. + +shared: + api_key: 123 + +# Environmental secrets are only available for that specific environment. + development: secret_key_base: <%= app_secret %> @@ -18,5 +25,6 @@ test: # Do not keep production secrets in the repository, # instead read values from the environment. + production: secret_key_base: <%%= ENV["SECRET_KEY_BASE"] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/spring.rb b/railties/lib/rails/generators/rails/app/templates/config/spring.rb new file mode 100644 index 0000000000..c9119b40c0 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/spring.rb @@ -0,0 +1,6 @@ +%w( + .ruby-version + .rbenv-vars + tmp/restart.txt + tmp/caching-dev.txt +).each { |path| Spring.watch(path) } diff --git a/railties/lib/rails/generators/rails/app/templates/public/robots.txt b/railties/lib/rails/generators/rails/app/templates/public/robots.txt index 3c9c7c01f3..37b576a4a0 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/robots.txt +++ b/railties/lib/rails/generators/rails/app/templates/public/robots.txt @@ -1,5 +1 @@ # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file -# -# To ban all spiders from the entire site uncomment the next two lines: -# User-agent: * -# Disallow: / diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb index 87b8fe3516..2f92168eef 100644 --- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb +++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb @@ -1,4 +1,3 @@ -ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../../config/environment', __FILE__) require 'rails/test_help' diff --git a/railties/lib/rails/generators/rails/assets/assets_generator.rb b/railties/lib/rails/generators/rails/assets/assets_generator.rb index 6f4b86e708..265dada2ca 100644 --- a/railties/lib/rails/generators/rails/assets/assets_generator.rb +++ b/railties/lib/rails/generators/rails/assets/assets_generator.rb @@ -9,17 +9,17 @@ module Rails protected - def asset_name - file_name - end + def asset_name + file_name + end - hook_for :javascript_engine do |javascript_engine| - invoke javascript_engine, [name] if options[:javascripts] - end + hook_for :javascript_engine do |javascript_engine| + invoke javascript_engine, [name] if options[:javascripts] + end - hook_for :stylesheet_engine do |stylesheet_engine| - invoke stylesheet_engine, [name] if options[:stylesheets] - end + hook_for :stylesheet_engine do |stylesheet_engine| + invoke stylesheet_engine, [name] if options[:stylesheets] + end end end end diff --git a/railties/lib/rails/generators/rails/controller/controller_generator.rb b/railties/lib/rails/generators/rails/controller/controller_generator.rb index 0a4c509a31..ced3c85c00 100644 --- a/railties/lib/rails/generators/rails/controller/controller_generator.rb +++ b/railties/lib/rails/generators/rails/controller/controller_generator.rb @@ -3,11 +3,13 @@ module Rails class ControllerGenerator < NamedBase # :nodoc: argument :actions, type: :array, default: [], banner: "action action" class_option :skip_routes, type: :boolean, desc: "Don't add routes to config/routes.rb." + class_option :helper, type: :boolean + class_option :assets, type: :boolean check_class_collision suffix: "Controller" def create_controller_files - template 'controller.rb', File.join('app/controllers', class_path, "#{file_name}_controller.rb") + template "controller.rb", File.join("app/controllers", class_path, "#{file_name}_controller.rb") end def add_routes @@ -19,8 +21,7 @@ module Rails end end - hook_for :template_engine, :test_framework - hook_for :helper, :assets, hide: true + hook_for :template_engine, :test_framework, :helper, :assets private diff --git a/railties/lib/rails/generators/rails/generator/generator_generator.rb b/railties/lib/rails/generators/rails/generator/generator_generator.rb index 15d88f06ac..8040ec5e7b 100644 --- a/railties/lib/rails/generators/rails/generator/generator_generator.rb +++ b/railties/lib/rails/generators/rails/generator/generator_generator.rb @@ -7,7 +7,7 @@ module Rails desc: "Namespace generator under lib/generators/name" def create_generator_files - directory '.', generator_dir + directory ".", generator_dir end hook_for :test_framework @@ -21,7 +21,6 @@ module Rails File.join("lib", "generators", regular_class_path) end end - end end end diff --git a/railties/lib/rails/generators/rails/helper/helper_generator.rb b/railties/lib/rails/generators/rails/helper/helper_generator.rb index 5ff38e4111..e48b1b6fb3 100644 --- a/railties/lib/rails/generators/rails/helper/helper_generator.rb +++ b/railties/lib/rails/generators/rails/helper/helper_generator.rb @@ -4,7 +4,7 @@ module Rails check_class_collision suffix: "Helper" def create_helper_files - template 'helper.rb', File.join('app/helpers', class_path, "#{file_name}_helper.rb") + template "helper.rb", File.join("app/helpers", class_path, "#{file_name}_helper.rb") end hook_for :test_framework diff --git a/railties/lib/rails/generators/rails/model/USAGE b/railties/lib/rails/generators/rails/model/USAGE index 025bcf4774..c95e523fb5 100644 --- a/railties/lib/rails/generators/rails/model/USAGE +++ b/railties/lib/rails/generators/rails/model/USAGE @@ -35,17 +35,7 @@ Available field types: type. If no type is specified the string type will be used by default. You can use the following types: - integer - primary_key - decimal - float - boolean - binary - string - text - date - time - datetime + <%= ActiveRecord::Base.connection.native_database_types.keys.join("\n\t") %> You can also consider `references` as a kind of type. For instance, if you run: diff --git a/railties/lib/rails/generators/rails/model/model_generator.rb b/railties/lib/rails/generators/rails/model/model_generator.rb index ec78fd855d..c32a8a079a 100644 --- a/railties/lib/rails/generators/rails/model/model_generator.rb +++ b/railties/lib/rails/generators/rails/model/model_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/model_helpers' +require "rails/generators/model_helpers" module Rails module Generators diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb index b5e836b584..9ffeab4fbe 100644 --- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb +++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb @@ -1,6 +1,6 @@ -require 'active_support/core_ext/hash/slice' +require "active_support/core_ext/hash/slice" require "rails/generators/rails/app/app_generator" -require 'date' +require "date" module Rails # The plugin builder allows you to override elements of the plugin @@ -18,20 +18,20 @@ module Rails def app if mountable? if api? - directory 'app', exclude_pattern: %r{app/(views|helpers)} + directory "app", exclude_pattern: %r{app/(views|helpers)} else - directory 'app' + directory "app" empty_directory_with_keep_file "app/assets/images/#{namespaced_name}" end elsif full? - empty_directory_with_keep_file 'app/models' - empty_directory_with_keep_file 'app/controllers' - empty_directory_with_keep_file 'app/mailers' + empty_directory_with_keep_file "app/models" + empty_directory_with_keep_file "app/controllers" + empty_directory_with_keep_file "app/mailers" unless api? empty_directory_with_keep_file "app/assets/images/#{namespaced_name}" - empty_directory_with_keep_file 'app/helpers' - empty_directory_with_keep_file 'app/views' + empty_directory_with_keep_file "app/helpers" + empty_directory_with_keep_file "app/views" end end end @@ -90,6 +90,7 @@ task default: :test opts[:force] = force opts[:skip_bundle] = true opts[:api] = options.api? + opts[:skip_listen] = true invoke Rails::Generators::AppGenerator, [ File.expand_path(dummy_path, destination_root) ], opts @@ -148,7 +149,7 @@ task default: :test end def bin(force = false) - bin_file = engine? ? 'bin/rails.tt' : 'bin/test.tt' + bin_file = engine? ? "bin/rails.tt" : "bin/test.tt" template bin_file, force: force do |content| "#{shebang}\n" + content end @@ -257,7 +258,7 @@ task default: :test build(:leftovers) end - public_task :apply_rails_template, :run_bundle + public_task :apply_rails_template def run_after_bundle_callbacks @after_bundle_callbacks.each do |callback| @@ -282,15 +283,11 @@ task default: :test end def namespaced_name - @namespaced_name ||= name.gsub('-', '/') + @namespaced_name ||= name.gsub("-", "/") end protected - def app_templates_dir - "../../app/templates" - end - def create_dummy_app(path = nil) dummy_path(path) if path @@ -323,7 +320,7 @@ task default: :test end def with_dummy_app? - options[:skip_test].blank? || options[:dummy_path] != 'test/dummy' + options[:skip_test].blank? || options[:dummy_path] != "test/dummy" end def api? @@ -331,7 +328,7 @@ task default: :test end def self.banner - "rails plugin new #{self.arguments.map(&:usage).join(' ')} [options]" + "rails plugin new #{arguments.map(&:usage).join(' ')} [options]" end def original_name @@ -343,7 +340,7 @@ task default: :test end def wrap_in_modules(unwrapped_code) - unwrapped_code = "#{unwrapped_code}".strip.gsub(/\s$\n/, '') + unwrapped_code = "#{unwrapped_code}".strip.gsub(/\s$\n/, "") modules.reverse.inject(unwrapped_code) do |content, mod| str = "module #{mod}\n" str += content.lines.map { |line| " #{line}" }.join @@ -360,7 +357,7 @@ task default: :test end def camelized - @camelized ||= name.gsub(/\W/, '_').squeeze('_').camelize + @camelized ||= name.gsub(/\W/, "_").squeeze("_").camelize end def author @@ -445,7 +442,7 @@ end def relative_path return unless inside_application? - app_path.sub(/^#{rails_app_path}\//, '') + app_path.sub(/^#{rails_app_path}\//, "") end end end diff --git a/railties/lib/rails/generators/rails/plugin/templates/Rakefile b/railties/lib/rails/generators/rails/plugin/templates/Rakefile index f1943644e4..383d2fb2d1 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/Rakefile +++ b/railties/lib/rails/generators/rails/plugin/templates/Rakefile @@ -25,5 +25,5 @@ load 'rails/tasks/statistics.rake' <% unless options[:skip_gemspec] -%> -Bundler::GemHelper.install_tasks +require 'bundler/gem_tasks' <% end %> 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 3edaac35c9..56e7925c6b 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt @@ -1,4 +1,5 @@ -# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application. +# This command will automatically be run when you run "rails" with Rails gems +# installed from the root of your application. ENGINE_ROOT = File.expand_path('../..', __FILE__) ENGINE_PATH = File.expand_path('../../lib/<%= namespaced_name -%>/engine', __FILE__) diff --git a/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt b/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt index 62b94618fd..c0fbb84a93 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt @@ -5,4 +5,6 @@ require 'rails/test_unit/minitest_plugin' Rails::TestUnitReporter.executable = 'bin/test' -exit Minitest.run(ARGV) +Minitest.run_via[:rails] = true + +require "active_support/testing/autorun" diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb index b1038c839e..d03b1be878 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb @@ -1,4 +1,4 @@ -require File.expand_path('../boot', __FILE__) +require_relative 'boot' <% if include_all_railties? -%> require 'rails/all' @@ -6,10 +6,12 @@ require 'rails/all' # Pick the frameworks you want: <%= comment_if :skip_active_record %>require "active_record/railtie" require "action_controller/railtie" -<%= comment_if :skip_action_mailer %>require "action_mailer/railtie" require "action_view/railtie" -<%= comment_if :skip_sprockets %>require "sprockets/railtie" +<%= comment_if :skip_action_mailer %>require "action_mailer/railtie" +require "active_job/railtie" +<%= comment_if :skip_action_cable %>require "action_cable/engine" <%= comment_if :skip_test %>require "rails/test_unit/railtie" +<%= comment_if :skip_sprockets %>require "sprockets/railtie" <% end -%> Bundler.require(*Rails.groups) diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb index 6266cfc509..c9aef85d40 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb @@ -1,5 +1,5 @@ # Set up gems listed in the Gemfile. -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__) +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__) require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) -$LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__) +$LOAD_PATH.unshift File.expand_path('../../../lib', __dir__) diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb index a5eebcb19f..e84e403018 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb @@ -1,6 +1,3 @@ -# Configure Rails Environment -ENV["RAILS_ENV"] = "test" - require File.expand_path("../../<%= options[:dummy_path] -%>/config/environment.rb", __FILE__) <% unless options[:skip_active_record] -%> ActiveRecord::Migrator.migrations_paths = [File.expand_path("../../<%= options[:dummy_path] -%>/db/migrate", __FILE__)] diff --git a/railties/lib/rails/generators/rails/resource/resource_generator.rb b/railties/lib/rails/generators/rails/resource/resource_generator.rb index 3acf21df13..5ac5164af0 100644 --- a/railties/lib/rails/generators/rails/resource/resource_generator.rb +++ b/railties/lib/rails/generators/rails/resource/resource_generator.rb @@ -1,5 +1,5 @@ -require 'rails/generators/resource_helpers' -require 'rails/generators/rails/model/model_generator' +require "rails/generators/resource_helpers" +require "rails/generators/rails/model/model_generator" module Rails module Generators diff --git a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb index 17c32bfdb3..ed6bf7f7d7 100644 --- a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/rails/resource/resource_generator' +require "rails/generators/rails/resource/resource_generator" module Rails module Generators diff --git a/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css index 79f8b7f96f..cd4f3de38d 100644 --- a/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css +++ b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css @@ -1,13 +1,13 @@ body { background-color: #fff; color: #333; + margin: 33px; } body, p, ol, ul, td { font-family: verdana, arial, helvetica, sans-serif; font-size: 13px; line-height: 18px; - margin: 33px; } pre { @@ -34,9 +34,7 @@ th { } td { - padding-bottom: 7px; - padding-left: 5px; - padding-right: 5px; + padding: 0 5px 7px; } div.field, @@ -57,8 +55,7 @@ div.actions { #error_explanation { width: 450px; border: 2px solid red; - padding: 7px; - padding-bottom: 0; + padding: 7px 7px 0; margin-bottom: 20px; background-color: #f0f0f0; } @@ -68,8 +65,7 @@ div.actions { font-weight: bold; padding: 5px 5px 5px 15px; font-size: 12px; - margin: -7px; - margin-bottom: 0; + margin: -7px -7px 0; background-color: #c00; color: #fff; } 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 d0b8cad896..e4f3161ffd 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 @@ -1,4 +1,4 @@ -require 'rails/generators/resource_helpers' +require "rails/generators/resource_helpers" module Rails module Generators @@ -17,7 +17,7 @@ module Rails def create_controller_files template_file = options.api? ? "api_controller.rb" : "controller.rb" - template template_file, File.join('app/controllers', controller_class_path, "#{controller_file_name}_controller.rb") + template template_file, File.join("app/controllers", controller_class_path, "#{controller_file_name}_controller.rb") end hook_for :template_engine, :test_framework, as: :scaffold diff --git a/railties/lib/rails/generators/rails/task/task_generator.rb b/railties/lib/rails/generators/rails/task/task_generator.rb index 754824ca0c..bb96bdf0dd 100644 --- a/railties/lib/rails/generators/rails/task/task_generator.rb +++ b/railties/lib/rails/generators/rails/task/task_generator.rb @@ -4,9 +4,8 @@ module Rails argument :actions, type: :array, default: [], banner: "action action" def create_task_files - template 'task.rb', File.join('lib/tasks', "#{file_name}.rake") + template "task.rb", File.join("lib/tasks", "#{file_name}.rake") end - end end end diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb index 9c2037783e..6d80003271 100644 --- a/railties/lib/rails/generators/resource_helpers.rb +++ b/railties/lib/rails/generators/resource_helpers.rb @@ -1,12 +1,11 @@ -require 'rails/generators/active_model' -require 'rails/generators/model_helpers' +require "rails/generators/active_model" +require "rails/generators/model_helpers" module Rails module Generators # Deal with controller names on scaffold and add some helpers to deal with # ActiveModel. module ResourceHelpers # :nodoc: - def self.included(base) #:nodoc: base.include(Rails::Generators::ModelHelpers) base.class_option :model_name, type: :string, desc: "ModelName to be used" @@ -18,7 +17,7 @@ module Rails controller_name = name if options[:model_name] self.name = options[:model_name] - assign_names!(self.name) + assign_names!(name) end assign_controller_names!(controller_name.pluralize) @@ -38,21 +37,21 @@ module Rails def assign_controller_names!(name) @controller_name = name - @controller_class_path = name.include?('/') ? name.split('/') : name.split('::') + @controller_class_path = name.include?("/") ? name.split("/") : name.split("::") @controller_class_path.map!(&:underscore) @controller_file_name = @controller_class_path.pop end def controller_file_path - @controller_file_path ||= (controller_class_path + [controller_file_name]).join('/') + @controller_file_path ||= (controller_class_path + [controller_file_name]).join("/") end def controller_class_name - (controller_class_path + [controller_file_name]).map!(&:camelize).join('::') + (controller_class_path + [controller_file_name]).map!(&:camelize).join("::") end def controller_i18n_scope - @controller_i18n_scope ||= controller_file_path.tr('/', '.') + @controller_i18n_scope ||= controller_file_path.tr("/", ".") end # Loads the ORM::Generators::ActiveModel class. This class is responsible diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb index 58592b4f8e..3eec929aeb 100644 --- a/railties/lib/rails/generators/test_case.rb +++ b/railties/lib/rails/generators/test_case.rb @@ -1,8 +1,8 @@ -require 'rails/generators' -require 'rails/generators/testing/behaviour' -require 'rails/generators/testing/setup_and_teardown' -require 'rails/generators/testing/assertions' -require 'fileutils' +require "rails/generators" +require "rails/generators/testing/behaviour" +require "rails/generators/testing/setup_and_teardown" +require "rails/generators/testing/assertions" +require "fileutils" module Rails module Generators @@ -30,7 +30,6 @@ module Rails include Rails::Generators::Testing::SetupAndTeardown include Rails::Generators::Testing::Assertions include FileUtils - end end end diff --git a/railties/lib/rails/generators/test_unit.rb b/railties/lib/rails/generators/test_unit.rb index fe45c9e15d..722efcf492 100644 --- a/railties/lib/rails/generators/test_unit.rb +++ b/railties/lib/rails/generators/test_unit.rb @@ -1,4 +1,4 @@ -require 'rails/generators/named_base' +require "rails/generators/named_base" module TestUnit # :nodoc: module Generators # :nodoc: diff --git a/railties/lib/rails/generators/test_unit/controller/controller_generator.rb b/railties/lib/rails/generators/test_unit/controller/controller_generator.rb index b5aa581769..ac528d94f1 100644 --- a/railties/lib/rails/generators/test_unit/controller/controller_generator.rb +++ b/railties/lib/rails/generators/test_unit/controller/controller_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/test_unit' +require "rails/generators/test_unit" module TestUnit # :nodoc: module Generators # :nodoc: @@ -7,8 +7,8 @@ module TestUnit # :nodoc: check_class_collision suffix: "ControllerTest" def create_test_files - template 'functional_test.rb', - File.join('test/controllers', class_path, "#{file_name}_controller_test.rb") + template "functional_test.rb", + File.join("test/controllers", class_path, "#{file_name}_controller_test.rb") end end end 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 d7307398ce..59f8d40343 100644 --- a/railties/lib/rails/generators/test_unit/generator/generator_generator.rb +++ b/railties/lib/rails/generators/test_unit/generator/generator_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/test_unit' +require "rails/generators/test_unit" module TestUnit # :nodoc: module Generators # :nodoc: @@ -9,7 +9,7 @@ module TestUnit # :nodoc: desc: "Namespace generator under lib/generators/name" def create_generator_files - template 'generator_test.rb', File.join('test/lib/generators', class_path, "#{file_name}_generator_test.rb") + template "generator_test.rb", File.join("test/lib/generators", class_path, "#{file_name}_generator_test.rb") end protected diff --git a/railties/lib/rails/generators/test_unit/helper/helper_generator.rb b/railties/lib/rails/generators/test_unit/helper/helper_generator.rb index bde4e88915..6674a15fa3 100644 --- a/railties/lib/rails/generators/test_unit/helper/helper_generator.rb +++ b/railties/lib/rails/generators/test_unit/helper/helper_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/test_unit' +require "rails/generators/test_unit" module TestUnit # :nodoc: module Generators # :nodoc: diff --git a/railties/lib/rails/generators/test_unit/integration/integration_generator.rb b/railties/lib/rails/generators/test_unit/integration/integration_generator.rb index e004835bd5..9d065c1297 100644 --- a/railties/lib/rails/generators/test_unit/integration/integration_generator.rb +++ b/railties/lib/rails/generators/test_unit/integration/integration_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/test_unit' +require "rails/generators/test_unit" module TestUnit # :nodoc: module Generators # :nodoc: @@ -6,7 +6,7 @@ module TestUnit # :nodoc: check_class_collision suffix: "Test" def create_test_files - template 'integration_test.rb', File.join('test/integration', class_path, "#{file_name}_test.rb") + template "integration_test.rb", File.join("test/integration", class_path, "#{file_name}_test.rb") end end end diff --git a/railties/lib/rails/generators/test_unit/job/job_generator.rb b/railties/lib/rails/generators/test_unit/job/job_generator.rb index 566b61ca66..6975252b99 100644 --- a/railties/lib/rails/generators/test_unit/job/job_generator.rb +++ b/railties/lib/rails/generators/test_unit/job/job_generator.rb @@ -1,12 +1,12 @@ -require 'rails/generators/test_unit' +require "rails/generators/test_unit" module TestUnit # :nodoc: module Generators # :nodoc: class JobGenerator < Base # :nodoc: - check_class_collision suffix: 'JobTest' + check_class_collision suffix: "JobTest" def create_test_file - template 'unit_test.rb.erb', File.join('test/jobs', class_path, "#{file_name}_job_test.rb") + template "unit_test.rb.erb", File.join("test/jobs", class_path, "#{file_name}_job_test.rb") end end end 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 343c8a3949..806279788e 100644 --- a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb +++ b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/test_unit' +require "rails/generators/test_unit" module TestUnit # :nodoc: module Generators # :nodoc: @@ -10,16 +10,16 @@ module TestUnit # :nodoc: end def create_test_files - template "functional_test.rb", File.join('test/mailers', class_path, "#{file_name}_mailer_test.rb") + template "functional_test.rb", File.join("test/mailers", class_path, "#{file_name}_mailer_test.rb") end def create_preview_files - template "preview.rb", File.join('test/mailers/previews', class_path, "#{file_name}_mailer_preview.rb") + template "preview.rb", File.join("test/mailers/previews", class_path, "#{file_name}_mailer_preview.rb") end protected def file_name - @_file_name ||= super.gsub(/\_mailer/i, '') + @_file_name ||= super.gsub(/_mailer/i, "") end end end diff --git a/railties/lib/rails/generators/test_unit/model/model_generator.rb b/railties/lib/rails/generators/test_unit/model/model_generator.rb index 086588750e..99495d5247 100644 --- a/railties/lib/rails/generators/test_unit/model/model_generator.rb +++ b/railties/lib/rails/generators/test_unit/model/model_generator.rb @@ -1,9 +1,8 @@ -require 'rails/generators/test_unit' +require "rails/generators/test_unit" module TestUnit # :nodoc: module Generators # :nodoc: class ModelGenerator < Base # :nodoc: - RESERVED_YAML_KEYWORDS = %w(y yes n no true false on off null) argument :attributes, type: :array, default: [], banner: "field:type field:type" @@ -12,14 +11,14 @@ module TestUnit # :nodoc: check_class_collision suffix: "Test" def create_test_file - template 'unit_test.rb', File.join('test/models', class_path, "#{file_name}_test.rb") + template "unit_test.rb", File.join("test/models", class_path, "#{file_name}_test.rb") end hook_for :fixture_replacement def create_fixture_file if options[:fixture] && options[:fixture_replacement].nil? - template 'fixtures.yml', File.join('test/fixtures', class_path, "#{fixture_file_name}.yml") + template "fixtures.yml", File.join("test/fixtures", class_path, "#{fixture_file_name}.yml") end end diff --git a/railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb b/railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb index b5d4f38444..f1c9b6da5b 100644 --- a/railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb +++ b/railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/test_unit' +require "rails/generators/test_unit" module TestUnit # :nodoc: module Generators # :nodoc: @@ -6,7 +6,7 @@ module TestUnit # :nodoc: check_class_collision suffix: "Test" def create_test_files - directory '.', 'test' + directory ".", "test" end end end diff --git a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb index 0171da7cc7..8840a86d0d 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb @@ -1,5 +1,5 @@ -require 'rails/generators/test_unit' -require 'rails/generators/resource_helpers' +require "rails/generators/test_unit" +require "rails/generators/resource_helpers" module TestUnit # :nodoc: module Generators # :nodoc: @@ -39,7 +39,7 @@ module TestUnit # :nodoc: else "#{name}: @#{singular_table_name}.#{name}" end - end.sort.join(', ') + end.sort.join(", ") end end end diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb index 0d18478043..c469c188e6 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb @@ -11,31 +11,31 @@ class <%= controller_class_name %>ControllerTest < ActionDispatch::IntegrationTe end test "should get index" do - get <%= index_helper %>_url + get <%= index_helper %>_url, as: :json assert_response :success end test "should create <%= singular_table_name %>" do assert_difference('<%= class_name %>.count') do - post <%= index_helper %>_url, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> } + post <%= index_helper %>_url, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }, as: :json end assert_response 201 end test "should show <%= singular_table_name %>" do - get <%= show_helper %> + get <%= show_helper %>, as: :json assert_response :success end test "should update <%= singular_table_name %>" do - patch <%= show_helper %>, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> } + patch <%= show_helper %>, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }, as: :json assert_response 200 end test "should destroy <%= singular_table_name %>" do assert_difference('<%= class_name %>.count', -1) do - delete <%= show_helper %> + delete <%= show_helper %>, as: :json end assert_response 204 diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb index 0e6bef12fc..c33375b7b4 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb @@ -25,7 +25,7 @@ class <%= controller_class_name %>ControllerTest < ActionDispatch::IntegrationTe post <%= index_helper %>_url, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> } end - assert_redirected_to <%= singular_table_name %>_path(<%= class_name %>.last) + assert_redirected_to <%= singular_table_name %>_url(<%= class_name %>.last) end test "should show <%= singular_table_name %>" do @@ -40,7 +40,7 @@ class <%= controller_class_name %>ControllerTest < ActionDispatch::IntegrationTe test "should update <%= singular_table_name %>" do patch <%= show_helper %>, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> } - assert_redirected_to <%= singular_table_name %>_path(<%= "@#{singular_table_name}" %>) + assert_redirected_to <%= singular_table_name %>_url(<%= "@#{singular_table_name}" %>) end test "should destroy <%= singular_table_name %>" do @@ -48,7 +48,7 @@ class <%= controller_class_name %>ControllerTest < ActionDispatch::IntegrationTe delete <%= show_helper %> end - assert_redirected_to <%= index_helper %>_path + assert_redirected_to <%= index_helper %>_url end end <% end -%> diff --git a/railties/lib/rails/generators/testing/assertions.rb b/railties/lib/rails/generators/testing/assertions.rb index 76758df86d..1cabf4e28c 100644 --- a/railties/lib/rails/generators/testing/assertions.rb +++ b/railties/lib/rails/generators/testing/assertions.rb @@ -29,10 +29,10 @@ module Rails contents.each do |content| case content - when String - assert_equal content, read - when Regexp - assert_match content, read + when String + assert_equal content, read + when Regexp + assert_match content, read end end end diff --git a/railties/lib/rails/generators/testing/behaviour.rb b/railties/lib/rails/generators/testing/behaviour.rb index 94b5e52224..a1e5a233b9 100644 --- a/railties/lib/rails/generators/testing/behaviour.rb +++ b/railties/lib/rails/generators/testing/behaviour.rb @@ -1,10 +1,10 @@ -require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/module/delegation' -require 'active_support/core_ext/hash/reverse_merge' -require 'active_support/core_ext/kernel/reporting' -require 'active_support/testing/stream' -require 'active_support/concern' -require 'rails/generators' +require "active_support/core_ext/class/attribute" +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/hash/reverse_merge" +require "active_support/core_ext/kernel/reporting" +require "active_support/testing/stream" +require "active_support/concern" +require "rails/generators" module Rails module Generators @@ -62,24 +62,24 @@ module Rails # # You can provide a configuration hash as second argument. This method returns the output # printed by the generator. - def run_generator(args=self.default_arguments, config={}) + def run_generator(args = default_arguments, config = {}) capture(:stdout) do - args += ['--skip-bundle'] unless args.include? '--dev' - self.generator_class.start(args, config.reverse_merge(destination_root: destination_root)) + args += ["--skip-bundle"] unless args.include? "--dev" + generator_class.start(args, config.reverse_merge(destination_root: destination_root)) end end # Instantiate the generator. - def generator(args=self.default_arguments, options={}, config={}) - @generator ||= self.generator_class.new(args, options, config.reverse_merge(destination_root: destination_root)) + def generator(args = default_arguments, options = {}, config = {}) + @generator ||= generator_class.new(args, options, config.reverse_merge(destination_root: destination_root)) end # Create a Rails::Generators::GeneratedAttribute by supplying the # attribute type and, optionally, the attribute name: # # create_generated_attribute(:string, 'name') - def create_generated_attribute(attribute_type, name = 'test', index = nil) - Rails::Generators::GeneratedAttribute.parse([name, attribute_type, index].compact.join(':')) + def create_generated_attribute(attribute_type, name = "test", index = nil) + Rails::Generators::GeneratedAttribute.parse([name, attribute_type, index].compact.join(":")) end protected @@ -100,10 +100,9 @@ module Rails def migration_file_name(relative) # :nodoc: absolute = File.expand_path(relative, destination_root) - dirname, file_name = File.dirname(absolute), File.basename(absolute).sub(/\.rb$/, '') + dirname, file_name = File.dirname(absolute), File.basename(absolute).sub(/\.rb$/, "") Dir.glob("#{dirname}/[0-9]*_*.rb").grep(/\d+_#{file_name}.rb$/).first end - end end end diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb index 5909446b66..5d4acd6f6b 100644 --- a/railties/lib/rails/info.rb +++ b/railties/lib/rails/info.rb @@ -38,64 +38,64 @@ module Rails alias inspect to_s def to_html - '<table>'.tap do |table| + "<table>".tap do |table| properties.each do |(name, value)| table << %(<tr><td class="name">#{CGI.escapeHTML(name.to_s)}</td>) formatted_value = if value.kind_of?(Array) - "<ul>" + value.map { |v| "<li>#{CGI.escapeHTML(v.to_s)}</li>" }.join + "</ul>" - else - CGI.escapeHTML(value.to_s) - end + "<ul>" + value.map { |v| "<li>#{CGI.escapeHTML(v.to_s)}</li>" }.join + "</ul>" + else + CGI.escapeHTML(value.to_s) + end table << %(<td class="value">#{formatted_value}</td></tr>) end - table << '</table>' + table << "</table>" end end end # The Rails version. - property 'Rails version' do + property "Rails version" do Rails.version.to_s end # The Ruby version and platform, e.g. "2.0.0-p247 (x86_64-darwin12.4.0)". - property 'Ruby version' do + property "Ruby version" do "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_PLATFORM})" end # The RubyGems version, if it's installed. - property 'RubyGems version' do + property "RubyGems version" do Gem::RubyGemsVersion end - property 'Rack version' do + property "Rack version" do ::Rack.release end - property 'JavaScript Runtime' do + property "JavaScript Runtime" do ExecJS.runtime.name end - property 'Middleware' do + property "Middleware" do Rails.configuration.middleware.map(&:inspect) end # The application's location on the filesystem. - property 'Application root' do + property "Application root" do File.expand_path(Rails.root) end # The current Rails environment (development, test, or production). - property 'Environment' do + property "Environment" do Rails.env end # The name of the database adapter for the current environment. - property 'Database adapter' do - ActiveRecord::Base.configurations[Rails.env]['adapter'] + property "Database adapter" do + ActiveRecord::Base.configurations[Rails.env]["adapter"] end - property 'Database schema version' do + property "Database schema version" do ActiveRecord::Migrator.current_version rescue nil end end diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb index 778105c5f7..8b553aea79 100644 --- a/railties/lib/rails/info_controller.rb +++ b/railties/lib/rails/info_controller.rb @@ -1,9 +1,9 @@ -require 'rails/application_controller' -require 'action_dispatch/routing/inspector' +require "rails/application_controller" +require "action_dispatch/routing/inspector" class Rails::InfoController < Rails::ApplicationController # :nodoc: prepend_view_path ActionDispatch::DebugExceptions::RESCUES_TEMPLATE_PATH - layout -> { request.xhr? ? false : 'application' } + layout -> { request.xhr? ? false : "application" } before_action :require_local! @@ -13,7 +13,7 @@ class Rails::InfoController < Rails::ApplicationController # :nodoc: def properties @info = Rails::Info.to_html - @page_title = 'Properties' + @page_title = "Properties" end def routes @@ -21,24 +21,24 @@ class Rails::InfoController < Rails::ApplicationController # :nodoc: path = URI.parser.escape path normalized_path = with_leading_slash path render json: { - exact: match_route {|it| it.match normalized_path }, - fuzzy: match_route {|it| it.spec.to_s.match path } + exact: match_route { |it| it.match normalized_path }, + fuzzy: match_route { |it| it.spec.to_s.match path } } else @routes_inspector = ActionDispatch::Routing::RoutesInspector.new(_routes.routes) - @page_title = 'Routes' + @page_title = "Routes" end end private - def match_route - _routes.routes.select {|route| - yield route.path - }.map {|route| route.path.spec.to_s } - end + def match_route + _routes.routes.select { |route| + yield route.path + }.map { |route| route.path.spec.to_s } + end - def with_leading_slash(path) - ('/' + path).squeeze('/') - end + def with_leading_slash(path) + ("/" + path).squeeze("/") + end end diff --git a/railties/lib/rails/initializable.rb b/railties/lib/rails/initializable.rb index 1a0b6d1e1a..81b1cd8110 100644 --- a/railties/lib/rails/initializable.rb +++ b/railties/lib/rails/initializable.rb @@ -1,4 +1,4 @@ -require 'tsort' +require "tsort" module Rails module Initializable @@ -34,6 +34,10 @@ module Rails return self if @context Initializer.new(@name, context, @options, &block) end + + def context_class + @context.class + end end class Collection < Array diff --git a/railties/lib/rails/mailers_controller.rb b/railties/lib/rails/mailers_controller.rb index 6143cf2dd9..95de998208 100644 --- a/railties/lib/rails/mailers_controller.rb +++ b/railties/lib/rails/mailers_controller.rb @@ -1,4 +1,4 @@ -require 'rails/application_controller' +require "rails/application_controller" class Rails::MailersController < Rails::ApplicationController # :nodoc: prepend_view_path ActionDispatch::DebugExceptions::RESCUES_TEMPLATE_PATH @@ -14,7 +14,7 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc: def preview if params[:path] == @preview.preview_name @page_title = "Mailer Previews for #{@preview.preview_name}" - render action: 'mailer' + render action: "mailer" else @email_action = File.basename(params[:path]) @@ -32,7 +32,7 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc: end else @part = find_preferred_part(request.format, Mime[:html], Mime[:text]) - render action: 'email', layout: false, formats: %w[html] + render action: "email", layout: false, formats: %w[html] end else raise AbstractController::ActionNotFound, "Email '#{@email_action}' not found in #{@preview.name}" @@ -47,8 +47,8 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc: def find_preview candidates = [] - params[:path].to_s.scan(%r{/|$}){ candidates << $` } - preview = candidates.detect{ |candidate| ActionMailer::Preview.exists?(candidate) } + params[:path].to_s.scan(%r{/|$}) { candidates << $` } + preview = candidates.detect { |candidate| ActionMailer::Preview.exists?(candidate) } if preview @preview = ActionMailer::Preview.find(preview) @@ -64,7 +64,7 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc: end end - if formats.any?{ |f| @email.mime_type == f } + if formats.any? { |f| @email.mime_type == f } @email end end diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb index e47616a87f..1c1810dde6 100644 --- a/railties/lib/rails/paths.rb +++ b/railties/lib/rails/paths.rb @@ -30,7 +30,7 @@ module Rails # root["config/routes"].inspect # => ["config/routes.rb"] # # The +add+ method accepts the following options as arguments: - # eager_load, autoload, autoload_once and glob. + # eager_load, autoload, autoload_once, and glob. # # Finally, the +Path+ object also provides a few helpers: # @@ -180,7 +180,7 @@ module Rails end def extensions # :nodoc: - $1.split(',') if @glob =~ /\{([\S]+)\}/ + $1.split(",") if @glob =~ /\{([\S]+)\}/ end # Expands all paths against the root and return all unique values. diff --git a/railties/lib/rails/rack/debugger.rb b/railties/lib/rails/rack/debugger.rb index 1fde3db070..dccfa3e9bb 100644 --- a/railties/lib/rails/rack/debugger.rb +++ b/railties/lib/rails/rack/debugger.rb @@ -1,3 +1,3 @@ -require 'active_support/deprecation' +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 b63d3a58d2..e3fee75603 100644 --- a/railties/lib/rails/rack/logger.rb +++ b/railties/lib/rails/rack/logger.rb @@ -1,8 +1,8 @@ -require 'active_support/core_ext/time/conversions' -require 'active_support/core_ext/object/blank' -require 'active_support/log_subscriber' -require 'action_dispatch/http/request' -require 'rack/body_proxy' +require "active_support/core_ext/time/conversions" +require "active_support/core_ext/object/blank" +require "active_support/log_subscriber" +require "action_dispatch/http/request" +require "rack/body_proxy" module Rails module Rack @@ -31,7 +31,7 @@ module Rails def call_app(request, env) instrumenter = ActiveSupport::Notifications.instrumenter - instrumenter.start 'request.action_dispatch', request: request + 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) } @@ -67,14 +67,14 @@ module Rails private - def finish(request) - instrumenter = ActiveSupport::Notifications.instrumenter - instrumenter.finish 'request.action_dispatch', request: request - end + def finish(request) + instrumenter = ActiveSupport::Notifications.instrumenter + instrumenter.finish "request.action_dispatch", request: request + end - def logger - Rails.logger - end + def logger + Rails.logger + end end end end diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index 8c24d1d56d..eb3f5d4ee9 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -1,38 +1,37 @@ -require 'rails/initializable' -require 'rails/configuration' -require 'active_support/inflector' -require 'active_support/core_ext/module/introspection' -require 'active_support/core_ext/module/delegation' +require "rails/initializable" +require "active_support/inflector" +require "active_support/core_ext/module/introspection" +require "active_support/core_ext/module/delegation" module Rails - # Railtie is the core of the Rails framework and provides several hooks to extend - # Rails and/or modify the initialization process. + # <tt>Rails::Railtie</tt> is the core of the Rails framework and provides + # several hooks to extend Rails and/or modify the initialization process. # - # Every major component of Rails (Action Mailer, Action Controller, - # Action View and Active Record) is a Railtie. Each of - # them is responsible for their own initialization. This makes Rails itself - # absent of any component hooks, allowing other components to be used in - # place of any of the Rails defaults. + # Every major component of Rails (Action Mailer, Action Controller, Active + # Record, etc.) implements a railtie. Each of them is responsible for their + # own initialization. This makes Rails itself absent of any component hooks, + # allowing other components to be used in place of any of the Rails defaults. # - # Developing a Rails extension does _not_ require any implementation of - # Railtie, but if you need to interact with the Rails framework during - # or after boot, then Railtie is needed. + # Developing a Rails extension does _not_ require implementing a railtie, but + # if you need to interact with the Rails framework during or after boot, then + # a railtie is needed. # - # For example, an extension doing any of the following would require Railtie: + # For example, an extension doing any of the following would need a railtie: # # * creating initializers # * configuring a Rails framework for the application, like setting a generator # * adding <tt>config.*</tt> keys to the environment - # * setting up a subscriber with ActiveSupport::Notifications - # * adding rake tasks + # * setting up a subscriber with <tt>ActiveSupport::Notifications</tt> + # * adding Rake tasks # - # == Creating your Railtie + # == Creating a Railtie # - # To extend Rails using Railtie, create a Railtie class which inherits - # from Rails::Railtie within your extension's namespace. This class must be - # loaded during the Rails boot process. + # To extend Rails using a railtie, create a subclass of <tt>Rails::Railtie</tt>. + # This class must be loaded during the Rails boot process, and is conventionally + # called <tt>MyNamespace::Railtie</tt>. # - # The following example demonstrates an extension which can be used with or without Rails. + # The following example demonstrates an extension which can be used with or + # without Rails. # # # lib/my_gem/railtie.rb # module MyGem @@ -45,8 +44,8 @@ module Rails # # == Initializers # - # To add an initialization step from your Railtie to Rails boot process, you just need - # to create an initializer block: + # To add an initialization step to the Rails boot process from your railtie, just + # define the initialization code with the +initializer+ macro: # # class MyRailtie < Rails::Railtie # initializer "my_railtie.configure_rails_initialization" do @@ -55,7 +54,7 @@ module Rails # end # # If specified, the block can also receive the application object, in case you - # need to access some application specific configuration, like middleware: + # need to access some application-specific configuration, like middleware: # # class MyRailtie < Rails::Railtie # initializer "my_railtie.configure_rails_initialization" do |app| @@ -63,54 +62,54 @@ module Rails # end # end # - # Finally, you can also pass <tt>:before</tt> and <tt>:after</tt> as option to initializer, - # in case you want to couple it with a specific step in the initialization process. + # Finally, you can also pass <tt>:before</tt> and <tt>:after</tt> as options to + # +initializer+, in case you want to couple it with a specific step in the + # initialization process. # # == Configuration # - # Inside the Railtie class, you can access a config object which contains configuration - # shared by all railties and the application: + # Railties can access a config object which contains configuration shared by all + # railties and the application: # # class MyRailtie < Rails::Railtie # # Customize the ORM # config.app_generators.orm :my_railtie_orm # # # Add a to_prepare block which is executed once in production - # # and before each request in development + # # and before each request in development. # config.to_prepare do # MyRailtie.setup! # end # end # - # == Loading rake tasks and generators + # == Loading Rake Tasks and Generators # - # If your railtie has rake tasks, you can tell Rails to load them through the method - # rake_tasks: + # If your railtie has Rake tasks, you can tell Rails to load them through the method + # +rake_tasks+: # # class MyRailtie < Rails::Railtie # rake_tasks do - # load "path/to/my_railtie.tasks" + # load 'path/to/my_railtie.tasks' # end # end # # By default, Rails loads generators from your load path. However, if you want to place - # your generators at a different location, you can specify in your Railtie a block which + # your generators at a different location, you can specify in your railtie a block which # will load them during normal generators lookup: # # class MyRailtie < Rails::Railtie # generators do - # require "path/to/my_railtie_generator" + # require 'path/to/my_railtie_generator' # end # end # # == Application and Engine # - # A Rails::Engine is nothing more than a Railtie with some initializers already set. - # And since Rails::Application is an engine, the same configuration described here - # can be used in both. + # An engine is nothing more than a railtie with some initializers already set. And since + # <tt>Rails::Application</tt> is an engine, the same configuration described here can be + # used in both. # # Be sure to look at the documentation of those specific classes for more information. - # class Railtie autoload :Configuration, "rails/railtie/configuration" @@ -183,7 +182,7 @@ module Rails end protected - def generate_railtie_name(string) + def generate_railtie_name(string) #:nodoc: ActiveSupport::Inflector.underscore(string).tr("/", "_") end @@ -200,51 +199,54 @@ module Rails delegate :railtie_name, to: :class - def initialize + def initialize #:nodoc: if self.class.abstract_railtie? raise "#{self.class.name} is abstract, you cannot instantiate it directly." end end - def configure(&block) + def configure(&block) #:nodoc: instance_eval(&block) end + # This is used to create the <tt>config</tt> object on Railties, an instance of + # Railtie::Configuration, that is used by Railties and Application to store + # related configuration. def config @config ||= Railtie::Configuration.new end - def railtie_namespace + def railtie_namespace #:nodoc: @railtie_namespace ||= self.class.parents.detect { |n| n.respond_to?(:railtie_namespace) } end protected - def run_console_blocks(app) #:nodoc: - each_registered_block(:console) { |block| block.call(app) } - end + def run_console_blocks(app) #:nodoc: + each_registered_block(:console) { |block| block.call(app) } + end - def run_generators_blocks(app) #:nodoc: - each_registered_block(:generators) { |block| block.call(app) } - end + def run_generators_blocks(app) #:nodoc: + each_registered_block(:generators) { |block| block.call(app) } + end - def run_runner_blocks(app) #:nodoc: - each_registered_block(:runner) { |block| block.call(app) } - end + def run_runner_blocks(app) #:nodoc: + each_registered_block(:runner) { |block| block.call(app) } + end - def run_tasks_blocks(app) #:nodoc: - extend Rake::DSL - each_registered_block(:rake_tasks) { |block| instance_exec(app, &block) } - end + def run_tasks_blocks(app) #:nodoc: + extend Rake::DSL + each_registered_block(:rake_tasks) { |block| instance_exec(app, &block) } + end private - def each_registered_block(type, &block) - klass = self.class - while klass.respond_to?(type) - klass.public_send(type).each(&block) - klass = klass.superclass + def each_registered_block(type, &block) + klass = self.class + while klass.respond_to?(type) + klass.public_send(type).each(&block) + klass = klass.superclass + end end - end end end diff --git a/railties/lib/rails/railtie/configurable.rb b/railties/lib/rails/railtie/configurable.rb index 1572af0b2a..39f1f87575 100644 --- a/railties/lib/rails/railtie/configurable.rb +++ b/railties/lib/rails/railtie/configurable.rb @@ -1,4 +1,4 @@ -require 'active_support/concern' +require "active_support/concern" module Rails class Railtie @@ -9,7 +9,7 @@ module Rails delegate :config, to: :instance def inherited(base) - raise "You cannot inherit from a #{self.superclass.name} child" + raise "You cannot inherit from a #{superclass.name} child" end def instance @@ -26,9 +26,9 @@ module Rails protected - def method_missing(*args, &block) - instance.send(*args, &block) - end + def method_missing(*args, &block) + instance.send(*args, &block) + end end end end diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb index eb3b2d8ef4..aecc81c491 100644 --- a/railties/lib/rails/railtie/configuration.rb +++ b/railties/lib/rails/railtie/configuration.rb @@ -1,4 +1,4 @@ -require 'rails/configuration' +require "rails/configuration" module Rails class Railtie diff --git a/railties/lib/rails/ruby_version_check.rb b/railties/lib/rails/ruby_version_check.rb index 67a19d8a94..b212835df7 100644 --- a/railties/lib/rails/ruby_version_check.rb +++ b/railties/lib/rails/ruby_version_check.rb @@ -1,4 +1,4 @@ -if RUBY_VERSION < '2.2.2' && RUBY_ENGINE == 'ruby' +if RUBY_VERSION < "2.2.2" && RUBY_ENGINE == "ruby" desc = defined?(RUBY_DESCRIPTION) ? RUBY_DESCRIPTION : "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})" abort <<-end_message diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb index 8dd87b6cc5..f0df76d3f3 100644 --- a/railties/lib/rails/source_annotation_extractor.rb +++ b/railties/lib/rails/source_annotation_extractor.rb @@ -1,9 +1,9 @@ # Implements the logic behind the rake tasks for annotations like # -# rake notes -# rake notes:optimize +# rails notes +# rails notes:optimize # -# and friends. See <tt>rake -T notes</tt> and <tt>railties/lib/rails/tasks/annotations.rake</tt>. +# and friends. See <tt>rails -T notes</tt> and <tt>railties/lib/rails/tasks/annotations.rake</tt>. # # Annotation objects are triplets <tt>:line</tt>, <tt>:tag</tt>, <tt>:text</tt> that # represent the line where the annotation lives, its tag, and its text. Note @@ -15,7 +15,13 @@ class SourceAnnotationExtractor class Annotation < Struct.new(:line, :tag, :text) def self.directories - @@directories ||= %w(app config db lib test) + (ENV['SOURCE_ANNOTATION_DIRECTORIES'] || '').split(',') + @@directories ||= %w(app config db lib test) + (ENV["SOURCE_ANNOTATION_DIRECTORIES"] || "").split(",") + end + + # Registers additional directories to be included + # SourceAnnotationExtractor::Annotation.register_directories("spec","another") + def self.register_directories(*dirs) + directories.push(*dirs) end def self.extensions diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb index d3e33584d7..48675b3845 100644 --- a/railties/lib/rails/tasks.rb +++ b/railties/lib/rails/tasks.rb @@ -1,4 +1,4 @@ -require 'rake' +require "rake" # Load Rails Rakefile extensions %w( @@ -13,7 +13,7 @@ require 'rake' routes tmp ).tap { |arr| - arr << 'statistics' if Rake.application.current_scope.empty? + arr << "statistics" if Rake.application.current_scope.empty? }.each do |task| load "rails/tasks/#{task}.rake" end diff --git a/railties/lib/rails/tasks/annotations.rake b/railties/lib/rails/tasks/annotations.rake index 386ecf44be..9a69eb9934 100644 --- a/railties/lib/rails/tasks/annotations.rake +++ b/railties/lib/rails/tasks/annotations.rake @@ -1,4 +1,4 @@ -require 'rails/source_annotation_extractor' +require "rails/source_annotation_extractor" desc "Enumerate all annotations (use notes:optimize, :fixme, :todo for focus)" task :notes do @@ -15,6 +15,6 @@ namespace :notes do desc "Enumerate a custom annotation, specify with ANNOTATION=CUSTOM" task :custom do - SourceAnnotationExtractor.enumerate ENV['ANNOTATION'] + SourceAnnotationExtractor.enumerate ENV["ANNOTATION"] end -end
\ No newline at end of file +end diff --git a/railties/lib/rails/tasks/dev.rake b/railties/lib/rails/tasks/dev.rake index 4593100465..334e968123 100644 --- a/railties/lib/rails/tasks/dev.rake +++ b/railties/lib/rails/tasks/dev.rake @@ -1,14 +1,8 @@ +require "rails/dev_caching" + namespace :dev do - desc 'Toggle development mode caching on/off' + desc "Toggle development mode caching on/off" task :cache do - if File.exist? 'tmp/caching-dev.txt' - File.delete 'tmp/caching-dev.txt' - puts 'Development mode is no longer being cached.' - else - FileUtils.touch 'tmp/caching-dev.txt' - puts 'Development mode is now being cached.' - end - - FileUtils.touch 'tmp/restart.txt' + Rails::DevCaching.enable_by_file end end diff --git a/railties/lib/rails/tasks/engine.rake b/railties/lib/rails/tasks/engine.rake index e678103f63..c92b42f6c1 100644 --- a/railties/lib/rails/tasks/engine.rake +++ b/railties/lib/rails/tasks/engine.rake @@ -2,7 +2,7 @@ task "load_app" do namespace :app do load APP_RAKEFILE end - task :environment => "app:environment" + task environment: "app:environment" if !defined?(ENGINE_ROOT) || !ENGINE_ROOT ENGINE_ROOT = find_engine_path(APP_RAKEFILE) @@ -26,11 +26,11 @@ namespace :db do desc "Display status of migrations" app_task "migrate:status" - desc 'Create the database from config/database.yml for the current Rails.env (use db:create:all to create all databases in the config)' + desc "Create the database from config/database.yml for the current Rails.env (use db:create:all to create all databases in the config)" app_task "create" app_task "create:all" - desc 'Drops the database for the current Rails.env (use db:drop:all to drop all databases)' + desc "Drops the database for the current Rails.env (use db:drop:all to drop all databases)" app_task "drop" app_task "drop:all" @@ -65,7 +65,7 @@ def find_engine_path(path) if Rails::Engine.find(path) path else - find_engine_path(File.expand_path('..', path)) + find_engine_path(File.expand_path("..", path)) end end diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index 7601836809..a2167796eb 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -1,14 +1,16 @@ -namespace :rails do +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" ] + task update: [ "update:configs", "update:bin", "update:upgrade_guide_info" ] desc "Applies the template supplied by LOCATION=(/path/to/template) or URL" task template: :environment do template = ENV["LOCATION"] raise "No LOCATION value given. Please set LOCATION either as path to a file or a URL" if template.blank? template = File.expand_path(template) if template !~ %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} - require 'rails/generators' - require 'rails/generators/rails/app/app_generator' + require "rails/generators" + require "rails/generators/rails/app/app_generator" generator = Rails::Generators::AppGenerator.new [Rails.root], {}, destination_root: Rails.root generator.apply template, verbose: false end @@ -24,12 +26,12 @@ namespace :rails do default_templates.each do |type, names| local_template_type_dir = File.join(project_templates, type) - FileUtils.mkdir_p local_template_type_dir + mkdir_p local_template_type_dir, verbose: false names.each do |name| dst_name = File.join(local_template_type_dir, name) src_name = File.join(generators_lib, type, name, "templates") - FileUtils.cp_r src_name, dst_name + cp_r src_name, dst_name, verbose: false end end end @@ -43,10 +45,11 @@ namespace :rails do def self.app_generator @app_generator ||= begin - require 'rails/generators' - require 'rails/generators/rails/app/app_generator' - gen = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true, api: !!Rails.application.config.api_only }, - destination_root: Rails.root + require "rails/generators" + require "rails/generators/rails/app/app_generator" + gen = Rails::Generators::AppGenerator.new ["rails"], + { api: !!Rails.application.config.api_only, update: true }, + destination_root: Rails.root File.exist?(Rails.root.join("config", "application.rb")) ? gen.send(:app_const) : gen.send(:valid_const?) gen @@ -64,5 +67,21 @@ namespace :rails do task :bin do RailsUpdate.invoke_from_app_generator :create_bin_files end + + task :upgrade_guide_info do + RailsUpdate.invoke_from_app_generator :display_upgrade_guide_info + 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/initializers.rake b/railties/lib/rails/tasks/initializers.rake index 2968b5cb53..6522f2ae5a 100644 --- a/railties/lib/rails/tasks/initializers.rake +++ b/railties/lib/rails/tasks/initializers.rake @@ -1,6 +1,6 @@ desc "Print out all defined initializers in the order they are invoked by Rails." task initializers: :environment do Rails.application.initializers.tsort_each do |initializer| - puts initializer.name + puts "#{initializer.context_class}.#{initializer.name}" end end diff --git a/railties/lib/rails/tasks/log.rake b/railties/lib/rails/tasks/log.rake index 073f235ec5..c376234fee 100644 --- a/railties/lib/rails/tasks/log.rake +++ b/railties/lib/rails/tasks/log.rake @@ -1,8 +1,8 @@ namespace :log do - - ## + + ## # Truncates all/specified log files - # ENV['LOGS'] + # ENV['LOGS'] # - defaults to standard environment 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 @@ -14,21 +14,21 @@ namespace :log do end def log_files - if ENV['LOGS'] == 'all' + if ENV["LOGS"] == "all" FileList["log/*.log"] - elsif ENV['LOGS'] - log_files_to_truncate(ENV['LOGS']) + elsif ENV["LOGS"] + log_files_to_truncate(ENV["LOGS"]) else log_files_to_truncate("development,test,production") end end def log_files_to_truncate(envs) - envs.split(',') + envs.split(",") .map { |file| "log/#{file.strip}.log" } .select { |file| File.exist?(file) } end - + def clear_log_file(file) f = File.open(file, "w") f.close diff --git a/railties/lib/rails/tasks/middleware.rake b/railties/lib/rails/tasks/middleware.rake index 31e961b483..fd98be1ea9 100644 --- a/railties/lib/rails/tasks/middleware.rake +++ b/railties/lib/rails/tasks/middleware.rake @@ -1,4 +1,4 @@ -desc 'Prints out your Rack middleware stack' +desc "Prints out your Rack middleware stack" task middleware: :environment do Rails.configuration.middleware.each do |middleware| puts "use #{middleware.inspect}" diff --git a/railties/lib/rails/tasks/misc.rake b/railties/lib/rails/tasks/misc.rake index 4195106961..29ea0ff804 100644 --- a/railties/lib/rails/tasks/misc.rake +++ b/railties/lib/rails/tasks/misc.rake @@ -1,43 +1,60 @@ -desc 'Generate a cryptographically secure secret key (this is typically used to generate a secret for cookie sessions).' +desc "Generate a cryptographically secure secret key (this is typically used to generate a secret for cookie sessions)." task :secret do - require 'securerandom' + require "securerandom" puts SecureRandom.hex(64) end -desc 'List versions of all Rails frameworks and the environment' +desc "List versions of all Rails frameworks and the environment" task about: :environment do puts Rails::Info end namespace :time do + desc "List all time zones, list by two-letter country code (`rails time:zones[US]`), or list by UTC offset (`rails time:zones[-8]`)" + task :zones, :country_or_offset do |t, args| + zones, offset = ActiveSupport::TimeZone.all, nil + + if country_or_offset = args[:country_or_offset] + begin + zones = ActiveSupport::TimeZone.country_zones(country_or_offset) + rescue TZInfo::InvalidCountryCode + offset = country_or_offset + end + end + + build_time_zone_list zones, offset + end + namespace :zones do - desc 'Displays all time zones, also available: time:zones:us, time:zones:local -- filter with OFFSET parameter, e.g., OFFSET=-6' + # desc 'Displays all time zones, also available: time:zones:us, time:zones:local -- filter with OFFSET parameter, e.g., OFFSET=-6' task :all do - build_time_zone_list(:all) + build_time_zone_list ActiveSupport::TimeZone.all end # desc 'Displays names of US time zones recognized by the Rails TimeZone class, grouped by offset. Results can be filtered with optional OFFSET parameter, e.g., OFFSET=-6' task :us do - build_time_zone_list(:us_zones) + build_time_zone_list ActiveSupport::TimeZone.us_zones end # desc 'Displays names of time zones recognized by the Rails TimeZone class with the same offset as the system local time' task :local do - require 'active_support' - require 'active_support/time' + require "active_support" + require "active_support/time" + jan_offset = Time.now.beginning_of_year.utc_offset jul_offset = Time.now.beginning_of_year.change(month: 7).utc_offset offset = jan_offset < jul_offset ? jan_offset : jul_offset - build_time_zone_list(:all, offset) + + build_time_zone_list(ActiveSupport::TimeZone.all, offset) end # to find UTC -06:00 zones, OFFSET can be set to either -6, -6:00 or 21600 - def build_time_zone_list(method, offset = ENV['OFFSET']) - require 'active_support' - require 'active_support/time' + def build_time_zone_list(zones, offset = ENV["OFFSET"]) + require "active_support" + require "active_support/time" if offset offset = if offset.to_s.match(/(\+|-)?(\d+):(\d+)/) - sign = $1 == '-' ? -1 : 1 + sign = $1 == "-" ? -1 : 1 hours, minutes = $2.to_f, $3.to_f ((hours * 3600) + (minutes.to_f * 60)) * sign elsif offset.to_f.abs <= 13 @@ -47,7 +64,7 @@ namespace :time do end end previous_offset = nil - ActiveSupport::TimeZone.__send__(method).each do |zone| + zones.each do |zone| if offset.nil? || offset == zone.utc_offset puts "\n* UTC #{zone.formatted_offset} *" unless zone.utc_offset == previous_offset puts zone.name diff --git a/railties/lib/rails/tasks/restart.rake b/railties/lib/rails/tasks/restart.rake index f36c86d81b..03177d9954 100644 --- a/railties/lib/rails/tasks/restart.rake +++ b/railties/lib/rails/tasks/restart.rake @@ -1,5 +1,8 @@ desc "Restart app by touching tmp/restart.txt" task :restart do - FileUtils.mkdir_p('tmp') - FileUtils.touch('tmp/restart.txt') + verbose(false) do + mkdir_p "tmp" + touch "tmp/restart.txt" + rm_f "tmp/pids/server.pid" + end end diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake index 69103aa5d9..f5e5b9ae87 100644 --- a/railties/lib/rails/tasks/routes.rake +++ b/railties/lib/rails/tasks/routes.rake @@ -1,13 +1,13 @@ -require 'active_support/deprecation' -require 'active_support/core_ext/string/strip' # for strip_heredoc -require 'optparse' +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' +desc "Print out all defined routes in match order, with names. Target specific controller with -c option, or grep routes using -g option" task routes: :environment do all_routes = Rails.application.routes.routes - require 'action_dispatch/routing/inspector' + require "action_dispatch/routing/inspector" inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes) - if ARGV.any?{ |argv| argv.start_with? 'CONTROLLER' } + 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. @@ -15,10 +15,13 @@ task routes: :environment do end routes_filter = nil - routes_filter = { controller: ENV['CONTROLLER'] } if ENV['CONTROLLER'] + routes_filter = { controller: ENV["CONTROLLER"] } if ENV["CONTROLLER"] OptionParser.new do |opts| opts.banner = "Usage: rails routes [options]" + + Rake.application.standard_rake_options.each { |args| opts.on(*args) } + opts.on("-c CONTROLLER") do |controller| routes_filter = { controller: controller } end diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake index a919d36939..a6cdd1e99c 100644 --- a/railties/lib/rails/tasks/statistics.rake +++ b/railties/lib/rails/tasks/statistics.rake @@ -7,6 +7,7 @@ STATS_DIRECTORIES = [ %w(Jobs app/jobs), %w(Models app/models), %w(Mailers app/mailers), + %w(Channels app/channels), %w(Javascripts app/assets/javascripts), %w(Libraries lib/), %w(Tasks lib/tasks), @@ -23,6 +24,6 @@ end.select { |name, dir| File.directory?(dir) } desc "Report code statistics (KLOCs, etc) from the application or engine" task :stats do - require 'rails/code_statistics' + require "rails/code_statistics" CodeStatistics.new(*STATS_DIRECTORIES).to_s end diff --git a/railties/lib/rails/tasks/tmp.rake b/railties/lib/rails/tasks/tmp.rake index 9162ef234a..d42a890cb6 100644 --- a/railties/lib/rails/tasks/tmp.rake +++ b/railties/lib/rails/tasks/tmp.rake @@ -2,12 +2,10 @@ namespace :tmp do desc "Clear cache and socket files from tmp/ (narrow w/ tmp:cache:clear, tmp:sockets:clear)" task clear: ["tmp:cache:clear", "tmp:sockets:clear"] - tmp_dirs = [ 'tmp/cache', - 'tmp/sockets', - 'tmp/pids', - 'tmp/cache/assets/development', - 'tmp/cache/assets/test', - 'tmp/cache/assets/production' ] + tmp_dirs = [ "tmp/cache", + "tmp/sockets", + "tmp/pids", + "tmp/cache/assets" ] tmp_dirs.each { |d| directory d } @@ -17,21 +15,21 @@ namespace :tmp do namespace :cache do # desc "Clears all files and directories in tmp/cache" task :clear do - FileUtils.rm_rf(Dir['tmp/cache/[^.]*']) + rm_rf Dir["tmp/cache/[^.]*"], verbose: false end end namespace :sockets do # desc "Clears all files in tmp/sockets" task :clear do - FileUtils.rm(Dir['tmp/sockets/[^.]*']) + rm Dir["tmp/sockets/[^.]*"], verbose: false end end namespace :pids do # desc "Clears all files in tmp/pids" task :clear do - FileUtils.rm(Dir['tmp/pids/[^.]*']) + rm Dir["tmp/pids/[^.]*"], verbose: false end end end diff --git a/railties/lib/rails/templates/rails/mailers/email.html.erb b/railties/lib/rails/templates/rails/mailers/email.html.erb index fed96fbc85..c63781ed0c 100644 --- a/railties/lib/rails/templates/rails/mailers/email.html.erb +++ b/railties/lib/rails/templates/rails/mailers/email.html.erb @@ -88,7 +88,10 @@ <% unless @email.attachments.nil? || @email.attachments.empty? %> <dt>Attachments:</dt> <dd> - <%= @email.attachments.map { |a| a.respond_to?(:original_filename) ? a.original_filename : a.filename }.join(', ') %> + <% @email.attachments.each do |a| %> + <% filename = a.respond_to?(:original_filename) ? a.original_filename : a.filename %> + <%= link_to filename, "data:application/octet-stream;charset=utf-8;base64,#{Base64.encode64(a.body.to_s)}", download: filename %> + <% end %> </dd> <% end %> diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index 5cc1b5b219..db341dd847 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -3,13 +3,13 @@ abort("Abort testing: Your Rails environment is running in production mode!") if Rails.env.production? require "rails/test_unit/minitest_plugin" -require 'active_support/test_case' -require 'action_controller' -require 'action_controller/test_case' -require 'action_dispatch/testing/integration' -require 'rails/generators/test_case' +require "active_support/test_case" +require "action_controller" +require "action_controller/test_case" +require "action_dispatch/testing/integration" +require "rails/generators/test_case" -require 'active_support/testing/autorun' +require "active_support/testing/autorun" if defined?(ActiveRecord::Base) ActiveRecord::Migration.maintain_test_schema! diff --git a/railties/lib/rails/test_unit/line_filtering.rb b/railties/lib/rails/test_unit/line_filtering.rb index dd9732bb12..32ba744701 100644 --- a/railties/lib/rails/test_unit/line_filtering.rb +++ b/railties/lib/rails/test_unit/line_filtering.rb @@ -1,4 +1,4 @@ -require 'method_source' +require "method_source" module Rails module LineFiltering # :nodoc: @@ -39,7 +39,7 @@ module Rails def derive_line_filters(patterns) patterns.flat_map do |file_and_line| - file, *lines = file_and_line.split(':') + file, *lines = file_and_line.split(":") if lines.empty? Filter.new(@runnable, file, nil) if file diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb index 29a3d991b8..6e196a32ab 100644 --- a/railties/lib/rails/test_unit/minitest_plugin.rb +++ b/railties/lib/rails/test_unit/minitest_plugin.rb @@ -1,6 +1,7 @@ require "active_support/core_ext/module/attribute_accessors" require "rails/test_unit/reporter" require "rails/test_unit/test_requirer" +require "shellwords" module Minitest class SuppressedSummaryReporter < SummaryReporter @@ -42,7 +43,7 @@ module Minitest end opts.on("-f", "--fail-fast", - "Abort test run on first failure") do + "Abort test run on first failure or error") do options[:fail_fast] = true end @@ -53,26 +54,36 @@ module Minitest options[:color] = true options[:output_inline] = true - options[:patterns] = opts.order! + options[:patterns] = defined?(@rake_patterns) ? @rake_patterns : opts.order! 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 - passed = run - exit passed unless passed - passed + autorun end - def self.plugin_rails_init(options) - self.run_with_rails_extension = true + module RunRespectingRakeTestopts + def run(args = []) + if defined?(@rake_patterns) + args = Shellwords.split(ENV["TESTOPTS"] || "") + end + + super + end + end + + singleton_class.prepend RunRespectingRakeTestopts + # Owes great inspiration to test runner trailblazers like RSpec, + # minitest-reporters, maxitest and others. + def self.plugin_rails_init(options) ENV["RAILS_ENV"] = options[:environment] || "test" - unless run_with_autorun - patterns = defined?(@rake_patterns) ? @rake_patterns : options[:patterns] - ::Rails::TestRequirer.require_files(patterns) + # If run via `ruby` we've been passed the files to run directly. + unless run_via[:ruby] + ::Rails::TestRequirer.require_files(options[:patterns]) end unless options[:full_backtrace] || ENV["BACKTRACE"] @@ -81,14 +92,17 @@ module Minitest end # Replace progress reporter for colors. - self.reporter.reporters.delete_if { |reporter| reporter.kind_of?(SummaryReporter) || reporter.kind_of?(ProgressReporter) } - self.reporter << SuppressedSummaryReporter.new(options[:io], options) - self.reporter << ::Rails::TestUnitReporter.new(options[:io], options) + reporter.reporters.delete_if { |reporter| reporter.kind_of?(SummaryReporter) || reporter.kind_of?(ProgressReporter) } + reporter << SuppressedSummaryReporter.new(options[:io], options) + reporter << ::Rails::TestUnitReporter.new(options[:io], options) end - mattr_accessor(:run_with_autorun) { false } - mattr_accessor(:run_with_rails_extension) { false } + mattr_accessor(:run_via) { Hash.new } end +# Put Rails as the first plugin minitest initializes so other plugins +# can override or replace our default reporter setup. +# Since minitest only loads plugins if its extensions are empty we have +# to call `load_plugins` first. Minitest.load_plugins -Minitest.extensions << 'rails' +Minitest.extensions.unshift "rails" diff --git a/railties/lib/rails/test_unit/railtie.rb b/railties/lib/rails/test_unit/railtie.rb index 511cee33bd..746120e6a1 100644 --- a/railties/lib/rails/test_unit/railtie.rb +++ b/railties/lib/rails/test_unit/railtie.rb @@ -1,7 +1,7 @@ -require 'rails/test_unit/line_filtering' +require "rails/test_unit/line_filtering" if defined?(Rake.application) && Rake.application.top_level_tasks.grep(/^(default$|test(:|$))/).any? - ENV['RAILS_ENV'] ||= 'test' + ENV["RAILS_ENV"] ||= "test" end module Rails @@ -14,7 +14,9 @@ module Rails end initializer "test_unit.line_filtering" do - ActiveSupport::TestCase.extend Rails::LineFiltering + ActiveSupport.on_load(:active_support_test_case) { + ActiveSupport::TestCase.extend Rails::LineFiltering + } end rake_tasks do diff --git a/railties/lib/rails/test_unit/reporter.rb b/railties/lib/rails/test_unit/reporter.rb index 73b8d7d27b..fe11664d5e 100644 --- a/railties/lib/rails/test_unit/reporter.rb +++ b/railties/lib/rails/test_unit/reporter.rb @@ -18,13 +18,13 @@ module Rails if output_inline? && result.failure && (!result.skipped? || options[:verbose]) io.puts io.puts - io.puts format_failures(result).map { |line| color_output(line, by: result) } + io.puts color_output(result, by: result) io.puts io.puts format_rerun_snippet(result) io.puts end - if fail_fast? && result.failure && !result.error? && !result.skipped? + if fail_fast? && result.failure && !result.skipped? raise Interrupt end end @@ -50,7 +50,7 @@ module Rails end def relative_path_for(file) - file.sub(/^#{app_root}\/?/, '') + file.sub(/^#{app_root}\/?/, "") end private @@ -66,21 +66,9 @@ module Rails "%s#%s = %.2f s = %s" % [result.class, result.name, result.time, result.result_code] end - def format_failures(result) - result.failures.map do |failure| - "#{failure.result_label}:\n#{result.class}##{result.name}:\n#{failure.message}\n" - end - end - def format_rerun_snippet(result) - # Try to extract path to assertion from backtrace. - if result.location =~ /\[(.*)\]\z/ - assertion_path = $1 - else - assertion_path = result.method(result.name).source_location.join(':') - end - - "#{self.executable} #{relative_path_for(assertion_path)}" + location, line = result.method(result.name).source_location + "#{executable} #{relative_path_for(location)}:#{line}" end def app_root diff --git a/railties/lib/rails/test_unit/test_requirer.rb b/railties/lib/rails/test_unit/test_requirer.rb index 8b211ce130..fe35934abc 100644 --- a/railties/lib/rails/test_unit/test_requirer.rb +++ b/railties/lib/rails/test_unit/test_requirer.rb @@ -1,5 +1,5 @@ -require 'active_support/core_ext/object/blank' -require 'rake/file_list' +require "active_support/core_ext/object/blank" +require "rake/file_list" module Rails class TestRequirer # :nodoc: @@ -7,7 +7,7 @@ module Rails def require_files(patterns) patterns = expand_patterns(patterns) - Rake::FileList[patterns.compact.presence || 'test/**/*_test.rb'].to_a.each do |file| + Rake::FileList[patterns.compact.presence || "test/**/*_test.rb"].to_a.each do |file| require File.expand_path(file) end end @@ -15,7 +15,7 @@ module Rails private def expand_patterns(patterns) patterns.map do |arg| - arg = arg.gsub(/(:\d+)+?$/, '') + arg = arg.gsub(/(:\d+)+?$/, "") if Dir.exist?(arg) "#{arg}/**/*_test.rb" else diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake index 41921e43f3..4c157c1262 100644 --- a/railties/lib/rails/test_unit/testing.rake +++ b/railties/lib/rails/test_unit/testing.rake @@ -1,17 +1,17 @@ -gem 'minitest' -require 'minitest' -require 'rails/test_unit/minitest_plugin' +gem "minitest" +require "minitest" +require "rails/test_unit/minitest_plugin" task default: :test desc "Runs all tests in test folder" task :test do $: << "test" - pattern = if ENV.key?('TEST') - ENV['TEST'] - else - "test" - end + pattern = if ENV.key?("TEST") + ENV["TEST"] + else + "test" + end Minitest.rake_run([pattern]) end @@ -21,10 +21,10 @@ namespace :test do # If used with Active Record, this task runs before the database schema is synchronized. end - task :run => %w[test] + task run: %w[test] desc "Run tests quickly, but also reset db" - task :db => %w[db:test:prepare test] + task db: %w[db:test:prepare test] ["models", "helpers", "controllers", "mailers", "integration", "jobs"].each do |name| task name => "test:prepare" do @@ -33,17 +33,17 @@ namespace :test do end end - task :generators => "test:prepare" do + task generators: "test:prepare" do $: << "test" Minitest.rake_run(["test/lib/generators"]) end - task :units => "test:prepare" do + task units: "test:prepare" do $: << "test" Minitest.rake_run(["test/models", "test/helpers", "test/unit"]) end - task :functionals => "test:prepare" do + task functionals: "test:prepare" do $: << "test" Minitest.rake_run(["test/controllers", "test/mailers", "test/functional"]) end diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb index df351c4238..3d8e8291d1 100644 --- a/railties/lib/rails/version.rb +++ b/railties/lib/rails/version.rb @@ -1,4 +1,4 @@ -require_relative 'gem_version' +require_relative "gem_version" module Rails # Returns the version of the currently loaded Rails as a string. diff --git a/railties/lib/rails/welcome_controller.rb b/railties/lib/rails/welcome_controller.rb index de9cd18b01..b757dc72ef 100644 --- a/railties/lib/rails/welcome_controller.rb +++ b/railties/lib/rails/welcome_controller.rb @@ -1,4 +1,4 @@ -require 'rails/application_controller' +require "rails/application_controller" class Rails::WelcomeController < Rails::ApplicationController # :nodoc: layout false diff --git a/railties/railties.gemspec b/railties/railties.gemspec index a06336f698..76de2b4639 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -1,34 +1,34 @@ -version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip +version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY - s.name = 'railties' + s.name = "railties" s.version = version - s.summary = 'Tools for creating, working with, and running Rails applications.' - s.description = 'Rails internals: application bootup, plugins, generators, and rake tasks.' + s.summary = "Tools for creating, working with, and running Rails applications." + s.description = "Rails internals: application bootup, plugins, generators, and rake tasks." - s.required_ruby_version = '>= 2.2.2' + s.required_ruby_version = ">= 2.2.2" - s.license = 'MIT' + s.license = "MIT" - s.author = 'David Heinemeier Hansson' - s.email = 'david@loudthinking.com' - s.homepage = 'http://www.rubyonrails.org' + s.author = "David Heinemeier Hansson" + s.email = "david@loudthinking.com" + s.homepage = "http://rubyonrails.org" - s.files = Dir['CHANGELOG.md', 'README.rdoc', 'MIT-LICENSE', 'RDOC_MAIN.rdoc', 'exe/**/*', 'lib/**/{*,.[a-z]*}'] - s.require_path = 'lib' + s.files = Dir["CHANGELOG.md", "README.rdoc", "MIT-LICENSE", "RDOC_MAIN.rdoc", "exe/**/*", "lib/**/{*,.[a-z]*}"] + s.require_path = "lib" - s.bindir = 'exe' - s.executables = ['rails'] + s.bindir = "exe" + s.executables = ["rails"] - s.rdoc_options << '--exclude' << '.' + s.rdoc_options << "--exclude" << "." - s.add_dependency 'activesupport', version - s.add_dependency 'actionpack', version + s.add_dependency "activesupport", version + s.add_dependency "actionpack", version - s.add_dependency 'rake', '>= 0.8.7' - s.add_dependency 'thor', '>= 0.18.1', '< 2.0' - s.add_dependency 'method_source' + s.add_dependency "rake", ">= 0.8.7" + s.add_dependency "thor", ">= 0.18.1", "< 2.0" + s.add_dependency "method_source" - s.add_development_dependency 'actionview', version + s.add_development_dependency "actionview", version end diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb index 794d180e5d..fd1e1b9662 100644 --- a/railties/test/abstract_unit.rb +++ b/railties/test/abstract_unit.rb @@ -1,30 +1,28 @@ ENV["RAILS_ENV"] ||= "test" -require File.expand_path("../../../load_paths", __FILE__) +require "stringio" +require "active_support/testing/autorun" +require "active_support/testing/stream" +require "fileutils" -require 'stringio' -require 'active_support/testing/autorun' -require 'active_support/testing/stream' -require 'fileutils' - -require 'active_support' -require 'action_controller' -require 'action_view' -require 'rails/all' +require "active_support" +require "action_controller" +require "action_view" +require "rails/all" module TestApp class Application < Rails::Application config.root = File.dirname(__FILE__) - secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33' + secrets.secret_key_base = "b3c631c314c0bbca50c1b2843150fe33" end end # Skips the current run on Rubinius using Minitest::Assertions#skip -def rubinius_skip(message = '') - skip message if RUBY_ENGINE == 'rbx' +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 = '') +def jruby_skip(message = "") skip message if defined?(JRUBY_VERSION) end diff --git a/railties/test/app_loader_test.rb b/railties/test/app_loader_test.rb index 5946c8fd4c..7fd5c72c1c 100644 --- a/railties/test/app_loader_test.rb +++ b/railties/test/app_loader_test.rb @@ -1,6 +1,6 @@ -require 'tmpdir' -require 'abstract_unit' -require 'rails/app_loader' +require "tmpdir" +require "abstract_unit" +require "rails/app_loader" class AppLoaderTest < ActiveSupport::TestCase def loader @@ -27,12 +27,12 @@ class AppLoaderTest < ActiveSupport::TestCase end setup do - @tmp = Dir.mktmpdir('railties-rails-loader-test-suite') + @tmp = Dir.mktmpdir("railties-rails-loader-test-suite") @cwd = Dir.pwd Dir.chdir(@tmp) end - ['bin', 'script'].each do |script_dir| + ["bin", "script"].each do |script_dir| exe = "#{script_dir}/rails" test "is not in a Rails application if #{exe} is not found in the current or parent directories" do @@ -47,7 +47,7 @@ class AppLoaderTest < ActiveSupport::TestCase assert !loader.exec_app end - ['APP_PATH', 'ENGINE_PATH'].each do |keyword| + ["APP_PATH", "ENGINE_PATH"].each do |keyword| test "is in a Rails application if #{exe} exists and contains #{keyword}" do write exe, keyword @@ -66,7 +66,7 @@ class AppLoaderTest < ActiveSupport::TestCase write "foo/bar/#{exe}" write "foo/#{exe}", keyword - Dir.chdir('foo/bar') + Dir.chdir("foo/bar") loader.exec_app diff --git a/railties/test/application/asset_debugging_test.rb b/railties/test/application/asset_debugging_test.rb index bcb6aff0d7..3e17a1efa5 100644 --- a/railties/test/application/asset_debugging_test.rb +++ b/railties/test/application/asset_debugging_test.rb @@ -1,5 +1,5 @@ -require 'isolation/abstract_unit' -require 'rack/test' +require "isolation/abstract_unit" +require "rack/test" module ApplicationTests class AssetDebuggingTest < ActiveSupport::TestCase @@ -28,8 +28,6 @@ module ApplicationTests RUBY ENV["RAILS_ENV"] = "production" - - boot_rails end def teardown @@ -44,7 +42,7 @@ module ApplicationTests test "assets are concatenated when debug is off and compile is off either if debug_assets param is provided" do # config.assets.debug and config.assets.compile are false for production environment ENV["RAILS_ENV"] = "production" - output = Dir.chdir(app_path){ `bin/rails assets:precompile --trace 2>&1` } + output = Dir.chdir(app_path) { `bin/rails assets:precompile --trace 2>&1` } assert $?.success?, output # Load app env @@ -53,7 +51,7 @@ module ApplicationTests class ::PostsController < ActionController::Base ; end # the debug_assets params isn't used if compile is off - get '/posts?debug_assets=true' + get "/posts?debug_assets=true" assert_match(/<script src="\/assets\/application-([0-z]+)\.js"><\/script>/, last_response.body) assert_no_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js"><\/script>/, last_response.body) end @@ -66,9 +64,106 @@ module ApplicationTests class ::PostsController < ActionController::Base ; end - get '/posts?debug_assets=true' + get "/posts?debug_assets=true" assert_match(/<script src="\/assets\/application(\.self)?-([0-z]+)\.js\?body=1"><\/script>/, last_response.body) assert_match(/<script src="\/assets\/xmlhr(\.self)?-([0-z]+)\.js\?body=1"><\/script>/, last_response.body) end + + test "public path and tag methods are not over-written by the asset pipeline" do + contents = "doesnotexist" + cases = { + asset_path: %r{/#{contents}}, + image_path: %r{/images/#{contents}}, + video_path: %r{/videos/#{contents}}, + audio_path: %r{/audios/#{contents}}, + font_path: %r{/fonts/#{contents}}, + javascript_path: %r{/javascripts/#{contents}}, + stylesheet_path: %r{/stylesheets/#{contents}}, + image_tag: %r{<img src="/images/#{contents}"}, + favicon_link_tag: %r{<link rel="shortcut icon" type="image/x-icon" href="/images/#{contents}" />}, + stylesheet_link_tag: %r{<link rel="stylesheet" media="screen" href="/stylesheets/#{contents}.css" />}, + javascript_include_tag: %r{<script src="/javascripts/#{contents}.js">}, + audio_tag: %r{<audio src="/audios/#{contents}"></audio>}, + video_tag: %r{<video src="/videos/#{contents}"></video>} + } + + cases.each do |(view_method, tag_match)| + app_file "app/views/posts/index.html.erb", "<%= #{view_method} '#{contents}', skip_pipeline: true %>" + + app "development" + + class ::PostsController < ActionController::Base ; end + + get "/posts?debug_assets=true" + + body = last_response.body + assert_match(tag_match, body, "Expected `#{view_method}` to produce a match to #{tag_match}, but did not: #{body}") + end + end + + test "public url methods are not over-written by the asset pipeline" do + contents = "doesnotexist" + cases = { + asset_url: %r{http://example.org/#{contents}}, + image_url: %r{http://example.org/images/#{contents}}, + video_url: %r{http://example.org/videos/#{contents}}, + audio_url: %r{http://example.org/audios/#{contents}}, + font_url: %r{http://example.org/fonts/#{contents}}, + javascript_url: %r{http://example.org/javascripts/#{contents}}, + stylesheet_url: %r{http://example.org/stylesheets/#{contents}}, + } + + cases.each do |(view_method, tag_match)| + app_file "app/views/posts/index.html.erb", "<%= #{view_method} '#{contents}', skip_pipeline: true %>" + + app "development" + + class ::PostsController < ActionController::Base ; end + + get "/posts?debug_assets=true" + + body = last_response.body + assert_match(tag_match, body, "Expected `#{view_method}` to produce a match to #{tag_match}, but did not: #{body}") + end + end + + test "{ skip_pipeline: true } does not use the asset pipeline" do + cases = { + /\/assets\/application-.*.\.js/ => {}, + /application.js/ => { skip_pipeline: true }, + } + cases.each do |(tag_match, options_hash)| + app_file "app/views/posts/index.html.erb", "<%= asset_path('application.js', #{options_hash}) %>" + + app "development" + + class ::PostsController < ActionController::Base ; end + + get "/posts?debug_assets=true" + + body = last_response.body.strip + assert_match(tag_match, body, "Expected `asset_path` with `#{options_hash}` to produce a match to #{tag_match}, but did not: #{body}") + end + end + + test "public_compute_asset_path does not use the asset pipeline" do + cases = { + compute_asset_path: /\/assets\/application-.*.\.js/, + public_compute_asset_path: /application.js/, + } + + cases.each do |(view_method, tag_match)| + app_file "app/views/posts/index.html.erb", "<%= #{ view_method } 'application.js' %>" + + app "development" + + class ::PostsController < ActionController::Base ; end + + get "/posts?debug_assets=true" + + body = last_response.body.strip + assert_match(tag_match, body, "Expected `#{view_method}` to produce a match to #{ tag_match }, but did not: #{ body }") + end + end end end diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb index 2670fad618..741ae543e0 100644 --- a/railties/test/application/assets_test.rb +++ b/railties/test/application/assets_test.rb @@ -1,6 +1,6 @@ -require 'isolation/abstract_unit' -require 'rack/test' -require 'active_support/json' +require "isolation/abstract_unit" +require "rack/test" +require "active_support/json" module ApplicationTests class AssetsTest < ActiveSupport::TestCase @@ -9,7 +9,6 @@ module ApplicationTests def setup build_app(initializers: true) - boot_rails end def teardown @@ -36,7 +35,7 @@ module ApplicationTests def clean_assets! quietly do - assert Dir.chdir(app_path) { system('bin/rails assets:clobber') } + assert Dir.chdir(app_path) { system("bin/rails assets:clobber") } end end @@ -53,7 +52,7 @@ module ApplicationTests app_file "app/assets/images/rails.png", "notactuallyapng" app_file "app/assets/javascripts/demo.js.erb", "a = <%= image_path('rails.png').inspect %>;" - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get '*path', to: lambda { |env| [200, { "Content-Type" => "text/html" }, ["Not an asset"]] } end @@ -111,8 +110,8 @@ module ApplicationTests eoruby precompile! \ - RAILS_ENV: 'production', - DATABASE_URL: 'postgresql://baduser:badpass@127.0.0.1/dbname' + RAILS_ENV: "production", + DATABASE_URL: "postgresql://baduser:badpass@127.0.0.1/dbname" files = Dir["#{app_path}/public/assets/application-*.js"] files << Dir["#{app_path}/public/assets/foo/application-*.js"].first @@ -177,26 +176,46 @@ module ApplicationTests assert_file_exists("#{app_path}/public/assets/something/index-*.js") end - test 'precompile use assets defined in app env config' do - add_to_env_config 'production', 'config.assets.precompile = [ "something.js" ]' - app_file 'app/assets/javascripts/something.js.erb', 'alert();' + test "precompile use assets defined in app env config" do + add_to_env_config "production", 'config.assets.precompile = [ "something.js" ]' + app_file "app/assets/javascripts/something.js.erb", "alert();" - precompile! RAILS_ENV: 'production' + precompile! RAILS_ENV: "production" assert_file_exists("#{app_path}/public/assets/something-*.js") end - test 'precompile use assets defined in app config and reassigned in app env config' do + test "sprockets cache is not shared between environments" do + app_file "app/assets/images/rails.png", "notactuallyapng" + app_file "app/assets/stylesheets/application.css.erb", "body { background: '<%= asset_path('rails.png') %>'; }" + add_to_env_config "production", 'config.assets.prefix = "production_assets"' + + precompile! + + assert_file_exists("#{app_path}/public/assets/application-*.css") + + file = Dir["#{app_path}/public/assets/application-*.css"].first + assert_match(/assets\/rails-([0-z]+)\.png/, File.read(file)) + + precompile! RAILS_ENV: "production" + + assert_file_exists("#{app_path}/public/production_assets/application-*.css") + + file = Dir["#{app_path}/public/production_assets/application-*.css"].first + assert_match(/production_assets\/rails-([0-z]+)\.png/, File.read(file)) + end + + test "precompile use assets defined in app config and reassigned in app env config" do add_to_config 'config.assets.precompile = [ "something_manifest.js" ]' - add_to_env_config 'production', 'config.assets.precompile += [ "another_manifest.js" ]' + add_to_env_config "production", 'config.assets.precompile += [ "another_manifest.js" ]' - app_file 'app/assets/config/something_manifest.js', '//= link something.js' - app_file 'app/assets/config/another_manifest.js', '//= link another.js' + app_file "app/assets/config/something_manifest.js", "//= link something.js" + app_file "app/assets/config/another_manifest.js", "//= link another.js" - app_file 'app/assets/javascripts/something.js.erb', 'alert();' - app_file 'app/assets/javascripts/another.js.erb', 'alert();' + app_file "app/assets/javascripts/something.js.erb", "alert();" + app_file "app/assets/javascripts/another.js.erb", "alert();" - precompile! RAILS_ENV: 'production' + precompile! RAILS_ENV: "production" assert_file_exists("#{app_path}/public/assets/something_manifest-*.js") assert_file_exists("#{app_path}/public/assets/something-*.js") @@ -242,7 +261,7 @@ module ApplicationTests app_file "app/assets/javascripts/application.js", "alert();" add_to_env_config "production", "config.public_file_server.enabled = true" - precompile! RAILS_ENV: 'production' + precompile! RAILS_ENV: "production" manifest = Dir["#{app_path}/public/assets/.sprockets-manifest-*.json"].first assets = ActiveSupport::JSON.decode(File.read(manifest)) @@ -273,7 +292,7 @@ module ApplicationTests app_file "app/assets/stylesheets/application.css.erb", "p { background-image: url(<%= asset_path('rails.png') %>) }" - precompile! RAILS_ENV: 'production' + precompile! RAILS_ENV: "production" manifest = Dir["#{app_path}/public/assets/.sprockets-manifest-*.json"].first assets = ActiveSupport::JSON.decode(File.read(manifest)) @@ -287,11 +306,11 @@ module ApplicationTests assert_not_equal asset_path, assets["assets"]["application.css"] end - test "precompile appends the md5 hash to files referenced with asset_path and run in production with digest true" do + test "precompile appends the MD5 hash to files referenced with asset_path and run in production with digest true" do app_file "app/assets/images/rails.png", "notactuallyapng" app_file "app/assets/stylesheets/application.css.erb", "p { background-image: url(<%= asset_path('rails.png') %>) }" - precompile! RAILS_ENV: 'production' + precompile! RAILS_ENV: "production" file = Dir["#{app_path}/public/assets/application-*.css"].first assert_match(/\/assets\/rails-([0-z]+)\.png/, File.read(file)) @@ -324,8 +343,7 @@ module ApplicationTests clean_assets! - files = Dir["#{app_path}/public/assets/**/*", "#{app_path}/tmp/cache/assets/development/*", - "#{app_path}/tmp/cache/assets/test/*", "#{app_path}/tmp/cache/assets/production/*"] + files = Dir["#{app_path}/public/assets/**/*"] assert_equal 0, files.length, "Expected no assets, but found #{files.join(', ')}" end @@ -357,12 +375,12 @@ module ApplicationTests class ::OmgController < ActionController::Base def index flash[:cool_story] = true - render text: "ok" + render plain: "ok" end end get "/omg" - assert_equal 'ok', last_response.body + assert_equal "ok", last_response.body get "/assets/demo.js" assert_match "alert()", last_response.body @@ -390,7 +408,7 @@ module ApplicationTests app_with_assets_in_view # config.assets.debug and config.assets.compile are false for production environment - precompile! RAILS_ENV: 'production' + precompile! RAILS_ENV: "production" # Load app env app "production" @@ -398,7 +416,7 @@ module ApplicationTests class ::PostsController < ActionController::Base ; end # the debug_assets params isn't used if compile is off - get '/posts?debug_assets=true' + get "/posts?debug_assets=true" assert_match(/<script src="\/assets\/application-([0-z]+)\.js"><\/script>/, last_response.body) assert_no_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js"><\/script>/, last_response.body) end @@ -457,9 +475,9 @@ module ApplicationTests class ::PostsController < ActionController::Base; end - get '/posts', {}, {'HTTPS'=>'off'} + get "/posts", {}, "HTTPS"=>"off" assert_match('src="http://example.com/assets/application.self.js', last_response.body) - get '/posts', {}, {'HTTPS'=>'on'} + get "/posts", {}, "HTTPS"=>"on" assert_match('src="https://example.com/assets/application.self.js', last_response.body) end @@ -489,16 +507,16 @@ module ApplicationTests private - def app_with_assets_in_view - app_file "app/assets/javascripts/application.js", "//= require_tree ." - app_file "app/assets/javascripts/xmlhr.js", "function f1() { alert(); }" - app_file "app/views/posts/index.html.erb", "<%= javascript_include_tag 'application' %>" + def app_with_assets_in_view + app_file "app/assets/javascripts/application.js", "//= require_tree ." + app_file "app/assets/javascripts/xmlhr.js", "function f1() { alert(); }" + app_file "app/views/posts/index.html.erb", "<%= javascript_include_tag 'application' %>" - app_file "config/routes.rb", <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get '/posts', :to => "posts#index" end RUBY - end + end end end diff --git a/railties/test/application/bin_setup_test.rb b/railties/test/application/bin_setup_test.rb index a07c51a60f..0bbd25db2b 100644 --- a/railties/test/application/bin_setup_test.rb +++ b/railties/test/application/bin_setup_test.rb @@ -1,4 +1,4 @@ -require 'isolation/abstract_unit' +require "isolation/abstract_unit" module ApplicationTests class BinSetupTest < ActiveSupport::TestCase @@ -6,15 +6,22 @@ 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 Dir.chdir(app_path) do - app_file 'db/schema.rb', <<-RUBY + app_file "db/schema.rb", <<-RUBY ActiveRecord::Schema.define(version: 20140423102712) do create_table(:articles) {} end @@ -23,7 +30,7 @@ module ApplicationTests list_tables = lambda { `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip } File.write("log/test.log", "zomg!") - assert_equal '[]', list_tables.call + assert_equal "[]", list_tables.call assert_equal 5, File.size("log/test.log") assert_not File.exist?("tmp/restart.txt") `bin/setup 2>&1` @@ -35,7 +42,7 @@ module ApplicationTests def test_bin_setup_output Dir.chdir(app_path) do - app_file 'db/schema.rb', "" + app_file "db/schema.rb", "" output = `bin/setup 2>&1` assert_equal(<<-OUTPUT, output) @@ -43,6 +50,8 @@ module ApplicationTests The Gemfile's dependencies are satisfied == Preparing database == +Created database 'db/development.sqlite3' +Created database 'db/test.sqlite3' == Removing old logs and tempfiles == @@ -50,5 +59,16 @@ The Gemfile's dependencies are satisfied 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 28b3b2f2d6..13fc98f0d6 100644 --- a/railties/test/application/configuration/custom_test.rb +++ b/railties/test/application/configuration/custom_test.rb @@ -1,11 +1,10 @@ -require 'isolation/abstract_unit' +require "isolation/abstract_unit" module ApplicationTests module ConfigurationTests class CustomTest < ActiveSupport::TestCase def setup build_app - boot_rails FileUtils.rm_rf("#{app_path}/config/environments") end @@ -14,7 +13,7 @@ module ApplicationTests FileUtils.rm_rf(new_app) if File.directory?(new_app) end - test 'access custom configuration point' do + test "access custom configuration point" do add_to_config <<-RUBY config.x.payment_processing.schedule = :daily config.x.payment_processing.retries = 3 @@ -33,6 +32,13 @@ module ApplicationTests assert_nil x.i_do_not_exist.zomg end + test "custom configuration responds to all messages" do + x = Rails.configuration.x + 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") diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 7bcfc86d03..b0f5b30174 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -1,6 +1,6 @@ require "isolation/abstract_unit" -require 'rack/test' -require 'env_helpers' +require "rack/test" +require "env_helpers" class ::MyMailInterceptor def self.delivering_email(email); email; end @@ -34,9 +34,9 @@ module ApplicationTests FileUtils.cp_r(app_path, new_app) end - def app(env = 'development') + def app(env = "development") @app ||= begin - ENV['RAILS_ENV'] = env + ENV["RAILS_ENV"] = env # FIXME: shush Sass warning spam, not relevant to testing Railties Kernel.silence_warnings do @@ -45,13 +45,12 @@ module ApplicationTests Rails.application ensure - ENV.delete 'RAILS_ENV' + ENV.delete "RAILS_ENV" end end def setup build_app - boot_rails supress_default_config end @@ -75,7 +74,7 @@ module ApplicationTests switch_env "RAILS_ENV", nil do Rails.env = "development" assert_equal "development", Rails.env - assert_nil ENV['RAILS_ENV'] + assert_nil ENV["RAILS_ENV"] end end @@ -83,7 +82,7 @@ module ApplicationTests restore_default_config with_rails_env "development" do - app 'development' + app "development" assert Rails.application.config.log_tags.blank? end end @@ -92,13 +91,13 @@ module ApplicationTests restore_default_config with_rails_env "production" do - app 'production' + app "production" assert_equal [:request_id], Rails.application.config.log_tags end end test "lib dir is on LOAD_PATH during config" do - app_file 'lib/my_logger.rb', <<-RUBY + app_file "lib/my_logger.rb", <<-RUBY require "logger" class MyLogger < ::Logger end @@ -108,9 +107,9 @@ module ApplicationTests config.logger = MyLogger.new STDOUT RUBY - app 'development' + app "development" - assert_equal 'MyLogger', Rails.application.config.logger.class.name + assert_equal "MyLogger", Rails.application.config.logger.class.name end test "a renders exception on pending migration" do @@ -120,7 +119,7 @@ module ApplicationTests config.action_dispatch.show_exceptions = true RUBY - app_file 'db/migrate/20140708012246_create_user.rb', <<-RUBY + app_file "db/migrate/20140708012246_create_user.rb", <<-RUBY class CreateUser < ActiveRecord::Migration::Current def change create_table :users @@ -128,7 +127,7 @@ module ApplicationTests end RUBY - app 'development' + app "development" ActiveRecord::Migrator.migrations_paths = ["#{app_path}/db/migrate"] @@ -157,31 +156,31 @@ module ApplicationTests end test "Rails.application is nil until app is initialized" do - require 'rails' + require "rails" assert_nil Rails.application - app 'development' + app "development" assert_equal AppTemplate::Application.instance, Rails.application end test "Rails.application responds to all instance methods" do - app 'development' + app "development" assert_respond_to Rails.application, :routes_reloader assert_equal Rails.application.routes_reloader, AppTemplate::Application.routes_reloader end test "Rails::Application responds to paths" do - app 'development' + app "development" assert_respond_to AppTemplate::Application, :paths assert_equal ["#{app_path}/app/views"], AppTemplate::Application.paths["app/views"].expanded end test "the application root is set correctly" do - app 'development' + app "development" assert_equal Pathname.new(app_path), Rails.application.root end test "the application root can be seen from the application singleton" do - app 'development' + app "development" assert_equal Pathname.new(app_path), AppTemplate::Application.root end @@ -193,7 +192,7 @@ module ApplicationTests use_frameworks [] - app 'development' + app "development" assert_equal Pathname.new(new_app), Rails.application.root end @@ -204,7 +203,7 @@ module ApplicationTests use_frameworks [] Dir.chdir("#{app_path}") do - app 'development' + app "development" assert_equal Pathname.new("#{app_path}"), Rails.application.root end end @@ -214,7 +213,7 @@ module ApplicationTests config.root = "#{app_path}" RUBY - app 'development' + app "development" assert_instance_of Pathname, Rails.root end @@ -224,7 +223,7 @@ module ApplicationTests config.paths["public"] = "somewhere" RUBY - app 'development' + app "development" assert_instance_of Pathname, Rails.public_path end @@ -235,13 +234,13 @@ module ApplicationTests config.cache_classes = true RUBY - app 'development' + app "development" assert_equal :require, ActiveSupport::Dependencies.mechanism end test "application is always added to eager_load namespaces" do - app 'development' + app "development" assert_includes Rails.application.config.eager_load_namespaces, AppTemplate::Application end @@ -257,7 +256,7 @@ module ApplicationTests use_frameworks [] assert_nothing_raised do - app 'development' + app "development" end end @@ -269,18 +268,18 @@ module ApplicationTests RUBY assert_nothing_raised do - app 'development' + app "development" end end test "filter_parameters should be able to set via config.filter_parameters in an initializer" do - app_file 'config/initializers/filter_parameters_logging.rb', <<-RUBY + app_file "config/initializers/filter_parameters_logging.rb", <<-RUBY Rails.application.config.filter_parameters += [ :password, :foo, 'bar' ] RUBY - app 'development' + app "development" - assert_equal [:password, :foo, 'bar'], Rails.application.env_config['action_dispatch.parameter_filter'] + assert_equal [:password, :foo, "bar"], Rails.application.env_config["action_dispatch.parameter_filter"] end test "config.to_prepare is forwarded to ActionDispatch" do @@ -294,7 +293,7 @@ module ApplicationTests assert !$prepared - app 'development' + app "development" get "/" assert $prepared @@ -306,7 +305,7 @@ module ApplicationTests end test "skipping config.encoding still results in 'utf-8' as the default" do - app 'development' + app "development" assert_utf8 end @@ -315,7 +314,7 @@ module ApplicationTests config.encoding = "utf-8" RUBY - app 'development' + app "development" assert_utf8 end @@ -324,7 +323,7 @@ module ApplicationTests config.paths["public"] = "somewhere" RUBY - app 'development' + app "development" assert_equal Pathname.new(app_path).join("somewhere"), Rails.public_path end @@ -332,7 +331,7 @@ module ApplicationTests restore_default_config with_rails_env "production" do - app 'production' + app "production" assert_not app.config.public_file_server.enabled end end @@ -342,18 +341,29 @@ module ApplicationTests with_rails_env "production" do switch_env "RAILS_SERVE_STATIC_FILES", "1" do - app 'production' + app "production" assert app.config.public_file_server.enabled end end end + test "In production mode, STDOUT logging is enabled when RAILS_LOG_TO_STDOUT is set" do + restore_default_config + + with_rails_env "production" do + switch_env "RAILS_LOG_TO_STDOUT", "1" do + app "production" + assert ActiveSupport::Logger.logger_outputs_to?(app.config.logger, STDOUT) + end + end + end + test "In production mode, config.public_file_server.enabled is disabled when RAILS_SERVE_STATIC_FILES is blank" do restore_default_config with_rails_env "production" do switch_env "RAILS_SERVE_STATIC_FILES", " " do - app 'production' + app "production" assert_not app.config.public_file_server.enabled end end @@ -381,66 +391,66 @@ module ApplicationTests test "Use key_generator when secret_key_base is set" do make_basic_app do |application| - application.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33' + application.secrets.secret_key_base = "b3c631c314c0bbca50c1b2843150fe33" application.config.session_store :disabled end class ::OmgController < ActionController::Base def index cookies.signed[:some_key] = "some_value" - render text: cookies[:some_key] + render plain: cookies[:some_key] end end get "/" - secret = app.key_generator.generate_key('signed cookie') + secret = app.key_generator.generate_key("signed cookie") verifier = ActiveSupport::MessageVerifier.new(secret) - assert_equal 'some_value', verifier.verify(last_response.body) + assert_equal "some_value", verifier.verify(last_response.body) end test "application verifier can be used in the entire application" do make_basic_app do |application| - application.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33' + application.secrets.secret_key_base = "b3c631c314c0bbca50c1b2843150fe33" application.config.session_store :disabled end message = app.message_verifier(:sensitive_value).generate("some_value") - assert_equal 'some_value', Rails.application.message_verifier(:sensitive_value).verify(message) + assert_equal "some_value", Rails.application.message_verifier(:sensitive_value).verify(message) - secret = app.key_generator.generate_key('sensitive_value') + secret = app.key_generator.generate_key("sensitive_value") verifier = ActiveSupport::MessageVerifier.new(secret) - assert_equal 'some_value', verifier.verify(message) + assert_equal "some_value", verifier.verify(message) end test "application message verifier can be used when the key_generator is ActiveSupport::LegacyKeyGenerator" do - app_file 'config/initializers/secret_token.rb', <<-RUBY + app_file "config/initializers/secret_token.rb", <<-RUBY Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33" RUBY - app_file 'config/secrets.yml', <<-YAML + app_file "config/secrets.yml", <<-YAML development: secret_key_base: YAML - app 'development' + app "development" - assert_equal app.env_config['action_dispatch.key_generator'], Rails.application.key_generator - assert_equal app.env_config['action_dispatch.key_generator'].class, ActiveSupport::LegacyKeyGenerator + assert_equal app.env_config["action_dispatch.key_generator"], Rails.application.key_generator + assert_equal app.env_config["action_dispatch.key_generator"].class, ActiveSupport::LegacyKeyGenerator message = app.message_verifier(:sensitive_value).generate("some_value") - assert_equal 'some_value', Rails.application.message_verifier(:sensitive_value).verify(message) + assert_equal "some_value", Rails.application.message_verifier(:sensitive_value).verify(message) end test "warns when secrets.secret_key_base is blank and config.secret_token is set" do - app_file 'config/initializers/secret_token.rb', <<-RUBY + app_file "config/initializers/secret_token.rb", <<-RUBY Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33" RUBY - app_file 'config/secrets.yml', <<-YAML + app_file "config/secrets.yml", <<-YAML development: secret_key_base: YAML - app 'development' + app "development" assert_deprecated(/You didn't set `secret_key_base`./) do app.env_config @@ -448,12 +458,12 @@ module ApplicationTests end test "raise when secrets.secret_key_base is not a type of string" do - app_file 'config/secrets.yml', <<-YAML + app_file "config/secrets.yml", <<-YAML development: secret_key_base: 123 YAML - app 'development' + app "development" assert_raise(ArgumentError) do app.key_generator @@ -461,31 +471,31 @@ module ApplicationTests end test "prefer secrets.secret_token over config.secret_token" do - app_file 'config/initializers/secret_token.rb', <<-RUBY + app_file "config/initializers/secret_token.rb", <<-RUBY Rails.application.config.secret_token = "" RUBY - app_file 'config/secrets.yml', <<-YAML + app_file "config/secrets.yml", <<-YAML development: secret_token: 3b7cd727ee24e8444053437c36cc66c3 YAML - app 'development' + app "development" - assert_equal '3b7cd727ee24e8444053437c36cc66c3', app.secrets.secret_token + assert_equal "3b7cd727ee24e8444053437c36cc66c3", app.secrets.secret_token end test "application verifier can build different verifiers" do make_basic_app do |application| - application.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33' + application.secrets.secret_key_base = "b3c631c314c0bbca50c1b2843150fe33" application.config.session_store :disabled end default_verifier = app.message_verifier(:sensitive_value) text_verifier = app.message_verifier(:text) - message = text_verifier.generate('some_value') + message = text_verifier.generate("some_value") - assert_equal 'some_value', text_verifier.verify(message) + assert_equal "some_value", text_verifier.verify(message) assert_raises ActiveSupport::MessageVerifier::InvalidSignature do default_verifier.verify(message) end @@ -495,106 +505,131 @@ module ApplicationTests end test "secrets.secret_key_base is used when config/secrets.yml is present" do - app_file 'config/secrets.yml', <<-YAML + app_file "config/secrets.yml", <<-YAML development: secret_key_base: 3b7cd727ee24e8444053437c36cc66c3 YAML - app 'development' - assert_equal '3b7cd727ee24e8444053437c36cc66c3', app.secrets.secret_key_base + app "development" + assert_equal "3b7cd727ee24e8444053437c36cc66c3", app.secrets.secret_key_base end test "secret_key_base is copied from config to secrets when not set" do remove_file "config/secrets.yml" - app_file 'config/initializers/secret_token.rb', <<-RUBY + app_file "config/initializers/secret_token.rb", <<-RUBY Rails.application.config.secret_key_base = "3b7cd727ee24e8444053437c36cc66c3" RUBY - app 'development' - assert_equal '3b7cd727ee24e8444053437c36cc66c3', app.secrets.secret_key_base + app "development" + assert_equal "3b7cd727ee24e8444053437c36cc66c3", app.secrets.secret_key_base end test "config.secret_token over-writes a blank secrets.secret_token" do - app_file 'config/initializers/secret_token.rb', <<-RUBY + app_file "config/initializers/secret_token.rb", <<-RUBY Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33" RUBY - app_file 'config/secrets.yml', <<-YAML + app_file "config/secrets.yml", <<-YAML development: secret_key_base: secret_token: YAML - app 'development' + app "development" - assert_equal 'b3c631c314c0bbca50c1b2843150fe33', app.secrets.secret_token - assert_equal 'b3c631c314c0bbca50c1b2843150fe33', app.config.secret_token + assert_equal "b3c631c314c0bbca50c1b2843150fe33", app.secrets.secret_token + assert_equal "b3c631c314c0bbca50c1b2843150fe33", app.config.secret_token end test "custom secrets saved in config/secrets.yml are loaded in app secrets" do - app_file 'config/secrets.yml', <<-YAML + app_file "config/secrets.yml", <<-YAML development: secret_key_base: 3b7cd727ee24e8444053437c36cc66c3 aws_access_key_id: myamazonaccesskeyid aws_secret_access_key: myamazonsecretaccesskey YAML - app 'development' + app "development" - assert_equal 'myamazonaccesskeyid', app.secrets.aws_access_key_id - assert_equal 'myamazonsecretaccesskey', app.secrets.aws_secret_access_key + assert_equal "myamazonaccesskeyid", app.secrets.aws_access_key_id + assert_equal "myamazonsecretaccesskey", app.secrets.aws_secret_access_key + end + + test "shared secrets saved in config/secrets.yml are loaded in app secrets" do + app_file "config/secrets.yml", <<-YAML + shared: + api_key: 3b7cd727 + YAML + + app "development" + + assert_equal "3b7cd727", app.secrets.api_key + end + + test "shared secrets will yield to environment specific secrets" do + app_file "config/secrets.yml", <<-YAML + shared: + api_key: 3b7cd727 + + development: + api_key: abc12345 + YAML + + app "development" + + assert_equal "abc12345", app.secrets.api_key end test "blank config/secrets.yml does not crash the loading process" do - app_file 'config/secrets.yml', <<-YAML + app_file "config/secrets.yml", <<-YAML YAML - app 'development' + app "development" assert_nil app.secrets.not_defined end test "config.secret_key_base over-writes a blank secrets.secret_key_base" do - app_file 'config/initializers/secret_token.rb', <<-RUBY + app_file "config/initializers/secret_token.rb", <<-RUBY Rails.application.config.secret_key_base = "iaminallyoursecretkeybase" RUBY - app_file 'config/secrets.yml', <<-YAML + app_file "config/secrets.yml", <<-YAML development: secret_key_base: YAML - app 'development' + app "development" assert_equal "iaminallyoursecretkeybase", app.secrets.secret_key_base end test "uses ActiveSupport::LegacyKeyGenerator as app.key_generator when secrets.secret_key_base is blank" do - app_file 'config/initializers/secret_token.rb', <<-RUBY + app_file "config/initializers/secret_token.rb", <<-RUBY Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33" RUBY - app_file 'config/secrets.yml', <<-YAML + app_file "config/secrets.yml", <<-YAML development: secret_key_base: YAML - app 'development' + app "development" - assert_equal 'b3c631c314c0bbca50c1b2843150fe33', app.config.secret_token + assert_equal "b3c631c314c0bbca50c1b2843150fe33", app.config.secret_token assert_equal nil, app.secrets.secret_key_base assert_equal app.key_generator.class, ActiveSupport::LegacyKeyGenerator end test "uses ActiveSupport::LegacyKeyGenerator with config.secret_token as app.key_generator when secrets.secret_key_base is blank" do - app_file 'config/initializers/secret_token.rb', <<-RUBY + app_file "config/initializers/secret_token.rb", <<-RUBY Rails.application.config.secret_token = "" RUBY - app_file 'config/secrets.yml', <<-YAML + app_file "config/secrets.yml", <<-YAML development: secret_key_base: YAML - app 'development' + app "development" - assert_equal '', app.config.secret_token + assert_equal "", app.config.secret_token assert_equal nil, app.secrets.secret_key_base assert_raise ArgumentError, /\AA secret is required/ do app.key_generator @@ -615,7 +650,7 @@ module ApplicationTests end test "default form builder specified as a string" do - app_file 'config/initializers/form_builder.rb', <<-RUBY + app_file "config/initializers/form_builder.rb", <<-RUBY class CustomFormBuilder < ActionView::Helpers::FormBuilder def text_field(attribute, *args) label(attribute) + super(attribute, *args) @@ -624,15 +659,14 @@ module ApplicationTests Rails.configuration.action_view.default_form_builder = "CustomFormBuilder" RUBY - app_file 'app/models/post.rb', <<-RUBY + app_file "app/models/post.rb", <<-RUBY class Post include ActiveModel::Model attr_accessor :name end RUBY - - app_file 'app/controllers/posts_controller.rb', <<-RUBY + app_file "app/controllers/posts_controller.rb", <<-RUBY class PostsController < ApplicationController def index render inline: "<%= begin; form_for(Post.new) {|f| f.text_field(:name)}; rescue => e; e.to_s; end %>" @@ -646,14 +680,14 @@ module ApplicationTests end RUBY - app 'development' + app "development" get "/posts" assert_match(/label/, last_response.body) end test "default method for update can be changed" do - app_file 'app/models/post.rb', <<-RUBY + app_file "app/models/post.rb", <<-RUBY class Post include ActiveModel::Model def to_key; [1]; end @@ -663,19 +697,19 @@ module ApplicationTests token = "cf50faa3fe97702ca1ae" - app_file 'app/controllers/posts_controller.rb', <<-RUBY + app_file "app/controllers/posts_controller.rb", <<-RUBY class PostsController < ApplicationController def show render inline: "<%= begin; form_for(Post.new) {}; rescue => e; e.to_s; end %>" end def update - render text: "update" + render plain: "update" end private - def form_authenticity_token(*args); token; end # stub the authenticy token + def form_authenticity_token(*args); token; end # stub the authenticity token end RUBY @@ -685,7 +719,7 @@ module ApplicationTests end RUBY - app 'development' + app "development" params = { authenticity_token: token } @@ -707,7 +741,7 @@ module ApplicationTests test "request forgery token param can be changed" do make_basic_app do |application| - application.config.action_controller.request_forgery_protection_token = '_xsrf_token_here' + application.config.action_controller.request_forgery_protection_token = "_xsrf_token_here" end class ::OmgController < ActionController::Base @@ -738,12 +772,12 @@ module ApplicationTests config.action_mailer.interceptors = MyMailInterceptor RUBY - app 'development' + app "development" require "mail" _ = ActionMailer::Base - assert_equal [::MyMailInterceptor], ::Mail.send(:class_variable_get, "@@delivery_interceptors") + assert_equal [::MyMailInterceptor], ::Mail.class_variable_get(:@@delivery_interceptors) end test "registers multiple interceptors with ActionMailer" do @@ -751,12 +785,12 @@ module ApplicationTests config.action_mailer.interceptors = [MyMailInterceptor, "MyOtherMailInterceptor"] RUBY - app 'development' + app "development" require "mail" _ = ActionMailer::Base - assert_equal [::MyMailInterceptor, ::MyOtherMailInterceptor], ::Mail.send(:class_variable_get, "@@delivery_interceptors") + assert_equal [::MyMailInterceptor, ::MyOtherMailInterceptor], ::Mail.class_variable_get(:@@delivery_interceptors) end test "registers preview interceptors with ActionMailer" do @@ -764,7 +798,7 @@ module ApplicationTests config.action_mailer.preview_interceptors = MyPreviewMailInterceptor RUBY - app 'development' + app "development" require "mail" _ = ActionMailer::Base @@ -777,7 +811,7 @@ module ApplicationTests config.action_mailer.preview_interceptors = [MyPreviewMailInterceptor, "MyOtherPreviewMailInterceptor"] RUBY - app 'development' + app "development" require "mail" _ = ActionMailer::Base @@ -786,11 +820,11 @@ module ApplicationTests end test "default preview interceptor can be removed" do - app_file 'config/initializers/preview_interceptors.rb', <<-RUBY + app_file "config/initializers/preview_interceptors.rb", <<-RUBY ActionMailer::Base.preview_interceptors.delete(ActionMailer::InlinePreviewInterceptor) RUBY - app 'development' + app "development" require "mail" _ = ActionMailer::Base @@ -803,12 +837,12 @@ module ApplicationTests config.action_mailer.observers = MyMailObserver RUBY - app 'development' + app "development" require "mail" _ = ActionMailer::Base - assert_equal [::MyMailObserver], ::Mail.send(:class_variable_get, "@@delivery_notification_observers") + assert_equal [::MyMailObserver], ::Mail.class_variable_get(:@@delivery_notification_observers) end test "registers multiple observers with ActionMailer" do @@ -816,12 +850,12 @@ module ApplicationTests config.action_mailer.observers = [MyMailObserver, "MyOtherMailObserver"] RUBY - app 'development' + app "development" require "mail" _ = ActionMailer::Base - assert_equal [::MyMailObserver, ::MyOtherMailObserver], ::Mail.send(:class_variable_get, "@@delivery_notification_observers") + assert_equal [::MyMailObserver, ::MyOtherMailObserver], ::Mail.class_variable_get(:@@delivery_notification_observers) end test "allows setting the queue name for the ActionMailer::DeliveryJob" do @@ -829,12 +863,12 @@ module ApplicationTests config.action_mailer.deliver_later_queue_name = 'test_default' RUBY - app 'development' + app "development" require "mail" _ = ActionMailer::Base - assert_equal 'test_default', ActionMailer::Base.send(:class_variable_get, "@@deliver_later_queue_name") + assert_equal "test_default", ActionMailer::Base.class_variable_get(:@@deliver_later_queue_name) end test "valid timezone is setup correctly" do @@ -843,7 +877,7 @@ module ApplicationTests config.time_zone = "Wellington" RUBY - app 'development' + app "development" assert_equal "Wellington", Rails.application.config.time_zone end @@ -855,7 +889,7 @@ module ApplicationTests RUBY assert_raise(ArgumentError) do - app 'development' + app "development" end end @@ -865,7 +899,7 @@ module ApplicationTests config.beginning_of_week = :wednesday RUBY - app 'development' + app "development" assert_equal :wednesday, Rails.application.config.beginning_of_week end @@ -877,15 +911,15 @@ module ApplicationTests RUBY assert_raise(ArgumentError) do - app 'development' + app "development" end end test "config.action_view.cache_template_loading with cache_classes default" do add_to_config "config.cache_classes = true" - app 'development' - require 'action_view/base' + app "development" + require "action_view/base" assert_equal true, ActionView::Resolver.caching? end @@ -893,8 +927,8 @@ module ApplicationTests test "config.action_view.cache_template_loading without cache_classes default" do add_to_config "config.cache_classes = false" - app 'development' - require 'action_view/base' + app "development" + require "action_view/base" assert_equal false, ActionView::Resolver.caching? end @@ -905,8 +939,8 @@ module ApplicationTests config.action_view.cache_template_loading = false RUBY - app 'development' - require 'action_view/base' + app "development" + require "action_view/base" assert_equal false, ActionView::Resolver.caching? end @@ -917,8 +951,8 @@ module ApplicationTests config.action_view.cache_template_loading = true RUBY - app 'development' - require 'action_view/base' + app "development" + require "action_view/base" assert_equal true, ActionView::Resolver.caching? end @@ -928,11 +962,11 @@ module ApplicationTests add_to_env_config "development", "config.cache_classes = false" # These requires are to emulate an engine loading Action View before the application - require 'action_view' - require 'action_view/railtie' - require 'action_view/base' + require "action_view" + require "action_view/railtie" + require "action_view/base" - app 'development' + app "development" assert_equal false, ActionView::Resolver.caching? end @@ -944,20 +978,20 @@ module ApplicationTests class ::OmgController < ActionController::Base def index - render text: env["action_dispatch.show_exceptions"] + render plain: env["action_dispatch.show_exceptions"] end end get "/" - assert_equal 'true', last_response.body + assert_equal "true", last_response.body end test "config.action_controller.wrap_parameters is set in ActionController::Base" do - app_file 'config/initializers/wrap_parameters.rb', <<-RUBY + app_file "config/initializers/wrap_parameters.rb", <<-RUBY ActionController::Base.wrap_parameters format: [:json] RUBY - app_file 'app/models/post.rb', <<-RUBY + app_file "app/models/post.rb", <<-RUBY class Post def self.attribute_names %w(title) @@ -965,16 +999,16 @@ module ApplicationTests end RUBY - app_file 'app/controllers/application_controller.rb', <<-RUBY + app_file "app/controllers/application_controller.rb", <<-RUBY class ApplicationController < ActionController::Base protect_from_forgery with: :reset_session # as we are testing API here end RUBY - app_file 'app/controllers/posts_controller.rb', <<-RUBY + app_file "app/controllers/posts_controller.rb", <<-RUBY class PostsController < ApplicationController def create - render text: params[:post].inspect + render plain: params[:post].inspect end end RUBY @@ -985,17 +1019,17 @@ module ApplicationTests end RUBY - app 'development' + app "development" post "/posts.json", '{ "title": "foo", "name": "bar" }', "CONTENT_TYPE" => "application/json" - assert_equal '{"title"=>"foo"}', last_response.body + assert_equal '<ActionController::Parameters {"title"=>"foo"} permitted: false>', last_response.body end test "config.action_controller.permit_all_parameters = true" do - app_file 'app/controllers/posts_controller.rb', <<-RUBY + app_file "app/controllers/posts_controller.rb", <<-RUBY class PostsController < ActionController::Base def create - render text: params[:post].permitted? ? "permitted" : "forbidden" + render plain: params[:post].permitted? ? "permitted" : "forbidden" end end RUBY @@ -1007,17 +1041,17 @@ module ApplicationTests config.action_controller.permit_all_parameters = true RUBY - app 'development' + app "development" - post "/posts", {post: {"title" =>"zomg"}} - assert_equal 'permitted', last_response.body + post "/posts", post: { "title" =>"zomg" } + assert_equal "permitted", last_response.body end test "config.action_controller.action_on_unpermitted_parameters = :raise" do - app_file 'app/controllers/posts_controller.rb', <<-RUBY + app_file "app/controllers/posts_controller.rb", <<-RUBY class PostsController < ActionController::Base def create - render text: params.require(:post).permit(:name) + render plain: params.require(:post).permit(:name) end end RUBY @@ -1029,16 +1063,16 @@ module ApplicationTests config.action_controller.action_on_unpermitted_parameters = :raise RUBY - app 'development' + app "development" assert_equal :raise, ActionController::Parameters.action_on_unpermitted_parameters - post "/posts", {post: {"title" =>"zomg"}} + post "/posts", post: { "title" =>"zomg" } assert_match "We're sorry, but something went wrong", last_response.body end test "config.action_controller.always_permitted_parameters are: controller, action by default" do - app 'development' + app "development" assert_equal %w(controller action), ActionController::Parameters.always_permitted_parameters end @@ -1047,16 +1081,16 @@ module ApplicationTests config.action_controller.always_permitted_parameters = %w( controller action format ) RUBY - app 'development' + app "development" assert_equal %w( controller action format ), ActionController::Parameters.always_permitted_parameters end - test "config.action_controller.always_permitted_parameters = ['controller','action','format'] does not raise exeception" do - app_file 'app/controllers/posts_controller.rb', <<-RUBY + test "config.action_controller.always_permitted_parameters = ['controller','action','format'] does not raise exception" do + app_file "app/controllers/posts_controller.rb", <<-RUBY class PostsController < ActionController::Base def create - render text: params.permit(post: [:title]) + render plain: params.permit(post: [:title]) end end RUBY @@ -1069,28 +1103,28 @@ module ApplicationTests config.action_controller.action_on_unpermitted_parameters = :raise RUBY - app 'development' + app "development" assert_equal :raise, ActionController::Parameters.action_on_unpermitted_parameters - post "/posts", {post: {"title" =>"zomg"}, format: "json"} + post "/posts", post: { "title" =>"zomg" }, format: "json" assert_equal 200, last_response.status end test "config.action_controller.action_on_unpermitted_parameters is :log by default on development" do - app 'development' + app "development" assert_equal :log, ActionController::Parameters.action_on_unpermitted_parameters end test "config.action_controller.action_on_unpermitted_parameters is :log by default on test" do - app 'test' + app "test" assert_equal :log, ActionController::Parameters.action_on_unpermitted_parameters end test "config.action_controller.action_on_unpermitted_parameters is false by default on production" do - app 'production' + app "production" assert_equal false, ActionController::Parameters.action_on_unpermitted_parameters end @@ -1103,28 +1137,28 @@ module ApplicationTests class ::OmgController < ActionController::Base def index respond_to do |format| - format.html { render text: "HTML" } - format.xml { render text: "XML" } + format.html { render plain: "HTML" } + format.xml { render plain: "XML" } end end end get "/", {}, "HTTP_ACCEPT" => "application/xml" - assert_equal 'HTML', last_response.body + assert_equal "HTML", last_response.body get "/", { format: :xml }, "HTTP_ACCEPT" => "application/xml" - assert_equal 'XML', last_response.body + assert_equal "XML", last_response.body end test "Rails.application#env_config exists and include some existing parameters" do make_basic_app assert_respond_to app, :env_config - assert_equal app.env_config['action_dispatch.parameter_filter'], app.config.filter_parameters - assert_equal app.env_config['action_dispatch.show_exceptions'], app.config.action_dispatch.show_exceptions - assert_equal app.env_config['action_dispatch.logger'], Rails.logger - assert_equal app.env_config['action_dispatch.backtrace_cleaner'], Rails.backtrace_cleaner - assert_equal app.env_config['action_dispatch.key_generator'], Rails.application.key_generator + assert_equal app.env_config["action_dispatch.parameter_filter"], app.config.filter_parameters + assert_equal app.env_config["action_dispatch.show_exceptions"], app.config.action_dispatch.show_exceptions + assert_equal app.env_config["action_dispatch.logger"], Rails.logger + assert_equal app.env_config["action_dispatch.backtrace_cleaner"], Rails.backtrace_cleaner + assert_equal app.env_config["action_dispatch.key_generator"], Rails.application.key_generator end test "config.colorize_logging default is true" do @@ -1151,6 +1185,22 @@ module ApplicationTests end end + test "default session store initializer does not overwrite the user defined session store even if it is disabled" do + make_basic_app do |application| + application.config.session_store :disabled + end + + assert_equal nil, app.config.session_store + end + + test "default session store initializer sets session store to cookie store" do + session_options = { key: "_myapp_session", cookie_only: true } + make_basic_app + + assert_equal ActionDispatch::Session::CookieStore, app.config.session_store + assert_equal session_options, app.config.session_options + end + test "config.log_level with custom logger" do make_basic_app do |application| application.config.logger = Logger.new(STDOUT) @@ -1169,13 +1219,13 @@ module ApplicationTests test "config.active_record.dump_schema_after_migration is false on production" do build_app - app 'production' + app "production" assert_not ActiveRecord::Base.dump_schema_after_migration end test "config.active_record.dump_schema_after_migration is true by default on development" do - app 'development' + app "development" assert ActiveRecord::Base.dump_schema_after_migration end @@ -1201,12 +1251,12 @@ module ApplicationTests end RUBY - app 'development' + app "development" assert_not Rails.configuration.ran_block - require 'rake' - require 'rake/testtask' - require 'rdoc/task' + require "rake" + require "rake/testtask" + require "rdoc/task" Rails.application.load_tasks assert Rails.configuration.ran_block @@ -1223,7 +1273,7 @@ module ApplicationTests end RUBY - app 'development' + app "development" assert_not Rails.configuration.ran_block Rails.application.load_generators @@ -1241,7 +1291,7 @@ module ApplicationTests end RUBY - app 'development' + app "development" assert_not Rails.configuration.ran_block Rails.application.load_console @@ -1259,7 +1309,7 @@ module ApplicationTests end RUBY - app 'development' + app "development" assert_not Rails.configuration.ran_block Rails.application.load_runner @@ -1267,7 +1317,7 @@ module ApplicationTests end test "loading the first existing database configuration available" do - app_file 'config/environments/development.rb', <<-RUBY + app_file "config/environments/development.rb", <<-RUBY Rails.application.configure do config.paths.add 'config/database', with: 'config/nonexistent.yml' @@ -1275,44 +1325,44 @@ module ApplicationTests end RUBY - app 'development' + app "development" assert_kind_of Hash, Rails.application.config.database_configuration end - test 'raises with proper error message if no database configuration found' do + test "raises with proper error message if no database configuration found" do FileUtils.rm("#{app_path}/config/database.yml") - app 'development' + app "development" err = assert_raises RuntimeError do Rails.application.config.database_configuration end - assert_match 'config/database', err.message + assert_match "config/database", err.message end - test 'config.action_mailer.show_previews defaults to true in development' do - app 'development' + test "config.action_mailer.show_previews defaults to true in development" do + app "development" assert Rails.application.config.action_mailer.show_previews end - test 'config.action_mailer.show_previews defaults to false in production' do - app 'production' + test "config.action_mailer.show_previews defaults to false in production" do + app "production" assert_equal false, Rails.application.config.action_mailer.show_previews end - test 'config.action_mailer.show_previews can be set in the configuration file' do + test "config.action_mailer.show_previews can be set in the configuration file" do add_to_config <<-RUBY config.action_mailer.show_previews = true RUBY - app 'production' + app "production" assert_equal true, Rails.application.config.action_mailer.show_previews end test "config_for loads custom configuration from yaml files" do - app_file 'config/custom.yml', <<-RUBY + app_file "config/custom.yml", <<-RUBY development: key: 'custom key' RUBY @@ -1321,13 +1371,13 @@ module ApplicationTests config.my_custom_config = config_for('custom') RUBY - app 'development' + app "development" - assert_equal 'custom key', Rails.application.config.my_custom_config['key'] + assert_equal "custom key", Rails.application.config.my_custom_config["key"] end - test "config_for use the Pathname object if it is provided" do - app_file 'config/custom.yml', <<-RUBY + test "config_for uses the Pathname object if it is provided" do + app_file "config/custom.yml", <<-RUBY development: key: 'custom key' RUBY @@ -1336,9 +1386,9 @@ module ApplicationTests config.my_custom_config = config_for(Pathname.new(Rails.root.join("config/custom.yml"))) RUBY - app 'development' + app "development" - assert_equal 'custom key', Rails.application.config.my_custom_config['key'] + assert_equal "custom key", Rails.application.config.my_custom_config["key"] end test "config_for raises an exception if the file does not exist" do @@ -1347,14 +1397,14 @@ module ApplicationTests RUBY exception = assert_raises(RuntimeError) do - app 'development' + app "development" end assert_equal "Could not load configuration. No such file - #{app_path}/config/custom.yml", exception.message end test "config_for without the environment configured returns an empty hash" do - app_file 'config/custom.yml', <<-RUBY + app_file "config/custom.yml", <<-RUBY test: key: 'custom key' RUBY @@ -1363,26 +1413,26 @@ module ApplicationTests config.my_custom_config = config_for('custom') RUBY - app 'development' + app "development" assert_equal({}, Rails.application.config.my_custom_config) end test "config_for with empty file returns an empty hash" do - app_file 'config/custom.yml', <<-RUBY + app_file "config/custom.yml", <<-RUBY RUBY add_to_config <<-RUBY config.my_custom_config = config_for('custom') RUBY - app 'development' + app "development" assert_equal({}, Rails.application.config.my_custom_config) end test "config_for containing ERB tags should evaluate" do - app_file 'config/custom.yml', <<-RUBY + app_file "config/custom.yml", <<-RUBY development: key: <%= 'custom key' %> RUBY @@ -1391,13 +1441,13 @@ module ApplicationTests config.my_custom_config = config_for('custom') RUBY - app 'development' + app "development" - assert_equal 'custom key', Rails.application.config.my_custom_config['key'] + assert_equal "custom key", Rails.application.config.my_custom_config["key"] end test "config_for with syntax error show a more descriptive exception" do - app_file 'config/custom.yml', <<-RUBY + app_file "config/custom.yml", <<-RUBY development: key: foo: RUBY @@ -1407,14 +1457,14 @@ module ApplicationTests RUBY exception = assert_raises(RuntimeError) do - app 'development' + app "development" end - assert_match 'YAML syntax error occurred while parsing', exception.message + assert_match "YAML syntax error occurred while parsing", exception.message end test "config_for allows overriding the environment" do - app_file 'config/custom.yml', <<-RUBY + app_file "config/custom.yml", <<-RUBY test: key: 'walrus' production: @@ -1426,11 +1476,11 @@ module ApplicationTests RUBY require "#{app_path}/config/environment" - assert_equal 'unicorn', Rails.application.config.my_custom_config['key'] + assert_equal "unicorn", Rails.application.config.my_custom_config["key"] end test "api_only is false by default" do - app 'development' + app "development" refute Rails.application.config.api_only end @@ -1438,33 +1488,33 @@ module ApplicationTests add_to_config <<-RUBY config.api_only = true RUBY - app 'development' + app "development" Rails.application.load_generators assert Rails.configuration.api_only end - test "debug_exception_response_format is :api by default if only_api is enabled" do + test "debug_exception_response_format is :api by default if api_only is enabled" do add_to_config <<-RUBY config.api_only = true RUBY - app 'development' + app "development" assert_equal :api, Rails.configuration.debug_exception_response_format end - test "debug_exception_response_format can be override" do + test "debug_exception_response_format can be overridden" do add_to_config <<-RUBY config.api_only = true RUBY - app_file 'config/environments/development.rb', <<-RUBY + app_file "config/environments/development.rb", <<-RUBY Rails.application.configure do config.debug_exception_response_format = :default end RUBY - app 'development' + app "development" assert_equal :default, Rails.configuration.debug_exception_response_format end diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb index 7bf123d12b..72f340df34 100644 --- a/railties/test/application/console_test.rb +++ b/railties/test/application/console_test.rb @@ -1,11 +1,10 @@ -require 'isolation/abstract_unit' +require "isolation/abstract_unit" class ConsoleTest < ActiveSupport::TestCase include ActiveSupport::Testing::Isolation def setup build_app - boot_rails end def teardown @@ -30,7 +29,7 @@ class ConsoleTest < ActiveSupport::TestCase end def test_app_can_access_path_helper_method - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get 'foo', to: 'foo#index' end @@ -38,7 +37,7 @@ class ConsoleTest < ActiveSupport::TestCase load_environment console_session = irb_context.app - assert_equal '/foo', console_session.foo_path + assert_equal "/foo", console_session.foo_path end def test_new_session_should_return_integration_session @@ -52,12 +51,11 @@ class ConsoleTest < ActiveSupport::TestCase a = b = c = nil # TODO: These should be defined on the initializer - ActionDispatch::Reloader.to_cleanup { a = b = c = 1 } - ActionDispatch::Reloader.to_cleanup { b = c = 2 } - ActionDispatch::Reloader.to_prepare { c = 3 } + ActiveSupport::Reloader.to_complete { a = b = c = 1 } + ActiveSupport::Reloader.to_complete { b = c = 2 } + ActiveSupport::Reloader.to_prepare { c = 3 } - # Hide Reloading... output - silence_stream(STDOUT) { irb_context.reload! } + irb_context.reload!(false) assert_equal 1, a assert_equal 2, b @@ -81,7 +79,7 @@ class ConsoleTest < ActiveSupport::TestCase MODEL assert !User.new.respond_to?(:age) - silence_stream(STDOUT) { irb_context.reload! } + irb_context.reload!(false) assert User.new.respond_to?(:age) end @@ -90,8 +88,8 @@ class ConsoleTest < ActiveSupport::TestCase helper = irb_context.helper assert_not_nil helper assert_instance_of ActionView::Base, helper - assert_equal 'Once upon a time in a world...', - helper.truncate('Once upon a time in a world far far away') + assert_equal "Once upon a time in a world...", + helper.truncate("Once upon a time in a world far far away") end end @@ -105,7 +103,7 @@ class FullStackConsoleTest < ActiveSupport::TestCase skip "PTY unavailable" unless defined?(PTY) && PTY.respond_to?(:open) build_app - app_file 'app/models/post.rb', <<-CODE + app_file "app/models/post.rb", <<-CODE class Post < ActiveRecord::Base end CODE @@ -128,7 +126,7 @@ class FullStackConsoleTest < ActiveSupport::TestCase end end - assert output.include?(expected), "#{expected.inspect} expected, but got:\n\n#{output}" + assert_includes output, expected, "#{expected.inspect} expected, but got:\n\n#{output}" end def write_prompt(command, expected_output = nil) diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index 84cc6e120b..0153f94504 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -6,7 +6,6 @@ module ApplicationTests def setup build_app - boot_rails end def teardown @@ -30,7 +29,7 @@ module ApplicationTests end test "allow running plugin new generator inside Rails app directory" do - FileUtils.cd(rails_root){ `ruby bin/rails plugin new vendor/plugins/bukkits` } + FileUtils.cd(rails_root) { `ruby bin/rails plugin new vendor/plugins/bukkits` } assert File.exist?(File.join(rails_root, "vendor/plugins/bukkits/test/dummy/config/application.rb")) end @@ -114,7 +113,7 @@ module ApplicationTests test "generators with string and hash for options should generate symbol keys" do with_bare_config do |c| c.generators do |g| - g.orm 'data_mapper', migration: false + g.orm "data_mapper", migration: false end expected = { @@ -135,10 +134,10 @@ module ApplicationTests require "#{app_path}/config/environment" Rails.application.load_generators - assert Rails::Generators.hidden_namespaces.include?("assets") - assert Rails::Generators.hidden_namespaces.include?("helper") - assert Rails::Generators.hidden_namespaces.include?("js") - assert Rails::Generators.hidden_namespaces.include?("css") + assert_includes Rails::Generators.hidden_namespaces, "assets" + assert_includes Rails::Generators.hidden_namespaces, "helper" + assert_includes Rails::Generators.hidden_namespaces, "js" + assert_includes Rails::Generators.hidden_namespaces, "css" assert Rails::Generators.options[:rails][:api] assert_equal false, Rails::Generators.options[:rails][:assets] assert_equal false, Rails::Generators.options[:rails][:helper] @@ -160,5 +159,15 @@ module ApplicationTests assert Rails::Generators.options[:rails][:helper] assert_equal :my_template, Rails::Generators.options[:rails][:template_engine] end + + test "api only generator generate mailer views" do + add_to_config <<-RUBY + config.api_only = true + RUBY + + FileUtils.cd(rails_root) { `bin/rails generate mailer notifier foo` } + 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 end end diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index 4c06b6324c..b2cd339c1a 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -6,7 +6,6 @@ module ApplicationTests def setup build_app - boot_rails FileUtils.rm_rf "#{app_path}/config/environments" end @@ -16,7 +15,7 @@ module ApplicationTests # AC & AM test "set load paths set only if action controller or action mailer are in use" do - assert_nothing_raised NameError do + assert_nothing_raised do add_to_config <<-RUBY config.root = "#{app_path}" RUBY @@ -129,7 +128,7 @@ module ApplicationTests end RUBY - require 'rack/test' + require "rack/test" extend Rack::Test::Methods get "/foo/included_helpers" @@ -161,10 +160,10 @@ module ApplicationTests end RUBY - require 'rack/test' + require "rack/test" extend Rack::Test::Methods - get 'omg/show' + get "omg/show" assert_equal '{"omg":"omg"}', last_response.body end @@ -176,7 +175,7 @@ module ApplicationTests end test "assignment config.encoding to default_charset" do - charset = 'Shift_JIS' + charset = "Shift_JIS" add_to_config "config.encoding = '#{charset}'" require "#{app_path}/config/environment" assert_equal charset, ActionDispatch::Response.default_charset @@ -204,7 +203,7 @@ module ApplicationTests test "active_record extensions are applied to ActiveRecord" do add_to_config "config.active_record.table_name_prefix = 'tbl_'" require "#{app_path}/config/environment" - assert_equal 'tbl_', ActiveRecord::Base.table_name_prefix + assert_equal "tbl_", ActiveRecord::Base.table_name_prefix end test "database middleware doesn't initialize when activerecord is not in frameworks" do @@ -238,7 +237,7 @@ module ApplicationTests begin require "#{app_path}/config/environment" orig_database_url = ENV.delete("DATABASE_URL") - orig_rails_env, Rails.env = Rails.env, 'development' + orig_rails_env, Rails.env = Rails.env, "development" ActiveRecord::Base.establish_connection assert ActiveRecord::Base.connection assert_match(/#{ActiveRecord::Base.configurations[Rails.env]['database']}/, ActiveRecord::Base.connection_config[:database]) @@ -253,7 +252,7 @@ module ApplicationTests begin require "#{app_path}/config/environment" orig_database_url = ENV.delete("DATABASE_URL") - orig_rails_env, Rails.env = Rails.env, 'development' + orig_rails_env, Rails.env = Rails.env, "development" database_url_db_name = "db/database_url_db.sqlite3" ENV["DATABASE_URL"] = "sqlite3:#{database_url_db_name}" ActiveRecord::Base.establish_connection diff --git a/railties/test/application/initializers/hooks_test.rb b/railties/test/application/initializers/hooks_test.rb index b2cea0a8e1..0309d02730 100644 --- a/railties/test/application/initializers/hooks_test.rb +++ b/railties/test/application/initializers/hooks_test.rb @@ -6,7 +6,6 @@ module ApplicationTests def setup build_app - boot_rails FileUtils.rm_rf "#{app_path}/config/environments" end @@ -58,7 +57,7 @@ module ApplicationTests require "#{app_path}/config/environment" assert $activerecord_configurations - assert $activerecord_configurations['development'] + assert $activerecord_configurations["development"] end test "after_initialize happens after to_prepare in development" do diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb index ab7f29b0f2..206e42703b 100644 --- a/railties/test/application/initializers/i18n_test.rb +++ b/railties/test/application/initializers/i18n_test.rb @@ -6,7 +6,6 @@ module ApplicationTests def setup build_app - boot_rails FileUtils.rm_rf "#{app_path}/config/environments" require "rails/all" end @@ -31,7 +30,7 @@ module ApplicationTests end def assert_no_fallbacks - assert !I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks) + assert_not_includes I18n.backend.class.included_modules, I18n::Backend::Fallbacks end # Locales @@ -63,8 +62,8 @@ module ApplicationTests "#{app_path}/config/locales/en.yml", "#{app_path}/config/another_locale.yml" ], Rails.application.config.i18n.load_path - assert I18n.load_path.include?("#{app_path}/config/locales/en.yml") - assert I18n.load_path.include?("#{app_path}/config/another_locale.yml") + assert_includes I18n.load_path, "#{app_path}/config/locales/en.yml" + assert_includes I18n.load_path, "#{app_path}/config/another_locale.yml" end test "load_path is populated before eager loaded models" do @@ -77,19 +76,19 @@ en: foo: "1" YAML - app_file 'app/models/foo.rb', <<-RUBY + app_file "app/models/foo.rb", <<-RUBY class Foo < ActiveRecord::Base @foo = I18n.t(:foo) end RUBY - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get '/i18n', :to => lambda { |env| [200, {}, [Foo.instance_variable_get('@foo')]] } end RUBY - require 'rack/test' + require "rack/test" extend Rack::Test::Methods load_app @@ -107,13 +106,13 @@ en: foo: "1" YAML - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get '/i18n', :to => lambda { |env| [200, {}, [I18n.t(:foo)]] } end RUBY - require 'rack/test' + require "rack/test" extend Rack::Test::Methods load_app @@ -142,13 +141,13 @@ en: foo: "1" YAML - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get '/i18n', :to => lambda { |env| [200, {}, [I18n.t(:foo)]] } end RUBY - require 'rack/test' + require "rack/test" extend Rack::Test::Methods load_app @@ -178,13 +177,13 @@ en: foo: "1" YAML - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get '/i18n', :to => lambda { |env| [200, {}, [I18n.load_path.inspect]] } end RUBY - require 'rack/test' + require "rack/test" extend Rack::Test::Methods load_app @@ -215,7 +214,7 @@ fr: test "config.i18n.fallbacks = true initializes I18n.fallbacks with default settings" do I18n::Railtie.config.i18n.fallbacks = true load_app - assert I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks) + assert_includes I18n.backend.class.included_modules, I18n::Backend::Fallbacks assert_fallbacks de: [:de, :en] end @@ -223,7 +222,7 @@ fr: I18n::Railtie.config.i18n.fallbacks = true I18n::Railtie.config.i18n.backend = Class.new(I18n::Backend::Simple).new load_app - assert I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks) + assert_includes I18n.backend.class.included_modules, I18n::Backend::Fallbacks assert_fallbacks de: [:de, :en] end @@ -234,7 +233,7 @@ fr: end test "config.i18n.fallbacks.map = { :ca => :'es-ES' } initializes fallbacks with a mapping ca => es-ES" do - I18n::Railtie.config.i18n.fallbacks.map = { :ca => :'es-ES' } + I18n::Railtie.config.i18n.fallbacks.map = { ca: :'es-ES' } load_app assert_fallbacks ca: [:ca, :"es-ES", :es, :en] end @@ -245,18 +244,24 @@ fr: assert_fallbacks de: [:de, :'en-US', :en] end - test "[shortcut] config.i18n.fallbacks = [{ :ca => :'es-ES' }] initializes fallbacks with a mapping de-AT => de-DE" do - I18n::Railtie.config.i18n.fallbacks.map = { :ca => :'es-ES' } + test "[shortcut] config.i18n.fallbacks = [{ :ca => :'es-ES' }] initializes fallbacks with a mapping ca => es-ES" do + I18n::Railtie.config.i18n.fallbacks.map = { ca: :'es-ES' } load_app assert_fallbacks ca: [:ca, :"es-ES", :es, :en] end test "[shortcut] config.i18n.fallbacks = [:'en-US', { :ca => :'es-ES' }] initializes fallbacks with the given arguments" do - I18n::Railtie.config.i18n.fallbacks = [:'en-US', { :ca => :'es-ES' }] + I18n::Railtie.config.i18n.fallbacks = [:'en-US', { ca: :'es-ES' }] load_app assert_fallbacks ca: [:ca, :"es-ES", :es, :'en-US', :en] end + test "[shortcut] config.i18n.fallbacks = { ca: :en } initializes fallbacks with a mapping ca => :en" do + I18n::Railtie.config.i18n.fallbacks = { ca: :en } + load_app + assert_fallbacks ca: [:ca, :en] + end + test "disable config.i18n.enforce_available_locales" do add_to_config <<-RUBY config.i18n.enforce_available_locales = false diff --git a/railties/test/application/initializers/load_path_test.rb b/railties/test/application/initializers/load_path_test.rb index cd05956356..dbefb22837 100644 --- a/railties/test/application/initializers/load_path_test.rb +++ b/railties/test/application/initializers/load_path_test.rb @@ -6,7 +6,6 @@ module ApplicationTests def setup build_app - boot_rails FileUtils.rm_rf "#{app_path}/config/environments" end @@ -20,7 +19,7 @@ module ApplicationTests RUBY require "#{app_path}/config/environment" - assert $:.include?("#{app_path}/app/models") + assert_includes $:, "#{app_path}/app/models" end test "initializing an application allows to load code on lib path inside application class definition" do @@ -37,7 +36,7 @@ module ApplicationTests require "#{app_path}/config/environment" end - assert $:.include?("#{app_path}/lib") + assert_includes $:, "#{app_path}/lib" end test "initializing an application eager load any path under app" do diff --git a/railties/test/application/initializers/notifications_test.rb b/railties/test/application/initializers/notifications_test.rb index 95655b74cf..b847ac6b36 100644 --- a/railties/test/application/initializers/notifications_test.rb +++ b/railties/test/application/initializers/notifications_test.rb @@ -6,7 +6,6 @@ module ApplicationTests def setup build_app - boot_rails end def teardown @@ -40,17 +39,17 @@ module ApplicationTests assert_match(/SHOW tables/, logger.logged(:debug).last) end - test 'rails load_config_initializer event is instrumented' do - app_file 'config/initializers/foo.rb', '' + test "rails load_config_initializer event is instrumented" do + app_file "config/initializers/foo.rb", "" events = [] callback = ->(*_) { events << _ } - ActiveSupport::Notifications.subscribed(callback, 'load_config_initializer.railties') do + ActiveSupport::Notifications.subscribed(callback, "load_config_initializer.railties") do app end assert_equal %w[load_config_initializer.railties], events.map(&:first) - assert_includes events.first.last[:initializer], 'config/initializers/foo.rb' + assert_includes events.first.last[:initializer], "config/initializers/foo.rb" end end end diff --git a/railties/test/application/integration_test_case_test.rb b/railties/test/application/integration_test_case_test.rb new file mode 100644 index 0000000000..1118e5037a --- /dev/null +++ b/railties/test/application/integration_test_case_test.rb @@ -0,0 +1,73 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class IntegrationTestCaseTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + setup do + build_app + end + + teardown do + teardown_app + end + + test "resets Action Mailer test deliveries" do + script("generate mailer BaseMailer welcome") + + app_file "test/integration/mailer_integration_test.rb", <<-RUBY + require 'test_helper' + + class MailerIntegrationTest < ActionDispatch::IntegrationTest + setup do + @old_delivery_method = ActionMailer::Base.delivery_method + ActionMailer::Base.delivery_method = :test + end + + teardown do + ActionMailer::Base.delivery_method = @old_delivery_method + end + + 2.times do |i| + define_method "test_resets_deliveries_\#{i}" do + BaseMailer.welcome.deliver_now + assert_equal 1, ActionMailer::Base.deliveries.count + end + end + end + RUBY + + output = Dir.chdir(app_path) { `bin/rails test 2>&1` } + assert_equal 0, $?.to_i, output + assert_match(/0 failures, 0 errors/, output) + end + end + + class IntegrationTestDefaultApp < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + setup do + build_app + end + + teardown do + teardown_app + end + + test "app method of integration tests returns test_app by default" do + app_file "test/integration/default_app_test.rb", <<-RUBY + require 'test_helper' + + class DefaultAppIntegrationTest < ActionDispatch::IntegrationTest + def test_app_returns_action_dispatch_test_app_by_default + assert_equal ActionDispatch.test_app, app + end + end + RUBY + + output = Dir.chdir(app_path) { `bin/rails test 2>&1` } + assert_equal 0, $?.to_i, output + assert_match(/0 failures, 0 errors/, output) + end + end +end diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb index 40abaf860d..38f96f3ab9 100644 --- a/railties/test/application/loading_test.rb +++ b/railties/test/application/loading_test.rb @@ -1,11 +1,10 @@ -require 'isolation/abstract_unit' +require "isolation/abstract_unit" class LoadingTest < ActiveSupport::TestCase include ActiveSupport::Testing::Isolation def setup build_app - boot_rails end def teardown @@ -26,11 +25,11 @@ class LoadingTest < ActiveSupport::TestCase require "#{rails_root}/config/environment" setup_ar! - p = Post.create(title: 'omg') + p = Post.create(title: "omg") assert_equal 1, Post.count - assert_equal 'omg', p.title + assert_equal "omg", p.title p = Post.first - assert_equal 'omg', p.title + assert_equal "omg", p.title end test "concerns in app are autoloaded" do @@ -56,10 +55,10 @@ class LoadingTest < ActiveSupport::TestCase require "#{rails_root}/config/environment" - assert_nothing_raised(NameError) { Trackable } - assert_nothing_raised(NameError) { EmailLoggable } - assert_nothing_raised(NameError) { Orderable } - assert_nothing_raised(NameError) { Matchable } + assert_nothing_raised { Trackable } + assert_nothing_raised { EmailLoggable } + assert_nothing_raised { Orderable } + assert_nothing_raised { Matchable } end test "models without table do not panic on scope definitions when loaded" do @@ -103,14 +102,14 @@ class LoadingTest < ActiveSupport::TestCase end MODEL - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get '/load', to: lambda { |env| [200, {}, Post.all] } get '/unload', to: lambda { |env| [200, {}, []] } end RUBY - require 'rack/test' + require "rack/test" extend Rack::Test::Methods require "#{rails_root}/config/environment" @@ -133,7 +132,7 @@ class LoadingTest < ActiveSupport::TestCase config.cache_classes = false RUBY - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get '/c', to: lambda { |env| [200, {"Content-Type" => "text/plain"}, [User.counter.to_s]] } end @@ -145,7 +144,7 @@ class LoadingTest < ActiveSupport::TestCase end MODEL - require 'rack/test' + require "rack/test" extend Rack::Test::Methods require "#{rails_root}/config/environment" @@ -174,7 +173,7 @@ class LoadingTest < ActiveSupport::TestCase end RUBY - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get '/c', to: lambda { |env| [200, {"Content-Type" => "text/plain"}, [User.counter.to_s]] } end @@ -186,7 +185,7 @@ class LoadingTest < ActiveSupport::TestCase end MODEL - require 'rack/test' + require "rack/test" extend Rack::Test::Methods require "#{rails_root}/config/environment" @@ -209,7 +208,7 @@ class LoadingTest < ActiveSupport::TestCase config.cache_classes = false RUBY - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY $counter ||= 0 Rails.application.routes.draw do get '/c', to: lambda { |env| User.name; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] } @@ -222,7 +221,7 @@ class LoadingTest < ActiveSupport::TestCase end MODEL - require 'rack/test' + require "rack/test" extend Rack::Test::Methods require "#{rails_root}/config/environment" @@ -241,7 +240,7 @@ class LoadingTest < ActiveSupport::TestCase config.cache_classes = false RUBY - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY $counter ||= 1 $counter *= 2 Rails.application.routes.draw do @@ -255,7 +254,7 @@ class LoadingTest < ActiveSupport::TestCase end MODEL - require 'rack/test' + require "rack/test" extend Rack::Test::Methods require "#{rails_root}/config/environment" @@ -274,7 +273,7 @@ class LoadingTest < ActiveSupport::TestCase config.cache_classes = false RUBY - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get '/title', to: lambda { |env| [200, {"Content-Type" => "text/plain"}, [Post.new.title]] } get '/body', to: lambda { |env| [200, {"Content-Type" => "text/plain"}, [Post.new.body]] } @@ -286,7 +285,7 @@ class LoadingTest < ActiveSupport::TestCase end MODEL - require 'rack/test' + require "rack/test" extend Rack::Test::Methods app_file "db/migrate/1_create_posts.rb", <<-MIGRATION @@ -299,7 +298,7 @@ class LoadingTest < ActiveSupport::TestCase end MIGRATION - Dir.chdir(app_path) { `rake db:migrate`} + Dir.chdir(app_path) { `rake db:migrate` } require "#{rails_root}/config/environment" get "/title" @@ -341,11 +340,11 @@ class LoadingTest < ActiveSupport::TestCase require "#{rails_root}/config/environment" - require 'rack/test' + require "rack/test" extend Rack::Test::Methods - get '/omg/show' - assert_equal 'OK', last_response.body + get "/omg/show" + assert_equal "OK", last_response.body end def test_initialize_can_be_called_at_any_time @@ -360,13 +359,13 @@ class LoadingTest < ActiveSupport::TestCase protected - def setup_ar! - ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") - ActiveRecord::Migration.verbose = false - ActiveRecord::Schema.define(version: 1) do - create_table :posts do |t| - t.string :title + def setup_ar! + ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") + ActiveRecord::Migration.verbose = false + ActiveRecord::Schema.define(version: 1) do + create_table :posts do |t| + t.string :title + end end end - end end diff --git a/railties/test/application/mailer_previews_test.rb b/railties/test/application/mailer_previews_test.rb index 643d876a26..790aca2aa4 100644 --- a/railties/test/application/mailer_previews_test.rb +++ b/railties/test/application/mailer_previews_test.rb @@ -1,6 +1,6 @@ -require 'isolation/abstract_unit' -require 'rack/test' -require 'base64' +require "isolation/abstract_unit" +require "rack/test" +require "base64" module ApplicationTests class MailerPreviewsTest < ActiveSupport::TestCase @@ -9,7 +9,6 @@ module ApplicationTests def setup build_app - boot_rails end def teardown @@ -28,10 +27,10 @@ module ApplicationTests assert_equal 404, last_response.status end - test "/rails/mailers is accessible with correct configuraiton" do + test "/rails/mailers is accessible with correct configuration" do add_to_config "config.action_mailer.show_previews = true" app("production") - get "/rails/mailers", {}, {"REMOTE_ADDR" => "4.2.42.42"} + get "/rails/mailers", {}, "REMOTE_ADDR" => "4.2.42.42" assert_equal 200, last_response.status end @@ -54,7 +53,7 @@ module ApplicationTests end test "mailer previews are loaded from the default preview_path" do - mailer 'notifier', <<-RUBY + mailer "notifier", <<-RUBY class Notifier < ActionMailer::Base default from: "from@example.com" @@ -64,11 +63,11 @@ module ApplicationTests end RUBY - text_template 'notifier/foo', <<-RUBY + text_template "notifier/foo", <<-RUBY Hello, World! RUBY - mailer_preview 'notifier', <<-RUBY + mailer_preview "notifier", <<-RUBY class NotifierPreview < ActionMailer::Preview def foo Notifier.foo @@ -76,7 +75,7 @@ module ApplicationTests end RUBY - app('development') + app("development") get "/rails/mailers" assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body @@ -86,7 +85,7 @@ module ApplicationTests test "mailer previews are loaded from a custom preview_path" do add_to_config "config.action_mailer.preview_path = '#{app_path}/lib/mailer_previews'" - mailer 'notifier', <<-RUBY + mailer "notifier", <<-RUBY class Notifier < ActionMailer::Base default from: "from@example.com" @@ -96,11 +95,11 @@ module ApplicationTests end RUBY - text_template 'notifier/foo', <<-RUBY + text_template "notifier/foo", <<-RUBY Hello, World! RUBY - app_file 'lib/mailer_previews/notifier_preview.rb', <<-RUBY + app_file "lib/mailer_previews/notifier_preview.rb", <<-RUBY class NotifierPreview < ActionMailer::Preview def foo Notifier.foo @@ -108,7 +107,7 @@ module ApplicationTests end RUBY - app('development') + app("development") get "/rails/mailers" assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body @@ -116,12 +115,12 @@ module ApplicationTests end test "mailer previews are reloaded across requests" do - app('development') + app("development") get "/rails/mailers" assert_no_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body - mailer 'notifier', <<-RUBY + mailer "notifier", <<-RUBY class Notifier < ActionMailer::Base default from: "from@example.com" @@ -131,11 +130,11 @@ module ApplicationTests end RUBY - text_template 'notifier/foo', <<-RUBY + text_template "notifier/foo", <<-RUBY Hello, World! RUBY - mailer_preview 'notifier', <<-RUBY + mailer_preview "notifier", <<-RUBY class NotifierPreview < ActionMailer::Preview def foo Notifier.foo @@ -146,7 +145,7 @@ module ApplicationTests get "/rails/mailers" assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body - remove_file 'test/mailers/previews/notifier_preview.rb' + remove_file "test/mailers/previews/notifier_preview.rb" sleep(1) get "/rails/mailers" @@ -154,7 +153,7 @@ module ApplicationTests end test "mailer preview actions are added and removed" do - mailer 'notifier', <<-RUBY + mailer "notifier", <<-RUBY class Notifier < ActionMailer::Base default from: "from@example.com" @@ -164,11 +163,11 @@ module ApplicationTests end RUBY - text_template 'notifier/foo', <<-RUBY + text_template "notifier/foo", <<-RUBY Hello, World! RUBY - mailer_preview 'notifier', <<-RUBY + mailer_preview "notifier", <<-RUBY class NotifierPreview < ActionMailer::Preview def foo Notifier.foo @@ -176,14 +175,14 @@ module ApplicationTests end RUBY - app('development') + app("development") get "/rails/mailers" assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body assert_match '<li><a href="/rails/mailers/notifier/foo">foo</a></li>', last_response.body assert_no_match '<li><a href="/rails/mailers/notifier/bar">bar</a></li>', last_response.body - mailer 'notifier', <<-RUBY + mailer "notifier", <<-RUBY class Notifier < ActionMailer::Base default from: "from@example.com" @@ -197,15 +196,15 @@ module ApplicationTests end RUBY - text_template 'notifier/foo', <<-RUBY + text_template "notifier/foo", <<-RUBY Hello, World! RUBY - text_template 'notifier/bar', <<-RUBY + text_template "notifier/bar", <<-RUBY Goodbye, World! RUBY - mailer_preview 'notifier', <<-RUBY + mailer_preview "notifier", <<-RUBY class NotifierPreview < ActionMailer::Preview def foo Notifier.foo @@ -224,7 +223,7 @@ module ApplicationTests assert_match '<li><a href="/rails/mailers/notifier/foo">foo</a></li>', last_response.body assert_match '<li><a href="/rails/mailers/notifier/bar">bar</a></li>', last_response.body - mailer 'notifier', <<-RUBY + mailer "notifier", <<-RUBY class Notifier < ActionMailer::Base default from: "from@example.com" @@ -234,9 +233,9 @@ module ApplicationTests end RUBY - remove_file 'app/views/notifier/bar.text.erb' + remove_file "app/views/notifier/bar.text.erb" - mailer_preview 'notifier', <<-RUBY + mailer_preview "notifier", <<-RUBY class NotifierPreview < ActionMailer::Preview def foo Notifier.foo @@ -255,12 +254,12 @@ module ApplicationTests test "mailer previews are reloaded from a custom preview_path" do add_to_config "config.action_mailer.preview_path = '#{app_path}/lib/mailer_previews'" - app('development') + app("development") get "/rails/mailers" assert_no_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body - mailer 'notifier', <<-RUBY + mailer "notifier", <<-RUBY class Notifier < ActionMailer::Base default from: "from@example.com" @@ -270,11 +269,11 @@ module ApplicationTests end RUBY - text_template 'notifier/foo', <<-RUBY + text_template "notifier/foo", <<-RUBY Hello, World! RUBY - app_file 'lib/mailer_previews/notifier_preview.rb', <<-RUBY + app_file "lib/mailer_previews/notifier_preview.rb", <<-RUBY class NotifierPreview < ActionMailer::Preview def foo Notifier.foo @@ -285,7 +284,7 @@ module ApplicationTests get "/rails/mailers" assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body - remove_file 'lib/mailer_previews/notifier_preview.rb' + remove_file "lib/mailer_previews/notifier_preview.rb" sleep(1) get "/rails/mailers" @@ -293,14 +292,14 @@ module ApplicationTests end test "mailer preview not found" do - app('development') + app("development") get "/rails/mailers/notifier" assert last_response.not_found? assert_match "Mailer preview 'notifier' not found", last_response.body end test "mailer preview email not found" do - mailer 'notifier', <<-RUBY + mailer "notifier", <<-RUBY class Notifier < ActionMailer::Base default from: "from@example.com" @@ -310,11 +309,11 @@ module ApplicationTests end RUBY - text_template 'notifier/foo', <<-RUBY + text_template "notifier/foo", <<-RUBY Hello, World! RUBY - mailer_preview 'notifier', <<-RUBY + mailer_preview "notifier", <<-RUBY class NotifierPreview < ActionMailer::Preview def foo Notifier.foo @@ -322,7 +321,7 @@ module ApplicationTests end RUBY - app('development') + app("development") get "/rails/mailers/notifier/bar" assert last_response.not_found? @@ -330,7 +329,7 @@ module ApplicationTests end test "mailer preview NullMail" do - mailer 'notifier', <<-RUBY + mailer "notifier", <<-RUBY class Notifier < ActionMailer::Base default from: "from@example.com" @@ -340,7 +339,7 @@ module ApplicationTests end RUBY - mailer_preview 'notifier', <<-RUBY + mailer_preview "notifier", <<-RUBY class NotifierPreview < ActionMailer::Preview def foo Notifier.foo @@ -348,7 +347,7 @@ module ApplicationTests end RUBY - app('development') + app("development") get "/rails/mailers/notifier/foo" assert_match "You are trying to preview an email that does not have any content.", last_response.body @@ -356,7 +355,7 @@ module ApplicationTests end test "mailer preview email part not found" do - mailer 'notifier', <<-RUBY + mailer "notifier", <<-RUBY class Notifier < ActionMailer::Base default from: "from@example.com" @@ -366,11 +365,11 @@ module ApplicationTests end RUBY - text_template 'notifier/foo', <<-RUBY + text_template "notifier/foo", <<-RUBY Hello, World! RUBY - mailer_preview 'notifier', <<-RUBY + mailer_preview "notifier", <<-RUBY class NotifierPreview < ActionMailer::Preview def foo Notifier.foo @@ -378,7 +377,7 @@ module ApplicationTests end RUBY - app('development') + app("development") get "/rails/mailers/notifier/foo?part=text%2Fhtml" assert last_response.not_found? @@ -386,7 +385,7 @@ module ApplicationTests end test "message header uses full display names" do - mailer 'notifier', <<-RUBY + mailer "notifier", <<-RUBY class Notifier < ActionMailer::Base default from: "Ruby on Rails <core@rubyonrails.org>" @@ -397,11 +396,11 @@ module ApplicationTests end RUBY - text_template 'notifier/foo', <<-RUBY + text_template "notifier/foo", <<-RUBY Hello, World! RUBY - mailer_preview 'notifier', <<-RUBY + mailer_preview "notifier", <<-RUBY class NotifierPreview < ActionMailer::Preview def foo Notifier.foo @@ -409,7 +408,7 @@ module ApplicationTests end RUBY - app('development') + app("development") get "/rails/mailers/notifier/foo" assert_equal 200, last_response.status @@ -419,7 +418,7 @@ module ApplicationTests end test "part menu selects correct option" do - mailer 'notifier', <<-RUBY + mailer "notifier", <<-RUBY class Notifier < ActionMailer::Base default from: "from@example.com" @@ -429,15 +428,15 @@ module ApplicationTests end RUBY - html_template 'notifier/foo', <<-RUBY + html_template "notifier/foo", <<-RUBY <p>Hello, World!</p> RUBY - text_template 'notifier/foo', <<-RUBY + text_template "notifier/foo", <<-RUBY Hello, World! RUBY - mailer_preview 'notifier', <<-RUBY + mailer_preview "notifier", <<-RUBY class NotifierPreview < ActionMailer::Preview def foo Notifier.foo @@ -445,7 +444,7 @@ module ApplicationTests end RUBY - app('development') + app("development") get "/rails/mailers/notifier/foo.html" assert_equal 200, last_response.status @@ -457,7 +456,7 @@ module ApplicationTests end test "mailer previews create correct links when loaded on a subdirectory" do - mailer 'notifier', <<-RUBY + mailer "notifier", <<-RUBY class Notifier < ActionMailer::Base default from: "from@example.com" @@ -467,11 +466,11 @@ module ApplicationTests end RUBY - text_template 'notifier/foo', <<-RUBY + text_template "notifier/foo", <<-RUBY Hello, World! RUBY - mailer_preview 'notifier', <<-RUBY + mailer_preview "notifier", <<-RUBY class NotifierPreview < ActionMailer::Preview def foo Notifier.foo @@ -479,9 +478,9 @@ module ApplicationTests end RUBY - app('development') + app("development") - get "/rails/mailers", {}, 'SCRIPT_NAME' => '/my_app' + get "/rails/mailers", {}, "SCRIPT_NAME" => "/my_app" assert_match '<h3><a href="/my_app/rails/mailers/notifier">Notifier</a></h3>', last_response.body assert_match '<li><a href="/my_app/rails/mailers/notifier/foo">foo</a></li>', last_response.body end @@ -489,7 +488,7 @@ module ApplicationTests test "plain text mailer preview with attachment" do image_file "pixel.png", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo=" - mailer 'notifier', <<-RUBY + mailer "notifier", <<-RUBY class Notifier < ActionMailer::Base default from: "from@example.com" @@ -500,11 +499,11 @@ module ApplicationTests end RUBY - text_template 'notifier/foo', <<-RUBY + text_template "notifier/foo", <<-RUBY Hello, World! RUBY - mailer_preview 'notifier', <<-RUBY + mailer_preview "notifier", <<-RUBY class NotifierPreview < ActionMailer::Preview def foo Notifier.foo @@ -512,7 +511,7 @@ module ApplicationTests end RUBY - app('development') + app("development") get "/rails/mailers/notifier/foo" assert_equal 200, last_response.status @@ -526,7 +525,7 @@ module ApplicationTests test "multipart mailer preview with attachment" do image_file "pixel.png", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo=" - mailer 'notifier', <<-RUBY + mailer "notifier", <<-RUBY class Notifier < ActionMailer::Base default from: "from@example.com" @@ -537,15 +536,15 @@ module ApplicationTests end RUBY - text_template 'notifier/foo', <<-RUBY + text_template "notifier/foo", <<-RUBY Hello, World! RUBY - html_template 'notifier/foo', <<-RUBY + html_template "notifier/foo", <<-RUBY <p>Hello, World!</p> RUBY - mailer_preview 'notifier', <<-RUBY + mailer_preview "notifier", <<-RUBY class NotifierPreview < ActionMailer::Preview def foo Notifier.foo @@ -553,7 +552,7 @@ module ApplicationTests end RUBY - app('development') + app("development") get "/rails/mailers/notifier/foo" assert_equal 200, last_response.status @@ -571,7 +570,7 @@ module ApplicationTests test "multipart mailer preview with inline attachment" do image_file "pixel.png", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo=" - mailer 'notifier', <<-RUBY + mailer "notifier", <<-RUBY class Notifier < ActionMailer::Base default from: "from@example.com" @@ -582,16 +581,16 @@ module ApplicationTests end RUBY - text_template 'notifier/foo', <<-RUBY + text_template "notifier/foo", <<-RUBY Hello, World! RUBY - html_template 'notifier/foo', <<-RUBY + html_template "notifier/foo", <<-RUBY <p>Hello, World!</p> <%= image_tag attachments['pixel.png'].url %> RUBY - mailer_preview 'notifier', <<-RUBY + mailer_preview "notifier", <<-RUBY class NotifierPreview < ActionMailer::Preview def foo Notifier.foo @@ -599,7 +598,7 @@ module ApplicationTests end RUBY - app('development') + app("development") get "/rails/mailers/notifier/foo" assert_equal 200, last_response.status @@ -616,7 +615,7 @@ module ApplicationTests end test "multipart mailer preview with attached email" do - mailer 'notifier', <<-RUBY + mailer "notifier", <<-RUBY class Notifier < ActionMailer::Base default from: "from@example.com" @@ -641,15 +640,15 @@ module ApplicationTests end RUBY - text_template 'notifier/foo', <<-RUBY + text_template "notifier/foo", <<-RUBY Hello, World! RUBY - html_template 'notifier/foo', <<-RUBY + html_template "notifier/foo", <<-RUBY <p>Hello, World!</p> RUBY - mailer_preview 'notifier', <<-RUBY + mailer_preview "notifier", <<-RUBY class NotifierPreview < ActionMailer::Preview def foo Notifier.foo @@ -657,7 +656,7 @@ module ApplicationTests end RUBY - app('development') + app("development") get "/rails/mailers/notifier/foo" assert_equal 200, last_response.status @@ -695,7 +694,7 @@ module ApplicationTests end def image_file(name, contents) - app_file("public/images/#{name}", Base64.strict_decode64(contents), 'wb') + app_file("public/images/#{name}", Base64.strict_decode64(contents), "wb") end end end diff --git a/railties/test/application/middleware/cache_test.rb b/railties/test/application/middleware/cache_test.rb index c951dabd6c..dc1d816dc5 100644 --- a/railties/test/application/middleware/cache_test.rb +++ b/railties/test/application/middleware/cache_test.rb @@ -1,4 +1,4 @@ -require 'isolation/abstract_unit' +require "isolation/abstract_unit" module ApplicationTests class CacheTest < ActiveSupport::TestCase @@ -6,8 +6,7 @@ module ApplicationTests def setup build_app - boot_rails - require 'rack/test' + require "rack/test" extend Rack::Test::Methods end @@ -20,7 +19,7 @@ module ApplicationTests class ExpiresController < ApplicationController def expires_header expires_in 10, public: !params[:private] - render text: SecureRandom.hex(16) + render plain: SecureRandom.hex(16) end def expires_etag @@ -33,18 +32,18 @@ module ApplicationTests end def keeps_if_modified_since - render :text => request.headers['If-Modified-Since'] + render plain: request.headers['If-Modified-Since'] end private def render_conditionally(headers) if stale?(headers.merge(public: !params[:private])) - render text: SecureRandom.hex(16) + render plain: SecureRandom.hex(16) end end end RUBY - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get ':controller(/:action)' end @@ -66,12 +65,12 @@ module ApplicationTests app("development") get "/expires/expires_header" - assert_nil last_response.headers['X-Rack-Cache'] + assert_nil last_response.headers["X-Rack-Cache"] body = last_response.body get "/expires/expires_header" - assert_nil last_response.headers['X-Rack-Cache'] + assert_nil last_response.headers["X-Rack-Cache"] assert_not_equal body, last_response.body end @@ -138,7 +137,7 @@ module ApplicationTests body = last_response.body etag = last_response.headers["ETag"] - get "/expires/expires_etag", {private: true}, "If-None-Match" => etag + get "/expires/expires_etag", { private: true }, "If-None-Match" => etag assert_equal "miss", last_response.headers["X-Rack-Cache"] assert_not_equal body, last_response.body end @@ -172,7 +171,7 @@ module ApplicationTests body = last_response.body last = last_response.headers["Last-Modified"] - get "/expires/expires_last_modified", {private: true}, "If-Modified-Since" => last + get "/expires/expires_last_modified", { private: true }, "If-Modified-Since" => last assert_equal "miss", last_response.headers["X-Rack-Cache"] assert_not_equal body, last_response.body end diff --git a/railties/test/application/middleware/cookies_test.rb b/railties/test/application/middleware/cookies_test.rb index bbb7627be9..1e4b5d086c 100644 --- a/railties/test/application/middleware/cookies_test.rb +++ b/railties/test/application/middleware/cookies_test.rb @@ -1,4 +1,4 @@ -require 'isolation/abstract_unit' +require "isolation/abstract_unit" module ApplicationTests class CookiesTest < ActiveSupport::TestCase @@ -10,7 +10,6 @@ module ApplicationTests def setup build_app - boot_rails FileUtils.rm_rf("#{app_path}/config/environments") end @@ -19,27 +18,27 @@ module ApplicationTests FileUtils.rm_rf(new_app) if File.directory?(new_app) end - test 'always_write_cookie is true by default in development' do - require 'rails' - Rails.env = 'development' + test "always_write_cookie is true by default in development" do + require "rails" + Rails.env = "development" require "#{app_path}/config/environment" assert_equal true, ActionDispatch::Cookies::CookieJar.always_write_cookie end - test 'always_write_cookie is false by default in production' do - require 'rails' - Rails.env = 'production' + test "always_write_cookie is false by default in production" do + require "rails" + Rails.env = "production" require "#{app_path}/config/environment" assert_equal false, ActionDispatch::Cookies::CookieJar.always_write_cookie end - test 'always_write_cookie can be overridden' do + test "always_write_cookie can be overridden" do add_to_config <<-RUBY config.action_dispatch.always_write_cookie = false RUBY - require 'rails' - Rails.env = 'development' + require "rails" + Rails.env = "development" require "#{app_path}/config/environment" assert_equal false, ActionDispatch::Cookies::CookieJar.always_write_cookie end diff --git a/railties/test/application/middleware/exceptions_test.rb b/railties/test/application/middleware/exceptions_test.rb index 7b4babb13b..cbb990f13b 100644 --- a/railties/test/application/middleware/exceptions_test.rb +++ b/railties/test/application/middleware/exceptions_test.rb @@ -1,5 +1,5 @@ -require 'isolation/abstract_unit' -require 'rack/test' +require "isolation/abstract_unit" +require "rack/test" module ApplicationTests class MiddlewareExceptionsTest < ActiveSupport::TestCase @@ -8,7 +8,6 @@ module ApplicationTests def setup build_app - boot_rails end def teardown @@ -70,7 +69,7 @@ module ApplicationTests app.config.action_dispatch.show_exceptions = true - get '/foo' + get "/foo" assert_equal 500, last_response.status end @@ -78,15 +77,15 @@ module ApplicationTests app.config.action_dispatch.show_exceptions = false assert_raise(ActionController::RoutingError) do - get '/foo' + get "/foo" end end test "unspecified route when action_dispatch.show_exceptions is set shows 404" do app.config.action_dispatch.show_exceptions = true - assert_nothing_raised(ActionController::RoutingError) do - get '/foo' + assert_nothing_raised do + get "/foo" assert_match "The page you were looking for doesn't exist.", last_response.body end end @@ -95,8 +94,8 @@ module ApplicationTests app.config.action_dispatch.show_exceptions = true app.config.consider_all_requests_local = true - assert_nothing_raised(ActionController::RoutingError) do - get '/foo' + assert_nothing_raised do + get "/foo" assert_match "No route matches", last_response.body end end @@ -112,12 +111,12 @@ module ApplicationTests app.config.action_dispatch.show_exceptions = true app.config.consider_all_requests_local = true - app_file 'app/views/foo/index.html.erb', <<-ERB + app_file "app/views/foo/index.html.erb", <<-ERB <% raise 'boooom' %> ✓測試テスト시험 ERB - get '/foo', :utf8 => '✓' + get "/foo", utf8: "✓" assert_match(/boooom/, last_response.body) assert_match(/測試テスト시험/, last_response.body) end diff --git a/railties/test/application/middleware/remote_ip_test.rb b/railties/test/application/middleware/remote_ip_test.rb index 97d5b5c698..c34d9d6ee7 100644 --- a/railties/test/application/middleware/remote_ip_test.rb +++ b/railties/test/application/middleware/remote_ip_test.rb @@ -1,6 +1,6 @@ -require 'ipaddr' -require 'isolation/abstract_unit' -require 'active_support/key_generator' +require "ipaddr" +require "isolation/abstract_unit" +require "active_support/key_generator" module ApplicationTests class RemoteIpTest < ActiveSupport::TestCase @@ -9,8 +9,8 @@ module ApplicationTests def remote_ip(env = {}) remote_ip = nil env = Rack::MockRequest.env_for("/").merge(env).merge!( - 'action_dispatch.show_exceptions' => false, - 'action_dispatch.key_generator' => ActiveSupport::LegacyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33') + "action_dispatch.show_exceptions" => false, + "action_dispatch.key_generator" => ActiveSupport::LegacyKeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33") ) endpoint = Proc.new do |e| @@ -36,10 +36,10 @@ module ApplicationTests test "works with both headers individually" do make_basic_app - assert_nothing_raised(ActionDispatch::RemoteIp::IpSpoofAttackError) do + assert_nothing_raised do assert_equal "1.1.1.1", remote_ip("HTTP_X_FORWARDED_FOR" => "1.1.1.1") end - assert_nothing_raised(ActionDispatch::RemoteIp::IpSpoofAttackError) do + assert_nothing_raised do assert_equal "1.1.1.2", remote_ip("HTTP_CLIENT_IP" => "1.1.1.2") end end @@ -49,7 +49,7 @@ module ApplicationTests app.config.action_dispatch.ip_spoofing_check = false end - assert_nothing_raised(ActionDispatch::RemoteIp::IpSpoofAttackError) do + assert_nothing_raised do assert_equal "1.1.1.1", remote_ip("HTTP_X_FORWARDED_FOR" => "1.1.1.1", "HTTP_CLIENT_IP" => "1.1.1.2") end end @@ -69,7 +69,7 @@ module ApplicationTests test "the user can set trusted proxies with an IPAddr argument" do make_basic_app do |app| - app.config.action_dispatch.trusted_proxies = IPAddr.new('4.2.42.0/24') + app.config.action_dispatch.trusted_proxies = IPAddr.new("4.2.42.0/24") end assert_equal "1.1.1.1", remote_ip("REMOTE_ADDR" => "1.1.1.1", "HTTP_X_FORWARDED_FOR" => "10.0.0.0,4.2.42.42") diff --git a/railties/test/application/middleware/sendfile_test.rb b/railties/test/application/middleware/sendfile_test.rb index be86f1a3b8..4938402fdc 100644 --- a/railties/test/application/middleware/sendfile_test.rb +++ b/railties/test/application/middleware/sendfile_test.rb @@ -1,4 +1,4 @@ -require 'isolation/abstract_unit' +require "isolation/abstract_unit" module ApplicationTests class SendfileTest < ActiveSupport::TestCase @@ -6,7 +6,6 @@ module ApplicationTests def setup build_app - boot_rails FileUtils.rm_rf "#{app_path}/config/environments" end @@ -49,7 +48,7 @@ module ApplicationTests test "config.action_dispatch.x_sendfile_header is sent to Rack::Sendfile" do make_basic_app do |app| - app.config.action_dispatch.x_sendfile_header = 'X-Lighttpd-Send-File' + app.config.action_dispatch.x_sendfile_header = "X-Lighttpd-Send-File" end simple_controller @@ -60,7 +59,7 @@ module ApplicationTests test "files handled by ActionDispatch::Static are handled by Rack::Sendfile" do make_basic_app do |app| - app.config.action_dispatch.x_sendfile_header = 'X-Sendfile' + app.config.action_dispatch.x_sendfile_header = "X-Sendfile" app.config.public_file_server.enabled = true app.paths["public"] = File.join(rails_root, "public") end diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb index f847e80471..0e4acfdcec 100644 --- a/railties/test/application/middleware/session_test.rb +++ b/railties/test/application/middleware/session_test.rb @@ -1,5 +1,5 @@ -require 'isolation/abstract_unit' -require 'rack/test' +require "isolation/abstract_unit" +require "rack/test" module ApplicationTests class MiddlewareSessionTest < ActiveSupport::TestCase @@ -8,7 +8,6 @@ module ApplicationTests def setup build_app - boot_rails FileUtils.rm_rf "#{app_path}/config/environments" end @@ -54,7 +53,7 @@ module ApplicationTests end test "session is empty and isn't saved on unverified request when using :null_session protect method" do - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get ':controller(/:action)' post ':controller(/:action)' @@ -71,7 +70,7 @@ module ApplicationTests end def read_session - render text: session[:foo].inspect + render plain: session[:foo].inspect end end RUBY @@ -82,20 +81,20 @@ module ApplicationTests require "#{app_path}/config/environment" - get '/foo/write_session' - get '/foo/read_session' - assert_equal '1', last_response.body + get "/foo/write_session" + get "/foo/read_session" + assert_equal "1", last_response.body - post '/foo/read_session' # Read session using POST request without CSRF token - assert_equal 'nil', last_response.body # Stored value shouldn't be accessible + post "/foo/read_session" # Read session using POST request without CSRF token + assert_equal "nil", last_response.body # Stored value shouldn't be accessible - post '/foo/write_session' # Write session using POST request without CSRF token - get '/foo/read_session' # Session shouldn't be changed - assert_equal '1', last_response.body + post "/foo/write_session" # Write session using POST request without CSRF token + get "/foo/read_session" # Session shouldn't be changed + assert_equal "1", last_response.body end test "cookie jar is empty and isn't saved on unverified request when using :null_session protect method" do - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get ':controller(/:action)' post ':controller(/:action)' @@ -112,7 +111,7 @@ module ApplicationTests end def read_cookie - render text: cookies[:foo].inspect + render plain: cookies[:foo].inspect end end RUBY @@ -123,20 +122,20 @@ module ApplicationTests require "#{app_path}/config/environment" - get '/foo/write_cookie' - get '/foo/read_cookie' + get "/foo/write_cookie" + get "/foo/read_cookie" assert_equal '"1"', last_response.body - post '/foo/read_cookie' # Read cookie using POST request without CSRF token - assert_equal 'nil', last_response.body # Stored value shouldn't be accessible + post "/foo/read_cookie" # Read cookie using POST request without CSRF token + assert_equal "nil", last_response.body # Stored value shouldn't be accessible - post '/foo/write_cookie' # Write cookie using POST request without CSRF token - get '/foo/read_cookie' # Cookie shouldn't be changed + post "/foo/write_cookie" # Write cookie using POST request without CSRF token + get "/foo/read_cookie" # Cookie shouldn't be changed assert_equal '"1"', last_response.body end test "session using encrypted cookie store" do - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get ':controller(/:action)' end @@ -150,38 +149,38 @@ module ApplicationTests end def read_session - render text: session[:foo] + render plain: session[:foo] end def read_encrypted_cookie - render text: cookies.encrypted[:_myapp_session]['foo'] + render plain: cookies.encrypted[:_myapp_session]['foo'] end def read_raw_cookie - render text: cookies[:_myapp_session] + render plain: cookies[:_myapp_session] end end RUBY require "#{app_path}/config/environment" - get '/foo/write_session' - get '/foo/read_session' - assert_equal '1', last_response.body + get "/foo/write_session" + get "/foo/read_session" + assert_equal "1", last_response.body - get '/foo/read_encrypted_cookie' - assert_equal '1', last_response.body + get "/foo/read_encrypted_cookie" + assert_equal "1", last_response.body - secret = app.key_generator.generate_key('encrypted cookie') - sign_secret = app.key_generator.generate_key('signed encrypted cookie') + 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) - get '/foo/read_raw_cookie' - assert_equal 1, encryptor.decrypt_and_verify(last_response.body)['foo'] + get "/foo/read_raw_cookie" + assert_equal 1, encryptor.decrypt_and_verify(last_response.body)["foo"] end test "session upgrading signature to encryption cookie store works the same way as encrypted cookie store" do - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get ':controller(/:action)' end @@ -195,15 +194,15 @@ module ApplicationTests end def read_session - render text: session[:foo] + render plain: session[:foo] end def read_encrypted_cookie - render text: cookies.encrypted[:_myapp_session]['foo'] + render plain: cookies.encrypted[:_myapp_session]['foo'] end def read_raw_cookie - render text: cookies[:_myapp_session] + render plain: cookies[:_myapp_session] end end RUBY @@ -214,23 +213,23 @@ module ApplicationTests require "#{app_path}/config/environment" - get '/foo/write_session' - get '/foo/read_session' - assert_equal '1', last_response.body + get "/foo/write_session" + get "/foo/read_session" + assert_equal "1", last_response.body - get '/foo/read_encrypted_cookie' - assert_equal '1', last_response.body + get "/foo/read_encrypted_cookie" + assert_equal "1", last_response.body - secret = app.key_generator.generate_key('encrypted cookie') - sign_secret = app.key_generator.generate_key('signed encrypted cookie') + 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) - get '/foo/read_raw_cookie' - assert_equal 1, encryptor.decrypt_and_verify(last_response.body)['foo'] + get "/foo/read_raw_cookie" + assert_equal 1, encryptor.decrypt_and_verify(last_response.body)["foo"] end test "session upgrading signature to encryption cookie store upgrades session to encrypted mode" do - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get ':controller(/:action)' end @@ -250,15 +249,15 @@ module ApplicationTests end def read_session - render text: session[:foo] + render plain: session[:foo] end def read_encrypted_cookie - render text: cookies.encrypted[:_myapp_session]['foo'] + render plain: cookies.encrypted[:_myapp_session]['foo'] end def read_raw_cookie - render text: cookies[:_myapp_session] + render plain: cookies[:_myapp_session] end end RUBY @@ -269,27 +268,27 @@ module ApplicationTests require "#{app_path}/config/environment" - get '/foo/write_raw_session' - get '/foo/read_session' - assert_equal '1', last_response.body + get "/foo/write_raw_session" + get "/foo/read_session" + assert_equal "1", last_response.body - get '/foo/write_session' - get '/foo/read_session' - assert_equal '2', last_response.body + get "/foo/write_session" + get "/foo/read_session" + assert_equal "2", last_response.body - get '/foo/read_encrypted_cookie' - assert_equal '2', last_response.body + get "/foo/read_encrypted_cookie" + assert_equal "2", last_response.body - secret = app.key_generator.generate_key('encrypted cookie') - sign_secret = app.key_generator.generate_key('signed encrypted cookie') + 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) - get '/foo/read_raw_cookie' - assert_equal 2, encryptor.decrypt_and_verify(last_response.body)['foo'] + get "/foo/read_raw_cookie" + assert_equal 2, encryptor.decrypt_and_verify(last_response.body)["foo"] end test "session upgrading legacy signed cookies to new signed cookies" do - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get ':controller(/:action)' end @@ -309,15 +308,15 @@ module ApplicationTests end def read_session - render text: session[:foo] + render plain: session[:foo] end def read_signed_cookie - render text: cookies.signed[:_myapp_session]['foo'] + render plain: cookies.signed[:_myapp_session]['foo'] end def read_raw_cookie - render text: cookies[:_myapp_session] + render plain: cookies[:_myapp_session] end end RUBY @@ -329,21 +328,55 @@ module ApplicationTests require "#{app_path}/config/environment" - get '/foo/write_raw_session' - get '/foo/read_session' - assert_equal '1', last_response.body + get "/foo/write_raw_session" + get "/foo/read_session" + assert_equal "1", last_response.body - get '/foo/write_session' - get '/foo/read_session' - assert_equal '2', last_response.body + get "/foo/write_session" + get "/foo/read_session" + assert_equal "2", last_response.body - get '/foo/read_signed_cookie' - assert_equal '2', last_response.body + get "/foo/read_signed_cookie" + assert_equal "2", last_response.body verifier = ActiveSupport::MessageVerifier.new(app.secrets.secret_token) - get '/foo/read_raw_cookie' - assert_equal 2, verifier.verify(last_response.body)['foo'] + get "/foo/read_raw_cookie" + assert_equal 2, verifier.verify(last_response.body)["foo"] + end + + test "calling reset_session on request does not trigger an error for API apps" do + add_to_config "config.api_only = true" + + controller :test, <<-RUBY + class TestController < ApplicationController + def dump_flash + request.reset_session + render plain: 'It worked!' + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/dump_flash' => "test#dump_flash" + end + RUBY + + require "#{app_path}/config/environment" + + get "/dump_flash" + + assert_equal 200, last_response.status + assert_equal "It worked!", last_response.body + + assert_not_includes Rails.application.middleware, ActionDispatch::Flash + end + + test "cookie_only is set to true even if user tries to overwrite it" do + add_to_config "config.session_store :cookie_store, key: '_myapp_session', cookie_only: false" + require "#{app_path}/config/environment" + assert app.config.session_options[:cookie_only], "Expected cookie_only to be set to true" end end end diff --git a/railties/test/application/middleware/static_test.rb b/railties/test/application/middleware/static_test.rb index 1246e20d94..5cd3e4325e 100644 --- a/railties/test/application/middleware/static_test.rb +++ b/railties/test/application/middleware/static_test.rb @@ -1,5 +1,5 @@ -require 'isolation/abstract_unit' -require 'rack/test' +require "isolation/abstract_unit" +require "rack/test" module ApplicationTests class MiddlewareStaticTest < ActiveSupport::TestCase @@ -18,17 +18,17 @@ module ApplicationTests # Regression test to #8907 # See https://github.com/rails/rails/commit/9cc82b77196d21a5c7021f6dca59ab9b2b158a45#commitcomment-2416514 test "doesn't set Cache-Control header when it is nil" do - app_file "public/foo.html", 'static' + app_file "public/foo.html", "static" require "#{app_path}/config/environment" - get 'foo' + get "foo" - assert_not last_response.headers.has_key?('Cache-Control'), "Cache-Control should not be set" + assert_not last_response.headers.has_key?("Cache-Control"), "Cache-Control should not be set" end test "headers for static files are configurable" do - app_file "public/about.html", 'static' + app_file "public/about.html", "static" add_to_config <<-CONFIG config.public_file_server.headers = { "Access-Control-Allow-Origin" => "http://rubyonrails.org", @@ -38,19 +38,19 @@ module ApplicationTests require "#{app_path}/config/environment" - get '/about.html' + get "/about.html" - assert_equal 'http://rubyonrails.org', last_response.headers["Access-Control-Allow-Origin"] - assert_equal 'public, max-age=60', last_response.headers["Cache-Control"] + assert_equal "http://rubyonrails.org", last_response.headers["Access-Control-Allow-Origin"] + assert_equal "public, max-age=60", last_response.headers["Cache-Control"] end test "public_file_server.index_name defaults to 'index'" do app_file "public/index.html", "/index.html" - + require "#{app_path}/config/environment" - get '/' - + get "/" + assert_equal "/index.html\n", last_response.body end @@ -60,7 +60,7 @@ module ApplicationTests require "#{app_path}/config/environment" - get '/' + get "/" assert_equal "/other-index.html\n", last_response.body end diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index 1434522cce..9baf7360a5 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -1,4 +1,4 @@ -require 'isolation/abstract_unit' +require "isolation/abstract_unit" module ApplicationTests class MiddlewareTest < ActiveSupport::TestCase @@ -6,7 +6,6 @@ module ApplicationTests def setup build_app - boot_rails FileUtils.rm_rf "#{app_path}/config/environments" end @@ -26,7 +25,7 @@ module ApplicationTests assert_equal [ "Rack::Sendfile", "ActionDispatch::Static", - "ActionDispatch::LoadInterlock", + "ActionDispatch::Executor", "ActiveSupport::Cache::Strategy::LocalCache", "Rack::Runtime", "Rack::MethodOverride", @@ -38,8 +37,6 @@ module ApplicationTests "ActionDispatch::Reloader", "ActionDispatch::Callbacks", "ActiveRecord::Migration::CheckPending", - "ActiveRecord::ConnectionAdapters::ConnectionManagement", - "ActiveRecord::QueryCache", "ActionDispatch::Cookies", "ActionDispatch::Session::CookieStore", "ActionDispatch::Flash", @@ -57,7 +54,7 @@ module ApplicationTests assert_equal [ "Rack::Sendfile", "ActionDispatch::Static", - "ActionDispatch::LoadInterlock", + "ActionDispatch::Executor", "ActiveSupport::Cache::Strategy::LocalCache", "Rack::Runtime", "ActionDispatch::RequestId", @@ -67,8 +64,6 @@ module ApplicationTests "ActionDispatch::RemoteIp", "ActionDispatch::Reloader", "ActionDispatch::Callbacks", - "ActiveRecord::ConnectionAdapters::ConnectionManagement", - "ActiveRecord::QueryCache", "Rack::Head", "Rack::ConditionalGet", "Rack::ETag" @@ -78,7 +73,7 @@ module ApplicationTests test "Rack::Cache is not included by default" do boot! - assert !middleware.include?("Rack::Cache"), "Rack::Cache is not included in the default stack unless you set config.action_dispatch.rack_cache" + assert_not_includes middleware, "Rack::Cache", "Rack::Cache is not included in the default stack unless you set config.action_dispatch.rack_cache" end test "Rack::Cache is present when action_dispatch.rack_cache is set" do @@ -86,7 +81,7 @@ module ApplicationTests boot! - assert middleware.include?("Rack::Cache") + assert_includes middleware, "Rack::Cache" end test "ActiveRecord::Migration::CheckPending is present when active_record.migration_error is set to :page_load" do @@ -94,43 +89,32 @@ module ApplicationTests boot! - assert middleware.include?("ActiveRecord::Migration::CheckPending") + assert_includes middleware, "ActiveRecord::Migration::CheckPending" end test "ActionDispatch::SSL is present when force_ssl is set" do add_to_config "config.force_ssl = true" boot! - assert middleware.include?("ActionDispatch::SSL") + assert_includes middleware, "ActionDispatch::SSL" end test "ActionDispatch::SSL is configured with options when given" do add_to_config "config.force_ssl = true" - add_to_config "config.ssl_options = { host: 'example.com' }" + add_to_config "config.ssl_options = { redirect: { host: 'example.com' } }" boot! - assert_equal [{host: 'example.com'}], Rails.application.middleware.first.args + assert_equal [{ redirect: { host: "example.com" } }], Rails.application.middleware.first.args end test "removing Active Record omits its middleware" do use_frameworks [] boot! - assert !middleware.include?("ActiveRecord::ConnectionAdapters::ConnectionManagement") - assert !middleware.include?("ActiveRecord::QueryCache") - assert !middleware.include?("ActiveRecord::Migration::CheckPending") + assert_not_includes middleware, "ActiveRecord::Migration::CheckPending" end - test "includes interlock if cache_classes is set but eager_load is not" do - add_to_config "config.cache_classes = true" + test "includes executor" do boot! - assert_not_includes middleware, "Rack::Lock" - assert_includes middleware, "ActionDispatch::LoadInterlock" - end - - test "includes interlock if cache_classes is off" do - add_to_config "config.cache_classes = false" - boot! - assert_not_includes middleware, "Rack::Lock" - assert_includes middleware, "ActionDispatch::LoadInterlock" + assert_includes middleware, "ActionDispatch::Executor" end test "does not include lock if cache_classes is set and so is eager_load" do @@ -138,40 +122,37 @@ module ApplicationTests add_to_config "config.eager_load = true" boot! assert_not_includes middleware, "Rack::Lock" - assert_not_includes middleware, "ActionDispatch::LoadInterlock" end test "does not include lock if allow_concurrency is set to :unsafe" do add_to_config "config.allow_concurrency = :unsafe" boot! assert_not_includes middleware, "Rack::Lock" - assert_not_includes middleware, "ActionDispatch::LoadInterlock" end test "includes lock if allow_concurrency is disabled" do add_to_config "config.allow_concurrency = false" boot! assert_includes middleware, "Rack::Lock" - assert_not_includes middleware, "ActionDispatch::LoadInterlock" end test "removes static asset server if public_file_server.enabled is disabled" do add_to_config "config.public_file_server.enabled = false" boot! - assert !middleware.include?("ActionDispatch::Static") + assert_not_includes middleware, "ActionDispatch::Static" end test "can delete a middleware from the stack" do add_to_config "config.middleware.delete ActionDispatch::Static" boot! - assert !middleware.include?("ActionDispatch::Static") + assert_not_includes middleware, "ActionDispatch::Static" end test "can delete a middleware from the stack even if insert_before is added after delete" do add_to_config "config.middleware.delete Rack::Runtime" add_to_config "config.middleware.insert_before(Rack::Runtime, Rack::Config)" boot! - assert middleware.include?("Rack::Config") + assert_includes middleware, "Rack::Config" assert_not middleware.include?("Rack::Runtime") end @@ -179,21 +160,21 @@ module ApplicationTests add_to_config "config.middleware.delete Rack::Runtime" add_to_config "config.middleware.insert_after(Rack::Runtime, Rack::Config)" boot! - assert middleware.include?("Rack::Config") + assert_includes middleware, "Rack::Config" assert_not middleware.include?("Rack::Runtime") end test "includes exceptions middlewares even if action_dispatch.show_exceptions is disabled" do add_to_config "config.action_dispatch.show_exceptions = false" boot! - assert middleware.include?("ActionDispatch::ShowExceptions") - assert middleware.include?("ActionDispatch::DebugExceptions") + assert_includes middleware, "ActionDispatch::ShowExceptions" + assert_includes middleware, "ActionDispatch::DebugExceptions" end test "removes ActionDispatch::Reloader if cache_classes is true" do add_to_config "config.cache_classes = true" boot! - assert !middleware.include?("ActionDispatch::Reloader") + assert_not_includes middleware, "ActionDispatch::Reloader" end test "use middleware" do @@ -209,10 +190,10 @@ module ApplicationTests assert_equal "Rack::Config", middleware.second end - test 'unshift middleware' do - add_to_config 'config.middleware.unshift Rack::Config' + test "unshift middleware" do + add_to_config "config.middleware.unshift Rack::Config" boot! - assert_equal 'Rack::Config', middleware.first + assert_equal "Rack::Config", middleware.first end test "Rails.cache does not respond to middleware" do @@ -246,19 +227,19 @@ module ApplicationTests class ::OmgController < ActionController::Base def index if params[:nothing] - render text: "" + render plain: "" else - render text: "OMG" + render plain: "OMG" end end end - etag = "W/" + "5af83e3196bf99f440f31f2e1a6c9afe".inspect + etag = "W/" + "c00862d1c6c1cf7c1b49388306e7b3c1".inspect get "/" assert_equal 200, last_response.status assert_equal "OMG", last_response.body - assert_equal "text/html; charset=utf-8", last_response.headers["Content-Type"] + assert_equal "text/plain; charset=utf-8", 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"] @@ -272,7 +253,7 @@ module ApplicationTests get "/?nothing=true" assert_equal 200, last_response.status assert_equal "", last_response.body - assert_equal "text/html; charset=utf-8", last_response.headers["Content-Type"] + 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"] end diff --git a/railties/test/application/multiple_applications_test.rb b/railties/test/application/multiple_applications_test.rb index f2770a9cb4..26b810af73 100644 --- a/railties/test/application/multiple_applications_test.rb +++ b/railties/test/application/multiple_applications_test.rb @@ -1,4 +1,4 @@ -require 'isolation/abstract_unit' +require "isolation/abstract_unit" module ApplicationTests class MultipleApplicationsTest < ActiveSupport::TestCase @@ -6,9 +6,8 @@ module ApplicationTests def setup build_app(initializers: true) - boot_rails require "#{rails_root}/config/environment" - Rails.application.config.some_setting = 'something_or_other' + Rails.application.config.some_setting = "something_or_other" end def teardown @@ -88,9 +87,9 @@ module ApplicationTests require "#{app_path}/config/environment" assert_equal 0, run_count, "The count should stay at zero without any calls to the rake tasks" - require 'rake' - require 'rake/testtask' - require 'rdoc/task' + require "rake" + require "rake/testtask" + require "rdoc/task" Rails.application.load_tasks assert_equal 2, run_count, "Calling a rake task should result in two increments to the count" end diff --git a/railties/test/application/paths_test.rb b/railties/test/application/paths_test.rb index 4029984ce9..515205296c 100644 --- a/railties/test/application/paths_test.rb +++ b/railties/test/application/paths_test.rb @@ -6,7 +6,6 @@ module ApplicationTests def setup build_app - boot_rails FileUtils.rm_rf("#{app_path}/config/environments") app_file "config/environments/development.rb", "" add_to_config <<-RUBY @@ -56,9 +55,9 @@ module ApplicationTests test "booting up Rails yields a list of paths that are eager" do eager_load = @paths.eager_load - assert eager_load.include?(root("app/controllers")) - assert eager_load.include?(root("app/helpers")) - assert eager_load.include?(root("app/models")) + assert_includes eager_load, root("app/controllers") + assert_includes eager_load, root("app/helpers") + assert_includes eager_load, root("app/models") end test "environments has a glob equal to the current environment" do diff --git a/railties/test/application/per_request_digest_cache_test.rb b/railties/test/application/per_request_digest_cache_test.rb index 3198e12662..6c003e9bcc 100644 --- a/railties/test/application/per_request_digest_cache_test.rb +++ b/railties/test/application/per_request_digest_cache_test.rb @@ -1,9 +1,9 @@ -require 'isolation/abstract_unit' -require 'rack/test' -require 'minitest/mock' +require "isolation/abstract_unit" +require "rack/test" +require "minitest/mock" -require 'action_view' -require 'active_support/testing/method_call_assertions' +require "action_view" +require "active_support/testing/method_call_assertions" class PerRequestDigestCacheTest < ActiveSupport::TestCase include ActiveSupport::Testing::Isolation @@ -12,30 +12,32 @@ class PerRequestDigestCacheTest < ActiveSupport::TestCase setup do build_app - add_to_config 'config.consider_all_requests_local = true' + add_to_config "config.consider_all_requests_local = true" - app_file 'app/models/customer.rb', <<-RUBY + app_file "app/models/customer.rb", <<-RUBY class Customer < Struct.new(:name, :id) extend ActiveModel::Naming include ActiveModel::Conversion end RUBY - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do resources :customers, only: :index end RUBY - app_file 'app/controllers/customers_controller.rb', <<-RUBY + app_file "app/controllers/customers_controller.rb", <<-RUBY class CustomersController < ApplicationController + self.perform_caching = true + def index render [ Customer.new('david', 1), Customer.new('dingus', 2) ] end end RUBY - app_file 'app/views/customers/_customer.html.erb', <<-RUBY + app_file "app/views/customers/_customer.html.erb", <<-RUBY <% cache customer do %> <%= customer.name %> <% end %> @@ -47,16 +49,17 @@ class PerRequestDigestCacheTest < ActiveSupport::TestCase teardown :teardown_app test "digests are reused when rendering the same template twice" do - get '/customers' + get "/customers" assert_equal 200, last_response.status - assert_equal [ '8ba099b7749542fe765ff34a6824d548' ], ActionView::Digestor.cache.values + values = ActionView::LookupContext::DetailsKey.digest_caches.first.values + assert_equal [ "8ba099b7749542fe765ff34a6824d548" ], values assert_equal %w(david dingus), last_response.body.split.map(&:strip) end test "template digests are cleared before a request" do - assert_called(ActionView::Digestor.cache, :clear) do - get '/customers' + assert_called(ActionView::LookupContext::DetailsKey, :clear) do + get "/customers" assert_equal 200, last_response.status end end diff --git a/railties/test/application/rack/logger_test.rb b/railties/test/application/rack/logger_test.rb index 0082ec9cd2..e71bcbc536 100644 --- a/railties/test/application/rack/logger_test.rb +++ b/railties/test/application/rack/logger_test.rb @@ -41,13 +41,13 @@ module ApplicationTests end test "logger logs HTTP verb override" do - post "/", _method: 'put' + post "/", _method: "put" wait assert_match 'Started PUT "/"', logs end test "logger logs HEAD requests" do - post "/", _method: 'head' + post "/", _method: "head" wait assert_match 'Started HEAD "/"', logs end diff --git a/railties/test/application/rackup_test.rb b/railties/test/application/rackup_test.rb index 49ac9fc66c..2943e9ee5d 100644 --- a/railties/test/application/rackup_test.rb +++ b/railties/test/application/rackup_test.rb @@ -12,14 +12,13 @@ module ApplicationTests def setup build_app - boot_rails end def teardown teardown_app end - test "rails app is present" do + test "Rails app is present" do assert File.exist?(app_path("config")) end @@ -37,7 +36,7 @@ module ApplicationTests test "the config object is available on the application object" do rackup - assert_equal 'UTC', Rails.application.config.time_zone + assert_equal "UTC", Rails.application.config.time_zone end end end diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index a229609e84..51db634b75 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -8,7 +8,6 @@ module ApplicationTests def setup build_app - boot_rails FileUtils.rm_rf("#{app_path}/config/environments") end @@ -21,7 +20,7 @@ module ApplicationTests end def set_database_url - ENV['DATABASE_URL'] = "sqlite3:#{database_url_db_name}" + ENV["DATABASE_URL"] = "sqlite3:#{database_url_db_name}" # ensure it's using the DATABASE_URL FileUtils.rm_rf("#{app_path}/config/database.yml") end @@ -29,21 +28,21 @@ module ApplicationTests def db_create_and_drop(expected_database) Dir.chdir(app_path) do output = `bin/rails db:create` - assert_empty output + assert_match(/Created database/, output) assert File.exist?(expected_database) assert_equal expected_database, ActiveRecord::Base.connection_config[:database] output = `bin/rails db:drop` - assert_empty output + assert_match(/Dropped database/, output) assert !File.exist?(expected_database) end end - test 'db:create and db:drop without database url' do + test "db:create and db:drop without database url" do require "#{app_path}/config/environment" - db_create_and_drop ActiveRecord::Base.configurations[Rails.env]['database'] + db_create_and_drop ActiveRecord::Base.configurations[Rails.env]["database"] end - test 'db:create and db:drop with database url' do + test "db:create and db:drop with database url" do require "#{app_path}/config/environment" set_database_url db_create_and_drop database_url_db_name @@ -58,7 +57,7 @@ module ApplicationTests end end - test 'db:create failure because database exists' do + test "db:create failure because database exists" do with_database_existing do output = `bin/rails db:create 2>&1` assert_match(/already exists/, output) @@ -75,7 +74,7 @@ module ApplicationTests end end - test 'db:create failure because bad permissions' do + test "db:create failure because bad permissions" do with_bad_permissions do output = `bin/rails db:create 2>&1` assert_match(/Couldn't create database/, output) @@ -83,7 +82,7 @@ module ApplicationTests end end - test 'db:drop failure because database does not exist' do + test "db:drop failure because database does not exist" do Dir.chdir(app_path) do output = `bin/rails db:drop:_unsafe --trace 2>&1` assert_match(/does not exist/, output) @@ -91,7 +90,7 @@ module ApplicationTests end end - test 'db:drop failure because bad permissions' do + test "db:drop failure because bad permissions" do with_database_existing do with_bad_permissions do output = `bin/rails db:drop 2>&1` @@ -111,12 +110,12 @@ module ApplicationTests end end - test 'db:migrate and db:migrate:status without database_url' do + test "db:migrate and db:migrate:status without database_url" do require "#{app_path}/config/environment" - db_migrate_and_status ActiveRecord::Base.configurations[Rails.env]['database'] + db_migrate_and_status ActiveRecord::Base.configurations[Rails.env]["database"] end - test 'db:migrate and db:migrate:status with database_url' do + test "db:migrate and db:migrate:status with database_url" do require "#{app_path}/config/environment" set_database_url db_migrate_and_status database_url_db_name @@ -131,11 +130,11 @@ module ApplicationTests end end - test 'db:schema:dump without database_url' do + test "db:schema:dump without database_url" do db_schema_dump end - test 'db:schema:dump with database_url' do + test "db:schema:dump with database_url" do set_database_url db_schema_dump end @@ -150,18 +149,18 @@ module ApplicationTests end end - test 'db:fixtures:load without database_url' do + test "db:fixtures:load without database_url" do require "#{app_path}/config/environment" - db_fixtures_load ActiveRecord::Base.configurations[Rails.env]['database'] + db_fixtures_load ActiveRecord::Base.configurations[Rails.env]["database"] end - test 'db:fixtures:load with database_url' do + test "db:fixtures:load with database_url" do require "#{app_path}/config/environment" set_database_url db_fixtures_load database_url_db_name end - test 'db:fixtures:load with namespaced fixture' do + test "db:fixtures:load with namespaced fixture" do require "#{app_path}/config/environment" Dir.chdir(app_path) do `bin/rails generate model admin::book title:string; @@ -185,18 +184,18 @@ module ApplicationTests end end - test 'db:structure:dump and db:structure:load without database_url' do + test "db:structure:dump and db:structure:load without database_url" do require "#{app_path}/config/environment" - db_structure_dump_and_load ActiveRecord::Base.configurations[Rails.env]['database'] + db_structure_dump_and_load ActiveRecord::Base.configurations[Rails.env]["database"] end - test 'db:structure:dump and db:structure:load with database_url' do + test "db:structure:dump and db:structure:load with database_url" do require "#{app_path}/config/environment" set_database_url db_structure_dump_and_load database_url_db_name end - test 'db:structure:dump does not dump schema information when no migrations are used' do + test "db:structure:dump does not dump schema information when no migrations are used" do Dir.chdir(app_path) do # create table without migrations `bin/rails runner 'ActiveRecord::Base.connection.create_table(:posts) {|t| t.string :title }'` @@ -208,11 +207,11 @@ module ApplicationTests end end - test 'db:schema:load and db:structure:load do not purge the existing database' do + test "db:schema:load and db:structure:load do not purge the existing database" do Dir.chdir(app_path) do `bin/rails runner 'ActiveRecord::Base.connection.create_table(:posts) {|t| t.string :title }'` - app_file 'db/schema.rb', <<-RUBY + app_file "db/schema.rb", <<-RUBY ActiveRecord::Schema.define(version: 20140423102712) do create_table(:comments) {} end @@ -224,7 +223,7 @@ module ApplicationTests `bin/rails db:schema:load` assert_equal '["posts", "comments", "schema_migrations", "ar_internal_metadata"]', list_tables[] - app_file 'db/structure.sql', <<-SQL + app_file "db/structure.sql", <<-SQL CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255)); SQL @@ -235,15 +234,15 @@ module ApplicationTests test "db:schema:load with inflections" do Dir.chdir(app_path) do - app_file 'config/initializers/inflection.rb', <<-RUBY + app_file "config/initializers/inflection.rb", <<-RUBY ActiveSupport::Inflector.inflections do |inflect| inflect.irregular 'goose', 'geese' end RUBY - app_file 'config/initializers/primary_key_table_name.rb', <<-RUBY + app_file "config/initializers/primary_key_table_name.rb", <<-RUBY ActiveRecord::Base.primary_key_prefix_type = :table_name RUBY - app_file 'db/schema.rb', <<-RUBY + app_file "db/schema.rb", <<-RUBY ActiveRecord::Schema.define(version: 20140423102712) do create_table("goose".pluralize) do |t| t.string :name @@ -270,24 +269,24 @@ module ApplicationTests require "#{app_path}/app/models/book" #if structure is not loaded correctly, exception would be raised assert_equal 0, Book.count - assert_match ActiveRecord::Base.configurations['test']['database'], + assert_match ActiveRecord::Base.configurations["test"]["database"], ActiveRecord::Base.connection_config[:database] end end - test 'db:test:load_structure without database_url' do + test "db:test:load_structure without database_url" do require "#{app_path}/config/environment" db_test_load_structure end - test 'db:setup loads schema and seeds database' do + test "db:setup loads schema and seeds database" do begin @old_rails_env = ENV["RAILS_ENV"] @old_rack_env = ENV["RACK_ENV"] ENV.delete "RAILS_ENV" ENV.delete "RACK_ENV" - app_file 'db/schema.rb', <<-RUBY + app_file "db/schema.rb", <<-RUBY ActiveRecord::Schema.define(version: "1") do create_table :users do |t| t.string :name @@ -295,7 +294,7 @@ module ApplicationTests end RUBY - app_file 'db/seeds.rb', <<-RUBY + app_file "db/seeds.rb", <<-RUBY puts ActiveRecord::Base.connection_config[:database] RUBY diff --git a/railties/test/application/rake/dev_test.rb b/railties/test/application/rake/dev_test.rb index 43d7a5e156..4f992d9c8d 100644 --- a/railties/test/application/rake/dev_test.rb +++ b/railties/test/application/rake/dev_test.rb @@ -1,4 +1,4 @@ -require 'isolation/abstract_unit' +require "isolation/abstract_unit" module ApplicationTests module RakeTests @@ -13,22 +13,31 @@ module ApplicationTests teardown_app end - test 'dev:cache creates file and outputs message' do + test "dev:cache creates file and outputs message" do Dir.chdir(app_path) do - output = `rake dev:cache` - assert File.exist?('tmp/caching-dev.txt') + output = `rails dev:cache` + assert File.exist?("tmp/caching-dev.txt") assert_match(/Development mode is now being cached/, output) end end - test 'dev:cache deletes file and outputs message' do + test "dev:cache deletes file and outputs message" do Dir.chdir(app_path) do `rails dev:cache` # Create caching file. output = `rails dev:cache` # Delete caching file. - assert_not File.exist?('tmp/caching-dev.txt') + assert_not File.exist?("tmp/caching-dev.txt") assert_match(/Development mode is no longer being cached/, output) end end + + test "dev:cache removes server.pid also" do + Dir.chdir(app_path) do + FileUtils.mkdir_p("tmp/pids") + FileUtils.touch("tmp/pids/server.pid") + `rails dev:cache` + assert_not File.exist?("tmp/pids/server.pid") + end + end end end end diff --git a/railties/test/application/rake/framework_test.rb b/railties/test/application/rake/framework_test.rb index ec57af79f6..7ac37b7700 100644 --- a/railties/test/application/rake/framework_test.rb +++ b/railties/test/application/rake/framework_test.rb @@ -8,7 +8,6 @@ module ApplicationTests def setup build_app - boot_rails FileUtils.rm_rf("#{app_path}/config/environments") end @@ -17,14 +16,14 @@ module ApplicationTests end def load_tasks - require 'rake' - require 'rdoc/task' - require 'rake/testtask' + require "rake" + require "rdoc/task" + require "rake/testtask" Rails.application.load_tasks end - test 'requiring the rake task should not define method .app_generator on Object' do + test "requiring the rake task should not define method .app_generator on Object" do require "#{app_path}/config/environment" load_tasks @@ -34,7 +33,7 @@ module ApplicationTests end end - test 'requiring the rake task should not define method .invoke_from_app_generator on Object' do + test "requiring the rake task should not define method .invoke_from_app_generator on Object" do require "#{app_path}/config/environment" load_tasks diff --git a/railties/test/application/rake/migrations_test.rb b/railties/test/application/rake/migrations_test.rb index 7e2519ae5a..76cb302c62 100644 --- a/railties/test/application/rake/migrations_test.rb +++ b/railties/test/application/rake/migrations_test.rb @@ -5,7 +5,6 @@ module ApplicationTests class RakeMigrationsTest < ActiveSupport::TestCase def setup build_app - boot_rails FileUtils.rm_rf("#{app_path}/config/environments") end @@ -13,7 +12,7 @@ module ApplicationTests teardown_app end - test 'running migrations with given scope' do + test "running migrations with given scope" do Dir.chdir(app_path) do `bin/rails generate model user username:string password:string` @@ -38,31 +37,31 @@ module ApplicationTests end end - test 'model and migration generator with change syntax' do + test "model and migration generator with change syntax" do Dir.chdir(app_path) do `bin/rails generate model user username:string password:string; bin/rails generate migration add_email_to_users email:string` - output = `bin/rails db:migrate` - assert_match(/create_table\(:users\)/, output) - assert_match(/CreateUsers: migrated/, output) - assert_match(/add_column\(:users, :email, :string\)/, output) - assert_match(/AddEmailToUsers: migrated/, output) - - output = `bin/rails db:rollback STEP=2` - assert_match(/drop_table\(:users\)/, output) - assert_match(/CreateUsers: reverted/, output) - assert_match(/remove_column\(:users, :email, :string\)/, output) - assert_match(/AddEmailToUsers: reverted/, output) + output = `bin/rails db:migrate` + assert_match(/create_table\(:users\)/, output) + assert_match(/CreateUsers: migrated/, output) + assert_match(/add_column\(:users, :email, :string\)/, output) + assert_match(/AddEmailToUsers: migrated/, output) + + output = `bin/rails db:rollback STEP=2` + assert_match(/drop_table\(:users\)/, output) + assert_match(/CreateUsers: reverted/, output) + assert_match(/remove_column\(:users, :email, :string\)/, output) + assert_match(/AddEmailToUsers: reverted/, output) end end - test 'migration status when schema migrations table is not present' do - output = Dir.chdir(app_path){ `bin/rails db:migrate:status 2>&1` } + test "migration status when schema migrations table is not present" do + output = Dir.chdir(app_path) { `bin/rails db:migrate:status 2>&1` } assert_equal "Schema migrations table does not exist yet.\n", output end - test 'test migration status' do + test "test migration status" do Dir.chdir(app_path) do `bin/rails generate model user username:string password:string; bin/rails generate migration add_email_to_users email:string; @@ -81,8 +80,8 @@ module ApplicationTests end end - test 'migration status without timestamps' do - add_to_config('config.active_record.timestamped_migrations = false') + test "migration status without timestamps" do + add_to_config("config.active_record.timestamped_migrations = false") Dir.chdir(app_path) do `bin/rails generate model user username:string password:string; @@ -102,59 +101,59 @@ module ApplicationTests end end - test 'test migration status after rollback and redo' do + test "test migration status after rollback and redo" do Dir.chdir(app_path) do `bin/rails generate model user username:string password:string; bin/rails generate migration add_email_to_users email:string; bin/rails db:migrate` - output = `bin/rails db:migrate:status` + output = `bin/rails db:migrate:status` - assert_match(/up\s+\d{14}\s+Create users/, output) - assert_match(/up\s+\d{14}\s+Add email to users/, output) + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/up\s+\d{14}\s+Add email to users/, output) - `bin/rails db:rollback STEP=2` - output = `bin/rails db:migrate:status` + `bin/rails db:rollback STEP=2` + output = `bin/rails db:migrate:status` - assert_match(/down\s+\d{14}\s+Create users/, output) - assert_match(/down\s+\d{14}\s+Add email to users/, output) + assert_match(/down\s+\d{14}\s+Create users/, output) + assert_match(/down\s+\d{14}\s+Add email to users/, output) - `bin/rails db:migrate:redo` - output = `bin/rails db:migrate:status` + `bin/rails db:migrate:redo` + output = `bin/rails db:migrate:status` - assert_match(/up\s+\d{14}\s+Create users/, output) - assert_match(/up\s+\d{14}\s+Add email to users/, output) + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/up\s+\d{14}\s+Add email to users/, output) end end - test 'migration status after rollback and redo without timestamps' do - add_to_config('config.active_record.timestamped_migrations = false') + test "migration status after rollback and redo without timestamps" do + add_to_config("config.active_record.timestamped_migrations = false") Dir.chdir(app_path) do `bin/rails generate model user username:string password:string; bin/rails generate migration add_email_to_users email:string; bin/rails db:migrate` - output = `bin/rails db:migrate:status` + output = `bin/rails db:migrate:status` - assert_match(/up\s+\d{3,}\s+Create users/, output) - assert_match(/up\s+\d{3,}\s+Add email to users/, output) + assert_match(/up\s+\d{3,}\s+Create users/, output) + assert_match(/up\s+\d{3,}\s+Add email to users/, output) - `bin/rails db:rollback STEP=2` - output = `bin/rails db:migrate:status` + `bin/rails db:rollback STEP=2` + output = `bin/rails db:migrate:status` - assert_match(/down\s+\d{3,}\s+Create users/, output) - assert_match(/down\s+\d{3,}\s+Add email to users/, output) + assert_match(/down\s+\d{3,}\s+Create users/, output) + assert_match(/down\s+\d{3,}\s+Add email to users/, output) - `bin/rails db:migrate:redo` - output = `bin/rails db:migrate:status` + `bin/rails db:migrate:redo` + output = `bin/rails db:migrate:status` - assert_match(/up\s+\d{3,}\s+Create users/, output) - assert_match(/up\s+\d{3,}\s+Add email to users/, output) + assert_match(/up\s+\d{3,}\s+Create users/, output) + assert_match(/up\s+\d{3,}\s+Add email to users/, output) end end - test 'running migrations with not timestamp head migration files' do + test "running migrations with not timestamp head migration files" do Dir.chdir(app_path) do app_file "db/migrate/1_one_migration.rb", <<-MIGRATION @@ -169,15 +168,15 @@ module ApplicationTests `bin/rails db:migrate` - output = `bin/rails db:migrate:status` + output = `bin/rails db:migrate:status` - assert_match(/up\s+001\s+One migration/, output) - assert_match(/up\s+002\s+Two migration/, output) + assert_match(/up\s+001\s+One migration/, output) + assert_match(/up\s+002\s+Two migration/, output) end end - test 'schema generation when dump_schema_after_migration is set' do - add_to_config('config.active_record.dump_schema_after_migration = false') + test "schema generation when dump_schema_after_migration is set" do + add_to_config("config.active_record.dump_schema_after_migration = false") Dir.chdir(app_path) do `bin/rails generate model book title:string` @@ -188,7 +187,7 @@ module ApplicationTests assert !File.exist?("db/schema.rb"), "should not dump schema when configured not to" end - add_to_config('config.active_record.dump_schema_after_migration = true') + add_to_config("config.active_record.dump_schema_after_migration = true") Dir.chdir(app_path) do `bin/rails generate model reviews book_id:integer` @@ -199,7 +198,7 @@ module ApplicationTests end end - test 'default schema generation after migration' do + test "default schema generation after migration" do Dir.chdir(app_path) do `bin/rails generate model book title:string; bin/rails db:migrate` @@ -209,7 +208,7 @@ module ApplicationTests end end - test 'test migration status migrated file is deleted' do + test "test migration status migrated file is deleted" do Dir.chdir(app_path) do `bin/rails generate model user username:string password:string; bin/rails generate migration add_email_to_users email:string; @@ -217,7 +216,7 @@ module ApplicationTests rm db/migrate/*email*.rb` output = `bin/rails db:migrate:status` - File.write('test.txt', output) + File.write("test.txt", output) assert_match(/up\s+\d{14}\s+Create users/, output) assert_match(/up\s+\d{14}\s+\** NO FILE \**/, output) diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb index 50def9beb0..e7ffea2e71 100644 --- a/railties/test/application/rake/notes_test.rb +++ b/railties/test/application/rake/notes_test.rb @@ -1,5 +1,5 @@ require "isolation/abstract_unit" -require 'rails/source_annotation_extractor' +require "rails/source_annotation_extractor" module ApplicationTests module RakeTests @@ -17,15 +17,15 @@ module ApplicationTests teardown_app end - test 'notes finds notes for certain file_types' do + test "notes finds notes for certain file_types" do app_file "app/views/home/index.html.erb", "<% # TODO: note in erb %>" app_file "app/assets/javascripts/application.js", "// TODO: note in js" app_file "app/assets/stylesheets/application.css", "// TODO: note in css" app_file "app/controllers/application_controller.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in ruby" app_file "lib/tasks/task.rake", "# TODO: note in rake" - app_file 'app/views/home/index.html.builder', '# TODO: note in builder' - app_file 'config/locales/en.yml', '# TODO: note in yml' - app_file 'config/locales/en.yaml', '# TODO: note in yaml' + app_file "app/views/home/index.html.builder", "# TODO: note in builder" + app_file "config/locales/en.yml", "# TODO: note in yml" + app_file "config/locales/en.yaml", "# TODO: note in yaml" app_file "app/views/home/index.ruby", "# TODO: note in ruby" run_rake_notes do |output, lines| @@ -43,7 +43,7 @@ module ApplicationTests end end - test 'notes finds notes in default directories' do + test "notes finds notes in default directories" do app_file "app/controllers/some_controller.rb", "# TODO: note in app directory" app_file "config/initializers/some_initializer.rb", "# TODO: note in config directory" app_file "db/some_seeds.rb", "# TODO: note in db directory" @@ -65,7 +65,7 @@ module ApplicationTests end end - test 'notes finds notes in custom directories' do + test "notes finds notes in custom directories" do app_file "app/controllers/some_controller.rb", "# TODO: note in app directory" app_file "config/initializers/some_initializer.rb", "# TODO: note in config directory" app_file "db/some_seeds.rb", "# TODO: note in db directory" @@ -88,7 +88,7 @@ module ApplicationTests end end - test 'custom rake task finds specific notes in specific directories' do + test "custom rake task finds specific notes in specific directories" do app_file "app/controllers/some_controller.rb", "# TODO: note in app directory" app_file "lib/some_file.rb", "# OPTIMIZE: note in lib directory\n" << "# FIXME: note in lib directory" app_file "test/some_test.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in test directory" @@ -113,7 +113,7 @@ module ApplicationTests end end - test 'register a new extension' do + test "register a new extension" do add_to_config "config.assets.precompile = []" add_to_config %q{ config.annotations.register_extensions("scss", "sass") { |annotation| /\/\/\s*(#{annotation}):?\s*(.*)$/ } } app_file "app/assets/stylesheets/application.css.scss", "// TODO: note in scss" @@ -126,32 +126,43 @@ module ApplicationTests end end + test "register additional directories" do + app_file "spec/spec_helper.rb", "# TODO: note in spec" + app_file "spec/models/user_spec.rb", "# TODO: note in model spec" + add_to_config ' config.annotations.register_directories("spec") ' + + run_rake_notes do |output, lines| + assert_match(/note in spec/, output) + assert_match(/note in model spec/, output) + assert_equal 2, lines.size + end + end + private - def run_rake_notes(command = 'bin/rails notes') - boot_rails - load_tasks + def run_rake_notes(command = "bin/rails notes") + boot_rails + load_tasks - Dir.chdir(app_path) do - output = `#{command}` - lines = output.scan(/\[([0-9\s]+)\]\s/).flatten + Dir.chdir(app_path) do + output = `#{command}` + lines = output.scan(/\[([0-9\s]+)\]\s/).flatten - yield output, lines + yield output, lines + end end - end - def load_tasks - require 'rake' - require 'rdoc/task' - require 'rake/testtask' + def load_tasks + require "rake" + require "rdoc/task" + require "rake/testtask" - Rails.application.load_tasks - end + Rails.application.load_tasks + end - def boot_rails - super - require "#{app_path}/config/environment" - end + def boot_rails + require "#{app_path}/config/environment" + end end end end diff --git a/railties/test/application/rake/restart_test.rb b/railties/test/application/rake/restart_test.rb index 4cae199e6b..6ebd2d5461 100644 --- a/railties/test/application/rake/restart_test.rb +++ b/railties/test/application/rake/restart_test.rb @@ -7,31 +7,39 @@ module ApplicationTests def setup build_app - boot_rails end def teardown teardown_app end - test 'rake restart touches tmp/restart.txt' do + test "rails restart touches tmp/restart.txt" do Dir.chdir(app_path) do - `rake restart` + `bin/rails restart` assert File.exist?("tmp/restart.txt") prev_mtime = File.mtime("tmp/restart.txt") sleep(1) - `rake restart` + `bin/rails restart` curr_mtime = File.mtime("tmp/restart.txt") assert_not_equal prev_mtime, curr_mtime end end - test 'rake restart should work even if tmp folder does not exist' do + test "rails restart should work even if tmp folder does not exist" do Dir.chdir(app_path) do - FileUtils.remove_dir('tmp') - `rake restart` - assert File.exist?('tmp/restart.txt') + FileUtils.remove_dir("tmp") + `bin/rails restart` + assert File.exist?("tmp/restart.txt") + end + end + + test "rails restart removes server.pid also" do + Dir.chdir(app_path) do + FileUtils.mkdir_p("tmp/pids") + FileUtils.touch("tmp/pids/server.pid") + `bin/rails restart` + assert_not File.exist?("tmp/pids/server.pid") end end end diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index d1c828b509..5fd5507453 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -7,7 +7,6 @@ module ApplicationTests def setup build_app - boot_rails end def teardown @@ -24,7 +23,7 @@ module ApplicationTests assert $task_loaded end - def test_the_test_rake_task_is_protected_when_previous_migration_was_production + test "task is protected when previous migration was production" do Dir.chdir(app_path) do output = `bin/rails generate model product name:string; env RAILS_ENV=production bin/rails db:create db:migrate; @@ -55,7 +54,7 @@ module ApplicationTests Rails.application.initialize! RUBY - assert_match("SuperMiddleware", Dir.chdir(app_path){ `bin/rails middleware` }) + assert_match("SuperMiddleware", Dir.chdir(app_path) { `bin/rails middleware` }) end def test_initializers_are_executed_in_rake_tasks @@ -70,7 +69,7 @@ module ApplicationTests end RUBY - output = Dir.chdir(app_path){ `bin/rails do_nothing` } + output = Dir.chdir(app_path) { `bin/rails do_nothing` } assert_match "Doing something...", output end @@ -83,7 +82,7 @@ module ApplicationTests end RUBY - app_file 'app/models/hello.rb', <<-RUBY + app_file "app/models/hello.rb", <<-RUBY class Hello def world puts 'Hello world' @@ -92,7 +91,7 @@ module ApplicationTests RUBY output = Dir.chdir(app_path) { `bin/rails do_nothing` } - assert_match 'Hello world', output + assert_match "Hello world", output end def test_should_not_eager_load_model_for_rake @@ -103,37 +102,37 @@ module ApplicationTests end RUBY - add_to_env_config 'production', <<-RUBY + add_to_env_config "production", <<-RUBY config.eager_load = true RUBY - app_file 'app/models/hello.rb', <<-RUBY + app_file "app/models/hello.rb", <<-RUBY raise 'should not be pre-required for rake even eager_load=true' RUBY Dir.chdir(app_path) do - assert system('bin/rails do_nothing RAILS_ENV=production'), - 'should not be pre-required for rake even eager_load=true' + assert system("bin/rails do_nothing RAILS_ENV=production"), + "should not be pre-required for rake even eager_load=true" end end def test_code_statistics_sanity - assert_match "Code LOC: 14 Test LOC: 0 Code to Test Ratio: 1:0.0", - Dir.chdir(app_path){ `bin/rails stats` } + assert_match "Code LOC: 26 Test LOC: 0 Code to Test Ratio: 1:0.0", + Dir.chdir(app_path) { `bin/rails stats` } end - def test_rake_routes_calls_the_route_inspector + def test_rails_routes_calls_the_route_inspector app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get '/cart', to: 'cart#show' end RUBY - output = Dir.chdir(app_path){ `bin/rails routes` } + output = Dir.chdir(app_path) { `bin/rails routes` } assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output end - def test_rake_routes_with_controller_environment + def test_rails_routes_with_controller_environment app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get '/cart', to: 'cart#show' @@ -141,17 +140,17 @@ module ApplicationTests end RUBY - output = Dir.chdir(app_path){ `bin/rails routes CONTROLLER=cart` } + 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` } + 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_rake_routes_with_namespaced_controller_environment + def test_rails_routes_with_namespaced_controller_environment app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do namespace :admin do @@ -168,14 +167,14 @@ module ApplicationTests " PUT /admin/post(.:format) admin/posts#update", " DELETE /admin/post(.:format) admin/posts#destroy\n"].join("\n") - output = Dir.chdir(app_path){ `bin/rails routes -c Admin::PostController` } + 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` } + output = Dir.chdir(app_path) { `bin/rails routes -c PostController` } assert_equal expected_output, output end - def test_rake_routes_with_global_search_key + def test_rails_routes_with_global_search_key app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get '/cart', to: 'cart#show' @@ -184,18 +183,18 @@ module ApplicationTests end RUBY - output = Dir.chdir(app_path){ `bin/rails routes -g show` } + output = Dir.chdir(app_path) { `bin/rails routes -g show` } assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output - output = Dir.chdir(app_path){ `bin/rails routes -g POST` } + output = Dir.chdir(app_path) { `bin/rails routes -g POST` } assert_equal "Prefix Verb URI Pattern Controller#Action\n POST /cart(.:format) cart#create\n", output - output = Dir.chdir(app_path){ `bin/rails routes -g basketballs` } + output = Dir.chdir(app_path) { `bin/rails routes -g basketballs` } assert_equal " Prefix Verb URI Pattern Controller#Action\n" \ "basketballs GET /basketballs(.:format) basketball#index\n", output end - def test_rake_routes_with_controller_search_key + def test_rails_routes_with_controller_search_key app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get '/cart', to: 'cart#show' @@ -203,23 +202,23 @@ module ApplicationTests end RUBY - output = Dir.chdir(app_path){ `bin/rails routes -c cart` } + 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 - output = Dir.chdir(app_path){ `bin/rails routes -c Cart` } + 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 - output = Dir.chdir(app_path){ `bin/rails routes -c CartController` } + output = Dir.chdir(app_path) { `bin/rails routes -c CartController` } assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output end - def test_rake_routes_displays_message_when_no_routes_are_defined + def test_rails_routes_displays_message_when_no_routes_are_defined app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do end RUBY - assert_equal <<-MESSAGE.strip_heredoc, Dir.chdir(app_path){ `bin/rails routes` } + assert_equal <<-MESSAGE.strip_heredoc, Dir.chdir(app_path) { `bin/rails routes` } You don't have any routes defined! Please add some routes in config/routes.rb. @@ -228,6 +227,17 @@ module ApplicationTests MESSAGE end + def test_rake_routes_with_rake_options + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/cart', to: 'cart#show' + end + RUBY + + output = Dir.chdir(app_path) { `bin/rake --rakefile Rakefile routes` } + assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output + end + def test_logger_is_flushed_when_exiting_production_rake_tasks add_to_config <<-RUBY rake_tasks do @@ -237,7 +247,7 @@ module ApplicationTests end RUBY - output = Dir.chdir(app_path){ `bin/rails log_something RAILS_ENV=production && cat log/production.log` } + output = Dir.chdir(app_path) { `bin/rails log_something RAILS_ENV=production && cat log/production.log` } assert_match "Sample log message", output end @@ -276,7 +286,7 @@ module ApplicationTests RAILS_ENV=test bin/rails db:migrate test` end - assert_match(/7 runs, 12 assertions, 0 failures, 0 errors/, output) + assert_match(/7 runs, 9 assertions, 0 failures, 0 errors/, output) assert_no_match(/Errors running/, output) end @@ -307,7 +317,7 @@ module ApplicationTests RAILS_ENV=test bin/rails db:migrate test` end - assert_match(/7 runs, 12 assertions, 0 failures, 0 errors/, output) + assert_match(/7 runs, 9 assertions, 0 failures, 0 errors/, output) assert_no_match(/Errors running/, output) end @@ -336,7 +346,7 @@ module ApplicationTests # ensure we have a schema_migrations table to dump `bin/rails db:migrate db:structure:dump SCHEMA=db/my_structure.sql` end - assert File.exist?(File.join(app_path, 'db', 'my_structure.sql')) + assert File.exist?(File.join(app_path, "db", "my_structure.sql")) end def test_rake_dump_structure_should_be_called_twice_when_migrate_redo @@ -357,24 +367,24 @@ 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.dump")) 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.dump")) end def test_copy_templates Dir.chdir(app_path) do - `bin/rails rails:templates:copy` + `bin/rails app:templates:copy` %w(controller mailer scaffold).each do |dir| - assert File.exist?(File.join(app_path, 'lib', 'templates', 'erb', dir)) + assert File.exist?(File.join(app_path, "lib", "templates", "erb", dir)) end %w(controller helper scaffold_controller assets).each do |dir| - assert File.exist?(File.join(app_path, 'lib', 'templates', 'rails', dir)) + assert File.exist?(File.join(app_path, "lib", "templates", "rails", dir)) end end end @@ -384,7 +394,7 @@ module ApplicationTests app_file "template.rb", "" output = Dir.chdir(app_path) do - `bin/rails rails:template LOCATION=template.rb` + `bin/rails app:template LOCATION=template.rb` end assert_match(/Hello, World!/, output) diff --git a/railties/test/application/rendering_test.rb b/railties/test/application/rendering_test.rb index b01febd768..ccafc5b6f1 100644 --- a/railties/test/application/rendering_test.rb +++ b/railties/test/application/rendering_test.rb @@ -1,5 +1,5 @@ -require 'isolation/abstract_unit' -require 'rack/test' +require "isolation/abstract_unit" +require "rack/test" module ApplicationTests class RoutingTest < ActiveSupport::TestCase @@ -8,7 +8,6 @@ module ApplicationTests def setup build_app - boot_rails end def teardown @@ -16,13 +15,13 @@ module ApplicationTests end test "Unknown format falls back to HTML template" do - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get 'pages/:id', to: 'pages#show' end RUBY - app_file 'app/controllers/pages_controller.rb', <<-RUBY + app_file "app/controllers/pages_controller.rb", <<-RUBY class PagesController < ApplicationController layout false @@ -31,14 +30,14 @@ module ApplicationTests end RUBY - app_file 'app/views/pages/show.html.erb', <<-RUBY + app_file "app/views/pages/show.html.erb", <<-RUBY <%= params[:id] %> RUBY - get '/pages/foo' + get "/pages/foo" assert_equal 200, last_response.status - get '/pages/foo.bar' + get "/pages/foo.bar" assert_equal 200, last_response.status end end diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb index e51f32aaed..c515e2b270 100644 --- a/railties/test/application/routing_test.rb +++ b/railties/test/application/routing_test.rb @@ -1,5 +1,5 @@ -require 'isolation/abstract_unit' -require 'rack/test' +require "isolation/abstract_unit" +require "rack/test" module ApplicationTests class RoutingTest < ActiveSupport::TestCase @@ -8,7 +8,6 @@ module ApplicationTests def setup build_app - boot_rails end def teardown @@ -39,27 +38,46 @@ module ApplicationTests assert_equal 200, last_response.status end + test "/rails/info routes are accessible with globbing route present" do + app("development") + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '*foo', to: 'foo#index' + end + RUBY + + get "/rails/info" + assert_equal 302, last_response.status + + get "rails/info/routes" + assert_equal 200, last_response.status + + get "rails/info/properties" + assert_equal 200, last_response.status + end + test "root takes precedence over internal welcome controller" do app("development") - assert_welcome get('/') + assert_welcome get("/") controller :foo, <<-RUBY class FooController < ApplicationController def index - render text: "foo" + render plain: "foo" end end RUBY - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do root to: "foo#index" end RUBY - get '/' - assert_equal 'foo', last_response.body + get "/" + assert_equal "foo", last_response.body end test "rails/welcome in production" do @@ -89,8 +107,8 @@ module ApplicationTests test "simple controller" do simple_controller - get '/foo' - assert_equal 'foo', last_response.body + get "/foo" + assert_equal "foo", last_response.body end test "simple controller with helper" do @@ -102,7 +120,7 @@ module ApplicationTests end RUBY - app_file 'app/helpers/bar_helper.rb', <<-RUBY + app_file "app/helpers/bar_helper.rb", <<-RUBY module BarHelper def foo_or_bar? "bar" @@ -110,18 +128,18 @@ module ApplicationTests end RUBY - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get ':controller(/:action)' end RUBY - get '/foo' - assert_equal 'bar', last_response.body + get "/foo" + assert_equal "bar", last_response.body end test "mount rack app" do - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do mount lambda { |env| [200, {}, [env["PATH_INFO"]]] }, at: "/blog" # The line below is required because mount sometimes @@ -130,35 +148,35 @@ module ApplicationTests end RUBY - get '/blog/archives' - assert_equal '/archives', last_response.body + get "/blog/archives" + assert_equal "/archives", last_response.body end test "mount named rack app" do controller :foo, <<-RUBY class FooController < ApplicationController def index - render text: my_blog_path + render plain: my_blog_path end end RUBY - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do mount lambda { |env| [200, {}, [env["PATH_INFO"]]] }, at: "/blog", as: "my_blog" get '/foo' => 'foo#index' end RUBY - get '/foo' - assert_equal '/blog', last_response.body + get "/foo" + assert_equal "/blog", last_response.body end test "multiple controllers" do controller :foo, <<-RUBY class FooController < ApplicationController def index - render text: "foo" + render plain: "foo" end end RUBY @@ -166,59 +184,59 @@ module ApplicationTests controller :bar, <<-RUBY class BarController < ActionController::Base def index - render text: "bar" + render plain: "bar" end end RUBY - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get ':controller(/:action)' end RUBY - get '/foo' - assert_equal 'foo', last_response.body + get "/foo" + assert_equal "foo", last_response.body - get '/bar' - assert_equal 'bar', last_response.body + get "/bar" + assert_equal "bar", last_response.body end test "nested controller" do - controller 'foo', <<-RUBY + controller "foo", <<-RUBY class FooController < ApplicationController def index - render text: "foo" + render plain: "foo" end end RUBY - controller 'admin/foo', <<-RUBY + controller "admin/foo", <<-RUBY module Admin class FooController < ApplicationController def index - render text: "admin::foo" + render plain: "admin::foo" end end end RUBY - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get 'admin/foo', to: 'admin/foo#index' get 'foo', to: 'foo#index' end RUBY - get '/foo' - assert_equal 'foo', last_response.body + get "/foo" + assert_equal "foo", last_response.body - get '/admin/foo' - assert_equal 'admin::foo', last_response.body + get "/admin/foo" + assert_equal "admin::foo", last_response.body end test "routes appending blocks" do - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get ':controller/:action' end @@ -230,36 +248,36 @@ module ApplicationTests end R - app 'development' + app "development" - get '/win' - assert_equal 'WIN', last_response.body + get "/win" + assert_equal "WIN", last_response.body - app_file 'config/routes.rb', <<-R + app_file "config/routes.rb", <<-R Rails.application.routes.draw do get 'lol' => 'hello#index' end R - get '/win' - assert_equal 'WIN', last_response.body + get "/win" + assert_equal "WIN", last_response.body end - {"development" => "baz", "production" => "bar"}.each do |mode, expected| + { "development" => "baz", "production" => "bar" }.each do |mode, expected| test "reloads routes when configuration is changed in #{mode}" do controller :foo, <<-RUBY class FooController < ApplicationController def bar - render text: "bar" + render plain: "bar" end def baz - render text: "baz" + render plain: "baz" end end RUBY - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get 'foo', to: 'foo#bar' end @@ -267,10 +285,10 @@ module ApplicationTests app(mode) - get '/foo' - assert_equal 'bar', last_response.body + get "/foo" + assert_equal "bar", last_response.body - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get 'foo', to: 'foo#baz' end @@ -278,12 +296,12 @@ module ApplicationTests sleep 0.1 - get '/foo' + get "/foo" assert_equal expected, last_response.body end end - test 'routes are loaded just after initialization' do + test "routes are loaded just after initialization" do require "#{app_path}/config/application" # Create the rack app just inside after initialize callback @@ -291,17 +309,17 @@ module ApplicationTests ::InitializeRackApp = lambda { |env| [200, {}, ["InitializeRackApp"]] } end - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get 'foo', to: ::InitializeRackApp end RUBY - get '/foo' + get "/foo" assert_equal "InitializeRackApp", last_response.body end - test 'reload_routes! is part of Rails.application API' do + test "reload_routes! is part of Rails.application API" do app("development") assert_nothing_raised do Rails.application.reload_routes! @@ -309,36 +327,36 @@ module ApplicationTests end def test_root_path - app('development') + app("development") controller :foo, <<-RUBY class FooController < ApplicationController def index - render :text => "foo" + render plain: "foo" end end RUBY - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get 'foo', :to => 'foo#index' root :to => 'foo#index' end RUBY - remove_file 'public/index.html' + remove_file "public/index.html" - get '/' - assert_equal 'foo', last_response.body + get "/" + assert_equal "foo", last_response.body end - test 'routes are added and removed when reloading' do - app('development') + test "routes are added and removed when reloading" do + app("development") controller :foo, <<-RUBY class FooController < ApplicationController def index - render text: "foo" + render plain: "foo" end end RUBY @@ -346,28 +364,28 @@ module ApplicationTests controller :bar, <<-RUBY class BarController < ApplicationController def index - render text: "bar" + render plain: "bar" end end RUBY - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get 'foo', to: 'foo#index' end RUBY - get '/foo' - assert_equal 'foo', last_response.body - assert_equal '/foo', Rails.application.routes.url_helpers.foo_path + get "/foo" + assert_equal "foo", last_response.body + assert_equal "/foo", Rails.application.routes.url_helpers.foo_path - get '/bar' + get "/bar" assert_equal 404, last_response.status assert_raises NoMethodError do - assert_equal '/bar', Rails.application.routes.url_helpers.bar_path + assert_equal "/bar", Rails.application.routes.url_helpers.bar_path end - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get 'foo', to: 'foo#index' get 'bar', to: 'bar#index' @@ -376,15 +394,15 @@ module ApplicationTests Rails.application.reload_routes! - get '/foo' - assert_equal 'foo', last_response.body - assert_equal '/foo', Rails.application.routes.url_helpers.foo_path + get "/foo" + assert_equal "foo", last_response.body + assert_equal "/foo", Rails.application.routes.url_helpers.foo_path - get '/bar' - assert_equal 'bar', last_response.body - assert_equal '/bar', Rails.application.routes.url_helpers.bar_path + get "/bar" + assert_equal "bar", last_response.body + assert_equal "/bar", Rails.application.routes.url_helpers.bar_path - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get 'foo', to: 'foo#index' end @@ -392,24 +410,24 @@ module ApplicationTests Rails.application.reload_routes! - get '/foo' - assert_equal 'foo', last_response.body - assert_equal '/foo', Rails.application.routes.url_helpers.foo_path + get "/foo" + assert_equal "foo", last_response.body + assert_equal "/foo", Rails.application.routes.url_helpers.foo_path - get '/bar' + get "/bar" assert_equal 404, last_response.status assert_raises NoMethodError do - assert_equal '/bar', Rails.application.routes.url_helpers.bar_path + assert_equal "/bar", Rails.application.routes.url_helpers.bar_path end end - test 'named routes are cleared when reloading' do - app('development') + test "named routes are cleared when reloading" do + app("development") controller :foo, <<-RUBY class FooController < ApplicationController def index - render text: "foo" + render plain: "foo" end end RUBY @@ -417,22 +435,22 @@ module ApplicationTests controller :bar, <<-RUBY class BarController < ApplicationController def index - render text: "bar" + render plain: "bar" end end RUBY - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get ':locale/foo', to: 'foo#index', as: 'foo' end RUBY - get '/en/foo' - assert_equal 'foo', last_response.body - assert_equal '/en/foo', Rails.application.routes.url_helpers.foo_path(:locale => 'en') + get "/en/foo" + assert_equal "foo", last_response.body + assert_equal "/en/foo", Rails.application.routes.url_helpers.foo_path(locale: "en") - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get ':locale/bar', to: 'bar#index', as: 'foo' end @@ -440,39 +458,39 @@ module ApplicationTests Rails.application.reload_routes! - get '/en/foo' + get "/en/foo" assert_equal 404, last_response.status - get '/en/bar' - assert_equal 'bar', last_response.body - assert_equal '/en/bar', Rails.application.routes.url_helpers.foo_path(:locale => 'en') + get "/en/bar" + assert_equal "bar", last_response.body + assert_equal "/en/bar", Rails.application.routes.url_helpers.foo_path(locale: "en") end - test 'resource routing with irregular inflection' do - app_file 'config/initializers/inflection.rb', <<-RUBY + test "resource routing with irregular inflection" do + app_file "config/initializers/inflection.rb", <<-RUBY ActiveSupport::Inflector.inflections do |inflect| inflect.irregular 'yazi', 'yazilar' end RUBY - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do resources :yazilar end RUBY - controller 'yazilar', <<-RUBY + controller "yazilar", <<-RUBY class YazilarController < ApplicationController def index - render text: 'yazilar#index' + render plain: 'yazilar#index' end end RUBY - get '/yazilars' + get "/yazilars" assert_equal 404, last_response.status - get '/yazilar' + get "/yazilar" assert_equal 200, last_response.status end end diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb index 9f15ce5e85..8769703f66 100644 --- a/railties/test/application/runner_test.rb +++ b/railties/test/application/runner_test.rb @@ -1,5 +1,5 @@ -require 'isolation/abstract_unit' -require 'env_helpers' +require "isolation/abstract_unit" +require "env_helpers" module ApplicationTests class RunnerTest < ActiveSupport::TestCase @@ -8,7 +8,6 @@ module ApplicationTests def setup build_app - boot_rails # Lets create a model so we have something to play with app_file "app/models/user.rb", <<-MODEL @@ -44,6 +43,15 @@ module ApplicationTests assert_match "42", Dir.chdir(app_path) { `bin/rails runner "bin/count_users.rb"` } end + def test_no_minitest_loaded_in_production_mode + app_file "bin/print_features.rb", <<-SCRIPT + p $LOADED_FEATURES.grep(/minitest/) + SCRIPT + assert_match "[]", Dir.chdir(app_path) { + `RAILS_ENV=production bin/rails runner "bin/print_features.rb"` + } + end + def test_should_set_dollar_0_to_file app_file "bin/dollar0.rb", <<-SCRIPT puts $0 @@ -75,13 +83,15 @@ module ApplicationTests end def test_runner_detects_syntax_errors - Dir.chdir(app_path) { `bin/rails runner "puts 'hello world" 2>&1` } - refute $?.success? + output = Dir.chdir(app_path) { `bin/rails runner "puts 'hello world" 2>&1` } + assert_not $?.success? + assert_match "unterminated string meets end of file", output end def test_runner_detects_bad_script_name - Dir.chdir(app_path) { `bin/rails runner "iuiqwiourowe" 2>&1` } - refute $?.success? + output = Dir.chdir(app_path) { `bin/rails runner "iuiqwiourowe" 2>&1` } + assert_not $?.success? + assert_match "undefined local variable or method `iuiqwiourowe' for", output end def test_environment_with_rails_env diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb index 7ecadb60ca..b442891769 100644 --- a/railties/test/application/test_runner_test.rb +++ b/railties/test/application/test_runner_test.rb @@ -1,6 +1,6 @@ -require 'isolation/abstract_unit' -require 'active_support/core_ext/string/strip' -require 'env_helpers' +require "isolation/abstract_unit" +require "active_support/core_ext/string/strip" +require "env_helpers" module ApplicationTests class TestRunnerTest < ActiveSupport::TestCase @@ -16,31 +16,31 @@ module ApplicationTests end def test_run_single_file - create_test_file :models, 'foo' - create_test_file :models, 'bar' + create_test_file :models, "foo" + create_test_file :models, "bar" assert_match "1 runs, 1 assertions, 0 failures", run_test_command("test/models/foo_test.rb") end def test_run_multiple_files - create_test_file :models, 'foo' - create_test_file :models, 'bar' + create_test_file :models, "foo" + create_test_file :models, "bar" assert_match "2 runs, 2 assertions, 0 failures", run_test_command("test/models/foo_test.rb test/models/bar_test.rb") end def test_run_file_with_syntax_error - app_file 'test/models/error_test.rb', <<-RUBY + app_file "test/models/error_test.rb", <<-RUBY require 'test_helper' def; end RUBY - error = capture(:stderr) { run_test_command('test/models/error_test.rb') } + error = capture(:stderr) { run_test_command("test/models/error_test.rb") } assert_match "syntax error", error end def test_run_models - create_test_file :models, 'foo' - create_test_file :models, 'bar' - create_test_file :controllers, 'foobar_controller' + create_test_file :models, "foo" + create_test_file :models, "bar" + create_test_file :controllers, "foobar_controller" run_test_command("test/models").tap do |output| assert_match "FooTest", output assert_match "BarTest", output @@ -49,9 +49,9 @@ module ApplicationTests end def test_run_helpers - create_test_file :helpers, 'foo_helper' - create_test_file :helpers, 'bar_helper' - create_test_file :controllers, 'foobar_controller' + create_test_file :helpers, "foo_helper" + create_test_file :helpers, "bar_helper" + create_test_file :controllers, "foobar_controller" run_test_command("test/helpers").tap do |output| assert_match "FooHelperTest", output assert_match "BarHelperTest", output @@ -61,10 +61,10 @@ module ApplicationTests def test_run_units skip "we no longer have the concept of unit tests. Just different directories..." - create_test_file :models, 'foo' - create_test_file :helpers, 'bar_helper' - create_test_file :unit, 'baz_unit' - create_test_file :controllers, 'foobar_controller' + create_test_file :models, "foo" + create_test_file :helpers, "bar_helper" + create_test_file :unit, "baz_unit" + create_test_file :controllers, "foobar_controller" run_test_units_command.tap do |output| assert_match "FooTest", output assert_match "BarHelperTest", output @@ -74,9 +74,9 @@ module ApplicationTests end def test_run_controllers - create_test_file :controllers, 'foo_controller' - create_test_file :controllers, 'bar_controller' - create_test_file :models, 'foo' + create_test_file :controllers, "foo_controller" + create_test_file :controllers, "bar_controller" + create_test_file :models, "foo" run_test_command("test/controllers").tap do |output| assert_match "FooControllerTest", output assert_match "BarControllerTest", output @@ -85,9 +85,9 @@ module ApplicationTests end def test_run_mailers - create_test_file :mailers, 'foo_mailer' - create_test_file :mailers, 'bar_mailer' - create_test_file :models, 'foo' + create_test_file :mailers, "foo_mailer" + create_test_file :mailers, "bar_mailer" + create_test_file :models, "foo" run_test_command("test/mailers").tap do |output| assert_match "FooMailerTest", output assert_match "BarMailerTest", output @@ -96,9 +96,9 @@ module ApplicationTests end def test_run_jobs - create_test_file :jobs, 'foo_job' - create_test_file :jobs, 'bar_job' - create_test_file :models, 'foo' + create_test_file :jobs, "foo_job" + create_test_file :jobs, "bar_job" + create_test_file :models, "foo" run_test_command("test/jobs").tap do |output| assert_match "FooJobTest", output assert_match "BarJobTest", output @@ -108,10 +108,10 @@ module ApplicationTests def test_run_functionals skip "we no longer have the concept of functional tests. Just different directories..." - create_test_file :mailers, 'foo_mailer' - create_test_file :controllers, 'bar_controller' - create_test_file :functional, 'baz_functional' - create_test_file :models, 'foo' + create_test_file :mailers, "foo_mailer" + create_test_file :controllers, "bar_controller" + create_test_file :functional, "baz_functional" + create_test_file :models, "foo" run_test_functionals_command.tap do |output| assert_match "FooMailerTest", output assert_match "BarControllerTest", output @@ -121,8 +121,8 @@ module ApplicationTests end def test_run_integration - create_test_file :integration, 'foo_integration' - create_test_file :models, 'foo' + create_test_file :integration, "foo_integration" + create_test_file :models, "foo" run_test_command("test/integration").tap do |output| assert_match "FooIntegration", output assert_match "1 runs, 1 assertions, 0 failures", output @@ -132,14 +132,14 @@ module ApplicationTests def test_run_all_suites suites = [:models, :helpers, :unit, :controllers, :mailers, :functional, :integration, :jobs] suites.each { |suite| create_test_file suite, "foo_#{suite}" } - run_test_command('') .tap do |output| + run_test_command("") .tap do |output| suites.each { |suite| assert_match "Foo#{suite.to_s.camelize}Test", output } assert_match "8 runs, 8 assertions, 0 failures", output end end def test_run_named_test - app_file 'test/unit/chu_2_koi_test.rb', <<-RUBY + app_file "test/unit/chu_2_koi_test.rb", <<-RUBY require 'test_helper' class Chu2KoiTest < ActiveSupport::TestCase @@ -153,14 +153,14 @@ module ApplicationTests end RUBY - run_test_command('-n test_rikka test/unit/chu_2_koi_test.rb').tap do |output| + run_test_command("-n test_rikka test/unit/chu_2_koi_test.rb").tap do |output| assert_match "Rikka", output assert_no_match "Sanae", output end end def test_run_matched_test - app_file 'test/unit/chu_2_koi_test.rb', <<-RUBY + app_file "test/unit/chu_2_koi_test.rb", <<-RUBY require 'test_helper' class Chu2KoiTest < ActiveSupport::TestCase @@ -174,7 +174,7 @@ module ApplicationTests end RUBY - run_test_command('-n /rikka/ test/unit/chu_2_koi_test.rb').tap do |output| + run_test_command("-n /rikka/ test/unit/chu_2_koi_test.rb").tap do |output| assert_match "Rikka", output assert_no_match "Sanae", output end @@ -195,14 +195,14 @@ module ApplicationTests def test_run_with_model skip "These feel a bit odd. Not sure we should keep supporting them." create_model_with_fixture - create_fixture_test 'models', 'user' + create_fixture_test "models", "user" assert_match "3 users", run_task(["test models/user"]) assert_match "3 users", run_task(["test app/models/user.rb"]) end def test_run_different_environment_using_env_var skip "no longer possible. Running tests in a different environment should be explicit" - app_file 'test/unit/env_test.rb', <<-RUBY + app_file "test/unit/env_test.rb", <<-RUBY require 'test_helper' class EnvTest < ActiveSupport::TestCase @@ -212,14 +212,14 @@ module ApplicationTests end RUBY - ENV['RAILS_ENV'] = 'development' - assert_match "development", run_test_command('test/unit/env_test.rb') + ENV["RAILS_ENV"] = "development" + assert_match "development", run_test_command("test/unit/env_test.rb") end def test_run_in_test_environment_by_default create_env_test - assert_match "Current Environment: test", run_test_command('test/unit/env_test.rb') + assert_match "Current Environment: test", run_test_command("test/unit/env_test.rb") end def test_run_different_environment @@ -231,27 +231,27 @@ module ApplicationTests def test_generated_scaffold_works_with_rails_test create_scaffold - assert_match "0 failures, 0 errors, 0 skips", run_test_command('') + assert_match "0 failures, 0 errors, 0 skips", run_test_command("") end def test_generated_controller_works_with_rails_test create_controller - assert_match "0 failures, 0 errors, 0 skips", run_test_command('') + assert_match "0 failures, 0 errors, 0 skips", run_test_command("") end def test_run_multiple_folders - create_test_file :models, 'account' - create_test_file :controllers, 'accounts_controller' + create_test_file :models, "account" + create_test_file :controllers, "accounts_controller" - run_test_command('test/models test/controllers').tap do |output| - assert_match 'AccountTest', output - assert_match 'AccountsControllerTest', output - assert_match '2 runs, 2 assertions, 0 failures, 0 errors, 0 skips', output + run_test_command("test/models test/controllers").tap do |output| + assert_match "AccountTest", output + assert_match "AccountsControllerTest", output + assert_match "2 runs, 2 assertions, 0 failures, 0 errors, 0 skips", output end end def test_run_with_ruby_command - app_file 'test/models/post_test.rb', <<-RUBY + app_file "test/models/post_test.rb", <<-RUBY require 'test_helper' class PostTest < ActiveSupport::TestCase @@ -264,15 +264,15 @@ module ApplicationTests Dir.chdir(app_path) do `ruby -Itest test/models/post_test.rb`.tap do |output| - assert_match 'PostTest', output - assert_no_match 'is already defined in', output + assert_match "PostTest", output + assert_no_match "is already defined in", output end end end def test_mix_files_and_line_filters - create_test_file :models, 'account' - app_file 'test/models/post_test.rb', <<-RUBY + create_test_file :models, "account" + app_file "test/models/post_test.rb", <<-RUBY require 'test_helper' class PostTest < ActiveSupport::TestCase @@ -287,15 +287,15 @@ module ApplicationTests end RUBY - run_test_command('test/models/account_test.rb test/models/post_test.rb:4').tap do |output| - assert_match 'AccountTest', output - assert_match 'PostTest', output - assert_match '2 runs, 2 assertions', output + run_test_command("test/models/account_test.rb test/models/post_test.rb:4").tap do |output| + assert_match "AccountTest", output + assert_match "PostTest", output + assert_match "2 runs, 2 assertions", output end end def test_more_than_one_line_filter - app_file 'test/models/post_test.rb', <<-RUBY + app_file "test/models/post_test.rb", <<-RUBY require 'test_helper' class PostTest < ActiveSupport::TestCase @@ -315,15 +315,15 @@ module ApplicationTests end RUBY - run_test_command('test/models/post_test.rb:4:9').tap do |output| - assert_match 'PostTest:FirstFilter', output - assert_match 'PostTest:SecondFilter', output - assert_match '2 runs, 2 assertions', output + run_test_command("test/models/post_test.rb:4:9").tap do |output| + assert_match "PostTest:FirstFilter", output + assert_match "PostTest:SecondFilter", output + assert_match "2 runs, 2 assertions", output end end def test_more_than_one_line_filter_with_multiple_files - app_file 'test/models/account_test.rb', <<-RUBY + app_file "test/models/account_test.rb", <<-RUBY require 'test_helper' class AccountTest < ActiveSupport::TestCase @@ -343,7 +343,7 @@ module ApplicationTests end RUBY - app_file 'test/models/post_test.rb', <<-RUBY + app_file "test/models/post_test.rb", <<-RUBY require 'test_helper' class PostTest < ActiveSupport::TestCase @@ -363,27 +363,27 @@ module ApplicationTests end RUBY - run_test_command('test/models/account_test.rb:4:9 test/models/post_test.rb:4:9').tap do |output| - assert_match 'AccountTest:FirstFilter', output - assert_match 'AccountTest:SecondFilter', output - assert_match 'PostTest:FirstFilter', output - assert_match 'PostTest:SecondFilter', output - assert_match '4 runs, 4 assertions', output + run_test_command("test/models/account_test.rb:4:9 test/models/post_test.rb:4:9").tap do |output| + assert_match "AccountTest:FirstFilter", output + assert_match "AccountTest:SecondFilter", output + assert_match "PostTest:FirstFilter", output + assert_match "PostTest:SecondFilter", output + assert_match "4 runs, 4 assertions", output end end def test_multiple_line_filters - create_test_file :models, 'account' - create_test_file :models, 'post' + create_test_file :models, "account" + create_test_file :models, "post" - run_test_command('test/models/account_test.rb:4 test/models/post_test.rb:4').tap do |output| - assert_match 'AccountTest', output - assert_match 'PostTest', output + run_test_command("test/models/account_test.rb:4 test/models/post_test.rb:4").tap do |output| + assert_match "AccountTest", output + assert_match "PostTest", output end end def test_line_filters_trigger_only_one_runnable - app_file 'test/models/post_test.rb', <<-RUBY + app_file "test/models/post_test.rb", <<-RUBY require 'test_helper' class PostTest < ActiveSupport::TestCase @@ -400,14 +400,14 @@ module ApplicationTests RUBY # Pass seed guaranteeing failure. - run_test_command('test/models/post_test.rb:4 --seed 30410').tap do |output| - assert_no_match 'ran second runnable', output - assert_match '1 runs, 1 assertions', output + run_test_command("test/models/post_test.rb:4 --seed 30410").tap do |output| + assert_no_match "ran second runnable", output + assert_match "1 runs, 1 assertions", output end end def test_line_filter_with_minitest_string_filter - app_file 'test/models/post_test.rb', <<-RUBY + app_file "test/models/post_test.rb", <<-RUBY require 'test_helper' class PostTest < ActiveSupport::TestCase @@ -423,94 +423,128 @@ module ApplicationTests end RUBY - run_test_command('test/models/post_test.rb:4 -n test_by_name').tap do |output| - assert_match 'by line', output - assert_match 'by name', output - assert_match '2 runs, 2 assertions', output + run_test_command("test/models/post_test.rb:4 -n test_by_name").tap do |output| + assert_match "by line", output + assert_match "by name", output + assert_match "2 runs, 2 assertions", output end end def test_shows_filtered_backtrace_by_default create_backtrace_test - assert_match 'Rails::BacktraceCleaner', run_test_command('test/unit/backtrace_test.rb') + assert_match "Rails::BacktraceCleaner", run_test_command("test/unit/backtrace_test.rb") end def test_backtrace_option create_backtrace_test - assert_match 'Minitest::BacktraceFilter', run_test_command('test/unit/backtrace_test.rb -b') - assert_match 'Minitest::BacktraceFilter', - run_test_command('test/unit/backtrace_test.rb --backtrace') + assert_match "Minitest::BacktraceFilter", run_test_command("test/unit/backtrace_test.rb -b") + assert_match "Minitest::BacktraceFilter", + run_test_command("test/unit/backtrace_test.rb --backtrace") end def test_show_full_backtrace_using_backtrace_environment_variable create_backtrace_test - switch_env 'BACKTRACE', 'true' do - assert_match 'Minitest::BacktraceFilter', run_test_command('test/unit/backtrace_test.rb') + switch_env "BACKTRACE", "true" do + assert_match "Minitest::BacktraceFilter", run_test_command("test/unit/backtrace_test.rb") end end def test_run_app_without_rails_loaded # Simulate a real Rails app boot. - app_file 'config/boot.rb', <<-RUBY + app_file "config/boot.rb", <<-RUBY ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) require 'bundler/setup' # Set up gems listed in the Gemfile. RUBY - assert_match '0 runs, 0 assertions', run_test_command('') + assert_match "0 runs, 0 assertions", run_test_command("") end def test_output_inline_by_default - create_test_file :models, 'post', pass: false + create_test_file :models, "post", pass: false - output = run_test_command('test/models/post_test.rb') - expect = %r{Running:\n\nPostTest\nF\n\nFailure:\nPostTest#test_truth:\nwups!\n\nbin/rails test test/models/post_test.rb:6\n\n\n\n} + output = run_test_command("test/models/post_test.rb") + expect = %r{Running:\n\nPostTest\nF\n\nFailure:\nPostTest#test_truth \[[^\]]+test/models/post_test.rb:6\]:\nwups!\n\nbin/rails test test/models/post_test.rb:4\n\n\n\n} assert_match expect, output end def test_only_inline_failure_output - create_test_file :models, 'post', pass: false + create_test_file :models, "post", pass: false - output = run_test_command('test/models/post_test.rb') + output = run_test_command("test/models/post_test.rb") assert_match %r{Finished in.*\n\n1 runs, 1 assertions}, output end def test_fail_fast - create_test_file :models, 'post', pass: false + create_test_file :models, "post", pass: false assert_match(/Interrupt/, - capture(:stderr) { run_test_command('test/models/post_test.rb --fail-fast') }) + capture(:stderr) { run_test_command("test/models/post_test.rb --fail-fast") }) end def test_raise_error_when_specified_file_does_not_exist - error = capture(:stderr) { run_test_command('test/not_exists.rb') } + error = capture(:stderr) { run_test_command("test/not_exists.rb") } assert_match(%r{cannot load such file.+test/not_exists\.rb}, error) end def test_pass_TEST_env_on_rake_test - create_test_file :models, 'account' - create_test_file :models, 'post', pass: false + create_test_file :models, "account" + create_test_file :models, "post", pass: false # This specifically verifies TEST for backwards compatibility with rake test # as bin/rails test already supports running tests from a single file more cleanly. output = Dir.chdir(app_path) { `bin/rake test TEST=test/models/post_test.rb` } assert_match "PostTest", output, "passing TEST= should run selected test" assert_no_match "AccountTest", output, "passing TEST= should only run selected test" - assert_match '1 runs, 1 assertions', output + assert_match "1 runs, 1 assertions", output + end + + def test_pass_rake_options + create_test_file :models, "account" + output = Dir.chdir(app_path) { `bin/rake --rakefile Rakefile --trace=stdout test` } + + assert_match "1 runs, 1 assertions", output + assert_match "Execute test", output + end + + def test_rails_db_create_all_restores_db_connection + create_test_file :models, "account" + output = Dir.chdir(app_path) { `bin/rails db:create:all db:migrate && echo ".tables" | rails dbconsole` } + assert_match "ar_internal_metadata", output, "tables should be dumped" + end + + def test_rails_db_create_all_restores_db_connection_after_drop + create_test_file :models, "account" + Dir.chdir(app_path) { `bin/rails db:create:all` } # create all to avoid warnings + output = Dir.chdir(app_path) { `bin/rails db:drop:all db:create:all db:migrate && echo ".tables" | rails dbconsole` } + assert_match "ar_internal_metadata", output, "tables should be dumped" + end + + def test_rake_passes_TESTOPTS_to_minitest + create_test_file :models, "account" + output = Dir.chdir(app_path) { `bin/rake test TESTOPTS=-v` } + assert_match "AccountTest#test_truth", output, "passing TEST= should run selected test" + end + + def test_rake_passes_multiple_TESTOPTS_to_minitest + create_test_file :models, "account" + output = Dir.chdir(app_path) { `bin/rake test TESTOPTS='-v --seed=1234'` } + assert_match "AccountTest#test_truth", output, "passing TEST= should run selected test" + assert_match "seed=1234", output, "passing TEST= should run selected test" end private - def run_test_command(arguments = 'test/unit/test_test.rb') + def run_test_command(arguments = "test/unit/test_test.rb") Dir.chdir(app_path) { `bin/rails t #{arguments}` } end def create_model_with_fixture - script 'generate model user name:string' + script "generate model user name:string" - app_file 'test/fixtures/users.yml', <<-YAML.strip_heredoc + app_file "test/fixtures/users.yml", <<-YAML.strip_heredoc vampire: id: 1 name: Koyomi Araragi @@ -525,7 +559,7 @@ module ApplicationTests run_migration end - def create_fixture_test(path = :unit, name = 'test') + def create_fixture_test(path = :unit, name = "test") app_file "test/#{path}/#{name}_test.rb", <<-RUBY require 'test_helper' @@ -538,7 +572,7 @@ module ApplicationTests end def create_backtrace_test - app_file 'test/unit/backtrace_test.rb', <<-RUBY + app_file "test/unit/backtrace_test.rb", <<-RUBY require 'test_helper' class BacktraceTest < ActiveSupport::TestCase @@ -550,10 +584,10 @@ module ApplicationTests end def create_schema - app_file 'db/schema.rb', '' + app_file "db/schema.rb", "" end - def create_test_file(path = :unit, name = 'test', pass: true) + def create_test_file(path = :unit, name = "test", pass: true) app_file "test/#{path}/#{name}_test.rb", <<-RUBY require 'test_helper' @@ -567,7 +601,7 @@ module ApplicationTests end def create_env_test - app_file 'test/unit/env_test.rb', <<-RUBY + app_file "test/unit/env_test.rb", <<-RUBY require 'test_helper' class EnvTest < ActiveSupport::TestCase @@ -579,13 +613,13 @@ module ApplicationTests end def create_scaffold - script 'generate scaffold user name:string' - Dir.chdir(app_path) { File.exist?('app/models/user.rb') } + script "generate scaffold user name:string" + Dir.chdir(app_path) { File.exist?("app/models/user.rb") } run_migration end def create_controller - script 'generate controller admin/dashboard index' + script "generate controller admin/dashboard index" end def run_migration diff --git a/railties/test/application/test_test.rb b/railties/test/application/test_test.rb index 85b003fce9..32d2a6857c 100644 --- a/railties/test/application/test_test.rb +++ b/railties/test/application/test_test.rb @@ -1,4 +1,4 @@ -require 'isolation/abstract_unit' +require "isolation/abstract_unit" module ApplicationTests class TestTest < ActiveSupport::TestCase @@ -6,15 +6,14 @@ module ApplicationTests def setup build_app - boot_rails end def teardown teardown_app end - test "truth" do - app_file 'test/unit/foo_test.rb', <<-RUBY + test "simple successful test" do + app_file "test/unit/foo_test.rb", <<-RUBY require 'test_helper' class FooTest < ActiveSupport::TestCase @@ -24,20 +23,52 @@ module ApplicationTests end RUBY - assert_successful_test_run 'unit/foo_test.rb' + assert_successful_test_run "unit/foo_test.rb" + end + + test "after_run" do + app_file "test/unit/foo_test.rb", <<-RUBY + require 'test_helper' + + Minitest.after_run { puts "WORLD" } + Minitest.after_run { puts "HELLO" } + + class FooTest < ActiveSupport::TestCase + def test_truth + assert true + end + end + RUBY + + result = assert_successful_test_run "unit/foo_test.rb" + assert_equal ["HELLO", "WORLD"], result.scan(/HELLO|WORLD/) # only once and in correct order + end + + test "simple failed test" do + app_file "test/unit/foo_test.rb", <<-RUBY + require 'test_helper' + + class FooTest < ActiveSupport::TestCase + def test_truth + assert false + end + end + RUBY + + assert_unsuccessful_run "unit/foo_test.rb", "Failed assertion" end test "integration test" do - controller 'posts', <<-RUBY + controller "posts", <<-RUBY class PostsController < ActionController::Base end RUBY - app_file 'app/views/posts/index.html.erb', <<-HTML + app_file "app/views/posts/index.html.erb", <<-HTML Posts#index HTML - app_file 'test/integration/posts_test.rb', <<-RUBY + app_file "test/integration/posts_test.rb", <<-RUBY require 'test_helper' class PostsTest < ActionDispatch::IntegrationTest @@ -49,11 +80,11 @@ module ApplicationTests end RUBY - assert_successful_test_run 'integration/posts_test.rb' + assert_successful_test_run "integration/posts_test.rb" end test "enable full backtraces on test failures" do - app_file 'test/unit/failing_test.rb', <<-RUBY + app_file "test/unit/failing_test.rb", <<-RUBY require 'test_helper' class FailingTest < ActiveSupport::TestCase @@ -63,16 +94,16 @@ module ApplicationTests end RUBY - output = run_test_file('unit/failing_test.rb', env: { "BACKTRACE" => "1" }) + output = run_test_file("unit/failing_test.rb", env: { "BACKTRACE" => "1" }) assert_match %r{test/unit/failing_test\.rb}, output assert_match %r{test/unit/failing_test\.rb:4}, output end test "ruby schema migrations" do - output = script('generate model user name:string') + output = script("generate model user name:string") version = output.match(/(\d+)_create_users\.rb/)[1] - app_file 'test/models/user_test.rb', <<-RUBY + app_file "test/models/user_test.rb", <<-RUBY require 'test_helper' class UserTest < ActiveSupport::TestCase @@ -81,11 +112,11 @@ module ApplicationTests end end RUBY - app_file 'db/schema.rb', '' + app_file "db/schema.rb", "" assert_unsuccessful_run "models/user_test.rb", "Migrations are pending" - app_file 'db/schema.rb', <<-RUBY + app_file "db/schema.rb", <<-RUBY ActiveRecord::Schema.define(version: #{version}) do create_table :users do |t| t.string :name @@ -93,7 +124,7 @@ module ApplicationTests end RUBY - app_file 'config/initializers/disable_maintain_test_schema.rb', <<-RUBY + app_file "config/initializers/disable_maintain_test_schema.rb", <<-RUBY Rails.application.config.active_record.maintain_test_schema = false RUBY @@ -101,15 +132,15 @@ module ApplicationTests File.delete "#{app_path}/config/initializers/disable_maintain_test_schema.rb" - result = assert_successful_test_run('models/user_test.rb') - assert !result.include?("create_table(:users)") + result = assert_successful_test_run("models/user_test.rb") + assert_not_includes result, "create_table(:users)" end test "sql structure migrations" do - output = script('generate model user name:string') + output = script("generate model user name:string") version = output.match(/(\d+)_create_users\.rb/)[1] - app_file 'test/models/user_test.rb', <<-RUBY + app_file "test/models/user_test.rb", <<-RUBY require 'test_helper' class UserTest < ActiveSupport::TestCase @@ -119,21 +150,21 @@ module ApplicationTests end RUBY - app_file 'db/structure.sql', '' - app_file 'config/initializers/enable_sql_schema_format.rb', <<-RUBY + app_file "db/structure.sql", "" + app_file "config/initializers/enable_sql_schema_format.rb", <<-RUBY Rails.application.config.active_record.schema_format = :sql RUBY assert_unsuccessful_run "models/user_test.rb", "Migrations are pending" - app_file 'db/structure.sql', <<-SQL + app_file "db/structure.sql", <<-SQL CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL); CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version"); CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255)); INSERT INTO schema_migrations (version) VALUES ('#{version}'); SQL - app_file 'config/initializers/disable_maintain_test_schema.rb', <<-RUBY + app_file "config/initializers/disable_maintain_test_schema.rb", <<-RUBY Rails.application.config.active_record.maintain_test_schema = false RUBY @@ -141,14 +172,14 @@ module ApplicationTests File.delete "#{app_path}/config/initializers/disable_maintain_test_schema.rb" - assert_successful_test_run('models/user_test.rb') + assert_successful_test_run("models/user_test.rb") end test "sql structure migrations when adding column to existing table" do - output_1 = script('generate model user name:string') + output_1 = script("generate model user name:string") version_1 = output_1.match(/(\d+)_create_users\.rb/)[1] - app_file 'test/models/user_test.rb', <<-RUBY + app_file "test/models/user_test.rb", <<-RUBY require 'test_helper' class UserTest < ActiveSupport::TestCase test "user" do @@ -157,23 +188,23 @@ module ApplicationTests end RUBY - app_file 'config/initializers/enable_sql_schema_format.rb', <<-RUBY + app_file "config/initializers/enable_sql_schema_format.rb", <<-RUBY Rails.application.config.active_record.schema_format = :sql RUBY - app_file 'db/structure.sql', <<-SQL + app_file "db/structure.sql", <<-SQL CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL); CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version"); CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255)); INSERT INTO schema_migrations (version) VALUES ('#{version_1}'); SQL - assert_successful_test_run('models/user_test.rb') + assert_successful_test_run("models/user_test.rb") - output_2 = script('generate migration add_email_to_users') + output_2 = script("generate migration add_email_to_users") version_2 = output_2.match(/(\d+)_add_email_to_users\.rb/)[1] - app_file 'test/models/user_test.rb', <<-RUBY + app_file "test/models/user_test.rb", <<-RUBY require 'test_helper' class UserTest < ActiveSupport::TestCase @@ -183,7 +214,7 @@ module ApplicationTests end RUBY - app_file 'db/structure.sql', <<-SQL + app_file "db/structure.sql", <<-SQL CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL); CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version"); CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255), "email" varchar(255)); @@ -191,17 +222,17 @@ module ApplicationTests INSERT INTO schema_migrations (version) VALUES ('#{version_2}'); SQL - assert_successful_test_run('models/user_test.rb') + assert_successful_test_run("models/user_test.rb") end # TODO: would be nice if we could detect the schema change automatically. # For now, the user has to synchronize the schema manually. # This test-case serves as a reminder for this use-case. test "manually synchronize test schema after rollback" do - output = script('generate model user name:string') + output = script("generate model user name:string") version = output.match(/(\d+)_create_users\.rb/)[1] - app_file 'test/models/user_test.rb', <<-RUBY + app_file "test/models/user_test.rb", <<-RUBY require 'test_helper' class UserTest < ActiveSupport::TestCase @@ -210,7 +241,7 @@ module ApplicationTests end end RUBY - app_file 'db/schema.rb', <<-RUBY + app_file "db/schema.rb", <<-RUBY ActiveRecord::Schema.define(version: #{version}) do create_table :users do |t| t.string :name @@ -221,7 +252,7 @@ module ApplicationTests assert_successful_test_run "models/user_test.rb" # Simulate `db:rollback` + edit of the migration file + `db:migrate` - app_file 'db/schema.rb', <<-RUBY + app_file "db/schema.rb", <<-RUBY ActiveRecord::Schema.define(version: #{version}) do create_table :users do |t| t.string :name @@ -241,10 +272,10 @@ Expected: ["id", "name"] end test "hooks for plugins" do - output = script('generate model user name:string') + output = script("generate model user name:string") version = output.match(/(\d+)_create_users\.rb/)[1] - app_file 'lib/tasks/hooks.rake', <<-RUBY + app_file "lib/tasks/hooks.rake", <<-RUBY task :before_hook do has_user_table = ActiveRecord::Base.connection.table_exists?('users') puts "before: " + has_user_table.to_s @@ -259,7 +290,7 @@ Expected: ["id", "name"] Rake::Task[:after_hook].invoke end RUBY - app_file 'test/models/user_test.rb', <<-RUBY + app_file "test/models/user_test.rb", <<-RUBY require 'test_helper' class UserTest < ActiveSupport::TestCase test "user" do @@ -269,7 +300,7 @@ Expected: ["id", "name"] RUBY # Simulate `db:migrate` - app_file 'db/schema.rb', <<-RUBY + app_file "db/schema.rb", <<-RUBY ActiveRecord::Schema.define(version: #{version}) do create_table :users do |t| t.string :name @@ -290,7 +321,7 @@ Expected: ["id", "name"] def assert_unsuccessful_run(name, message) result = run_test_file(name) assert_not_equal 0, $?.to_i - assert result.include?(message) + assert_includes result, message result end diff --git a/railties/test/application/url_generation_test.rb b/railties/test/application/url_generation_test.rb index 894e18cb39..37f129475c 100644 --- a/railties/test/application/url_generation_test.rb +++ b/railties/test/application/url_generation_test.rb @@ -1,4 +1,4 @@ -require 'isolation/abstract_unit' +require "isolation/abstract_unit" module ApplicationTests class UrlGenerationTest < ActiveSupport::TestCase @@ -9,7 +9,6 @@ module ApplicationTests end test "it works" do - boot_rails require "rails" require "action_controller/railtie" require "action_view/railtie" @@ -28,7 +27,7 @@ module ApplicationTests class ::OmgController < ::ApplicationController def index - render text: omg_path + render plain: omg_path end end @@ -36,7 +35,7 @@ module ApplicationTests get "/" => "omg#index", as: :omg end - require 'rack/test' + require "rack/test" extend Rack::Test::Methods get "/" @@ -44,12 +43,11 @@ module ApplicationTests end def test_routes_know_the_relative_root - boot_rails require "rails" require "action_controller/railtie" require "action_view/railtie" - relative_url = '/hello' + relative_url = "/hello" ENV["RAILS_RELATIVE_URL_ROOT"] = relative_url app = Class.new(Rails::Application) assert_equal relative_url, app.routes.relative_url_root diff --git a/railties/test/backtrace_cleaner_test.rb b/railties/test/backtrace_cleaner_test.rb index 2dd74f8fd1..17201d6f77 100644 --- a/railties/test/backtrace_cleaner_test.rb +++ b/railties/test/backtrace_cleaner_test.rb @@ -1,24 +1,32 @@ -require 'abstract_unit' -require 'rails/backtrace_cleaner' +require "abstract_unit" +require "rails/backtrace_cleaner" -class BacktraceCleanerVendorGemTest < ActiveSupport::TestCase +class BacktraceCleanerTest < ActiveSupport::TestCase def setup @cleaner = Rails::BacktraceCleaner.new end test "should format installed gems correctly" do - @backtrace = [ "#{Gem.path[0]}/gems/nosuchgem-1.2.3/lib/foo.rb" ] - @result = @cleaner.clean(@backtrace, :all) - assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0] + backtrace = [ "#{Gem.path[0]}/gems/nosuchgem-1.2.3/lib/foo.rb" ] + result = @cleaner.clean(backtrace, :all) + assert_equal "nosuchgem (1.2.3) lib/foo.rb", result[0] end test "should format installed gems not in Gem.default_dir correctly" do - @target_dir = Gem.path.detect { |p| p != Gem.default_dir } + target_dir = Gem.path.detect { |p| p != Gem.default_dir } # skip this test if default_dir is the only directory on Gem.path if @target_dir - @backtrace = [ "#{@target_dir}/gems/nosuchgem-1.2.3/lib/foo.rb" ] - @result = @cleaner.clean(@backtrace, :all) - assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0] + backtrace = [ "#{target_dir}/gems/nosuchgem-1.2.3/lib/foo.rb" ] + result = @cleaner.clean(backtrace, :all) + assert_equal "nosuchgem (1.2.3) lib/foo.rb", result[0] end end + + test "should consider traces from irb lines as User code" do + backtrace = [ "from (irb):1", + "from /Path/to/rails/railties/lib/rails/commands/console.rb:77:in `start'", + "from bin/rails:4:in `<main>'" ] + result = @cleaner.clean(backtrace, :all) + assert_equal "from (irb):1", result[0] + end end diff --git a/railties/test/code_statistics_calculator_test.rb b/railties/test/code_statistics_calculator_test.rb index cecc3908b3..2dba3c6e96 100644 --- a/railties/test/code_statistics_calculator_test.rb +++ b/railties/test/code_statistics_calculator_test.rb @@ -1,12 +1,12 @@ -require 'abstract_unit' -require 'rails/code_statistics_calculator' +require "abstract_unit" +require "rails/code_statistics_calculator" class CodeStatisticsCalculatorTest < ActiveSupport::TestCase def setup @code_statistics_calculator = CodeStatisticsCalculator.new end - test 'calculate statistics using #add_by_file_path' do + test "calculate statistics using #add_by_file_path" do code = <<-RUBY def foo puts 'foo' @@ -14,7 +14,7 @@ class CodeStatisticsCalculatorTest < ActiveSupport::TestCase end RUBY - temp_file 'stats.rb', code do |path| + temp_file "stats.rb", code do |path| @code_statistics_calculator.add_by_file_path path assert_equal 4, @code_statistics_calculator.lines @@ -24,7 +24,7 @@ class CodeStatisticsCalculatorTest < ActiveSupport::TestCase end end - test 'count number of methods in MiniTest file' do + test "count number of methods in MiniTest file" do code = <<-RUBY class FooTest < ActionController::TestCase test 'expectation' do @@ -37,13 +37,13 @@ class CodeStatisticsCalculatorTest < ActiveSupport::TestCase end RUBY - temp_file 'foo_test.rb', code do |path| + temp_file "foo_test.rb", code do |path| @code_statistics_calculator.add_by_file_path path assert_equal 2, @code_statistics_calculator.methods end end - test 'add statistics to another using #add' do + test "add statistics to another using #add" do code_statistics_calculator_1 = CodeStatisticsCalculator.new(1, 2, 3, 4) @code_statistics_calculator.add(code_statistics_calculator_1) @@ -61,7 +61,7 @@ class CodeStatisticsCalculatorTest < ActiveSupport::TestCase assert_equal 9, @code_statistics_calculator.methods end - test 'accumulate statistics using #add_by_io' do + test "accumulate statistics using #add_by_io" do code_statistics_calculator_1 = CodeStatisticsCalculator.new(1, 2, 3, 4) @code_statistics_calculator.add(code_statistics_calculator_1) @@ -82,7 +82,7 @@ class CodeStatisticsCalculatorTest < ActiveSupport::TestCase assert_equal 6, @code_statistics_calculator.methods end - test 'calculate number of Ruby methods' do + test "calculate number of Ruby methods" do code = <<-'CODE' def foo puts 'foo' @@ -101,7 +101,7 @@ class CodeStatisticsCalculatorTest < ActiveSupport::TestCase assert_equal 3, @code_statistics_calculator.methods end - test 'calculate Ruby LOCs' do + test "calculate Ruby LOCs" do code = <<-'CODE' def foo puts 'foo' @@ -119,7 +119,7 @@ class CodeStatisticsCalculatorTest < ActiveSupport::TestCase assert_equal 5, @code_statistics_calculator.code_lines end - test 'calculate number of Ruby classes' do + test "calculate number of Ruby classes" do code = <<-'CODE' class Foo < Bar def foo @@ -138,7 +138,7 @@ class CodeStatisticsCalculatorTest < ActiveSupport::TestCase assert_equal 2, @code_statistics_calculator.classes end - test 'skip Ruby comments' do + test "skip Ruby comments" do code = <<-'CODE' =begin class Foo @@ -160,7 +160,7 @@ class CodeStatisticsCalculatorTest < ActiveSupport::TestCase assert_equal 0, @code_statistics_calculator.methods end - test 'calculate number of JS methods' do + test "calculate number of JS methods" do code = <<-'CODE' function foo(x, y, z) { doX(); @@ -179,7 +179,7 @@ class CodeStatisticsCalculatorTest < ActiveSupport::TestCase assert_equal 3, @code_statistics_calculator.methods end - test 'calculate JS LOCs' do + test "calculate JS LOCs" do code = <<-'CODE' function foo() alert('foo'); @@ -196,7 +196,7 @@ class CodeStatisticsCalculatorTest < ActiveSupport::TestCase assert_equal 4, @code_statistics_calculator.code_lines end - test 'skip JS comments' do + test "skip JS comments" do code = <<-'CODE' /* * var f = function () { @@ -216,7 +216,7 @@ class CodeStatisticsCalculatorTest < ActiveSupport::TestCase assert_equal 0, @code_statistics_calculator.methods end - test 'calculate number of CoffeeScript methods' do + test "calculate number of CoffeeScript methods" do code = <<-'CODE' square = (x) -> x * x @@ -235,7 +235,7 @@ class CodeStatisticsCalculatorTest < ActiveSupport::TestCase assert_equal 4, @code_statistics_calculator.methods end - test 'calculate CoffeeScript LOCs' do + test "calculate CoffeeScript LOCs" do code = <<-'CODE' # Assignment: number = 42 @@ -256,7 +256,7 @@ class CodeStatisticsCalculatorTest < ActiveSupport::TestCase assert_equal 3, @code_statistics_calculator.code_lines end - test 'calculate number of CoffeeScript classes' do + test "calculate number of CoffeeScript classes" do code = <<-'CODE' class Animal constructor: (@name) -> @@ -277,7 +277,7 @@ class CodeStatisticsCalculatorTest < ActiveSupport::TestCase assert_equal 2, @code_statistics_calculator.classes end - test 'skip CoffeeScript comments' do + test "skip CoffeeScript comments" do code = <<-'CODE' ### class Animal @@ -299,7 +299,7 @@ class Animal assert_equal 0, @code_statistics_calculator.methods end - test 'count rake tasks' do + test "count rake tasks" do code = <<-'CODE' task :test_task do puts 'foo' @@ -317,7 +317,7 @@ class Animal private def temp_file(name, content) - dir = File.expand_path '../fixtures/tmp', __FILE__ + dir = File.expand_path "../fixtures/tmp", __FILE__ path = "#{dir}/#{name}" FileUtils.mkdir_p dir diff --git a/railties/test/code_statistics_test.rb b/railties/test/code_statistics_test.rb index 4d80901217..965b6eeb79 100644 --- a/railties/test/code_statistics_test.rb +++ b/railties/test/code_statistics_test.rb @@ -1,10 +1,10 @@ -require 'abstract_unit' -require 'rails/code_statistics' +require "abstract_unit" +require "rails/code_statistics" class CodeStatisticsTest < ActiveSupport::TestCase def setup - @tmp_path = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'tmp')) - @dir_js = File.join(@tmp_path, 'lib.js') + @tmp_path = File.expand_path(File.join(File.dirname(__FILE__), "fixtures", "tmp")) + @dir_js = File.join(@tmp_path, "lib.js") FileUtils.mkdir_p(@dir_js) end @@ -12,22 +12,21 @@ class CodeStatisticsTest < ActiveSupport::TestCase FileUtils.rm_rf(@tmp_path) end - test 'ignores directories that happen to have source files extensions' do + test "ignores directories that happen to have source files extensions" do assert_nothing_raised do - @code_statistics = CodeStatistics.new(['tmp dir', @tmp_path]) + @code_statistics = CodeStatistics.new(["tmp dir", @tmp_path]) end end - test 'ignores hidden files' do - File.write File.join(@tmp_path, '.example.rb'), <<-CODE + test "ignores hidden files" do + File.write File.join(@tmp_path, ".example.rb"), <<-CODE def foo puts 'foo' end CODE assert_nothing_raised do - CodeStatistics.new(['hidden file', @tmp_path]) + CodeStatistics.new(["hidden file", @tmp_path]) end end - end diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb index de0cf0ba9e..4fc082e4ca 100644 --- a/railties/test/commands/console_test.rb +++ b/railties/test/commands/console_test.rb @@ -1,6 +1,7 @@ -require 'abstract_unit' -require 'env_helpers' -require 'rails/commands/console' +require "abstract_unit" +require "env_helpers" +require "rails/command" +require "rails/commands/console/console_command" class Rails::ConsoleTest < ActiveSupport::TestCase include EnvHelpers @@ -40,7 +41,6 @@ class Rails::ConsoleTest < ActiveSupport::TestCase def test_start_with_sandbox start ["--sandbox"] - assert app.console.started? assert app.sandbox assert_match(/Loading \w+ environment in sandbox \(Rails/, output) @@ -64,52 +64,60 @@ class Rails::ConsoleTest < ActiveSupport::TestCase end def test_default_environment_with_rails_env - with_rails_env 'special-production' do + with_rails_env "special-production" do start assert_match(/\sspecial-production\s/, output) end end def test_default_environment_with_rack_env - with_rack_env 'production' do + with_rack_env "production" do start assert_match(/\sproduction\s/, output) end end def test_e_option - start ['-e', 'special-production'] + start ["-e", "special-production"] assert_match(/\sspecial-production\s/, output) end def test_environment_option - start ['--environment=special-production'] + start ["--environment=special-production"] assert_match(/\sspecial-production\s/, output) end def test_rails_env_is_production_when_first_argument_is_p - start ['p'] + start ["p"] assert_match(/\sproduction\s/, output) end def test_rails_env_is_test_when_first_argument_is_t - start ['t'] + start ["t"] assert_match(/\stest\s/, output) end def test_rails_env_is_development_when_argument_is_d - start ['d'] + start ["d"] assert_match(/\sdevelopment\s/, output) end def test_rails_env_is_dev_when_argument_is_dev_and_dev_env_is_present - stubbed_console = Class.new(Rails::Console) do - def available_environments - ['dev'] + Rails::Command::ConsoleCommand.class_eval do + alias_method :old_environments, :available_environments + + define_method :available_environments do + ["dev"] end end - options = stubbed_console.parse_arguments(['dev']) - assert_match('dev', options[:environment]) + + assert_match("dev", parse_arguments(["dev"])[:environment]) + ensure + Rails::Command::ConsoleCommand.class_eval do + undef_method :available_environments + alias_method :available_environments, :old_environments + undef_method :old_environments + end end attr_reader :output @@ -117,38 +125,53 @@ class Rails::ConsoleTest < ActiveSupport::TestCase private - def start(argv = []) - rails_console = Rails::Console.new(app, parse_arguments(argv)) - @output = capture(:stdout) { rails_console.start } - end + def start(argv = []) + rails_console = Rails::Console.new(app, parse_arguments(argv)) + @output = capture(:stdout) { rails_console.start } + end - def app - @app ||= build_app(FakeConsole) - end + def app + @app ||= build_app(FakeConsole) + end - def build_app(console) - mocked_console = Class.new do - attr_reader :sandbox, :console + def build_app(console) + mocked_console = Class.new do + attr_reader :sandbox, :console - def initialize(console) - @console = console - end + def initialize(console) + @console = console + end + + def config + self + end - def config - self + def sandbox=(arg) + @sandbox = arg + end + + def load_console + end end + mocked_console.new(console) + end - def sandbox=(arg) - @sandbox = arg + def parse_arguments(args) + Rails::Command::ConsoleCommand.class_eval do + alias_method :old_perform, :perform + define_method(:perform) do + extract_environment_option_from_argument + + options + end end - def load_console + Rails::Command.invoke(:console, args) + ensure + Rails::Command::ConsoleCommand.class_eval do + undef_method :perform + alias_method :perform, :old_perform + undef_method :old_perform end end - mocked_console.new(console) - end - - def parse_arguments(args) - Rails::Console.parse_arguments(args) - end end diff --git a/railties/test/commands/dbconsole_test.rb b/railties/test/commands/dbconsole_test.rb index a5aa6c14a2..2ddb269eae 100644 --- a/railties/test/commands/dbconsole_test.rb +++ b/railties/test/commands/dbconsole_test.rb @@ -1,17 +1,16 @@ -require 'abstract_unit' -require 'minitest/mock' -require 'rails/commands/dbconsole' +require "abstract_unit" +require "minitest/mock" +require "rails/command" +require "rails/commands/dbconsole/dbconsole_command" class Rails::DBConsoleTest < ActiveSupport::TestCase - - def setup - Rails::DBConsole.const_set('APP_PATH', 'rails/all') + Rails::DBConsole.const_set("APP_PATH", "rails/all") end def teardown - Rails::DBConsole.send(:remove_const, 'APP_PATH') - %w[PGUSER PGHOST PGPORT PGPASSWORD DATABASE_URL].each{|key| ENV.delete(key)} + Rails::DBConsole.send(:remove_const, "APP_PATH") + %w[PGUSER PGHOST PGPORT PGPASSWORD DATABASE_URL].each { |key| ENV.delete(key) } end def test_config_with_db_config_only @@ -41,7 +40,7 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase end def test_config_with_database_url_only - ENV['DATABASE_URL'] = 'postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000' + ENV["DATABASE_URL"] = "postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000" expected = { "adapter" => "postgresql", "host" => "localhost", @@ -60,7 +59,7 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase def test_config_choose_database_url_if_exists host = "database-url-host.com" - ENV['DATABASE_URL'] = "postgresql://foo:bar@#{host}:9000/foo_test?pool=5&timeout=3000" + ENV["DATABASE_URL"] = "postgresql://foo:bar@#{host}:9000/foo_test?pool=5&timeout=3000" sample_config = { "test" => { "adapter" => "postgresql", @@ -81,144 +80,138 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase def test_env assert_equal "test", Rails::DBConsole.new.environment - ENV['RAILS_ENV'] = nil - ENV['RACK_ENV'] = nil + ENV["RAILS_ENV"] = nil + ENV["RACK_ENV"] = nil Rails.stub(:respond_to?, false) do assert_equal "development", Rails::DBConsole.new.environment - ENV['RACK_ENV'] = "rack_env" + ENV["RACK_ENV"] = "rack_env" assert_equal "rack_env", Rails::DBConsole.new.environment - ENV['RAILS_ENV'] = "rails_env" + ENV["RAILS_ENV"] = "rails_env" assert_equal "rails_env", Rails::DBConsole.new.environment end ensure - ENV['RAILS_ENV'] = "test" - ENV['RACK_ENV'] = nil + ENV["RAILS_ENV"] = "test" + ENV["RACK_ENV"] = nil end def test_rails_env_is_development_when_argument_is_dev - Rails::DBConsole.stub(:available_environments, ['development', 'test']) do - options = Rails::DBConsole.send(:parse_arguments, ['dev']) - assert_match('development', options[:environment]) + stub_available_environments([ "development", "test" ]) do + assert_match("development", parse_arguments([ "dev" ])[:environment]) end end def test_rails_env_is_dev_when_argument_is_dev_and_dev_env_is_present - Rails::DBConsole.stub(:available_environments, ['dev']) do - options = Rails::DBConsole.send(:parse_arguments, ['dev']) - assert_match('dev', options[:environment]) + stub_available_environments([ "dev" ]) do + assert_match("dev", parse_arguments([ "dev" ])[:environment]) end end def test_mysql - start(adapter: 'mysql2', database: 'db') + start(adapter: "mysql2", database: "db") assert !aborted - assert_equal [%w[mysql mysql5], 'db'], dbconsole.find_cmd_and_exec_args + assert_equal [%w[mysql mysql5], "db"], dbconsole.find_cmd_and_exec_args end def test_mysql_full - start(adapter: 'mysql2', database: 'db', host: 'locahost', port: 1234, socket: 'socket', username: 'user', password: 'qwerty', encoding: 'UTF-8') + start(adapter: "mysql2", database: "db", host: "locahost", port: 1234, socket: "socket", username: "user", password: "qwerty", encoding: "UTF-8") assert !aborted - assert_equal [%w[mysql mysql5], '--host=locahost', '--port=1234', '--socket=socket', '--user=user', '--default-character-set=UTF-8', '-p', 'db'], dbconsole.find_cmd_and_exec_args + assert_equal [%w[mysql mysql5], "--host=locahost", "--port=1234", "--socket=socket", "--user=user", "--default-character-set=UTF-8", "-p", "db"], dbconsole.find_cmd_and_exec_args end def test_mysql_include_password - start({adapter: 'mysql2', database: 'db', username: 'user', password: 'qwerty'}, ['-p']) + start({ adapter: "mysql2", database: "db", username: "user", password: "qwerty" }, ["-p"]) assert !aborted - assert_equal [%w[mysql mysql5], '--user=user', '--password=qwerty', 'db'], dbconsole.find_cmd_and_exec_args + assert_equal [%w[mysql mysql5], "--user=user", "--password=qwerty", "db"], dbconsole.find_cmd_and_exec_args end def test_postgresql - start(adapter: 'postgresql', database: 'db') + start(adapter: "postgresql", database: "db") assert !aborted - assert_equal ['psql', 'db'], dbconsole.find_cmd_and_exec_args + assert_equal ["psql", "db"], dbconsole.find_cmd_and_exec_args end def test_postgresql_full - start(adapter: 'postgresql', database: 'db', username: 'user', password: 'q1w2e3', host: 'host', port: 5432) + start(adapter: "postgresql", database: "db", username: "user", password: "q1w2e3", host: "host", port: 5432) assert !aborted - assert_equal ['psql', 'db'], dbconsole.find_cmd_and_exec_args - assert_equal 'user', ENV['PGUSER'] - assert_equal 'host', ENV['PGHOST'] - assert_equal '5432', ENV['PGPORT'] - assert_not_equal 'q1w2e3', ENV['PGPASSWORD'] + assert_equal ["psql", "db"], dbconsole.find_cmd_and_exec_args + assert_equal "user", ENV["PGUSER"] + assert_equal "host", ENV["PGHOST"] + assert_equal "5432", ENV["PGPORT"] + assert_not_equal "q1w2e3", ENV["PGPASSWORD"] end def test_postgresql_include_password - start({adapter: 'postgresql', database: 'db', username: 'user', password: 'q1w2e3'}, ['-p']) + start({ adapter: "postgresql", database: "db", username: "user", password: "q1w2e3" }, ["-p"]) assert !aborted - assert_equal ['psql', 'db'], dbconsole.find_cmd_and_exec_args - assert_equal 'user', ENV['PGUSER'] - assert_equal 'q1w2e3', ENV['PGPASSWORD'] + assert_equal ["psql", "db"], dbconsole.find_cmd_and_exec_args + assert_equal "user", ENV["PGUSER"] + assert_equal "q1w2e3", ENV["PGPASSWORD"] end def test_sqlite3 - start(adapter: 'sqlite3', database: 'db.sqlite3') + start(adapter: "sqlite3", database: "db.sqlite3") assert !aborted - assert_equal ['sqlite3', Rails.root.join('db.sqlite3').to_s], dbconsole.find_cmd_and_exec_args + assert_equal ["sqlite3", Rails.root.join("db.sqlite3").to_s], dbconsole.find_cmd_and_exec_args end def test_sqlite3_mode - start({adapter: 'sqlite3', database: 'db.sqlite3'}, ['--mode', 'html']) + start({ adapter: "sqlite3", database: "db.sqlite3" }, ["--mode", "html"]) assert !aborted - assert_equal ['sqlite3', '-html', Rails.root.join('db.sqlite3').to_s], dbconsole.find_cmd_and_exec_args + assert_equal ["sqlite3", "-html", Rails.root.join("db.sqlite3").to_s], dbconsole.find_cmd_and_exec_args end def test_sqlite3_header - start({adapter: 'sqlite3', database: 'db.sqlite3'}, ['--header']) - assert_equal ['sqlite3', '-header', Rails.root.join('db.sqlite3').to_s], dbconsole.find_cmd_and_exec_args + start({ adapter: "sqlite3", database: "db.sqlite3" }, ["--header"]) + assert_equal ["sqlite3", "-header", Rails.root.join("db.sqlite3").to_s], dbconsole.find_cmd_and_exec_args end def test_sqlite3_db_absolute_path - start(adapter: 'sqlite3', database: '/tmp/db.sqlite3') + start(adapter: "sqlite3", database: "/tmp/db.sqlite3") assert !aborted - assert_equal ['sqlite3', '/tmp/db.sqlite3'], dbconsole.find_cmd_and_exec_args + assert_equal ["sqlite3", "/tmp/db.sqlite3"], dbconsole.find_cmd_and_exec_args end def test_sqlite3_db_without_defined_rails_root Rails.stub(:respond_to?, false) do - start(adapter: 'sqlite3', database: 'config/db.sqlite3') + start(adapter: "sqlite3", database: "config/db.sqlite3") assert !aborted - assert_equal ['sqlite3', Rails.root.join('../config/db.sqlite3').to_s], dbconsole.find_cmd_and_exec_args + assert_equal ["sqlite3", Rails.root.join("../config/db.sqlite3").to_s], dbconsole.find_cmd_and_exec_args end end def test_oracle - start(adapter: 'oracle', database: 'db', username: 'user', password: 'secret') + start(adapter: "oracle", database: "db", username: "user", password: "secret") assert !aborted - assert_equal ['sqlplus', 'user@db'], dbconsole.find_cmd_and_exec_args + assert_equal ["sqlplus", "user@db"], dbconsole.find_cmd_and_exec_args end def test_oracle_include_password - start({adapter: 'oracle', database: 'db', username: 'user', password: 'secret'}, ['-p']) + start({ adapter: "oracle", database: "db", username: "user", password: "secret" }, ["-p"]) assert !aborted - assert_equal ['sqlplus', 'user/secret@db'], dbconsole.find_cmd_and_exec_args + assert_equal ["sqlplus", "user/secret@db"], dbconsole.find_cmd_and_exec_args end def test_unknown_command_line_client - start(adapter: 'unknown', database: 'db') + start(adapter: "unknown", database: "db") assert aborted assert_match(/Unknown command-line client for db/, output) end def test_print_help_short stdout = capture(:stdout) do - start({}, ['-h']) + Rails::Command.invoke(:dbconsole, ["-h"]) end - assert aborted - assert_equal '', output - assert_match(/Usage:.*dbconsole/, stdout) + assert_match(/bin\/rails dbconsole \[environment\]/, stdout) end def test_print_help_long stdout = capture(:stdout) do - start({}, ['--help']) + Rails::Command.invoke(:dbconsole, ["--help"]) end - assert aborted - assert_equal '', output - assert_match(/Usage:.*dbconsole/, stdout) + assert_match(/bin\/rails dbconsole \[environment\]/, stdout) end attr_reader :aborted, :output @@ -226,39 +219,76 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase private - def app_db_config(results) - Rails.application.config.stub(:database_configuration, results || {}) do - yield + def app_db_config(results) + Rails.application.config.stub(:database_configuration, results || {}) do + yield + end end - end - def dbconsole - @dbconsole ||= Class.new(Rails::DBConsole) do - attr_reader :find_cmd_and_exec_args + def make_dbconsole + Class.new(Rails::DBConsole) do + attr_reader :find_cmd_and_exec_args - def find_cmd_and_exec(*args) - @find_cmd_and_exec_args = args + def find_cmd_and_exec(*args) + @find_cmd_and_exec_args = args + end end - end.new(nil) - end + end - def start(config = {}, argv = []) - dbconsole.stub(:config, config.stringify_keys) do - dbconsole.stub(:arguments, argv) do - capture_abort { dbconsole.start } + attr_reader :dbconsole + + def start(config = {}, argv = []) + @dbconsole = make_dbconsole.new(parse_arguments(argv)) + @dbconsole.stub(:config, config.stringify_keys) do + capture_abort { @dbconsole.start } end end - end - def capture_abort - @aborted = false - @output = capture(:stderr) do - begin - yield - rescue SystemExit - @aborted = true + def capture_abort + @aborted = false + @output = capture(:stderr) do + begin + yield + rescue SystemExit + @aborted = true + end end end - end + def stub_available_environments(environments) + Rails::Command::DbconsoleCommand.class_eval do + alias_method :old_environments, :available_environments + + define_method :available_environments do + environments + end + end + + yield + ensure + Rails::Command::DbconsoleCommand.class_eval do + undef_method :available_environments + alias_method :available_environments, :old_environments + undef_method :old_environments + end + end + + def parse_arguments(args) + Rails::Command::DbconsoleCommand.class_eval do + alias_method :old_perform, :perform + define_method(:perform) do + extract_environment_option_from_argument + + options + end + end + + Rails::Command.invoke(:dbconsole, args) + ensure + Rails::Command::DbconsoleCommand.class_eval do + undef_method :perform + alias_method :perform, :old_perform + undef_method :old_perform + end + end end diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb index 0c49bd9c53..391886bf33 100644 --- a/railties/test/commands/server_test.rb +++ b/railties/test/commands/server_test.rb @@ -1,6 +1,7 @@ -require 'abstract_unit' -require 'env_helpers' -require 'rails/commands/server' +require "abstract_unit" +require "env_helpers" +require "rails/command" +require "rails/commands/server/server_command" class Rails::ServerTest < ActiveSupport::TestCase include EnvHelpers @@ -8,14 +9,14 @@ class Rails::ServerTest < ActiveSupport::TestCase def test_environment_with_server_option args = ["thin", "-e", "production"] options = Rails::Server::Options.new.parse!(args) - assert_equal 'production', options[:environment] - assert_equal 'thin', options[:server] + 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) - assert_equal 'production', options[:environment] + assert_equal "production", options[:environment] assert_nil options[:server] end @@ -23,23 +24,23 @@ class Rails::ServerTest < ActiveSupport::TestCase args = ["thin"] options = Rails::Server::Options.new.parse!(args) assert_nil options[:environment] - assert_equal 'thin', options[:server] + assert_equal "thin", options[:server] end def test_environment_with_rails_env with_rack_env nil do - with_rails_env 'production' do + with_rails_env "production" do server = Rails::Server.new - assert_equal 'production', server.options[:environment] + assert_equal "production", server.options[:environment] end end end def test_environment_with_rack_env with_rails_env nil do - with_rack_env 'production' do + with_rack_env "production" do server = Rails::Server.new - assert_equal 'production', server.options[:environment] + assert_equal "production", server.options[:environment] end end end @@ -51,10 +52,18 @@ class Rails::ServerTest < ActiveSupport::TestCase 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] + end + end + def test_caching_without_option args = [] options = Rails::Server::Options.new.parse!(args) - assert_equal nil, options[:caching] + merged_options = Rails::Server.new.default_options.merge(options) + assert_equal nil, merged_options[:caching] end def test_caching_with_option @@ -82,25 +91,25 @@ class Rails::ServerTest < ActiveSupport::TestCase options = Rails::Server::Options.new.parse!(args) assert_equal false, options[:log_stdout] - with_rack_env 'development' do + with_rack_env "development" do args = [] options = Rails::Server::Options.new.parse!(args) assert_equal true, options[:log_stdout] end - with_rack_env 'production' do + with_rack_env "production" do args = [] options = Rails::Server::Options.new.parse!(args) assert_equal false, options[:log_stdout] end - with_rails_env 'development' do + with_rails_env "development" do args = [] options = Rails::Server::Options.new.parse!(args) assert_equal true, options[:log_stdout] end - with_rails_env 'production' do + with_rails_env "production" do args = [] options = Rails::Server::Options.new.parse!(args) assert_equal false, options[:log_stdout] @@ -117,4 +126,18 @@ class Rails::ServerTest < ActiveSupport::TestCase assert_equal old_default_options, server.default_options end end + + def test_restart_command_contains_customized_options + original_args = ARGV.dup + args = ["-p", "4567"] + ARGV.replace args + + options = Rails::Server::Options.new.parse! args + server = Rails::Server.new options + expected = "bin/rails server -p 4567" + + assert_equal expected, server.default_options[:restart_cmd] + ensure + ARGV.replace original_args + end end diff --git a/railties/test/configuration/middleware_stack_proxy_test.rb b/railties/test/configuration/middleware_stack_proxy_test.rb index d5072614cf..559ce72693 100644 --- a/railties/test/configuration/middleware_stack_proxy_test.rb +++ b/railties/test/configuration/middleware_stack_proxy_test.rb @@ -1,8 +1,8 @@ -require 'active_support' -require 'active_support/testing/autorun' -require 'rails/configuration' -require 'active_support/test_case' -require 'minitest/mock' +require "active_support" +require "active_support/testing/autorun" +require "rails/configuration" +require "active_support/test_case" +require "minitest/mock" module Rails module Configuration @@ -50,12 +50,12 @@ module Rails private - def assert_playback(msg_name, args) - mock = Minitest::Mock.new - mock.expect :send, nil, [msg_name, args] - @stack.merge_into(mock) - mock.verify - end + def assert_playback(msg_name, args) + mock = Minitest::Mock.new + mock.expect :send, nil, [msg_name, args] + @stack.merge_into(mock) + mock.verify + end end end end diff --git a/railties/test/engine/commands_tasks_test.rb b/railties/test/engine/commands_tasks_test.rb new file mode 100644 index 0000000000..817175b9ef --- /dev/null +++ b/railties/test/engine/commands_tasks_test.rb @@ -0,0 +1,24 @@ +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_test.rb b/railties/test/engine_test.rb index f46fb748f5..248afa2d2c 100644 --- a/railties/test/engine_test.rb +++ b/railties/test/engine_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class EngineTest < ActiveSupport::TestCase test "reports routes as available only if they're actually present" do diff --git a/railties/test/env_helpers.rb b/railties/test/env_helpers.rb index 330fe150ca..1f64d5fda3 100644 --- a/railties/test/env_helpers.rb +++ b/railties/test/env_helpers.rb @@ -1,30 +1,30 @@ -require 'rails' +require "rails" module EnvHelpers private - def with_rails_env(env) - Rails.instance_variable_set :@_env, nil - switch_env 'RAILS_ENV', env do - switch_env 'RACK_ENV', nil do - yield + def with_rails_env(env) + Rails.instance_variable_set :@_env, nil + switch_env "RAILS_ENV", env do + switch_env "RACK_ENV", nil do + yield + end end end - end - def with_rack_env(env) - Rails.instance_variable_set :@_env, nil - switch_env 'RACK_ENV', env do - switch_env 'RAILS_ENV', nil do - yield + def with_rack_env(env) + Rails.instance_variable_set :@_env, nil + switch_env "RACK_ENV", env do + switch_env "RAILS_ENV", nil do + yield + end end end - end - def switch_env(key, value) - old, ENV[key] = ENV[key], value - yield - ensure - ENV[key] = old - end + def switch_env(key, value) + old, ENV[key] = ENV[key], value + yield + ensure + ENV[key] = old + end end diff --git a/railties/test/fixtures/about_yml_plugins/bad_about_yml/init.rb b/railties/test/fixtures/about_yml_plugins/bad_about_yml/init.rb index d4262f8971..636bc1a8ab 100644 --- a/railties/test/fixtures/about_yml_plugins/bad_about_yml/init.rb +++ b/railties/test/fixtures/about_yml_plugins/bad_about_yml/init.rb @@ -1 +1 @@ -# intentionally empty
\ No newline at end of file +# intentionally empty diff --git a/railties/test/fixtures/about_yml_plugins/plugin_without_about_yml/init.rb b/railties/test/fixtures/about_yml_plugins/plugin_without_about_yml/init.rb index d4262f8971..636bc1a8ab 100644 --- a/railties/test/fixtures/about_yml_plugins/plugin_without_about_yml/init.rb +++ b/railties/test/fixtures/about_yml_plugins/plugin_without_about_yml/init.rb @@ -1 +1 @@ -# intentionally empty
\ No newline at end of file +# intentionally empty diff --git a/railties/test/fixtures/lib/generators/active_record/fixjour_generator.rb b/railties/test/fixtures/lib/generators/active_record/fixjour_generator.rb index a7d079a1bc..1139350b94 100644 --- a/railties/test/fixtures/lib/generators/active_record/fixjour_generator.rb +++ b/railties/test/fixtures/lib/generators/active_record/fixjour_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/active_record' +require "rails/generators/active_record" module ActiveRecord module Generators diff --git a/railties/test/fixtures/lib/generators/model_generator.rb b/railties/test/fixtures/lib/generators/model_generator.rb index 9098a8a354..0905fa9631 100644 --- a/railties/test/fixtures/lib/generators/model_generator.rb +++ b/railties/test/fixtures/lib/generators/model_generator.rb @@ -1 +1 @@ -raise "I should never be loaded"
\ No newline at end of file +raise "I should never be loaded" diff --git a/railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb b/railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb index 078b0f9412..21b0ff6c28 100644 --- a/railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb +++ b/railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators' +require "rails/generators" class UsageTemplateGenerator < Rails::Generators::Base source_root File.expand_path("templates", File.dirname(__FILE__)) diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index 3300850604..0a26897a4d 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -1,6 +1,6 @@ -require 'generators/generators_test_helper' -require 'rails/generators/rails/app/app_generator' -require 'env_helpers' +require "generators/generators_test_helper" +require "rails/generators/rails/app/app_generator" +require "env_helpers" class ActionsTest < Rails::Generators::TestCase include GeneratorsTestHelper @@ -19,98 +19,98 @@ class ActionsTest < Rails::Generators::TestCase end def test_invoke_other_generator_with_shortcut - action :invoke, 'model', ['my_model'] - assert_file 'app/models/my_model.rb', /MyModel/ + action :invoke, "model", ["my_model"] + assert_file "app/models/my_model.rb", /MyModel/ end def test_invoke_other_generator_with_full_namespace - action :invoke, 'rails:model', ['my_model'] - assert_file 'app/models/my_model.rb', /MyModel/ + action :invoke, "rails:model", ["my_model"] + assert_file "app/models/my_model.rb", /MyModel/ end def test_create_file_should_write_data_to_file_path - action :create_file, 'lib/test_file.rb', 'heres test data' - assert_file 'lib/test_file.rb', 'heres test data' + action :create_file, "lib/test_file.rb", "heres test data" + assert_file "lib/test_file.rb", "heres test data" end def test_create_file_should_write_block_contents_to_file_path - action(:create_file, 'lib/test_file.rb'){ 'heres block data' } - assert_file 'lib/test_file.rb', 'heres block data' + action(:create_file, "lib/test_file.rb") { "heres block data" } + assert_file "lib/test_file.rb", "heres block data" end def test_add_source_adds_source_to_gemfile run_generator - action :add_source, 'http://gems.github.com' - assert_file 'Gemfile', /source 'http:\/\/gems\.github\.com'/ + action :add_source, "http://gems.github.com" + assert_file "Gemfile", /source 'http:\/\/gems\.github\.com'/ end def test_add_source_with_block_adds_source_to_gemfile_with_gem run_generator - action :add_source, 'http://gems.github.com' do - gem 'rspec-rails' + action :add_source, "http://gems.github.com" do + gem "rspec-rails" end - assert_file 'Gemfile', /source 'http:\/\/gems\.github\.com' do\n gem 'rspec-rails'\nend/ + assert_file "Gemfile", /source 'http:\/\/gems\.github\.com' do\n gem 'rspec-rails'\nend/ end def test_add_source_with_block_adds_source_to_gemfile_after_gem run_generator - action :gem, 'will-paginate' - action :add_source, 'http://gems.github.com' do - gem 'rspec-rails' + action :gem, "will-paginate" + action :add_source, "http://gems.github.com" do + gem "rspec-rails" end - assert_file 'Gemfile', /gem 'will-paginate'\nsource 'http:\/\/gems\.github\.com' do\n gem 'rspec-rails'\nend/ + assert_file "Gemfile", /gem 'will-paginate'\nsource 'http:\/\/gems\.github\.com' do\n gem 'rspec-rails'\nend/ end def test_gem_should_put_gem_dependency_in_gemfile run_generator - action :gem, 'will-paginate' - assert_file 'Gemfile', /gem 'will\-paginate'/ + action :gem, "will-paginate" + assert_file "Gemfile", /gem 'will\-paginate'/ end def test_gem_with_version_should_include_version_in_gemfile run_generator - action :gem, 'rspec', '>=2.0.0.a5' + action :gem, "rspec", ">=2.0.0.a5" - assert_file 'Gemfile', /gem 'rspec', '>=2.0.0.a5'/ + assert_file "Gemfile", /gem 'rspec', '>=2.0.0.a5'/ end def test_gem_should_insert_on_separate_lines run_generator - File.open('Gemfile', 'a') {|f| f.write('# Some content...') } + File.open("Gemfile", "a") { |f| f.write("# Some content...") } - action :gem, 'rspec' - action :gem, 'rspec-rails' + action :gem, "rspec" + action :gem, "rspec-rails" - assert_file 'Gemfile', /^gem 'rspec'$/ - assert_file 'Gemfile', /^gem 'rspec-rails'$/ + assert_file "Gemfile", /^gem 'rspec'$/ + assert_file "Gemfile", /^gem 'rspec-rails'$/ end def test_gem_should_include_options run_generator - action :gem, 'rspec', github: 'dchelimsky/rspec', tag: '1.2.9.rc1' + action :gem, "rspec", github: "dchelimsky/rspec", tag: "1.2.9.rc1" - assert_file 'Gemfile', /gem 'rspec', github: 'dchelimsky\/rspec', tag: '1\.2\.9\.rc1'/ + assert_file "Gemfile", /gem 'rspec', github: 'dchelimsky\/rspec', tag: '1\.2\.9\.rc1'/ end def test_gem_with_non_string_options run_generator - action :gem, 'rspec', require: false - action :gem, 'rspec-rails', group: [:development, :test] + action :gem, "rspec", require: false + action :gem, "rspec-rails", group: [:development, :test] - assert_file 'Gemfile', /^gem 'rspec', require: false$/ - assert_file 'Gemfile', /^gem 'rspec-rails', group: \[:development, :test\]$/ + assert_file "Gemfile", /^gem 'rspec', require: false$/ + assert_file "Gemfile", /^gem 'rspec-rails', group: \[:development, :test\]$/ end def test_gem_falls_back_to_inspect_if_string_contains_single_quote run_generator - action :gem, 'rspec', ">=2.0'0" + action :gem, "rspec", ">=2.0'0" - assert_file 'Gemfile', /^gem 'rspec', ">=2\.0'0"$/ + assert_file "Gemfile", /^gem 'rspec', ">=2\.0'0"$/ end def test_gem_works_even_if_frozen_string_is_passed_as_argument @@ -118,34 +118,34 @@ class ActionsTest < Rails::Generators::TestCase action :gem, "frozen_gem".freeze, "1.0.0".freeze - assert_file 'Gemfile', /^gem 'frozen_gem', '1.0.0'$/ + assert_file "Gemfile", /^gem 'frozen_gem', '1.0.0'$/ end def test_gem_group_should_wrap_gems_in_a_group run_generator action :gem_group, :development, :test do - gem 'rspec-rails' + gem "rspec-rails" end action :gem_group, :test do - gem 'fakeweb' + gem "fakeweb" end - assert_file 'Gemfile', /\ngroup :development, :test do\n gem 'rspec-rails'\nend\n\ngroup :test do\n gem 'fakeweb'\nend/ + assert_file "Gemfile", /\ngroup :development, :test do\n gem 'rspec-rails'\nend\n\ngroup :test do\n gem 'fakeweb'\nend/ end def test_environment_should_include_data_in_environment_initializer_block run_generator autoload_paths = 'config.autoload_paths += %w["#{Rails.root}/app/extras"]' action :environment, autoload_paths - assert_file 'config/application.rb', / class Application < Rails::Application\n #{Regexp.escape(autoload_paths)}/ + assert_file "config/application.rb", / class Application < Rails::Application\n #{Regexp.escape(autoload_paths)}/ end def test_environment_should_include_data_in_environment_initializer_block_with_env_option run_generator autoload_paths = 'config.autoload_paths += %w["#{Rails.root}/app/extras"]' - action :environment, autoload_paths, env: 'development' + action :environment, autoload_paths, env: "development" assert_file "config/environments/development.rb", /Rails\.application\.configure do\n #{Regexp.escape(autoload_paths)}/ end @@ -153,94 +153,132 @@ class ActionsTest < Rails::Generators::TestCase run_generator action :environment do - _ = '# This wont be added'# assignment to silence parse-time warning "unused literal ignored" - '# This will be added' + _ = "# This wont be added"# assignment to silence parse-time warning "unused literal ignored" + "# This will be added" end - assert_file 'config/application.rb' do |content| + assert_file "config/application.rb" do |content| assert_match(/# This will be added/, content) assert_no_match(/# This wont be added/, content) end end def test_git_with_symbol_should_run_command_using_git_scm - assert_called_with(generator, :run, ['git init']) do + assert_called_with(generator, :run, ["git init"]) do action :git, :init end end def test_git_with_hash_should_run_each_command_using_git_scm assert_called_with(generator, :run, [ ["git rm README"], ["git add ."] ]) do - action :git, rm: 'README', add: '.' + action :git, rm: "README", add: "." end end def test_vendor_should_write_data_to_file_in_vendor - action :vendor, 'vendor_file.rb', '# vendor data' - assert_file 'vendor/vendor_file.rb', '# vendor data' + action :vendor, "vendor_file.rb", "# vendor data" + assert_file "vendor/vendor_file.rb", "# vendor data" end def test_lib_should_write_data_to_file_in_lib - action :lib, 'my_library.rb', 'class MyLibrary' - assert_file 'lib/my_library.rb', 'class MyLibrary' + action :lib, "my_library.rb", "class MyLibrary" + assert_file "lib/my_library.rb", "class MyLibrary" end def test_rakefile_should_write_date_to_file_in_lib_tasks - action :rakefile, 'myapp.rake', 'task run: [:environment]' - assert_file 'lib/tasks/myapp.rake', 'task run: [:environment]' + action :rakefile, "myapp.rake", "task run: [:environment]" + assert_file "lib/tasks/myapp.rake", "task run: [:environment]" end def test_initializer_should_write_date_to_file_in_config_initializers - action :initializer, 'constants.rb', 'MY_CONSTANT = 42' - assert_file 'config/initializers/constants.rb', 'MY_CONSTANT = 42' + action :initializer, "constants.rb", "MY_CONSTANT = 42" + assert_file "config/initializers/constants.rb", "MY_CONSTANT = 42" end def test_generate_should_run_script_generate_with_argument_and_options - assert_called_with(generator, :run_ruby_script, ['bin/rails generate model MyModel', verbose: false]) do - action :generate, 'model', 'MyModel' + assert_called_with(generator, :run_ruby_script, ["bin/rails generate model MyModel", verbose: false]) do + action :generate, "model", "MyModel" end end - def test_rake_should_run_rake_command_with_default_env + def test_rails_should_run_rake_command_with_default_env assert_called_with(generator, :run, ["rake log:clear RAILS_ENV=development", verbose: false]) do with_rails_env nil do - action :rake, 'log:clear' + action :rake, "log:clear" end end end - def test_rake_with_env_option_should_run_rake_command_in_env - assert_called_with(generator, :run, ['rake log:clear RAILS_ENV=production', verbose: false]) do - action :rake, 'log:clear', env: 'production' + def test_rails_with_env_option_should_run_rake_command_in_env + assert_called_with(generator, :run, ["rake log:clear RAILS_ENV=production", verbose: false]) do + action :rake, "log:clear", env: "production" end end - def test_rake_with_rails_env_variable_should_run_rake_command_in_env - assert_called_with(generator, :run, ['rake log:clear RAILS_ENV=production', verbose: false]) do + test "rails command with RAILS_ENV variable should run rake command in env" do + assert_called_with(generator, :run, ["rake log:clear RAILS_ENV=production", verbose: false]) do with_rails_env "production" do - action :rake, 'log:clear' + action :rake, "log:clear" end end end - def test_env_option_should_win_over_rails_env_variable_when_running_rake - assert_called_with(generator, :run, ['rake log:clear RAILS_ENV=production', verbose: false]) do + test "env option should win over RAILS_ENV variable when running rake" do + assert_called_with(generator, :run, ["rake log:clear RAILS_ENV=production", verbose: false]) do with_rails_env "staging" do - action :rake, 'log:clear', env: 'production' + action :rake, "log:clear", env: "production" end end end - def test_rake_with_sudo_option_should_run_rake_command_with_sudo + test "rails command with sudo option should run rake command with sudo" do assert_called_with(generator, :run, ["sudo rake log:clear RAILS_ENV=development", verbose: false]) do with_rails_env nil do - action :rake, 'log:clear', sudo: true + action :rake, "log:clear", sudo: true + end + end + end + + test "rails command should run rails_command with default env" do + assert_called_with(generator, :run, ["rails log:clear RAILS_ENV=development", verbose: false]) do + with_rails_env nil do + action :rails_command, "log:clear" + end + end + end + + test "rails command with env option should run rails_command with same env" do + assert_called_with(generator, :run, ["rails log:clear RAILS_ENV=production", verbose: false]) do + action :rails_command, "log:clear", env: "production" + end + end + + test "rails command with RAILS_ENV variable should run rails_command in env" do + assert_called_with(generator, :run, ["rails log:clear RAILS_ENV=production", verbose: false]) do + with_rails_env "production" do + action :rails_command, "log:clear" + end + end + end + + def test_env_option_should_win_over_rails_env_variable_when_running_rails + assert_called_with(generator, :run, ["rails log:clear RAILS_ENV=production", verbose: false]) do + with_rails_env "staging" do + action :rails_command, "log:clear", env: "production" + end + end + end + + test "rails command with sudo option should run rails_command with sudo" do + assert_called_with(generator, :run, ["sudo rails log:clear RAILS_ENV=development", verbose: false]) do + with_rails_env nil do + action :rails_command, "log:clear", sudo: true end end end def test_capify_should_run_the_capify_command - assert_called_with(generator, :run, ['capify .', verbose: false]) do + assert_called_with(generator, :run, ["capify .", verbose: false]) do action :capify! end end @@ -249,12 +287,12 @@ class ActionsTest < Rails::Generators::TestCase run_generator route_command = "route '/login', controller: 'sessions', action: 'new'" action :route, route_command - assert_file 'config/routes.rb', /#{Regexp.escape(route_command)}/ + assert_file "config/routes.rb", /#{Regexp.escape(route_command)}/ end def test_route_should_be_idempotent run_generator - route_path = File.expand_path('config/routes.rb', destination_root) + route_path = File.expand_path("config/routes.rb", destination_root) # runs first time, not asserting action :route, "root 'welcome#index'" @@ -274,8 +312,8 @@ class ActionsTest < Rails::Generators::TestCase content = File.read(route_path) # Remove all of the comments and blank lines from the routes file - content.gsub!(/^ \#.*\n/, '') - content.gsub!(/^\n/, '') + content.gsub!(/^ \#.*\n/, "") + content.gsub!(/^\n/, "") File.open(route_path, "wb") { |file| file.write(content) } @@ -334,7 +372,6 @@ F protected def action(*args, &block) - capture(:stdout){ generator.send(*args, &block) } + capture(:stdout) { generator.send(*args, &block) } end - end diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb index 1ea5661006..bbb814ef4e 100644 --- a/railties/test/generators/api_app_generator_test.rb +++ b/railties/test/generators/api_app_generator_test.rb @@ -1,11 +1,11 @@ -require 'generators/generators_test_helper' -require 'rails/generators/rails/app/app_generator' +require "generators/generators_test_helper" +require "rails/generators/rails/app/app_generator" class ApiAppGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper tests Rails::Generators::AppGenerator - arguments [destination_root, '--api'] + arguments [destination_root, "--api"] def setup Rails.application = TestApp::Application @@ -62,47 +62,61 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase end end - private + def test_generator_skips_per_form_csrf_token_and_origin_check_configs_for_api_apps + run_generator - def default_files - files = %W( - .gitignore - Gemfile - Rakefile - config.ru - app/controllers - app/mailers - app/models - config/environments - config/initializers - config/locales - db - lib - lib/tasks - log - test/fixtures - test/controllers - test/integration - test/models - tmp - vendor - ) - files.concat %w(bin/bundle bin/rails bin/rake) - files + assert_file "config/initializers/new_framework_defaults.rb" do |initializer_content| + assert_no_match(/per_form_csrf_tokens/, initializer_content) + assert_no_match(/forgery_protection_origin_check/, initializer_content) + end end - def skipped_files - %w(app/assets - app/helpers - app/views - config/initializers/assets.rb - config/initializers/cookies_serializer.rb - config/initializers/session_store.rb - config/initializers/request_forgery_protection.rb - config/initializers/per_form_csrf_tokens.rb - lib/assets - vendor/assets - test/helpers - tmp/cache/assets) - end + private + + def default_files + files = %W( + .gitignore + Gemfile + Rakefile + config.ru + app/controllers + app/mailers + app/models + app/views/layouts/mailer.html.erb + app/views/layouts/mailer.text.erb + config/environments + config/initializers + config/locales + db + lib + lib/tasks + log + test/fixtures + test/controllers + test/integration + test/models + tmp + vendor + ) + files.concat %w(bin/bundle bin/rails bin/rake) + files + end + + def skipped_files + %w(app/assets + app/helpers + app/views/layouts/application.html.erb + config/initializers/assets.rb + config/initializers/cookies_serializer.rb + lib/assets + vendor/assets + test/helpers + tmp/cache/assets + public/404.html + public/422.html + public/500.html + public/apple-touch-icon-precomposed.png + public/apple-touch-icon.png + public/favicon.ico) + end end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index be05e779ea..9299b9ebea 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -1,6 +1,6 @@ -require 'generators/generators_test_helper' -require 'rails/generators/rails/app/app_generator' -require 'generators/shared_generator_tests' +require "generators/generators_test_helper" +require "rails/generators/rails/app/app_generator" +require "generators/shared_generator_tests" DEFAULT_APP_FILES = %w( .gitignore @@ -28,6 +28,7 @@ DEFAULT_APP_FILES = %w( config/locales config/cable.yml config/puma.rb + config/spring.rb db lib lib/tasks @@ -64,8 +65,8 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_assets run_generator - assert_file("app/views/layouts/application.html.erb", /stylesheet_link_tag\s+'application', media: 'all', 'data-turbolinks-track' => true/) - assert_file("app/views/layouts/application.html.erb", /javascript_include_tag\s+'application', 'data-turbolinks-track' => true/) + assert_file("app/views/layouts/application.html.erb", /stylesheet_link_tag\s+'application', media: 'all', 'data-turbolinks-track': 'reload'/) + assert_file("app/views/layouts/application.html.erb", /javascript_include_tag\s+'application', 'data-turbolinks-track': 'reload'/) assert_file("app/assets/stylesheets/application.css") assert_file("app/assets/javascripts/application.js") end @@ -76,7 +77,7 @@ class AppGeneratorTest < Rails::Generators::TestCase end def test_invalid_application_name_raises_an_error - content = capture(:stderr){ run_generator [File.join(destination_root, "43-things")] } + content = capture(:stderr) { run_generator [File.join(destination_root, "43-things")] } assert_equal "Invalid application name 43-things. Please give a name which does not start with numbers.\n", content end @@ -87,12 +88,12 @@ class AppGeneratorTest < Rails::Generators::TestCase end def test_application_new_exits_with_non_zero_code_on_invalid_application_name - quietly { system 'rails new test --no-rc' } + quietly { system "rails new test --no-rc" } assert_equal false, $?.success? end def test_application_new_exits_with_message_and_non_zero_code_when_generating_inside_existing_rails_directory - app_root = File.join(destination_root, 'myfirstapp') + app_root = File.join(destination_root, "myfirstapp") run_generator [app_root] output = nil Dir.chdir(app_root) do @@ -103,7 +104,7 @@ class AppGeneratorTest < Rails::Generators::TestCase end def test_application_new_show_help_message_inside_existing_rails_directory - app_root = File.join(destination_root, 'myfirstapp') + app_root = File.join(destination_root, "myfirstapp") run_generator [app_root] output = Dir.chdir(app_root) do `rails new --help` @@ -125,25 +126,23 @@ class AppGeneratorTest < Rails::Generators::TestCase # make sure we are in correct dir FileUtils.cd(app_moved_root) - generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, + generator = Rails::Generators::AppGenerator.new ["rails"], [], destination_root: app_moved_root, shell: @shell generator.send(:app_const) quietly { generator.send(:update_config_files) } assert_file "myapp_moved/config/environment.rb", /Rails\.application\.initialize!/ - assert_file "myapp_moved/config/initializers/session_store.rb", /_myapp_session/ end end end def test_rails_update_generates_correct_session_key - app_root = File.join(destination_root, 'myapp') + app_root = File.join(destination_root, "myapp") run_generator [app_root] stub_rails_application(app_root) do - generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell + generator = Rails::Generators::AppGenerator.new ["rails"], [], destination_root: app_root, shell: @shell generator.send(:app_const) quietly { generator.send(:update_config_files) } - assert_file "myapp/config/initializers/session_store.rb", /_myapp_session/ end end @@ -156,88 +155,92 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_new_application_not_include_api_initializers run_generator - assert_no_file 'config/initializers/cors.rb' + assert_no_file "config/initializers/cors.rb" end def test_rails_update_keep_the_cookie_serializer_if_it_is_already_configured - app_root = File.join(destination_root, 'myapp') + app_root = File.join(destination_root, "myapp") run_generator [app_root] stub_rails_application(app_root) do - generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell + generator = Rails::Generators::AppGenerator.new ["rails"], [], destination_root: app_root, shell: @shell generator.send(:app_const) quietly { generator.send(:update_config_files) } assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :json/) end end - def test_rails_update_does_not_create_callback_terminator_initializer - app_root = File.join(destination_root, 'myapp') + def test_rails_update_set_the_cookie_serializer_to_marshal_if_it_is_not_already_configured + app_root = File.join(destination_root, "myapp") run_generator [app_root] - FileUtils.rm("#{app_root}/config/initializers/callback_terminator.rb") + FileUtils.rm("#{app_root}/config/initializers/cookies_serializer.rb") stub_rails_application(app_root) do - generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell + generator = Rails::Generators::AppGenerator.new ["rails"], [], destination_root: app_root, shell: @shell generator.send(:app_const) quietly { generator.send(:update_config_files) } - assert_no_file "#{app_root}/config/initializers/callback_terminator.rb" + assert_file("#{app_root}/config/initializers/cookies_serializer.rb", + /Valid options are :json, :marshal, and :hybrid\.\nRails\.application\.config\.action_dispatch\.cookies_serializer = :marshal/) end end - def test_rails_update_does_not_remove_callback_terminator_initializer_if_already_present - app_root = File.join(destination_root, 'myapp') + def test_rails_update_dont_set_file_watcher + app_root = File.join(destination_root, "myapp") run_generator [app_root] - FileUtils.touch("#{app_root}/config/initializers/callback_terminator.rb") - stub_rails_application(app_root) do - generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell + generator = Rails::Generators::AppGenerator.new ["rails"], [], destination_root: app_root, shell: @shell generator.send(:app_const) quietly { generator.send(:update_config_files) } - assert_file "#{app_root}/config/initializers/callback_terminator.rb" + assert_file "#{app_root}/config/environments/development.rb" do |content| + assert_match(/# config.file_watcher/, content) + end end end - def test_rails_update_set_the_cookie_serializer_to_marchal_if_it_is_not_already_configured - app_root = File.join(destination_root, 'myapp') + def test_rails_update_does_not_create_new_framework_defaults_by_default + app_root = File.join(destination_root, "myapp") run_generator [app_root] - FileUtils.rm("#{app_root}/config/initializers/cookies_serializer.rb") + FileUtils.rm("#{app_root}/config/initializers/new_framework_defaults.rb") stub_rails_application(app_root) do - generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell + generator = Rails::Generators::AppGenerator.new ["rails"], { update: true }, destination_root: app_root, shell: @shell generator.send(:app_const) quietly { generator.send(:update_config_files) } - assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :marshal/) + + 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 end end - def test_rails_update_does_not_create_active_record_belongs_to_required_by_default - app_root = File.join(destination_root, 'myapp') + def test_rails_update_does_not_create_rack_cors + app_root = File.join(destination_root, "myapp") run_generator [app_root] - FileUtils.rm("#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb") - stub_rails_application(app_root) do - generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell + generator = Rails::Generators::AppGenerator.new ["rails"], [], destination_root: app_root, shell: @shell generator.send(:app_const) quietly { generator.send(:update_config_files) } - assert_no_file "#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb" + assert_no_file "#{app_root}/config/initializers/cors.rb" end end - def test_rails_update_does_not_remove_active_record_belongs_to_required_by_default_if_already_present - app_root = File.join(destination_root, 'myapp') + def test_rails_update_does_not_remove_rack_cors_if_already_present + app_root = File.join(destination_root, "myapp") run_generator [app_root] - FileUtils.touch("#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb") + FileUtils.touch("#{app_root}/config/initializers/cors.rb") stub_rails_application(app_root) do - generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell + generator = Rails::Generators::AppGenerator.new ["rails"], [], destination_root: app_root, shell: @shell generator.send(:app_const) quietly { generator.send(:update_config_files) } - assert_file "#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb" + assert_file "#{app_root}/config/initializers/cors.rb" end end @@ -249,7 +252,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_gemfile_has_no_whitespace_errors run_generator absolute = File.expand_path("Gemfile", destination_root) - File.open(absolute, 'r') do |f| + File.open(absolute, "r") do |f| f.each_line do |line| assert_no_match %r{/^[ \t]+$/}, line end @@ -338,6 +341,11 @@ class AppGeneratorTest < Rails::Generators::TestCase end end + def test_generator_defaults_to_puma_version + run_generator [destination_root] + assert_gem "puma", "'~> 3.0'" + end + def test_generator_if_skip_puma_is_given run_generator [destination_root, "--skip-puma"] assert_no_file "config/puma.rb" @@ -348,13 +356,17 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_generator_if_skip_active_record_is_given run_generator [destination_root, "--skip-active-record"] + assert_no_directory "db/" assert_no_file "config/database.yml" - assert_no_file "config/initializers/active_record_belongs_to_required_by_default.rb" assert_no_file "app/models/application_record.rb" assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ assert_file "test/test_helper.rb" do |helper_content| assert_no_match(/fixtures :all/, helper_content) end + + assert_file "config/initializers/new_framework_defaults.rb" do |initializer_content| + assert_no_match(/belongs_to_required_by_default/, initializer_content) + end end def test_generator_if_skip_action_mailer_is_given @@ -369,13 +381,15 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "config/environments/production.rb" do |content| assert_no_match(/config\.action_mailer/, content) end + assert_no_directory "app/mailers" + assert_no_directory "test/mailers" end def test_generator_has_assets_gems run_generator - assert_gem 'sass-rails' - assert_gem 'uglifier' + assert_gem "sass-rails" + assert_gem "uglifier" end def test_generator_if_skip_sprockets_is_given @@ -398,17 +412,17 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_no_match(/config\.assets\.js_compressor = :uglifier/, content) assert_no_match(/config\.assets\.css_compressor = :sass/, content) end + assert_file "config/initializers/new_framework_defaults.rb" do |content| + assert_no_match(/unknown_asset_fallback/, content) + end 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["']/ assert_no_file "config/cable.yml" - assert_no_file "app/assets/javascripts/cable.coffee" + assert_no_file "app/assets/javascripts/cable.js" assert_no_file "app/channels" - assert_file "app/views/layouts/application.html.erb" do |content| - assert_no_match(/action_cable_meta_tag/, content) - end assert_file "Gemfile" do |content| assert_no_match(/redis/, content) end @@ -438,7 +452,7 @@ class AppGeneratorTest < Rails::Generators::TestCase end def test_other_javascript_libraries - run_generator [destination_root, '-j', 'prototype'] + run_generator [destination_root, "-j", "prototype"] assert_file "app/assets/javascripts/application.js" do |contents| assert_match %r{^//= require prototype}, contents assert_match %r{^//= require prototype_ujs}, contents @@ -460,12 +474,17 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "Gemfile" do |content| assert_no_match(/coffee-rails/, content) assert_no_match(/jquery-rails/, content) + assert_no_match(/uglifier/, content) + end + + assert_file "config/environments/production.rb" do |content| + assert_no_match(/config\.assets\.js_compressor = :uglifier/, content) end end def test_inclusion_of_jbuilder run_generator - assert_gem 'jbuilder' + assert_gem "jbuilder" end def test_inclusion_of_a_debugger @@ -475,26 +494,28 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_no_match(/byebug/, content) end else - assert_gem 'byebug' + assert_gem "byebug" end end - def test_inclusion_of_listen_related_gems + def test_inclusion_of_listen_related_configuration_by_default run_generator - if RbConfig::CONFIG['host_os'] =~ /darwin|linux/ - assert_gem 'listen' - assert_gem 'spring-watcher-listen' + if RbConfig::CONFIG["host_os"] =~ /darwin|linux/ + assert_listen_related_configuration else - assert_file 'Gemfile' do |content| - assert_no_match(/listen/, content) - end + assert_no_listen_related_configuration end end + def test_non_inclusion_of_listen_related_configuration_if_skip_listen + run_generator [destination_root, "--skip-listen"] + assert_no_listen_related_configuration + end + def test_evented_file_update_checker_config run_generator - assert_file 'config/environments/development.rb' do |content| - if RbConfig::CONFIG['host_os'] =~ /darwin|linux/ + assert_file "config/environments/development.rb" do |content| + if RbConfig::CONFIG["host_os"] =~ /darwin|linux/ assert_match(/^\s*config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content) else assert_match(/^\s*# config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content) @@ -524,8 +545,8 @@ class AppGeneratorTest < Rails::Generators::TestCase end def test_file_is_added_for_backwards_compatibility - action :file, 'lib/test_file.rb', 'heres test data' - assert_file 'lib/test_file.rb', 'heres test data' + action :file, "lib/test_file.rb", "heres test data" + assert_file "lib/test_file.rb", "heres test data" end def test_tests_are_removed_from_frameworks_if_skip_test_is_given @@ -540,13 +561,6 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "config/application.rb", /\s+require\s+["']active_job\/railtie["']/ end - def test_new_hash_style - run_generator - assert_file "config/initializers/session_store.rb" do |file| - assert_match(/config.session_store :cookie_store, key: '_.+_session'/, file) - end - end - def test_pretend_option output = run_generator [File.join(destination_root, "myapp"), "--pretend"] assert_no_match(/run bundle install/, output) @@ -556,15 +570,14 @@ class AppGeneratorTest < Rails::Generators::TestCase path = File.join(destination_root, "foo bar") # This also applies to MySQL apps but not with SQLite - run_generator [path, "-d", 'postgresql'] + run_generator [path, "-d", "postgresql"] assert_file "foo bar/config/database.yml", /database: foo_bar_development/ - assert_file "foo bar/config/initializers/session_store.rb", /key: '_foo_bar/ end def test_web_console run_generator - assert_gem 'web-console' + assert_gem "web-console" end def test_web_console_with_dev_option @@ -572,7 +585,7 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "Gemfile" do |content| assert_match(/gem 'web-console',\s+github: 'rails\/web-console'/, content) - assert_no_match(/gem 'web-console', '~> 3.0'/, content) + assert_no_match(/\Agem 'web-console', '>= 3.3.0'\z/, content) end end @@ -581,13 +594,28 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "Gemfile" do |content| assert_match(/gem 'web-console',\s+github: 'rails\/web-console'/, content) - assert_no_match(/gem 'web-console', '~> 3.0'/, content) + assert_no_match(/\Agem 'web-console', '>= 3.3.0'\z/, content) end end + def test_generation_runs_bundle_install + assert_generates_with_bundler + end + + def test_dev_option + assert_generates_with_bundler dev: true + rails_path = File.expand_path("../../..", Rails.root) + assert_file "Gemfile", /^gem\s+["']rails["'],\s+path:\s+["']#{Regexp.escape(rails_path)}["']$/ + end + + def test_edge_option + assert_generates_with_bundler edge: true + assert_file "Gemfile", %r{^gem\s+["']rails["'],\s+github:\s+["']#{Regexp.escape("rails/rails")}["']$} + end + def test_spring run_generator - assert_gem 'spring' + assert_gem "spring" end def test_spring_binstubs @@ -596,9 +624,9 @@ class AppGeneratorTest < Rails::Generators::TestCase @binstub_called ||= 0 case command - when 'install' + when "install" # Called when running bundle, we just want to stub it so nothing to do here. - when 'exec spring binstub --all' + when "exec spring binstub --all" @binstub_called += 1 assert_equal 1, @binstub_called, "exec spring binstub --all expected to be called once, but was called #{@install_called} times." end @@ -611,7 +639,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_spring_no_fork jruby_skip "spring doesn't run on JRuby" - assert_called_with(Process, :respond_to?, [:fork], returns: false) do + assert_called_with(Process, :respond_to?, [[:fork], [:fork]], returns: false) do run_generator assert_file "Gemfile" do |content| @@ -623,6 +651,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_skip_spring run_generator [destination_root, "--skip-spring"] + assert_no_file "config/spring.rb" assert_file "Gemfile" do |content| assert_no_match(/spring/, content) end @@ -653,15 +682,15 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_gitignore_when_sqlite3 run_generator - assert_file '.gitignore' do |content| + assert_file ".gitignore" do |content| assert_match(/sqlite3/, content) end end def test_gitignore_when_no_active_record - run_generator [destination_root, '--skip-active-record'] + run_generator [destination_root, "--skip-active-record"] - assert_file '.gitignore' do |content| + assert_file ".gitignore" do |content| assert_no_match(/sqlite/i, content) end end @@ -669,7 +698,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_gitignore_when_non_sqlite3_db run_generator([destination_root, "-d", "mysql"]) - assert_file '.gitignore' do |content| + assert_file ".gitignore" do |content| assert_no_match(/sqlite/i, content) end end @@ -712,17 +741,17 @@ class AppGeneratorTest < Rails::Generators::TestCase end def test_after_bundle_callback - path = 'http://example.org/rails_template' + path = "http://example.org/rails_template" template = %{ after_bundle { run 'echo ran after_bundle' } } template.instance_eval "def read; self; end" # Make the string respond to read check_open = -> *args do - assert_equal [ path, 'Accept' => 'application/x-thor-template' ], args + assert_equal [ path, "Accept" => "application/x-thor-template" ], args template end - sequence = ['install', 'exec spring binstub --all', 'echo ran after_bundle'] - @sequence_step ||= 0 + sequence = ["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}" @sequence_step += 1 @@ -741,22 +770,61 @@ class AppGeneratorTest < Rails::Generators::TestCase protected - def stub_rails_application(root) - Rails.application.config.root = root - Rails.application.class.stub(:name, "Myapp") do - yield + def stub_rails_application(root) + Rails.application.config.root = root + Rails.application.class.stub(:name, "Myapp") do + yield + end end - end - def action(*args, &block) - capture(:stdout) { generator.send(*args, &block) } - end + def action(*args, &block) + capture(:stdout) { generator.send(*args, &block) } + end - def assert_gem(gem, constraint = nil) - if constraint - assert_file "Gemfile", /^\s*gem\s+["']#{gem}["'], #{constraint}$*/ - else - assert_file "Gemfile", /^\s*gem\s+["']#{gem}["']$*/ + def assert_gem(gem, constraint = nil) + if constraint + assert_file "Gemfile", /^\s*gem\s+["']#{gem}["'], #{constraint}$*/ + else + assert_file "Gemfile", /^\s*gem\s+["']#{gem}["']$*/ + end + end + + def assert_listen_related_configuration + assert_gem "listen" + assert_gem "spring-watcher-listen" + + assert_file "config/environments/development.rb" do |content| + assert_match(/^\s*config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content) + end + end + + def assert_no_listen_related_configuration + assert_file "Gemfile" do |content| + assert_no_match(/listen/, content) + end + + assert_file "config/environments/development.rb" do |content| + assert_match(/^\s*# config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content) + end + end + + def assert_generates_with_bundler(options = {}) + generator([destination_root], options) + + command_check = -> command do + @install_called ||= 0 + + case command + when "install" + @install_called += 1 + assert_equal 1, @install_called, "install expected to be called once, but was called #{@install_called} times" + when "exec spring binstub --all" + # Called when running tests with spring, let through unscathed. + end + end + + generator.stub :bundle_command, command_check do + quietly { generator.invoke_all } + end end - end end diff --git a/railties/test/generators/argv_scrubber_test.rb b/railties/test/generators/argv_scrubber_test.rb index 31e07bc8da..7f4295a20f 100644 --- a/railties/test/generators/argv_scrubber_test.rb +++ b/railties/test/generators/argv_scrubber_test.rb @@ -1,7 +1,7 @@ -require 'active_support/test_case' -require 'active_support/testing/autorun' -require 'rails/generators/rails/app/app_generator' -require 'tempfile' +require "active_support/test_case" +require "active_support/testing/autorun" +require "rails/generators/rails/app/app_generator" +require "tempfile" module Rails module Generators @@ -11,7 +11,7 @@ module Rails # *must* act this way, I just want to prevent regressions. def test_version - ['-v', '--version'].each do |str| + ["-v", "--version"].each do |str| scrubber = ARGVScrubber.new [str] output = nil exit_code = nil @@ -26,37 +26,37 @@ module Rails end def test_default_help - argv = ['zomg', 'how', 'are', 'you'] + argv = ["zomg", "how", "are", "you"] scrubber = ARGVScrubber.new argv args = scrubber.prepare! - assert_equal ['--help'] + argv.drop(1), args + assert_equal ["--help"] + argv.drop(1), args end def test_prepare_returns_args - scrubber = ARGVScrubber.new ['hi mom'] + scrubber = ARGVScrubber.new ["hi mom"] args = scrubber.prepare! - assert_equal '--help', args.first + assert_equal "--help", args.first end def test_no_mutations - scrubber = ARGVScrubber.new ['hi mom'].freeze + scrubber = ARGVScrubber.new ["hi mom"].freeze args = scrubber.prepare! - assert_equal '--help', args.first + assert_equal "--help", args.first end def test_new_command_no_rc scrubber = Class.new(ARGVScrubber) { def self.default_rc_file - File.join(Dir.tmpdir, 'whatever') + File.join(Dir.tmpdir, "whatever") end - }.new ['new'] + }.new ["new"] args = scrubber.prepare! assert_equal [], args end def test_new_homedir_rc - file = Tempfile.new 'myrcfile' - file.puts '--hello-world' + file = Tempfile.new "myrcfile" + file.puts "--hello-world" file.flush message = nil @@ -65,10 +65,10 @@ module Rails file.path end define_method(:puts) { |msg| message = msg } - }.new ['new'] + }.new ["new"] args = scrubber.prepare! - assert_equal ['--hello-world'], args - assert_match 'hello-world', message + assert_equal ["--hello-world"], args + assert_match "hello-world", message assert_match file.path, message ensure file.close @@ -76,33 +76,33 @@ module Rails end def test_rc_whitespace_separated - file = Tempfile.new 'myrcfile' - file.puts '--hello --world' + file = Tempfile.new "myrcfile" + file.puts "--hello --world" file.flush message = nil scrubber = Class.new(ARGVScrubber) { define_method(:puts) { |msg| message = msg } - }.new ['new', "--rc=#{file.path}"] + }.new ["new", "--rc=#{file.path}"] args = scrubber.prepare! - assert_equal ['--hello', '--world'], args + assert_equal ["--hello", "--world"], args ensure file.close file.unlink end def test_new_rc_option - file = Tempfile.new 'myrcfile' - file.puts '--hello-world' + file = Tempfile.new "myrcfile" + file.puts "--hello-world" file.flush message = nil scrubber = Class.new(ARGVScrubber) { define_method(:puts) { |msg| message = msg } - }.new ['new', "--rc=#{file.path}"] + }.new ["new", "--rc=#{file.path}"] args = scrubber.prepare! - assert_equal ['--hello-world'], args - assert_match 'hello-world', message + assert_equal ["--hello-world"], args + assert_match "hello-world", message assert_match file.path, message ensure file.close @@ -110,14 +110,14 @@ module Rails end def test_new_rc_option_and_custom_options - file = Tempfile.new 'myrcfile' - file.puts '--hello' - file.puts '--world' + file = Tempfile.new "myrcfile" + file.puts "--hello" + file.puts "--world" file.flush scrubber = Class.new(ARGVScrubber) { define_method(:puts) { |msg| } - }.new ['new', 'tenderapp', '--love', "--rc=#{file.path}"] + }.new ["new", "tenderapp", "--love", "--rc=#{file.path}"] args = scrubber.prepare! assert_equal ["tenderapp", "--hello", "--world", "--love"], args @@ -127,7 +127,7 @@ module Rails end def test_no_rc - scrubber = ARGVScrubber.new ['new', '--no-rc'] + scrubber = ARGVScrubber.new ["new", "--no-rc"] args = scrubber.prepare! assert_equal [], args end diff --git a/railties/test/generators/assets_generator_test.rb b/railties/test/generators/assets_generator_test.rb index a2b94f2e50..1c02c67e42 100644 --- a/railties/test/generators/assets_generator_test.rb +++ b/railties/test/generators/assets_generator_test.rb @@ -1,5 +1,5 @@ -require 'generators/generators_test_helper' -require 'rails/generators/rails/assets/assets_generator' +require "generators/generators_test_helper" +require "rails/generators/rails/assets/assets_generator" class AssetsGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper diff --git a/railties/test/generators/channel_generator_test.rb b/railties/test/generators/channel_generator_test.rb index c1f0c03fbf..a1d54200ba 100644 --- a/railties/test/generators/channel_generator_test.rb +++ b/railties/test/generators/channel_generator_test.rb @@ -1,29 +1,77 @@ -require 'generators/generators_test_helper' -require 'rails/generators/channel/channel_generator' +require "generators/generators_test_helper" +require "rails/generators/channel/channel_generator" class ChannelGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper tests Rails::Generators::ChannelGenerator + def test_application_cable_skeleton_is_created + run_generator ["books"] + + assert_file "app/channels/application_cable/channel.rb" do |cable| + assert_match(/module ApplicationCable\n class Channel < ActionCable::Channel::Base\n/, cable) + end + + assert_file "app/channels/application_cable/connection.rb" do |cable| + assert_match(/module ApplicationCable\n class Connection < ActionCable::Connection::Base\n/, cable) + end + end + def test_channel_is_created - run_generator ['chat'] + run_generator ["chat"] assert_file "app/channels/chat_channel.rb" do |channel| assert_match(/class ChatChannel < ApplicationCable::Channel/, channel) end - assert_file "app/assets/javascripts/channels/chat.coffee" do |channel| - assert_match(/App.cable.subscriptions.create "ChatChannel"/, channel) + assert_file "app/assets/javascripts/channels/chat.js" do |channel| + assert_match(/App.chat = App.cable.subscriptions.create\("ChatChannel/, channel) + end + end + + def test_channel_with_multiple_actions_is_created + run_generator ["chat", "speak", "mute"] + + assert_file "app/channels/chat_channel.rb" do |channel| + assert_match(/class ChatChannel < ApplicationCable::Channel/, channel) + assert_match(/def speak/, channel) + assert_match(/def mute/, channel) + end + + assert_file "app/assets/javascripts/channels/chat.js" do |channel| + assert_match(/App.chat = App.cable.subscriptions.create\("ChatChannel/, channel) + assert_match(/,\n\n speak/, channel) + assert_match(/,\n\n mute: function\(\) \{\n return this\.perform\('mute'\);\n \}\n\}\);/, channel) end end def test_channel_asset_is_not_created_when_skip_assets_is_passed - run_generator ['chat', '--skip-assets'] + run_generator ["chat", "--skip-assets"] assert_file "app/channels/chat_channel.rb" do |channel| assert_match(/class ChatChannel < ApplicationCable::Channel/, channel) end - assert_no_file "app/assets/javascripts/channels/chat.coffee" + assert_no_file "app/assets/javascripts/channels/chat.js" + end + + def test_cable_js_is_created_if_not_present_already + run_generator ["chat"] + FileUtils.rm("#{destination_root}/app/assets/javascripts/cable.js") + run_generator ["camp"] + + assert_file "app/assets/javascripts/cable.js" + end + + def test_channel_on_revoke + run_generator ["chat"] + run_generator ["chat"], behavior: :revoke + + assert_no_file "app/channels/chat_channel.rb" + assert_no_file "app/assets/javascripts/channels/chat.js" + + assert_file "app/channels/application_cable/channel.rb" + assert_file "app/channels/application_cable/connection.rb" + assert_file "app/assets/javascripts/cable.js" end end diff --git a/railties/test/generators/controller_generator_test.rb b/railties/test/generators/controller_generator_test.rb index 1351151afb..9b986a636a 100644 --- a/railties/test/generators/controller_generator_test.rb +++ b/railties/test/generators/controller_generator_test.rb @@ -1,5 +1,5 @@ -require 'generators/generators_test_helper' -require 'rails/generators/rails/controller/controller_generator' +require "generators/generators_test_helper" +require "rails/generators/rails/controller/controller_generator" class ControllerGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper @@ -19,7 +19,7 @@ class ControllerGeneratorTest < Rails::Generators::TestCase def test_check_class_collision Object.send :const_set, :ObjectController, Class.new - content = capture(:stderr){ run_generator ["object"] } + content = capture(:stderr) { run_generator ["object"] } assert_match(/The name 'ObjectController' is either already used in your application or reserved/, content) ensure Object.send :remove_const, :ObjectController diff --git a/railties/test/generators/create_migration_test.rb b/railties/test/generators/create_migration_test.rb index e16a77479a..ddd40e4d02 100644 --- a/railties/test/generators/create_migration_test.rb +++ b/railties/test/generators/create_migration_test.rb @@ -1,5 +1,5 @@ -require 'generators/generators_test_helper' -require 'rails/generators/rails/migration/migration_generator' +require "generators/generators_test_helper" +require "rails/generators/rails/migration/migration_generator" class CreateMigrationTest < Rails::Generators::TestCase include GeneratorsTestHelper @@ -19,12 +19,12 @@ class CreateMigrationTest < Rails::Generators::TestCase end def create_migration(destination_path = default_destination_path, config = {}, generator_options = {}, &block) - migration_name = File.basename(destination_path, '.rb') + migration_name = File.basename(destination_path, ".rb") generator([migration_name], generator_options) generator.set_migration_assigns!(destination_path) dir, base = File.split(destination_path) - timestamped_destination_path = File.join(dir, ["%migration_number%", base].join('_')) + timestamped_destination_path = File.join(dir, ["%migration_number%", base].join("_")) @migration = Rails::Generators::Actions::CreateMigration.new(generator, timestamped_destination_path, block || "contents", config) end @@ -51,7 +51,7 @@ class CreateMigrationTest < Rails::Generators::TestCase end def test_invoke_pretended - create_migration(default_destination_path, {}, { pretend: true }) + create_migration(default_destination_path, {}, pretend: true) assert_no_file @migration.destination end @@ -92,7 +92,7 @@ class CreateMigrationTest < Rails::Generators::TestCase def test_invoke_forced_pretended_when_exists_not_identical migration_exists! - create_migration(default_destination_path, { force: true }, { pretend: true }) do + create_migration(default_destination_path, { force: true }, pretend: true) do "different content" end @@ -104,7 +104,7 @@ class CreateMigrationTest < Rails::Generators::TestCase def test_invoke_skipped_when_exists_not_identical migration_exists! - create_migration(default_destination_path, {}, { skip: true }) { "different content" } + create_migration(default_destination_path, {}, skip: true) { "different content" } assert_match(/skip db\/migrate\/2_create_articles.rb\n/, invoke!) assert_no_file @migration.destination @@ -120,7 +120,7 @@ class CreateMigrationTest < Rails::Generators::TestCase def test_revoke_pretended migration_exists! - create_migration(default_destination_path, {}, { pretend: true }) + create_migration(default_destination_path, {}, pretend: true) assert_match(/remove db\/migrate\/1_create_articles.rb\n/, revoke!) assert_file @existing_migration.destination diff --git a/railties/test/generators/generated_attribute_test.rb b/railties/test/generators/generated_attribute_test.rb index ee7c009305..97847c8624 100644 --- a/railties/test/generators/generated_attribute_test.rb +++ b/railties/test/generators/generated_attribute_test.rb @@ -1,5 +1,5 @@ -require 'generators/generators_test_helper' -require 'rails/generators/generated_attribute' +require "generators/generators_test_helper" +require "rails/generators/generated_attribute" class GeneratedAttributeTest < Rails::Generators::TestCase include GeneratorsTestHelper @@ -51,7 +51,7 @@ class GeneratedAttributeTest < Rails::Generators::TestCase end def test_default_value_is_decimal - assert_field_default_value :decimal, '9.99' + assert_field_default_value :decimal, "9.99" end def test_default_value_is_datetime @@ -65,7 +65,7 @@ class GeneratedAttributeTest < Rails::Generators::TestCase end def test_default_value_is_string - assert_field_default_value :string, 'MyString' + assert_field_default_value :string, "MyString" end def test_default_value_for_type @@ -74,7 +74,7 @@ class GeneratedAttributeTest < Rails::Generators::TestCase end def test_default_value_is_text - assert_field_default_value :text, 'MyText' + assert_field_default_value :text, "MyText" end def test_default_value_is_boolean @@ -89,14 +89,14 @@ class GeneratedAttributeTest < Rails::Generators::TestCase def test_default_value_is_empty_string %w(foo bar baz).each do |attribute_type| - assert_field_default_value attribute_type, '' + assert_field_default_value attribute_type, "" end end def test_human_name assert_equal( - 'Full name', - create_generated_attribute(:string, 'full_name').human_name + "Full name", + create_generated_attribute(:string, "full_name").human_name ) end @@ -125,21 +125,21 @@ class GeneratedAttributeTest < Rails::Generators::TestCase end def test_blank_type_defaults_to_string_raises_exception - assert_equal :string, create_generated_attribute(nil, 'title').type - assert_equal :string, create_generated_attribute("", 'title').type + assert_equal :string, create_generated_attribute(nil, "title").type + assert_equal :string, create_generated_attribute("", "title").type end def test_handles_index_names_for_references - assert_equal "post", create_generated_attribute('string', 'post').index_name - assert_equal "post_id", create_generated_attribute('references', 'post').index_name - assert_equal "post_id", create_generated_attribute('belongs_to', 'post').index_name - assert_equal ["post_id", "post_type"], create_generated_attribute('references{polymorphic}', 'post').index_name + assert_equal "post", create_generated_attribute("string", "post").index_name + assert_equal "post_id", create_generated_attribute("references", "post").index_name + assert_equal "post_id", create_generated_attribute("belongs_to", "post").index_name + assert_equal ["post_id", "post_type"], create_generated_attribute("references{polymorphic}", "post").index_name end def test_handles_column_names_for_references - assert_equal "post", create_generated_attribute('string', 'post').column_name - assert_equal "post_id", create_generated_attribute('references', 'post').column_name - assert_equal "post_id", create_generated_attribute('belongs_to', 'post').column_name + assert_equal "post", create_generated_attribute("string", "post").column_name + assert_equal "post_id", create_generated_attribute("references", "post").column_name + assert_equal "post_id", create_generated_attribute("belongs_to", "post").column_name end def test_parse_required_attribute_with_index diff --git a/railties/test/generators/generator_generator_test.rb b/railties/test/generators/generator_generator_test.rb index dcfeaaa8e0..5ff8bb0357 100644 --- a/railties/test/generators/generator_generator_test.rb +++ b/railties/test/generators/generator_generator_test.rb @@ -1,5 +1,5 @@ -require 'generators/generators_test_helper' -require 'rails/generators/rails/generator/generator_generator' +require "generators/generators_test_helper" +require "rails/generators/rails/generator/generator_generator" class GeneratorGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper @@ -12,7 +12,7 @@ class GeneratorGeneratorTest < Rails::Generators::TestCase lib/generators/awesome lib/generators/awesome/USAGE lib/generators/awesome/templates - ).each{ |path| assert_file path } + ).each { |path| assert_file path } assert_file "lib/generators/awesome/awesome_generator.rb", /class AwesomeGenerator < Rails::Generators::NamedBase/ @@ -28,7 +28,7 @@ class GeneratorGeneratorTest < Rails::Generators::TestCase lib/generators/rails/awesome lib/generators/rails/awesome/USAGE lib/generators/rails/awesome/templates - ).each{ |path| assert_file path } + ).each { |path| assert_file path } assert_file "lib/generators/rails/awesome/awesome_generator.rb", /class Rails::AwesomeGenerator < Rails::Generators::NamedBase/ @@ -44,7 +44,7 @@ class GeneratorGeneratorTest < Rails::Generators::TestCase lib/generators/ lib/generators/USAGE lib/generators/templates - ).each{ |path| assert_file path } + ).each { |path| assert_file path } assert_file "lib/generators/awesome_generator.rb", /class AwesomeGenerator < Rails::Generators::NamedBase/ @@ -60,7 +60,7 @@ class GeneratorGeneratorTest < Rails::Generators::TestCase lib/generators/rails lib/generators/rails/USAGE lib/generators/rails/templates - ).each{ |path| assert_file path } + ).each { |path| assert_file path } assert_file "lib/generators/rails/awesome_generator.rb", /class Rails::AwesomeGenerator < Rails::Generators::NamedBase/ diff --git a/railties/test/generators/generator_test.rb b/railties/test/generators/generator_test.rb index 8ef44a8dcb..c4e4747468 100644 --- a/railties/test/generators/generator_test.rb +++ b/railties/test/generators/generator_test.rb @@ -1,6 +1,6 @@ -require 'active_support/test_case' -require 'active_support/testing/autorun' -require 'rails/generators/app_base' +require "active_support/test_case" +require "active_support/testing/autorun" +require "rails/generators/app_base" module Rails module Generators @@ -21,46 +21,46 @@ module Rails def test_construction klass = make_builder_class - assert klass.start(['new', 'blah']) + assert klass.start(["new", "blah"]) end def test_add_gem klass = make_builder_class - generator = klass.start(['new', 'blah']) - generator.gemfile_entry 'tenderlove' - assert_includes generator.gemfile_entries.map(&:name), 'tenderlove' + generator = klass.start(["new", "blah"]) + generator.gemfile_entry "tenderlove" + assert_includes generator.gemfile_entries.map(&:name), "tenderlove" end def test_add_gem_with_version klass = make_builder_class - generator = klass.start(['new', 'blah']) - generator.gemfile_entry 'tenderlove', '2.0.0' + generator = klass.start(["new", "blah"]) + generator.gemfile_entry "tenderlove", "2.0.0" assert generator.gemfile_entries.find { |gfe| - gfe.name == 'tenderlove' && gfe.version == '2.0.0' + gfe.name == "tenderlove" && gfe.version == "2.0.0" } end def test_add_github_gem klass = make_builder_class - generator = klass.start(['new', 'blah']) - generator.gemfile_entry 'tenderlove', github: 'hello world' + generator = klass.start(["new", "blah"]) + generator.gemfile_entry "tenderlove", github: "hello world" assert generator.gemfile_entries.find { |gfe| - gfe.name == 'tenderlove' && gfe.options[:github] == 'hello world' + gfe.name == "tenderlove" && gfe.options[:github] == "hello world" } end def test_add_path_gem klass = make_builder_class - generator = klass.start(['new', 'blah']) - generator.gemfile_entry 'tenderlove', path: 'hello world' + generator = klass.start(["new", "blah"]) + generator.gemfile_entry "tenderlove", path: "hello world" assert generator.gemfile_entries.find { |gfe| - gfe.name == 'tenderlove' && gfe.options[:path] == 'hello world' + gfe.name == "tenderlove" && gfe.options[:path] == "hello world" } end def test_filter klass = make_builder_class - generator = klass.start(['new', 'blah']) + generator = klass.start(["new", "blah"]) gems = generator.gemfile_entries generator.add_gem_entry_filter { |gem| gem.name != gems.first.name @@ -70,7 +70,7 @@ module Rails def test_two_filters klass = make_builder_class - generator = klass.start(['new', 'blah']) + generator = klass.start(["new", "blah"]) gems = generator.gemfile_entries generator.add_gem_entry_filter { |gem| gem.name != gems.first.name @@ -83,17 +83,17 @@ module Rails def test_recommended_rails_versions klass = make_builder_class - generator = klass.start(['new', 'blah']) + generator = klass.start(["new", "blah"]) specifier_for = -> v { generator.send(:rails_version_specifier, Gem::Version.new(v)) } - assert_equal '~> 4.1.13', specifier_for['4.1.13'] - assert_equal ['>= 4.1.6.rc1', '< 4.2'], specifier_for['4.1.6.rc1'] - assert_equal ['~> 4.1.7', '>= 4.1.7.1'], specifier_for['4.1.7.1'] - assert_equal ['~> 4.1.7', '>= 4.1.7.1.2'], specifier_for['4.1.7.1.2'] - assert_equal ['>= 4.1.7.1.rc2', '< 4.2'], specifier_for['4.1.7.1.rc2'] - assert_equal ['>= 4.2.0.beta1', '< 4.3'], specifier_for['4.2.0.beta1'] - assert_equal ['>= 5.0.0.beta1', '< 5.1'], specifier_for['5.0.0.beta1'] + assert_equal "~> 4.1.13", specifier_for["4.1.13"] + assert_equal [">= 4.1.6.rc1", "< 4.2"], specifier_for["4.1.6.rc1"] + assert_equal ["~> 4.1.7", ">= 4.1.7.1"], specifier_for["4.1.7.1"] + assert_equal ["~> 4.1.7", ">= 4.1.7.1.2"], specifier_for["4.1.7.1.2"] + assert_equal [">= 4.1.7.1.rc2", "< 4.2"], specifier_for["4.1.7.1.rc2"] + assert_equal [">= 4.2.0.beta1", "< 4.3"], specifier_for["4.2.0.beta1"] + assert_equal [">= 5.0.0.beta1", "< 5.1"], specifier_for["5.0.0.beta1"] end end end diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb index b19a5a7144..2cdddc8713 100644 --- a/railties/test/generators/generators_test_helper.rb +++ b/railties/test/generators/generators_test_helper.rb @@ -1,15 +1,15 @@ -require 'abstract_unit' -require 'active_support/core_ext/module/remove_method' -require 'active_support/testing/stream' -require 'active_support/testing/method_call_assertions' -require 'rails/generators' -require 'rails/generators/test_case' +require "abstract_unit" +require "active_support/core_ext/module/remove_method" +require "active_support/testing/stream" +require "active_support/testing/method_call_assertions" +require "rails/generators" +require "rails/generators/test_case" module Rails class << self remove_possible_method :root def root - @root ||= Pathname.new(File.expand_path('../../fixtures', __FILE__)) + @root ||= Pathname.new(File.expand_path("../../fixtures", __FILE__)) end end end @@ -20,9 +20,9 @@ Rails.application.config.generators.templates = [File.join(Rails.root, "lib", "t # Rails.application.config.generators to Rails::Generators Rails.application.load_generators -require 'active_record' -require 'action_dispatch' -require 'action_view' +require "active_record" +require "action_dispatch" +require "action_view" module GeneratorsTestHelper include ActiveSupport::Testing::Stream @@ -34,7 +34,7 @@ module GeneratorsTestHelper setup :prepare_destination begin - base.tests Rails::Generators.const_get(base.name.sub(/Test$/, '')) + base.tests Rails::Generators.const_get(base.name.sub(/Test$/, "")) rescue end end @@ -46,5 +46,4 @@ module GeneratorsTestHelper FileUtils.mkdir_p(destination) FileUtils.cp routes, destination end - end diff --git a/railties/test/generators/helper_generator_test.rb b/railties/test/generators/helper_generator_test.rb index add04f21a4..d9e6e0a85a 100644 --- a/railties/test/generators/helper_generator_test.rb +++ b/railties/test/generators/helper_generator_test.rb @@ -1,5 +1,5 @@ -require 'generators/generators_test_helper' -require 'rails/generators/rails/helper/helper_generator' +require "generators/generators_test_helper" +require "rails/generators/rails/helper/helper_generator" ObjectHelper = Class.new AnotherObjectHelperTest = Class.new @@ -14,7 +14,7 @@ class HelperGeneratorTest < Rails::Generators::TestCase end def test_check_class_collision - content = capture(:stderr){ run_generator ["object"] } + content = capture(:stderr) { run_generator ["object"] } assert_match(/The name 'ObjectHelper' is either already used in your application or reserved/, content) end diff --git a/railties/test/generators/integration_test_generator_test.rb b/railties/test/generators/integration_test_generator_test.rb index d05ed76d24..8bcc02440a 100644 --- a/railties/test/generators/integration_test_generator_test.rb +++ b/railties/test/generators/integration_test_generator_test.rb @@ -1,5 +1,5 @@ -require 'generators/generators_test_helper' -require 'rails/generators/rails/integration_test/integration_test_generator' +require "generators/generators_test_helper" +require "rails/generators/rails/integration_test/integration_test_generator" class IntegrationTestGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper diff --git a/railties/test/generators/job_generator_test.rb b/railties/test/generators/job_generator_test.rb index 7fd8f2062f..68d158eb39 100644 --- a/railties/test/generators/job_generator_test.rb +++ b/railties/test/generators/job_generator_test.rb @@ -1,5 +1,5 @@ -require 'generators/generators_test_helper' -require 'rails/generators/job/job_generator' +require "generators/generators_test_helper" +require "rails/generators/job/job_generator" class JobGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper @@ -26,4 +26,11 @@ class JobGeneratorTest < Rails::Generators::TestCase assert_match(/queue_as :admin/, job) end end + + def test_application_job_skeleton_is_created + run_generator ["refresh_counters"] + assert_file "app/jobs/application_job.rb" do |job| + assert_match(/class ApplicationJob < ActiveJob::Base/, job) + end + end end diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb index f8d9ccacb4..7d69d7470d 100644 --- a/railties/test/generators/mailer_generator_test.rb +++ b/railties/test/generators/mailer_generator_test.rb @@ -1,5 +1,5 @@ -require 'generators/generators_test_helper' -require 'rails/generators/mailer/mailer_generator' +require "generators/generators_test_helper" +require "rails/generators/mailer/mailer_generator" class MailerGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper @@ -12,6 +12,12 @@ class MailerGeneratorTest < Rails::Generators::TestCase assert_no_match(/default from: "from@example.com"/, mailer) assert_no_match(/layout :mailer_notifier/, mailer) end + + assert_file "app/mailers/application_mailer.rb" do |mailer| + assert_match(/class ApplicationMailer < ActionMailer::Base/, mailer) + assert_match(/default from: 'from@example.com'/, mailer) + assert_match(/layout 'mailer'/, mailer) + end end def test_mailer_with_i18n_helper @@ -24,7 +30,7 @@ class MailerGeneratorTest < Rails::Generators::TestCase def test_check_class_collision Object.send :const_set, :NotifierMailer, Class.new - content = capture(:stderr){ run_generator } + content = capture(:stderr) { run_generator } assert_match(/The name 'NotifierMailer' is either already used in your application or reserved/, content) ensure Object.send :remove_const, :NotifierMailer @@ -53,7 +59,7 @@ class MailerGeneratorTest < Rails::Generators::TestCase def test_check_test_class_collision Object.send :const_set, :NotifierMailerTest, Class.new - content = capture(:stderr){ run_generator } + content = capture(:stderr) { run_generator } assert_match(/The name 'NotifierMailerTest' is either already used in your application or reserved/, content) ensure Object.send :remove_const, :NotifierMailerTest @@ -61,7 +67,7 @@ class MailerGeneratorTest < Rails::Generators::TestCase def test_check_preview_class_collision Object.send :const_set, :NotifierMailerPreview, Class.new - content = capture(:stderr){ run_generator } + content = capture(:stderr) { run_generator } assert_match(/The name 'NotifierMailerPreview' is either already used in your application or reserved/, content) ensure Object.send :remove_const, :NotifierMailerPreview @@ -78,6 +84,10 @@ class MailerGeneratorTest < Rails::Generators::TestCase assert_match(%r(\sapp/views/notifier_mailer/bar\.text\.erb), view) assert_match(/<%= @greeting %>/, view) end + + assert_file "app/views/layouts/mailer.text.erb" do |view| + assert_match(/<%= yield %>/, view) + end end def test_invokes_default_html_template_engine @@ -91,6 +101,10 @@ class MailerGeneratorTest < Rails::Generators::TestCase assert_match(%r(\sapp/views/notifier_mailer/bar\.html\.erb), view) assert_match(/<%= @greeting %>/, view) end + + assert_file "app/views/layouts/mailer.html.erb" do |view| + assert_match(%r{<body>\n <%= yield %>\n </body>}, view) + end end def test_invokes_default_template_engine_even_with_no_action diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb index 46154b7db2..6e1d1b70a9 100644 --- a/railties/test/generators/migration_generator_test.rb +++ b/railties/test/generators/migration_generator_test.rb @@ -1,5 +1,5 @@ -require 'generators/generators_test_helper' -require 'rails/generators/rails/migration/migration_generator' +require "generators/generators_test_helper" +require "rails/generators/rails/migration/migration_generator" class MigrationGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper @@ -17,7 +17,7 @@ class MigrationGeneratorTest < Rails::Generators::TestCase run_generator [migration] file_name = migration_file_name "db/migrate/#{migration}.rb" - File.basename(file_name).split('_').first + File.basename(file_name).split("_").first end assert_not_equal first_migration_number, second_migration_number @@ -248,7 +248,7 @@ class MigrationGeneratorTest < Rails::Generators::TestCase def test_migration_with_singular_table_name with_singular_table_name do migration = "add_title_body_to_post" - run_generator [migration, 'title:string'] + run_generator [migration, "title:string"] assert_migration "db/migrate/#{migration}.rb" do |content| assert_method :change, content do |change| assert_match(/add_column :post, :title, :string/, change) diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index c8c8f0aa3b..701d3ceaf2 100644 --- a/railties/test/generators/model_generator_test.rb +++ b/railties/test/generators/model_generator_test.rb @@ -1,11 +1,19 @@ -require 'generators/generators_test_helper' -require 'rails/generators/rails/model/model_generator' -require 'active_support/core_ext/string/strip' +require "generators/generators_test_helper" +require "rails/generators/rails/model/model_generator" +require "active_support/core_ext/string/strip" class ModelGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper arguments %w(Account name:string age:integer) + def test_application_record_skeleton_is_created + run_generator + assert_file "app/models/application_record.rb" do |record| + assert_match(/class ApplicationRecord < ActiveRecord::Base/, record) + assert_match(/self.abstract_class = true/, record) + end + end + def test_help_shows_invoked_generators_options content = run_generator ["--help"] assert_match(/ActiveRecord options:/, content) @@ -26,7 +34,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase def test_invokes_default_orm run_generator - assert_file "app/models/account.rb", /class Account < ActiveRecord::Base/ + assert_file "app/models/account.rb", /class Account < ApplicationRecord/ end def test_model_with_parent_option @@ -48,7 +56,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase def test_plural_names_are_singularized content = run_generator ["accounts".freeze] - assert_file "app/models/account.rb", /class Account < ActiveRecord::Base/ + assert_file "app/models/account.rb", /class Account < ApplicationRecord/ assert_file "test/models/account_test.rb", /class AccountTest/ assert_match(/\[WARNING\] The model name 'accounts' was recognized as a plural, using the singular 'account' instead\. Override with --force-plural or setup custom inflection rules for this noun before running the generator\./, content) end @@ -63,7 +71,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase assert_file "app/models/admin.rb", /module Admin/ assert_file "app/models/admin.rb", /def self\.table_name_prefix/ assert_file "app/models/admin.rb", /'admin_'/ - assert_file "app/models/admin/account.rb", /class Admin::Account < ActiveRecord::Base/ + assert_file "app/models/admin/account.rb", /class Admin::Account < ApplicationRecord/ end def test_migration @@ -267,7 +275,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase def test_migration_error_is_not_shown_on_revoke run_generator - error = capture(:stderr){ run_generator ["Account"], behavior: :revoke } + error = capture(:stderr) { run_generator ["Account"], behavior: :revoke } assert_no_match(/Another migration is already named create_accounts/, error) end @@ -292,7 +300,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase assert_file "test/fixtures/accounts.yml", /name: MyString/, /age: 1/ assert_generated_fixture("test/fixtures/accounts.yml", - {"one"=>{"name"=>"MyString", "age"=>1}, "two"=>{"name"=>"MyString", "age"=>1}}) + "one"=>{ "name"=>"MyString", "age"=>1 }, "two"=>{ "name"=>"MyString", "age"=>1 }) end def test_fixtures_use_the_references_ids @@ -300,7 +308,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase assert_file "test/fixtures/line_items.yml", /product: one\n cart: one/ assert_generated_fixture("test/fixtures/line_items.yml", - {"one"=>{"product"=>"one", "cart"=>"one"}, "two"=>{"product"=>"two", "cart"=>"two"}}) + "one"=>{ "product"=>"one", "cart"=>"one" }, "two"=>{ "product"=>"two", "cart"=>"two" }) end def test_fixtures_use_the_references_ids_and_type @@ -308,15 +316,15 @@ class ModelGeneratorTest < Rails::Generators::TestCase assert_file "test/fixtures/line_items.yml", /product: one\n product_type: Product\n cart: one/ assert_generated_fixture("test/fixtures/line_items.yml", - {"one"=>{"product"=>"one", "product_type"=>"Product", "cart"=>"one"}, - "two"=>{"product"=>"two", "product_type"=>"Product", "cart"=>"two"}}) + "one"=>{ "product"=>"one", "product_type"=>"Product", "cart"=>"one" }, + "two"=>{ "product"=>"two", "product_type"=>"Product", "cart"=>"two" }) end def test_fixtures_respect_reserved_yml_keywords run_generator ["LineItem", "no:integer", "Off:boolean", "ON:boolean"] assert_generated_fixture("test/fixtures/line_items.yml", - {"one"=>{"no"=>1, "Off"=>false, "ON"=>false}, "two"=>{"no"=>1, "Off"=>false, "ON"=>false}}) + "one"=>{ "no"=>1, "Off"=>false, "ON"=>false }, "two"=>{ "no"=>1, "Off"=>false, "ON"=>false }) end def test_fixture_is_skipped @@ -335,13 +343,13 @@ class ModelGeneratorTest < Rails::Generators::TestCase ActiveRecord::Base.pluralize_table_names = false run_generator assert_generated_fixture("test/fixtures/account.yml", - {"one"=>{"name"=>"MyString", "age"=>1}, "two"=>{"name"=>"MyString", "age"=>1}}) + "one"=>{ "name"=>"MyString", "age"=>1 }, "two"=>{ "name"=>"MyString", "age"=>1 }) ensure ActiveRecord::Base.pluralize_table_names = original_pluralize_table_name end def test_check_class_collision - content = capture(:stderr){ run_generator ["object"] } + content = capture(:stderr) { run_generator ["object"] } assert_match(/The name 'Object' is either already used in your application or reserved/, content) end @@ -378,7 +386,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase run_generator ["account", "supplier:references{required}"] expected_file = <<-FILE.strip_heredoc - class Account < ActiveRecord::Base + class Account < ApplicationRecord belongs_to :supplier, required: true end FILE @@ -389,7 +397,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase run_generator ["account", "supplier:references{required,polymorphic}"] expected_file = <<-FILE.strip_heredoc - class Account < ActiveRecord::Base + class Account < ApplicationRecord belongs_to :supplier, polymorphic: true, required: true end FILE @@ -400,7 +408,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase run_generator ["account", "supplier:references{polymorphic.required}"] expected_file = <<-FILE.strip_heredoc - class Account < ActiveRecord::Base + class Account < ApplicationRecord belongs_to :supplier, polymorphic: true, required: true end FILE @@ -451,7 +459,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase def test_token_option_adds_has_secure_token run_generator ["user", "token:token", "auth_token:token"] expected_file = <<-FILE.strip_heredoc - class User < ActiveRecord::Base + class User < ApplicationRecord has_secure_token has_secure_token :auth_token end diff --git a/railties/test/generators/named_base_test.rb b/railties/test/generators/named_base_test.rb index 291f5e06c3..0258b3b9d7 100644 --- a/railties/test/generators/named_base_test.rb +++ b/railties/test/generators/named_base_test.rb @@ -1,84 +1,84 @@ -require 'generators/generators_test_helper' -require 'rails/generators/rails/scaffold_controller/scaffold_controller_generator' +require "generators/generators_test_helper" +require "rails/generators/rails/scaffold_controller/scaffold_controller_generator" class NamedBaseTest < Rails::Generators::TestCase include GeneratorsTestHelper tests Rails::Generators::ScaffoldControllerGenerator def test_named_generator_with_underscore - g = generator ['line_item'] - assert_name g, 'line_item', :name + g = generator ["line_item"] + assert_name g, "line_item", :name assert_name g, %w(), :class_path - assert_name g, 'LineItem', :class_name - assert_name g, 'line_item', :file_path - assert_name g, 'line_item', :file_name - assert_name g, 'Line item', :human_name - assert_name g, 'line_item', :singular_name - assert_name g, 'line_items', :plural_name - assert_name g, 'line_item', :i18n_scope - assert_name g, 'line_items', :table_name + assert_name g, "LineItem", :class_name + assert_name g, "line_item", :file_path + assert_name g, "line_item", :file_name + assert_name g, "Line item", :human_name + assert_name g, "line_item", :singular_name + assert_name g, "line_items", :plural_name + assert_name g, "line_item", :i18n_scope + assert_name g, "line_items", :table_name end def test_named_generator_attributes - g = generator ['admin/foo'] - assert_name g, 'admin/foo', :name + g = generator ["admin/foo"] + assert_name g, "admin/foo", :name assert_name g, %w(admin), :class_path - assert_name g, 'Admin::Foo', :class_name - assert_name g, 'admin/foo', :file_path - assert_name g, 'foo', :file_name - assert_name g, 'Foo', :human_name - assert_name g, 'foo', :singular_name - assert_name g, 'foos', :plural_name - assert_name g, 'admin.foo', :i18n_scope - assert_name g, 'admin_foos', :table_name + assert_name g, "Admin::Foo", :class_name + assert_name g, "admin/foo", :file_path + assert_name g, "foo", :file_name + assert_name g, "Foo", :human_name + assert_name g, "foo", :singular_name + assert_name g, "foos", :plural_name + assert_name g, "admin.foo", :i18n_scope + assert_name g, "admin_foos", :table_name end def test_named_generator_attributes_as_ruby - g = generator ['Admin::Foo'] - assert_name g, 'Admin::Foo', :name + g = generator ["Admin::Foo"] + assert_name g, "Admin::Foo", :name assert_name g, %w(admin), :class_path - assert_name g, 'Admin::Foo', :class_name - assert_name g, 'admin/foo', :file_path - assert_name g, 'foo', :file_name - assert_name g, 'foo', :singular_name - assert_name g, 'Foo', :human_name - assert_name g, 'foos', :plural_name - assert_name g, 'admin.foo', :i18n_scope - assert_name g, 'admin_foos', :table_name + assert_name g, "Admin::Foo", :class_name + assert_name g, "admin/foo", :file_path + assert_name g, "foo", :file_name + assert_name g, "foo", :singular_name + assert_name g, "Foo", :human_name + assert_name g, "foos", :plural_name + assert_name g, "admin.foo", :i18n_scope + assert_name g, "admin_foos", :table_name end def test_named_generator_attributes_without_pluralized original_pluralize_table_names = ActiveRecord::Base.pluralize_table_names ActiveRecord::Base.pluralize_table_names = false - g = generator ['admin/foo'] - assert_name g, 'admin_foo', :table_name + g = generator ["admin/foo"] + assert_name g, "admin_foo", :table_name ensure ActiveRecord::Base.pluralize_table_names = original_pluralize_table_names end def test_scaffold_plural_names - g = generator ['admin/foo'] - assert_name g, 'admin/foos', :controller_name + g = generator ["admin/foo"] + assert_name g, "admin/foos", :controller_name assert_name g, %w(admin), :controller_class_path - assert_name g, 'Admin::Foos', :controller_class_name - assert_name g, 'admin/foos', :controller_file_path - assert_name g, 'foos', :controller_file_name - assert_name g, 'admin.foos', :controller_i18n_scope + assert_name g, "Admin::Foos", :controller_class_name + assert_name g, "admin/foos", :controller_file_path + assert_name g, "foos", :controller_file_name + assert_name g, "admin.foos", :controller_i18n_scope end def test_scaffold_plural_names_as_ruby - g = generator ['Admin::Foo'] - assert_name g, 'Admin::Foos', :controller_name + g = generator ["Admin::Foo"] + assert_name g, "Admin::Foos", :controller_name assert_name g, %w(admin), :controller_class_path - assert_name g, 'Admin::Foos', :controller_class_name - assert_name g, 'admin/foos', :controller_file_path - assert_name g, 'foos', :controller_file_name - assert_name g, 'admin.foos', :controller_i18n_scope + assert_name g, "Admin::Foos", :controller_class_name + assert_name g, "admin/foos", :controller_file_path + assert_name g, "foos", :controller_file_name + assert_name g, "admin.foos", :controller_i18n_scope end def test_application_name - g = generator ['Admin::Foo'] + g = generator ["Admin::Foo"] Rails.stub(:application, Object.new) do assert_name g, "object", :application_name end @@ -89,46 +89,46 @@ class NamedBaseTest < Rails::Generators::TestCase end def test_index_helper - g = generator ['Post'] - assert_name g, 'posts', :index_helper + g = generator ["Post"] + assert_name g, "posts", :index_helper end def test_index_helper_to_pluralize_once - g = generator ['Stadium'] - assert_name g, 'stadia', :index_helper + g = generator ["Stadium"] + assert_name g, "stadia", :index_helper end def test_index_helper_with_uncountable - g = generator ['Sheep'] - assert_name g, 'sheep_index', :index_helper + g = generator ["Sheep"] + assert_name g, "sheep_index", :index_helper end def test_hide_namespace - g = generator ['Hidden'] - g.class.stub(:namespace, 'hidden') do - assert !Rails::Generators.hidden_namespaces.include?('hidden') + g = generator ["Hidden"] + g.class.stub(:namespace, "hidden") do + assert_not_includes Rails::Generators.hidden_namespaces, "hidden" g.class.hide! - assert Rails::Generators.hidden_namespaces.include?('hidden') + assert_includes Rails::Generators.hidden_namespaces, "hidden" end end def test_scaffold_plural_names_with_model_name_option - g = generator ['Admin::Foo'], model_name: 'User' - assert_name g, 'user', :singular_name - assert_name g, 'User', :name - assert_name g, 'user', :file_path - assert_name g, 'User', :class_name - assert_name g, 'user', :file_name - assert_name g, 'User', :human_name - assert_name g, 'users', :plural_name - assert_name g, 'user', :i18n_scope - assert_name g, 'users', :table_name - assert_name g, 'Admin::Foos', :controller_name + g = generator ["Admin::Foo"], model_name: "User" + assert_name g, "user", :singular_name + assert_name g, "User", :name + assert_name g, "user", :file_path + assert_name g, "User", :class_name + assert_name g, "user", :file_name + assert_name g, "User", :human_name + assert_name g, "users", :plural_name + assert_name g, "user", :i18n_scope + assert_name g, "users", :table_name + assert_name g, "Admin::Foos", :controller_name assert_name g, %w(admin), :controller_class_path - assert_name g, 'Admin::Foos', :controller_class_name - assert_name g, 'admin/foos', :controller_file_path - assert_name g, 'foos', :controller_file_name - assert_name g, 'admin.foos', :controller_i18n_scope + assert_name g, "Admin::Foos", :controller_class_name + assert_name g, "admin/foos", :controller_file_path + assert_name g, "foos", :controller_file_name + assert_name g, "admin.foos", :controller_i18n_scope end protected diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb index d76759a7d1..1caabbe6b1 100644 --- a/railties/test/generators/namespaced_generators_test.rb +++ b/railties/test/generators/namespaced_generators_test.rb @@ -1,8 +1,8 @@ -require 'generators/generators_test_helper' -require 'rails/generators/rails/controller/controller_generator' -require 'rails/generators/rails/model/model_generator' -require 'rails/generators/mailer/mailer_generator' -require 'rails/generators/rails/scaffold/scaffold_generator' +require "generators/generators_test_helper" +require "rails/generators/rails/controller/controller_generator" +require "rails/generators/rails/model/model_generator" +require "rails/generators/mailer/mailer_generator" +require "rails/generators/rails/scaffold/scaffold_generator" class NamespacedGeneratorTestCase < Rails::Generators::TestCase include GeneratorsTestHelper @@ -91,7 +91,7 @@ class NamespacedModelGeneratorTest < NamespacedGeneratorTestCase def test_adds_namespace_to_model run_generator - assert_file "app/models/test_app/account.rb", /module TestApp/, / class Account < ActiveRecord::Base/ + assert_file "app/models/test_app/account.rb", /module TestApp/, / class Account < ApplicationRecord/ end def test_model_with_namespace @@ -99,7 +99,7 @@ class NamespacedModelGeneratorTest < NamespacedGeneratorTestCase assert_file "app/models/test_app/admin.rb", /module TestApp/, /module Admin/ assert_file "app/models/test_app/admin.rb", /def self\.table_name_prefix/ assert_file "app/models/test_app/admin.rb", /'test_app_admin_'/ - assert_file "app/models/test_app/admin/account.rb", /module TestApp/, /class Admin::Account < ActiveRecord::Base/ + assert_file "app/models/test_app/admin/account.rb", /module TestApp/, /class Admin::Account < ApplicationRecord/ end def test_migration @@ -201,7 +201,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase run_generator # Model - assert_file "app/models/test_app/product_line.rb", /module TestApp\n class ProductLine < ActiveRecord::Base/ + assert_file "app/models/test_app/product_line.rb", /module TestApp\n class ProductLine < ApplicationRecord/ assert_file "test/models/test_app/product_line_test.rb", /module TestApp\n class ProductLineTest < ActiveSupport::TestCase/ assert_file "test/fixtures/test_app/product_lines.yml" assert_migration "db/migrate/create_test_app_product_lines.rb" @@ -268,7 +268,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase # Model assert_file "app/models/test_app/admin.rb", /module TestApp\n module Admin/ - assert_file "app/models/test_app/admin/role.rb", /module TestApp\n class Admin::Role < ActiveRecord::Base/ + assert_file "app/models/test_app/admin/role.rb", /module TestApp\n class Admin::Role < ApplicationRecord/ assert_file "test/models/test_app/admin/role_test.rb", /module TestApp\n class Admin::RoleTest < ActiveSupport::TestCase/ assert_file "test/fixtures/test_app/admin/roles.yml" assert_migration "db/migrate/create_test_app_admin_roles.rb" @@ -336,7 +336,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase # Model assert_file "app/models/test_app/admin/user/special.rb", /module TestApp\n module Admin/ - assert_file "app/models/test_app/admin/user/special/role.rb", /module TestApp\n class Admin::User::Special::Role < ActiveRecord::Base/ + assert_file "app/models/test_app/admin/user/special/role.rb", /module TestApp\n class Admin::User::Special::Role < ApplicationRecord/ assert_file "test/models/test_app/admin/user/special/role_test.rb", /module TestApp\n class Admin::User::Special::RoleTest < ActiveSupport::TestCase/ assert_file "test/fixtures/test_app/admin/user/special/roles.yml" assert_migration "db/migrate/create_test_app_admin_user_special_roles.rb" @@ -402,7 +402,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase # Model assert_file "app/models/test_app/admin.rb", /module TestApp\n module Admin/ - assert_file "app/models/test_app/admin/role.rb", /module TestApp\n class Admin::Role < ActiveRecord::Base/ + assert_file "app/models/test_app/admin/role.rb", /module TestApp\n class Admin::Role < ApplicationRecord/ assert_file "test/models/test_app/admin/role_test.rb", /module TestApp\n class Admin::RoleTest < ActiveSupport::TestCase/ assert_file "test/fixtures/test_app/admin/roles.yml" assert_migration "db/migrate/create_test_app_admin_roles.rb" diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index 4111a30664..15079f2735 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -1,6 +1,6 @@ -require 'generators/generators_test_helper' -require 'rails/generators/rails/plugin/plugin_generator' -require 'generators/shared_generator_tests' +require "generators/generators_test_helper" +require "rails/generators/rails/plugin/plugin_generator" +require "generators/shared_generator_tests" DEFAULT_PLUGIN_FILES = %w( .gitignore @@ -27,19 +27,19 @@ class PluginGeneratorTest < Rails::Generators::TestCase include SharedGeneratorTests def test_invalid_plugin_name_raises_an_error - content = capture(:stderr){ run_generator [File.join(destination_root, "my_plugin-31fr-extension")] } + content = capture(:stderr) { run_generator [File.join(destination_root, "my_plugin-31fr-extension")] } assert_equal "Invalid plugin name my_plugin-31fr-extension. Please give a name which does not contain a namespace starting with numeric characters.\n", content - content = capture(:stderr){ run_generator [File.join(destination_root, "things4.3")] } + content = capture(:stderr) { run_generator [File.join(destination_root, "things4.3")] } assert_equal "Invalid plugin name things4.3. Please give a name which uses only alphabetic, numeric, \"_\" or \"-\" characters.\n", content - content = capture(:stderr){ run_generator [File.join(destination_root, "43things")] } + content = capture(:stderr) { run_generator [File.join(destination_root, "43things")] } assert_equal "Invalid plugin name 43things. Please give a name which does not start with numbers.\n", content - content = capture(:stderr){ run_generator [File.join(destination_root, "plugin")] } + content = capture(:stderr) { run_generator [File.join(destination_root, "plugin")] } assert_equal "Invalid plugin name plugin. Please give a name which does not match one of the reserved rails words: application, destroy, plugin, runner, test\n", content - content = capture(:stderr){ run_generator [File.join(destination_root, "Digest")] } + content = capture(:stderr) { run_generator [File.join(destination_root, "Digest")] } assert_equal "Invalid plugin name Digest, constant Digest is already in use. Please choose another plugin name.\n", content end @@ -68,8 +68,8 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_match(/Rails::TestUnitReporter\.executable = 'bin\/test'/, content) end assert_file "test/bukkits_test.rb", /assert_kind_of Module, Bukkits/ - assert_file 'bin/test' - assert_no_file 'bin/rails' + assert_file "bin/test" + assert_no_file "bin/rails" end def test_generating_test_files_in_full_mode @@ -80,7 +80,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase end def test_inclusion_of_a_debugger - run_generator [destination_root, '--full'] + run_generator [destination_root, "--full"] if defined?(JRUBY_VERSION) || RUBY_ENGINE == "rbx" assert_file "Gemfile" do |content| assert_no_match(/byebug/, content) @@ -101,7 +101,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase end def test_generating_adds_dummy_app_rake_tasks_without_unit_test_files - run_generator [destination_root, "-T", "--mountable", '--dummy-path', 'my_dummy_app'] + run_generator [destination_root, "-T", "--mountable", "--dummy-path", "my_dummy_app"] assert_file "Rakefile", /APP_RAKEFILE/ end @@ -193,13 +193,24 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "test/dummy/config/database.yml", /postgres/ end - def test_generation_runs_bundle_install_with_full_and_mountable - result = run_generator [destination_root, "--mountable", "--full", "--dev"] - assert_match(/run bundle install/, result) - assert $?.success?, "Command failed: #{result}" - assert_file "#{destination_root}/Gemfile.lock" do |contents| - assert_match(/bukkits/, contents) - end + def test_generation_runs_bundle_install + assert_generates_without_bundler + end + + def test_dev_option + assert_generates_without_bundler(dev: true) + rails_path = File.expand_path("../../..", Rails.root) + assert_file "Gemfile", /^gem\s+["']rails["'],\s+path:\s+["']#{Regexp.escape(rails_path)}["']$/ + end + + def test_edge_option + assert_generates_without_bundler(edge: true) + assert_file "Gemfile", %r{^gem\s+["']rails["'],\s+github:\s+["']#{Regexp.escape("rails/rails")}["']$} + end + + def test_generation_does_not_run_bundle_install_with_full_and_mountable + assert_generates_without_bundler(mountable: true, full: true, dev: true) + assert_no_file "#{destination_root}/Gemfile.lock" end def test_skipping_javascripts_without_mountable_option @@ -225,21 +236,21 @@ class PluginGeneratorTest < Rails::Generators::TestCase def test_ensure_that_tests_work run_generator FileUtils.cd destination_root - quietly { system 'bundle install' } + quietly { system "bundle install" } assert_match(/1 runs, 1 assertions, 0 failures, 0 errors/, `bin/test 2>&1`) end def test_ensure_that_tests_works_in_full_mode run_generator [destination_root, "--full", "--skip_active_record"] FileUtils.cd destination_root - quietly { system 'bundle install' } + quietly { system "bundle install" } assert_match(/1 runs, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test 2>&1`) end def test_ensure_that_migration_tasks_work_with_mountable_option run_generator [destination_root, "--mountable"] FileUtils.cd destination_root - quietly { system 'bundle install' } + quietly { system "bundle install" } output = `bin/rails db:migrate 2>&1` assert $?.success?, "Command failed: #{output}" end @@ -322,7 +333,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_match(/ActionDispatch::IntegrationTest\.fixture_path = ActiveSupport::TestCase\.fixture_pat/, content) assert_no_match(/Rails::TestUnitReporter\.executable = 'bin\/test'/, content) end - assert_no_file 'bin/test' + assert_no_file "bin/test" end def test_create_mountable_application_with_mountable_option_and_hypenated_name @@ -439,11 +450,19 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "spec/dummy/config/application.rb" assert_no_file "test" assert_no_file "test/test_helper.rb" - assert_file '.gitignore' do |contents| + assert_file ".gitignore" do |contents| assert_match(/spec\/dummy/, contents) end end + def test_dummy_appplication_skip_listen_by_default + run_generator + + assert_file "test/dummy/config/environments/development.rb" do |contents| + assert_match(/^\s*# config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, contents) + end + end + def test_ensure_that_gitignore_can_be_generated_from_a_template_for_dummy_path FileUtils.cd(Rails.root) run_generator([destination_root, "--dummy_path", "spec/dummy", "--skip-test"]) @@ -454,21 +473,21 @@ class PluginGeneratorTest < Rails::Generators::TestCase def test_unnecessary_files_are_not_generated_in_dummy_application run_generator - assert_no_file 'test/dummy/.gitignore' - assert_no_file 'test/dummy/db/seeds.rb' - assert_no_file 'test/dummy/Gemfile' - assert_no_file 'test/dummy/public/robots.txt' - assert_no_file 'test/dummy/README.md' - assert_no_directory 'test/dummy/lib/tasks' - assert_no_directory 'test/dummy/doc' - assert_no_directory 'test/dummy/test' - assert_no_directory 'test/dummy/vendor' + assert_no_file "test/dummy/.gitignore" + assert_no_file "test/dummy/db/seeds.rb" + assert_no_file "test/dummy/Gemfile" + assert_no_file "test/dummy/public/robots.txt" + assert_no_file "test/dummy/README.md" + assert_no_directory "test/dummy/lib/tasks" + assert_no_directory "test/dummy/doc" + assert_no_directory "test/dummy/test" + assert_no_directory "test/dummy/vendor" end def test_skipping_test_files run_generator [destination_root, "--skip-test"] assert_no_file "test" - assert_file '.gitignore' do |contents| + assert_file ".gitignore" do |contents| assert_no_match(/test\dummy/, contents) end end @@ -477,7 +496,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase run_generator [destination_root, "--skip-gemspec"] assert_no_file "bukkits.gemspec" assert_file "Gemfile" do |contents| - assert_no_match('gemspec', contents) + assert_no_match("gemspec", contents) assert_match(/gem 'rails'/, contents) assert_match_sqlite3(contents) assert_no_match(/# gem "jquery-rails"/, contents) @@ -488,7 +507,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase run_generator [destination_root, "--skip-gemspec", "--full"] assert_no_file "bukkits.gemspec" assert_file "Gemfile" do |contents| - assert_no_match('gemspec', contents) + assert_no_match("gemspec", contents) assert_match(/gem 'rails'/, contents) assert_match_sqlite3(contents) end @@ -497,21 +516,21 @@ class PluginGeneratorTest < Rails::Generators::TestCase def test_creating_plugin_in_app_directory_adds_gemfile_entry # simulate application existence gemfile_path = "#{Rails.root}/Gemfile" - Object.const_set('APP_PATH', Rails.root) + Object.const_set("APP_PATH", Rails.root) FileUtils.touch gemfile_path run_generator assert_file gemfile_path, /gem 'bukkits', path: 'tmp\/bukkits'/ ensure - Object.send(:remove_const, 'APP_PATH') + Object.send(:remove_const, "APP_PATH") FileUtils.rm gemfile_path end def test_skipping_gemfile_entry # simulate application existence gemfile_path = "#{Rails.root}/Gemfile" - Object.const_set('APP_PATH', Rails.root) + Object.const_set("APP_PATH", Rails.root) FileUtils.touch gemfile_path run_generator [destination_root, "--skip-gemfile-entry"] @@ -520,7 +539,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_no_match(/gem 'bukkits', path: 'tmp\/bukkits'/, contents) end ensure - Object.send(:remove_const, 'APP_PATH') + Object.send(:remove_const, "APP_PATH") FileUtils.rm gemfile_path end @@ -561,7 +580,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase name = "TODO: Write your name" email = "TODO: Write your email address" - run_generator [destination_root, '--skip-git'] + run_generator [destination_root, "--skip-git"] assert_file "MIT-LICENSE" do |contents| assert_match name, contents end @@ -572,8 +591,8 @@ class PluginGeneratorTest < Rails::Generators::TestCase end def test_skipping_useless_folders_generation_for_api_engines - ['--full', '--mountable'].each do |option| - run_generator [destination_root, option, '--api'] + ["--full", "--mountable"].each do |option| + run_generator [destination_root, option, "--api"] assert_no_directory "app/assets" assert_no_directory "app/helpers" @@ -584,7 +603,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase end def test_application_controller_parent_for_mountable_api_plugins - run_generator [destination_root, '--mountable', '--api'] + run_generator [destination_root, "--mountable", "--api"] assert_file "app/controllers/bukkits/application_controller.rb" do |content| assert_match "ApplicationController < ActionController::API", content @@ -592,16 +611,15 @@ class PluginGeneratorTest < Rails::Generators::TestCase end def test_dummy_api_application_for_api_plugins - run_generator [destination_root, '--api'] + run_generator [destination_root, "--api"] assert_file "test/dummy/config/application.rb" do |content| assert_match "config.api_only = true", content end end - def test_api_generators_configuration_for_api_engines - run_generator [destination_root, '--full', '--api'] + run_generator [destination_root, "--full", "--api"] assert_file "lib/bukkits/engine.rb" do |content| assert_match "config.generators.api_only = true", content @@ -609,7 +627,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase end def test_scaffold_generator_for_mountable_api_plugins - run_generator [destination_root, '--mountable', '--api'] + run_generator [destination_root, "--mountable", "--api"] capture(:stdout) do `#{destination_root}/bin/rails g scaffold article` @@ -626,7 +644,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase end def test_model_with_existent_application_record_in_mountable_engine - run_generator [destination_root, '--mountable'] + run_generator [destination_root, "--mountable"] capture(:stdout) do `#{destination_root}/bin/rails g model article` end @@ -634,48 +652,93 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "app/models/bukkits/article.rb", /class Article < ApplicationRecord/ end - def test_after_bundle_callback - path = 'http://example.org/rails_template' - template = %{ after_bundle { run 'echo ran after_bundle' } } - template.instance_eval "def read; self; end" # Make the string respond to read - - check_open = -> *args do - assert_equal [ path, 'Accept' => 'application/x-thor-template' ], args - template + def test_generate_application_record_when_does_not_exist_in_mountable_engine + run_generator [destination_root, "--mountable"] + FileUtils.rm "#{destination_root}/app/models/bukkits/application_record.rb" + capture(:stdout) do + `#{destination_root}/bin/rails g model article` end - sequence = ['install', '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}" - @sequence_step += 1 + assert_file "#{destination_root}/app/models/bukkits/application_record.rb" do |record| + assert_match(/module Bukkits/, record) + assert_match(/class ApplicationRecord < ActiveRecord::Base/, record) + assert_match(/self.abstract_class = true/, record) end + end - generator([destination_root], template: path).stub(:open, check_open, template) do - generator.stub(:bundle_command, ensure_bundler_first) do - generator.stub(:run, ensure_bundler_first) do - quietly { generator.invoke_all } - end - end + def test_generate_application_mailer_when_does_not_exist_in_mountable_engine + run_generator [destination_root, "--mountable"] + FileUtils.rm "#{destination_root}/app/mailers/bukkits/application_mailer.rb" + capture(:stdout) do + `#{destination_root}/bin/rails g mailer User` end - assert_equal 2, @sequence_step + assert_file "#{destination_root}/app/mailers/bukkits/application_mailer.rb" do |mailer| + assert_match(/module Bukkits/, mailer) + assert_match(/class ApplicationMailer < ActionMailer::Base/, mailer) + end end -protected - def action(*args, &block) - silence(:stdout){ generator.send(*args, &block) } - end + def test_generate_mailer_layouts_when_does_not_exist_in_mountable_engine + run_generator [destination_root, "--mountable"] + capture(:stdout) do + `#{destination_root}/bin/rails g mailer User` + end - def default_files - ::DEFAULT_PLUGIN_FILES + assert_file "#{destination_root}/app/views/layouts/bukkits/mailer.text.erb" do |view| + assert_match(/<%= yield %>/, view) + end + + assert_file "#{destination_root}/app/views/layouts/bukkits/mailer.html.erb" do |view| + assert_match(%r{<body>\n <%= yield %>\n </body>}, view) + end end - def assert_match_sqlite3(contents) - if defined?(JRUBY_VERSION) - assert_match(/group :development do\n gem 'activerecord-jdbcsqlite3-adapter'\nend/, contents) - else - assert_match(/group :development do\n gem 'sqlite3'\nend/, contents) + def test_generate_application_job_when_does_not_exist_in_mountable_engine + run_generator [destination_root, "--mountable"] + FileUtils.rm "#{destination_root}/app/jobs/bukkits/application_job.rb" + capture(:stdout) do + `#{destination_root}/bin/rails g job refresh_counters` + end + + assert_file "#{destination_root}/app/jobs/bukkits/application_job.rb" do |record| + assert_match(/module Bukkits/, record) + assert_match(/class ApplicationJob < ActiveJob::Base/, record) end end + + protected + + def action(*args, &block) + silence(:stdout) { generator.send(*args, &block) } + end + + def default_files + ::DEFAULT_PLUGIN_FILES + end + + def assert_match_sqlite3(contents) + if defined?(JRUBY_VERSION) + assert_match(/group :development do\n gem 'activerecord-jdbcsqlite3-adapter'\nend/, contents) + else + assert_match(/group :development do\n gem 'sqlite3'\nend/, contents) + end + end + + def assert_generates_without_bundler(options = {}) + generator([destination_root], options) + + command_check = -> command do + case command + when "install" + flunk "install expected to not be called" + when "exec spring binstub --all" + # Called when running tests with spring, let through unscathed. + end + end + + generator.stub :bundle_command, command_check do + quietly { generator.invoke_all } + end + end end diff --git a/railties/test/generators/plugin_test_helper.rb b/railties/test/generators/plugin_test_helper.rb index 96c1b1d31f..8ac90e3484 100644 --- a/railties/test/generators/plugin_test_helper.rb +++ b/railties/test/generators/plugin_test_helper.rb @@ -1,5 +1,5 @@ -require 'abstract_unit' -require 'tmpdir' +require "abstract_unit" +require "tmpdir" module PluginTestHelper def create_test_file(name, pass: true) @@ -15,7 +15,7 @@ module PluginTestHelper RUBY end - def plugin_file(path, contents, mode: 'w') + def plugin_file(path, contents, mode: "w") FileUtils.mkdir_p File.dirname("#{plugin_path}/#{path}") File.open("#{plugin_path}/#{path}", mode) do |f| f.puts contents diff --git a/railties/test/generators/plugin_test_runner_test.rb b/railties/test/generators/plugin_test_runner_test.rb index f492cd49ef..7a10a2afa9 100644 --- a/railties/test/generators/plugin_test_runner_test.rb +++ b/railties/test/generators/plugin_test_runner_test.rb @@ -1,12 +1,12 @@ -require 'generators/plugin_test_helper' +require "generators/plugin_test_helper" class PluginTestRunnerTest < ActiveSupport::TestCase include PluginTestHelper def setup - @destination_root = Dir.mktmpdir('bukkits') + @destination_root = Dir.mktmpdir("bukkits") Dir.chdir(@destination_root) { `bundle exec rails plugin new bukkits --skip-bundle` } - plugin_file 'test/dummy/db/schema.rb', '' + plugin_file "test/dummy/db/schema.rb", "" end def teardown @@ -14,20 +14,20 @@ class PluginTestRunnerTest < ActiveSupport::TestCase end def test_run_single_file - create_test_file 'foo' - create_test_file 'bar' + create_test_file "foo" + create_test_file "bar" assert_match "1 runs, 1 assertions, 0 failures", run_test_command("test/foo_test.rb") end def test_run_multiple_files - create_test_file 'foo' - create_test_file 'bar' + create_test_file "foo" + create_test_file "bar" assert_match "2 runs, 2 assertions, 0 failures", run_test_command("test/foo_test.rb test/bar_test.rb") end def test_mix_files_and_line_filters - create_test_file 'account' - plugin_file 'test/post_test.rb', <<-RUBY + create_test_file "account" + plugin_file "test/post_test.rb", <<-RUBY require 'test_helper' class PostTest < ActiveSupport::TestCase @@ -42,50 +42,56 @@ class PluginTestRunnerTest < ActiveSupport::TestCase end RUBY - run_test_command('test/account_test.rb test/post_test.rb:4').tap do |output| - assert_match 'AccountTest', output - assert_match 'PostTest', output - assert_match '2 runs, 2 assertions', output + run_test_command("test/account_test.rb test/post_test.rb:4").tap do |output| + assert_match "AccountTest", output + assert_match "PostTest", output + assert_match "2 runs, 2 assertions", output end end def test_multiple_line_filters - create_test_file 'account' - create_test_file 'post' + create_test_file "account" + create_test_file "post" - run_test_command('test/account_test.rb:4 test/post_test.rb:4').tap do |output| - assert_match 'AccountTest', output - assert_match 'PostTest', output + run_test_command("test/account_test.rb:4 test/post_test.rb:4").tap do |output| + assert_match "AccountTest", output + assert_match "PostTest", output end end def test_output_inline_by_default - create_test_file 'post', pass: false + create_test_file "post", pass: false - output = run_test_command('test/post_test.rb') - expect = %r{Running:\n\nPostTest\nF\n\nFailure:\nPostTest#test_truth:\nwups!\n\nbin/test (/private)?#{plugin_path}/test/post_test.rb:6} + output = run_test_command("test/post_test.rb") + expect = %r{Running:\n\nPostTest\nF\n\nFailure:\nPostTest#test_truth \[[^\]]+test/post_test.rb:6\]:\nwups!\n\nbin/test (/private)?#{plugin_path}/test/post_test.rb:4} assert_match expect, output end def test_only_inline_failure_output - create_test_file 'post', pass: false + create_test_file "post", pass: false - output = run_test_command('test/post_test.rb') + output = run_test_command("test/post_test.rb") assert_match %r{Finished in.*\n\n1 runs, 1 assertions}, output end def test_fail_fast - create_test_file 'post', pass: false + create_test_file "post", pass: false assert_match(/Interrupt/, - capture(:stderr) { run_test_command('test/post_test.rb --fail-fast') }) + capture(:stderr) { run_test_command("test/post_test.rb --fail-fast") }) end def test_raise_error_when_specified_file_does_not_exist - error = capture(:stderr) { run_test_command('test/not_exists.rb') } + error = capture(:stderr) { run_test_command("test/not_exists.rb") } assert_match(%r{cannot load such file.+test/not_exists\.rb}, error) end + def test_executed_only_once + create_test_file "foo" + result = run_test_command("test/foo_test.rb") + assert_equal 1, result.scan(/1 runs, 1 assertions, 0 failures/).length + end + private def plugin_path "#{@destination_root}/bukkits" diff --git a/railties/test/generators/resource_generator_test.rb b/railties/test/generators/resource_generator_test.rb index addaf83bc8..e976e58180 100644 --- a/railties/test/generators/resource_generator_test.rb +++ b/railties/test/generators/resource_generator_test.rb @@ -1,5 +1,5 @@ -require 'generators/generators_test_helper' -require 'rails/generators/rails/resource/resource_generator' +require "generators/generators_test_helper" +require "rails/generators/rails/resource/resource_generator" class ResourceGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper @@ -60,14 +60,14 @@ class ResourceGeneratorTest < Rails::Generators::TestCase def test_plural_names_are_singularized content = run_generator ["accounts".freeze] - assert_file "app/models/account.rb", /class Account < ActiveRecord::Base/ + assert_file "app/models/account.rb", /class Account < ApplicationRecord/ assert_file "test/models/account_test.rb", /class AccountTest/ assert_match(/\[WARNING\] The model name 'accounts' was recognized as a plural, using the singular 'account' instead\. Override with --force-plural or setup custom inflection rules for this noun before running the generator\./, content) end def test_plural_names_can_be_forced content = run_generator ["accounts", "--force-plural"] - assert_file "app/models/accounts.rb", /class Accounts < ActiveRecord::Base/ + assert_file "app/models/accounts.rb", /class Accounts < ApplicationRecord/ assert_file "test/models/accounts_test.rb", /class AccountsTest/ assert_no_match(/\[WARNING\]/, content) end diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index c37e289f4b..bd23faf268 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -1,5 +1,5 @@ -require 'generators/generators_test_helper' -require 'rails/generators/rails/scaffold_controller/scaffold_controller_generator' +require "generators/generators_test_helper" +require "rails/generators/rails/scaffold_controller/scaffold_controller_generator" module Unknown module Generators @@ -238,8 +238,8 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase assert_file "test/controllers/users_controller_test.rb" do |content| assert_match(/class UsersControllerTest < ActionDispatch::IntegrationTest/, content) assert_match(/test "should get index"/, content) - assert_match(/post users_url, params: \{ user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}/, content) - assert_match(/patch user_url\(@user\), params: \{ user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}/, content) + assert_match(/post users_url, params: \{ user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}, as: :json/, content) + assert_match(/patch user_url\(@user\), params: \{ user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}, as: :json/, content) assert_no_match(/assert_redirected_to/, content) end end diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 5e45120704..6b7e2c91d7 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -1,5 +1,5 @@ -require 'generators/generators_test_helper' -require 'rails/generators/rails/scaffold/scaffold_generator' +require "generators/generators_test_helper" +require "rails/generators/rails/scaffold/scaffold_generator" class ScaffoldGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper @@ -11,7 +11,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase run_generator # Model - assert_file "app/models/product_line.rb", /class ProductLine < ActiveRecord::Base/ + assert_file "app/models/product_line.rb", /class ProductLine < ApplicationRecord/ assert_file "test/models/product_line_test.rb", /class ProductLineTest < ActiveSupport::TestCase/ assert_file "test/fixtures/product_lines.yml" assert_migration "db/migrate/create_product_lines.rb", /belongs_to :product/ @@ -74,8 +74,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end assert_file "app/views/product_lines/_form.html.erb" do |test| - assert_match 'product_line', test - assert_no_match '@product_line', test + assert_match "product_line", test + assert_no_match "@product_line", test end # Helpers @@ -91,7 +91,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase run_generator %w(product_line title:string product:belongs_to user:references --api --no-template-engine --no-helper --no-assets) # Model - assert_file "app/models/product_line.rb", /class ProductLine < ActiveRecord::Base/ + assert_file "app/models/product_line.rb", /class ProductLine < ApplicationRecord/ assert_file "test/models/product_line_test.rb", /class ProductLineTest < ActiveSupport::TestCase/ assert_file "test/fixtures/product_lines.yml" assert_migration "db/migrate/create_product_lines.rb", /belongs_to :product/ @@ -205,7 +205,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase # Model assert_file "app/models/admin.rb", /module Admin/ - assert_file "app/models/admin/role.rb", /class Admin::Role < ActiveRecord::Base/ + assert_file "app/models/admin/role.rb", /class Admin::Role < ApplicationRecord/ assert_file "test/models/admin/role_test.rb", /class Admin::RoleTest < ActiveSupport::TestCase/ assert_file "test/fixtures/admin/roles.yml" assert_migration "db/migrate/create_admin_roles.rb" @@ -269,7 +269,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase def test_scaffold_with_namespace_on_revoke run_generator [ "admin/role", "name:string", "description:string" ] - run_generator [ "admin/role" ], :behavior => :revoke + run_generator [ "admin/role" ], behavior: :revoke # Model assert_file "app/models/admin.rb" # ( should not be remove ) @@ -310,7 +310,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end File.open(route_path, "wb") { |file| file.write(content) } - run_generator ["product_line"], :behavior => :revoke + run_generator ["product_line"], behavior: :revoke assert_file "config/routes.rb", /\.routes\.draw do\s*\|map\|\s*$/ end @@ -322,13 +322,13 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase content = File.read(route_path) # Remove all of the comments and blank lines from the routes file - content.gsub!(/^ \#.*\n/, '') - content.gsub!(/^\n/, '') + content.gsub!(/^ \#.*\n/, "") + content.gsub!(/^\n/, "") File.open(route_path, "wb") { |file| file.write(content) } assert_file "config/routes.rb", /\.routes\.draw do\n resources :product_lines\nend\n\z/ - run_generator ["product_line"], :behavior => :revoke + run_generator ["product_line"], behavior: :revoke assert_file "config/routes.rb", /\.routes\.draw do\nend\n\z/ end @@ -488,7 +488,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase `bin/rails g scaffold User name:string age:integer; bin/rails db:migrate` end - assert_match(/8 runs, 13 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`) + assert_match(/8 runs, 10 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`) end end @@ -502,7 +502,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase `bin/rails g scaffold User name:string age:integer; bin/rails db:migrate` end - assert_match(/8 runs, 13 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`) + assert_match(/8 runs, 10 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`) end end diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb index e83d54890a..27b2fc8955 100644 --- a/railties/test/generators/shared_generator_tests.rb +++ b/railties/test/generators/shared_generator_tests.rb @@ -5,7 +5,7 @@ module SharedGeneratorTests def setup Rails.application = TestApp::Application super - Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) + Rails::Generators::AppGenerator.instance_variable_set("@desc", nil) Kernel::silence_warnings do Thor::Base.shell.send(:attr_accessor, :always_force) @@ -16,7 +16,7 @@ module SharedGeneratorTests def teardown super - Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) + Rails::Generators::AppGenerator.instance_variable_set("@desc", nil) Rails.application = TestApp::Application.instance end @@ -26,37 +26,13 @@ module SharedGeneratorTests default_files.each { |path| assert_file path } end - def assert_generates_with_bundler(options = {}) - generator([destination_root], options) - - command_check = -> command do - @install_called ||= 0 - - case command - when 'install' - @install_called += 1 - assert_equal 1, @install_called, "install expected to be called once, but was called #{@install_called} times" - when 'exec spring binstub --all' - # Called when running tests with spring, let through unscathed. - end - end - - generator.stub :bundle_command, command_check do - quietly { generator.invoke_all } - end - end - - def test_generation_runs_bundle_install - assert_generates_with_bundler - end - def test_plugin_new_generate_pretend run_generator ["testapp", "--pretend"] - default_files.each{ |path| assert_no_file File.join("testapp",path) } + default_files.each { |path| assert_no_file File.join("testapp",path) } end def test_invalid_database_option_raises_an_error - content = capture(:stderr){ run_generator([destination_root, "-d", "unknown"]) } + content = capture(:stderr) { run_generator([destination_root, "-d", "unknown"]) } assert_match(/Invalid value for \-\-database option/, content) end @@ -68,14 +44,14 @@ module SharedGeneratorTests def test_name_collision_raises_an_error reserved_words = %w[application destroy plugin runner test] reserved_words.each do |reserved| - content = capture(:stderr){ run_generator [File.join(destination_root, reserved)] } + content = capture(:stderr) { run_generator [File.join(destination_root, reserved)] } assert_match(/Invalid \w+ name #{reserved}. Please give a name which does not match one of the reserved rails words: application, destroy, plugin, runner, test\n/, content) end end def test_name_raises_an_error_if_name_already_used_constant %w{ String Hash Class Module Set Symbol }.each do |ruby_class| - content = capture(:stderr){ run_generator [File.join(destination_root, ruby_class)] } + content = capture(:stderr) { run_generator [File.join(destination_root, ruby_class)] } assert_match(/Invalid \w+ name #{ruby_class}, constant #{ruby_class} is already in use. Please choose another \w+ name.\n/, content) end end @@ -92,7 +68,7 @@ module SharedGeneratorTests def test_template_raises_an_error_with_invalid_path quietly do - content = capture(:stderr){ run_generator([destination_root, "-m", "non/existent/path"]) } + content = capture(:stderr) { run_generator([destination_root, "-m", "non/existent/path"]) } assert_match(/The template \[.*\] could not be loaded/, content) assert_match(/non\/existent\/path/, content) @@ -105,7 +81,7 @@ module SharedGeneratorTests template.instance_eval "def read; self; end" # Make the string respond to read check_open = -> *args do - assert_equal [ path, 'Accept' => 'application/x-thor-template' ], args + assert_equal [ path, "Accept" => "application/x-thor-template" ], args template end @@ -114,21 +90,10 @@ module SharedGeneratorTests end end - def test_dev_option - assert_generates_with_bundler dev: true - rails_path = File.expand_path('../../..', Rails.root) - assert_file 'Gemfile', /^gem\s+["']rails["'],\s+path:\s+["']#{Regexp.escape(rails_path)}["']$/ - end - - def test_edge_option - assert_generates_with_bundler edge: true - assert_file 'Gemfile', %r{^gem\s+["']rails["'],\s+github:\s+["']#{Regexp.escape("rails/rails")}["']$} - end - def test_skip_gemfile assert_not_called(generator([destination_root], skip_gemfile: true), :bundle_command) do quietly { generator.invoke_all } - assert_no_file 'Gemfile' + assert_no_file "Gemfile" end end @@ -137,22 +102,22 @@ module SharedGeneratorTests quietly { generator.invoke_all } # skip_bundle is only about running bundle install, ensure the Gemfile is still # generated. - assert_file 'Gemfile' + assert_file "Gemfile" end end def test_skip_git - run_generator [destination_root, '--skip-git', '--full'] - assert_no_file('.gitignore') + run_generator [destination_root, "--skip-git", "--full"] + assert_no_file(".gitignore") end def test_skip_keeps - run_generator [destination_root, '--skip-keeps', '--full'] + run_generator [destination_root, "--skip-keeps", "--full"] - assert_file '.gitignore' do |content| + assert_file ".gitignore" do |content| assert_no_match(/\.keep/, content) end - assert_no_file('app/models/concerns/.keep') + assert_no_file("app/models/concerns/.keep") end end diff --git a/railties/test/generators/task_generator_test.rb b/railties/test/generators/task_generator_test.rb index d5bd44b9db..2285534bb9 100644 --- a/railties/test/generators/task_generator_test.rb +++ b/railties/test/generators/task_generator_test.rb @@ -1,5 +1,5 @@ -require 'generators/generators_test_helper' -require 'rails/generators/rails/task/task_generator' +require "generators/generators_test_helper" +require "rails/generators/rails/task/task_generator" class TaskGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper @@ -15,10 +15,10 @@ class TaskGeneratorTest < Rails::Generators::TestCase end def test_task_on_revoke - task_path = 'lib/tasks/feeds.rake' + task_path = "lib/tasks/feeds.rake" run_generator assert_file task_path - run_generator ['feeds'], behavior: :revoke + run_generator ["feeds"], behavior: :revoke assert_no_file task_path end end diff --git a/railties/test/generators/test_runner_in_engine_test.rb b/railties/test/generators/test_runner_in_engine_test.rb index 69906b962b..4b5fb3ba3f 100644 --- a/railties/test/generators/test_runner_in_engine_test.rb +++ b/railties/test/generators/test_runner_in_engine_test.rb @@ -1,12 +1,12 @@ -require 'generators/plugin_test_helper' +require "generators/plugin_test_helper" class TestRunnerInEngineTest < ActiveSupport::TestCase include PluginTestHelper def setup - @destination_root = Dir.mktmpdir('bukkits') + @destination_root = Dir.mktmpdir("bukkits") Dir.chdir(@destination_root) { `bundle exec rails plugin new bukkits --full --skip-bundle` } - plugin_file 'test/dummy/db/schema.rb', '' + plugin_file "test/dummy/db/schema.rb", "" end def teardown @@ -14,10 +14,10 @@ class TestRunnerInEngineTest < ActiveSupport::TestCase end def test_rerun_snippet_is_relative_path - create_test_file 'post', pass: false + create_test_file "post", pass: false - output = run_test_command('test/post_test.rb') - expect = %r{Running:\n\nPostTest\nF\n\nFailure:\nPostTest#test_truth:\nwups!\n\nbin/rails test test/post_test.rb:6} + output = run_test_command("test/post_test.rb") + expect = %r{Running:\n\nPostTest\nF\n\nFailure:\nPostTest#test_truth \[[^\]]+test/post_test.rb:6\]:\nwups!\n\nbin/rails test test/post_test.rb:4} assert_match expect, output end diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb index 291415858c..68ba435393 100644 --- a/railties/test/generators_test.rb +++ b/railties/test/generators_test.rb @@ -1,6 +1,6 @@ -require 'generators/generators_test_helper' -require 'rails/generators/rails/model/model_generator' -require 'rails/generators/test_unit/model/model_generator' +require "generators/generators_test_helper" +require "rails/generators/rails/model/model_generator" +require "rails/generators/test_unit/model/model_generator" class GeneratorsTest < Rails::Generators::TestCase include GeneratorsTestHelper @@ -15,7 +15,7 @@ class GeneratorsTest < Rails::Generators::TestCase end def test_simple_invoke - assert File.exist?(File.join(@path, 'generators', 'model_generator.rb')) + assert File.exist?(File.join(@path, "generators", "model_generator.rb")) assert_called_with(TestUnit::Generators::ModelGenerator, :start, [["Account"], {}]) do Rails::Generators.invoke("test_unit:model", ["Account"]) end @@ -23,32 +23,32 @@ class GeneratorsTest < Rails::Generators::TestCase def test_invoke_when_generator_is_not_found name = :unknown - output = capture(:stdout){ Rails::Generators.invoke name } + output = capture(:stdout) { Rails::Generators.invoke name } assert_match "Could not find generator '#{name}'", output assert_match "`rails generate --help`", output end def test_generator_suggestions name = :migrationz - output = capture(:stdout){ Rails::Generators.invoke name } + output = capture(:stdout) { Rails::Generators.invoke name } assert_match "Maybe you meant 'migration'", output end def test_generator_multiple_suggestions name = :tas - output = capture(:stdout){ Rails::Generators.invoke name } + output = capture(:stdout) { Rails::Generators.invoke name } assert_match "Maybe you meant 'task', 'job' or", output end def test_help_when_a_generator_with_required_arguments_is_invoked_without_arguments - output = capture(:stdout){ Rails::Generators.invoke :model, [] } + output = capture(:stdout) { Rails::Generators.invoke :model, [] } assert_match(/Description:/, output) end def test_should_give_higher_preference_to_rails_generators - assert File.exist?(File.join(@path, 'generators', 'model_generator.rb')) + assert File.exist?(File.join(@path, "generators", "model_generator.rb")) assert_called_with(Rails::Generators::ModelGenerator, :start, [["Account"], {}]) do - warnings = capture(:stderr){ Rails::Generators.invoke :model, ["Account"] } + warnings = capture(:stderr) { Rails::Generators.invoke :model, ["Account"] } assert warnings.empty? end end @@ -108,14 +108,14 @@ class GeneratorsTest < Rails::Generators::TestCase def test_invoke_with_nested_namespaces model_generator = Minitest::Mock.new model_generator.expect(:start, nil, [["Account"], {}]) - assert_called_with(Rails::Generators, :find_by_namespace, ['namespace', 'my:awesome'], returns: model_generator) do - Rails::Generators.invoke 'my:awesome:namespace', ["Account"] + assert_called_with(Rails::Generators, :find_by_namespace, ["namespace", "my:awesome"], returns: model_generator) do + Rails::Generators.invoke "my:awesome:namespace", ["Account"] end model_generator.verify end def test_rails_generators_help_with_builtin_information - output = capture(:stdout){ Rails::Generators.help } + output = capture(:stdout) { Rails::Generators.help } assert_match(/Rails:/, output) assert_match(/^ model$/, output) assert_match(/^ scaffold_controller$/, output) @@ -123,19 +123,19 @@ class GeneratorsTest < Rails::Generators::TestCase end def test_rails_generators_help_does_not_include_app_nor_plugin_new - output = capture(:stdout){ Rails::Generators.help } + output = capture(:stdout) { Rails::Generators.help } assert_no_match(/app/, output) assert_no_match(/[^:]plugin/, output) end def test_rails_generators_with_others_information - output = capture(:stdout){ Rails::Generators.help } + output = capture(:stdout) { Rails::Generators.help } assert_match(/Fixjour:/, output) assert_match(/^ fixjour$/, output) end def test_rails_generators_does_not_show_active_record_hooks - output = capture(:stdout){ Rails::Generators.help } + output = capture(:stdout) { Rails::Generators.help } assert_match(/ActiveRecord:/, output) assert_match(/^ active_record:fixjour$/, output) end @@ -214,7 +214,7 @@ class GeneratorsTest < Rails::Generators::TestCase # Create template mkdir_p(File.dirname(template)) - File.open(template, 'w'){ |f| f.write "empty" } + File.open(template, "w") { |f| f.write "empty" } capture(:stdout) do Rails::Generators.invoke :model, ["user"], destination_root: destination_root @@ -229,18 +229,18 @@ class GeneratorsTest < Rails::Generators::TestCase def test_source_paths_for_not_namespaced_generators mspec = Rails::Generators.find_by_namespace :fixjour - assert mspec.source_paths.include?(File.join(Rails.root, "lib", "templates", "fixjour")) + assert_includes mspec.source_paths, File.join(Rails.root, "lib", "templates", "fixjour") end def test_usage_with_embedded_ruby require File.expand_path("fixtures/lib/generators/usage_template/usage_template_generator", File.dirname(__FILE__)) - output = capture(:stdout) { Rails::Generators.invoke :usage_template, ['--help'] } + output = capture(:stdout) { Rails::Generators.invoke :usage_template, ["--help"] } assert_match(/:: 2 ::/, output) end def test_hide_namespace - assert !Rails::Generators.hidden_namespaces.include?("special:namespace") + assert_not_includes Rails::Generators.hidden_namespaces, "special:namespace" Rails::Generators.hide_namespace("special:namespace") - assert Rails::Generators.hidden_namespaces.include?("special:namespace") + assert_includes Rails::Generators.hidden_namespaces, "special:namespace" end end diff --git a/railties/test/initializable_test.rb b/railties/test/initializable_test.rb index ed9573453b..4b67c91cc5 100644 --- a/railties/test/initializable_test.rb +++ b/railties/test/initializable_test.rb @@ -1,8 +1,7 @@ -require 'abstract_unit' -require 'rails/initializable' +require "abstract_unit" +require "rails/initializable" module InitializableTests - class Foo include Rails::Initializable attr_accessor :foo, :bar @@ -174,6 +173,11 @@ module InitializableTests end end end + + test "Initializer provides context's class name" do + foo = Foo.new + assert_equal foo.class, foo.initializers.first.context_class + end end class BeforeAfter < ActiveSupport::TestCase @@ -215,8 +219,8 @@ module InitializableTests class WithArgsTest < ActiveSupport::TestCase test "running initializers with args" do $with_arg = nil - WithArgs.new.run_initializers(:default, 'foo') - assert_equal 'foo', $with_arg + WithArgs.new.run_initializers(:default, "foo") + assert_equal "foo", $with_arg end end diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index e7a261fa1f..6880cf306a 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -1,31 +1,32 @@ # Note: # It is important to keep this file as light as possible # the goal for tests that require this is to test booting up -# rails from an empty state, so anything added here could +# Rails from an empty state, so anything added here could # hide potential failures # # It is also good to know what is the bare minimum to get # Rails booted up. -require 'fileutils' +require "fileutils" -require 'bundler/setup' unless defined?(Bundler) -require 'active_support' -require 'active_support/testing/autorun' -require 'active_support/testing/stream' -require 'active_support/test_case' +require "bundler/setup" unless defined?(Bundler) +require "active_support" +require "active_support/testing/autorun" +require "active_support/testing/stream" +require "active_support/test_case" RAILS_FRAMEWORK_ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../..") # These files do not require any others and are needed # to run the tests +require "active_support/core_ext/object/blank" require "active_support/testing/isolation" require "active_support/core_ext/kernel/reporting" -require 'tmpdir' +require "tmpdir" module TestHelpers module Paths def app_template_path - File.join Dir.tmpdir, 'app_template' + File.join Dir.tmpdir, "app_template" end def tmp_path(*args) @@ -65,7 +66,7 @@ module TestHelpers def extract_body(response) "".tap do |body| - response[2].each {|chunk| body << chunk } + response[2].each { |chunk| body << chunk } end end @@ -77,8 +78,8 @@ module TestHelpers resp = Array(resp) assert_equal 200, resp[0] - assert_match 'text/html', resp[1]["Content-Type"] - assert_match 'charset=utf-8', resp[1]["Content-Type"] + assert_match "text/html", resp[1]["Content-Type"] + assert_match "charset=utf-8", resp[1]["Content-Type"] assert extract_body(resp).match(/Yay! You.*re on Rails!/) end @@ -102,29 +103,24 @@ module TestHelpers module Generation # Build an application by invoking the generator and going through the whole stack. def build_app(options = {}) - @prev_rails_env = ENV['RAILS_ENV'] - ENV['RAILS_ENV'] = "development" - ENV['SECRET_KEY_BASE'] ||= SecureRandom.hex(16) + @prev_rails_env = ENV["RAILS_ENV"] + ENV["RAILS_ENV"] = "development" + ENV["SECRET_KEY_BASE"] ||= SecureRandom.hex(16) FileUtils.rm_rf(app_path) FileUtils.cp_r(app_template_path, app_path) # Delete the initializers unless requested unless options[:initializers] - Dir["#{app_path}/config/initializers/*.rb"].each do |initializer| + Dir["#{app_path}/config/initializers/**/*.rb"].each do |initializer| File.delete(initializer) end end - gemfile_path = "#{app_path}/Gemfile" - if options[:gemfile].blank? && File.exist?(gemfile_path) - File.delete gemfile_path - end - routes = File.read("#{app_path}/config/routes.rb") if routes =~ /(\n\s*end\s*)\Z/ - File.open("#{app_path}/config/routes.rb", 'w') do |f| - f.puts $` + "\nmatch ':controller(/:action(/:id))(.:format)', via: :all\n" + $1 + File.open("#{app_path}/config/routes.rb", "w") do |f| + f.puts $` + "\nActiveSupport::Deprecation.silence { match ':controller(/:action(/:id))(.:format)', via: :all }\n" + $1 end end @@ -154,12 +150,10 @@ module TestHelpers config.action_controller.allow_forgery_protection = false config.log_level = :info RUBY - - remove_from_env_config('development', 'config.file_watcher.*') end def teardown_app - ENV['RAILS_ENV'] = @prev_rails_env if @prev_rails_env + ENV["RAILS_ENV"] = @prev_rails_env if @prev_rails_env end # Make a very basic app, without creating the whole directory structure. @@ -168,7 +162,7 @@ module TestHelpers require "rails" require "action_controller/railtie" require "action_view/railtie" - require 'action_dispatch/middleware/flash' + require "action_dispatch/middleware/flash" @app = Class.new(Rails::Application) @app.config.eager_load = false @@ -185,7 +179,7 @@ module TestHelpers get "/" => "omg#index" end - require 'rack/test' + require "rack/test" extend ::Rack::Test::Methods end @@ -193,12 +187,12 @@ module TestHelpers controller :foo, <<-RUBY class FooController < ApplicationController def index - render text: "foo" + render plain: "foo" end end RUBY - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get ':controller(/:action)' end @@ -215,7 +209,7 @@ module TestHelpers def write(file, string) path = "#{@path}/#{file}" FileUtils.mkdir_p(File.dirname(path)) - File.open(path, "w") {|f| f.puts string } + File.open(path, "w") { |f| f.puts string } end def delete(file) @@ -231,7 +225,7 @@ module TestHelpers app.insert(2, "$:.unshift(\"#{dir}/lib\")") app.insert(3, "require #{name.inspect}") - File.open("#{app_path}/config/application.rb", 'r+') do |f| + File.open("#{app_path}/config/application.rb", "r+") do |f| f.puts app end @@ -249,7 +243,7 @@ module TestHelpers def add_to_top_of_config(str) environment = File.read("#{app_path}/config/application.rb") if environment =~ /(Rails::Application\s*)/ - File.open("#{app_path}/config/application.rb", 'w') do |f| + File.open("#{app_path}/config/application.rb", "w") do |f| f.puts $` + $1 + "\n#{str}\n" + $' end end @@ -258,7 +252,7 @@ module TestHelpers def add_to_config(str) environment = File.read("#{app_path}/config/application.rb") if environment =~ /(\n\s*end\s*end\s*)\Z/ - File.open("#{app_path}/config/application.rb", 'w') do |f| + File.open("#{app_path}/config/application.rb", "w") do |f| f.puts $` + "\n#{str}\n" + $1 end end @@ -267,7 +261,7 @@ module TestHelpers def add_to_env_config(env, str) environment = File.read("#{app_path}/config/environments/#{env}.rb") if environment =~ /(\n\s*end\s*)\Z/ - File.open("#{app_path}/config/environments/#{env}.rb", 'w') do |f| + File.open("#{app_path}/config/environments/#{env}.rb", "w") do |f| f.puts $` + "\n#{str}\n" + $1 end end @@ -283,11 +277,11 @@ module TestHelpers def remove_from_file(file, str) contents = File.read(file) - contents.sub!(/#{str}/, '') + contents.sub!(/#{str}/, "") File.write(file, contents) end - def app_file(path, contents, mode = 'w') + def app_file(path, contents, mode = "w") FileUtils.mkdir_p File.dirname("#{app_path}/#{path}") File.open("#{app_path}/#{path}", mode) do |f| f.puts contents @@ -306,17 +300,10 @@ module TestHelpers to_remove = [:actionmailer, :activerecord] - arr if to_remove.include?(:activerecord) - remove_from_config 'config.active_record.*' + remove_from_config "config.active_record.*" end - $:.reject! {|path| path =~ %r'/(#{to_remove.join('|')})/' } - end - - def boot_rails - # FIXME: shush Sass warning spam, not relevant to testing Railties - Kernel.silence_warnings do - require File.expand_path('../../../../load_paths', __FILE__) - end + $:.reject! { |path| path =~ %r'/(#{to_remove.join('|')})/' } end end end @@ -328,7 +315,6 @@ class ActiveSupport::TestCase include ActiveSupport::Testing::Stream self.test_order = :sorted - end # Create a scope and build a fixture rails app @@ -339,12 +325,8 @@ Module.new do FileUtils.rm_rf(app_template_path) FileUtils.mkdir(app_template_path) - environment = File.expand_path('../../../../load_paths', __FILE__) - require_environment = "-r #{environment}" - - `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/exe/rails new #{app_template_path} --skip-gemfile --no-rc` - File.open("#{app_template_path}/config/boot.rb", 'w') do |f| - f.puts "require '#{environment}'" + `#{Gem.ruby} #{RAILS_FRAMEWORK_ROOT}/railties/exe/rails new #{app_template_path} --skip-gemfile --skip-listen --no-rc` + File.open("#{app_template_path}/config/boot.rb", "w") do |f| f.puts "require 'rails/all'" end end unless defined?(RAILS_ISOLATED_ENGINE) diff --git a/railties/test/json_params_parsing_test.rb b/railties/test/json_params_parsing_test.rb new file mode 100644 index 0000000000..7fff3bb465 --- /dev/null +++ b/railties/test/json_params_parsing_test.rb @@ -0,0 +1,49 @@ +require "abstract_unit" +require "action_dispatch" +require "active_record" + +class JsonParamsParsingTest < ActionDispatch::IntegrationTest + def test_prevent_null_query + # Make sure we have data to find + klass = Class.new(ActiveRecord::Base) do + def self.name; "Foo"; end + establish_connection adapter: "sqlite3", database: ":memory:" + connection.create_table "foos" do |t| + t.string :title + t.timestamps null: false + end + end + klass.create + assert klass.first + + app = ->(env) { + request = ActionDispatch::Request.new env + params = ActionController::Parameters.new request.parameters + if params[:t] + klass.find_by_title(params[:t]) + else + nil + end + } + + assert_nil app.call(make_env("t" => nil)) + assert_nil app.call(make_env("t" => [nil])) + + [[[nil]], [[[nil]]]].each do |data| + assert_nil app.call(make_env("t" => data)) + end + ensure + klass.connection.drop_table("foos") + end + + private + def make_env(json) + data = JSON.dump json + content_length = data.length + { + "CONTENT_LENGTH" => content_length, + "CONTENT_TYPE" => "application/json", + "rack.input" => StringIO.new(data) + } + end +end diff --git a/railties/test/path_generation_test.rb b/railties/test/path_generation_test.rb index a16adc72a6..579e50ac95 100644 --- a/railties/test/path_generation_test.rb +++ b/railties/test/path_generation_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'active_support/core_ext/object/with_options' -require 'active_support/core_ext/object/json' +require "abstract_unit" +require "active_support/core_ext/object/with_options" +require "active_support/core_ext/object/json" class PathGenerationTest < ActiveSupport::TestCase attr_reader :app @@ -30,7 +30,7 @@ class PathGenerationTest < ActiveSupport::TestCase end def make_request(env) - Request.new super, self.url_helpers, @block + Request.new(super, url_helpers, @block) end end @@ -38,11 +38,11 @@ class PathGenerationTest < ActiveSupport::TestCase 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 } - params['SCRIPT_NAME'] = script_name if script_name + params["SCRIPT_NAME"] = script_name if script_name status, headers, body = app.call(params) new_body = [] @@ -72,11 +72,11 @@ class PathGenerationTest < ActiveSupport::TestCase url = URI("http://example.org/blogs") - send_request(url, 'GET', nil, '/FOO') - assert_equal '/FOO/blogs', app.instance.controller.blogs_path + send_request(url, "GET", nil, "/FOO") + assert_equal "/FOO/blogs", app.instance.controller.blogs_path - send_request(url, 'GET', nil) - assert_equal '/blogs', app.instance.controller.blogs_path + send_request(url, "GET", nil) + assert_equal "/blogs", app.instance.controller.blogs_path ensure Rails.logger = original_logger end diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb index 96b54c7264..7b2551062a 100644 --- a/railties/test/paths_test.rb +++ b/railties/test/paths_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'rails/paths' -require 'minitest/mock' +require "abstract_unit" +require "rails/paths" +require "minitest/mock" class PathsTest < ActiveSupport::TestCase def setup @@ -103,7 +103,7 @@ class PathsTest < ActiveSupport::TestCase @root.add "app", with: "/app" @root["app"].autoload_once! assert @root["app"].autoload_once? - assert @root.autoload_once.include?(@root["app"].expanded.first) + assert_includes @root.autoload_once, @root["app"].expanded.first end end @@ -114,14 +114,14 @@ class PathsTest < ActiveSupport::TestCase @root["app"].skip_autoload_once! assert !@root["app"].autoload_once? - assert !@root.autoload_once.include?(@root["app"].expanded.first) + assert_not_includes @root.autoload_once, @root["app"].expanded.first end test "it is possible to add a path without assignment and specify it should be loaded only once" do File.stub(:exist?, true) do @root.add "app", with: "/app", autoload_once: true assert @root["app"].autoload_once? - assert @root.autoload_once.include?("/app") + assert_includes @root.autoload_once, "/app" end end @@ -129,8 +129,8 @@ class PathsTest < ActiveSupport::TestCase File.stub(:exist?, true) do @root.add "app", with: ["/app", "/app2"], autoload_once: true assert @root["app"].autoload_once? - assert @root.autoload_once.include?("/app") - assert @root.autoload_once.include?("/app2") + assert_includes @root.autoload_once, "/app" + assert_includes @root.autoload_once, "/app2" end end @@ -139,7 +139,7 @@ class PathsTest < ActiveSupport::TestCase @root["app"] = "/app" @root["app"].autoload_once! @root["app"].autoload_once! - assert_equal 1, @root.autoload_once.select {|p| p == @root["app"].expanded.first }.size + assert_equal 1, @root.autoload_once.select { |p| p == @root["app"].expanded.first }.size end end @@ -157,7 +157,7 @@ class PathsTest < ActiveSupport::TestCase @root["app"] = "/app" @root["app"].eager_load! assert @root["app"].eager_load? - assert @root.eager_load.include?(@root["app"].to_a.first) + assert_includes @root.eager_load, @root["app"].to_a.first end end @@ -168,14 +168,14 @@ class PathsTest < ActiveSupport::TestCase @root["app"].skip_eager_load! assert !@root["app"].eager_load? - assert !@root.eager_load.include?(@root["app"].to_a.first) + assert_not_includes @root.eager_load, @root["app"].to_a.first end test "it is possible to add a path without assignment and mark it as eager" do File.stub(:exist?, true) do @root.add "app", with: "/app", eager_load: true assert @root["app"].eager_load? - assert @root.eager_load.include?("/app") + assert_includes @root.eager_load, "/app" end end @@ -183,8 +183,8 @@ class PathsTest < ActiveSupport::TestCase File.stub(:exist?, true) do @root.add "app", with: ["/app", "/app2"], eager_load: true assert @root["app"].eager_load? - assert @root.eager_load.include?("/app") - assert @root.eager_load.include?("/app2") + assert_includes @root.eager_load, "/app" + assert_includes @root.eager_load, "/app2" end end @@ -193,8 +193,8 @@ class PathsTest < ActiveSupport::TestCase @root.add "app", with: "/app", eager_load: true, autoload_once: true assert @root["app"].eager_load? assert @root["app"].autoload_once? - assert @root.eager_load.include?("/app") - assert @root.autoload_once.include?("/app") + assert_includes @root.eager_load, "/app" + assert_includes @root.autoload_once, "/app" end end @@ -203,7 +203,7 @@ class PathsTest < ActiveSupport::TestCase @root["app"] = "/app" @root["app"].eager_load! @root["app"].eager_load! - assert_equal 1, @root.eager_load.select {|p| p == @root["app"].expanded.first }.size + assert_equal 1, @root.eager_load.select { |p| p == @root["app"].expanded.first }.size end end diff --git a/railties/test/rack_logger_test.rb b/railties/test/rack_logger_test.rb index fcc79b57fb..7dd91a2465 100644 --- a/railties/test/rack_logger_test.rb +++ b/railties/test/rack_logger_test.rb @@ -1,8 +1,8 @@ -require 'abstract_unit' -require 'active_support/testing/autorun' -require 'active_support/test_case' -require 'rails/rack/logger' -require 'logger' +require "abstract_unit" +require "active_support/testing/autorun" +require "active_support/test_case" +require "rails/rack/logger" +require "logger" module Rails module Rack @@ -39,7 +39,7 @@ module Rails def setup @subscriber = Subscriber.new @notifier = ActiveSupport::Notifications.notifier - @subscription = notifier.subscribe 'request.action_dispatch', subscriber + @subscription = notifier.subscribe "request.action_dispatch", subscriber end def teardown @@ -47,11 +47,11 @@ module Rails end def test_notification - logger = TestLogger.new { } + logger = TestLogger.new {} - assert_difference('subscriber.starts.length') do - assert_difference('subscriber.finishes.length') do - logger.call('REQUEST_METHOD' => 'GET').last.close + assert_difference("subscriber.starts.length") do + assert_difference("subscriber.finishes.length") do + logger.call("REQUEST_METHOD" => "GET").last.close end end end @@ -62,10 +62,10 @@ module Rails raise NotImplementedError end - assert_difference('subscriber.starts.length') do - assert_difference('subscriber.finishes.length') do + assert_difference("subscriber.starts.length") do + assert_difference("subscriber.finishes.length") do assert_raises(NotImplementedError) do - logger.call 'REQUEST_METHOD' => 'GET' + logger.call "REQUEST_METHOD" => "GET" end end end diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb index c51503c2b7..d795629ccd 100644 --- a/railties/test/rails_info_controller_test.rb +++ b/railties/test/rails_info_controller_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" module ActionController class Base @@ -11,8 +11,8 @@ class InfoControllerTest < ActionController::TestCase def setup Rails.application.routes.draw do - get '/rails/info/properties' => "rails/info#properties" - get '/rails/info/routes' => "rails/info#routes" + get "/rails/info/properties" => "rails/info#properties" + get "/rails/info/routes" => "rails/info#routes" end @routes = Rails.application.routes @@ -30,7 +30,7 @@ class InfoControllerTest < ActionController::TestCase test "info controller renders an error message when request was forbidden" do @request.env["REMOTE_ADDR"] = "example.org" get :properties - assert_select 'p' + assert_select "p" end test "info controller allows requests when all requests are considered local" do @@ -45,7 +45,7 @@ class InfoControllerTest < ActionController::TestCase test "info controller renders a table with properties" do get :properties - assert_select 'table' + assert_select "table" end test "info controller renders with routes" do @@ -54,28 +54,34 @@ class InfoControllerTest < ActionController::TestCase end test "info controller returns exact matches" do - exact_count = -> { JSON(response.body)['exact'].size } + exact_count = -> { JSON(response.body)["exact"].size } - get :routes, params: { path: 'rails/info/route' } - assert exact_count.call == 0, 'should not match incomplete routes' + get :routes, params: { path: "rails/info/route" } + assert exact_count.call == 0, "should not match incomplete routes" - get :routes, params: { path: 'rails/info/routes' } - assert exact_count.call == 1, 'should match complete routes' + get :routes, params: { path: "rails/info/routes" } + assert exact_count.call == 1, "should match complete routes" - get :routes, params: { path: 'rails/info/routes.html' } - assert exact_count.call == 1, 'should match complete routes with optional parts' + get :routes, params: { path: "rails/info/routes.html" } + assert exact_count.call == 1, "should match complete routes with optional parts" end test "info controller returns fuzzy matches" do - fuzzy_count = -> { JSON(response.body)['fuzzy'].size } + fuzzy_count = -> { JSON(response.body)["fuzzy"].size } - get :routes, params: { path: 'rails/info' } - assert fuzzy_count.call == 2, 'should match incomplete routes' + get :routes, params: { path: "rails/info" } + assert fuzzy_count.call == 2, "should match incomplete routes" - get :routes, params: { path: 'rails/info/routes' } - assert fuzzy_count.call == 1, 'should match complete routes' + get :routes, params: { path: "rails/info/routes" } + assert fuzzy_count.call == 1, "should match complete routes" - get :routes, params: { path: 'rails/info/routes.html' } - assert fuzzy_count.call == 0, 'should match optional parts of route literally' + get :routes, params: { path: "rails/info/routes.html" } + assert fuzzy_count.call == 0, "should match optional parts of route literally" + end + + test "internal routes do not have a default params[:internal] value" do + get :properties + assert_response :success + assert_nil @controller.params[:internal] end end diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb index 92e4af25b5..59e79de41a 100644 --- a/railties/test/rails_info_test.rb +++ b/railties/test/rails_info_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" unless defined?(Rails) && defined?(Rails::Info) module Rails @@ -11,46 +11,46 @@ require "active_support/core_ext/kernel/reporting" class InfoTest < ActiveSupport::TestCase def setup Rails.send :remove_const, :Info - silence_warnings { load 'rails/info.rb' } + silence_warnings { load "rails/info.rb" } end def test_property_with_block_swallows_exceptions_and_ignores_property assert_nothing_raised do Rails::Info.module_eval do - property('Bogus') {raise} + property("Bogus") { raise } end end - assert !property_defined?('Bogus') + assert !property_defined?("Bogus") end def test_property_with_string Rails::Info.module_eval do - property 'Hello', 'World' + property "Hello", "World" end - assert_property 'Hello', 'World' + assert_property "Hello", "World" end def test_property_with_block Rails::Info.module_eval do - property('Goodbye') {'World'} + property("Goodbye") { "World" } end - assert_property 'Goodbye', 'World' + assert_property "Goodbye", "World" end def test_rails_version - assert_property 'Rails version', - File.read(File.realpath('../../../RAILS_VERSION', __FILE__)).chomp + assert_property "Rails version", + File.read(File.realpath("../../../RAILS_VERSION", __FILE__)).chomp end def test_html_includes_middleware Rails::Info.module_eval do - property 'Middleware', ['Rack::Lock', 'Rack::Static'] + property "Middleware", ["Rack::Lock", "Rack::Static"] end html = Rails::Info.to_html - assert html.include?('<tr><td class="name">Middleware</td>') - properties.value_for('Middleware').each do |value| - assert html.include?("<li>#{CGI.escapeHTML(value)}</li>") + assert_includes html, '<tr><td class="name">Middleware</td>' + properties.value_for("Middleware").each do |value| + assert_includes html, "<li>#{CGI.escapeHTML(value)}</li>" end end diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 4a47ab32b4..397037a394 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -4,7 +4,6 @@ require "rack/test" module RailtiesTest class EngineTest < ActiveSupport::TestCase - include ActiveSupport::Testing::Isolation include Rack::Test::Methods @@ -28,7 +27,6 @@ module RailtiesTest end def boot_rails - super require "#{app_path}/config/environment" end @@ -37,8 +35,6 @@ module RailtiesTest add_to_env_config "development", "config.assets.digest = false" boot_rails - require 'rack/test' - extend Rack::Test::Methods get "/assets/engine.js" assert_match "alert()", last_response.body @@ -102,7 +98,7 @@ module RailtiesTest assert_no_match(/2_create_users/, output.join("\n")) - bukkits_migration_order = output.index(output.detect{|o| /NOTE: Migration 3_create_sessions.rb from bukkits has been skipped/ =~ o }) + bukkits_migration_order = output.index(output.detect { |o| /NOTE: Migration 3_create_sessions.rb from bukkits has been skipped/ =~ o }) assert_not_nil bukkits_migration_order, "Expected migration to be skipped" migrations_count = Dir["#{app_path}/db/migrate/*.rb"].length @@ -112,7 +108,7 @@ module RailtiesTest end end - test 'respects the order of railties when installing migrations' do + test "respects the order of railties when installing migrations" do @blog = engine "blog" do |plugin| plugin.write "lib/blog.rb", <<-RUBY module Blog @@ -213,11 +209,11 @@ module RailtiesTest test "no rake task without migrations" do boot_rails - require 'rake' - require 'rdoc/task' - require 'rake/testtask' + require "rake" + require "rdoc/task" + require "rake/testtask" Rails.application.load_tasks - assert !Rake::Task.task_defined?('bukkits:install:migrations') + assert !Rake::Task.task_defined?("bukkits:install:migrations") end test "puts its lib directory on load path" do @@ -322,8 +318,6 @@ module RailtiesTest RUBY boot_rails - require 'rack/test' - extend Rack::Test::Methods get "/sprokkit" assert_equal "I am a Sprokkit", last_response.body @@ -333,7 +327,7 @@ module RailtiesTest controller "foo", <<-RUBY class FooController < ActionController::Base def index - render :text => "foo" + render plain: "foo" end end RUBY @@ -347,7 +341,7 @@ module RailtiesTest @plugin.write "app/controllers/bar_controller.rb", <<-RUBY class BarController < ActionController::Base def index - render :text => "bar" + render plain: "bar" end end RUBY @@ -360,14 +354,12 @@ module RailtiesTest RUBY boot_rails - require 'rack/test' - extend Rack::Test::Methods - get '/foo' - assert_equal 'foo', last_response.body + get "/foo" + assert_equal "foo", last_response.body - get '/bar' - assert_equal 'bar', last_response.body + get "/bar" + assert_equal "bar", last_response.body end test "rake tasks lib tasks are loaded" do @@ -379,9 +371,9 @@ module RailtiesTest RUBY boot_rails - require 'rake' - require 'rdoc/task' - require 'rake/testtask' + require "rake" + require "rdoc/task" + require "rake/testtask" Rails.application.load_tasks Rake::Task[:foo].invoke assert $executed @@ -392,18 +384,18 @@ module RailtiesTest config.i18n.load_path << "#{app_path}/app/locales/en.yml" RUBY - app_file 'app/locales/en.yml', <<-YAML + app_file "app/locales/en.yml", <<-YAML en: bar: "1" YAML - app_file 'config/locales/en.yml', <<-YAML + app_file "config/locales/en.yml", <<-YAML en: foo: "2" bar: "2" YAML - @plugin.write 'config/locales/en.yml', <<-YAML + @plugin.write "config/locales/en.yml", <<-YAML en: foo: "3" YAML @@ -444,14 +436,12 @@ YAML @plugin.write "app/controllers/admin/foo/bar_controller.rb", <<-RUBY class Admin::Foo::BarController < ApplicationController def index - render text: "Rendered from namespace" + render plain: "Rendered from namespace" end end RUBY boot_rails - require 'rack/test' - extend Rack::Test::Methods get "/admin/foo/bar" assert_equal 200, last_response.status @@ -481,7 +471,7 @@ YAML end RUBY - add_to_config "config.middleware.use \"Bukkits\"" + add_to_config "config.middleware.use Bukkits" boot_rails end @@ -546,7 +536,7 @@ YAML controller "foo", <<-RUBY class FooController < ActionController::Base def index - render text: params[:username] + render plain: params[:username] end end RUBY @@ -637,11 +627,11 @@ YAML env = Rack::MockRequest.env_for("/") Bukkits::Engine.call(env) - assert_equal Bukkits::Engine.routes, env['action_dispatch.routes'] + assert_equal Bukkits::Engine.routes, env["action_dispatch.routes"] env = Rack::MockRequest.env_for("/") Rails.application.call(env) - assert_equal Rails.application.routes, env['action_dispatch.routes'] + assert_equal Rails.application.routes, env["action_dispatch.routes"] end test "isolated engine should include only its own routes and helpers" do @@ -710,7 +700,7 @@ YAML end def show - render text: foo_path + render plain: foo_path end def from_app @@ -722,7 +712,7 @@ YAML end def polymorphic_path_without_namespace - render text: polymorphic_path(Post.new) + render plain: polymorphic_path(Post.new) end end RUBY @@ -845,7 +835,7 @@ YAML @plugin.write "app/controllers/bukkits/awesome/foo_controller.rb", <<-RUBY class Bukkits::Awesome::FooController < ActionController::Base def index - render :text => "ok" + render plain: "ok" end end RUBY @@ -1028,7 +1018,7 @@ YAML # check expanding paths engine_dir = @plugin.path.chomp("/").split("/").last - engine_path = File.join(@plugin.path, '..', engine_dir) + engine_path = File.join(@plugin.path, "..", engine_dir) assert_equal Bukkits::Engine.instance, Rails::Engine.find(engine_path) end @@ -1155,10 +1145,10 @@ YAML assert_equal "App's bar partial", last_response.body.strip get("/assets/foo.js") - assert_equal "// Bukkit's foo js", last_response.body.strip + assert_match "// Bukkit's foo js", last_response.body.strip get("/assets/bar.js") - assert_equal "// App's bar js", last_response.body.strip + assert_match "// App's bar js", last_response.body.strip # ensure that railties are not added twice railties = Rails.application.send(:ordered_railties).map(&:class) @@ -1230,13 +1220,12 @@ YAML fullpath: \#{request.fullpath} path: \#{request.path} TEXT - render text: text + render plain: text end end end RUBY - app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do mount Bukkits::Engine => "/" @@ -1268,7 +1257,7 @@ YAML app_file "app/controllers/bar_controller.rb", <<-RUBY class BarController < ApplicationController def index - render text: bukkits.bukkit_path + render plain: bukkits.bukkit_path end end RUBY @@ -1286,22 +1275,21 @@ YAML end RUBY - @plugin.write "app/controllers/bukkits/bukkit_controller.rb", <<-RUBY class Bukkits::BukkitController < ActionController::Base def index - render text: main_app.bar_path + render plain: main_app.bar_path end end RUBY boot_rails - get("/bukkits/bukkit", {}, {'SCRIPT_NAME' => '/foo'}) - assert_equal '/foo/bar', last_response.body + get("/bukkits/bukkit", {}, "SCRIPT_NAME" => "/foo") + assert_equal "/foo/bar", last_response.body - get("/bar", {}, {'SCRIPT_NAME' => '/foo'}) - assert_equal '/foo/bukkits/bukkit', last_response.body + get("/bar", {}, "SCRIPT_NAME" => "/foo") + assert_equal "/foo/bukkits/bukkit", last_response.body end test "paths are properly generated when application is mounted at sub-path and relative_url_root is set" do @@ -1318,7 +1306,7 @@ YAML app_file "app/controllers/bar_controller.rb", <<-RUBY class BarController < ApplicationController def index - render text: bukkits.bukkit_path + render plain: bukkits.bukkit_path end end RUBY @@ -1339,18 +1327,18 @@ YAML @plugin.write "app/controllers/bukkits/bukkit_controller.rb", <<-RUBY class Bukkits::BukkitController < ActionController::Base def index - render text: main_app.bar_path + render plain: main_app.bar_path end end RUBY boot_rails - get("/bukkits/bukkit", {}, {'SCRIPT_NAME' => '/foo'}) - assert_equal '/foo/bar', last_response.body + get("/bukkits/bukkit", {}, "SCRIPT_NAME" => "/foo") + assert_equal "/foo/bar", last_response.body - get("/bar", {}, {'SCRIPT_NAME' => '/foo'}) - assert_equal '/foo/bukkits/bukkit', last_response.body + get("/bar", {}, "SCRIPT_NAME" => "/foo") + assert_equal "/foo/bukkits/bukkit", last_response.body end private diff --git a/railties/test/railties/generators_test.rb b/railties/test/railties/generators_test.rb index 5f4171d44b..732898d0c0 100644 --- a/railties/test/railties/generators_test.rb +++ b/railties/test/railties/generators_test.rb @@ -1,7 +1,7 @@ RAILS_ISOLATED_ENGINE = true require "isolation/abstract_unit" -require 'generators/generators_test_helper' +require "generators/generators_test_helper" require "rails/generators/test_case" module RailtiesTests @@ -9,7 +9,7 @@ module RailtiesTests include ActiveSupport::Testing::Isolation def destination_root - tmp_path 'foo_bar' + tmp_path "foo_bar" end def tmp_path(*args) @@ -18,7 +18,7 @@ module RailtiesTests end def engine_path - tmp_path('foo_bar') + tmp_path("foo_bar") end def bundled_rails(cmd) @@ -26,11 +26,7 @@ module RailtiesTests end def rails(cmd) - environment = File.expand_path('../../../../load_paths', __FILE__) - if File.exist?("#{environment}.rb") - require_environment = "-r #{environment}" - end - `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/exe/rails #{cmd}` + `#{Gem.ruby} #{RAILS_FRAMEWORK_ROOT}/railties/exe/rails #{cmd}` end def build_engine(is_mountable=false) @@ -43,7 +39,7 @@ module RailtiesTests Dir.chdir(engine_path) do File.open("Gemfile", "w") do |f| - f.write <<-GEMFILE.gsub(/^ {12}/, '') + f.write <<-GEMFILE.gsub(/^ {12}/, "") source "https://rubygems.org" gem 'rails', path: '#{RAILS_FRAMEWORK_ROOT}' diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb index fb2071c7c3..5838d0d7e7 100644 --- a/railties/test/railties/mounted_engine_test.rb +++ b/railties/test/railties/mounted_engine_test.rb @@ -1,10 +1,10 @@ -require 'isolation/abstract_unit' +require "isolation/abstract_unit" module ApplicationTests class ApplicationRoutingTest < ActiveSupport::TestCase - require 'rack/test' - include Rack::Test::Methods - include ActiveSupport::Testing::Isolation + require "rack/test" + include Rack::Test::Methods + include ActiveSupport::Testing::Isolation def setup build_app @@ -15,7 +15,7 @@ module ApplicationTests @plugin = engine "blog" @metrics_plugin = engine "metrics" - app_file 'config/routes.rb', <<-RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do mount Weblog::Engine, :at => '/', :as => 'weblog' resources :posts @@ -34,7 +34,6 @@ module ApplicationTests end RUBY - @simple_plugin.write "lib/weblog.rb", <<-RUBY module Weblog class Engine < ::Rails::Engine @@ -51,7 +50,7 @@ module ApplicationTests @simple_plugin.write "app/controllers/weblogs_controller.rb", <<-RUBY class WeblogsController < ActionController::Base def index - render text: request.url + render plain: request.url end end RUBY @@ -75,7 +74,7 @@ module ApplicationTests module Metrics class GeneratingController < ActionController::Base def generate_blog_route - render text: blog.post_path(1) + render plain: blog.post_path(1) end def generate_blog_route_in_view @@ -123,14 +122,14 @@ module ApplicationTests module Blog class PostsController < ActionController::Base def index - render text: blog.post_path(1) + render plain: blog.post_path(1) end def generate_application_route path = main_app.url_for(controller: "/main", action: "index", only_path: true) - render text: path + render plain: path end def application_route_in_view @@ -138,7 +137,7 @@ module ApplicationTests end def engine_polymorphic_path - render text: polymorphic_path(Post.new) + render plain: polymorphic_path(Post.new) end def engine_asset_path @@ -151,7 +150,7 @@ module ApplicationTests app_file "app/controllers/application_generating_controller.rb", <<-RUBY class ApplicationGeneratingController < ActionController::Base def engine_route - render text: blog.posts_path + render plain: blog.posts_path end def engine_route_in_view @@ -159,7 +158,7 @@ module ApplicationTests end def weblog_engine_route - render text: weblog.weblogs_path + render plain: weblog.weblogs_path end def weblog_engine_route_in_view @@ -167,20 +166,18 @@ module ApplicationTests end def url_for_engine_route - render text: blog.url_for(controller: "blog/posts", action: "index", user: "john", only_path: true) + render plain: blog.url_for(controller: "blog/posts", action: "index", user: "john", only_path: true) end def polymorphic_route - render text: polymorphic_url([blog, Blog::Post.new]) + render plain: polymorphic_url([blog, Blog::Post.new]) end def application_polymorphic_path - render text: polymorphic_path(Blog::Post.new) + render plain: polymorphic_path(Blog::Post.new) end end RUBY - - boot_rails end def teardown @@ -200,7 +197,7 @@ module ApplicationTests assert_equal "/john/blog/posts/1", last_response.body # test generating engine's route from engine with default_url_options - get "/john/blog/posts", {}, 'SCRIPT_NAME' => "/foo" + get "/john/blog/posts", {}, "SCRIPT_NAME" => "/foo" assert_equal "/foo/john/blog/posts/1", last_response.body # test generating engine's route from application @@ -214,10 +211,10 @@ module ApplicationTests assert_equal "/john/blog/posts", last_response.body # test generating engine's route from application with default_url_options - get "/engine_route", {}, 'SCRIPT_NAME' => "/foo" + get "/engine_route", {}, "SCRIPT_NAME" => "/foo" assert_equal "/foo/anonymous/blog/posts", last_response.body - get "/url_for_engine_route", {}, 'SCRIPT_NAME' => "/foo" + get "/url_for_engine_route", {}, "SCRIPT_NAME" => "/foo" assert_equal "/foo/john/blog/posts", last_response.body # test generating application's route from engine @@ -229,21 +226,20 @@ module ApplicationTests # test generating engine's route from other engine get "/metrics/generate_blog_route" - assert_equal '/anonymous/blog/posts/1', last_response.body + assert_equal "/anonymous/blog/posts/1", last_response.body get "/metrics/generate_blog_route_in_view" - assert_equal '/anonymous/blog/posts/1', last_response.body + assert_equal "/anonymous/blog/posts/1", last_response.body # test generating engine's route from other engine with default_url_options - get "/metrics/generate_blog_route", {}, 'SCRIPT_NAME' => '/foo' - assert_equal '/foo/anonymous/blog/posts/1', last_response.body - - get "/metrics/generate_blog_route_in_view", {}, 'SCRIPT_NAME' => '/foo' - assert_equal '/foo/anonymous/blog/posts/1', last_response.body + get "/metrics/generate_blog_route", {}, "SCRIPT_NAME" => "/foo" + assert_equal "/foo/anonymous/blog/posts/1", last_response.body + get "/metrics/generate_blog_route_in_view", {}, "SCRIPT_NAME" => "/foo" + assert_equal "/foo/anonymous/blog/posts/1", last_response.body # test generating application's route from engine with default_url_options - get "/someone/blog/generate_application_route", {}, 'SCRIPT_NAME' => '/foo' + get "/someone/blog/generate_application_route", {}, "SCRIPT_NAME" => "/foo" assert_equal "/foo/", last_response.body # test polymorphic routes diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb index 5042d628cf..30cd525266 100644 --- a/railties/test/railties/railtie_test.rb +++ b/railties/test/railties/railtie_test.rb @@ -6,7 +6,6 @@ module RailtiesTest def setup build_app - boot_rails FileUtils.rm_rf("#{app_path}/config/environments") require "rails/all" end @@ -80,6 +79,13 @@ module RailtiesTest assert_equal app_path, $before_configuration end + test "before_configuration callbacks run as soon as the application constant inherits from Rails::Application" do + $before_configuration = false + class Foo < Rails::Railtie ; config.before_configuration { $before_configuration = true } ; end + class Application < Rails::Application ; end + assert $before_configuration + end + test "railtie can add after_initialize callbacks" do $after_initialize = false class Foo < Rails::Railtie ; config.after_initialize { $after_initialize = true } ; end @@ -100,9 +106,9 @@ module RailtiesTest require "#{app_path}/config/environment" assert !$ran_block - require 'rake' - require 'rake/testtask' - require 'rdoc/task' + require "rake" + require "rake/testtask" + require "rdoc/task" Rails.application.load_tasks assert $ran_block @@ -124,12 +130,12 @@ module RailtiesTest require "#{app_path}/config/environment" assert_equal [], $ran_block - require 'rake' - require 'rake/testtask' - require 'rdoc/task' + require "rake" + require "rake/testtask" + require "rdoc/task" Rails.application.load_tasks - assert $ran_block.include?("my_tie") + assert_includes $ran_block, "my_tie" end test "generators block is executed when MyApp.load_generators is called" do @@ -197,8 +203,8 @@ module RailtiesTest test "we can change our environment if we want to" do begin original_env = Rails.env - Rails.env = 'foo' - assert_equal('foo', Rails.env) + Rails.env = "foo" + assert_equal("foo", Rails.env) ensure Rails.env = original_env assert_equal(original_env, Rails.env) diff --git a/railties/test/test_unit/reporter_test.rb b/railties/test/test_unit/reporter_test.rb index 2d08d4ec30..e22c939981 100644 --- a/railties/test/test_unit/reporter_test.rb +++ b/railties/test/test_unit/reporter_test.rb @@ -1,6 +1,6 @@ -require 'abstract_unit' -require 'rails/test_unit/reporter' -require 'minitest/mock' +require "abstract_unit" +require "rails/test_unit/reporter" +require "minitest/mock" class TestUnitReporterTest < ActiveSupport::TestCase class ExampleTest < Minitest::Test @@ -33,7 +33,7 @@ class TestUnitReporterTest < ActiveSupport::TestCase @reporter.record(passing_test) @reporter.record(skipped_test) @reporter.report - assert_no_match 'Failed tests:', @output.string + assert_no_match "Failed tests:", @output.string assert_rerun_snippet_count 0 end @@ -62,7 +62,7 @@ class TestUnitReporterTest < ActiveSupport::TestCase @reporter.record(failed_test) @reporter.report - expect = %r{\AF\n\nFailure:\nTestUnitReporterTest::ExampleTest#woot:\nboo\n\nbin/rails test test/test_unit/reporter_test.rb:\d+\n\n\z} + expect = %r{\AF\n\nFailure:\nTestUnitReporterTest::ExampleTest#woot \[[^\]]+\]:\nboo\n\nbin/rails test test/test_unit/reporter_test.rb:\d+\n\n\z} assert_match expect, @output.string end @@ -79,7 +79,7 @@ class TestUnitReporterTest < ActiveSupport::TestCase verbose.record(skipped_test) verbose.report - expect = %r{\ATestUnitReporterTest::ExampleTest#woot = 10\.00 s = S\n\n\nSkipped:\nTestUnitReporterTest::ExampleTest#woot:\nskipchurches, misstemples\n\nbin/rails test test/test_unit/reporter_test.rb:\d+\n\n\z} + expect = %r{\ATestUnitReporterTest::ExampleTest#woot = 10\.00 s = S\n\n\nSkipped:\nTestUnitReporterTest::ExampleTest#woot \[[^\]]+\]:\nskipchurches, misstemples\n\nbin/rails test test/test_unit/reporter_test.rb:\d+\n\n\z} assert_match expect, @output.string end @@ -87,7 +87,7 @@ class TestUnitReporterTest < ActiveSupport::TestCase @reporter.record(failed_test) @reporter.report - assert_no_match 'Failed tests:', @output.string + assert_no_match "Failed tests:", @output.string end test "fail fast interrupts run on failure" do @@ -100,18 +100,29 @@ class TestUnitReporterTest < ActiveSupport::TestCase rescue Interrupt interrupt_raised = true ensure - assert interrupt_raised, 'Expected Interrupt to be raised.' + assert interrupt_raised, "Expected Interrupt to be raised." end end - test "fail fast does not interrupt run errors or skips" do + test "fail fast interrupts run on error" do fail_fast = Rails::TestUnitReporter.new @output, fail_fast: true + interrupt_raised = false + + # Minitest passes through Interrupt, catch it manually. + begin + fail_fast.record(errored_test) + rescue Interrupt + interrupt_raised = true + ensure + assert interrupt_raised, "Expected Interrupt to be raised." + end + end - fail_fast.record(errored_test) - assert_no_match 'Failed tests:', @output.string + test "fail fast does not interrupt run skips" do + fail_fast = Rails::TestUnitReporter.new @output, fail_fast: true fail_fast.record(skipped_test) - assert_no_match 'Failed tests:', @output.string + assert_no_match "Failed tests:", @output.string end test "outputs colored passing results" do @@ -145,38 +156,38 @@ class TestUnitReporterTest < ActiveSupport::TestCase end private - def assert_rerun_snippet_count(snippet_count) - assert_equal snippet_count, @output.string.scan(%r{^bin/rails test }).size - end + def assert_rerun_snippet_count(snippet_count) + assert_equal snippet_count, @output.string.scan(%r{^bin/rails test }).size + end - def failed_test - ft = ExampleTest.new(:woot) - ft.failures << begin - raise Minitest::Assertion, "boo" - rescue Minitest::Assertion => e - e - end - ft - end + def failed_test + ft = ExampleTest.new(:woot) + ft.failures << begin + raise Minitest::Assertion, "boo" + rescue Minitest::Assertion => e + e + end + ft + end - def errored_test - et = ExampleTest.new(:woot) - et.failures << Minitest::UnexpectedError.new(ArgumentError.new("wups")) - et - end + def errored_test + et = ExampleTest.new(:woot) + et.failures << Minitest::UnexpectedError.new(ArgumentError.new("wups")) + et + end - def passing_test - ExampleTest.new(:woot) - end + def passing_test + ExampleTest.new(:woot) + end - def skipped_test - st = ExampleTest.new(:woot) - st.failures << begin - raise Minitest::Skip, "skipchurches, misstemples" - rescue Minitest::Assertion => e - e - end - st.time = 10 - st - end + def skipped_test + st = ExampleTest.new(:woot) + st.failures << begin + raise Minitest::Skip, "skipchurches, misstemples" + rescue Minitest::Assertion => e + e + end + st.time = 10 + st + end end diff --git a/railties/test/version_test.rb b/railties/test/version_test.rb index f270d8f0c9..86a482e091 100644 --- a/railties/test/version_test.rb +++ b/railties/test/version_test.rb @@ -1,4 +1,4 @@ -require 'abstract_unit' +require "abstract_unit" class VersionTest < ActiveSupport::TestCase def test_rails_version_returns_a_string diff --git a/tasks/release.rb b/tasks/release.rb index de9c51a140..d1717cec52 100644 --- a/tasks/release.rb +++ b/tasks/release.rb @@ -1,19 +1,18 @@ FRAMEWORKS = %w( activesupport activemodel activerecord actionview actionpack activejob actionmailer actioncable railties ) -root = File.expand_path('../../', __FILE__) +root = File.expand_path("../../", __FILE__) version = File.read("#{root}/RAILS_VERSION").strip tag = "v#{version}" directory "pkg" -(FRAMEWORKS + ['rails']).each do |framework| +(FRAMEWORKS + ["rails"]).each do |framework| namespace framework do gem = "pkg/#{framework}-#{version}.gem" gemspec = "#{framework}.gemspec" task :clean do rm_f gem - sh "cd #{framework} && bundle exec rake package:clean" unless framework == "rails" end task :update_versions do @@ -28,7 +27,7 @@ directory "pkg" file = Dir[glob].first ruby = File.read(file) - major, minor, tiny, pre = version.split('.', 4) + major, minor, tiny, pre = version.split(".", 4) pre = pre ? pre.inspect : "nil" ruby.gsub!(/^(\s*)MAJOR(\s*)= .*?$/, "\\1MAJOR = #{major}") @@ -43,7 +42,7 @@ directory "pkg" ruby.gsub!(/^(\s*)PRE(\s*)= .*?$/, "\\1PRE = #{pre}") raise "Could not insert PRE in #{file}" unless $1 - File.open(file, 'w') { |f| f.write ruby } + File.open(file, "w") { |f| f.write ruby } end task gem => %w(update_versions pkg) do @@ -54,44 +53,79 @@ directory "pkg" sh cmd end - task :build => [:clean, gem] - task :install => :build do - sh "gem install #{gem}" + task build: [:clean, gem] + task install: :build do + sh "gem install --pre #{gem}" end - task :push => :build do + task push: :build do sh "gem push #{gem}" + + # When running the release task we usually run build first to check that the gem works properly. + # NPM will refuse to publish or rebuild the gem if the version is changed when the Rails gem + # versions are changed. This then causes the gem push to fail. Because of this we need to update + # the version and publish at the same time. + if File.exist?("#{framework}/package.json") + Dir.chdir("#{framework}") do + # This "npm-ifies" the current version + # With npm, versions such as "5.0.0.rc1" or "5.0.0.beta1.1" are not compliant with its + # versioning system, so they must be transformed to "5.0.0-rc1" and "5.0.0-beta1-1" respectively. + + # In essence, the code below runs through all "."s that appear in the version, + # and checks to see if their index in the version string is greater than or equal to 2, + # and if so, it will change the "." to a "-". + + # Sample version transformations: + # irb(main):001:0> version = "5.0.1.1" + # => "5.0.1.1" + # irb(main):002:0> version.gsub(/\./).with_index { |s, i| i >= 2 ? '-' : s } + # => "5.0.1-1" + # irb(main):003:0> version = "5.0.0.rc1" + # => "5.0.0.rc1" + # irb(main):004:0> version.gsub(/\./).with_index { |s, i| i >= 2 ? '-' : s } + # => "5.0.0-rc1" + version = version.gsub(/\./).with_index { |s, i| i >= 2 ? "-" : s } + + # Check if npm is installed, and raise an error if not + if sh "which npm" + sh "npm version #{version} --no-git-tag-version" + sh "npm publish" + else + raise "You must have npm installed to release Rails." + end + end + end end end end namespace :changelog do task :header do - (FRAMEWORKS + ['guides']).each do |fw| - require 'date' - fname = File.join fw, 'CHANGELOG.md' + (FRAMEWORKS + ["guides"]).each do |fw| + require "date" + fname = File.join fw, "CHANGELOG.md" header = "## Rails #{version} (#{Date.today.strftime('%B %d, %Y')}) ##\n\n* No changes.\n\n\n" contents = header + File.read(fname) - File.open(fname, 'wb') { |f| f.write contents } + File.open(fname, "wb") { |f| f.write contents } end end task :release_date do - (FRAMEWORKS + ['guides']).each do |fw| - require 'date' + (FRAMEWORKS + ["guides"]).each do |fw| + require "date" replace = "## Rails #{version} (#{Date.today.strftime('%B %d, %Y')}) ##\n" - fname = File.join fw, 'CHANGELOG.md' + fname = File.join fw, "CHANGELOG.md" contents = File.read(fname).sub(/^(## Rails .*)\n/, replace) - File.open(fname, 'wb') { |f| f.write contents } + File.open(fname, "wb") { |f| f.write contents } end end task :release_summary do - (FRAMEWORKS + ['guides']).each do |fw| + (FRAMEWORKS + ["guides"]).each do |fw| puts "## #{fw}" - fname = File.join fw, 'CHANGELOG.md' + fname = File.join fw, "CHANGELOG.md" contents = File.readlines fname contents.shift changes = [] @@ -103,28 +137,28 @@ namespace :changelog do end namespace :all do - task :build => FRAMEWORKS.map { |f| "#{f}:build" } + ['rails:build'] - task :update_versions => FRAMEWORKS.map { |f| "#{f}:update_versions" } + ['rails:update_versions'] - task :install => FRAMEWORKS.map { |f| "#{f}:install" } + ['rails:install'] - task :push => FRAMEWORKS.map { |f| "#{f}:push" } + ['rails:push'] + task build: FRAMEWORKS.map { |f| "#{f}:build" } + ["rails:build"] + task update_versions: FRAMEWORKS.map { |f| "#{f}:update_versions" } + ["rails:update_versions"] + task install: FRAMEWORKS.map { |f| "#{f}:install" } + ["rails:install"] + task push: FRAMEWORKS.map { |f| "#{f}:push" } + ["rails:push"] task :ensure_clean_state do unless `git status -s | grep -v 'RAILS_VERSION\\|CHANGELOG\\|Gemfile.lock'`.strip.empty? abort "[ABORTING] `git status` reports a dirty tree. Make sure all changes are committed" end - unless ENV['SKIP_TAG'] || `git tag | grep '^#{tag}$'`.strip.empty? + unless ENV["SKIP_TAG"] || `git tag | grep '^#{tag}$'`.strip.empty? abort "[ABORTING] `git tag` shows that #{tag} already exists. Has this version already\n"\ " been released? Git tagging can be skipped by setting SKIP_TAG=1" end end task :bundle do - sh 'bundle check' + sh "bundle check" end task :commit do - File.open('pkg/commit_message.txt', 'w') do |f| + File.open("pkg/commit_message.txt", "w") do |f| f.puts "# Preparing for #{version} release\n" f.puts f.puts "# UNCOMMENT THE LINE ABOVE TO APPROVE THIS COMMIT" @@ -135,11 +169,11 @@ namespace :all do end task :tag do - sh "git tag -m '#{tag} release' #{tag}" + sh "git tag -s -m '#{tag} release' #{tag}" sh "git push --tags" end - task :prep_release => %w(ensure_clean_state build) + task prep_release: %w(ensure_clean_state build) - task :release => %w(ensure_clean_state build bundle commit tag push) + task release: %w(ensure_clean_state build bundle commit tag push) end diff --git a/tools/README.md b/tools/README.md index 25ab798bd5..b2e7e4b0ae 100644 --- a/tools/README.md +++ b/tools/README.md @@ -1,7 +1,8 @@ -## Rails dev tools +# Rails dev tools This is a collection of utilities used for Rails internal development. They aren't used by Rails apps directly. * `console` drops you in irb and loads local Rails repos * `profile` profiles `Kernel#require` to help reduce startup time + * `line_statistics` provides CodeTools module and LineStatistics class to count lines diff --git a/tools/console b/tools/console index ea995a1a54..58c76fb1b6 100755 --- a/tools/console +++ b/tools/console @@ -1,7 +1,9 @@ #!/usr/bin/env ruby -require File.expand_path('../../load_paths', __FILE__) -require 'rails/all' -require 'active_support/all' -require 'irb' -require 'irb/completion' +require "bundler" +Bundler.setup + +require "rails/all" +require "active_support/all" +require "irb" +require "irb/completion" IRB.start diff --git a/tools/profile b/tools/profile index 191e73b3dd..f7d91e51cf 100755 --- a/tools/profile +++ b/tools/profile @@ -4,8 +4,8 @@ # # Example: # tools/profile activesupport/lib/active_support.rb [ruby-prof mode] [ruby-prof printer] -ENV['NO_RELOAD'] ||= '1' -ENV['RAILS_ENV'] ||= 'development' +ENV["NO_RELOAD"] ||= "1" +ENV["RAILS_ENV"] ||= "development" module CodeTools class Profiler @@ -15,7 +15,7 @@ module CodeTools def initialize(path, mode=nil) assert_ruby_file_exists(path) @path, @mode = path, mode - require 'benchmark' + require "benchmark" end def profile_requires @@ -23,7 +23,7 @@ module CodeTools before_rss = `ps -o rss= -p #{Process.pid}`.to_i if mode - require 'ruby-prof' + require "ruby-prof" RubyProf.measure_mode = RubyProf.const_get(mode.upcase) RubyProf.start else @@ -43,13 +43,13 @@ module CodeTools elsif RubyProf.const_defined?(:CallStackPrinter) filename = "#{File.basename(path, '.rb')}.#{mode}.html" puts "RubyProf outputting to #{filename}" - File.open(filename, 'w') do |out| + File.open(filename, "w") do |out| RubyProf::CallStackPrinter.new(results).print(out) end else filename = "#{File.basename(path, '.rb')}.#{mode}.callgrind" puts "RubyProf outputting to #{filename}" - File.open(filename, 'w') do |out| + File.open(filename, "w") do |out| RubyProf::CallTreePrinter.new(results).print(out) end end @@ -57,7 +57,7 @@ module CodeTools RequireProfiler.stats.each do |file, depth, sec| if sec - puts "%8.1f ms %s%s" % [sec * 1000, ' ' * depth, file] + puts "%8.1f ms %s%s" % [sec * 1000, " " * depth, file] else puts "#{' ' * (13 + depth)}#{file}" end @@ -67,51 +67,51 @@ module CodeTools private - def assert_ruby_file_exists(path) - fail Error.new("No such file") unless File.exist?(path) - fail Error.new("#{path} is a directory") if File.directory?(path) - ruby_extension = File.extname(path) == '.rb' - ruby_executable = File.open(path, 'rb') {|f| f.readline } =~ [/\A#!.*ruby/] - fail Error.new("Not a ruby file") unless ruby_extension or ruby_executable - end + def assert_ruby_file_exists(path) + fail Error.new("No such file") unless File.exist?(path) + fail Error.new("#{path} is a directory") if File.directory?(path) + ruby_extension = File.extname(path) == ".rb" + ruby_executable = File.open(path, "rb") { |f| f.readline } =~ [/\A#!.*ruby/] + fail Error.new("Not a ruby file") unless ruby_extension || ruby_executable + end - module RequireProfiler - private - def require(file, *args) RequireProfiler.profile(file) { super } end - def load(file, *args) RequireProfiler.profile(file) { super } end + module RequireProfiler + private + def require(file, *args) RequireProfiler.profile(file) { super } end + def load(file, *args) RequireProfiler.profile(file) { super } end - @depth, @stats = 0, [] - class << self - attr_accessor :depth - attr_accessor :stats + @depth, @stats = 0, [] + class << self + attr_accessor :depth + attr_accessor :stats - def profile(file) - stats << [file, depth] - self.depth += 1 - result = nil - elapsed = Benchmark.realtime { result = yield } - self.depth -= 1 - stats.pop if stats.last.first == file - stats << [file, depth, elapsed] if result - result - end + def profile(file) + stats << [file, depth] + self.depth += 1 + result = nil + elapsed = Benchmark.realtime { result = yield } + self.depth -= 1 + stats.pop if stats.last.first == file + stats << [file, depth, elapsed] if result + result + end + end end - end end end # ruby-prof printer name causes the third arg to be sent :classify # which is probably overkill if you already know the name of the ruby-prof # printer you want to use, e.g. Graph begin - require 'active_support/inflector' - require 'active_support/core_ext/string/inflections' + require "active_support/inflector" + require "active_support/core_ext/string/inflections" rescue LoadError STDERR.puts $!.message class String # File activesupport/lib/active_support/inflector/methods.rb, line 150 def classify # strip out any leading schema name - camelize(self.sub(/.*\./, '')) + camelize(sub(/.*\./, "")) end # File activesupport/lib/active_support/inflector/methods.rb, line 68 def camelize(uppercase_first_letter = true) @@ -121,7 +121,7 @@ rescue LoadError else string = string.sub(/^(?:(?=\b|[A-Z_])|\w)/) { |match| match.downcase } end - string.gsub(/(?:_|(\/))([a-z\d]*)/) { "#{$1}#{$2.capitalize}" }.gsub('/', '::') + string.gsub(/(?:_|(\/))([a-z\d]*)/) { "#{$1}#{$2.capitalize}" }.gsub("/", "::") end end end diff --git a/tools/test.rb b/tools/test.rb index 70f295b554..7819c13ee2 100644 --- a/tools/test.rb +++ b/tools/test.rb @@ -1,5 +1,8 @@ $: << File.expand_path("test", COMPONENT_ROOT) -require File.expand_path("../../load_paths", __FILE__) + +require "bundler" +Bundler.setup + require "rails/test_unit/minitest_plugin" module Rails diff --git a/version.rb b/version.rb index 93e0151602..9c49e0655a 100644 --- a/version.rb +++ b/version.rb @@ -6,9 +6,9 @@ module Rails module VERSION MAJOR = 5 - MINOR = 0 + MINOR = 1 TINY = 0 - PRE = "beta2" + PRE = "alpha" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end |